ctutils
Version:
Utilities for interacting with Certificate Transparency logs
253 lines (212 loc) • 7.12 kB
JavaScript
/**
* Certificate Transparency Utilities
* SCT class
*
* By Fotis Loukos <me@fotisl.com>
* @module ctutils
*/
import * as pkijs from 'pkijs';
import * as asn1js from 'asn1js';
import CTLog from './CTLog';
import { LogEntryType, SignatureType } from './Enums';
import { uint64ToArrayBuffer, arrayBufferToUint64 } from './Common';
/**
* SCT class
*/
export default class SignedCertificateTimestamp {
/**
* Construct an SCT object.
* @param {number} version - The version of the SCT, currently only 1 is
* defined and supported.
* @param {ArrayBuffer} logId - The id of the log.
* @param {number} timestamp - The timestamp of the SCT.
* @param {ArrayBuffer} extensions - The extensions.
* @param {ArrayBuffer} signature - The signature.
* @param {number} type - The type of the entry, either
* LogEntryType.x509_entry or LogEntryType.precert_entry.
* @param {ArrayBuffer} cert - The certificate or precertificate
* for this SCT.
*/
constructor(version, logId, timestamp, extensions, signature,
type = LogEntryType.x509_entry, cert = null) {
/**
* @type {number}
* @description The version of the SCT.
*/
this.version = version;
/**
* @type {ArrayBuffer}
* @description The id of the log.
*/
this.logId = logId;
/**
* @type {number}
* @description The timestamp of the SCT.
*/
this.timestamp = timestamp;
/**
* @type {ArrayBuffer}
* @description The extensions.
*/
this.extensions = extensions;
/**
* @type {ArrayBuffer}
* @description The signature.
*/
this.signature = signature;
/**
* @type {number}
* @description The type of the entry.
*/
this.type = type;
/**
* @type {ArrayBuffer}
* @description The certificate or precertificate for this SCT.
*/
this.cert = cert;
}
/**
* Encode the SCT and get the binary representation.
* @return {ArrayBuffer} An ArrayBuffer containing the binary representation
* of the SCT.
*/
toBinary() {
const logIdView = new Uint8Array(this.logId);
const extensionsView = new Uint8Array(this.extensions);
const signatureView = new Uint8Array(this.signature);
/*
* Total size is calculated from the following:
* 1 byte: version
* 32 bytes: log id
* 8 bytes: timestamp
* 2 bytes: length of extensions
* extensionsView.length bytes: the extensions
* signatureView.length bytes: the signature
*/
const sctLen = 1 + 32 + 8 + 2 + extensionsView.length +
signatureView.length;
const sct = new ArrayBuffer(sctLen);
const sctView = new Uint8Array(sct);
sctView[0] = this.version;
sctView.set(logIdView, 1);
sctView.set(new Uint8Array(uint64ToArrayBuffer(this.timestamp)), 33);
sctView[41] = (extensionsView.length >> 8) & 0xff;
sctView[42] = extensionsView.length & 0xff;
sctView.set(extensionsView, 43);
sctView.set(signatureView, 43 + extensionsView.length);
return sct;
}
/**
* Parse a binary SCT and return a new SCT object.
* @param {ArrayBuffer} sctBin - The binary SCT.
* @param {number} type - The type of the entry.
* @param {ArrayBuffer} cert - The certificate or precertificate
* for this SCT.
* @return {SCT} An SCT object containing all information from the binary SCT.
*/
static fromBinary(sctBin, type = LogEntryType.x509_entry, cert = null) {
const sctBinView = new Uint8Array(sctBin);
const version = sctBinView[0];
const logId = sctBinView.slice(1, 33).buffer;
const timestamp = arrayBufferToUint64(sctBinView.slice(33, 41).buffer);
const extLen = (sctBinView[41] << 8) + sctBinView[42];
const extensions = sctBinView.slice(43, 43 + extLen).buffer;
const signature = sctBinView.slice(43 + extLen).buffer;
return new SignedCertificateTimestamp(version, logId, timestamp, extensions,
signature, type, cert);
}
/**
* Verify the signature of an SCT.
* @param {(ArrayBuffer|CTLog)} log - The public key of the log as an
* ArrayBuffer, or a CTLog object.
* @return {Promise.<Boolean>} A promise that is resolved with the result
* of the verification.
*/
verify(log) {
let pubKey;
if(log instanceof CTLog) {
pubKey = log.pubKey;
} else if(log instanceof ArrayBuffer) {
pubKey = log;
} else {
return Promise.reject(new Error('Unknown key type'));
}
let sequence = Promise.resolve();
const signatureView = new Uint8Array(this.signature);
const certView = new Uint8Array(this.cert);
const extensionsView = new Uint8Array(this.extensions);
const dataStructLen = 17 + certView.length + extensionsView.length;
const dataStruct = new ArrayBuffer(dataStructLen);
const dataStructView = new Uint8Array(dataStruct);
/*
* Prepare the struct with the data that was signed.
*/
dataStructView[0] = this.version;
dataStructView[1] = SignatureType.certificate_timestamp;
dataStructView.set(new Uint8Array(uint64ToArrayBuffer(this.timestamp)), 2);
dataStructView[10] = (this.type >> 8) & 0xff;
dataStructView[11] = this.type & 0xff;
dataStructView[12] = (certView.length >> 16) & 0xff;
dataStructView[13] = (certView.length >> 8) & 0xff;
dataStructView[14] = certView.length & 0xff;
dataStructView.set(certView, 15);
dataStructView[16 + certView.length] =
(extensionsView.length >> 8) & 0xff;
dataStructView[16 + certView.length + 1] =
extensionsView.length & 0xff;
if(extensionsView.length > 0)
dataStructView.set(extensionsView, 18 + certView.length);
/*
* Per RFC6962 all signatures are either ECDSA with the NIST P-256 curve
* or RSA (RSASSA-PKCS1-V1_5) with SHA-256.
*/
const isECDSA = signatureView[1] === 3;
const pubKeyView = new Uint8Array(pubKey);
const webcrypto = pkijs.getEngine();
sequence = sequence.then(() => {
let opts;
if(isECDSA) {
opts = {
name: 'ECDSA',
namedCurve: 'P-256'
};
} else {
opts = {
name: 'RSASSA-PKCS1-v1_5',
hash: {
name: 'SHA-256'
}
};
}
return webcrypto.subtle.importKey('spki', pubKeyView, opts, false,
['verify']);
});
sequence = sequence.then(publicKey => {
let opts;
if(isECDSA) {
opts = {
name: 'ECDSA',
hash: {
name: 'SHA-256'
}
};
} else {
opts = {
name: 'RSASSA-PKCS1-v1_5'
};
}
if(isECDSA) {
/*
* Convert from a CMS signature to a webcrypto compatible one.
*/
const asn1 = asn1js.fromBER(this.signature.slice(4));
const ecdsaSig = pkijs.createECDSASignatureFromCMS(asn1.result);
return webcrypto.subtle.verify(opts, publicKey, ecdsaSig, dataStruct);
} else {
return webcrypto.subtle.verify(opts, publicKey, this.signature.slice(4),
dataStruct);
}
});
return sequence;
}
}