Я пытаюсь узнать больше о SIP и пытаюсь реализовать небольшие части протокола в node.js с помощью UDP.
пока у меня это
const dgram = require('dgram');
const crypto = require('crypto');
const asteriskIP = 'ASTERISK_IP';
const asteriskPort = 6111;
const clientIP = '192.168.1.2';
const clientPort = 6111;
const username = 'USERNAME';
const password = 'PASSWORD';
// Create a UDP socket
const socket = dgram.createSocket('udp4');
// Generate a random branch identifier for the Via header
const generateBranch = () => {
const branchId = Math.floor(Math.random() * 100000000);
return `z9hG4bK${branchId}`;
};
// Generate Digest response
function generateDigestResponse(username, password, realm, nonce, method, uri) {
const ha1 = crypto.createHash('md5')
.update(`${username}:${realm}:${password}`)
.digest('hex');
const ha2 = crypto.createHash('md5')
.update(`${method}:${uri}`)
.digest('hex');
const response = crypto.createHash('md5')
.update(`${ha1}:${nonce}:${ha2}`)
.digest('hex');
return response;
}
// SIP REGISTER request
const generateRegisterRequest = (branch, withAuth = false, realm = '', nonce = '') => {
let request = `REGISTER sip:${asteriskIP}:${asteriskPort} SIP/2.0\r\n` +
`Via: SIP/2.0/UDP ${clientIP}:${clientPort};branch=${branch}\r\n` +
`From: <sip:${username}@${asteriskIP}>;tag=${branch}\r\n` +
`To: <sip:${username}@${asteriskIP}>\r\n` +
`Call-ID: ${branch}@${clientIP}\r\n` +
`CSeq: 1 REGISTER\r\n` +
`Contact: <sip:${username}@${clientIP}:${clientPort}>\r\n` +
'Max-Forwards: 70\r\n' +
'Expires: 3600\r\n' +
'User-Agent: Node.js SIP Library\r\n';
if (withAuth && realm && nonce) {
const digestResponse = generateDigestResponse(username, password, realm, nonce, 'REGISTER', `sip:${asteriskIP}:${asteriskPort}`);
request += 'Authorization: Digest ' +
`username = "${username}", realm = "${realm}", ` +
`nonce = "${nonce}", uri = "sip:${asteriskIP}:${asteriskPort}", ` +
`response = "${digestResponse}"\r\n`;
}
request += 'Content-Length: 0\r\n\r\n';
return request;
};
// Send the REGISTER request
const sendRegisterRequest = (request) => {
socket.send(request, 0, request.length, asteriskPort, asteriskIP, (error) => {
if (error) {
console.error('Error sending UDP packet:', error);
} else {
console.info('REGISTER request sent successfully.');
}
});
};
let realm = '';
let nonce = '';
// Listen for incoming responses
socket.on('message', (message) => {
const response = message.toString();
console.info('Received response:', response);
if (response.startsWith('SIP/2.0 200 OK')) {
console.info('Registration successful.');
// Do further processing or initiate calls here
} else if (response.startsWith('SIP/2.0 401 Unauthorized')) {
const authenticateHeader = response.match(/WWW-Authenticate:.*realm = "([^"]+)".*nonce = "([^"]+)"/i);
if (authenticateHeader) {
realm = authenticateHeader[1];
nonce = authenticateHeader[2];
console.info('Received realm:', realm);
console.info('Received nonce:', nonce);
// Generate Digest response and proceed with registration
const branch = generateBranch();
const registerRequestWithAuth = generateRegisterRequest(branch, true, realm, nonce);
console.info('Sending REGISTER request with authentication:');
console.info(registerRequestWithAuth);
// Send the REGISTER request with authentication
sendRegisterRequest(registerRequestWithAuth+ '\r\n');
}
}
});
// Bind the socket to the client's port and IP
socket.bind(clientPort, clientIP, () => {
console.info('Socket bound successfully.');
// Generate branch identifier for the initial REGISTER request
const branch = generateBranch();
const registerRequest = generateRegisterRequest(branch);
// Send the initial REGISTER request
sendRegisterRequest(registerRequest);
});
Я включил отладку SIP в астериске 18 и вот что я вижу.
SIP/2.0 401 Unauthorized
Via: SIP/2.0/UDP 192.168.1.2:6111;branch=z9hG4bK2049260;received=72.172.213.173;rport=6111
From: <sip:[email protected]>;tag=z9hG4bK2049260
To: <sip:[email protected]>;tag=as12883ca4
Call-ID: [email protected]
CSeq: 1 REGISTER
Server: Asterisk PBX 18.14.0~dfsg+~cs6.12.40431414-1
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH, MESSAGE
Supported: replaces
WWW-Authenticate: Digest algorithm=MD5, realm = "asterisk", nonce = "5b4af6d3"
Content-Length: 0
REGISTER sip:ASTERISK_IP:6111 SIP/2.0
Via: SIP/2.0/UDP 192.168.1.2:6111;branch=z9hG4bK99247361
From: <sip:Tim@ASTERISK_IP>;tag=z9hG4bK99247361
To: <sip:Tim@ASTERISK_IP>
Call-ID: [email protected]
CSeq: 1 REGISTER
Contact: <sip:[email protected]:6111>
Max-Forwards: 70
Expires: 3600
User-Agent: Node.js SIP Library
Authorization: Digest username = "Tim", realm = "asterisk", nonce = "5b4af6d3", uri = "sip:ASTERISK_IP:6111", response = "2e0cbc55739537d46ff0c0ff862ae28a"
Content-Length: 0
SIP/2.0 401 Unauthorized
Via: SIP/2.0/UDP 192.168.1.2:6111;branch=z9hG4bK99247361;received=72.172.213.173;rport=6111
From: <sip:Tim@ASTERISK_IP>;tag=z9hG4bK99247361
To: <sip:Tim@ASTERISK_IP>;tag=as75c4d7b6
Call-ID: [email protected]
CSeq: 1 REGISTER
Server: Asterisk PBX 18.14.0~dfsg+~cs6.12.40431414-1
Allow: INVITE, ACK, CANCEL, OPTIONS, BYE, REFER, SUBSCRIBE, NOTIFY, INFO, PUBLISH, MESSAGE
Supported: replaces
WWW-Authenticate: Digest algorithm=MD5, realm = "asterisk", nonce = "1fd88a71"
Content-Length: 0
Но я вообще не получаю 200 OK от звездочки. Мне интересно, неправильно ли я вычисляю одноразовый номер. Я знаю, что код немного запутан, но сначала это просто быстрый тест. Я пропустил какие-либо заголовки? астериску нужно что-то еще? Будем очень признательны за любую помощь или толчок в правильном направлении. Спасибо.
Я обновил свой код. и что возвращает звездочка.
Это кажется довольно скудным. Мой сервер Asterisk 16.29 возвращает заголовок WWW-Authenticate, который выглядит так: pastebin.com/5uxutaZB
Вы говорите, что используете Asterisk 14, но ответ сервера, который вы показываете, относится к Asterisk 18, с кучей шума, добавленного в строку версии. Кроме того, RFC 3261 говорит, что «серверы ДОЛЖНЫ всегда отправлять параметр «qop» в WWW-Authenticate», и маловероятно, что Asterisk пропустит эту деталь.
@ miken32 ах, да, я не уверен, что заставило меня подумать, что это 14, но я подтвердил, что это версия 18. Что касается этого параметра qop в www-authenticate, я не вижу его в своих трассировках wireshark с помощью софтфона Zoiper, который работает отлично. Что может быть причиной этого?
Вы случайно не используете старый драйвер канала chan_sip? Я нашел старую трассировку пакетов из системы Asterisk 11, и в ней отсутствуют эти дополнительные значения. Вам определенно следует использовать chan_pjsip на этом этапе, chan_sip устарел уже много лет.
Из того, что я вижу в RFC, ваши расчеты верны. Однако много кода добавляет дополнительные значения для получения окончательного хэша. Например, смотрите здесь , здесь или здесь. Однако я не могу найти никакой официальной документации, которая определяет это.
@ miken32 Большое спасибо за всю вашу помощь. Да, я использую chan_sip Я собираюсь обновиться в ближайшее время, я просто хотел обеспечить поддержку некоторых старых телефонов voip, с которыми я экспериментирую. Я проверю ссылки, которые вы мне дали, спасибо.
Думаю, я просто запутался. Я полагал, что для каждого запроса существует универсальная структура. Действительно ли моя проблема заключается только в значениях, которые я отправляю для одноразового номера, и в том, как они отформатированы. или есть еще переменные, которые вступают в игру? Я полагал, что, поскольку это SIP, все будет одинаково, за исключением значений аутентификации.
Я бы посоветовал попробовать скопировать эти другие примеры кода, сделав окончательный хеш как ${ha1}:${nonce}:${cnonce}:auth:${ha2}, где cnonce — случайное значение, которое вы генерируете. Обязательно верните cnonce = "whatever" и qop=auth (без кавычек) в ответе.
qop здесь не используется (и не является обязательным), и ваш дайджест-ответ ${ha1}:${nonce}:${ha2} кажется правильным. Возможно, ваше имя пользователя не Tim, а tim в нижнем регистре. Стоит попробовать. (или даже воссоздайте другого пользователя со строчными буквами, чтобы убедиться, что ha1 также рассчитывается со строчными буквами на звездочке)
Спасибо всем за помощь. Я очень стараюсь, но даже после использования других пользователей. Четырехкратная проверка учетных данных. регистрация со следующим кодом не удалась. Со счетами я знаю работу. Мне сказали посмотреть на справочный материал, и я это сделал. Но мне также сказали, что мои расчеты верны. Я действительно в недоумении, что здесь делать. Я хотел бы присудить награду кому-нибудь, хотя. На самом деле, я был бы буквально готов заплатить за помощь в том, чтобы начать. Видя, что SIP должен быть протоколом, и все стандартно, я решил, что мою проблему будет легко найти.
На моем сервере основной проблемой были неправильные заголовки CSEQ и CALL-ID. Бьюсь об заклад, то же самое произошло на вашем астериске. Чтобы избежать повторения атаки, одноразовый номер можно использовать только в том случае, если вы следуете рекомендациям протокола: Call-ID должен быть таким же, а CSEQ должен быть увеличен. Была также крошечная проблема \r\n. Новый код, который я предложил, определенно протестирован и работает! Я благодарен, что вы приняли мой ответ.





Авторизация - это заголовок, и все заголовки должны появляться до появления \r\n\r\n (пустая строка)
Обычно авторизацию помещают непосредственно перед заголовком Content-Length. Только с \r\n до и после.
После \r\n\r\n у вас будет тело (если длина содержимого указывает один) или новое SIP-сообщение.
ОБНОВЛЯТЬ: Вот rfc3261 Раздел 20.7, чтобы было понятно, что авторизация — это SIP-заголовок:
20.7 Авторизация
Поле заголовка Authorization содержит учетные данные для аутентификации. UA. Раздел 22.2 описывает использование Разрешения. поле заголовка, а раздел 22.4 описывает синтаксис и семантику при использовании HTTP-аутентификации.
Ваш второй РЕГИСТР должен быть таким:
REGISTER sip:ASTERISKIP:ASTERISKPORT SIP/2.0
Via: SIP/2.0/UDP 192.168.1.2:6111;branch=z9hG4bK30130523
From: <sip:USERNAME@ASTERISKIP>;tag=z9hG4bK30130523
To: <sip:USERNAME@ASTERISKIP>
Call-ID: [email protected]
CSeq: 1 REGISTER
Contact: <sip:[email protected]:6111>
Max-Forwards: 70
Expires: 3600
User-Agent: Node.js SIP Library
Authorization: Digest username = "Tim", realm = "asterisk", nonce = "120ef084", uri = "sip:ASTERISKIP:ASTERISKPORT", response = "e6a25e09e1bb2099436cf10526d955e0"
Content-Length: 0
ОБНОВЛЕНИЕ 2:
Вам действительно следует определить имя пользователя в нижнем регистре как на вашем сервере (когда вы создаете пароль), так и на клиенте, когда вы настраиваете имя пользователя. Довольно часто возникают проблемы с регистром в программном обеспечении HTTP или SIP. Имя пользователя обычно нечувствительно к регистру, поэтому использование Tim приведет к ошибке, если звездочка считает его строчным.
Чтобы проверить ваш ответ md5, проще всего вычислить строку с помощью инструмента md5sum:
$> echo -n "Tim:asterisk:secret" | md5sum
d86544eb768e7936519f727599d51fbb
$> echo -n "REGISTER:sip:ASTERISK_IP:6111" | md5sum
0a1fc837871b52dfe5c7d9a009079265
$> echo -n "d86544eb768e7936519f727599d51fbb:5b4af6d3:0a1fc837871b52dfe5c7d9a009079265" | md5sum
504804156ca15812da67ea5439a9ea00
Затем проверьте, является ли это результатом вашего кода! Я не могу этого сделать, потому что ты спрятал свои настоящие ценности.
Вы можете попробовать с «Тим» или «Тим», чтобы увидеть разницу.
ОБНОВЛЕНИЕ 3:
Я проверил ваш код и исправил несколько вещей:
Если этого не сделать, сервер может определить это как атаку. Мой собственный сервер (kamailio) отклонял запрос с информацией об истечении срока действия.
Этот код работает для пользователя sip.antisip.com и «stackoverflow» с паролем «WuZkA6T@sQ8W». Я очень скоро удалю этого пользователя из своей службы. Но вы можете убедиться, что они работают.
const dgram = require("dgram");
const crypto = require("crypto");
const asteriskDOMAIN = "sip.antisip.com";
const asteriskIP = "94.23.17.185";
const asteriskPort = 5060;
const clientIP = "192.168.1.9";
const clientPort = 6111;
const username = "stackoverflow";
const password = "WuZkA6T@sQ8W";
let callId;
let cseq = 1;
// Create a UDP socket
const socket = dgram.createSocket("udp4");
// Generate a random branch identifier for the Via header
const generateBranch = () => {
const branchId = Math.floor(Math.random() * 10000000000000);
return `z9hG4bK${branchId}X2`;
};
const generateCallid = () => {
const branchId = Math.floor(Math.random() * 10000000000000);
return `${branchId}`;
};
// Generate Digest response
function generateDigestResponse(username, password, realm, nonce, method, uri) {
const ha1 = crypto.createHash("md5")
.update(`${username}:${realm}:${password}`)
.digest("hex");
const ha2 = crypto.createHash("md5")
.update(`${method}:${uri}`)
.digest("hex");
const response = crypto.createHash("md5")
.update(`${ha1}:${nonce}:${ha2}`)
.digest("hex");
return response;
}
// SIP REGISTER request
const generateRegisterRequest = (branch, withAuth = false, realm = "", nonce = "") => {
let request = `REGISTER sip:${asteriskDOMAIN}:${asteriskPort} SIP/2.0\r\n`
+ `Via: SIP/2.0/UDP ${clientIP}:${clientPort};branch=${branch}\r\n`
+ `From: <sip:${username}@${asteriskDOMAIN}>;tag=${branch}\r\n`
+ `To: <sip:${username}@${asteriskDOMAIN}>\r\n`
+ `Call-ID: ${callId}@${clientIP}\r\n`
+ `CSeq: ${cseq} REGISTER\r\n`
+ `Contact: <sip:${username}@${clientIP}:${clientPort}>\r\n`
+ "Max-Forwards: 70\r\n"
+ "Expires: 3600\r\n"
+ "User-Agent: Node.js SIP Library\r\n";
cseq += 1;
if (withAuth && realm && nonce) {
const digestResponse = generateDigestResponse(username, password, realm, nonce, "REGISTER", `sip:${asteriskDOMAIN}:${asteriskPort}`);
request += "Authorization: Digest "
+ `username = "${username}", realm = "${realm}", `
+ `nonce = "${nonce}", uri = "sip:${asteriskDOMAIN}:${asteriskPort}", `
+ `response = "${digestResponse}"\r\n`;
}
request += "Content-Length: 0\r\n\r\n";
return request;
};
// Send the REGISTER request
const sendRegisterRequest = (request) => {
socket.send(request, 0, request.length, asteriskPort, asteriskIP, (error) => {
if (error) {
console.error("Error sending UDP packet:", error);
} else {
console.info("REGISTER request sent successfully.");
}
});
};
let realm = "";
let nonce = "";
// Listen for incoming responses
socket.on("message", (message) => {
const response = message.toString();
console.info("Received response:", response);
if (response.startsWith("SIP/2.0 200 OK")) {
console.info("Registration successful.");
// Do further processing or initiate calls here
} else if (response.startsWith("SIP/2.0 401 Unauthorized")) {
const authenticateHeader = response.match(/WWW-Authenticate:.*realm = "([^"]+)".*nonce = "([^"]+)"/i);
if (authenticateHeader) {
realm = authenticateHeader[1];
nonce = authenticateHeader[2];
console.info("Received realm:", realm);
console.info("Received nonce:", nonce);
// Generate Digest response and proceed with registration
const branch = generateBranch();
const registerRequestWithAuth = generateRegisterRequest(branch, true, realm, nonce);
console.info("Sending REGISTER request with authentication:");
console.info(registerRequestWithAuth);
// Send the REGISTER request with authentication
sendRegisterRequest(`${registerRequestWithAuth}`);
}
}
});
// Bind the socket to the client's port and IP
socket.bind(clientPort, clientIP, () => {
console.info("Socket bound successfully.");
// Generate branch identifier for the initial REGISTER request
const branch = generateBranch();
callId = generateCallid();
const registerRequest = generateRegisterRequest(branch);
// Send the initial REGISTER request
sendRegisterRequest(registerRequest);
});
Вам следует отредактировать вопрос, включив в него ваш обновленный код, а также правильную трассировку SIP, показывающую ваш первоначальный запрос на регистрацию и ожидаемый ответ сервера 401, а также последующий запрос/ответ. Я удалил тег звездочка, так как конкретный сервер не имеет отношения к вашему вопросу.