ð sip-authentication-security
Use when implementing SIP authentication, security mechanisms, and encryption. Use when securing SIP servers, clients, or proxies.
Overview
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 {
// Generate authentication challenge (401/407 response)
static generateChallenge(realm: string): DigestChallenge {
return {
realm,
nonce: this.generateNonce(),
algorithm: 'MD5',
qop: 'auth',
opaque: this.generateOpaque()
};
}
// Create WWW-Authenticate or Proxy-Authenticate header
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;
}
// Calculate response for authentication
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';
// Calculate A1 = MD5(username:realm:password)
const a1 = this.hash(
hashFunc,
`${params.username}:${params.realm}:${params.password}`
);
// Calculate A2
let a2: string;
if (params.qop === 'auth-int') {
// A2 = MD5(method:uri:MD5(body))
const bodyHash = this.hash(hashFunc, params.body || '');
a2 = this.hash(hashFunc, `${params.method}:${params.uri}:${bodyHash}`);
} else {
// A2 = MD5(method:uri)
a2 = this.hash(hashFunc, `${params.method}:${params.uri}`);
}
// Calculate response
let response: string;
if (params.qop) {
// response = MD5(A1:nonce:nc:cnonce:qop:A2)
response = this.hash(
hashFunc,
`${a1}:${params.nonce}:${params.nc}:${params.cnonce}:${params.qop}:${a2}`
);
} else {
// response = MD5(A1:nonce:A2)
response = this.hash(hashFunc, `${a1}:${params.nonce}:${a2}`);
}
return response;
}
// Create Authorization or Proxy-Authorization header
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;
}
// Verify client credentials
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;
}
// Parse Authorization/Proxy-Authorization header
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 {
// Nonce = Base64(timestamp:ETag:private-key)
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;
}
// Send REGISTER with authentication
async register(server: string): Promise<void> {
// First attempt without credentials
let response = await this.sendRegister(server);
if (response.statusCode === 401) {
// Extract challenge from WWW-Authenticate header
const challenge = this.parseChallenge(response.headers['www-authenticate']);
if (!challenge) {
throw new Error('Invalid authentication challenge');
}
this.realm = challenge.realm;
this.nonce = challenge.nonce;
this.opaque = challenge.opaque;
// Send REGISTER with credentials
response = await this.sendRegister(server, true);
}
if (response.statusCode === 200) {
console.log('Registration successful');
} else {
throw new Error(`Registration failed: ${response.statusCode}`);
}
}
private async sendRegister(
server: string,
withAuth: boolean = false
): Promise<any> {
const uri = `sip:${server}`;
const method = 'REGISTER';
let message = `${method} ${uri} SIP/2.0\r
Via: SIP/2.0/UDP client.example.com;branch=z9hG4bK${this.generateBranch()}\r
Max-Forwards: 70\r
To: <sip:${this.username}@${server}>\r
From: <sip:${this.username}@${server}>;tag=${this.generateTag()}\r
Call-ID: ${this.generateCallId()}\r
CSeq: 1 REGISTER\r
Contact: <sip:${this.username}@client.example.com>\r
Expires: 3600\r
`;
if (withAuth && this.realm && this.nonce) {
const authHeader = SipDigestAuth.createAuthorizationHeader({
username: this.username,
password: this.password,
realm: this.realm,
method,
uri,
nonce: this.nonce,
opaque: this.opaque
});
message += `Authorization: ${authHeader}\r\n`;
}
message += 'Content-Length: 0\r\n\r\n';
// Send message and get response
return this.send(message);
}
private parseChallenge(header: string): DigestChallenge | null {
if (!header || !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 {
realm: params.realm,
nonce: params.nonce,
algorithm: params.algorithm || 'MD5',
qop: params.qop,
opaque: params.opaque
};
}
private generateBranch(): string {
return crypto.randomBytes(16).toString('hex');
}
private generateTag(): string {
return crypto.randomBytes(8).toString('hex');
}
private generateCallId(): string {
return `${crypto.randomBytes(16).toString('hex')}@client.example.com`;
}
private async send(message: string): Promise<any> {
// Implementation depends on transport
console.log('Sending:', message);
return { statusCode: 401, headers: {} };
}
}
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;
}
// Add user to database
addUser(username: string, password: string, domain: string): void {
this.users.set(username, { username, password, domain });
}
// Handle REGISTER request
handleRegister(request: SipRequest): SipResponse {
const authHeader = request.headers['authorization'];
if (!authHeader) {
// No credentials, send challenge
return this.sendChallenge();
}
// Parse credentials
const credentials = SipDigestAuth.parseAuthorizationHeader(authHeader);
if (!credentials) {
return this.createResponse(400, 'Bad Request');
}
// Verify nonce
const nonceInfo = this.nonces.get(credentials.nonce);
if (!nonceInfo) {
// Nonce expired or invalid, send new challenge
return this.sendChallenge(true);
}
// Check nonce count to prevent replay attacks
if (credentials.nc && parseInt(credentials.nc, 16) <= nonceInfo.nc) {
return this.createResponse(401, 'Unauthorized');
}
// Get user password
const user = this.users.get(credentials.username);
if (!user) {
return this.createResponse(403, 'Forbidden');
}
// Verify credentials
const valid = SipDigestAuth.verifyCredentials(
credentials,
user.password,
'REGISTER'
);
if (!valid) {
return this.createResponse(403, 'Forbidden');
}
// Update nonce count
if (credentials.nc) {
nonceInfo.nc = parseInt(credentials.nc, 16);
}
// Register contact
const contact = request.headers['contact'];
const expires = parseInt(request.headers['expires'] || '3600');
this.registerContact(credentials.username, contact, expires);
return this.createResponse(200, 'OK');
}
private sendChallenge(stale: boolean = false): SipResponse {
const challenge = SipDigestAuth.generateChallenge(this.realm);
challenge.stale = stale;
// Store nonce
this.nonces.set(challenge.nonce, {
nonce: challenge.nonce,
timestamp: Date.now(),
nc: 0
});
const response = this.createResponse(401, 'Unauthorized');
response.headers['www-authenticate'] =
SipDigestAuth.createChallengeHeader(challenge);
return response;
}
private registerContact(
username: string,
contact: string,
expires: number
): void {
const registration: Registration = {
username,
contact,
expires: Date.now() + expires * 1000
};
this.registrations.set(username, registration);
// Set expiration timer
setTimeout(() => {
this.registrations.delete(username);
}, expires * 1000);
}
private createResponse(statusCode: number, reason: string): SipResponse {
return {
version: 'SIP/2.0',
statusCode,
reasonPhrase: reason,
headers: {} as any,
body: undefined
};
}
// Clean up expired nonces
cleanupNonces(): void {
const now = Date.now();
const maxAge = 300000; // 5 minutes
for (const [nonce, info] of this.nonces.entries()) {
if (now - info.timestamp > maxAge) {
this.nonces.delete(nonce);
}
}
}
}
interface Registration {
username: string;
contact: string;
expires: number;
}
interface NonceInfo {
nonce: string;
timestamp: number;
nc: number;
}
interface SipRequest {
method: string;
requestUri: string;
version: string;
headers: any;
body?: string;
}
interface SipResponse {
version: string;
statusCode: number;
reasonPhrase: string;
headers: any;
body?: string;
}
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.key),
// Require client certificate for mutual TLS
requestCert: true,
rejectUnauthorized: this.config.rejectUnauthorized !== false,
// Use strong TLS version
minVersion: (this.config.minVersion as any) || 'TLSv1.2',
// Use secure cipher suites
ciphers: this.config.ciphers || [
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES128-GCM-SHA256',
'DHE-RSA-AES256-GCM-SHA384',
'DHE-RSA-AES128-GCM-SHA256'
].join(':')
};
if (this.config.ca) {
options.ca = fs.readFileSync(this.config.ca);
}
const server = tls.createServer(options, (socket) => {
this.handleConnection(socket);
});
return server;
}
private handleConnection(socket: tls.TLSSocket): void {
console.log('Secure connection established');
// Verify client certificate
if (socket.authorized) {
console.log('Client certificate verified');
const cert = socket.getPeerCertificate();
console.log('Client CN:', cert.subject.CN);
} else {
console.log('Client certificate not authorized:', socket.authorizationError);
socket.destroy();
return;
}
socket.on('data', (data) => {
this.handleData(socket, data);
});
socket.on('error', (error) => {
console.error('Socket error:', error);
});
socket.on('close', () => {
console.log('Connection closed');
});
}
private handleData(socket: tls.TLSSocket, data: Buffer): void {
const message = data.toString();
console.log('Received:', message);
// Process SIP message
// Send response
}
listen(port: number, host: string = '0.0.0.0'): void {
this.server.listen(port, host, () => {
console.log(`SIP TLS server listening on ${host}:${port}`);
});
}
close(): void {
this.server.close();
}
}
class SipTlsClient {
private config: TlsConfig;
constructor(config: TlsConfig) {
this.config = config;
}
connect(host: string, port: number): Promise<tls.TLSSocket> {
return new Promise((resolve, reject) => {
const options: tls.ConnectionOptions = {
host,
port,
cert: fs.readFileSync(this.config.cert),
key: fs.readFileSync(this.config.key),
rejectUnauthorized: this.config.rejectUnauthorized !== false,
minVersion: (this.config.minVersion as any) || 'TLSv1.2',
ciphers: this.config.ciphers
};
if (this.config.ca) {
options.ca = fs.readFileSync(this.config.ca);
}
const socket = tls.connect(options, () => {
if (socket.authorized) {
console.log('Connected to server, certificate verified');
resolve(socket);
} else {
console.error('Certificate verification failed:', socket.authorizationError);
socket.destroy();
reject(new Error('Certificate verification failed'));
}
});
socket.on('error', (error) => {
reject(error);
});
});
}
async send(host: string, port: number, message: string): Promise<void> {
const socket = await this.connect(host, port);
socket.write(message);
socket.on('data', (data) => {
console.log('Received:', data.toString());
});
}
}
// Usage example
const serverConfig: TlsConfig = {
cert: '/path/to/server-cert.pem',
key: '/path/to/server-key.pem',
ca: '/path/to/ca-cert.pem',
rejectUnauthorized: true
};
const server = new SipTlsServer(serverConfig);
server.listen(5061);
const clientConfig: TlsConfig = {
cert: '/path/to/client-cert.pem',
key: '/path/to/client-key.pem',
ca: '/path/to/ca-cert.pem'
};
const client = new SipTlsClient(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 {
// Parse crypto attribute from SDP
static parseCryptoAttribute(attr: string): SrtpParams | null {
// a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:base64key|lifetime|mkiValue
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
};
}
// Generate crypto attribute for SDP
static generateCryptoAttribute(tag: number = 1): string {
const cryptoSuite = 'AES_CM_128_HMAC_SHA1_80';
const masterKey = crypto.randomBytes(16); // 128 bits
const masterSalt = crypto.randomBytes(14); // 112 bits
// Concatenate key and salt
const keyMaterial = Buffer.concat([masterKey, masterSalt]);
const keyParams = keyMaterial.toString('base64');
// Session parameters
const lifetime = '2^20'; // 2^20 packets
const mki = '1:32'; // MKI value:length
return `crypto:${tag} ${cryptoSuite} inline:${keyParams}|${lifetime}|${mki}`;
}
// Derive session keys from master key
static deriveSessionKeys(
masterKey: Buffer,
masterSalt: Buffer,
index: number
): {
encryptionKey: Buffer;
authKey: Buffer;
saltingKey: Buffer;
} {
// Key derivation according to RFC 3711
const kdr = 0; // Key derivation rate (0 = never rekeyed)
// Calculate key_id
const r = index / (2 ** kdr);
// Derive encryption key
const encryptionKey = this.deriveKey(masterKey, masterSalt, 0x00, r);
// Derive authentication key
const authKey = this.deriveKey(masterKey, masterSalt, 0x01, r);
// Derive salting key
const saltingKey = this.deriveKey(masterKey, masterSalt, 0x02, r);
return { encryptionKey, authKey, saltingKey };
}
private static deriveKey(
masterKey: Buffer,
masterSalt: Buffer,
label: number,
index: number
): Buffer {
// PRF(masterKey, (masterSalt XOR (label || index)))
// Simplified implementation
const iv = Buffer.alloc(16);
masterSalt.copy(iv);
iv[7] ^= label;
const cipher = crypto.createCipheriv('aes-128-cbc', masterKey, iv);
const key = cipher.update(Buffer.alloc(16));
return key;
}
// Encrypt RTP packet
static encryptRtp(
packet: Buffer,
encryptionKey: Buffer,
saltingKey: Buffer,
ssrc: number,
sequenceNumber: number
): Buffer {
// Extract RTP header (first 12 bytes)
const header = packet.slice(0, 12);
// Extract payload
const payload = packet.slice(12);
// Generate IV from salting key and packet index
const iv = this.generateIv(saltingKey, ssrc, sequenceNumber);
// Encrypt payload
const cipher = crypto.createCipheriv('aes-128-ctr', encryptionKey, iv);
const encryptedPayload = Buffer.concat([
cipher.update(payload),
cipher.final()
]);
// Concatenate header and encrypted payload
return Buffer.concat([header, encryptedPayload]);
}
// Generate authentication tag
static generateAuthTag(
packet: Buffer,
authKey: Buffer
): Buffer {
const hmac = crypto.createHmac('sha1', authKey);
hmac.update(packet);
const tag = hmac.digest();
// Use first 10 bytes for AES_CM_128_HMAC_SHA1_80
return tag.slice(0, 10);
}
private static generateIv(
saltingKey: Buffer,
ssrc: number,
sequenceNumber: number
): Buffer {
const iv = Buffer.alloc(16);
// Copy salting key
saltingKey.copy(iv);
// XOR with SSRC
iv.writeUInt32BE(iv.readUInt32BE(4) ^ ssrc, 4);
// XOR with sequence number
iv.writeUInt16BE(iv.readUInt16BE(14) ^ sequenceNumber, 14);
return iv;
}
}
Security Best Practices
Input Validation and Sanitization
class SipSecurityValidator {
// Validate SIP URI to prevent injection attacks
static validateUri(uri: string): boolean {
// Check for valid SIP URI format
const sipUriRegex = /^sips?:[a-zA-Z0-9_.+-]+@[a-zA-Z0-9.-]+$/;
if (!sipUriRegex.test(uri)) {
return false;
}
// Check for dangerous characters
const dangerousChars = ['<', '>', '"', "'", ';', '&', '|', '`'];
for (const char of dangerousChars) {
if (uri.includes(char)) {
return false;
}
}
return true;
}
// Validate header values
static validateHeader(name: string, value: string): boolean {
// Check for CRLF injection
if (value.includes('\r') || value.includes('\n')) {
return false;
}
// Validate specific headers
switch (name.toLowerCase()) {
case 'content-length':
return /^\d+$/.test(value);
case 'max-forwards':
const maxForwards = parseInt(value);
return !isNaN(maxForwards) && maxForwards >= 0 && maxForwards <= 70;
case 'cseq':
return /^\d+\s+[A-Z]+$/.test(value);
default:
return true;
}
}
// Validate SDP to prevent injection
static validateSdp(sdp: string): boolean {
const lines = sdp.split('\n');
for (const line of lines) {
// Each line should be type=value
if (!line.match(/^[a-z]=.+$/)) {
return false;
}
// Check for dangerous content
if (line.includes('<script>') || line.includes('javascript:')) {
return false;
}
}
return true;
}
// Rate limiting to prevent DoS
static checkRateLimit(
source: string,
maxRequests: number = 10,
windowMs: number = 1000
): boolean {
const now = Date.now();
const requests = this.requestCounts.get(source) || { count: 0, resetTime: now + windowMs };
if (now > requests.resetTime) {
// Reset window
requests.count = 1;
requests.resetTime = now + windowMs;
this.requestCounts.set(source, requests);
return true;
}
if (requests.count >= maxRequests) {
return false;
}
requests.count++;
this.requestCounts.set(source, requests);
return true;
}
private static requestCounts = new Map<string, { count: number; resetTime: number }>();
}
Anti-Spoofing Measures
class SipAntiSpoofing {
// Validate Via header to detect spoofing
static validateVia(via: string, sourceIp: string, sourcePort: number): boolean {
// Parse Via header
const match = via.match(/SIP\/2.0\/(\w+)\s+([^;:]+)(?::(\d+))?/);
if (!match) {
return false;
}
const [, transport, host, port] = match;
// Check if received parameter matches source
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;
}
}
// Check if rport parameter matches source port
const rportMatch = via.match(/;rport(?:=(\d+))?/);
if (rportMatch && rportMatch[1]) {
const rport = parseInt(rportMatch[1]);
if (rport !== sourcePort) {
console.warn('Via rport parameter mismatch:', rport, 'vs', sourcePort);
return false;
}
}
return true;
}
// Add received and rport parameters to Via
static addReceivedRport(via: string, sourceIp: string, sourcePort: number): string {
let result = via;
// Add received parameter if not present
if (!via.includes('received=')) {
result += `;received=${sourceIp}`;
}
// Add rport parameter value
if (via.includes('rport') && !via.includes('rport=')) {
result = result.replace(/rport/, `rport=${sourcePort}`);
} else if (!via.includes('rport')) {
result += `;rport=${sourcePort}`;
}
return 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