Skip to content

Commit

Permalink
scitt
Browse files Browse the repository at this point in the history
  • Loading branch information
OR13 committed Aug 11, 2024
1 parent 1e6cf6f commit d8b74ef
Show file tree
Hide file tree
Showing 31 changed files with 273 additions and 167 deletions.
2 changes: 1 addition & 1 deletion dist/index.js

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion scripts/all.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ echo '{"message":"⌛ My lungs taste the air of Time Blown past falling sands"}'
npm run build

./scripts/jose.diagnostic.sh
./scripts/cose.diagnostic.sh
./scripts/cose.diagnostic.sh
./scripts/scitt.diagnostic.sh
4 changes: 4 additions & 0 deletions scripts/scitt.diagnostic.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@

# 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
3 changes: 2 additions & 1 deletion src/action/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { Arguments } from '../types'

import * as jose from '../jose'
import * as cose from '../cose'
import * as scitt from '../scitt'

const commands = { jose, cose }
const commands = { jose, cose, scitt }

export const handler = async (args: Arguments) => {
const [command] = args.positionals
Expand Down
103 changes: 0 additions & 103 deletions src/cose/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,109 +203,6 @@ export const handler = async function ({ positionals, values }: Arguments) {
}
break
}
// case 'encrypt': {
// const compact = values.compact || false
// const enc = values.enc || false
// const verbose = values.verbose || false
// const [pathToPublicKey, pathToMessage] = positionals
// const publicKey = JSON.parse(fs.readFileSync(pathToPublicKey).toString()) as jose.JWK
// const alg = publicKey.alg || values.alg
// if (!enc) {
// const message = `❌ --enc is required.`
// console.error(message)
// throw new Error(message)
// }
// if (!alg) {
// const message = `❌ --alg is required when not present in public key`
// console.error(message)
// throw new Error(message)
// }
// const message = new Uint8Array(fs.readFileSync(pathToMessage))
// const header = { alg } as jose.ProtectedHeaderParameters

// let jwe;

// if (compact) {
// jwe = await new jose.CompactEncrypt(
// message
// )
// .setProtectedHeader({ enc, alg: `${publicKey.alg}` })
// .encrypt(await jose.importJWK(publicKey))

// } else {
// jwe = await new jose.GeneralEncrypt(
// message
// )
// .setProtectedHeader({ enc })
// .addRecipient(await jose.importJWK(publicKey))
// .setUnprotectedHeader(header)
// .encrypt()
// }

// if (verbose) {
// const message = `🔑 ${publicKey.kid}`
// debug(message)
// }

// if (env.github()) {
// if (compact) {
// setOutput('jwe', jwe)
// } else {
// setOutput('json', jwe)
// }
// } else {
// if (compact) {
// console.log(jwe)
// } else {
// console.log(JSON.stringify(jwe, null, 2))
// }
// }
// break
// }
// case 'decrypt': {
// const output = values.output
// const compact = values.compact || false
// const verbose = values.verbose || false
// const [pathToPrivateKey, pathToMessage] = positionals
// const privateKey = JSON.parse(fs.readFileSync(pathToPrivateKey).toString()) as jose.JWK
// if (env.github()) {
// if (privateKey.d) {
// setSecret(privateKey.d)
// }
// }

// let jwe = fs.readFileSync(pathToMessage).toString() as any

// let result;
// if (compact) {
// result = await jose.compactDecrypt(
// jwe, await jose.importJWK(privateKey)
// )
// } else {
// jwe = JSON.parse(jwe)
// result = await jose.generalDecrypt(
// jwe, await jose.importJWK(privateKey)
// )
// }

// const { plaintext, protectedHeader } = result

// if (verbose) {
// const message = `🔑 ${privateKey.kid}`
// debug(message)
// }

// if (output) {
// fs.writeFileSync(output, plaintext)
// }

// if (env.github()) {
// setOutput('json', protectedHeader)
// } else {
// console.log(JSON.stringify(protectedHeader, null, 2))
// }
// break
// }
default: {
const message = `😕 Unknown Command`
console.error(message)
Expand Down
130 changes: 130 additions & 0 deletions src/scitt/handler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
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'

export const handler = async function ({ positionals, values }: Arguments) {
positionals = positionals.slice(1)
const operation = positionals.shift()
switch (operation) {
case 'sign': {
const output = values.output
const verbose = values.verbose || false
const [pathToPrivateKey, pathToMessage] = positionals
const privateKey = cose.cbor.decode(fs.readFileSync(pathToPrivateKey))
const thumbprint: any = privateKey.get(2) || await cose.key.thumbprint.calculateCoseKeyThumbprint(privateKey)
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 message = fs.readFileSync(pathToMessage)
const coseSign1 = await cose.hash
.signer({
remote: cose.crypto.signer({
privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(privateKey),
}),
})
.sign({
protectedHeader: cose.ProtectedHeader([
[cose.Protected.Alg, privateKey.get(3)],
[cose.Protected.PayloadHashAlgorithm, cose.Hash.SHA256],
// TODO: other commmand line options for headers
]),
unprotectedHeader: new Map<any, any>(),
payload: message,
})

if (output) {
fs.writeFileSync(output, Buffer.from(coseSign1))
}

if (env.github()) {
setOutput('cbor', Buffer.from(coseSign1).toString('hex'))
} else {
const text = await cose.cbor.diagnose(Buffer.from(coseSign1))
console.log(text)
}
break
}
case 'verify': {
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)
}
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
}

if (!alg) {
const message = `❌ --alg is required when not present in public key`
console.error(message)
throw new Error(message)
}
const coseSign1 = fs.readFileSync(pathToSignatures)
const result = await cose.attached
.verifier({
resolver: {
resolve: async () => {
return cose.key.convertCoseKeyToJsonWebKey(publicKey)
}
}
})
.verify({
coseSign1
})
if (hash) {
if (hash.toLowerCase() !== Buffer.from(result).toString('hex')) {
throw new Error(`Signature verification failed for hash: ${Buffer.from(result).toString('hex')}`)
}
} else {
throw new Error(`Unable to verify signature for hash: ${Buffer.from(result).toString('hex')}`)
}
if (output) {
fs.writeFileSync(output, Buffer.from(result))
}
if (env.github()) {
setOutput('cbor', Buffer.from(result).toString('hex'))
} else {
const text = await cose.cbor.diagnose(Buffer.from(coseSign1))
console.log(text)
}
break
}
default: {
const message = `😕 Unknown Command`
console.error(message)
throw new Error(message)
}
}

}
1 change: 1 addition & 0 deletions src/scitt/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './handler'
68 changes: 68 additions & 0 deletions tests/_sanity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import crypto from 'crypto'

import * as cose from '@transmute/cose'

it('cose sanity', async () => {
const privateKey = await cose.key.generate<cose.key.CoseKey>('ES256', 'application/cose-key')
const publicKey = await cose.key.extractPublicCoseKey(privateKey)
const signer = cose.attached.signer({
remote: cose.crypto.signer({
privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(privateKey),
}),
})
const verifier = cose.attached.verifier({
resolver: {
resolve: async () => {
return cose.key.convertCoseKeyToJsonWebKey(publicKey)
}
}
})
const message = new TextEncoder().encode('hello world')
const coseSign1 = await signer.sign({
protectedHeader: cose.ProtectedHeader([
[cose.Protected.Alg, privateKey.get(3)],
]),
unprotectedHeader: new Map<any, any>(),
payload: message,
})
const result = await verifier.verify({
coseSign1
})
expect(new TextDecoder().decode(result)).toBe('hello world')
})

it('scitt sanity', async () => {
const privateKey = await cose.key.generate<cose.key.CoseKey>('ES256', 'application/cose-key')
const publicKey = await cose.key.extractPublicCoseKey(privateKey)
const signer = await cose.hash.signer({
remote: cose.crypto.signer({
privateKeyJwk: await cose.key.convertCoseKeyToJsonWebKey(privateKey),
}),
})
const verifier = cose.attached.verifier({
resolver: {
resolve: async () => {
return cose.key.convertCoseKeyToJsonWebKey(publicKey)
}
}
})
const message = new TextEncoder().encode('hello world')
const coseSign1 = await signer.sign({
protectedHeader: cose.ProtectedHeader([
[cose.Protected.Alg, privateKey.get(3)],
[cose.Protected.PayloadHashAlgorithm, cose.Hash.SHA256],
]),
unprotectedHeader: new Map<any, any>(),
payload: message,
})
const result = await verifier.verify({
coseSign1,
})
const hash = crypto.createHash('sha256')
.update(message)
.digest();
expect(hash.toString('hex')).toBe(Buffer.from(result).toString('hex'))
expect(hash.toString('hex')).toBe('b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9')
})


2 changes: 1 addition & 1 deletion tests/fixtures/message.ciphertext.compact.jwe
Original file line number Diff line number Diff line change
@@ -1 +1 @@
eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiRUNESC1FUytBMTI4S1ciLCJlcGsiOnsieCI6Im5iQnJvYzI2dkx6VGdwdjFLdnM0WXJuR1VWbXhfeXQxM2JOYlJBWE45RkJqbjctMHM2bTNVdVh5TnNGbE1oTHciLCJjcnYiOiJQLTM4NCIsImt0eSI6IkVDIiwieSI6IlItZ3dOTFhKZ2dibTR4ZjczSW1qdVBEeW1teTNnZ0w4cHM0Q21pNVV4bUlya2VEendNTjZlVjMtWDRDOXVaU28ifX0.-WWSqli_b-4UpkK5zyx_7MXuHj3zmHgw.eZvk12RYBaXt_Cdp.h4cBBt0_i56J6W20tv-ODpDqCy4Un4kx2jKCST-2QHKsl_Us_15wOe1W1BLFDlq8d4-9kkLhLu7NvudxqnF8MeQdwIcnvbrfc-s.s1oaIbSFf3OrcmPZDME9-g
eyJlbmMiOiJBMTI4R0NNIiwiYWxnIjoiRUNESC1FUytBMTI4S1ciLCJlcGsiOnsieCI6Ikp6bEN3ZG53WDBNVkhFUjU0emRGd3ZaZEJpZEQxUERSZnMxNFZQUzFreEZfM0JqN2NSUU9heThBQlJLaGZTWFgiLCJjcnYiOiJQLTM4NCIsImt0eSI6IkVDIiwieSI6InU2cVdhQVo0YUJtNTNjX0RSMU5VNFhNamdfMzRmVU1lV1pHdTFiNVZkM1hucUNwNGZiTmgtdXp4eWg1S24zd0IifX0.BR03I42qlSXJVIhMcHlU88ystAyuY1Ca.wWDF0Fdlw7OTQJev.iHClUHnRASOLn03LTr8wqOt1sEDfGBDAGyW10mQeiHh9bkXVXjThJf9viUspUhu-Xa9ZS7yichdTKl3ijGMRJ6totpePo8CfTxo.U5Eo6MWEHlOZzXnbS8MZvQ
10 changes: 5 additions & 5 deletions tests/fixtures/message.ciphertext.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"ciphertext": "OdKdK6rNvOPDZR89e4nnaDLskwmyt1oRznSWNf5mec6N8plaCzpRohG-7eK0hdm29EzUucbgKrXPeRh5V4jW4ZMwWyGQj7k_Z1A",
"iv": "9g_gN2OElWQHf0-5",
"ciphertext": "H8VmMXcmqEqQqcNtv-wTEKQNj98G6G811PVnuyUWCqu1oW-w2rmRzKUXXMFGBh7mRkeK4d0pkZ-sC7cn4X-tj2-ktBmlTAEpNOI",
"iv": "pTw3gSET0CNt5eI7",
"recipients": [
{
"encrypted_key": "0bnz6BO332K3VnUGcpGsJy-hOxMXVszN",
"encrypted_key": "TBgu_8kSAe3ElzweqcbY19pdCsnV6Bcc",
"header": {
"alg": "ECDH-ES+A128KW"
}
}
],
"tag": "qUp_2FGNbGrimPayFO6CoQ",
"protected": "eyJlbmMiOiJBMTI4R0NNIiwiZXBrIjp7IngiOiJXQl8yQWtVMVlOamlrbGk1RkZQdENkRlR2eGEzaTNaZ1BSUXlSS08wV2JMUmdiQTEzYTFSby0tc2xQMlpTMjMwIiwiY3J2IjoiUC0zODQiLCJrdHkiOiJFQyIsInkiOiJJRVdHTlJSR2c1blBYc09RS0d1a2phNDVwTVkzUFFiMFpJMU9sT19SWDBGR29PTElHdE4tZzU1WS1BU2hZQU9zIn19"
"tag": "aWyF8ZScnWnix8uAw7C_9w",
"protected": "eyJlbmMiOiJBMTI4R0NNIiwiZXBrIjp7IngiOiIwWHV6bTN0UGxxTWh0MHVXZ3ZrNnVWakRDbFBPdjRnek56M0FrMnZVMEgtTzhXNXIzT2V5M3FtSURMWlplYmpEIiwiY3J2IjoiUC0zODQiLCJrdHkiOiJFQyIsInkiOiI2Tm5iMUY0R2w4cnliLUx4ZkgtUUZ3LVRFaTZkQXIwLTV0UFlpM21GNE13Q2VmV3k1aE1JRy13RkRyTFRUdHlkIn19"
}
4 changes: 2 additions & 2 deletions tests/fixtures/message.decrypted.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@
"enc": "A128GCM",
"alg": "ECDH-ES+A128KW",
"epk": {
"x": "nbBroc26vLzTgpv1Kvs4YrnGUVmx_yt13bNbRAXN9FBjn7-0s6m3UuXyNsFlMhLw",
"x": "JzlCwdnwX0MVHER54zdFwvZdBidD1PDRfs14VPS1kxF_3Bj7cRQOay8ABRKhfSXX",
"crv": "P-384",
"kty": "EC",
"y": "R-gwNLXJggbm4xf73ImjuPDymmy3ggL8ps4Cmi5UxmIrkeDzwMN6eV3-X4C9uZSo"
"y": "u6qWaAZ4aBm53c_DR1NU4XMjg_34fUMeWZGu1b5Vd3XnqCp4fbNh-uzxyh5Kn3wB"
}
}
1 change: 1 addition & 0 deletions tests/fixtures/message.hash-envelope.cbor
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
҄G�&9�/�X 0s��S��F�,{�uI^�xȆN�V/��U\"X@�~c͘w�������/�y���8s Y�Z��c[�<��p�jAU���������]�Ǻ�
2 changes: 2 additions & 0 deletions tests/fixtures/message.hash-envelope.diag
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
18([h'a20126391a8f2f', {}, h'3073d614f853aaec9a1146872c7bab75495ee678c8864ed3562f8787555c1e22', h'19a17e63cd9877a8fcd115ebfed2f42fae7907d41383c038731a0c59eba95afe82635ba43c0ffe9570ed9c6a1f4155b497e1a51cedfc149e94c0e75da9c7ba83'])

1 change: 1 addition & 0 deletions tests/fixtures/message.hash-envelope.verified.data
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
0s��S��F�,{�uI^�xȆN�V/��U\"
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@W>���0���3�����)�����:���$�|�)Mճ���£X'|��X��S^:
X@�D����6$���2�`��/9�ZH �㩥�kɛY|��OIҏ)��e��$R̓�+A;��Z
Binary file modified tests/fixtures/message.signature.detached.cbor
Binary file not shown.
2 changes: 1 addition & 1 deletion tests/fixtures/message.signature.detached.compact.jws
Original file line number Diff line number Diff line change
@@ -1 +1 @@
eyJhbGciOiJFUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..6rNcoEn42DSlbx_vzeEZiAuAVJWXq-RgKoCaIQ06BFusXtLJzrw5OaNa8ZEg_9xdvmrxwfZ5Yl-ymAzsUZCEeQ
eyJhbGciOiJFUzI1NiIsImI2NCI6ZmFsc2UsImNyaXQiOlsiYjY0Il19..KmH2h25eupWeZTKDlyWPYl1DAF6_c4dO4ofoDCSe5LxvXQhj56vtASybjgwWecYJgTSfGYU_A9mGtKtI9ivkYg
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'44361c7f...0346bdae'
/ signature / h'1f46c2c2...5da34e1a'
])
Loading

0 comments on commit d8b74ef

Please sign in to comment.