MyRepo-Ums/node_modules/sigstore/dist/tlog/verify/checkpoint.js

149 lines
6.7 KiB
JavaScript
Raw Normal View History

2024-01-19 10:09:11 +00:00
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.verifyCheckpoint = void 0;
const error_1 = require("../../error");
const util_1 = require("../../util");
// Separator between the note and the signatures in a checkpoint
const CHECKPOINT_SEPARATOR = '\n\n';
// Checkpoint signatures are of the following form:
// " <identity> <key_hint+signature_bytes>\n"
// where:
// - the prefix is an emdash (U+2014).
// - <identity> gives a human-readable representation of the signing ID.
// - <key_hint+signature_bytes> is the first 4 bytes of the SHA256 hash of the
// associated public key followed by the signature bytes.
const SIGNATURE_REGEX = /\u2014 (\S+) (\S+)\n/g;
// Verifies the checkpoint value in the given tlog entry. There are two steps
// to the verification:
// 1. Verify that all signatures in the checkpoint can be verified against a
// trusted public key
// 2. Verify that the root hash in the checkpoint matches the root hash in the
// inclusion proof
// See: https://github.com/transparency-dev/formats/blob/main/log/README.md
function verifyCheckpoint(entry, tlogs) {
// Filter tlog instances to just those which were valid at the time of the
// entry
const validTLogs = filterTLogInstances(tlogs, entry.integratedTime);
const inclusionProof = entry.inclusionProof;
const signedNote = SignedNote.fromString(inclusionProof.checkpoint.envelope);
const checkpoint = LogCheckpoint.fromString(signedNote.note);
// Verify that the signatures in the checkpoint are all valid, also check
// that the root hash from the checkpoint matches the root hash in the
// inclusion proof
return (signedNote.verify(validTLogs) &&
util_1.crypto.bufferEqual(checkpoint.logHash, inclusionProof.rootHash));
}
exports.verifyCheckpoint = verifyCheckpoint;
// SignedNote represents a signed note from a transparency log checkpoint. Consists
// of a body (or note) and one more signatures calculated over the body. See
// https://github.com/transparency-dev/formats/blob/main/log/README.md#signed-envelope
class SignedNote {
constructor(note, signatures) {
this.note = note;
this.signatures = signatures;
}
// Deserialize a SignedNote from a string
static fromString(envelope) {
if (!envelope.includes(CHECKPOINT_SEPARATOR)) {
throw new error_1.VerificationError('malformed checkpoint: no separator');
}
// Split the note into the header and the data portions at the separator
const split = envelope.indexOf(CHECKPOINT_SEPARATOR);
const header = envelope.slice(0, split + 1);
const data = envelope.slice(split + CHECKPOINT_SEPARATOR.length);
// Find all the signature lines in the data portion
const matches = data.matchAll(SIGNATURE_REGEX);
// Parse each of the matched signature lines into the name and signature.
// The first four bytes of the signature are the key hint (should match the
// first four bytes of the log ID), and the rest is the signature itself.
const signatures = Array.from(matches, (match) => {
const [, name, signature] = match;
const sigBytes = Buffer.from(signature, 'base64');
if (sigBytes.length < 5) {
throw new error_1.VerificationError('malformed checkpoint: invalid signature');
}
return {
name,
keyHint: sigBytes.subarray(0, 4),
signature: sigBytes.subarray(4),
};
});
if (signatures.length === 0) {
throw new error_1.VerificationError('malformed checkpoint: no signatures');
}
return new SignedNote(header, signatures);
}
// Verifies the signatures in the SignedNote. For each signature, the
// corresponding transparency log is looked up by the key hint and the
// signature is verified against the public key in the transparency log.
// Throws an error if any of the signatures are invalid.
verify(tlogs) {
const data = Buffer.from(this.note, 'utf-8');
return this.signatures.every((signature) => {
// Find the transparency log instance with the matching key hint
const tlog = tlogs.find((tlog) => util_1.crypto.bufferEqual(tlog.logId.keyId.subarray(0, 4), signature.keyHint));
if (!tlog) {
return false;
}
const publicKey = util_1.crypto.createPublicKey(tlog.publicKey.rawBytes);
return util_1.crypto.verifyBlob(data, publicKey, signature.signature);
});
}
}
// LogCheckpoint represents a transparency log checkpoint. Consists of the
// following:
// - origin: the name of the transparency log
// - logSize: the size of the log at the time of the checkpoint
// - logHash: the root hash of the log at the time of the checkpoint
// - rest: the rest of the checkpoint body, which is a list of log entries
// See:
// https://github.com/transparency-dev/formats/blob/main/log/README.md#checkpoint-body
class LogCheckpoint {
constructor(origin, logSize, logHash, rest) {
this.origin = origin;
this.logSize = logSize;
this.logHash = logHash;
this.rest = rest;
}
static fromString(note) {
const lines = note.trim().split('\n');
if (lines.length < 4) {
throw new error_1.VerificationError('malformed checkpoint: too few lines in header');
}
const origin = lines[0];
const logSize = BigInt(lines[1]);
const rootHash = Buffer.from(lines[2], 'base64');
const rest = lines.slice(3);
return new LogCheckpoint(origin, logSize, rootHash, rest);
}
}
// Filter the list of tlog instances to only those which have usable public
// keys and were valid at the given time.
function filterTLogInstances(tlogInstances, integratedTime) {
const targetDate = new Date(Number(integratedTime) * 1000);
return tlogInstances.filter((tlog) => {
// Must have a log ID
if (!tlog.logId) {
return false;
}
// If the tlog doesn't have a public key, we can't use it
const publicKey = tlog.publicKey;
if (publicKey === undefined) {
return false;
}
// If the tlog doesn't have a rawBytes field, we can't use it
if (publicKey.rawBytes === undefined) {
return false;
}
// If the tlog doesn't have a validFor field, we don't need to check it
const validFor = publicKey.validFor;
if (validFor === undefined) {
return true;
}
// Check that the integrated time is within the validFor range
return (validFor.start !== undefined &&
validFor.start <= targetDate &&
(validFor.end === undefined || targetDate <= validFor.end));
});
}