Skip to content

Commit

Permalink
update signature bundle format
Browse files Browse the repository at this point in the history
Signed-off-by: Brian DeHamer <bdehamer@github.com>
  • Loading branch information
bdehamer committed Aug 15, 2022
1 parent 11ebaae commit 06330a7
Show file tree
Hide file tree
Showing 7 changed files with 101 additions and 96 deletions.
19 changes: 5 additions & 14 deletions .github/workflows/build-sign-verify.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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
Expand All @@ -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
41 changes: 18 additions & 23 deletions src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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');
Expand All @@ -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');
Expand Down
31 changes: 18 additions & 13 deletions src/dsse.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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);
});
});

Expand Down
28 changes: 24 additions & 4 deletions src/dsse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,46 @@ 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<Envelope> {
): Promise<DSSEBundle> {
const paeBuffer = pae(payloadType, payload);
const signedPayload = await sigstore.sign(paeBuffer, options);
const bundle = await sigstore.sign(paeBuffer, options);

const envelope: Envelope = {
payloadType: payloadType,
payload: payload.toString('base64'),
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(
Expand Down
21 changes: 15 additions & 6 deletions src/sign.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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
);
});
});

Expand Down
53 changes: 19 additions & 34 deletions src/sign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,30 +13,22 @@ 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;
rekor: Rekor;
identityProviders: Provider[];
}

export interface SignedPayload {
base64Signature: string;
export interface SigstoreBundle {
attestationType: string;
attestation: Record<string, string>;
cert: string;
bundle?: RekorBundle;
}

export interface RekorBundle {
signedEntryTimestamp: string;
payload: RekorPayload;
}

export interface RekorPayload {
body: object;
integratedTime: number;
logIndex: number;
logID: string;
Expand All @@ -54,7 +46,7 @@ export class Signer {
this.identityProviders = options.identityProviders;
}

public async sign(payload: Buffer): Promise<SignedPayload> {
public async sign(payload: Buffer): Promise<SigstoreBundle> {
// Create emphemeral key pair
const keypair = generateKeyPair();

Expand Down Expand Up @@ -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<string> {
Expand All @@ -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,
},
};
}
4 changes: 2 additions & 2 deletions src/sigstore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -40,7 +40,7 @@ type IdentityProviderOptions = Pick<
export async function sign(
payload: Buffer,
options: SignOptions = {}
): Promise<SignedPayload> {
): Promise<SigstoreBundle> {
const fulcio = new Fulcio({ baseURL: options.fulcioBaseURL });
const rekor = new Rekor({ baseURL: options.rekorBaseURL });
const idps = configureIdentityProviders(options);
Expand Down

0 comments on commit 06330a7

Please sign in to comment.