Skip to content

Commit

Permalink
add v0.3 bundle support to DSSEBundleBuilder (#1102)
Browse files Browse the repository at this point in the history
Signed-off-by: Brian DeHamer <[email protected]>
  • Loading branch information
bdehamer authored Apr 3, 2024
1 parent 22baf75 commit 77e9e17
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/little-beers-notice.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@sigstore/sign": minor
---

Updates the `DSSEBundleBuilder` with a new `singleCertificate` option which will trigger the creation of v0.3 Sigstore bundles
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/sign/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"@types/make-fetch-happen": "^10.0.4"
},
"dependencies": {
"@sigstore/bundle": "^2.2.0",
"@sigstore/bundle": "^2.3.0",
"@sigstore/core": "^1.0.0",
"@sigstore/protobuf-specs": "^0.3.0",
"make-fetch-happen": "^13.0.0"
Expand Down
33 changes: 33 additions & 0 deletions packages/sign/src/__tests__/bundler/bundle.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,37 @@ describe('toDSSEBundle', () => {
).toEqual(pem.toDER(certificate));
});
});

describe('when the single-certificate representation is requested', () => {
const signature = {
key: {
$case: 'x509Certificate',
certificate: certificate,
},
signature: sigBytes,
} satisfies Signature;

it('returns a valid DSSE bundle', () => {
const b = toDSSEBundle(artifact, signature, true);

expect(b).toBeTruthy();
expect(b.mediaType).toEqual(
'application/vnd.dev.sigstore.bundle.v0.3+json'
);

assert(b.content?.$case === 'dsseEnvelope');
expect(b.content.dsseEnvelope).toBeTruthy();
expect(b.content.dsseEnvelope.payloadType).toEqual(artifact.type);
expect(b.content.dsseEnvelope.payload).toEqual(artifact.data);
expect(b.content.dsseEnvelope.signatures).toHaveLength(1);
expect(b.content.dsseEnvelope.signatures[0].sig).toEqual(sigBytes);
expect(b.content.dsseEnvelope.signatures[0].keyid).toEqual('');

expect(b.verificationMaterial).toBeTruthy();
assert(b.verificationMaterial.content?.$case === 'certificate');
expect(b.verificationMaterial.content?.certificate.rawBytes).toEqual(
pem.toDER(certificate)
);
});
});
});
46 changes: 46 additions & 0 deletions packages/sign/src/__tests__/bundler/dsse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,5 +128,51 @@ describe('DSSEBundleBuilder', () => {
);
});
});

describe('when a single-certificate bundle is requested', () => {
const subject = new DSSEBundleBuilder({
signer: signer,
witnesses: [],
singleCertificate: true,
});
const artifact = {
data: Buffer.from('artifact'),
type: 'text/plain',
} satisfies Artifact;

it('invokes the signer', async () => {
await subject.create(artifact);

const expectedBlob = dsse.preAuthEncoding(artifact.type, artifact.data);
expect(signer.sign).toHaveBeenCalledWith(expectedBlob);
});

it('returns a bundle', async () => {
const b = await subject.create(artifact);

expect(b).toBeTruthy();
expect(b.mediaType).toEqual(
'application/vnd.dev.sigstore.bundle.v0.3+json'
);

expect(b.content.dsseEnvelope).toBeTruthy();
expect(b.content.dsseEnvelope.payload).toEqual(artifact.data);
expect(b.content.dsseEnvelope.payloadType).toEqual(artifact.type);
expect(b.content.dsseEnvelope.signatures).toHaveLength(1);
expect(b.content.dsseEnvelope.signatures[0].keyid).toEqual(
signature.key.hint
);
expect(b.content.dsseEnvelope.signatures[0].sig).toEqual(
signature.signature
);

expect(b.verificationMaterial).toBeTruthy();
assert(b.verificationMaterial.content?.$case === 'publicKey');
expect(b.verificationMaterial.content?.publicKey).toBeTruthy();
expect(b.verificationMaterial.content?.publicKey.hint).toEqual(
signature.key.hint
);
});
});
});
});
4 changes: 3 additions & 1 deletion packages/sign/src/bundler/bundle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ export function toMessageSignatureBundle(
// DSSE envelope bundle - $case: 'dsseEnvelope'
export function toDSSEBundle(
artifact: Required<Artifact>,
signature: Signature
signature: Signature,
singleCertificate?: boolean
): sigstore.BundleWithDsseEnvelope {
return sigstore.toDSSEBundle({
artifact: artifact.data,
Expand All @@ -55,5 +56,6 @@ export function toDSSEBundle(
: undefined,
keyHint:
signature.key.$case === 'publicKey' ? signature.key.hint : undefined,
singleCertificate,
});
}
17 changes: 15 additions & 2 deletions packages/sign/src/bundler/dsse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,19 @@ import { toDSSEBundle } from './bundle';
import type { BundleWithDsseEnvelope } from '@sigstore/bundle';
import type { Signature } from '../signer';

type DSSEBundleBuilderOptions = BundleBuilderOptions & {
// When set to true, the bundle verification material will use the
// certifciate field instead of the x509CertificateChain field.
// When undefied/false, a v0.2 bundle will be created.
singleCertificate?: boolean;
};

// BundleBuilder implementation for DSSE wrapped attestations
export class DSSEBundleBuilder extends BaseBundleBuilder<BundleWithDsseEnvelope> {
constructor(options: BundleBuilderOptions) {
private singleCertificate?: boolean;
constructor(options: DSSEBundleBuilderOptions) {
super(options);
this.singleCertificate = options.singleCertificate ?? false;
}

// DSSE requires the artifact to be pre-encoded with the payload type
Expand All @@ -38,7 +47,11 @@ export class DSSEBundleBuilder extends BaseBundleBuilder<BundleWithDsseEnvelope>
artifact: Artifact,
signature: Signature
): Promise<BundleWithDsseEnvelope> {
return toDSSEBundle(artifactDefaults(artifact), signature);
return toDSSEBundle(
artifactDefaults(artifact),
signature,
this.singleCertificate
);
}
}

Expand Down

0 comments on commit 77e9e17

Please sign in to comment.