Comparing version 0.0.1-alpha.4 to 0.0.1-alpha.5
@@ -5,2 +5,3 @@ import fetch from 'make-fetch-happen'; | ||
response: Response; | ||
statusCode: number; | ||
constructor(response: Response); | ||
@@ -7,0 +8,0 @@ } |
@@ -8,2 +8,3 @@ "use strict"; | ||
this.response = response; | ||
this.statusCode = response.status; | ||
} | ||
@@ -10,0 +11,0 @@ } |
export declare class VerificationError extends Error { | ||
} | ||
export declare class InvalidBundleError extends Error { | ||
} | ||
export declare class UnsupportedVersionError extends Error { | ||
} |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.VerificationError = void 0; | ||
exports.UnsupportedVersionError = exports.InvalidBundleError = exports.VerificationError = void 0; | ||
class VerificationError extends Error { | ||
} | ||
exports.VerificationError = VerificationError; | ||
class InvalidBundleError extends Error { | ||
} | ||
exports.InvalidBundleError = InvalidBundleError; | ||
class UnsupportedVersionError extends Error { | ||
} | ||
exports.UnsupportedVersionError = UnsupportedVersionError; |
/// <reference types="node" /> | ||
import { SerializedEnvelope, SerializedBundle } from './types/bundle'; | ||
import { SerializedBundle, SerializedEnvelope } from './types/bundle'; | ||
import { GetPublicKeyFunc } from './verify'; | ||
export * as utils from './sigstore-utils'; | ||
@@ -16,3 +17,5 @@ export declare const DEFAULT_REKOR_BASE_URL = "https://rekor.sigstore.dev"; | ||
} & TLogOptions; | ||
export declare type VerifierOptions = TLogOptions; | ||
export declare type VerifierOptions = { | ||
getPublicKey?: GetPublicKeyFunc; | ||
} & TLogOptions; | ||
export declare type Bundle = SerializedBundle; | ||
@@ -19,0 +22,0 @@ export declare type Envelope = SerializedEnvelope; |
@@ -88,3 +88,7 @@ "use strict"; | ||
const tlogKeys = (0, keys_1.getKeys)(); | ||
const verifier = new verify_1.Verifier({ tlog, tlogKeys }); | ||
const verifier = new verify_1.Verifier({ | ||
tlog, | ||
tlogKeys, | ||
getPublicKey: options.getPublicKey, | ||
}); | ||
const b = (0, bundle_1.bundleFromJSON)(bundle); | ||
@@ -91,0 +95,0 @@ return verifier.verifyOffline(b, data); |
@@ -46,2 +46,7 @@ "use strict"; | ||
function toProposedIntotoV002Entry(envelope, signature) { | ||
// Calculate the value for the payloadHash field in the Rekor entry | ||
const payloadHash = util_1.crypto.hash(envelope.payload).toString('hex'); | ||
// Calculate the value for the hash field in the Rekor entry | ||
const envelopeHash = calculateDSSEHash(envelope); | ||
// Collect values for re-creating the DSSE envelope. | ||
// Double-encode payload and signature cause that's what Rekor expects | ||
@@ -52,4 +57,5 @@ const payload = util_1.encoding.base64Encode(envelope.payload.toString('base64')); | ||
const publicKey = util_1.encoding.base64Encode(toPublicKey(signature)); | ||
const payloadHash = util_1.crypto.hash(envelope.payload).toString('hex'); | ||
// Create the envelop portion first so that we can calculate its hash | ||
// Create the envelope portion of the entry. Note the inclusion of the | ||
// publicKey in the signature struct is not a standard part of a DSSE | ||
// envelope, but is required by Rekor. | ||
const dsse = { | ||
@@ -66,3 +72,2 @@ payloadType: envelope.payloadType, | ||
} | ||
const envelopeHash = util_1.crypto.hash(util_1.json.canonicalize(dsse)).toString('hex'); | ||
return { | ||
@@ -80,2 +85,21 @@ apiVersion: '0.0.2', | ||
} | ||
// Calculates the hash of a DSSE envelope for inclusion in a Rekor entry. | ||
// There is no standard way to do this, so the scheme we're using as as | ||
// follows: | ||
// * payload is base64 encoded | ||
// * signature is base64 encoded (only the first signature is used) | ||
// * keyid is included ONLY if it is NOT an empty string | ||
// * The resulting JSON is canonicalized and hashed to a hex string | ||
function calculateDSSEHash(envelope) { | ||
const dsse = { | ||
payloadType: envelope.payloadType, | ||
payload: envelope.payload.toString('base64'), | ||
signatures: [{ sig: envelope.signatures[0].sig.toString('base64') }], | ||
}; | ||
// If the keyid is an empty string, Rekor seems to remove it altogether. | ||
if (envelope.signatures[0].keyid.length > 0) { | ||
dsse.signatures[0].keyid = envelope.signatures[0].keyid; | ||
} | ||
return util_1.crypto.hash(util_1.json.canonicalize(dsse)).toString('hex'); | ||
} | ||
function toPublicKey(signature) { | ||
@@ -82,0 +106,0 @@ return signature.certificates |
@@ -39,7 +39,1 @@ import { HashedRekorV001Schema } from './__generated__/hashedrekord'; | ||
} | ||
export interface VerificationPayload { | ||
body: string; | ||
integratedTime: number; | ||
logIndex: number; | ||
logID: string; | ||
} |
/// <reference types="node" /> | ||
import { KeyObject } from 'crypto'; | ||
import { Bundle } from '../types/bundle'; | ||
export declare function verifyTLogSET(bundle: Bundle, tlogKeys: Record<string, KeyObject>): void; | ||
export declare function verifyTLogEntries(bundle: Bundle, tlogKeys: Record<string, KeyObject>): void; |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.verifyTLogSET = void 0; | ||
exports.verifyTLogEntries = void 0; | ||
const error_1 = require("../error"); | ||
const util_1 = require("../util"); | ||
const format_1 = require("./format"); | ||
// Verifies that all of the tlog entries in the given bundle can be verified. | ||
// Verification is peroformed by re-creating the original Rekor entry from the | ||
// bundle and then verifying the SET against the entry using the corresponding | ||
// key. | ||
function verifyTLogSET(bundle, tlogKeys) { | ||
var _a; | ||
(_a = bundle.verificationData) === null || _a === void 0 ? void 0 : _a.tlogEntries.forEach((entry, index) => { | ||
var _a; | ||
// Re-create the original Rekor verification payload | ||
const payload = toVerificationPayload(bundle, index); | ||
// Canonicalize the payload and turn into a buffer for verification | ||
const data = Buffer.from(util_1.json.canonicalize(payload), 'utf8'); | ||
// Find the public key for the transaction log which generated the SET | ||
const publicKey = tlogKeys[payload.logID]; | ||
if (!publicKey) { | ||
throw new Error('no key found for logID: ' + payload.logID); | ||
const TLOG_MISMATCH_ERROR_MSG = 'bundle content and tlog entry do not match'; | ||
// Verifies that all of the tlog entries in the given bundle. | ||
function verifyTLogEntries(bundle, tlogKeys) { | ||
var _a, _b, _c; | ||
// Extract the signing cert bytes if available | ||
let signingCert; | ||
if (((_b = (_a = bundle.verificationMaterial) === null || _a === void 0 ? void 0 : _a.content) === null || _b === void 0 ? void 0 : _b.$case) === 'x509CertificateChain') { | ||
signingCert = | ||
bundle.verificationMaterial.content.x509CertificateChain.certificates[0] | ||
.rawBytes; | ||
} | ||
// Iterate over the tlog entries and verify each one | ||
(_c = bundle.verificationData) === null || _c === void 0 ? void 0 : _c.tlogEntries.forEach((entry) => { | ||
verifyTLogBody(entry, bundle); | ||
verifyTLogSET(entry, tlogKeys); | ||
// If there is no signing certificate, we can't verify the integrated time | ||
if (signingCert) { | ||
verifyTLogIntegratedTime(entry, signingCert); | ||
} | ||
// Extract the SET from the tlog entry | ||
const signature = (_a = entry.inclusionPromise) === null || _a === void 0 ? void 0 : _a.signedEntryTimestamp; | ||
if (!signature) { | ||
throw new Error('no SET found in bundle'); | ||
} | ||
if (!util_1.crypto.verifyBlob(data, publicKey, signature)) { | ||
throw new Error('transparency log SET verification failed'); | ||
} | ||
}); | ||
} | ||
exports.verifyTLogSET = verifyTLogSET; | ||
// Returns a properly formatted "VerificationPayload" for one of the | ||
// transaction log entires in the given bundle which can be used for SET | ||
// verification. | ||
function toVerificationPayload(bundle, index = 0) { | ||
exports.verifyTLogEntries = verifyTLogEntries; | ||
// Verfifies the SET for the given entry using the provided keys. | ||
function verifyTLogSET(entry, tlogKeys) { | ||
var _a; | ||
// Ensure bundle has tlog entries | ||
const entries = (_a = bundle.verificationData) === null || _a === void 0 ? void 0 : _a.tlogEntries; | ||
if (!entries || entries.length - 1 < index) { | ||
throw new Error('No tlog entries found in bundle'); | ||
// Re-create the original Rekor verification payload | ||
const payload = toVerificationPayload(entry); | ||
// Canonicalize the payload and turn into a buffer for verification | ||
const data = Buffer.from(util_1.json.canonicalize(payload), 'utf8'); | ||
// Find the public key for the transaction log which generated the SET | ||
const tlogKey = tlogKeys[payload.logID]; | ||
if (!tlogKey) { | ||
throw new error_1.VerificationError('no key found for logID: ' + payload.logID); | ||
} | ||
// Rekor metadata | ||
const { integratedTime, logIndex, logId } = entries[index]; | ||
if (!logId) { | ||
throw new Error('No logId found in bundle'); | ||
// Extract the SET from the tlog entry | ||
const signature = (_a = entry.inclusionPromise) === null || _a === void 0 ? void 0 : _a.signedEntryTimestamp; | ||
if (!signature || signature.length === 0) { | ||
throw new error_1.InvalidBundleError('no SET found in bundle'); | ||
} | ||
// Recreate the Rekor entry from the bundle | ||
const body = toVerificationBody(bundle); | ||
return { | ||
body: util_1.encoding.base64Encode(util_1.json.canonicalize(body)), | ||
integratedTime: Number(integratedTime), | ||
logIndex: Number(logIndex), | ||
logID: logId.keyId.toString('hex'), | ||
}; | ||
if (!util_1.crypto.verifyBlob(data, tlogKey, signature)) { | ||
throw new error_1.VerificationError('transparency log SET verification failed'); | ||
} | ||
} | ||
// Recreates the original Rekor entry from the bundle. | ||
function toVerificationBody(bundle) { | ||
var _a; | ||
// Extract the public key (or signing cert) from the bundle | ||
if (!bundle.verificationMaterial) { | ||
throw new Error('No verification material found in bundle'); | ||
// Checks that the tlog integrated time is within the certificate's validity | ||
// period. | ||
function verifyTLogIntegratedTime(entry, signingCert) { | ||
const x509Cert = util_1.x509.parseCertificate(signingCert); | ||
const integratedTime = new Date(Number(entry.integratedTime) * 1000); | ||
if (integratedTime > x509Cert.validTo) { | ||
throw new error_1.VerificationError('tlog integrated time is after certificate expiration'); | ||
} | ||
const publicKey = toPublicKey(bundle.verificationMaterial); | ||
switch ((_a = bundle.content) === null || _a === void 0 ? void 0 : _a.$case) { | ||
case 'messageSignature': { | ||
return toMessageSignatureVerificationBody(bundle.content.messageSignature, publicKey); | ||
} | ||
case 'dsseEnvelope': { | ||
return toDSSEVerificationBody(bundle.content.dsseEnvelope, publicKey); | ||
} | ||
if (integratedTime < x509Cert.validFrom) { | ||
throw new error_1.VerificationError('tlog integrated time is before certificate issuance'); | ||
} | ||
} | ||
// Compare the given intoto tlog entry to the given bundle | ||
function verifyTLogBody(entry, bundle) { | ||
if (!entry.kindVersion) { | ||
throw new error_1.InvalidBundleError('no kindVersion found in bundle'); | ||
} | ||
const { kind, version } = entry.kindVersion; | ||
const body = JSON.parse(entry.canonicalizedBody.toString('utf8')); | ||
if (kind !== body.kind || version !== body.apiVersion) { | ||
throw new error_1.VerificationError(TLOG_MISMATCH_ERROR_MSG); | ||
} | ||
switch (body.kind) { | ||
case 'intoto': | ||
verifyIntotoTLogBody(body, bundle); | ||
break; | ||
case 'hashedrekord': | ||
verifyHashedRekordTLogBody(body, bundle); | ||
break; | ||
default: | ||
throw new Error('Unsupported bundle type'); | ||
throw new error_1.UnsupportedVersionError(`unsupported kind in tlog entry: ${kind}`); | ||
} | ||
} | ||
function toPublicKey(verificationMaterial) { | ||
var _a; | ||
switch ((_a = verificationMaterial === null || verificationMaterial === void 0 ? void 0 : verificationMaterial.content) === null || _a === void 0 ? void 0 : _a.$case) { | ||
case 'x509CertificateChain': { | ||
const der = verificationMaterial.content.x509CertificateChain.certificates[0]; | ||
return util_1.pem.fromDER(der.rawBytes); | ||
} | ||
case 'publicKey': | ||
// TODO: How to handle this? | ||
// eslint-disable-next-line no-fallthrough | ||
// Compare the given intoto tlog entry to the given bundle | ||
function verifyIntotoTLogBody(tlogEntry, bundle) { | ||
var _a, _b; | ||
if (((_a = bundle.content) === null || _a === void 0 ? void 0 : _a.$case) !== 'dsseEnvelope') { | ||
throw new error_1.UnsupportedVersionError(`unsupported bundle content: ${((_b = bundle.content) === null || _b === void 0 ? void 0 : _b.$case) || 'unknown'}`); | ||
} | ||
const dsse = bundle.content.dsseEnvelope; | ||
switch (tlogEntry.apiVersion) { | ||
case '0.0.2': | ||
verifyIntoto002TLogBody(tlogEntry, dsse); | ||
break; | ||
default: | ||
throw new Error('No certificate found in bundle'); | ||
throw new error_1.UnsupportedVersionError(`unsupported intoto version: ${tlogEntry.apiVersion}`); | ||
} | ||
} | ||
// Recreates a Rekor "hashedrekord" entry from the bundle. | ||
function toMessageSignatureVerificationBody(messageSignature, certificate) { | ||
var _a; | ||
const digest = ((_a = messageSignature.messageDigest) === null || _a === void 0 ? void 0 : _a.digest) || Buffer.from(''); | ||
const sig = messageSignature.signature; | ||
const sigMaterial = { | ||
signature: sig, | ||
certificates: [certificate], | ||
key: undefined, | ||
}; | ||
return (0, format_1.toProposedHashedRekordEntry)(digest, sigMaterial); | ||
// Compare the given hashedrekord tlog entry to the given bundle | ||
function verifyHashedRekordTLogBody(tlogEntry, bundle) { | ||
var _a, _b; | ||
if (((_a = bundle.content) === null || _a === void 0 ? void 0 : _a.$case) !== 'messageSignature') { | ||
throw new error_1.UnsupportedVersionError(`unsupported bundle content: ${((_b = bundle.content) === null || _b === void 0 ? void 0 : _b.$case) || 'unknown'}`); | ||
} | ||
const messageSignature = bundle.content.messageSignature; | ||
switch (tlogEntry.apiVersion) { | ||
case '0.0.1': | ||
verifyHashedrekor001TLogBody(tlogEntry, messageSignature); | ||
break; | ||
default: | ||
throw new error_1.UnsupportedVersionError(`unsupported hashedrekord version: ${tlogEntry.apiVersion}`); | ||
} | ||
} | ||
// Recreates a Rekor "intoto" entry from the bundle. | ||
function toDSSEVerificationBody(dsseEnvelope, certificate) { | ||
// Compare the given intoto v0.0.2 tlog entry to the given DSSE envelope. | ||
function verifyIntoto002TLogBody(tlogEntry, dsse) { | ||
var _a, _b; | ||
const sig = dsseEnvelope.signatures[0].sig; | ||
const sigMaterial = { | ||
signature: sig, | ||
certificates: [certificate], | ||
key: undefined, | ||
}; | ||
const body = (0, format_1.toProposedIntotoEntry)(dsseEnvelope, sigMaterial); | ||
// When Rekor saves the entry it removes the payload from the envelope | ||
if (body.apiVersion === '0.0.2') { | ||
(_b = (_a = body.spec.content) === null || _a === void 0 ? void 0 : _a.envelope) === null || _b === void 0 ? true : delete _b.payload; | ||
// Collect all of the signatures from the DSSE envelope | ||
// Turns them into base64-encoded strings for comparison | ||
const dsseSigs = dsse.signatures.map((signature) => signature.sig.toString('base64')); | ||
// Collect all of the signatures from the tlog entry | ||
// Remember that tlog signastures are double base64-encoded | ||
const tlogSigs = (_a = tlogEntry.spec.content.envelope) === null || _a === void 0 ? void 0 : _a.signatures.map((signature) => (signature.sig ? util_1.encoding.base64Decode(signature.sig) : '')); | ||
// Ensure the bundle's DSSE and the tlog entry contain the same number of signatures | ||
if (dsseSigs.length !== (tlogSigs === null || tlogSigs === void 0 ? void 0 : tlogSigs.length)) { | ||
throw new error_1.VerificationError(TLOG_MISMATCH_ERROR_MSG); | ||
} | ||
return body; | ||
// Ensure that every signature in the bundle's DSSE is present in the tlog entry | ||
if (!dsseSigs.every((dsseSig) => tlogSigs.includes(dsseSig))) { | ||
throw new error_1.VerificationError(TLOG_MISMATCH_ERROR_MSG); | ||
} | ||
// Ensure the digest of the bundle's DSSE payload matches the digest in the | ||
// tlog entry | ||
const dssePayloadHash = util_1.crypto.hash(dsse.payload).toString('hex'); | ||
if (dssePayloadHash !== ((_b = tlogEntry.spec.content.payloadHash) === null || _b === void 0 ? void 0 : _b.value)) { | ||
throw new error_1.VerificationError(TLOG_MISMATCH_ERROR_MSG); | ||
} | ||
} | ||
// Compare the given hashedrekord v0.0.1 tlog entry to the given message | ||
// signature | ||
function verifyHashedrekor001TLogBody(tlogEntry, messageSignature) { | ||
var _a, _b; | ||
// Ensure that the bundles message signature matches the tlog entry | ||
const msgSig = messageSignature.signature.toString('base64'); | ||
const tlogSig = tlogEntry.spec.signature.content; | ||
if (msgSig !== tlogSig) { | ||
throw new error_1.VerificationError(TLOG_MISMATCH_ERROR_MSG); | ||
} | ||
// Ensure that the bundle's message digest matches the tlog entry | ||
const msgDigest = (_a = messageSignature.messageDigest) === null || _a === void 0 ? void 0 : _a.digest.toString('hex'); | ||
const tlogDigest = (_b = tlogEntry.spec.data.hash) === null || _b === void 0 ? void 0 : _b.value; | ||
if (msgDigest !== tlogDigest) { | ||
throw new error_1.VerificationError(TLOG_MISMATCH_ERROR_MSG); | ||
} | ||
} | ||
// Returns a properly formatted "VerificationPayload" for one of the | ||
// transaction log entires in the given bundle which can be used for SET | ||
// verification. | ||
function toVerificationPayload(entry) { | ||
// Rekor metadata | ||
const { integratedTime, logIndex, logId, canonicalizedBody } = entry; | ||
if (!logId) { | ||
throw new error_1.InvalidBundleError('no logId found in bundle'); | ||
} | ||
return { | ||
body: canonicalizedBody.toString('base64'), | ||
integratedTime: Number(integratedTime), | ||
logIndex: Number(logIndex), | ||
logID: logId.keyId.toString('hex'), | ||
}; | ||
} |
@@ -109,2 +109,11 @@ /// <reference types="node" /> | ||
inclusionProof: InclusionProof | undefined; | ||
/** | ||
* The canonicalized Rekor entry body, used for SET verification. This | ||
* is the same as the body returned by Rekor. It's included here for | ||
* cases where the client cannot deterministically reconstruct the | ||
* bundle from the other fields. Clients MUST verify that the signature | ||
* referenced in the canonicalized_body matches the signature provided | ||
* in the bundle content. | ||
*/ | ||
canonicalizedBody: Buffer; | ||
} | ||
@@ -111,0 +120,0 @@ export declare const KindVersion: { |
@@ -105,2 +105,3 @@ "use strict"; | ||
inclusionProof: undefined, | ||
canonicalizedBody: Buffer.alloc(0), | ||
}; | ||
@@ -117,2 +118,5 @@ } | ||
inclusionProof: isSet(object.inclusionProof) ? exports.InclusionProof.fromJSON(object.inclusionProof) : undefined, | ||
canonicalizedBody: isSet(object.canonicalizedBody) | ||
? Buffer.from(bytesFromBase64(object.canonicalizedBody)) | ||
: Buffer.alloc(0), | ||
}; | ||
@@ -131,2 +135,4 @@ }, | ||
(obj.inclusionProof = message.inclusionProof ? exports.InclusionProof.toJSON(message.inclusionProof) : undefined); | ||
message.canonicalizedBody !== undefined && | ||
(obj.canonicalizedBody = base64FromBytes(message.canonicalizedBody !== undefined ? message.canonicalizedBody : Buffer.alloc(0))); | ||
return obj; | ||
@@ -133,0 +139,0 @@ }, |
@@ -10,2 +10,3 @@ /// <reference types="node" /> | ||
export * from './__generated__/sigstore_common'; | ||
export { TransparencyLogEntry } from './__generated__/sigstore_rekor'; | ||
export declare const bundleToJSON: (message: Bundle) => unknown; | ||
@@ -12,0 +13,0 @@ export declare const bundleFromJSON: (object: any) => Bundle; |
@@ -17,3 +17,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.bundle = exports.envelopeFromJSON = exports.envelopeToJSON = exports.bundleFromJSON = exports.bundleToJSON = void 0; | ||
exports.bundle = exports.envelopeFromJSON = exports.envelopeToJSON = exports.bundleFromJSON = exports.bundleToJSON = exports.TransparencyLogEntry = void 0; | ||
const util_1 = require("../../util"); | ||
@@ -27,2 +27,4 @@ const envelope_1 = require("./__generated__/envelope"); | ||
__exportStar(require("./__generated__/sigstore_common"), exports); | ||
var sigstore_rekor_1 = require("./__generated__/sigstore_rekor"); | ||
Object.defineProperty(exports, "TransparencyLogEntry", { enumerable: true, get: function () { return sigstore_rekor_1.TransparencyLogEntry; } }); | ||
exports.bundleToJSON = sigstore_bundle_1.Bundle.toJSON; | ||
@@ -89,2 +91,3 @@ exports.bundleFromJSON = sigstore_bundle_1.Bundle.fromJSON; | ||
inclusionProof: undefined, | ||
canonicalizedBody: Buffer.from(entry.body, 'base64'), | ||
}; | ||
@@ -91,0 +94,0 @@ } |
@@ -25,2 +25,3 @@ import { OneOf } from '../utility'; | ||
} | undefined; | ||
canonicalizedBody: string; | ||
}[]; | ||
@@ -27,0 +28,0 @@ timestampVerificationData: { |
export * as crypto from './crypto'; | ||
export * as dsse from './dsse'; | ||
export * as encoding from './encoding'; | ||
export * as json from './json'; | ||
export * as oidc from './oidc'; | ||
@@ -8,2 +9,2 @@ export * as pem from './pem'; | ||
export * as ua from './ua'; | ||
export * as json from './json'; | ||
export * as x509 from './x509'; |
@@ -26,3 +26,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.json = exports.ua = exports.promise = exports.pem = exports.oidc = exports.encoding = exports.dsse = exports.crypto = void 0; | ||
exports.x509 = exports.ua = exports.promise = exports.pem = exports.oidc = exports.json = exports.encoding = exports.dsse = exports.crypto = void 0; | ||
/* | ||
@@ -46,2 +46,3 @@ Copyright 2022 The Sigstore Authors. | ||
exports.encoding = __importStar(require("./encoding")); | ||
exports.json = __importStar(require("./json")); | ||
exports.oidc = __importStar(require("./oidc")); | ||
@@ -51,2 +52,2 @@ exports.pem = __importStar(require("./pem")); | ||
exports.ua = __importStar(require("./ua")); | ||
exports.json = __importStar(require("./json")); | ||
exports.x509 = __importStar(require("./x509")); |
@@ -6,5 +6,7 @@ /// <reference types="node" /> | ||
import { Bundle } from './types/bundle'; | ||
export declare type GetPublicKeyFunc = (keyId: string) => Promise<string | undefined>; | ||
export interface VerifyOptions { | ||
tlog: TLog; | ||
tlogKeys: Record<string, KeyObject>; | ||
getPublicKey?: GetPublicKeyFunc; | ||
} | ||
@@ -14,4 +16,6 @@ export declare class Verifier { | ||
private tlogKeys; | ||
private getExternalPublicKey; | ||
constructor(options: VerifyOptions); | ||
verifyOffline(bundle: Bundle, data?: Buffer): void; | ||
verifyOffline(bundle: Bundle, data?: Buffer): Promise<void>; | ||
getPublicKey(bundle: Bundle): Promise<string>; | ||
} |
@@ -11,7 +11,28 @@ "use strict"; | ||
this.tlogKeys = options.tlogKeys; | ||
this.getExternalPublicKey = | ||
options.getPublicKey || (() => Promise.resolve(undefined)); | ||
} | ||
verifyOffline(bundle, data) { | ||
verifyArtifactSignature(bundle, data); | ||
(0, verify_1.verifyTLogSET)(bundle, this.tlogKeys); | ||
async verifyOffline(bundle, data) { | ||
const publicKey = await this.getPublicKey(bundle); | ||
verifyArtifactSignature(bundle, publicKey, data); | ||
(0, verify_1.verifyTLogEntries)(bundle, this.tlogKeys); | ||
} | ||
async getPublicKey(bundle) { | ||
var _a, _b; | ||
let publicKey; | ||
switch ((_b = (_a = bundle.verificationMaterial) === null || _a === void 0 ? void 0 : _a.content) === null || _b === void 0 ? void 0 : _b.$case) { | ||
case 'x509CertificateChain': | ||
publicKey = getSigningCertificate(bundle.verificationMaterial.content.x509CertificateChain); | ||
break; | ||
case 'publicKey': | ||
publicKey = await this.getExternalPublicKey(bundle.verificationMaterial.content.publicKey.hint); | ||
break; | ||
default: | ||
throw new error_1.InvalidBundleError('no verification material found'); | ||
} | ||
if (!publicKey) { | ||
throw new error_1.VerificationError('no public key found for signature verification'); | ||
} | ||
return publicKey; | ||
} | ||
} | ||
@@ -21,3 +42,3 @@ exports.Verifier = Verifier; | ||
// content and delegates to the appropriate signature verification function. | ||
function verifyArtifactSignature(bundle, data) { | ||
function verifyArtifactSignature(bundle, publicKey, data) { | ||
var _a; | ||
@@ -27,11 +48,11 @@ switch ((_a = bundle.content) === null || _a === void 0 ? void 0 : _a.$case) { | ||
if (!data) { | ||
throw new error_1.VerificationError('No data provided for message signature verification'); | ||
throw new error_1.VerificationError('no data provided for message signature verification'); | ||
} | ||
verifyMessageSignature(bundle, data); | ||
verifyMessageSignature(bundle.content.messageSignature, publicKey, data); | ||
break; | ||
case 'dsseEnvelope': | ||
verifyDSSESignature(bundle); | ||
verifyDSSESignature(bundle.content.dsseEnvelope, publicKey); | ||
break; | ||
default: | ||
throw new error_1.VerificationError('Bundle is invalid'); | ||
throw new error_1.InvalidBundleError('no content found'); | ||
} | ||
@@ -41,13 +62,7 @@ } | ||
// Verifies the signature found in the bundle against the provided data. | ||
function verifyMessageSignature(bundle, data) { | ||
var _a; | ||
if (((_a = bundle.content) === null || _a === void 0 ? void 0 : _a.$case) !== 'messageSignature') { | ||
throw new error_1.VerificationError('No message signature found in bundle'); | ||
} | ||
function verifyMessageSignature(messageSignature, publicKey, data) { | ||
// Extract signature for message | ||
const signature = bundle.content.messageSignature.signature; | ||
// Get signing certificate containing public key | ||
const publicKey = getSigningCertificate(bundle); | ||
const signature = messageSignature.signature; | ||
if (!util_1.crypto.verifyBlob(data, publicKey, signature)) { | ||
throw new error_1.VerificationError('Artifact signature verification failed'); | ||
throw new error_1.VerificationError('artifact signature verification failed'); | ||
} | ||
@@ -58,21 +73,14 @@ } | ||
// signature in the envelope. | ||
function verifyDSSESignature(bundle) { | ||
var _a; | ||
if (((_a = bundle.content) === null || _a === void 0 ? void 0 : _a.$case) !== 'dsseEnvelope') { | ||
throw new error_1.VerificationError('Bundle is not a DSSE envelope'); | ||
} | ||
function verifyDSSESignature(envelope, publicKey) { | ||
// Construct payload over which the signature was originally created | ||
const payloadType = bundle.content.dsseEnvelope.payloadType; | ||
const payload = bundle.content.dsseEnvelope.payload; | ||
const { payloadType, payload } = envelope; | ||
const data = util_1.dsse.preAuthEncoding(payloadType, payload); | ||
// Extract signature from DSSE envelope | ||
if (bundle.content.dsseEnvelope.signatures.length < 1) { | ||
throw new error_1.VerificationError('No signatures found in DSSE envelope'); | ||
if (envelope.signatures.length < 1) { | ||
throw new error_1.InvalidBundleError('no signatures found in DSSE envelope'); | ||
} | ||
// TODO: Support multiple signatures | ||
const signature = bundle.content.dsseEnvelope.signatures[0].sig; | ||
// Get signing certificate containing public key | ||
const publicKey = getSigningCertificate(bundle); | ||
// Only support a single signature in DSSE | ||
const signature = envelope.signatures[0].sig; | ||
if (!util_1.crypto.verifyBlob(data, publicKey, signature)) { | ||
throw new error_1.VerificationError('Artifact signature verification failed'); | ||
throw new error_1.VerificationError('artifact signature verification failed'); | ||
} | ||
@@ -82,12 +90,5 @@ } | ||
// PEM-encoded string. | ||
function getSigningCertificate(bundle) { | ||
var _a, _b; | ||
if (((_b = (_a = bundle.verificationMaterial) === null || _a === void 0 ? void 0 : _a.content) === null || _b === void 0 ? void 0 : _b.$case) !== 'x509CertificateChain') { | ||
throw new error_1.VerificationError('No certificate found in bundle'); | ||
} | ||
const signingCert = bundle.verificationMaterial.content.x509CertificateChain.certificates[0]; | ||
if (!signingCert) { | ||
throw new error_1.VerificationError('No certificate found in bundle'); | ||
} | ||
return util_1.pem.fromDER(signingCert.rawBytes); | ||
function getSigningCertificate(chain) { | ||
const signingCert = chain.certificates[0]; | ||
return signingCert ? util_1.pem.fromDER(signingCert.rawBytes) : undefined; | ||
} |
{ | ||
"name": "sigstore", | ||
"version": "0.0.1-alpha.4", | ||
"version": "0.0.1-alpha.5", | ||
"description": "code-signing for npm packages", | ||
@@ -54,3 +54,6 @@ "main": "dist/index.js", | ||
"make-fetch-happen": "^11.0.1" | ||
}, | ||
"engines": { | ||
"node": "^14.17.0 || ^16.13.0 || >=18.0.0" | ||
} | ||
} |
@@ -5,3 +5,5 @@ # sigstore-js | ||
A tool for signing and verifying npm packages. | ||
A tool for signing and verifying signatures using JavaScript. | ||
One of the intended uses is to sign and verify npm packages | ||
but it can be used to sign and verify any file. | ||
@@ -8,0 +10,0 @@ ## Features |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
322158
97
6028
326
4