| name | sip-authentication-security |
| description | Use when implementing SIP authentication, security mechanisms, and encryption. Use when securing SIP servers, clients, or proxies. |
| allowed-tools | ["Bash","Read"] |
SIP Authentication and Security
Master SIP authentication mechanisms (HTTP Digest), TLS encryption, SIPS,
and security best practices for building secure VoIP applications.
HTTP Digest Authentication
Challenge-Response Flow
Client Server
| |
| REGISTER (no credentials) |
|---------------------------------------->|
| |
| 401 Unauthorized |
| WWW-Authenticate: Digest |
| realm="atlanta.com" |
| nonce="dcd98b7102dd..." |
| algorithm=MD5 |
| qop="auth" |
|<----------------------------------------|
| |
| REGISTER (with Authorization) |
| Authorization: Digest |
| username="alice" |
| realm="atlanta.com" |
| nonce="dcd98b7102dd..." |
| uri="sip:atlanta.com" |
| response="6629fae49393..." |
| algorithm=MD5 |
| qop=auth |
| nc=00000001 |
| cnonce="0a4f113b" |
|---------------------------------------->|
| |
| 200 OK |
|<----------------------------------------|
| |
Digest Authentication Implementation
import crypto from 'crypto';
interface DigestChallenge {
realm: string;
nonce: string;
algorithm: 'MD5' | 'SHA-256';
qop?: 'auth' | 'auth-int';
opaque?: string;
stale?: boolean;
}
interface DigestCredentials {
username: string;
realm: string;
nonce: string;
uri: string;
response: string;
algorithm: 'MD5' | 'SHA-256';
cnonce?: string;
nc?: string;
qop?: string;
opaque?: string;
}
class SipDigestAuth {
static generateChallenge(realm: string): DigestChallenge {
return {
realm,
nonce: this.generateNonce(),
algorithm: 'MD5',
qop: 'auth',
opaque: this.generateOpaque()
};
}
static createChallengeHeader(challenge: DigestChallenge): string {
let header = `Digest realm="${challenge.realm}", ` +
`nonce="${challenge.nonce}", ` +
`algorithm=${challenge.algorithm}`;
if (challenge.qop) {
header += `, qop="${challenge.qop}"`;
}
if (challenge.opaque) {
header += `, opaque="${challenge.opaque}"`;
}
if (challenge.stale) {
header += `, stale=TRUE`;
}
return header;
}
static calculateResponse(params: {
username: string;
password: string;
realm: string;
method: string;
uri: string;
nonce: string;
algorithm?: 'MD5' | 'SHA-256';
cnonce?: string;
nc?: string;
qop?: string;
body?: string;
}): string {
const algorithm = params.algorithm || 'MD5';
const hashFunc = algorithm === 'MD5' ? 'md5' : 'sha256';
const a1 = this.hash(
hashFunc,
`${params.username}:${params.realm}:${params.password}`
);
let a2: string;
if (params.qop === 'auth-int') {
const bodyHash = this.hash(hashFunc, params.body || '');
a2 = this.hash(hashFunc, `${params.method}:${params.uri}:${bodyHash}`);
} else {
a2 = this.hash(hashFunc, `${params.method}:${params.uri}`);
}
let response: string;
if (params.qop) {
response = this.hash(
hashFunc,
`${a1}:${params.nonce}:${params.nc}:${params.cnonce}:${params.qop}:${a2}`
);
} else {
response = this.hash(hashFunc, `${a1}:${params.nonce}:${a2}`);
}
return response;
}
static createAuthorizationHeader(params: {
username: string;
password: string;
realm: string;
method: string;
uri: string;
nonce: string;
algorithm?: 'MD5' | 'SHA-256';
qop?: string;
opaque?: string;
}): string {
const algorithm = params.algorithm || 'MD5';
const cnonce = this.generateCnonce();
const nc = '00000001';
const qop = params.qop || 'auth';
const response = this.calculateResponse({
username: params.username,
password: params.password,
realm: params.realm,
method: params.method,
uri: params.uri,
nonce: params.nonce,
algorithm,
cnonce,
nc,
qop
});
let header = `Digest username="${params.username}", ` +
`realm="${params.realm}", ` +
`nonce="${params.nonce}", ` +
`uri="${params.uri}", ` +
`response="${response}", ` +
`algorithm=${algorithm}`;
if (qop) {
header += `, qop=${qop}, nc=${nc}, cnonce="${cnonce}"`;
}
if (params.opaque) {
header += `, opaque="${params.opaque}"`;
}
return header;
}
static verifyCredentials(
credentials: DigestCredentials,
password: string,
method: string
): boolean {
const expectedResponse = this.calculateResponse({
username: credentials.username,
password,
realm: credentials.realm,
method,
uri: credentials.uri,
nonce: credentials.nonce,
algorithm: credentials.algorithm,
cnonce: credentials.cnonce,
nc: credentials.nc,
qop: credentials.qop
});
return credentials.response === expectedResponse;
}
static parseAuthorizationHeader(header: string): DigestCredentials | null {
if (!header.startsWith('Digest ')) {
return null;
}
const params: any = {};
const paramRegex = /(\w+)=(?:"([^"]+)"|([^,\s]+))/g;
let match;
while ((match = paramRegex.exec(header)) !== null) {
const key = match[1];
const value = match[2] || match[3];
params[key] = value;
}
return {
username: params.username,
realm: params.realm,
nonce: params.nonce,
uri: params.uri,
response: params.response,
algorithm: params.algorithm || 'MD5',
cnonce: params.cnonce,
nc: params.nc,
qop: params.qop,
opaque: params.opaque
};
}
private static hash(algorithm: string, data: string): string {
return crypto.createHash(algorithm).update(data).digest('hex');
}
private static generateNonce(): string {
const timestamp = Date.now();
const etag = crypto.randomBytes(16).toString('hex');
const privateKey = 'secret-server-key';
const nonce = `${timestamp}:${etag}:${privateKey}`;
return Buffer.from(nonce).toString('base64');
}
private static generateCnonce(): string {
return crypto.randomBytes(16).toString('hex');
}
private static generateOpaque(): string {
return crypto.randomBytes(16).toString('hex');
}
}
Complete Authentication Example
class SipAuthenticatedClient {
private username: string;
private password: string;
private realm?: string;
private nonce?: string;
private opaque?: string;
constructor(username: string, password: string) {
this.username = username;
this.password = password;
}
async register(server: string): Promise<void> {
let response = await this.sendRegister(server);
if (response.statusCode === 401) {
const challenge = this.parseChallenge(response.headers['www-authenticate']);
if (!challenge) {
throw new ();
}
. = challenge.;
. = challenge.;
. = challenge.;
response = .(server, );
}
(response. === ) {
.();
} {
();
}
}
(
: ,
: =
): <> {
uri = ;
method = ;
message = ;
(withAuth && . && .) {
authHeader = .({
: .,
: .,
: .,
method,
uri,
: .,
: .
});
message += ;
}
message += ;
.(message);
}
(: ): | {
(!header || !header.()) {
;
}
: = {};
paramRegex = ;
match;
((match = paramRegex.(header)) !== ) {
key = match[];
value = match[] || match[];
params[key] = value;
}
{
: params.,
: params.,
: params. || ,
: params.,
: params.
};
}
(): {
crypto.().();
}
(): {
crypto.().();
}
(): {
;
}
(: ): <> {
.(, message);
{ : , : {} };
}
}
Server-Side Authentication
Registration Server with Authentication
interface UserCredentials {
username: string;
password: string;
domain: string;
}
class SipRegistrar {
private users: Map<string, UserCredentials> = new Map();
private registrations: Map<string, Registration> = new Map();
private nonces: Map<string, NonceInfo> = new Map();
private realm: string;
constructor(realm: string) {
this.realm = realm;
}
addUser(username: string, password: string, domain: string): void {
this.users.set(username, { username, password, domain });
}
(: ): {
authHeader = request.[];
(!authHeader) {
.();
}
credentials = .(authHeader);
(!credentials) {
.(, );
}
nonceInfo = ..(credentials.);
(!nonceInfo) {
.();
}
(credentials. && (credentials., ) <= nonceInfo.) {
.(, );
}
user = ..(credentials.);
(!user) {
.(, );
}
valid = .(
credentials,
user.,
);
(!valid) {
.(, );
}
(credentials.) {
nonceInfo. = (credentials., );
}
contact = request.[];
expires = (request.[] || );
.(credentials., contact, expires);
.(, );
}
(: = ): {
challenge = .(.);
challenge. = stale;
..(challenge., {
: challenge.,
: .(),
:
});
response = .(, );
response.[] =
.(challenge);
response;
}
(
: ,
: ,
:
): {
: = {
username,
contact,
: .() + expires *
};
..(username, registration);
( {
..(username);
}, expires * );
}
(: , : ): {
{
: ,
statusCode,
: reason,
: {} ,
:
};
}
(): {
now = .();
maxAge = ;
( [nonce, info] ..()) {
(now - info. > maxAge) {
..(nonce);
}
}
}
}
{
: ;
: ;
: ;
}
{
: ;
: ;
: ;
}
{
: ;
: ;
: ;
: ;
?: ;
}
{
: ;
: ;
: ;
: ;
?: ;
}
TLS/SIPS Implementation
Secure SIP (SIPS) Setup
import tls from 'tls';
import fs from 'fs';
interface TlsConfig {
cert: string;
key: string;
ca?: string;
rejectUnauthorized?: boolean;
minVersion?: string;
ciphers?: string;
}
class SipTlsServer {
private server: tls.Server;
private config: TlsConfig;
constructor(config: TlsConfig) {
this.config = config;
this.server = this.createServer();
}
private createServer(): tls.Server {
const options: tls.TlsOptions = {
cert: fs.readFileSync(this.config.cert),
key: fs.readFileSync(this.config.),
: ,
: .. !== ,
: (.. ) || ,
: .. || [
,
,
,
].()
};
(..) {
options. = fs.(..);
}
server = tls.(options, {
.(socket);
});
server;
}
(: tls.): {
.();
(socket.) {
.();
cert = socket.();
.(, cert..);
} {
.(, socket.);
socket.();
;
}
socket.(, {
.(socket, data);
});
socket.(, {
.(, error);
});
socket.(, {
.();
});
}
(: tls., : ): {
message = data.();
.(, message);
}
(: , : = ): {
..(port, host, {
.();
});
}
(): {
..();
}
}
{
: ;
() {
. = config;
}
(: , : ): <tls.> {
( {
: tls. = {
host,
port,
: fs.(..),
: fs.(..),
: .. !== ,
: (.. ) || ,
: ..
};
(..) {
options. = fs.(..);
}
socket = tls.(options, {
(socket.) {
.();
(socket);
} {
.(, socket.);
socket.();
( ());
}
});
socket.(, {
(error);
});
});
}
(: , : , : ): <> {
socket = .(host, port);
socket.(message);
socket.(, {
.(, data.());
});
}
}
: = {
: ,
: ,
: ,
:
};
server = (serverConfig);
server.();
: = {
: ,
: ,
:
};
client = (clientConfig);
SRTP and Media Security
SRTP Key Exchange in SDP
v=0
o=alice 2890844526 2890844526 IN IP4 pc33.atlanta.com
s=Secure Session
c=IN IP4 pc33.atlanta.com
t=0 0
m=audio 49170 RTP/SAVP 0
a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
a=crypto:2 AES_CM_128_HMAC_SHA1_32 inline:PS1uQCVeeCFCanVmcjkpPywjNWhcYD0mXXtxaVBR|2^20|1:32
a=rtpmap:0 PCMU/8000
SRTP Implementation
import crypto from 'crypto';
interface SrtpParams {
cryptoSuite: string;
keyParams: string;
sessionParams?: string;
}
class SrtpCrypto {
static parseCryptoAttribute(attr: string): SrtpParams | null {
const match = attr.match(
/crypto:(\d+)\s+([^\s]+)\s+inline:([^\s|]+)(?:\|([^\s|]+))?(?:\|([^\s]+))?/
);
if (!match) {
return null;
}
const [, tag, cryptoSuite, keyParams, lifetime, mki] = match;
return {
cryptoSuite,
keyParams,
sessionParams: lifetime
};
}
static generateCryptoAttribute(tag: number = 1): string {
const cryptoSuite = 'AES_CM_128_HMAC_SHA1_80';
const masterKey = crypto.randomBytes(16);
const masterSalt = crypto.randomBytes(14);
keyMaterial = .([masterKey, masterSalt]);
keyParams = keyMaterial.();
lifetime = ;
mki = ;
;
}
(
: ,
: ,
:
): {
: ;
: ;
: ;
} {
kdr = ;
r = index / ( ** kdr);
encryptionKey = .(masterKey, masterSalt, , r);
authKey = .(masterKey, masterSalt, , r);
saltingKey = .(masterKey, masterSalt, , r);
{ encryptionKey, authKey, saltingKey };
}
(
: ,
: ,
: ,
:
): {
iv = .();
masterSalt.(iv);
iv[] ^= label;
cipher = crypto.(, masterKey, iv);
key = cipher.(.());
key;
}
(
: ,
: ,
: ,
: ,
:
): {
header = packet.(, );
payload = packet.();
iv = .(saltingKey, ssrc, sequenceNumber);
cipher = crypto.(, encryptionKey, iv);
encryptedPayload = .([
cipher.(payload),
cipher.()
]);
.([header, encryptedPayload]);
}
(
: ,
:
): {
hmac = crypto.(, authKey);
hmac.(packet);
tag = hmac.();
tag.(, );
}
(
: ,
: ,
:
): {
iv = .();
saltingKey.(iv);
iv.(iv.() ^ ssrc, );
iv.(iv.() ^ sequenceNumber, );
iv;
}
}
Security Best Practices
Input Validation and Sanitization
class SipSecurityValidator {
static validateUri(uri: string): boolean {
const sipUriRegex = /^sips?:[a-zA-Z0-9_.+-]+@[a-zA-Z0-9.-]+$/;
if (!sipUriRegex.test(uri)) {
return false;
}
const dangerousChars = ['<', '>', '"', "'", ';', '&', '|', '`'];
for (const char of dangerousChars) {
if (uri.includes(char)) {
return false;
}
}
return true;
}
static validateHeader(name: string, value: string): boolean {
if (value.includes('\r') || value.includes('\n')) {
return false;
}
(name.()) {
:
.(value);
:
maxForwards = (value);
!(maxForwards) && maxForwards >= && maxForwards <= ;
:
.(value);
:
;
}
}
(: ): {
lines = sdp.();
( line lines) {
(!line.()) {
;
}
(line.() || line.()) {
;
}
}
;
}
(
: ,
: = ,
: =
): {
now = .();
requests = ..(source) || { : , : now + windowMs };
(now > requests.) {
requests. = ;
requests. = now + windowMs;
..(source, requests);
;
}
(requests. >= maxRequests) {
;
}
requests.++;
..(source, requests);
;
}
requestCounts = <, { : ; : }>();
}
Anti-Spoofing Measures
class SipAntiSpoofing {
static validateVia(via: string, sourceIp: string, sourcePort: number): boolean {
const match = via.match(/SIP\/2.0\/(\w+)\s+([^;:]+)(?::(\d+))?/);
if (!match) {
return false;
}
const [, transport, host, port] = match;
const receivedMatch = via.match(/;received=([^;]+)/);
if (receivedMatch) {
const received = receivedMatch[1];
if (received !== sourceIp) {
console.warn('Via received parameter mismatch:', received, 'vs', sourceIp);
return false;
}
}
const rportMatch = via.match(/;rport(?:=(\d+))?/);
if (rportMatch && rportMatch[1]) {
const rport = parseInt(rportMatch[1]);
if (rport !== sourcePort) {
console.warn(, rport, , sourcePort);
;
}
}
;
}
(: , : , : ): {
result = via;
(!via.()) {
result += ;
}
(via.() && !via.()) {
result = result.(, );
} (!via.()) {
result += ;
}
result;
}
}
When to Use This Skill
Use sip-authentication-security when building applications that require:
- User authentication and authorization
- Secure SIP communications (SIPS/TLS)
- Protected media streams (SRTP)
- Registration with authentication
- Proxy authentication
- Certificate-based authentication
- Protection against replay attacks
- Defense against SIP-specific threats
- Compliance with security standards
- Enterprise VoIP security
Best Practices
- Always use digest authentication - Never send passwords in plaintext
- Implement nonce expiration - Prevent replay attacks with time-limited nonces
- Use strong hash algorithms - Prefer SHA-256 over MD5 when possible
- Validate nonce count (nc) - Detect replay attacks within nonce lifetime
- Generate cryptographically random values - Use crypto.randomBytes() for nonces, tags
- Use TLS for signaling - Encrypt SIP messages with TLS (SIPS)
- Use SRTP for media - Encrypt RTP streams with SRTP
- Implement mutual TLS - Require client certificates for server authentication
- Validate all inputs - Sanitize URIs, headers, and SDP content
- Add received/rport parameters - Prevent Via header spoofing
- Implement rate limiting - Prevent DoS and brute force attacks
- Use opaque values - Help detect tampered authentication responses
- Support stale nonces - Allow clients to retry with fresh nonce
- Log authentication failures - Monitor for security incidents
- Rotate master keys regularly - Limit exposure of compromised keys
Common Pitfalls
- Storing plaintext passwords - Always hash passwords before storage
- Not validating nonce freshness - Allows replay attacks
- Weak nonce generation - Predictable nonces compromise security
- Missing qop parameter - Reduces security, allows easier attacks
- Not checking nc increments - Misses replay attack attempts
- Accepting self-signed certificates - Opens door to MITM attacks
- Using weak cipher suites - Compromises TLS security
- Not validating certificate chain - Accepts invalid certificates
- Hardcoded credentials - Security vulnerability in production
- No rate limiting - Vulnerable to DoS and brute force
- Missing Content-Length validation - Enables buffer overflow attacks
- Not sanitizing SDP - Vulnerable to injection attacks
- Trusting Via headers - Enables IP spoofing attacks
- Using MD5 in production - Known vulnerabilities, use SHA-256
- Not implementing SRTP - Exposes media to eavesdropping
Resources