@@ -19,8 +19,10 @@ import fs from 'fs';
1919import path from 'path' ;
2020
2121import * as core from '@actions/core' ;
22- import { bundleFromJSON , bundleToJSON } from '@sigstore/bundle' ;
22+ import { bundleFromJSON , bundleToJSON , SerializedBundle } from '@sigstore/bundle' ;
2323import { Artifact , Bundle , CIContextProvider , DSSEBundleBuilder , FulcioSigner , RekorWitness , TSAWitness , Witness } from '@sigstore/sign' ;
24+ import * as tuf from '@sigstore/tuf' ;
25+ import { toSignedEntity , toTrustMaterial , Verifier } from '@sigstore/verify' ;
2426
2527import { Cosign } from '../cosign/cosign' ;
2628import { Exec } from '../exec' ;
@@ -39,6 +41,8 @@ import {
3941 SignProvenanceBlobsOpts ,
4042 SignProvenanceBlobsResult ,
4143 TSASERVER_URL ,
44+ VerifyArtifactOpts ,
45+ VerifyArtifactResult ,
4246 VerifySignedArtifactsOpts ,
4347 VerifySignedArtifactsResult ,
4448 VerifySignedManifestsOpts ,
@@ -329,6 +333,48 @@ export class Sigstore {
329333 return result ;
330334 }
331335
336+ public async verifyArtifact ( artifactPath : string , bundlePath : string , opts ?: VerifyArtifactOpts ) : Promise < VerifyArtifactResult > {
337+ core . info ( `Verifying keyless verification bundle signature` ) ;
338+ const parsedBundle = JSON . parse ( fs . readFileSync ( bundlePath , 'utf-8' ) ) as SerializedBundle ;
339+ const bundle = bundleFromJSON ( parsedBundle ) ;
340+
341+ core . info ( `Fetching Sigstore TUF trusted root metadata` ) ;
342+ const trustedRoot = await tuf . getTrustedRoot ( ) ;
343+ const trustMaterial = toTrustMaterial ( trustedRoot ) ;
344+
345+ try {
346+ core . info ( `Verifying artifact signature` ) ;
347+ const signedEntity = toSignedEntity ( bundle , fs . readFileSync ( artifactPath ) ) ;
348+ const signingCert = Sigstore . parseCertificate ( bundle ) ;
349+
350+ // collect transparency log ID if available
351+ const tlogEntries = bundle . verificationMaterial . tlogEntries ;
352+ const tlogID = tlogEntries . length > 0 ? tlogEntries [ 0 ] . logIndex : undefined ;
353+
354+ // TODO: remove when subjectAlternativeName check with regex is supported: https://github.com/sigstore/sigstore-js/pull/1556
355+ if ( opts ?. subjectAlternativeName && opts ?. subjectAlternativeName instanceof RegExp ) {
356+ if ( ! signingCert . subjectAltName ?. match ( opts . subjectAlternativeName ) ) {
357+ throw new Error ( `Signing certificate subjectAlternativeName "${ signingCert . subjectAltName } " does not match expected pattern` ) ;
358+ }
359+ }
360+
361+ const verifier = new Verifier ( trustMaterial ) ;
362+ const signer = verifier . verify ( signedEntity , {
363+ subjectAlternativeName : opts ?. subjectAlternativeName && typeof opts . subjectAlternativeName === 'string' ? opts . subjectAlternativeName : undefined ,
364+ extensions : opts ?. issuer ? { issuer : opts . issuer } : undefined
365+ } ) ;
366+ core . debug ( `Sigstore.verifyArtifact signer: ${ JSON . stringify ( signer ) } ` ) ;
367+
368+ return {
369+ payload : parsedBundle ,
370+ certificate : signingCert . toString ( ) ,
371+ tlogID : tlogID
372+ } ;
373+ } catch ( err ) {
374+ throw new Error ( `Failed to verify artifact signature: ${ err } ` ) ;
375+ }
376+ }
377+
332378 private signingEndpoints ( noTransparencyLog ?: boolean ) : Endpoints {
333379 noTransparencyLog = Sigstore . noTransparencyLog ( noTransparencyLog ) ;
334380 core . info ( `Upload to transparency log: ${ noTransparencyLog ? 'disabled' : 'enabled' } ` ) ;
@@ -410,6 +456,20 @@ export class Sigstore {
410456 }
411457
412458 private static parseBundle ( bundle : Bundle ) : ParsedBundle {
459+ const signingCert = Sigstore . parseCertificate ( bundle ) ;
460+
461+ // collect transparency log ID if available
462+ const tlogEntries = bundle . verificationMaterial . tlogEntries ;
463+ const tlogID = tlogEntries . length > 0 ? tlogEntries [ 0 ] . logIndex : undefined ;
464+
465+ return {
466+ payload : bundleToJSON ( bundle ) ,
467+ certificate : signingCert . toString ( ) ,
468+ tlogID : tlogID
469+ } ;
470+ }
471+
472+ private static parseCertificate ( bundle : Bundle ) : X509Certificate {
413473 let certBytes : Buffer ;
414474 switch ( bundle . verificationMaterial . content . $case ) {
415475 case 'x509CertificateChain' :
@@ -421,17 +481,6 @@ export class Sigstore {
421481 default :
422482 throw new Error ( 'Bundle must contain an x509 certificate' ) ;
423483 }
424-
425- const signingCert = new X509Certificate ( certBytes ) ;
426-
427- // collect transparency log ID if available
428- const tlogEntries = bundle . verificationMaterial . tlogEntries ;
429- const tlogID = tlogEntries . length > 0 ? tlogEntries [ 0 ] . logIndex : undefined ;
430-
431- return {
432- payload : bundleToJSON ( bundle ) ,
433- certificate : signingCert . toString ( ) ,
434- tlogID : tlogID
435- } ;
484+ return new X509Certificate ( certBytes ) ;
436485 }
437486}
0 commit comments