Skip to content

Commit

Permalink
integrate w/ Sigstore conformance test suite (#613)
Browse files Browse the repository at this point in the history
Signed-off-by: Brian DeHamer <bdehamer@github.com>
  • Loading branch information
bdehamer committed Jul 17, 2023
1 parent 6abe9ec commit 335cb74
Show file tree
Hide file tree
Showing 14 changed files with 395 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .changeset/dull-jokes-cheat.md
@@ -0,0 +1,2 @@
---
---
27 changes: 27 additions & 0 deletions .github/workflows/conformance.yml
@@ -0,0 +1,27 @@
name: "Conformance tests"

on:
workflow_dispatch:
push:
branches: ['main']
pull_request:
branches: ['main']

jobs:
conformance:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@c85c95e3d7251135ab7dc9ce3241c5835cc595a9 # v3
- name: Setup node
uses: actions/setup-node@e33196f7422957bea03ed53f6fbb155025ffc7b8 # v3
with:
node-version: 16
cache: npm
- name: Install dependencies
run: npm ci
- name: Build sigstore-js
run: npm run build
- uses: sigstore/sigstore-conformance@954233f9a4ea6d0b355626b283b4f837f465e4ec # v0.0.5
with:
entrypoint: ${{ github.workspace }}/packages/conformance/bin/run
31 changes: 31 additions & 0 deletions package-lock.json

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

11 changes: 11 additions & 0 deletions packages/conformance/README.md
@@ -0,0 +1,11 @@
# @sigstore/conformance

CLI for integrating with the [sigstore-conformance][1] test suite. Provides
a wrapper around the [client](../client) which conforms to the
[conformance protocol][2].

This is a private package and not published to the registry. For a
general-purpose CLI, please see the [cli](../cli) package.

[1]: https://github.com/sigstore/sigstore-conformance
[2]: https://github.com/sigstore/sigstore-conformance/blob/main/docs/cli_protocol.md
17 changes: 17 additions & 0 deletions packages/conformance/bin/dev
@@ -0,0 +1,17 @@
#!/usr/bin/env node

const oclif = require('@oclif/core')

const path = require('path')
const project = path.join(__dirname, '..', 'tsconfig.json')

// In dev mode -> use ts-node and dev plugins
process.env.NODE_ENV = 'development'

require('ts-node').register({project})

// In dev mode, always show stack traces
oclif.settings.debug = true;

// Start the CLI
oclif.run().then(oclif.flush).catch(oclif.Errors.handle)
5 changes: 5 additions & 0 deletions packages/conformance/bin/run
@@ -0,0 +1,5 @@
#!/usr/bin/env node

const oclif = require('@oclif/core')

oclif.run().then(require('@oclif/core/flush')).catch(require('@oclif/core/handle'))
35 changes: 35 additions & 0 deletions packages/conformance/package.json
@@ -0,0 +1,35 @@
{
"name": "@sigstore/conformance",
"version": "0.0.0",
"private": "true",
"description": "Sigstore Conformance Test CLI",
"bin": {
"sigstore": "./bin/run"
},
"files": [
"/bin",
"/dist",
"/oclif.manifest.json"
],
"scripts": {
"clean": "shx rm -rf dist",
"prebuild": "npm run clean",
"build": "tsc -b"
},
"dependencies": {
"@oclif/core": "^2",
"sigstore": "^1.7.0"
},
"devDependencies": {
"oclif": "^3",
"tslib": "^2.6.0"
},
"oclif": {
"bin": "sigstore-conformance",
"commands": "./dist/commands",
"topicSeparator": " "
},
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
}
37 changes: 37 additions & 0 deletions packages/conformance/src/commands/sign-bundle.ts
@@ -0,0 +1,37 @@
import { Args, Command, Flags } from '@oclif/core';
import fs from 'fs/promises';
import { sigstore } from 'sigstore';

export default class SignBundle extends Command {
static override flags = {
'identity-token': Flags.string({
description: 'OIDC identity token',
required: true,
}),
bundle: Flags.string({
description: 'path to which the bundle will be written',
required: true,
}),
};

static override args = {
artifact: Args.file({
description: 'artifact to sign',
required: true,
exists: true,
}),
};

public async run(): Promise<void> {
const { args, flags } = await this.parse(SignBundle);

const options: Parameters<typeof sigstore.sign>[1] = {
identityToken: flags['identity-token'],
};

const artifact = await fs.readFile(args.artifact);
const bundle = await sigstore.sign(artifact, options);

await fs.writeFile(flags['bundle'], JSON.stringify(bundle));
}
}
65 changes: 65 additions & 0 deletions packages/conformance/src/commands/sign.ts
@@ -0,0 +1,65 @@
import { Args, Command, Flags } from '@oclif/core';
import fs from 'fs/promises';
import { sigstore } from 'sigstore';

export default class Sign extends Command {
static override flags = {
'identity-token': Flags.string({
description: 'OIDC identity token',
required: true,
}),
signature: Flags.string({
description: 'path to which the signature will be written',
required: true,
}),
certificate: Flags.string({
description: 'path to which the certificate will be written',
required: true,
}),
};

static override args = {
artifact: Args.file({
description: 'artifact to sign',
required: true,
exists: true,
}),
};

public async run(): Promise<void> {
const { args, flags } = await this.parse(Sign);

const options: Parameters<typeof sigstore.sign>[1] = {
identityToken: flags['identity-token'],
};

const artifact = await fs.readFile(args.artifact);
const bundle = await sigstore.sign(artifact, options);

if (bundle.messageSignature?.signature) {
const signature = bundle.messageSignature.signature;
await fs.writeFile(flags['signature'], signature);
} else {
this.error('No signature found');
}

if (bundle.verificationMaterial.x509CertificateChain?.certificates) {
const certBytes =
bundle.verificationMaterial.x509CertificateChain.certificates[0]
.rawBytes;
const pem = toPEM(certBytes);
await fs.writeFile(flags['certificate'], pem);
} else {
this.error('No certificate found');
}
}
}

function toPEM(der: string): string {
// Split the certificate into lines of 64 characters.
const lines = der.match(/.{1,64}/g) || '';

return [`-----BEGIN CERTIFICATE-----`, ...lines, `-----END CERTIFICATE-----`]
.join('\n')
.concat('\n');
}
45 changes: 45 additions & 0 deletions packages/conformance/src/commands/verify-bundle.ts
@@ -0,0 +1,45 @@
import { Args, Command, Flags } from '@oclif/core';
import fs from 'fs/promises';
import { sigstore } from 'sigstore';

export default class VerifyBundle extends Command {
static override flags = {
bundle: Flags.string({
description: 'path to bundle',
required: true,
}),
'certificate-identity': Flags.string({
description:
'The expected identity in the signing ceritifcate SAN extension',
required: true,
}),
'certificate-oidc-issuer': Flags.string({
description: 'the expected OIDC issuer for the signing certificate',
required: true,
}),
};

static override args = {
file: Args.file({
description: 'artifact to verify',
required: true,
exists: true,
}),
};

public async run(): Promise<void> {
const { args, flags } = await this.parse(VerifyBundle);

const bundle = await fs
.readFile(flags.bundle)
.then((data) => JSON.parse(data.toString()));
const artifact = await fs.readFile(args.file);

const options: Parameters<typeof sigstore.verify>[2] = {
certificateIdentityURI: flags['certificate-identity'],
certificateIssuer: flags['certificate-oidc-issuer'],
};

sigstore.verify(bundle, artifact, options);
}
}

0 comments on commit 335cb74

Please sign in to comment.