Skip to content

Commit

Permalink
move bundle construction helpers to bundle pkg (#636)
Browse files Browse the repository at this point in the history
Signed-off-by: Brian DeHamer <[email protected]>
  • Loading branch information
bdehamer authored Jul 26, 2023
1 parent 77afa92 commit 84eee0b
Show file tree
Hide file tree
Showing 6 changed files with 314 additions and 86 deletions.
6 changes: 6 additions & 0 deletions .changeset/wet-wombats-rush.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@sigstore/bundle': minor
'@sigstore/sign': minor
---

Move bundle construction helpers to the `@sigstore/bundle` package.
153 changes: 153 additions & 0 deletions packages/bundle/src/__tests__/build.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/*
Copyright 2023 The Sigstore Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
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 { HashAlgorithm } from '@sigstore/protobuf-specs';
import assert from 'assert';
import { toDSSEBundle, toMessageSignatureBundle } from '../build';

const signature = Buffer.from('signature');
const keyHint = 'hint';
const certificate = Buffer.from('certificate');

describe('toMessageSignatureBundle', () => {
const digest = Buffer.from('digest');
it('returns a valid message signature bundle', () => {
const b = toMessageSignatureBundle({
digest,
signature,
certificate,
keyHint,
});

expect(b).toBeTruthy();
expect(b.mediaType).toEqual(
'application/vnd.dev.sigstore.bundle+json;version=0.1'
);

assert(b.content?.$case === 'messageSignature');
expect(b.content.messageSignature).toBeTruthy();
expect(b.content.messageSignature.messageDigest.algorithm).toEqual(
HashAlgorithm.SHA2_256
);
expect(b.content.messageSignature.messageDigest.digest).toEqual(digest);
expect(b.content.messageSignature.signature).toEqual(signature);

expect(b.verificationMaterial).toBeTruthy();
assert(b.verificationMaterial.content?.$case === 'x509CertificateChain');
expect(
b.verificationMaterial.content?.x509CertificateChain.certificates
).toHaveLength(1);
expect(
b.verificationMaterial.content?.x509CertificateChain.certificates[0]
.rawBytes
).toEqual(certificate);
});
});

describe('toDSSEBundle', () => {
const artifact = Buffer.from('data');
const artifactType = 'text/plain';

describe('when a public key w/ hint is provided', () => {
it('returns a valid DSSE bundle', () => {
const b = toDSSEBundle({
artifact,
artifactType,
signature,
keyHint,
});

expect(b).toBeTruthy();
expect(b.mediaType).toEqual(
'application/vnd.dev.sigstore.bundle+json;version=0.1'
);

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

expect(b.verificationMaterial).toBeTruthy();
assert(b.verificationMaterial.content?.$case === 'publicKey');
expect(b.verificationMaterial.content?.publicKey).toBeTruthy();
expect(b.verificationMaterial.content?.publicKey.hint).toEqual(keyHint);
});
});

describe('when a public key w/o hint is provided', () => {
it('returns a valid DSSE bundle', () => {
const b = toDSSEBundle({
artifact,
artifactType,
signature,
});

expect(b).toBeTruthy();
expect(b.mediaType).toEqual(
'application/vnd.dev.sigstore.bundle+json;version=0.1'
);

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

expect(b.verificationMaterial).toBeTruthy();
assert(b.verificationMaterial.content?.$case === 'publicKey');
expect(b.verificationMaterial.content?.publicKey).toBeTruthy();
expect(b.verificationMaterial.content?.publicKey.hint).toEqual('');
});
});

describe('when a certificate chain provided', () => {
it('returns a valid DSSE bundle', () => {
const b = toDSSEBundle({
artifact,
artifactType,
signature,
certificate,
});

expect(b).toBeTruthy();
expect(b.mediaType).toEqual(
'application/vnd.dev.sigstore.bundle+json;version=0.1'
);

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

expect(b.verificationMaterial).toBeTruthy();
assert(b.verificationMaterial.content?.$case === 'x509CertificateChain');
expect(
b.verificationMaterial.content?.x509CertificateChain.certificates
).toHaveLength(1);
expect(
b.verificationMaterial.content?.x509CertificateChain.certificates[0]
.rawBytes
).toEqual(certificate);
});
});
});
7 changes: 7 additions & 0 deletions packages/bundle/src/__tests__/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ import {
isBundleWithDsseEnvelope,
isBundleWithMessageSignature,
isBundleWithPublicKey,
toDSSEBundle,
toMessageSignatureBundle,
} from '../index';

describe('public interface', () => {
Expand Down Expand Up @@ -129,6 +131,11 @@ describe('public interface', () => {
expect(x509CertificateChain).toBeDefined();
});

it('exports bundle construction functions', () => {
expect(toDSSEBundle).toBeDefined();
expect(toMessageSignatureBundle).toBeDefined();
});

it('exports type guard functions', () => {
expect(isBundleWithCertificateChain).toBeDefined();
expect(isBundleWithDsseEnvelope).toBeDefined();
Expand Down
124 changes: 124 additions & 0 deletions packages/bundle/src/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
Copyright 2023 The Sigstore Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
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 { HashAlgorithm } from '@sigstore/protobuf-specs';
import { BUNDLE_V01_MEDIA_TYPE } from './bundle';

import type { Envelope, Signature } from '@sigstore/protobuf-specs';
import type {
Bundle,
BundleWithDsseEnvelope,
BundleWithMessageSignature,
} from './bundle';

// Helper functions for assembling the parts of a Sigstore bundle

type VerificationMaterialOptions = {
certificate?: Buffer;
keyHint?: string;
};

type MessageSignatureBundleOptions = {
digest: Buffer;
signature: Buffer;
} & VerificationMaterialOptions;

type DSSEBundleOptions = {
artifact: Buffer;
artifactType: string;
signature: Buffer;
} & VerificationMaterialOptions;

// Message signature bundle - $case: 'messageSignature'
export function toMessageSignatureBundle(
options: MessageSignatureBundleOptions
): BundleWithMessageSignature {
return {
mediaType: BUNDLE_V01_MEDIA_TYPE,
content: {
$case: 'messageSignature',
messageSignature: {
messageDigest: {
algorithm: HashAlgorithm.SHA2_256,
digest: options.digest,
},
signature: options.signature,
},
},
verificationMaterial: toVerificationMaterial(options),
};
}

// DSSE envelope bundle - $case: 'dsseEnvelope'

export function toDSSEBundle(
options: DSSEBundleOptions
): BundleWithDsseEnvelope {
return {
mediaType: BUNDLE_V01_MEDIA_TYPE,
content: {
$case: 'dsseEnvelope',
dsseEnvelope: toEnvelope(options),
},
verificationMaterial: toVerificationMaterial(options),
};
}

function toEnvelope(options: DSSEBundleOptions): Envelope {
return {
payloadType: options.artifactType,
payload: options.artifact,
signatures: [toSignature(options)],
};
}

function toSignature(options: DSSEBundleOptions): Signature {
return {
keyid: options.keyHint || '',
sig: options.signature,
};
}

// Verification material

function toVerificationMaterial(
options: VerificationMaterialOptions
): Bundle['verificationMaterial'] {
return {
content: toKeyContent(options),
tlogEntries: [],
timestampVerificationData: { rfc3161Timestamps: [] },
};
}

function toKeyContent(
options: VerificationMaterialOptions
): Bundle['verificationMaterial']['content'] {
if (options.certificate) {
return {
$case: 'x509CertificateChain',
x509CertificateChain: {
certificates: [{ rawBytes: options.certificate }],
},
};
} else {
return {
$case: 'publicKey',
publicKey: {
hint: options.keyHint || '',
},
};
}
}
1 change: 1 addition & 0 deletions packages/bundle/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ 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.
*/
export { toDSSEBundle, toMessageSignatureBundle } from './build';
export {
BUNDLE_V01_MEDIA_TYPE,
BUNDLE_V02_MEDIA_TYPE,
Expand Down
Loading

0 comments on commit 84eee0b

Please sign in to comment.