/**
* @file Provides facilities for handling certificates, CRLs, etc.
* @version 1.76
* @copyright 2014-2016, Rudolf Nickolaev. All rights reserved.
*/
/*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
(function (root, factory) {
/*
* Module imports and exports
*
*/ // <editor-fold defaultstate="collapsed">
if (typeof define === 'function' && define.amd) {
define(['gostCrypto', 'gostASN1'], factory);
} else if (typeof exports === 'object') {
module.exports = factory(require('gostCrypto'), require('gostASN1'));
} else {
root.GostCert = factory(root.gostCrypto, root.GostASN1);
}
// </editor-fold>
}(this, function (gostCrypto) {
/*
* Common algorithms
*
*/ // <editor-fold defaultstate="collapsed">
var root = this;
var Promise = root.Promise;
var Object = root.Object;
var CryptoOperationData = root.ArrayBuffer;
var Date = root.Date;
// Crypto subtle
var subtle = gostCrypto.subtle;
// Coding
var coding = gostCrypto.coding;
// ASN.1 syntax
var asn1 = gostCrypto.asn1;
// Providers
var providers = gostCrypto.security.providers;
// Expand javascript object
function expand(r) {
for (var i = 1, n = arguments.length; i < n; i++) {
var item = arguments[i];
if (typeof item === 'object')
for (var name in item)
if (item.hasOwnProperty(name))
r[name] = item[name];
}
return r;
}
// Extend javascript class
function extend(Class) {
var F = function () {
};
F.prototype = Class.prototype;
var r = new F, args = [r];
for (var i = 1; i < arguments.length; i++)
args.push(arguments[i]);
return expand.apply(this, args);
}
// Today date + n days
function today(n) {
var date = new Date();
date.setHours(0, 0, 0, 0);
if (n)
date.setDate(date.getDate() + n);
return date;
}
// Self resolver
function call(callback) {
try {
callback();
} catch (e) {
}
}
// Serial number
function genSerialNumber() {
var seed = new Uint8Array(4);
gostCrypto.getRandomValues(seed);
seed[0] = seed[0] & 0x7f;
return coding.Int16.encode(seed);
}
// True if equal numbers
var equalNumbers = (function () {
// Convert number to bigendian hex string
var hex = function (s) {
var t = typeof s;
return t === 'undefined' || s === '' ? '0' :
t === 'number' || s instanceof Number ? s.toString(16).toLowerCase() :
s.replace('0x', '').toLowerCase();
};
// Zero left padding
var lpad = function (s, size) {
return (new Array(size + 1).join('0') + s).slice(-size);
};
return function (s1, s2) {
s1 = hex(s1);
s2 = hex(s2);
var len = Math.max(s1.length, s2.length);
return lpad(s1, len) === lpad(s2, len);
};
})();
// Check equal names
function equalNames(name1, name2) {
for (var key in name1)
if (name1[key] !== name2[key])
return false;
for (var key in name2)
if (name1[key] !== name2[key])
return false;
return true;
}
// Check the buffers to equal
function equalBuffers(r1, r2) {
var s1 = new Uint8Array(r1),
s2 = new Uint8Array(r2);
if (s1.length !== s2.length)
return false;
for (var i = 0, n = s1.length; i < n; i++)
if (s1[i] !== s2[i])
return false;
return true;
}
// Match certificate
function matchCertificate(cert, selector) {
var skid = cert.extensions && cert.extensions.subjectKeyIdentifier;
return (cert && selector &&
(!selector.issuer || equalNames(cert.issuer, selector.issuer)) &&
(!selector.serialNumber || equalNumbers(cert.serialNumber, selector.serialNumber)) &&
(!selector.subjectKeyIdentifier || equalBuffers(skid, selector.subjectKeyIdentifier)) &&
(!selector.subject || equalNames(cert.subject, selector.subject)) &&
(!selector.date || (cert.notBefore.getTime() <= selector.date.getTime() &&
cert.notAfter.getTime() > selector.date.getTime())));
}
// Create authority certificate selector
function authoritySelector(cert, extensions, date) {
var selector = {subject: cert.issuer, date: date},
akid = extensions && extensions.authorityKeyIdentifier;
if (akid) {
selector.subjectKeyIdentifier = akid.keyIdentifier;
if (akid.authorityCertIssuer && akid.authorityCertIssuer[0] &&
akid.authorityCertSerialNumber) {
selector.issuer = akid.authorityCertIssuer[0];
selector.serialNumber = akid.authorityCertSerialNumber;
}
}
return selector;
}
// Find certificates
function selectCertificates(certs, selector) {
var result = [];
for (var i = 0, n = certs.length; i < n; i++)
if (matchCertificate(certs[i], selector))
result.push(certs[i]);
return result;
}
// Match CRL
function matchCRL(crl, selector) {
return ((!selector.issuer || equalNames(crl.issuer, selector.issuer)) &&
(!selector.date || (crl.thisUpdate.getTime() < selector.date.getTime())));
}
// Find certificates
function selectCRLs(crls, selector) {
var result = [];
for (var i = 0, n = crls.length; i < n; i++)
if (matchCRL(crls[i], selector))
result.push(crls[i]);
return result;
}
// Define provider for key algorithm
function keyProvider(algorithm) {
var id = algorithm.id;
for (var name in providers) {
var provider = providers[name];
if (provider.publicKey.id === id)
return provider;
}
}
function defineProperty(object, name, descriptor, enumerable) {
if (typeof descriptor !== 'object')
descriptor = {value: descriptor};
if (enumerable !== undefined)
descriptor.enumerable = enumerable;
Object.defineProperty(object, name, descriptor);
}
function defineProperties(object, properties, enumerable) {
for (var name in properties)
defineProperty(object, name, properties[name], enumerable);
}
// Extend javascript class
function extend(Super, Class, propertiesObject, propertiesClass) {
// If constructor not defined
if (typeof Class !== 'function') {
propertiesClass = propertiesObject;
propertiesObject = Class;
Class = function () {
Super.apply(this, arguments);
};
}
// Create prototype properties
Class.prototype = Object.create(Super.prototype, {
constructor: {
value: Class
},
superclass: {
value: Super.prototype
}
});
if (propertiesObject)
defineProperties(Class.prototype, propertiesObject, true);
// Inherites super class properties
if (Super !== Object)
for (var name in Super)
Class[name] = Super[name];
Class.super = Super;
if (propertiesClass)
defineProperties(Class, propertiesClass, true);
return Class;
}
// </editor-fold>
/**
* Provides facilities for handling certificates, CRLs, etc.
*
* @class GostCert
*/
function GostCert() {
}
/**
* Certificate templates
* <ul>
* <li>providerName - provider name for key generation, default 'CP-01'</li>
* <li>subject - template of subject name {countryName: 'RU', commonName: 'Anonymous'}</li>
* <li>caKeyUsage - default key usages for a CA certificates
* ['digitalSignature', 'nonRepudiation', 'keyEncipherment',
* 'dataEncipherment', 'keyAgreement', 'keyCertSign', 'cRLSign']</li>
* <li>caExtKeyUsage - default extended key usages for a CA certificates
* ['serverAuth', 'clientAuth', 'codeSigning', 'emailProtection', 'ipsecEndSystem',
* 'ipsecTunnel', 'ipsecUser', 'timeStamping', 'OCSPSigning']</li>
* <li>userKeyUsage - default key usages for a user certificate
* ['digitalSignature', 'nonRepudiation', 'keyEncipherment', 'dataEncipherment', 'keyAgreement'],
* <li>userExtKeyUsage - default extended key usages for user certificate
* ['clientAuth', 'emailProtection']</li>
* <li>days - validity of the certificate in days, default 7305</li>
* </ul>
*
* @memberOf GostCert
* @instance
*/
var options = {// <editor-fold defaultstate="collapsed">
providerName: 'CP-01',
subject: {countryName: 'RU', commonName: 'Anonymous'},
caKeyUsage: ['digitalSignature', 'nonRepudiation', 'keyEncipherment',
'dataEncipherment', 'keyAgreement', 'keyCertSign', 'cRLSign'],
caExtKeyUsage: ['serverAuth', 'clientAuth', 'codeSigning', 'emailProtection', 'ipsecEndSystem',
'ipsecTunnel', 'ipsecUser', 'timeStamping', 'OCSPSigning'],
userKeyUsage: ['digitalSignature', 'nonRepudiation', 'keyEncipherment', 'dataEncipherment', 'keyAgreement'],
userExtKeyUsage: ['clientAuth', 'emailProtection'],
days: 7305 // </editor-fold>
};
GostCert.prototype.options = options;
/**
* This class encapsulates X.509 Version 3 certificates.<br><br>
*
* Constructs an X.509 certificate from the given DER encoding or ASN.1 Certificate object.
*
* @class GostCert.X509
* @extends GostASN1.Certificate
* @param {(FormatedData|GostASN1.Certificate)} cert The certificate
*/
var X509 = function (cert) // <editor-fold defaultstate="collapsed">
{
try {
// Try to use decode X.509 certificate
asn1.Certificate.call(this, cert, true);
} catch (e) {
try {
// Try to decode certification request
cert = new asn1.CertificationRequest(cert, true);
} catch (e) {
}
// Create new certificate structure
cert = cert || {};
asn1.Certificate.call(this, {
version: 2,
serialNumber: cert.serialNumber || genSerialNumber(),
signature: cert.signature || {id: 'noSignature'},
issuer: cert.subject || options.subject,
notBefore: cert.notBefore || today(),
notAfter: cert.notAfter ||
today(cert.days || options.days),
subject: cert.subject || options.subject,
subjectPublicKeyInfo: cert.subjectPublicKeyInfo || {
algorithm: {id: 'noSignature'},
subjectPublicKey: new CryptoOperationData(0)
},
extensions: (cert.attributes && (cert.attributes.extensionRequest ||
cert.attributes.msCertExtensions)) || cert.extensions,
signatureAlgorithm: {id: 'noSignature'},
signatureValue: new CryptoOperationData(0)
});
}
}; // </editor-fold>
extend(asn1.Certificate, X509, {
/**
* Generate the contents of this certificate and sign it.<br><br>
*
* If issuerCertificate is not defined self signed certificate generated
*
* @memberOf GostCert.X509
* @instance
* @param {GostASN1.PrivateKeyInfo} issuerPrivateKey The issuer's private key
* @param {GostCert.X509} issuerCertificate The issuer's certificate or undefined for self-signed certificate
* @returns {Promise} Promise to return self object after sign the certificate
*/
sign: function (issuerPrivateKey, issuerCertificate) // <editor-fold defaultstate="collapsed">
{
var self = this, spki = self.subjectPublicKeyInfo;
return new Promise(call).then(function () {
// Need generated key
if (!spki || !spki.algorithm || spki.algorithm === 'noSignature')
throw new Error('Key pair was not generated for the certificate');
// Check issuer private key
if (!issuerPrivateKey)
throw new Error('The private key of the issuer is not defined');
// Certificate can be self signed
issuerCertificate = issuerCertificate || self;
// Calculate subject key indentifier
return subtle.digest('SHA-1', spki.subjectPublicKey);
}).then(function (digest) {
// Signature algorithm
var provider = issuerCertificate.getProvider() || providers[options.providerName];
if (!self.signature || self.signature.id === 'noSignature')
self.signature = provider.signature;
self.signatureAlgorithm = self.signature;
// Set issuer
self.issuer = issuerCertificate.subject;
// Set default extensions
if (!self.extensions)
self.extensions = {};
var exts = self.extensions, ae = issuerCertificate.extensions;
if (self === issuerCertificate) { // Self-signed CA certificate
// Set key usage
exts.keyUsage = exts.keyUsage || options.caKeyUsage;
exts.extKeyUsage = exts.extKeyUsage || options.caExtKeyUsage;
// Set basic constraints
exts.basicConstraints = exts.basicConstraints || {cA: true};
} else {
// Check key usage and validity
if (!issuerCertificate.checkUsage('keyCertSign', self.notBefore))
throw new Error('The issuer\'s certificate is not valid for signing a certificate');
// Set key usage
exts.keyUsage = exts.keyUsage || options.userKeyUsage;
exts.extKeyUsage = exts.extKeyUsage || options.userExtKeyUsage;
// Set basic constraints
exts.basicConstraints = exts.basicConstraints || {
cA: exts.keyUsage.indexOf('keyCertSign') >= 0
};
// Check path length constraint
if (exts.basicConstraints.cA) {
var pathLen = ae && ae.basicConstraints && ae.pathLenConstraint;
if (pathLen !== undefined) {
if (pathLen > 0)
exts.basicConstraints.pathLenConstraint = pathLen - 1;
else
throw new Error('Path length constraint exceeded');
}
}
}
// Subject key identifier 160 bit from public key hash
exts.subjectKeyIdentifier = digest;
// Authority key identifier
if (ae && ae.subjectKeyIdentifier)
exts.authorityKeyIdentifier = {
keyIdentifier: ae.subjectKeyIdentifier,
authorityCertIssuer: [issuerCertificate.issuer],
authorityCertSerialNumber: issuerCertificate.serialNumber};
// Import the private key
return subtle.importKey('pkcs8', issuerPrivateKey.encode(), issuerPrivateKey.privateKeyAlgorithm, false, ['sign']);
}).then(function (key) {
// Sign certificate
return subtle.sign(self.signatureAlgorithm, key, self.tbsCertificate.encode());
}).then(function (signatureValue) {
// Siganture value
self.signatureValue = signatureValue;
return self;
});
}, // </editor-fold>
/**
* Generate key pair for certificate
*
* @memberOf GostCert.X509
* @instance
* @param {(AlgorithmIdentifier|string)} keyAlgorithm The key algorithm or name of provider
* @returns {Promise} Promise to return {@link GostASN1.PrivateKeyInfo} after self-signed certificate generation
*/
generate: function (keyAlgorithm) // <editor-fold defaultstate="collapsed">
{
var self = this, privateKey, provider;
if (keyAlgorithm)
provider = providers[keyAlgorithm];
else
provider = this.getProvider() || providers[options.providerName];
if (provider)
keyAlgorithm = expand(provider.publicKey, {privateKey: provider.privateKey});
return new Promise(call).then(function () {
// Generate key pair
return subtle.generateKey(keyAlgorithm, 'true', ['sign', 'verify']);
}).then(function (keyPair) {
privateKey = keyPair.privateKey;
// Export public key
return subtle.exportKey('spki', keyPair.publicKey);
}).then(function (spki) {
self.subjectPublicKeyInfo = new asn1.SubjectPublicKeyInfo(spki);
return subtle.exportKey('pkcs8', privateKey);
}).then(function (pkcs8) {
return new asn1.PrivateKeyInfo(pkcs8);
});
}, // </editor-fold>
/**
* Gets the public key.
*
* @memberOf GostCert.X509
* @instance
* @returns {Promise} Promise to return {@link Key}
*/
getPublicKey: function () // <editor-fold defaultstate="collapsed">
{
var spki = this.subjectPublicKeyInfo,
keyUsages = (spki.algorithm.id === 'rsaEncryption') ? ['verify'] :
['verify', 'deriveKey', 'deriveBits'];
return subtle.importKey('spki', spki.encode(), spki.algorithm, 'false', keyUsages);
}, // </editor-fold>
/**
* Get appropriate crypto provider for public key
*
* @memberOf GostCert.X509
* @instance
* @returns Object Set of crypto provider algorithms
*/
getProvider: function () // <editor-fold defaultstate="collapsed">
{
return keyProvider(this.subjectPublicKeyInfo.algorithm);
}, // </editor-fold>
/**
* Verifies this certificate.<br><br>
*
* More precisely:<br><br>
* <ul>
* <li>Verifies that the current VM date/time is within the validity period of the certificate.</li>
* <li>If an unrecognized critical extension is present, the certificate is rejected.</li>
* <li>If the issuer certificate has been set, verifies that the signing certificate is a
* CA certificate, and that the signature is correct. The signing certificate is considered
* to be a CA certificate unless one of the following two conditions hold: The signing
* certificate contains a basicConstraints extension, and the CA flag is false; or the
* signing certificate contains a keyUsage extension, the keyUsage extension is marked
* critical, and the keyCertSign bit is false.</li>
* <li>If the issuer CRL has been set, verifies that the certificate has not been revoked.</li>
* </ul>
*
* @memberOf GostCert.X509
* @instance
* @param {GostCert.X509} issuerCertificate The issuer X.509 certificate
* @param {GostCert.CRL} issuerCRL The issuer CRL
* @param {Date} date Validation date. Default current date
* @returns {Promise} Promise to return self object if the certificate is valid
*/
verify: function (issuerCertificate, issuerCRL, date) // <editor-fold defaultstate="collapsed">
{
var self = this, exts = self.extensions;
return new Promise(call).then(function () {
// Current date
date = date || today();
if (self.notBefore.getTime() > date.getTime() ||
self.notAfter.getTime() <= date.getTime())
throw new Error('The certificate has not yet started or expired');
// A unrecognized critical extensions
for (var name in exts) {
var value = exts[name];
if (value.critical &&
['authorityKeyIdentifier', 'subjectKeyIdentifier', 'keyUsage', 'certificatePolicies',
'policyMappings', 'basicConstraints', 'nameConstraints', 'policyConstraints',
'extKeyUsage'].indexOf(name) < 0)
throw new Error('The critical extension \'' + name + '\' is unrecognized');
}
// The certificate can be self-signed
var selector = authoritySelector(self, exts, self.notBefore);
if (!issuerCertificate && matchCertificate(self, selector))
issuerCertificate = self;
// Check issuer
if (issuerCertificate) {
if (!matchCertificate(issuerCertificate, selector) ||
!issuerCertificate.checkUsage('keyCertSign', self.notBefore))
throw new Error('The issuer\'s certificate is not valid');
// Check certificate signature
return issuerCertificate.verifySignature(self.tbsCertificate.encode(),
self.signatureValue, self.signatureAlgorithm);
}
return true;
}).then(function (result) {
if (!result)
throw new Error('The certificate has invalid signature');
// Check CRL
if (issuerCRL) {
if (!matchCRL(issuerCRL, {issuer: self.issuer, date: date}))
throw new Error('The issuer\'s CRL is not valid');
if (issuerCRL.isRevoked(self.serialNumber))
throw new Error('The certificate is revoked');
}
return self;
});
}, // </editor-fold>
/**
* Verify a signature made with this certificate's public key.
*
* @memberOf GostCert.X509
* @instance
* @param {CryptoOperationData} data The signed document.
* @param {CryptoOperationData} signature The signature
* @param {AlgorithmIdentifier} algorithm The algorithm ID used for the signature.
* @returns {Promise} Promise to return true if the signature is verified, and false otherwise
*/
verifySignature: function (data, signature, algorithm) // <editor-fold defaultstate="collapsed">
{
return this.getPublicKey().then(function (publicKey) {
return subtle.verify(algorithm, publicKey, signature, data);
});
}, // </editor-fold>
/**
* Check key usage and date validation
*
* @memberOf GostCert.X509
* @instance
* @param {DOMString} operation The operation
* @param {Date} date Operation date. Default current date
* @returns {boolean}
*/
checkUsage: function (operation, date) // <editor-fold defaultstate="collapsed">
{
var self = this, exts = self.extensions;
date = date || today();
return (self.notBefore.getTime() <= date.getTime() && self.notAfter.getTime() > date.getTime()) &&
(!exts || !((['keyCertSign', 'cRLSign'].indexOf(operation) > 0 && exts.basicConstraints && !exts.basicConstraints.cA) ||
((exts.keyUsage && exts.keyUsage.indexOf(operation) < 0) && (exts.extKeyUsage && exts.extKeyUsage.indexOf(operation) < 0))));
} // </editor-fold>
});
/**
* This class encapsulates X.509 Version 3 certificates.
*
* @memberOf GostCert
* @type GostCert.X509
*/
GostCert.prototype.X509 = X509;
/**
* This class encapsulates a X.509 certificate revocation list (CRL) of RevokedCertificate objects.<br><br>
*
* Note: the methods and constructors that input a CRL do not automatically verify it.
* You need to explicitly call the verify method.
*
* @class GostCert.CRL
* @extends GostASN1.CertificateList
* @param {(FormatedData|GostASN1.CertificateList)} crl
*/
var CRL = function (crl) // <editor-fold defaultstate="collapsed">
{
// Call super
CRL.super.call(this, crl);
// Initialize defaults
if (!this.version)
this.version = 1;
if (!this.revokedCertificates)
this.revokedCertificates = [];
if (!this.thisUpdate)
this.thisUpdate = today();
}; // </editor-fold>
extend(asn1.CertificateList, CRL, {
/**
* Signs this CRL. The issuer's private key has to be set. The default random number generator is used, if needed.<br><br>
*
* Note: Making any modifications to the contents of the CRL after signing invalidates the signature.
* The sign method must be invoked again after any modifications for a valid signature to be computed.
*
* @memberOf GostCert.CRL
* @instance
* @param {GostASN1.PrivateKeyInfo} issuerPrivateKey the issuer's private signing key
* @param {GostCert.X509} issuerCertificate the issuer's certificate
* @returns {Promise} Promise to return self object after sign the CRL
*/
sign: function (issuerPrivateKey, issuerCertificate) // <editor-fold defaultstate="collapsed">
{
var self = this;
return new Promise(call).then(function () {
// Check issuer private key
if (!issuerPrivateKey)
throw new Error('The issuer\'s private key is not defined');
// Check issuer certificate
if (!issuerCertificate)
throw new Error('The issuer\'s certificate is not defined');
// Check issuer name
if (!self.issuer)
self.issuer = issuerCertificate.issuer;
else if (!equalNames(self.issuer, issuerCertificate.issuer))
throw new Error('The CRL prototype and authority certificate have different issuers');
// Check key usage and validity
if (!issuerCertificate.checkUsage('cRLSign', self.thisUpdate))
throw new Error('The issuer\'s certificate is not valid for signing a CRL');
// Signature algorithm
var provider = issuerCertificate.getProvider() || providers[options.providerName];
if (!self.signature)
self.signature = provider.signature;
self.signatureAlgorithm = self.signature;
// Set issuer
self.issuer = issuerCertificate.subject;
// Set default extensions
if (!self.crlExtensions)
self.crlExtensions = {};
var exts = self.crlExtensions,
ae = issuerCertificate.extensions;
if (ae && ae.subjectKeyIdentifier)
exts.authorityKeyIdentifier = {
keyIdentifier: ae.subjectKeyIdentifier,
authorityCertIssuer: [issuerCertificate.issuer],
authorityCertSerialNumber: issuerCertificate.serialNumber};
exts.cRLNumber = exts.cRLNumber || 0;
// Import the private key
return subtle.importKey('pkcs8', issuerPrivateKey.encode(),
issuerPrivateKey.privateKeyAlgorithm, false, ['sign']);
}).then(function (key) {
// Sign CRL
return subtle.sign(self.signatureAlgorithm, key, self.tbsCertList.encode());
}).then(function (signatureValue) {
// Siganture value
self.signatureValue = signatureValue;
return self;
});
}, // </editor-fold>
/**
* Verify the CRL. Checks the date and signature if issuer's certifiate has been defined.
*
* @memberOf GostCert.CRL
* @instance
* @param {GostCert.X509} issuerCertificate the issuer's certificate
* @param {Date} date Validation date. Default current date
* @returns {Promise} Promise to return self object if the certificate is valid
*/
verify: function (issuerCertificate, date) // <editor-fold defaultstate="collapsed">
{
var self = this, exts = self.crlExtensions;
return new Promise(call).then(function () {
// Current date
date = date || today();
if (!self.thisUpdate.getTime() > date.getTime())
throw new Error('The CRL has not yet started');
// Check issuer
if (issuerCertificate) {
if (!matchCertificate(issuerCertificate, authoritySelector(self, exts, self.thisUpdate)) ||
!issuerCertificate.checkUsage('cRLSign', self.thisUpdate))
throw new Error('The issuer\'s certificate is not valid');
if (!self.signatureValue || !self.signatureAlgorithm)
throw new Error('The has no signature');
// Check CRL signature
return issuerCertificate.verifySignature(self.tbsCertList.encode(),
self.signatureValue, self.signatureAlgorithm);
}
}).then(function (result) {
if (!result)
throw new Error('The CRL has invalid signature');
return self;
});
}, // </editor-fold>
/**
* Checks whether this certificate serial number is on the list.
*
* @memberOf GostCert.CRL
* @instance
* @param {Number} serialNumber the issuer's certificate
* @param {Date} date Validation date. Default current date
* @returns {boolean} True if the certificate is valid, and false otherwise
*/
isRevoked: function (serialNumber, date) // <editor-fold defaultstate="collapsed">
{
var rc = this.revokedCertificates;
date = date || today();
for (var i = 0; i < rc.length; i++) {
// Check date and serial number
if (date.getTime() >= rc[i].revocationDate.getTime() &&
equalNumbers(rc[i].userCertificate, serialNumber))
return true;
}
return false;
} // </editor-fold>
});
/**
* This class encapsulates a X.509 certificate revocation list (CRL) of RevokedCertificate objects.
*
* @memberOf GostCert
* @type GostCert.CRL
*/
GostCert.prototype.CRL = CRL;
/**
* A class that encapsulates a DER-encoded PKCS #10 certificate request. The request contains
* the subject's name and public key, and it is signed with the subject's private key.
* The public key contained in the request is used to verify the signature.
* The signature on the request is verified automatically when the request is read.
* Note that the subject's private key is used only to produce a signature when the request is output,
* and is not actually stored with the request.
*
* @class GostCert.Request
* @extends GostASN1.CertificationRequest
* @param {(FormatedData|GostASN1.CertificationRequest)} req
*/
function Request(req) // <editor-fold defaultstate="collapsed">
{
try {
// Try to use encode
asn1.CertificationRequest.call(this, req, true);
} catch (e) {
// Create new certificate structure
req = req || {};
asn1.CertificationRequest.call(this, {
version: 0,
subject: req.subject || options.subject,
subjectPublicKeyInfo: req.subjectPublicKeyInfo || {
algorithm: {id: 'noSignature'},
subjectPublicKey: new CryptoOperationData(0)
},
attributes: req.attributes || {
extensionRequest: {
keyUsage: options.userKeyUsage,
extKeyUsage: options.userExtKeyUsage
}
},
signatureAlgorithm: {id: 'noSignature'},
signatureValue: new CryptoOperationData(0)
});
}
} // </editor-fold>
extend(asn1.CertificationRequest, Request, {
/**
* Generate key pair and sign request
*
* @memberOf GostCert.Request
* @instance
* @param {(AlgorithmIdentifier|string)} keyAlgorithm The name of provider or algorithm
* @returns {Promise} Promise to return {@link GostASN1.PrivateKeyInfo} after request generation
*/
generate: function (keyAlgorithm) // <editor-fold defaultstate="collapsed">
{
var self = this, privateKey, provider;
if (keyAlgorithm)
provider = providers[keyAlgorithm];
else
provider = this.getProvider() || providers[options.providerName];
if (provider)
keyAlgorithm = expand(provider.publicKey, {privateKey: provider.privateKey});
return new Promise(call).then(function () {
// Generate key pair
return subtle.generateKey(keyAlgorithm, 'true', ['sign', 'verify']);
}).then(function (keyPair) {
privateKey = keyPair.privateKey;
// Export public key
return subtle.exportKey('spki', keyPair.publicKey);
}).then(function (spki) {
self.subjectPublicKeyInfo = new asn1.SubjectPublicKeyInfo(spki);
return subtle.exportKey('pkcs8', privateKey);
}).then(function (pkcs8) {
privateKey = new asn1.PrivateKeyInfo(pkcs8);
// Sign request
return self.sign(privateKey);
}).then(function () {
return privateKey;
});
}, // </editor-fold>
/**
* Get appropriate crypto provider for public key
*
* @memberOf GostCert.Request
* @instance
* @returns Object Set of crypto provider algorithms
*/
getProvider: function () // <editor-fold defaultstate="collapsed">
{
return keyProvider(this.subjectPublicKeyInfo.algorithm);
}, // </editor-fold>
/**
* Generate the contents of this request and sign it.<br><br>
*
* @memberOf GostCert.Request
* @instance
* @param {GostASN1.PrivateKeyInfo} privateKey The subject's private key
* @returns Promise to return self object after sign the request
*/
sign: function (privateKey) // <editor-fold defaultstate="collapsed">
{
var self = this, spki = self.subjectPublicKeyInfo;
return new Promise(call).then(function () {
// Need generated key
if (!spki || !spki.algorithm || spki.algorithm === 'noSignature')
throw new Error('Key pair was not generated for the certificate');
// Check issuer private key
if (!privateKey)
throw new Error('The private key is not defined');
// Signature algorithm
var provider = keyProvider(spki.algorithm) || providers[options.providerName];
self.signatureAlgorithm = provider.signature;
// Import the private key
return subtle.importKey('pkcs8', privateKey.encode(),
privateKey.privateKeyAlgorithm, false, ['sign']);
}).then(function (key) {
// Sign the certification request
return subtle.sign(self.signatureAlgorithm, key, self.requestInfo.encode());
}).then(function (signatureValue) {
// Siganture value
self.signatureValue = signatureValue;
return self;
});
}, // </editor-fold>
/**
* Verify the Certification Request. Checks the signature on the public key in the request.
*
* @memberOf GostCert.Request
* @instance
* @returns {Promise} Promise to return self object if the certificate is valid
*/
verify: function () // <editor-fold defaultstate="collapsed">
{
var self = this, spki = self.subjectPublicKeyInfo;
return new Promise(call).then(function () {
// Import key
return subtle.importKey('spki', spki.encode(), spki.algorithm, 'false', ['verify']);
}).then(function (publicKey) {
// Verify signature
return subtle.verify(self.signatureAlgorithm, publicKey, self.signatureValue,
self.requestInfo.encode());
}).then(function (result) {
if (!result)
throw new Error('The certification request has invalid signature');
return self;
});
} // </editor-fold>
});
/**
* A class that encapsulates a DER-encoded PKCS #10 certificate request.
*
* @memberOf GostCert
* @type GostCert.Request
*/
GostCert.prototype.Request = Request;
/**
* A class for retrieving Certificates and CRLs from a repository.<br><br>
*
* Once the CertStore has been created, it can be used to retrieve Certificates
* and CRLs by calling its getCertificates and getCRLs methods. Unlike a KeyStore,
* which provides access to a cache of private keys and trusted certificates,
* a CertStore is designed to provide access to a potentially vast repository
* of untrusted certificates and CRLs.
*
* @class GostCert.CertStore
* @param {GostCert.X509[]} certificates Certificates
* @param {GostCert.CRL[]} crls CLRs
*/
function CertStore(certificates, crls) // <editor-fold defaultstate="collapsed">
{
this.certificates = certificates || [];
this.crls = crls || [];
} // </editor-fold>
extend(Object, CertStore, {
/**
* Returns a Array of Certificates that match the specified selector.
*
* @memberOf GostCert.CertStore
* @instance
* @param {GostCert.CertSelector} selector Certificate filter selector
* @returns {GostCert.X509[]} Selected certificates
*/
getCertificates: function (selector) // <editor-fold defaultstate="collapsed">
{
return selectCertificates(this.certificates, selector);
}, // </editor-fold>
/**
* Returns a Collection of CRLs that match the specified selector.
*
* @memberOf GostCert.CertStore
* @instance
* @param {GostCert.CertSelector} selector CRL filter selector
* @returns {GostCert.CRL[]} selected CRLs
*/
getCRLs: function (selector) // <editor-fold defaultstate="collapsed">
{
return selectCRLs(this.certificates, selector);
}, // </editor-fold>
/**
* Loads this CertStore from the given PKCS#7 formated input stream.
*
* @memberOf GostCert.CertStore
* @instance
* @param {(FormatedData|GostASN1.ContentInfo)} store The input stream from which the certstore is loaded
* @returns {GostCert.CertStore} Self object after store loaded
*/
load: function (store) // <editor-fold defaultstate="collapsed">
{
var info = new asn1.ContentInfo(store),
certs = info.certificates, crls = info.crls;
for (var i = 0; i < certs.length; i++)
this.certificates.push(new X509(certs[i]));
for (var i = 0; i < crls.length; i++)
this.crls.push(new CRL(crls[i]));
return this;
}, // </editor-fold>
/**
* Stores this CertStore to the given output stream in PKCS#7 format.
*
* @memberOf GostCert.CertStore
* @instance
* @returns {GostASN1.ContentInfo} PKCS#7 content info with certificates and crls from CertStore
*/
store: function () // <editor-fold defaultstate="collapsed">
{
return new asn1.ContentInfo({
contentType: 'signedData',
version: 0,
digestAlgorithms: [],
encapContentInfo: {contentType: 'data'},
certificates: this.certs,
crls: this.crls,
signerInfos: []
});
} // </editor-fold>
});
/**
* A class for retrieving Certificates and CRLs from a repository.
*
* @memberOf GostCert
* @type GostCert.Request
*/
GostCert.prototype.CertStore = CertStore;
/**
* A class for building and validating certification paths (also known as certificate chains).
*
* @class GostCert.CertPath
* @param {GostCert.CertStore} certStore
*/
function CertPath(certStore) // <editor-fold defaultstate="collapsed">
{
this.certStore = certStore;
} // </editor-fold>
extend(Object, CertPath, {
/**
* Attempts to build a certification path using the specified algorithm parameter set.
*
* @memberOf GostCert.CertPath
* @instance
* @param {GostCert.X509} certificate Starting path certificate
* @param {Date} date Validation date. Default today
* @returns {Promise} Promise to return array of {@link GostCert.X509} with certification path
*/
build: function (certificate, date) // <editor-fold defaultstate="collapsed">
{
var self = this;
return new Promise(call).then(function () {
// Build certification path
var current = new X509(certificate), certPath = [], success = false, verifiers = [];
while (current) {
var foundCRLs = [], founds = [];
certPath.push(current);
if (!success) {
// Select issuer CRL
foundCRLs = self.certStore.getCRLs({issuer: current.issuer, date: date});
// Create issuer's selection criteria
var selector = authoritySelector(current, current.extensions,
current.notBefore);
// Self-signed certificate?
if (!matchCertificate(current, selector))
// Select issuer form trusted CA root list
founds = self.certStore.getCertificates(selector);
else
success = true;
}
// Add verification tasks
var next = founds.length > 0 && new X509(founds[0]),
crl = foundCRLs.length > 0 && new CRL(foundCRLs[0]);
// Verify CRLs
if (crl)
verifiers.push(crl.verify(next, date));
// Verify the certificate
verifiers.push(current.verify(next, crl, date));
current = next;
}
if (!success)
throw new Error('Root certificate is not found');
// Verify all certificates in path
return Promise.all(verifiers).then(function (results) {
for (var i = 0; i < results; i++)
if (!results[i])
throw new Error('Certification path is not validated');
return certPath;
});
});
} // </editor-fold>
});
/**
* A class for building and validating certification paths (also known as certificate chains).
*
* @memberOf GostCert
* @type GostCert.CertPath
*/
GostCert.prototype.CertPath = CertPath;
/**
* A generic interface for implementing a particular certificate verification
* scheme, such as constructing and verifying
* certificate chains.
*
* @class GostCert.CertificateTrustPolicy
*/
function CertificateTrustPolicy() {
}
extend(Object, CertificateTrustPolicy, {
/**
* Returns a certificate, known to be valid (according to criteria dependent
* on the verification scheme), which has the given selector, certificate and
* CRL lists to implement a particular certificate verification scheme,
* such a forming valid certificate chains.<br>
* Second and third argument to this method may be undefined, and such a case
* must be treated exactly the same as if the particular argument was an empty array.
*
* @memberOf GostCert.CertificateTrustPolicy
* @instance
* @param {GostCert.CertificateSelector} selector Certificate selector
* @param {GostCert.X509[]} certificates Certificates
* @param {GostCert.CRL[]} crls CLRs
* @returns {Promise} Promise to return valid {@link GostCert.X509}
*/
getValidCertificate: function (selector, certificates, crls) {
}
});
/**
* A generic interface for implementing a particular certificate verification
*
* @memberOf GostCert
* @type GostCert.CertificateTrustPolicy
*/
GostCert.prototype.CertificateTrustPolicy = CertificateTrustPolicy;
/**
* A certificate trust policy based on a set of trusted root CAs.<br><br>
*
* In this policy, a certificate will be trusted if and only if it is part of a
* valid certificate chain which terminates in one of the trusted root CAs. <br><br>
*
* This policy has two options for certificate chain verification:
* <ul>
* <li>requireCRL - If true, then for every certificate in a chain
* (unless it is one of the trusted root CA certificates) a valid CRL
* must be provided to determine its revocation status. The default is false.</li>
* <li>requireCAFlag - If true, then every intermediate CA certificate (excluding
* the root CA or the end entity certificate) must contain a Basic Constraints
* extension, with the CA flag set. The default for this option is true.</li>
* </ul>
*
* @class GostCert.TrustedCAPolicy
* @extends GostCert.CertificateTrustPolicy
* @param {GostCert.X509[]} trustedCACerts
* @param {boolean} requireCRL
* @param {boolean} requireCA
*/
function TrustedCAPolicy(trustedCACerts, requireCRL, requireCA) // <editor-fold defaultstate="collapsed">
{
this.trustedCACerts = trustedCACerts || [];
this.requireCRL = requireCRL || false;
this.requireCA = requireCA || true;
} // </editor-fold>
extend(CertificateTrustPolicy, TrustedCAPolicy, {
/**
* Returns a certificate, known to be valid (according to criteria dependent
* on the verification scheme), which has the given selector, certificate and
* CRL lists to implement a particular certificate verification scheme,
* such a forming valid certificate chains.<br>
* Second and third argument to this method may be undefined, and such a case
* must be treated exactly the same as if the particular argument was an empty array.
*
* @memberOf GostCert.TrustedCAPolicy
* @instance
* @param {GostCert.CertificateSelector} selector Certificate selector
* @param {GostASN1.Certificate[]} certificates Certificates
* @param {GostASN1.CertificateList[]} crls CLRs
* @param {Date} date Validation date. Default today
* @returns {Promise} Promise to return valid {@link GostCert.X509}
*/
getValidCertificate: function (selector, certificates, crls, date) // <editor-fold defaultstate="collapsed">
{
var self = this, certPath;
return new Promise(call).then(function () {
certificates = certificates || [];
crls = crls || [];
// Get certificates from the trusted list
var certs = selectCertificates(self.trustedCACerts, selector);
if (certs.length > 0)
return new X509(certs[0]);
// Get certificates from the list
certs = selectCertificates(certificates, selector);
if (certs.length === 0)
return;
// Build certification path
var current = new X509(certs[0]), success = false, verifiers = [];
certPath = [];
while (current) {
var foundCRLs = [], founds = [];
certPath.push(current);
if (!success) {
// Select issuer CRL
foundCRLs = selectCRLs(crls, {issuer: current.issuer, date: date});
if (foundCRLs.length === 0 && self.requireCRL)
return; // The issuer\'s CRL is not found
// Create issuer's selection criteria
selector = authoritySelector(current, current.extensions,
current.notBefore);
// Select issuer form trusted CA root list
founds = selectCertificates(self.trustedCACerts, selector);
if (founds.length === 0) {
// Non-trusted self-signed certificate?
if (!matchCertificate(current, selector)) {
// Select issuer from certificate list
founds = selectCertificates(certificates, selector);
if (founds.length > 0) {
// Check basic contrains and CA flag
var exts = founds[0].extensions;
if (self.requireCA) {
if (!exts || !exts.basicConstraints || !exts.basicConstraints.cA)
return; // The issuer\'s certificate is not valid
// Check path length limit
if (exts.basicConstraints.pathLenConstraint !== undefined &&
exts.basicConstraints.pathLenConstraint < certPath.length - 1)
return; // The issuer\'s certificate path length constraint exceeded
}
} else
return; // Certification path is not built
}
} else
success = true;
}
// Add verification tasks
var next = founds.length > 0 && new X509(founds[0]),
crl = foundCRLs.length > 0 && new CRL(foundCRLs[0]);
// Verify CRLs
if (crl)
verifiers.push(crl.verify(next, date));
// Verify the certificate
verifiers.push(current.verify(next, crl, date));
current = next;
}
if (!success)
throw new Error('Trusted root certificate is not found');
// Verify all certificates in path
return Promise.all(verifiers).then(function (results) {
for (var i = 0; i < results; i++)
if (!results[i])
throw new Error('Certification path is not validated');
return certPath[0];
});
});
} // </editor-fold>
});
/**
* A certificate trust policy based on a set of trusted root CAs.
*
* @memberOf GostCert
* @type GostCert.TrustedCAPolicy
*/
GostCert.prototype.TrustedCAPolicy = TrustedCAPolicy;
/**
* Provides facilities for handling certificates, CRLs, etc.
*
* @memberOf gostCrypto
* @type GostCert
*/
gostCrypto.cert = new GostCert();
return GostCert;
}));