From 06330a793c2d2f1d58f969c9693eaef9f1816254 Mon Sep 17 00:00:00 2001 From: Brian DeHamer Date: Mon, 15 Aug 2022 13:27:34 -0700 Subject: [PATCH] update signature bundle format Signed-off-by: Brian DeHamer --- .github/workflows/build-sign-verify.yml | 19 +++------ src/cli/index.ts | 41 +++++++++---------- src/dsse.test.ts | 31 +++++++++------ src/dsse.ts | 28 +++++++++++-- src/sign.test.ts | 21 +++++++--- src/sign.ts | 53 +++++++++---------------- src/sigstore.ts | 4 +- 7 files changed, 101 insertions(+), 96 deletions(-) diff --git a/.github/workflows/build-sign-verify.yml b/.github/workflows/build-sign-verify.yml index a1d8f0121..55cab9809 100644 --- a/.github/workflows/build-sign-verify.yml +++ b/.github/workflows/build-sign-verify.yml @@ -83,7 +83,7 @@ jobs: npm pack - name: Sign package run: | - ./bin/sigstore.js sign sigstore-0.0.0.tgz > artifact.sig + ./bin/sigstore.js sign sigstore-0.0.0.tgz > bundle.sigstore - name: Archive package uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3 with: @@ -92,13 +92,8 @@ jobs: - name: Archive signature uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3 with: - name: signature - path: artifact.sig - - name: Archive certificate - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3 - with: - name: certificate - path: signingcert.pem + name: bundle + path: bundle.sigstore verify-signature: name: Verify Signature @@ -121,14 +116,10 @@ jobs: - name: Download signature uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741 # v3 with: - name: signature - - name: Download certificate - uses: actions/download-artifact@fb598a63ae348fa914e94cd0ff38f362e927b741 # v3 - with: - name: certificate + name: bundle - name: Compile sigstore run: | npm run build - name: Verify artifact signature run: | - ./bin/sigstore.js verify sigstore-0.0.0.tgz artifact.sig signingcert.pem + ./bin/sigstore.js verify sigstore-0.0.0.tgz bundle.sigstore diff --git a/src/cli/index.ts b/src/cli/index.ts index 86eb205b0..ddae39836 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -26,10 +26,10 @@ async function cli(args: string[]) { await signDSSE(args[1], args[2]); break; case 'verify': - await verify(args[1], args[2], args[3]); + await verify(args[1], args[2]); break; case 'verify-dsse': - await verifyDSSE(args[1], args[2]); + await verifyDSSE(args[1]); break; default: throw 'Unknown command'; @@ -43,27 +43,24 @@ const signOptions = { async function sign(artifactPath: string) { const buffer = fs.readFileSync(artifactPath); - const signature = await sigstore.sign(buffer, signOptions); - const cert = base64Decode(signature.cert); - await fs.writeFileSync('signingcert.pem', cert, { flag: 'wx' }); - console.log(signature.base64Signature); + const bundle = await sigstore.sign(buffer, signOptions); + console.log(JSON.stringify(bundle)); } async function signDSSE(artifactPath: string, payloadType: string) { const buffer = fs.readFileSync(artifactPath); - const envelope = await dsse.sign(buffer, payloadType, signOptions); - console.log(JSON.stringify(envelope)); + const bundle = await dsse.sign(buffer, payloadType, signOptions); + console.log(JSON.stringify(bundle)); } -async function verify( - artifactPath: string, - signaturePath: string, - certPath: string -) { +async function verify(artifactPath: string, bundlePath: string) { const payload = fs.readFileSync(artifactPath); - const sig = fs.readFileSync(signaturePath); - const cert = fs.readFileSync(certPath); - const result = await sigstore.verify(payload, sig.toString('utf8'), cert); + const bundleFile = fs.readFileSync(bundlePath); + const bundle = JSON.parse(bundleFile.toString('utf-8')); + + const sig = bundle.attestation.base64Signature; + const cert = base64Decode(bundle.cert); + const result = await sigstore.verify(payload, sig, cert); if (result) { console.error('Verified OK'); @@ -72,13 +69,11 @@ async function verify( } } -async function verifyDSSE(artifactPath: string, certPath: string) { - const envelope = fs.readFileSync(artifactPath); - const cert = fs.readFileSync(certPath); - const result = await dsse.verify( - JSON.parse(envelope.toString('utf-8')), - cert - ); +async function verifyDSSE(bundlePath: string) { + const bundleFile = fs.readFileSync(bundlePath); + const bundle = JSON.parse(bundleFile.toString('utf-8')); + const cert = base64Decode(bundle.cert); + const result = await dsse.verify(bundle.attestation, cert); if (result) { console.error('Verified OK'); diff --git a/src/dsse.test.ts b/src/dsse.test.ts index c1152c315..4d8f49526 100644 --- a/src/dsse.test.ts +++ b/src/dsse.test.ts @@ -15,7 +15,7 @@ limitations under the License. */ import nock from 'nock'; import * as dsse from './dsse'; -import { base64Decode } from './util'; +import { base64Decode, base64Encode } from './util'; describe('sign', () => { const fulcioBaseURL = 'http://localhost:8001'; @@ -104,19 +104,24 @@ describe('sign', () => { .reply(201, rekorEntry); }); - it('returns an envelope', async () => { - const envelope = await dsse.sign(payload, payloadType, options); - - expect(envelope).toEqual({ - payload: payload.toString('base64'), - payloadType: payloadType, - signatures: [ - { - keyid: '', - sig: expect.any(String), - }, - ], + it('returns a DSSE bundle', async () => { + const bundle = await dsse.sign(payload, payloadType, options); + + expect(bundle.attestationType).toEqual('attestation/dsse'); + expect(bundle.attestation.payload).toEqual(payload.toString('base64')); + expect(bundle.attestation.payloadType).toEqual(payloadType); + expect(bundle.attestation.signatures).toHaveLength(1); + expect(bundle.attestation.signatures[0]).toEqual({ + keyid: '', + sig: expect.any(String), }); + expect(bundle.cert).toEqual(base64Encode(certificate)); + expect(bundle.integratedTime).toEqual(rekorEntry[uuid].integratedTime); + expect(bundle.signedEntryTimestamp).toEqual( + rekorEntry[uuid].verification.signedEntryTimestamp + ); + expect(bundle.logID).toEqual(rekorEntry[uuid].logID); + expect(bundle.logIndex).toEqual(rekorEntry[uuid].logIndex); }); }); diff --git a/src/dsse.ts b/src/dsse.ts index 6b0a8cefe..755c0162a 100644 --- a/src/dsse.ts +++ b/src/dsse.ts @@ -27,13 +27,23 @@ export interface Envelope { signatures: Signature[]; } +export interface DSSEBundle { + attestationType: string; + attestation: Envelope; + cert: string; + signedEntryTimestamp: string; + integratedTime: number; + logIndex: number; + logID: string; +} + export async function sign( payload: Buffer, payloadType: string, options: sigstore.SignOptions = {} -): Promise { +): Promise { const paeBuffer = pae(payloadType, payload); - const signedPayload = await sigstore.sign(paeBuffer, options); + const bundle = await sigstore.sign(paeBuffer, options); const envelope: Envelope = { payloadType: payloadType, @@ -41,12 +51,22 @@ export async function sign( signatures: [ { keyid: '', - sig: signedPayload.base64Signature, + sig: bundle.attestation.base64Signature, }, ], }; - return envelope; + const dsseBundle: DSSEBundle = { + attestationType: 'attestation/dsse', + attestation: envelope, + cert: bundle.cert, + signedEntryTimestamp: bundle.signedEntryTimestamp, + integratedTime: bundle.integratedTime, + logIndex: bundle.logIndex, + logID: bundle.logID, + }; + + return dsseBundle; } export async function verify( diff --git a/src/sign.test.ts b/src/sign.test.ts index 5c2a507b1..658f2be1b 100644 --- a/src/sign.test.ts +++ b/src/sign.test.ts @@ -16,6 +16,7 @@ limitations under the License. import nock from 'nock'; import { Fulcio, Rekor } from './client'; import { Signer } from './sign'; +import { base64Encode } from './util'; describe('Signer', () => { const fulcioBaseURL = 'http://localhost:8001'; @@ -129,12 +130,20 @@ describe('Signer', () => { }); it('returns a signature bundle', async () => { - const signedPayload = await subject.sign(payload); - - expect(signedPayload).toBeTruthy(); - expect(signedPayload.base64Signature).toBeTruthy(); - expect(signedPayload.cert).toBe(b64Cert); - expect(signedPayload.bundle).toBeDefined(); + const bundle = await subject.sign(payload); + + expect(bundle).toBeTruthy(); + expect(bundle.attestationType).toBe('attestation/blob'); + expect(bundle.attestation.payloadHash).toBeTruthy(); + expect(bundle.attestation.payloadAlgorithm).toBe('sha256'); + expect(bundle.attestation.base64Signature).toBeTruthy(); + expect(bundle.cert).toBe(base64Encode(certificate)); + expect(bundle.integratedTime).toBe(rekorEntry[uuid].integratedTime); + expect(bundle.logIndex).toBe(rekorEntry[uuid].logIndex); + expect(bundle.logID).toBe(rekorEntry[uuid].logID); + expect(bundle.signedEntryTimestamp).toBe( + rekorEntry[uuid].verification.signedEntryTimestamp + ); }); }); diff --git a/src/sign.ts b/src/sign.ts index 35e980069..edaac007f 100644 --- a/src/sign.ts +++ b/src/sign.ts @@ -13,10 +13,10 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ -import { Entry, Fulcio, Rekor } from './client'; +import { Fulcio, Rekor } from './client'; import { generateKeyPair, hash, signBlob } from './crypto'; import { Provider } from './identity'; -import { base64Decode, base64Encode, extractJWTSubject } from './util'; +import { base64Encode, extractJWTSubject } from './util'; export interface SignOptions { fulcio: Fulcio; @@ -24,19 +24,11 @@ export interface SignOptions { identityProviders: Provider[]; } -export interface SignedPayload { - base64Signature: string; +export interface SigstoreBundle { + attestationType: string; + attestation: Record; cert: string; - bundle?: RekorBundle; -} - -export interface RekorBundle { signedEntryTimestamp: string; - payload: RekorPayload; -} - -export interface RekorPayload { - body: object; integratedTime: number; logIndex: number; logID: string; @@ -54,7 +46,7 @@ export class Signer { this.identityProviders = options.identityProviders; } - public async sign(payload: Buffer): Promise { + public async sign(payload: Buffer): Promise { // Create emphemeral key pair const keypair = generateKeyPair(); @@ -98,12 +90,21 @@ export class Signer { `https://rekor.sigstore.dev/api/v1/log/entries/${entry.uuid}` ); - const signedPayload: SignedPayload = { - base64Signature: signature, + const bundle: SigstoreBundle = { + attestationType: 'attestation/blob', + attestation: { + payloadHash: digest, + payloadAlgorithm: 'sha256', + base64Signature: signature, + }, cert: b64Certificate, - bundle: entryToBundle(entry), + signedEntryTimestamp: entry.verification.signedEntryTimestamp, + integratedTime: entry.integratedTime, + logID: entry.logID, + logIndex: entry.logIndex, }; - return signedPayload; + + return bundle; } private async getIdentityToken(): Promise { @@ -123,19 +124,3 @@ export class Signer { throw new Error(`Identity token providers failed: ${aggErrs}`); } } - -function entryToBundle(entry: Entry): RekorBundle | undefined { - if (!entry.verification) { - return; - } - - return { - signedEntryTimestamp: entry.verification.signedEntryTimestamp, - payload: { - body: JSON.parse(base64Decode(entry.body)), - integratedTime: entry.integratedTime, - logIndex: entry.logIndex, - logID: entry.logID, - }, - }; -} diff --git a/src/sigstore.ts b/src/sigstore.ts index 7c6160fc1..4e06d81df 100644 --- a/src/sigstore.ts +++ b/src/sigstore.ts @@ -16,7 +16,7 @@ limitations under the License. import { KeyLike } from 'crypto'; import { Fulcio, Rekor } from './client'; import identity, { Provider } from './identity'; -import { SignedPayload, Signer } from './sign'; +import { Signer, SigstoreBundle } from './sign'; import { Verifier } from './verify'; export interface SignOptions { @@ -40,7 +40,7 @@ type IdentityProviderOptions = Pick< export async function sign( payload: Buffer, options: SignOptions = {} -): Promise { +): Promise { const fulcio = new Fulcio({ baseURL: options.fulcioBaseURL }); const rekor = new Rekor({ baseURL: options.rekorBaseURL }); const idps = configureIdentityProviders(options);