I am trying to learn more about SIP and trying to implement small parts of the protocol in node.js with UDP
so far I have this
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.log('REGISTER request sent successfully.');
}
});
};
let realm = '';
let nonce = '';
// Listen for incoming responses
socket.on('message', (message) => {
const response = message.toString();
console.log('Received response:', response);
if (response.startsWith('SIP/2.0 200 OK')) {
console.log('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.log('Received realm:', realm);
console.log('Received nonce:', nonce);
// Generate Digest response and proceed with registration
const branch = generateBranch();
const registerRequestWithAuth = generateRegisterRequest(branch, true, realm, nonce);
console.log('Sending REGISTER request with authentication:');
console.log(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.log('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);
});
I have turned on SIP debugging in asterisk 18 and this is what I see.
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:Tim@64.227.16.15>;tag=z9hG4bK2049260
To: <sip:Tim@64.227.16.15>;tag=as12883ca4
Call-ID: z9hG4bK2049260@192.168.1.2
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: z9hG4bK99247361@192.168.1.2
CSeq: 1 REGISTER
Contact: <sip:Tim@192.168.1.2: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: z9hG4bK99247361@192.168.1.2
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
But I am not getting 200 OK
from asterisk at all. I am wondering if I am calculating the nonce incorrectly. I know the code is a bit messy but this is just a quick test first. Am I missing any headers? does asterisk need something more? Any help or push in the right direction would be greatly appreciated. Thank you.
Authorization is a header and headers needs to all appear before the occurence of \r\n\r\n (empty line)
A common place would be to put the Authorization just before the Content-Length header. With only \r\n before and after.
After the \r\n\r\n, you will have a body (if content-length indicate one), or a new SIP message.
UPDATE: Here is rfc3261 Section 20.7 to make it clear that Authorization is a SIP header:
20.7 Authorization
The Authorization header field contains authentication credentials of a UA. Section 22.2 overviews the use of the Authorization header field, and Section 22.4 describes the syntax and semantics when used with HTTP authentication.
Your second REGISTER needs to be like this:
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: z9hG4bK30130523@192.168.1.2
CSeq: 1 REGISTER
Contact: <sip:Tim@192.168.1.2: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
UPDATE 2:
You should really define a lowercase username on both your server (when you create the password) AND on the client, when you configure the username. It is pretty common to have case issue on HTTP or SIP software. A username is usually case-insensitive, so using Tim will produce error if asterisk consider it as lowercase.
In order to validate your md5 response, the easiest is to calculate the string with the tool md5sum:
$> echo -n "Tim:asterisk:secret" | md5sum
d86544eb768e7936519f727599d51fbb
$> echo -n "REGISTER:sip:ASTERISK_IP:6111" | md5sum
0a1fc837871b52dfe5c7d9a009079265
$> echo -n "d86544eb768e7936519f727599d51fbb:5b4af6d3:0a1fc837871b52dfe5c7d9a009079265" | md5sum
504804156ca15812da67ea5439a9ea00
Then, validate if this is the result of your code! I can't do it because have you hidden your real values.
You can try with "Tim" or "tim", to see the difference.
UPDATE 3:
I have tested your code and fixed a few things:
When this is not done, the server may detect this as an attack. My own server (a kamailio) was rejecting the request with a "nonce expired" information.
This code is working for sip.antisip.com and "stackoverflow" user with password "WuZkA6T@sQ8W". I will remove this user very soon from my service. But you can validate that they work.
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.log("REGISTER request sent successfully.");
}
});
};
let realm = "";
let nonce = "";
// Listen for incoming responses
socket.on("message", (message) => {
const response = message.toString();
console.log("Received response:", response);
if (response.startsWith("SIP/2.0 200 OK")) {
console.log("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.log("Received realm:", realm);
console.log("Received nonce:", nonce);
// Generate Digest response and proceed with registration
const branch = generateBranch();
const registerRequestWithAuth = generateRegisterRequest(branch, true, realm, nonce);
console.log("Sending REGISTER request with authentication:");
console.log(registerRequestWithAuth);
// Send the REGISTER request with authentication
sendRegisterRequest(`${registerRequestWithAuth}`);
}
}
});
// Bind the socket to the client's port and IP
socket.bind(clientPort, clientIP, () => {
console.log("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);
});