jssip
Version:
the Javascript SIP library
207 lines (168 loc) • 7.83 kB
JavaScript
"use strict";
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
var Logger = require('./Logger');
var Utils = require('./Utils');
var logger = new Logger('DigestAuthentication');
module.exports = /*#__PURE__*/function () {
function DigestAuthentication(credentials) {
_classCallCheck(this, DigestAuthentication);
this._credentials = credentials;
this._cnonce = null;
this._nc = 0;
this._ncHex = '00000000';
this._algorithm = null;
this._realm = null;
this._nonce = null;
this._opaque = null;
this._stale = null;
this._qop = null;
this._method = null;
this._uri = null;
this._ha1 = null;
this._response = null;
}
_createClass(DigestAuthentication, [{
key: "get",
value: function get(parameter) {
switch (parameter) {
case 'realm':
return this._realm;
case 'ha1':
return this._ha1;
default:
logger.warn('get() | cannot get "%s" parameter', parameter);
return undefined;
}
}
/**
* Performs Digest authentication given a SIP request and the challenge
* received in a response to that request.
* Returns true if auth was successfully generated, false otherwise.
*/
}, {
key: "authenticate",
value: function authenticate(_ref, challenge)
/* test interface */
{
var method = _ref.method,
ruri = _ref.ruri,
body = _ref.body;
var cnonce = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
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') {
logger.warn('authenticate() | challenge with Digest algorithm different than "MD5", authentication aborted');
return false;
}
} else {
this._algorithm = 'MD5';
}
if (!this._nonce) {
logger.warn('authenticate() | challenge without Digest nonce, authentication aborted');
return false;
}
if (!this._realm) {
logger.warn('authenticate() | challenge without Digest realm, authentication aborted');
return false;
} // If no plain SIP password is provided.
if (!this._credentials.password) {
// If ha1 is not provided we cannot authenticate.
if (!this._credentials.ha1) {
logger.warn('authenticate() | no plain SIP password nor ha1 provided, authentication aborted');
return false;
} // If the realm does not match the stored realm we cannot authenticate.
if (this._credentials.realm !== this._realm) {
logger.warn('authenticate() | no plain SIP password, and stored `realm` does not match the given `realm`, cannot authenticate [stored:"%s", given:"%s"]', this._credentials.realm, this._realm);
return false;
}
} // 'qop' can contain a list of values (Array). Let's choose just one.
if (challenge.qop) {
if (challenge.qop.indexOf('auth-int') > -1) {
this._qop = 'auth-int';
} else if (challenge.qop.indexOf('auth') > -1) {
this._qop = 'auth';
} else {
// Otherwise 'qop' is present but does not contain 'auth' or 'auth-int', so abort here.
logger.warn('authenticate() | challenge without Digest qop different than "auth" or "auth-int", authentication aborted');
return false;
}
} else {
this._qop = null;
} // Fill other attributes.
this._method = method;
this._uri = ruri;
this._cnonce = cnonce || Utils.createRandomToken(12);
this._nc += 1;
var hex = Number(this._nc).toString(16);
this._ncHex = '00000000'.substr(0, 8 - hex.length) + hex; // Nc-value = 8LHEX. Max value = 'FFFFFFFF'.
if (this._nc === 4294967296) {
this._nc = 1;
this._ncHex = '00000001';
} // Calculate the Digest "response" value.
// If we have plain SIP password then regenerate ha1.
if (this._credentials.password) {
// HA1 = MD5(A1) = MD5(username:realm:password).
this._ha1 = Utils.calculateMD5("".concat(this._credentials.username, ":").concat(this._realm, ":").concat(this._credentials.password));
} // Otherwise reuse the stored ha1.
else {
this._ha1 = this._credentials.ha1;
}
var a2;
var ha2;
if (this._qop === 'auth') {
// HA2 = MD5(A2) = MD5(method:digestURI).
a2 = "".concat(this._method, ":").concat(this._uri);
ha2 = Utils.calculateMD5(a2);
logger.debug('authenticate() | using qop=auth [a2:"%s"]', a2); // Response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2).
this._response = Utils.calculateMD5("".concat(this._ha1, ":").concat(this._nonce, ":").concat(this._ncHex, ":").concat(this._cnonce, ":auth:").concat(ha2));
} else if (this._qop === 'auth-int') {
// HA2 = MD5(A2) = MD5(method:digestURI:MD5(entityBody)).
a2 = "".concat(this._method, ":").concat(this._uri, ":").concat(Utils.calculateMD5(body ? body : ''));
ha2 = Utils.calculateMD5(a2);
logger.debug('authenticate() | using qop=auth-int [a2:"%s"]', a2); // Response = MD5(HA1:nonce:nonceCount:credentialsNonce:qop:HA2).
this._response = Utils.calculateMD5("".concat(this._ha1, ":").concat(this._nonce, ":").concat(this._ncHex, ":").concat(this._cnonce, ":auth-int:").concat(ha2));
} else if (this._qop === null) {
// HA2 = MD5(A2) = MD5(method:digestURI).
a2 = "".concat(this._method, ":").concat(this._uri);
ha2 = Utils.calculateMD5(a2);
logger.debug('authenticate() | using qop=null [a2:"%s"]', a2); // Response = MD5(HA1:nonce:HA2).
this._response = Utils.calculateMD5("".concat(this._ha1, ":").concat(this._nonce, ":").concat(ha2));
}
logger.debug('authenticate() | response generated');
return true;
}
/**
* Return the Proxy-Authorization or WWW-Authorization header value.
*/
}, {
key: "toString",
value: function toString() {
var auth_params = [];
if (!this._response) {
throw new Error('response field does not exist, cannot generate Authorization header');
}
auth_params.push("algorithm=".concat(this._algorithm));
auth_params.push("username=\"".concat(this._credentials.username, "\""));
auth_params.push("realm=\"".concat(this._realm, "\""));
auth_params.push("nonce=\"".concat(this._nonce, "\""));
auth_params.push("uri=\"".concat(this._uri, "\""));
auth_params.push("response=\"".concat(this._response, "\""));
if (this._opaque) {
auth_params.push("opaque=\"".concat(this._opaque, "\""));
}
if (this._qop) {
auth_params.push("qop=".concat(this._qop));
auth_params.push("cnonce=\"".concat(this._cnonce, "\""));
auth_params.push("nc=".concat(this._ncHex));
}
return "Digest ".concat(auth_params.join(', '));
}
}]);
return DigestAuthentication;
}();