@@ -80,13 +80,13 @@ export class Sigstore {
8080 await core . group ( `Signing attestation manifest ${ attestationRef } ` , async ( ) => {
8181 // prettier-ignore
8282 const cosignArgs = [
83- 'sign' ,
84- '--yes' ,
85- '--oidc-provider' , 'github-actions' ,
86- '--registry-referrers-mode' , 'oci-1-1' ,
87- '--new-bundle-format' ,
88- '--use-signing-config'
89- ] ;
83+ 'sign' ,
84+ '--yes' ,
85+ '--oidc-provider' , 'github-actions' ,
86+ '--registry-referrers-mode' , 'oci-1-1' ,
87+ '--new-bundle-format' ,
88+ '--use-signing-config'
89+ ] ;
9090 if ( noTransparencyLog ) {
9191 cosignArgs . push ( '--tlog-upload=false' ) ;
9292 }
@@ -106,7 +106,8 @@ export class Sigstore {
106106 const errorMessages = signResult . errors . map ( e => `- [${ e . code } ] ${ e . message } : ${ e . detail } ` ) . join ( '\n' ) ;
107107 throw new Error ( `Cosign sign command failed with errors:\n${ errorMessages } ` ) ;
108108 } else {
109- throw new Error ( `Cosign sign command failed with exit code ${ execRes . exitCode } ` ) ;
109+ // prettier-ignore
110+ throw new Error ( `Cosign sign command failed with: ${ execRes . stderr . trim ( ) . split ( / \r ? \n / ) . filter ( line => line . length > 0 ) . pop ( ) ?? 'unknown error' } ` ) ;
110111 }
111112 }
112113 const parsedBundle = Sigstore . parseBundle ( bundleFromJSON ( signResult . bundle ) ) ;
@@ -127,69 +128,95 @@ export class Sigstore {
127128 return result ;
128129 }
129130
130- public async verifySignedManifests ( opts : VerifySignedManifestsOpts , signed : Record < string , SignAttestationManifestsResult > ) : Promise < Record < string , VerifySignedManifestsResult > > {
131+ public async verifySignedManifests ( signedManifestsResult : Record < string , SignAttestationManifestsResult > , opts : VerifySignedManifestsOpts ) : Promise < Record < string , VerifySignedManifestsResult > > {
132+ const result : Record < string , VerifySignedManifestsResult > = { } ;
133+ for ( const [ attestationRef , signedRes ] of Object . entries ( signedManifestsResult ) ) {
134+ await core . group ( `Verifying signature of ${ attestationRef } ` , async ( ) => {
135+ const verifyResult = await this . verifyImageAttestation ( attestationRef , {
136+ noTransparencyLog : opts . noTransparencyLog || ! signedRes . tlogID ,
137+ certificateIdentityRegexp : opts . certificateIdentityRegexp ,
138+ retries : opts . retries
139+ } ) ;
140+ core . info ( `Signature manifest verified: https://oci.dag.dev/?image=${ signedRes . imageName } @${ verifyResult . signatureManifestDigest } ` ) ;
141+ result [ attestationRef ] = verifyResult ;
142+ } ) ;
143+ }
144+ return result ;
145+ }
146+
147+ public async verifyImageAttestations ( image : string , opts : VerifySignedManifestsOpts ) : Promise < Record < string , VerifySignedManifestsResult > > {
131148 const result : Record < string , VerifySignedManifestsResult > = { } ;
149+
150+ const attestationDigests = await this . imageTools . attestationDigests ( image ) ;
151+ if ( attestationDigests . length === 0 ) {
152+ throw new Error ( `No attestation manifests found for ${ image } ` ) ;
153+ }
154+
155+ const imageName = image . split ( ':' , 1 ) [ 0 ] ;
156+ for ( const attestationDigest of attestationDigests ) {
157+ const attestationRef = `${ imageName } @${ attestationDigest } ` ;
158+ const verifyResult = await this . verifyImageAttestation ( attestationRef , opts ) ;
159+ core . info ( `Signature manifest verified: https://oci.dag.dev/?image=${ imageName } @${ verifyResult . signatureManifestDigest } ` ) ;
160+ result [ attestationRef ] = verifyResult ;
161+ }
162+
163+ return result ;
164+ }
165+
166+ public async verifyImageAttestation ( attestationRef : string , opts : VerifySignedManifestsOpts ) : Promise < VerifySignedManifestsResult > {
132167 const retries = opts . retries ?? 15 ;
133168
134169 if ( ! ( await this . cosign . isAvailable ( ) ) ) {
135170 throw new Error ( 'Cosign is required to verify signed manifests' ) ;
136171 }
137172
173+ // prettier-ignore
174+ const cosignArgs = [
175+ 'verify' ,
176+ '--experimental-oci11' ,
177+ '--new-bundle-format' ,
178+ '--certificate-oidc-issuer' , 'https://token.actions.githubusercontent.com' ,
179+ '--certificate-identity-regexp' , opts . certificateIdentityRegexp
180+ ] ;
181+ if ( opts . noTransparencyLog ) {
182+ // skip tlog verification but still verify the signed timestamp
183+ cosignArgs . push ( '--use-signed-timestamps' , '--insecure-ignore-tlog' ) ;
184+ }
185+
138186 let lastError : Error | undefined ;
139- for ( const [ attestationRef , signedRes ] of Object . entries ( signed ) ) {
140- await core . group ( `Verifying signature of ${ attestationRef } ` , async ( ) => {
141- // prettier-ignore
142- const cosignArgs = [
143- 'verify' ,
144- '--experimental-oci11' ,
145- '--new-bundle-format' ,
146- '--certificate-oidc-issuer' , 'https://token.actions.githubusercontent.com' ,
147- '--certificate-identity-regexp' , opts . certificateIdentityRegexp
148- ] ;
149- if ( ! signedRes . tlogID ) {
150- // skip tlog verification but still verify the signed timestamp
151- cosignArgs . push ( '--use-signed-timestamps' , '--insecure-ignore-tlog' ) ;
152- }
153- core . info ( `[command]cosign ${ [ ...cosignArgs , attestationRef ] . join ( ' ' ) } ` ) ;
154- for ( let attempt = 0 ; attempt < retries ; attempt ++ ) {
155- const execRes = await Exec . getExecOutput ( 'cosign' , [ '--verbose' , ...cosignArgs , attestationRef ] , {
156- ignoreReturnCode : true ,
157- silent : true ,
158- env : Object . assign ( { } , process . env , {
159- COSIGN_EXPERIMENTAL : '1'
160- } ) as { [ key : string ] : string }
161- } ) ;
162- const verifyResult = Cosign . parseCommandOutput ( execRes . stderr . trim ( ) ) ;
163- if ( execRes . exitCode === 0 ) {
164- result [ attestationRef ] = {
165- cosignArgs : cosignArgs ,
166- signatureManifestDigest : verifyResult . signatureManifestDigest !
167- } ;
168- lastError = undefined ;
169- core . info ( `Signature manifest verified: https://oci.dag.dev/?image=${ signedRes . imageName } @${ verifyResult . signatureManifestDigest } ` ) ;
170- break ;
187+ core . info ( `[command]cosign ${ [ ...cosignArgs , attestationRef ] . join ( ' ' ) } ` ) ;
188+ for ( let attempt = 0 ; attempt < retries ; attempt ++ ) {
189+ const execRes = await Exec . getExecOutput ( 'cosign' , [ '--verbose' , ...cosignArgs , attestationRef ] , {
190+ ignoreReturnCode : true ,
191+ silent : true ,
192+ env : Object . assign ( { } , process . env , {
193+ COSIGN_EXPERIMENTAL : '1'
194+ } ) as { [ key : string ] : string }
195+ } ) ;
196+ const verifyResult = Cosign . parseCommandOutput ( execRes . stderr . trim ( ) ) ;
197+ if ( execRes . exitCode === 0 ) {
198+ return {
199+ cosignArgs : cosignArgs ,
200+ signatureManifestDigest : verifyResult . signatureManifestDigest !
201+ } ;
202+ } else {
203+ if ( verifyResult . errors && verifyResult . errors . length > 0 ) {
204+ const errorMessages = verifyResult . errors . map ( e => `- [${ e . code } ] ${ e . message } : ${ e . detail } ` ) . join ( '\n' ) ;
205+ lastError = new Error ( `Cosign verify command failed with errors:\n${ errorMessages } ` ) ;
206+ if ( verifyResult . errors . some ( e => e . code === 'MANIFEST_UNKNOWN' ) ) {
207+ core . info ( `Cosign verify command failed with MANIFEST_UNKNOWN, retrying attempt ${ attempt + 1 } /${ retries } ...\n${ errorMessages } ` ) ;
208+ await new Promise ( res => setTimeout ( res , Math . pow ( 2 , attempt ) * 100 ) ) ;
171209 } else {
172- if ( verifyResult . errors && verifyResult . errors . length > 0 ) {
173- const errorMessages = verifyResult . errors . map ( e => `- [${ e . code } ] ${ e . message } : ${ e . detail } ` ) . join ( '\n' ) ;
174- lastError = new Error ( `Cosign verify command failed with errors:\n${ errorMessages } ` ) ;
175- if ( verifyResult . errors . some ( e => e . code === 'MANIFEST_UNKNOWN' ) ) {
176- core . info ( `Cosign verify command failed with MANIFEST_UNKNOWN, retrying attempt ${ attempt + 1 } /${ retries } ...\n${ errorMessages } ` ) ;
177- await new Promise ( res => setTimeout ( res , Math . pow ( 2 , attempt ) * 100 ) ) ;
178- } else {
179- throw lastError ;
180- }
181- } else {
182- throw new Error ( `Cosign verify command failed: ${ execRes . stderr } ` ) ;
183- }
210+ throw lastError ;
184211 }
212+ } else {
213+ // prettier-ignore
214+ throw new Error ( `Cosign verify command failed with: ${ execRes . stderr . trim ( ) . split ( / \r ? \n / ) . filter ( line => line . length > 0 ) . pop ( ) ?? 'unknown error' } ` ) ;
185215 }
186- } ) ;
187- }
188- if ( lastError ) {
189- throw lastError ;
216+ }
190217 }
191218
192- return result ;
219+ throw lastError ;
193220 }
194221
195222 public async signProvenanceBlobs ( opts : SignProvenanceBlobsOpts ) : Promise < Record < string , SignProvenanceBlobsResult > > {
@@ -245,12 +272,12 @@ export class Sigstore {
245272 return result ;
246273 }
247274
248- public async verifySignedArtifacts ( opts : VerifySignedArtifactsOpts , signed : Record < string , SignProvenanceBlobsResult > ) : Promise < Record < string , VerifySignedArtifactsResult > > {
275+ public async verifySignedArtifacts ( signedArtifactsResult : Record < string , SignProvenanceBlobsResult > , opts : VerifySignedArtifactsOpts ) : Promise < Record < string , VerifySignedArtifactsResult > > {
249276 const result : Record < string , VerifySignedArtifactsResult > = { } ;
250277 if ( ! ( await this . cosign . isAvailable ( ) ) ) {
251278 throw new Error ( 'Cosign is required to verify signed artifacts' ) ;
252279 }
253- for ( const [ provenancePath , signedRes ] of Object . entries ( signed ) ) {
280+ for ( const [ provenancePath , signedRes ] of Object . entries ( signedArtifactsResult ) ) {
254281 const baseDir = path . dirname ( provenancePath ) ;
255282 await core . group ( `Verifying signature bundle ${ signedRes . bundlePath } ` , async ( ) => {
256283 for ( const subject of signedRes . subjects ) {
@@ -263,7 +290,7 @@ export class Sigstore {
263290 '--certificate-oidc-issuer' , 'https://token.actions.githubusercontent.com' ,
264291 '--certificate-identity-regexp' , opts . certificateIdentityRegexp
265292 ]
266- if ( ! signedRes . tlogID ) {
293+ if ( opts . noTransparencyLog || ! signedRes . tlogID ) {
267294 // if there is no tlog entry, we skip tlog verification but still verify the signed timestamp
268295 cosignArgs . push ( '--use-signed-timestamps' , '--insecure-ignore-tlog' ) ;
269296 }
0 commit comments