snowflake-sdk
Version:
Node.js driver for Snowflake
275 lines • 10.3 kB
JavaScript
/*
* This software is licensed under the MIT License.
*
* Copyright Fedor Indutny, 2015.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
'use strict';
const process = require('process');
const ocsp = require('@techteamer/ocsp');
const rfc2560 = require('asn1.js-rfc2560');
const rfc5280 = require('asn1.js-rfc5280');
const crypto = require('crypto');
const bn = require('bn.js');
const Errors = require('../errors');
const ErrorCodes = Errors.codes;
const TOLERABLE_VALIDITY_RANGE_RATIO = 0.01;
const MAX_CLOCK_SKEW_IN_MILLISECONDS = 900000;
const MIN_CACHE_WARMUP_TIME_IN_MILLISECONDS = 18000000;
/**
* Builds the certificate id for a given certificate.
*
* @param cert
* @returns {*}
*/
exports.buildCertId = function (cert) {
let issuer = cert.issuerCertificate;
cert = cert.raw;
try {
cert = rfc5280.Certificate.decode(cert, 'der');
if (issuer) {
issuer = issuer.raw;
issuer = rfc5280.Certificate.decode(issuer, 'der');
}
}
catch (e) {
return null; // if we encountered an error during decoding, return null
}
const tbsCert = cert.tbsCertificate;
const tbsIssuer = issuer.tbsCertificate;
const certID = {
hashAlgorithm: {
// algorithm: [ 2, 16, 840, 1, 101, 3, 4, 2, 1 ] // sha256
algorithm: [1, 3, 14, 3, 2, 26], // sha1
},
issuerNameHash: sha1(rfc5280.Name.encode(tbsCert.issuer, 'der')),
issuerKeyHash: sha1(tbsIssuer.subjectPublicKeyInfo.subjectPublicKey.data),
serialNumber: tbsCert.serialNumber,
};
const certIDDer = rfc2560.CertID.encode(certID, 'der');
return encodeKey(certIDDer.toString('BASE64'));
};
function sha1(data) {
return crypto.createHash('sha1').update(data).digest();
}
/**
* Parses a certificate and returns an object that contains decoded versions
* of the certificate and its issuer.
*
* Note: this method might throw an error, so use a try-catch when calling it.
*
* @param cert
* @returns {{cert: *, issuer: *}}
*/
exports.decode = function (cert) {
let issuer = cert.issuerCertificate;
cert = cert.raw;
// note: this block might throw an error
cert = rfc5280.Certificate.decode(cert, 'der');
if (issuer) {
issuer = issuer.raw;
issuer = rfc5280.Certificate.decode(issuer, 'der');
}
return {
cert: cert,
issuer: issuer,
};
};
/**
* Encode certID to a cache key
* @param base64Key {Object}
* @return cache key {string}
*/
const encodeKey = function (base64Key) {
const buff = Buffer.from(base64Key, 'base64');
const certID = rfc2560.CertID.decode(buff, 'der');
return (certID.issuerNameHash.toString('BASE64') +
'#' +
certID.issuerKeyHash.toString('BASE64') +
'#' +
certID.serialNumber.toString(10));
};
exports.encodeKey = encodeKey;
/**
* Encode certID to a cache key
* @param cacheKey {Object}
*/
const decodeKey = function (cacheKey) {
// serialNumber.eq(certID.serialNumber)
const keys = cacheKey.split('#');
const issuerNameHash = Buffer.from(keys[0], 'base64');
const issuerKeyHash = Buffer.from(keys[1], 'base64');
const serialNumber = new bn(keys[2], 10);
const certID = {
hashAlgorithm: {
// algorithm: [ 2, 16, 840, 1, 101, 3, 4, 2, 1 ] // sha256
algorithm: [1, 3, 14, 3, 2, 26], // sha1
},
issuerNameHash: issuerNameHash,
issuerKeyHash: issuerKeyHash,
serialNumber: serialNumber,
};
const certIDDer = rfc2560.CertID.encode(certID, 'der');
return certIDDer.toString('BASE64');
};
exports.decodeKey = decodeKey;
/**
* Calculates Tolerable validity
* @param thisUpdate last update
* @param nextUpdate next update
* @returns {number}
*/
const calculateTolerableVadility = function (thisUpdate, nextUpdate) {
const currentRange = (nextUpdate - thisUpdate) * TOLERABLE_VALIDITY_RANGE_RATIO;
return currentRange > MIN_CACHE_WARMUP_TIME_IN_MILLISECONDS
? currentRange
: MIN_CACHE_WARMUP_TIME_IN_MILLISECONDS;
};
/**
* Checks the validity
* @param currentTime current time
* @param thisUpdate last update
* @param nextUpdate next update
* @return {boolean}
*/
const isValidityRange = function (currentTime, thisUpdate, nextUpdate) {
const tolerableValidity = calculateTolerableVadility(thisUpdate, nextUpdate);
return (thisUpdate - MAX_CLOCK_SKEW_IN_MILLISECONDS <= currentTime &&
currentTime <= nextUpdate + tolerableValidity);
};
exports.isValidityRange = isValidityRange;
/**
* Converts a epoch time in milliseconds to a UTC datetime string
* @param epochInMilliSeconds
* @returns {Date}
*/
const toUTCString = function (epochInMilliSeconds) {
return new Date(epochInMilliSeconds);
};
/**
* Return issuer certificate or signing certificate
* @param issuer issuer certificate
* @param certs
* @param raws
*/
const findResponder = function (issuer, certs, raws) {
let issuerKey = issuer.tbsCertificate.subjectPublicKeyInfo;
issuerKey = ocsp.utils.toPEM(rfc5280.SubjectPublicKeyInfo.encode(issuerKey, 'der'), 'PUBLIC KEY');
if (certs.length > 0) {
const currentTime = Date.now();
const cert = certs[0];
const certValidity = cert.tbsCertificate.validity;
if (certValidity.notAfter.value < currentTime || certValidity.notBefore.value > currentTime) {
return {
err: Errors.createOCSPError(ErrorCodes.ERR_OCSP_INVALID_CERTIFICATE_VALIDITY, 'Valid from:', toUTCString(certValidity.notBefore.value), ', Valid to:', toUTCString(certValidity.notAfter.value)),
responderKey: null,
};
}
const signAlg = ocsp.utils.sign[cert.signatureAlgorithm.algorithm.join('.')];
if (!signAlg) {
return {
err: Errors.createOCSPError(ErrorCodes.ERR_OCSP_NO_SIGNATURE_ALGORITHM),
responderKey: null,
};
}
const verify = crypto.createVerify(signAlg);
verify.update(raws[0]);
if (!verify.verify(issuerKey, cert.signature.data)) {
return {
err: Errors.createOCSPError(ErrorCodes.ERR_OCSP_INVALID_SIGNATURE),
responderKey: null,
};
}
let certKey = cert.tbsCertificate.subjectPublicKeyInfo;
certKey = ocsp.utils.toPEM(rfc5280.SubjectPublicKeyInfo.encode(certKey, 'der'), 'PUBLIC KEY');
return { err: null, responderKey: certKey };
}
return { err: null, responderKey: issuerKey };
};
/**
* Verify OCSP response. If issuer is not specified, the signature will not be
* verified.
* @param issuer issuer certificate
* @param rawRes OCSP Response
* @returns {{success, error, revoked}|{res, success, error}}
*/
const verifyOCSPResponse = function (issuer, rawRes) {
function done(err) {
return {
err: err,
res: rawRes,
};
}
let res;
try {
res = ocsp.utils.parseResponse(rawRes);
}
catch (e) {
return done(e);
}
const value = res.value;
if (issuer) {
// verify signature only if issuer is given
const certs = res.certs;
const rawTBS = rawRes.slice(res.start, res.end);
const raws = res.certsTbs.map(function (tbs) {
return rawRes.slice(tbs.start, tbs.end);
});
const signAlg = ocsp.utils.sign[value.signatureAlgorithm.algorithm.join('.')];
if (!signAlg) {
return done(Errors.createOCSPError(ErrorCodes.ERR_OCSP_NO_SIGNATURE_ALGORITHM));
}
const responderStatus = findResponder(issuer, certs, raws);
if (responderStatus.err) {
return done(responderStatus.err);
}
const responderKey = responderStatus.responderKey;
const v = crypto.createVerify(signAlg);
const signature = value.signature.data;
v.update(rawTBS);
if (!v.verify(responderKey, signature)) {
return done(Errors.createOCSPError(ErrorCodes.ERR_OCSP_INVALID_SIGNATURE));
}
}
const tbs = value.tbsResponseData;
if (tbs.responses.length < 1) {
return done(Errors.createOCSPError(ErrorCodes.ERR_OCSP_NO_RESPONSE));
}
const sd = tbs.responses[0];
if (sd.certStatus.type === 'revoked') {
return done(Errors.createOCSPError(ErrorCodes.ERR_OCSP_REVOKED));
}
const currentTime = Date.now();
const isInjectValidity = process.env.SF_OCSP_TEST_INJECT_VALIDITY_ERROR || '';
if (isInjectValidity.toLowerCase() === 'true' ||
!isValidityRange(currentTime, sd.thisUpdate, sd.nextUpdate)) {
return done(Errors.createOCSPError(ErrorCodes.ERR_OCSP_INVALID_VALIDITY, 'Valid from:', toUTCString(sd.thisUpdate), ', Valid to:', toUTCString(sd.nextUpdate)));
}
const isInjectUnknown = process.env.SF_OCSP_TEST_INJECT_UNKNOWN_STATUS || '';
if (isInjectUnknown.toLowerCase() === 'true' || sd.certStatus.type === 'unknown') {
return done(Errors.createOCSPError(ErrorCodes.ERR_OCSP_UNKNOWN));
}
if (sd.certStatus.type === 'good') {
return done(null);
}
return done(Errors.createOCSPError(ErrorCodes.ERR_OCSP_UNKNOWN_STATE));
};
exports.verifyOCSPResponse = verifyOCSPResponse;
//# sourceMappingURL=cert_util.js.map