MyRepo-Ums/node_modules/sigstore/dist/tlog/verify/checkpoint.js
2024-01-19 11:09:11 +01:00

149 lines
6.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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));
});
}