Skip to content

Commit

Permalink
receipt
Browse files Browse the repository at this point in the history
  • Loading branch information
OR13 committed Aug 11, 2024
1 parent 4d57f4e commit f698e49
Show file tree
Hide file tree
Showing 25 changed files with 192 additions and 29 deletions.
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions scripts/scitt.diagnostic.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@

# notary keys
npm run -s transmute -- cose keygen --alg ES256 --output ./tests/fixtures/private.notary.key.cbor > ./tests/fixtures/private.notary.key.diag
npm run -s transmute -- cose keypub ./tests/fixtures/private.notary.key.cbor --output ./tests/fixtures/public.notary.key.cbor > ./tests/fixtures/public.notary.key.diag


# sign hash envelope
npm run -s transmute -- scitt sign ./tests/fixtures/private.sig.key.cbor ./tests/fixtures/message.json --output ./tests/fixtures/message.hash-envelope.cbor > ./tests/fixtures/message.hash-envelope.diag
npm run -s transmute -- scitt verify ./tests/fixtures/public.sig.key.cbor ./tests/fixtures/message.hash-envelope.cbor 3073d614f853aaec9a1146872c7bab75495ee678c8864ed3562f8787555c1e22 --output ./tests/fixtures/message.hash-envelope.verified.data > ./tests/fixtures/message.hash-envelope.diag
npm run -s transmute -- scitt issue-statement ./tests/fixtures/private.notary.key.cbor ./tests/fixtures/message.json --output ./tests/fixtures/message.hash-envelope.cbor > ./tests/fixtures/message.hash-envelope.diag
npm run -s transmute -- scitt verify-statement-hash ./tests/fixtures/public.notary.key.cbor ./tests/fixtures/message.hash-envelope.cbor 3073d614f853aaec9a1146872c7bab75495ee678c8864ed3562f8787555c1e22 --output ./tests/fixtures/message.hash-envelope.verified.data > ./tests/fixtures/message.hash-envelope.diag

# sign receipt
npm run -s transmute -- scitt issue-receipt ./tests/fixtures/private.notary.key.cbor ./tests/fixtures/message.hash-envelope.cbor --log ./tests/fixtures/trans.json --output ./tests/fixtures/message.hash-envelope-with-receipt.cbor > ./tests/fixtures/message.hash-envelope-with-receipt.diag
npm run -s transmute -- scitt verify-receipt-hash ./tests/fixtures/public.notary.key.cbor ./tests/fixtures/message.hash-envelope-with-receipt.cbor 3073d614f853aaec9a1146872c7bab75495ee678c8864ed3562f8787555c1e22 > ./tests/fixtures/message.hash-envelope-with-receipt.diag
7 changes: 7 additions & 0 deletions src/action/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ export type CommonOptions = {
detached?: boolean
}

export type ScittOptions = {
log?: string
}

export const args = (prompt: string) => {
// https://stackoverflow.com/questions/29655760/convert-a-string-into-shell-arguments
const re = /"[^"]+"|'[^']+'|\S+/g
Expand Down Expand Up @@ -53,6 +57,9 @@ export const args = (prompt: string) => {
crv: {
type: 'string' as "string",
},
log: {
type: 'string' as "string",
},
},
})
}
137 changes: 126 additions & 11 deletions src/scitt/handler.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import fs from 'fs'
import * as cose from '@transmute/cose'
import * as edn from '@transmute/edn'

import { Arguments } from "../types"

import { setSecret, setOutput, debug } from '@actions/core'

import { env } from '../action'
import { base64url } from 'jose'

export const handler = async function ({ positionals, values }: Arguments) {
positionals = positionals.slice(1)
const operation = positionals.shift()
switch (operation) {
case 'sign': {
case 'issue-statement': {
const output = values.output
const verbose = values.verbose || false
const [pathToPrivateKey, pathToMessage] = positionals
const [pathToPrivateKey, pathToStatement] = positionals
const privateKey = cose.cbor.decode(fs.readFileSync(pathToPrivateKey))
const thumbprint: any = privateKey.get(2) || await cose.key.thumbprint.calculateCoseKeyThumbprint(privateKey)
if (verbose) {
Expand All @@ -36,7 +37,7 @@ export const handler = async function ({ positionals, values }: Arguments) {
console.error(message)
throw new Error(message)
}
const message = fs.readFileSync(pathToMessage)
const statement = fs.readFileSync(pathToStatement)
const coseSign1 = await cose.hash
.signer({
remote: cose.crypto.signer({
Expand All @@ -50,7 +51,7 @@ export const handler = async function ({ positionals, values }: Arguments) {
// TODO: other commmand line options for headers
]),
unprotectedHeader: new Map<any, any>(),
payload: message,
payload: statement,
})

if (output) {
Expand All @@ -65,7 +66,7 @@ export const handler = async function ({ positionals, values }: Arguments) {
}
break
}
case 'verify': {
case 'verify-statement-hash': {
const output = values.output
const verbose = values.verbose || false
const [pathToPublicKey, pathToSignatures, hash] = positionals
Expand All @@ -75,11 +76,6 @@ export const handler = async function ({ positionals, values }: Arguments) {
const message = `🔑 ${Buffer.from(thumbprint).toString('hex')}`
debug(message)
}
if (env.github()) {
if (publicKey.get(-4)) {
setSecret(Buffer.from(publicKey.get(-4)).toString('hex'))
}
}
let alg = values.alg
if (publicKey.get(3)) {
alg = cose.IANACOSEAlgorithms[`${publicKey.get(3)}`].Name
Expand Down Expand Up @@ -120,6 +116,125 @@ export const handler = async function ({ positionals, values }: Arguments) {
}
break
}
case 'issue-receipt': {
const log = values.log
const output = values.output
const verbose = values.verbose || false
const [pathToPrivateKey, pathToSignedStatement] = positionals
const privateKey = cose.cbor.decode(fs.readFileSync(pathToPrivateKey))
const thumbprint: any = privateKey.get(2) || await cose.key.thumbprint.calculateCoseKeyThumbprint(privateKey)

if (!log) {
const message = `❌ --log is required (only JSON is supported)`
console.error(message)
throw new Error(message)
}
if (verbose) {
const message = `🔑 ${Buffer.from(thumbprint).toString('hex')}`
debug(message)
}
if (env.github()) {
if (privateKey.get(-4)) {
setSecret(Buffer.from(privateKey.get(-4)).toString('hex'))
}
}
let alg = values.alg
if (privateKey.get(3)) {
alg = cose.IANACOSEAlgorithms[`${privateKey.get(3)}`].Name
}

if (!alg) {
const message = `❌ --alg is required when not present in private key`
console.error(message)
throw new Error(message)
}
const signedStatement = fs.readFileSync(pathToSignedStatement)

const notary = cose.detached.signer({
remote: cose.crypto.signer({
privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(privateKey),
}),
});

let entries = [] as Uint8Array[]
try {
entries = JSON.parse(fs.readFileSync(log).toString()).entries.map((leaf) => {
return base64url.decode(leaf)
})
} catch (e) {
// console.warn('failed to parse transparency log.')
}
const newLeaf = await cose.receipt.leaf(signedStatement)
entries.push(newLeaf)
const receipt = await cose.receipt.inclusion.issue({
protectedHeader: cose.ProtectedHeader([
[cose.Protected.Alg, cose.Signature.ES256],
[cose.Protected.ProofType, cose.Receipt.Inclusion],
// [cose.Protected.Kid, notaryPublicKeyJwk.kid],
]),
entry: entries.length - 1,
entries: entries,
signer: notary,
});

const encodedLog = entries.map((leaf) => {
return base64url.encode(leaf)
})

const transparentStatement = await cose.receipt.add(
signedStatement,
receipt
);

if (output) {
// write log to disk before writing receipt.
fs.writeFileSync(log, JSON.stringify({ entries: encodedLog }, null, 2))
fs.writeFileSync(output, Buffer.from(transparentStatement))
}

if (env.github()) {
setOutput('cbor', Buffer.from(transparentStatement).toString('hex'))
} else {
const text = await cose.cbor.diagnose(Buffer.from(transparentStatement))
console.log(text)
}
break
}
case 'verify-receipt-hash': {
const output = values.output
const verbose = values.verbose || false
const [pathToPublicKey, pathToSignatures, hash] = positionals
const publicKey = cose.cbor.decode(fs.readFileSync(pathToPublicKey))
const thumbprint: any = publicKey.get(2) || await cose.key.thumbprint.calculateCoseKeyThumbprint(publicKey)
if (verbose) {
const message = `🔑 ${Buffer.from(thumbprint).toString('hex')}`
debug(message)
}
const verifier = await cose.receipt.verifier({
resolve: async (): Promise<cose.PublicKeyJwk> => {
// TODO: could add support for resolve key from identifier here.
return await cose.key.convertCoseKeyToJsonWebKey(publicKey)
}
});
const transparentStatement = fs.readFileSync(pathToSignatures)
const verified = await verifier.verify({
coseSign1: transparentStatement,
payload: Buffer.from(hash, 'hex'),
});
if (Buffer.from(verified.payload).toString('hex') !== Buffer.from(hash, 'hex').toString('hex')) {
throw new Error(`Signature verification failed for hash: ${Buffer.from(verified.payload).toString('hex')}`)
}
if (output) {
fs.writeFileSync(output, Buffer.from(verified.payload))
}
if (env.github()) {
setOutput('cbor', Buffer.from(verified.payload).toString('hex'))
} else {
const text = await cose.cbor.diagnose(Buffer.from(transparentStatement))
console.log(text)
}
break
}
default: {
const message = `😕 Unknown Command`
console.error(message)
Expand Down
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

import { CommonOptions, JoseOptions } from './action/args'
import { CommonOptions, JoseOptions, ScittOptions } from './action/args'

export type PositionalArguments = string[]

export type Options = CommonOptions & JoseOptions
export type Options = CommonOptions & JoseOptions & ScittOptions

export type Arguments = { positionals: PositionalArguments, values: Options }
Binary file not shown.
2 changes: 2 additions & 0 deletions tests/fixtures/message.hash-envelope-with-receipt.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
18([h'a20126391a8f2f', {394: [h'd28447a2012619018b01a119018ca120815848830605825820968654e6ab6d965c0e2247807482b2e3575bd41168a7b06dc8d722311f168d075820bc0a9f560fa6ec11a21e4aba8a026ba8537e8917614e9bb10077a53d3a47a29bf6584056cdffa410ff8e5c04be4300cd4447e965aa6f2a40be03ca9fb6cab9929dfa3f937856134c0c32d507f8316359b32e7a88891c376d2287ec4c92e6820be25ec8']}, h'3073d614f853aaec9a1146872c7bab75495ee678c8864ed3562f8787555c1e22', h'a3925cfb614e42f1dac139862a6c7903872fd45aaa97ff34b2439dbe69965fea9abbec7f4a394634859792674c8575c1cfb7337c7a3ad716153ff63755b734c9'])

2 changes: 1 addition & 1 deletion tests/fixtures/message.hash-envelope.cbor
Original file line number Diff line number Diff line change
@@ -1 +1 @@
҄G�&9�/�X 0s��S��F�,{�uI^�xȆN�V/��U\"X@�~c͘w�������/�y���8s Y�Z��c[�<��p�jAU���������]�Ǻ
҄G�&9�/�X 0s��S��F�,{�uI^�xȆN�V/��U\"X@��\�aNB���9�*ly�/�Z���4�C��i�_ꚻ�J9F4���gL�u�Ϸ3|z:�?�7U�4
2 changes: 1 addition & 1 deletion tests/fixtures/message.hash-envelope.diag
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
18([h'a20126391a8f2f', {}, h'3073d614f853aaec9a1146872c7bab75495ee678c8864ed3562f8787555c1e22', h'19a17e63cd9877a8fcd115ebfed2f42fae7907d41383c038731a0c59eba95afe82635ba43c0ffe9570ed9c6a1f4155b497e1a51cedfc149e94c0e75da9c7ba83'])
18([h'a20126391a8f2f', {}, h'3073d614f853aaec9a1146872c7bab75495ee678c8864ed3562f8787555c1e22', h'a3925cfb614e42f1dac139862a6c7903872fd45aaa97ff34b2439dbe69965fea9abbec7f4a394634859792674c8575c1cfb7337c7a3ad716153ff63755b734c9'])

Binary file added tests/fixtures/message.hash-envelope.receipt.cbor
Binary file not shown.
2 changes: 2 additions & 0 deletions tests/fixtures/message.hash-envelope.receipt.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
18([h'a2012619018b01', {396: {-1: [h'83010080']}}, null, h'8630fc8b82cad2287800d2c3ff777a4e7dca4eb8db24adaf2fe6b015d829e9842f047ca8e9ca3ec508b4c3196eafb21704d500bda9aefa5b6a2b7f980e5e64d6'])

2 changes: 1 addition & 1 deletion tests/fixtures/message.signature.cbor
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
҄C�&�XJ{"message":"⌛ My lungs taste the air of Time Blown past falling sands"}
X@�D����6$���2�`��/9�ZH �㩥�kɛY|��OIҏ)��e��$R̓�+A;��Z
X@���*�}���m��¤�D�ޠ�&�ž c/��|��Q������� )���$'K�D���>ޕ8�
Binary file modified tests/fixtures/message.signature.detached.cbor
Binary file not shown.
2 changes: 1 addition & 1 deletion tests/fixtures/message.signature.detached.diag
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
/ unprotected / {
},
/ payload / nil,
/ signature / h'1f46c2c2...5da34e1a'
/ signature / h'ff96ab92...0885dce0'
])
2 changes: 1 addition & 1 deletion tests/fixtures/message.signature.diag
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
/ unprotected / {
},
/ payload / h'7b226d65...73227d0a',
/ signature / h'dc44b99c...8eed865a'
/ signature / h'1a8012e3...de953883'
])
Binary file added tests/fixtures/private.notary.key.cbor
Binary file not shown.
2 changes: 2 additions & 0 deletions tests/fixtures/private.notary.key.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{1: 2, -2: h'd8ae0500dfac30a91cba04174113a73a824323e8997cdda40521b1b778dc3f16', -3: h'cc2ed6304b8c7d77150a59500b568fe77babf1117a6b9ae6a5fb8a50a8699765', -1: 1, -4: h'fe5b1b1e847dfccbdee1242ca4935318911e4a2fd3dcdc8e6f9976b474daa5c8', 3: -7, 2: h'15c80c7968c10be3f9c64035f26c7bd16ca7943e8a32c84e4c8c5a9ccc4c9f82'}

2 changes: 1 addition & 1 deletion tests/fixtures/private.sig.key.cbor
Original file line number Diff line number Diff line change
@@ -1 +1 @@
�!X \W�Pœy~��M��sl�^�n�=���^&U�k"X ���N�����R�/yu��/�rd���Wr޶� #X ��������ȅ �i1L�e��}4f���&X >��]��ц-pt"Q�" �QG,��`~c j�
�!X �+���q�v�;���:$���Ѭѻ���"X �����8 \�v}ի�N͠p‹�,5E��yf #X /�Ei�̓5�n}���4����&�[^S�o &X �#V����j̑�ě���%UqV�����A
2 changes: 1 addition & 1 deletion tests/fixtures/private.sig.key.diag
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
{1: 2, -2: h'5c579550c593797eddf94d9e14d4736ce45e846ed63d18c7f2d75e102655a26b', -3: h'd8dff74e81a5aabffb520fb02f79759ae6a51b2fab7264f7f8a95772deb6e9a3', -1: 1, -4: h'fab59190bec07f020ef9c9c8850bbb69314cd565b704ea7d103466fb7f12beaa', 3: -7, 2: h'3e93835d10ec85199dd1862d70742251b2220ce251472cb688607e63206a128f'}
{1: 2, -2: h'a72b11c51ce6ed71c076973bed9e1fbe07d03a24a39efcd1acd1bbc4e1abd402', -3: h'9495caf6f3380c5ceb98767dd5ab03cb4e04cda07013c28bac2c3545ad987966', -1: 1, -4: h'2f844569d50ecc9335ab6e7dace90f8f34fac4ee168f1602269d5b5e53b76f20', 3: -7, 2: h'b2132313085680ffeebde86acc9186c49b9196ce25557156c80fdf118c94e641'}

Binary file added tests/fixtures/public.notary.key.cbor
Binary file not shown.
2 changes: 2 additions & 0 deletions tests/fixtures/public.notary.key.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
{1: 2, -2: h'd8ae0500dfac30a91cba04174113a73a824323e8997cdda40521b1b778dc3f16', -3: h'cc2ed6304b8c7d77150a59500b568fe77babf1117a6b9ae6a5fb8a50a8699765', -1: 1, 3: -7, 2: h'15c80c7968c10be3f9c64035f26c7bd16ca7943e8a32c84e4c8c5a9ccc4c9f82'}

2 changes: 1 addition & 1 deletion tests/fixtures/public.sig.key.cbor
Original file line number Diff line number Diff line change
@@ -1 +1 @@
�!X \W�Pœy~��M��sl�^�n�=���^&U�k"X ���N�����R�/yu��/�rd���Wr޶� &X >��]��ц-pt"Q�" �QG,��`~c j�
�!X �+���q�v�;���:$���Ѭѻ���"X �����8 \�v}ի�N͠p‹�,5E��yf &X �#V����j̑�ě���%UqV�����A
2 changes: 1 addition & 1 deletion tests/fixtures/public.sig.key.diag
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
{1: 2, -2: h'5c579550c593797eddf94d9e14d4736ce45e846ed63d18c7f2d75e102655a26b', -3: h'd8dff74e81a5aabffb520fb02f79759ae6a51b2fab7264f7f8a95772deb6e9a3', -1: 1, 3: -7, 2: h'3e93835d10ec85199dd1862d70742251b2220ce251472cb688607e63206a128f'}
{1: 2, -2: h'a72b11c51ce6ed71c076973bed9e1fbe07d03a24a39efcd1acd1bbc4e1abd402', -3: h'9495caf6f3380c5ceb98767dd5ab03cb4e04cda07013c28bac2c3545ad987966', -1: 1, 3: -7, 2: h'b2132313085680ffeebde86acc9186c49b9196ce25557156c80fdf118c94e641'}

10 changes: 10 additions & 0 deletions tests/fixtures/trans.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"entries": [
"BkQipgsQ5HcmRSAgqbmetV5YBH3B1rt-xi1MTPZwR0s",
"BkQipgsQ5HcmRSAgqbmetV5YBH3B1rt-xi1MTPZwR0s",
"2QAyOLMwqcIVMjFHNwoBmS6RqTmYcDw95XCwX3MzIoI",
"F5sV7wTYnXs_bJFJFuddVWQEu8xisyyQsVrS5_EyfAc",
"loZU5qttllwOIkeAdIKy41db1BFop7BtyNciMR8WjQc",
"wArLIQ_o5fHPqK0ZCZbAj244rCsMwoZGJnRsc8x6jW4"
]
}
22 changes: 18 additions & 4 deletions tests/scitt.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,32 @@ beforeEach(() => {
secret = jest.spyOn(core, 'setSecret').mockImplementation()
})

it('issue-statement', async () => {
await facade(`scitt issue-statement ./tests/fixtures/private.sig.key.cbor ./tests/fixtures/message.json --verbose`)
expect(debug).toHaveBeenCalledTimes(1)
expect(secret).toHaveBeenCalledTimes(1)
expect(output).toHaveBeenCalledTimes(1)
})

it('verify-statement-hash', async () => {
await facade(`scitt verify-statement-hash ./tests/fixtures/public.notary.key.cbor ./tests/fixtures/message.hash-envelope.cbor 3073d614f853aaec9a1146872c7bab75495ee678c8864ed3562f8787555c1e22 --verbose`)
expect(debug).toHaveBeenCalledTimes(1)
expect(output).toHaveBeenCalledTimes(1)
})

it('sign', async () => {
await facade(`scitt sign ./tests/fixtures/private.sig.key.cbor ./tests/fixtures/message.json --verbose`)
it('issue-receipt', async () => {
await facade(`scitt issue-receipt ./tests/fixtures/private.notary.key.cbor ./tests/fixtures/message.hash-envelope.cbor --log ./tests/fixtures/trans.json --verbose`)
expect(debug).toHaveBeenCalledTimes(1)
expect(secret).toHaveBeenCalledTimes(1)
expect(output).toHaveBeenCalledTimes(1)
})

it('verify hash', async () => {
await facade(`scitt verify ./tests/fixtures/public.sig.key.cbor ./tests/fixtures/message.hash-envelope.cbor 3073d614f853aaec9a1146872c7bab75495ee678c8864ed3562f8787555c1e22 --verbose`)
it('verify-receipt-hash', async () => {
await facade(`scitt verify-receipt-hash ./tests/fixtures/public.notary.key.cbor ./tests/fixtures/message.hash-envelope-with-receipt.cbor 3073d614f853aaec9a1146872c7bab75495ee678c8864ed3562f8787555c1e22 --verbose`)
expect(debug).toHaveBeenCalledTimes(1)
expect(output).toHaveBeenCalledTimes(1)
})




0 comments on commit f698e49

Please sign in to comment.