sip.js
Version:
A SIP library for JavaScript
150 lines (149 loc) • 5.6 kB
JavaScript
import { Md5 } from "./md5.js";
import { createRandomToken } from "./utils.js";
function MD5(s) {
return Md5.hashStr(s);
}
/**
* Digest Authentication.
* @internal
*/
export class DigestAuthentication {
/**
* Constructor.
* @param loggerFactory - LoggerFactory.
* @param username - Username.
* @param password - Password.
*/
constructor(loggerFactory, ha1, username, password) {
this.logger = loggerFactory.getLogger("sipjs.digestauthentication");
this.username = username;
this.password = password;
this.ha1 = ha1;
this.nc = 0;
this.ncHex = "00000000";
}
/**
* Performs Digest authentication given a SIP request and the challenge
* received in a response to that request.
* @param request -
* @param challenge -
* @returns true if credentials were successfully generated, false otherwise.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
authenticate(request, challenge, body) {
// Inspect and validate the challenge.
this.algorithm = challenge.algorithm;
this.realm = challenge.realm;
this.nonce = challenge.nonce;
this.opaque = challenge.opaque;
this.stale = challenge.stale;
if (this.algorithm) {
if (this.algorithm !== "MD5") {
this.logger.warn("challenge with Digest algorithm different than 'MD5', authentication aborted");
return false;
}
}
else {
this.algorithm = "MD5";
}
if (!this.realm) {
this.logger.warn("challenge without Digest realm, authentication aborted");
return false;
}
if (!this.nonce) {
this.logger.warn("challenge without Digest nonce, authentication aborted");
return false;
}
// 'qop' can contain a list of values (Array). Let's choose just one.
if (challenge.qop) {
if (challenge.qop.indexOf("auth") > -1) {
this.qop = "auth";
}
else if (challenge.qop.indexOf("auth-int") > -1) {
this.qop = "auth-int";
}
else {
// Otherwise 'qop' is present but does not contain 'auth' or 'auth-int', so abort here.
this.logger.warn("challenge without Digest qop different than 'auth' or 'auth-int', authentication aborted");
return false;
}
}
else {
this.qop = undefined;
}
// Fill other attributes.
this.method = request.method;
this.uri = request.ruri;
this.cnonce = createRandomToken(12);
this.nc += 1;
this.updateNcHex();
// nc-value = 8LHEX. Max value = 'FFFFFFFF'.
if (this.nc === 4294967296) {
this.nc = 1;
this.ncHex = "00000001";
}
// Calculate the Digest "response" value.
this.calculateResponse(body);
return true;
}
/**
* Return the Proxy-Authorization or WWW-Authorization header value.
*/
toString() {
const authParams = [];
if (!this.response) {
throw new Error("response field does not exist, cannot generate Authorization header");
}
authParams.push("algorithm=" + this.algorithm);
authParams.push('username="' + this.username + '"');
authParams.push('realm="' + this.realm + '"');
authParams.push('nonce="' + this.nonce + '"');
authParams.push('uri="' + this.uri + '"');
authParams.push('response="' + this.response + '"');
if (this.opaque) {
authParams.push('opaque="' + this.opaque + '"');
}
if (this.qop) {
authParams.push("qop=" + this.qop);
authParams.push('cnonce="' + this.cnonce + '"');
authParams.push("nc=" + this.ncHex);
}
return "Digest " + authParams.join(", ");
}
/**
* Generate the 'nc' value as required by Digest in this.ncHex by reading this.nc.
*/
updateNcHex() {
const hex = Number(this.nc).toString(16);
this.ncHex = "00000000".substr(0, 8 - hex.length) + hex;
}
/**
* Generate Digest 'response' value.
*/
calculateResponse(body) {
let ha1, ha2;
// HA1 = MD5(A1) = MD5(username:realm:password)
ha1 = this.ha1;
if (ha1 === "" || ha1 === undefined) {
ha1 = MD5(this.username + ":" + this.realm + ":" + this.password);
}
if (this.qop === "auth") {
// HA2 = MD5(A2) = MD5(method:digestURI)
ha2 = MD5(this.method + ":" + this.uri);
// response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2)`
this.response = MD5(ha1 + ":" + this.nonce + ":" + this.ncHex + ":" + this.cnonce + ":auth:" + ha2);
}
else if (this.qop === "auth-int") {
// HA2 = MD5(A2) = MD5(method:digestURI:MD5(entityBody))
ha2 = MD5(this.method + ":" + this.uri + ":" + MD5(body ? body : ""));
// response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2)
this.response = MD5(ha1 + ":" + this.nonce + ":" + this.ncHex + ":" + this.cnonce + ":auth-int:" + ha2);
}
else if (this.qop === undefined) {
// HA2 = MD5(A2) = MD5(method:digestURI)
ha2 = MD5(this.method + ":" + this.uri);
// response = MD5(HA1:nonce:HA2)
this.response = MD5(ha1 + ":" + this.nonce + ":" + ha2);
}
}
}