diff --git a/src/rfc/beautify/beautifyProtectedHeader.ts b/src/rfc/beautify/beautifyProtectedHeader.ts index e7ab764..4e6eba8 100644 --- a/src/rfc/beautify/beautifyProtectedHeader.ts +++ b/src/rfc/beautify/beautifyProtectedHeader.ts @@ -2,41 +2,48 @@ import { addComment } from "./addComment" import cbor from "../../cbor"; -import { maxBstrTruncateLength } from './constants' - -// https://www.iana.org/assignments/cose/cose.xhtml -const protectedHeaderTagToDescription = (tag: number) => { - const descriptions = new Map(); - descriptions.set(1, 'Algorithm') - descriptions.set(2, 'Critical parameters') - descriptions.set(3, 'Content type') - descriptions.set(4, 'Key identifier') - descriptions.set(-11111, 'Verifiable data structure') - - return descriptions.get(tag) || `${tag} unknown cbor content` -} + +import { bufferToTruncatedBstr } from "./bufferToTruncatedBstr"; +import { default as tags } from "../../unprotectedHeader"; export const beautifyProtectedHeader = async (data: Buffer | Uint8Array) => { - const diagnostic = await cbor.web.diagnose(data) - const mapItemSpacer = ` ` - let result = diagnostic; - result = result.replace('{', `{\n${mapItemSpacer}`) - result = result.replace(/, /g, `,\n${mapItemSpacer}`) - result = result.replace('}', `\n}`) - result = result.split('\n').map((line: string) => { - if (line.trim() === '{') { - line = addComment(`{`, `Protected`) - return line - } - if (line.includes(`h'`) && line.length > maxBstrTruncateLength) { - line = line.replace(/h'(.{8}).+(.{8})'/g, `h'$1...$2'`) - } - if (line === '' || line.trim() === '{' || line.trim() === '}') { - return line + const protectedHeader = await cbor.web.decode(data) + const lines = [] as string[] + for (const [label, value] of protectedHeader.entries()) { + if (label === 1) { + lines.push(addComment(` ${label}: ${value},`, 'Algorithm')) + } else if (label === 2) { + lines.push(addComment(` ${label}: ${value},`, 'Criticality')) + } else if (label === 3) { + lines.push(addComment(` ${label}: ${value},`, 'Content type')) + } else if (label === 4) { + lines.push(addComment(` ${label}: ${bufferToTruncatedBstr(value)},`, 'Key identifier')) + } else if (label === 13) { + lines.push(addComment(` ${label}: {`, 'CWT Claims')) + for (const [claimKey, claimValue] of value.entries()) { + if (claimKey === 1) { + lines.push(addComment(` ${claimKey}: ${claimValue},`, 'Issuer')) + } else if (claimKey === 2) { + lines.push(addComment(` ${claimKey}: ${claimValue},`, 'Subject')) + } else { + lines.push(addComment(` ${claimKey}: ${claimValue},`, 'Claim')) + } + + } + lines.push(` }`) + } else if (label === tags.verifiable_data_structure) { + lines.push(addComment(` ${label}: ${value},`, 'Verifiable Data Structure')) + } else { + lines.push(addComment(` ${label}: ${value},`, 'Parameter')) } - const maybeIntLabel = parseInt(line.split(':')[0], 10) - return addComment(line, `${protectedHeaderTagToDescription(maybeIntLabel)}`) - }).join('\n') - return result + + } + + + return ` +${addComment('{', 'Protected')} +${lines.join('\n')} +} + `.trim() } diff --git a/src/rfc/beautify/beautifyReceipts.ts b/src/rfc/beautify/beautifyReceipts.ts index ed6c146..b4edb9e 100644 --- a/src/rfc/beautify/beautifyReceipts.ts +++ b/src/rfc/beautify/beautifyReceipts.ts @@ -4,9 +4,12 @@ import { bufferToTruncatedBstr } from './bufferToTruncatedBstr'; import { beautifyCoseSign1 } from "./beautifyCoseSign1"; +import { default as tags } from '../../unprotectedHeader' + + export const beautifyReceipts = async (receipts: Buffer[]) => { const blocks = [ - `${addComment(` 300: [`, `Receipts (${receipts.length})`)} + `${addComment(` ${tags.scitt_receipt}: [`, `Receipts (${receipts.length})`)} ${receipts.map((receipt, i: number) => { const truncated = bufferToTruncatedBstr(receipt) return addComment(` ${truncated}`, `Receipt ${i + 1}`) diff --git a/src/scitt/index.ts b/src/scitt/index.ts index 16be920..0c0b983 100644 --- a/src/scitt/index.ts +++ b/src/scitt/index.ts @@ -1,3 +1,4 @@ +import * as statement from './statement' import * as receipt from './receipt' -export { receipt } \ No newline at end of file +export { statement, receipt } \ No newline at end of file diff --git a/src/scitt/receipt/issue.ts b/src/scitt/receipt/issue.ts index 6b3562f..7f359d4 100644 --- a/src/scitt/receipt/issue.ts +++ b/src/scitt/receipt/issue.ts @@ -16,6 +16,8 @@ import { SecretCoseKeyMap } from '../../key/types' import getSigner from "../../lib/signer" export type RequestScittReceipt = { + iss: string + sub: string index: number entries?: ArrayBuffer[] leaves?: Uint8Array[] @@ -24,7 +26,7 @@ export type RequestScittReceipt = { secretCoseKey?: SecretCoseKeyMap } -export const issue = async ({ index, entries, leaves, signer, secretCoseKey }: RequestScittReceipt): Promise => { +export const issue = async ({ iss, sub, index, entries, leaves, signer, secretCoseKey }: RequestScittReceipt): Promise => { let treeLeaves = leaves if (entries) { treeLeaves = entries.map((entry: ArrayBuffer) => { @@ -41,12 +43,16 @@ export const issue = async ({ index, entries, leaves, signer, secretCoseKey }: R ) let receiptSigner = signer const protectedHeaderMap = new Map() + const cwtClaimsMap = new Map() + cwtClaimsMap.set(1, iss) + cwtClaimsMap.set(2, sub) if (secretCoseKey) { const secretKeyJwk = await key.exportJWK(secretCoseKey as any) secretKeyJwk.alg = key.utils.algorithms.toJOSE.get(secretCoseKey.get(3) as number) protectedHeaderMap.set(1, secretCoseKey.get(3) as number) // set alg from the restricted key protectedHeaderMap.set(4, secretCoseKey.get(2) as number) // set kid from the restricted key protectedHeaderMap.set(unprotectedHeader.verifiable_data_structure, 1) // using RFC9162 verifiable data structure + protectedHeaderMap.set(13, cwtClaimsMap) receiptSigner = getSigner({ secretKeyJwk: secretKeyJwk as any }) diff --git a/src/scitt/statement/addReceipt.ts b/src/scitt/statement/addReceipt.ts new file mode 100644 index 0000000..2c91f97 --- /dev/null +++ b/src/scitt/statement/addReceipt.ts @@ -0,0 +1,26 @@ + +import cbor from '../../cbor' + +import { default as tags } from '../../unprotectedHeader' + +type RequestAddReceipt = { + statement: ArrayBuffer // really signed statement + receipt: ArrayBuffer +} + +export const addReceipt = ({ statement, receipt }: RequestAddReceipt) => { + const decoded = cbor.decode(statement) + let unprotectedHeader = decoded.value[1] + if (!(unprotectedHeader instanceof Map)) { + unprotectedHeader = new Map() + } + const existingReceipts = unprotectedHeader.get(tags.scitt_receipt) + if (!existingReceipts) { + unprotectedHeader.set(tags.scitt_receipt, [receipt]) + } else { + existingReceipts.push(receipt) + } + decoded.value[1] = unprotectedHeader + return cbor.encode(decoded) +} + diff --git a/src/scitt/statement/index.ts b/src/scitt/statement/index.ts new file mode 100644 index 0000000..b051d9b --- /dev/null +++ b/src/scitt/statement/index.ts @@ -0,0 +1,2 @@ +export * from './issue' +export * from './addReceipt' diff --git a/src/scitt/statement/issue.ts b/src/scitt/statement/issue.ts new file mode 100644 index 0000000..b0d4b38 --- /dev/null +++ b/src/scitt/statement/issue.ts @@ -0,0 +1,44 @@ + + +import detachPayload from '../../detachPayload' +import { typedArrayToBuffer } from '../../utils' +import * as key from '../../key' +import { SecretCoseKeyMap } from '../../key/types' +import getSigner from "../../lib/signer" + +export type RequestScittSignedStatement = { + iss: string + sub: string + cty: string + payload: ArrayBuffer + signer?: any, + secretCoseKey?: SecretCoseKeyMap +} + +export const issue = async ({ iss, sub, cty, payload, signer, secretCoseKey }: RequestScittSignedStatement): Promise => { + let receiptSigner = signer + const protectedHeaderMap = new Map() + const unprotectedHeaderMap = new Map() + const cwtClaimsMap = new Map() + cwtClaimsMap.set(1, iss) + cwtClaimsMap.set(2, sub) + if (secretCoseKey) { + const secretKeyJwk = await key.exportJWK(secretCoseKey as any) + secretKeyJwk.alg = key.utils.algorithms.toJOSE.get(secretCoseKey.get(3) as number) + protectedHeaderMap.set(1, secretCoseKey.get(3) as number) // set alg from the restricted key + protectedHeaderMap.set(3, cty) // content type of the payload + protectedHeaderMap.set(4, secretCoseKey.get(2) as number) // set kid from the restricted key + protectedHeaderMap.set(13, cwtClaimsMap) + receiptSigner = getSigner({ + secretKeyJwk: secretKeyJwk as any + }) + } + const signedStatement = await receiptSigner.sign({ + protectedHeader: protectedHeaderMap, + unprotectedHeader: unprotectedHeaderMap, + payload + }) + const { signature } = await detachPayload(signedStatement) + return typedArrayToBuffer(signature) + +} \ No newline at end of file diff --git a/src/types/UnprotectedHeader.ts b/src/types/UnprotectedHeader.ts index 73f7b3d..0e9d11c 100644 --- a/src/types/UnprotectedHeader.ts +++ b/src/types/UnprotectedHeader.ts @@ -1 +1 @@ -export type UnprotectedHeader = Map +export type UnprotectedHeader = Map diff --git a/src/unprotectedHeader.ts b/src/unprotectedHeader.ts index b4b943f..9131b56 100644 --- a/src/unprotectedHeader.ts +++ b/src/unprotectedHeader.ts @@ -8,11 +8,11 @@ const unprotectedHeaderTags = { counter_signature: 7, // will be registered in https://github.com/ietf-scitt/draft-steele-cose-merkle-tree-proofs - verifiable_data_structure: -11111, // int - verifiable_data_structure_proofs: -22222, // a map of ints to array of bstrs + verifiable_data_structure: -111, // 'TBD_1', // int + verifiable_data_structure_proofs: -222, //'TBD_2', // a map of ints to array of bstrs // will be registered in https://datatracker.ietf.org/doc/draft-birkholz-scitt-receipts/ - scitt_receipt: -33333, + scitt_receipt: -333, //'TBD_3', } const unprotectedHeader = { diff --git a/src/verifiable_data_structure_proofs.ts b/src/verifiable_data_structure_proofs.ts index 0e37733..af89038 100644 --- a/src/verifiable_data_structure_proofs.ts +++ b/src/verifiable_data_structure_proofs.ts @@ -1,8 +1,8 @@ const verifiable_data_structure_proofs = { - inclusion_proof: 1, - consistency_proof: 2, + inclusion_proof: -1, + consistency_proof: -2, } export default verifiable_data_structure_proofs \ No newline at end of file diff --git a/test/cometre/consistency-proof.md b/test/cometre/consistency-proof.md index f9c59b7..a4e3ac9 100644 --- a/test/cometre/consistency-proof.md +++ b/test/cometre/consistency-proof.md @@ -3,14 +3,14 @@ [ h'a3012604...392b6601', / Protected / { / Unprotected / - -22222: { / Proofs / - 2: [ / Consistency proofs (1) / + -222: { / Proofs / + -2: [ / Consistency proofs (1) / h'83040682...2e73a8ab', / Consistency proof 1 / ] }, }, h'430b6fd7...f74c7fc4', / Payload / - h'9ddf2848...90b06dad' / Signature / + h'd97befea...f30631cb' / Signature / ] ) ~~~~ @@ -19,7 +19,7 @@ { / Protected / 1: -7, / Algorithm / 4: h'68747470...6d706c65', / Key identifier / - -11111: 1 / Verifiable data structure / + -11111: 1, / Parameter / } ~~~~ diff --git a/test/cometre/inclusion-proof.md b/test/cometre/inclusion-proof.md index 41d49fa..a3b273b 100644 --- a/test/cometre/inclusion-proof.md +++ b/test/cometre/inclusion-proof.md @@ -3,14 +3,14 @@ [ h'a3012604...392b6601', / Protected / { / Unprotected / - -22222: { / Proofs / - 1: [ / Inclusion proofs (1) / + -222: { / Proofs / + -1: [ / Inclusion proofs (1) / h'83040282...1f487bb1', / Inclusion proof 1 / ] }, }, h'', / Detached payload / - h'affc058e...c340d8bc' / Signature / + h'94d8f4a3...250f27b2' / Signature / ] ) ~~~~ @@ -19,7 +19,7 @@ { / Protected / 1: -7, / Algorithm / 4: h'68747470...6d706c65', / Key identifier / - -11111: 1 / Verifiable data structure / + -11111: 1, / Parameter / } ~~~~ diff --git a/test/keys/examples.md b/test/keys/examples.md index 2662a1f..d9b3904 100644 --- a/test/keys/examples.md +++ b/test/keys/examples.md @@ -15,18 +15,18 @@ const cktUri = await cose.key.thumbprint.calculateCoseKeyThumbprintUri(coseKey) ``` ~~~~ text -urn:ietf:params:oauth:ckt:sha-256:lC-GkAD3bB7V6gyzn-WknVx0z9TvcgaAnRYoFKBz7zg +urn:ietf:params:oauth:ckt:sha-256:BOSZSJ0xKHMecqA7zZ1P5nb61sdpOug5DDm08RdUr9A ~~~~ ~~~~ cbor-diag { / COSE Key / 1: 2, / Type / - 2: h'6b775163...61523267', / Identifier / + 2: h'74704b76...59565a63', / Identifier / 3: -7, / Algorithm / -1: 1, / Curve / - -2: h'd69ce680...8383f9c7', / x public key component / - -3: h'40547ca1...c9c347bd', / y public key component / - -4: h'6caa6281...c06a086e', / d private key component / + -2: h'a4725ff9...ca65f639', / x public key component / + -3: h'b6f61758...08b3e9ba', / y public key component / + -4: h'c977ed33...9801a780', / d private key component / } ~~~~ @@ -41,18 +41,18 @@ const jktUri = await cose.key.thumbprint.calculateJwkThumbprintUri(jwk) ``` ~~~~ text -urn:ietf:params:oauth:jwk-thumbprint:sha-256:kwQc5cZuilOCixNhSdyPTwkEEHC8Dkx3Q0bKG_xaR2g +urn:ietf:params:oauth:jwk-thumbprint:sha-256:tpKv1rOPGAqKyhtBC4Z6FPDgSYMwrt7su8yoiYgYVZc ~~~~ ~~~~ json { "kty": "EC", - "kid": "kwQc5cZuilOCixNhSdyPTwkEEHC8Dkx3Q0bKG_xaR2g", + "kid": "tpKv1rOPGAqKyhtBC4Z6FPDgSYMwrt7su8yoiYgYVZc", "alg": "ES256", "crv": "P-256", - "x": "1pzmgGW4rNVZCbPcmYB8py_foFGI_tEENi3OR4OD-cc", - "y": "QFR8oRcJIjWUy7CATs-TsSYojg-cTrCpunurrcnDR70", - "d": "bKpigThFV3kenefjzxVZTxI75vgZiKCG-FqLacBqCG4" + "x": "pHJf-e_ddBsnbavnOOJAufI9KgTUF-_tZ3L5D8pl9jk", + "y": "tvYXWH4M3o9X5a1nrVU-vPa3URTo8QlddZYUGgiz6bo", + "d": "yXftM9XJu1LDkbF3DxrSSUbRy5PNr09Fbskj_5gBp4A" } ~~~~ @@ -69,18 +69,18 @@ const cktUri = await cose.key.thumbprint.calculateCoseKeyThumbprintUri(coseKey) ``` ~~~~ text -urn:ietf:params:oauth:ckt:sha-256:JKyHJ9DlJF9GcgNvjEe53IXZCsqqKeGMK13LWPjI50M +urn:ietf:params:oauth:ckt:sha-256:0zmk1A7RldKfBHaaA7zuHceFxcjwQHAkw9rYp-sxoZw ~~~~ ~~~~ cbor-diag { / COSE Key / 1: 2, / Type / - 2: h'674d4a56...74796134', / Identifier / + 2: h'44736545...6d504249', / Identifier / 3: -35, / Algorithm / -1: 2, / Curve / - -2: h'd05a6096...87943299', / x public key component / - -3: h'7c6e5a3c...85ef3625', / y public key component / - -4: h'a8fc80ed...c8fd2a8b', / d private key component / + -2: h'e68ee849...27ee94c6', / x public key component / + -3: h'a1db3cb2...5ed82927', / y public key component / + -4: h'7d229ea1...f4ce72af', / d private key component / } ~~~~ @@ -95,18 +95,18 @@ const jktUri = await cose.key.thumbprint.calculateJwkThumbprintUri(jwk) ``` ~~~~ text -urn:ietf:params:oauth:jwk-thumbprint:sha-256:gMJVOwztvWZ_0F09QApMClN_ynwaDp-sF0khbAktya4 +urn:ietf:params:oauth:jwk-thumbprint:sha-256:DseEfwsY_WgFXcnadulosVFk_O3SATgjHHtzh0dmPBI ~~~~ ~~~~ json { "kty": "EC", - "kid": "gMJVOwztvWZ_0F09QApMClN_ynwaDp-sF0khbAktya4", + "kid": "DseEfwsY_WgFXcnadulosVFk_O3SATgjHHtzh0dmPBI", "alg": "ES384", "crv": "P-384", - "x": "0Fpglh7gTVwrGMCfghiN13mRmK0Uaq2UoZMUosKWsAiEE6ZPnbfk9XG13ZSHlDKZ", - "y": "fG5aPCa2Ud_YDghvQN0kUy5Lfxccv9jN5UkIjtewW7tpTNYVBHZBiTaOXQyF7zYl", - "d": "qPyA7TXpnJX9JrKihyyza8BNmf6KJr_UvVHbr9YVuXLzqcMzyxE20pQXyTLI_SqL" + "x": "5o7oSdNuLQiT5W9SvMAnWtBu1jKnErkpg_Ph_FnkonhbOR-kE3kU4WCigKcn7pTG", + "y": "ods8sl2kHwan1nFjOs3hyk8uew2p7ngMJXm7aoGMLM9yiCfAi-fJu-B8vb5e2Ckn", + "d": "fSKeobGLtio-SNRcAVy4iyWXwU4rrPbgnlOE7eQUtiRxE95gVm9y7L_8h4v0znKv" } ~~~~ @@ -123,18 +123,18 @@ const cktUri = await cose.key.thumbprint.calculateCoseKeyThumbprintUri(coseKey) ``` ~~~~ text -urn:ietf:params:oauth:ckt:sha-256:-fMDrirSTd4f89-yghaFu9KC0ovpCQFpUCThB2OyKCg +urn:ietf:params:oauth:ckt:sha-256:WjKsoGUYFptXLxC0SG4cvkGau3bAFx2svWLnwguNCzc ~~~~ ~~~~ cbor-diag { / COSE Key / 1: 2, / Type / - 2: h'356c5a36...4c7a3259', / Identifier / + 2: h'6c70515a...7454526b', / Identifier / 3: -36, / Algorithm / -1: 3, / Curve / - -2: h'015c60b5...b05edafb', / x public key component / - -3: h'01f00374...4358780a', / y public key component / - -4: h'01a757bd...9655769f', / d private key component / + -2: h'00639eb3...da348de3', / x public key component / + -3: h'0085bd9b...7eda550c', / y public key component / + -4: h'00ed041c...7bcaa9f3', / d private key component / } ~~~~ @@ -149,17 +149,17 @@ const jktUri = await cose.key.thumbprint.calculateJwkThumbprintUri(jwk) ``` ~~~~ text -urn:ietf:params:oauth:jwk-thumbprint:sha-256:5lZ6atVornFTZWVNBEKUoAagwXwm8w0jNe2twyCLz2Y +urn:ietf:params:oauth:jwk-thumbprint:sha-256:lpQZ-748lee-iIfr64K1FiMJdkImogtRe9wLuTqtTRk ~~~~ ~~~~ json { "kty": "EC", - "kid": "5lZ6atVornFTZWVNBEKUoAagwXwm8w0jNe2twyCLz2Y", + "kid": "lpQZ-748lee-iIfr64K1FiMJdkImogtRe9wLuTqtTRk", "alg": "ES512", "crv": "P-521", - "x": "AVxgtRjO702Uc6etfKD3bqVkcEaUOGmOll9l0x0lMnf7vfZX12fR7h9SwjbBE2LxEGikG6JCJBE-RRFNVfqwXtr7", - "y": "AfADdAJkm0PHtWizyKxfQJqJ8T5gUT9IVa1rpw6jtHS_VTOaVVEtezqRgVFX1x9bzEoK37fTvROGmavI8EpDWHgK", - "d": "AadXvQSxFDrr8nIc8JZDkyxpIVN9tMuTAP8n6QsjXi6dXDD8bzILqESAREZhaAFwrN_y6Fz6tQDmf_FskKCWVXaf" + "x": "AGOes1DJjj8q2ddXBI_KbS02SHNlonrr98cBUowSYcrPm2ZREKkPwrR5RKDtJqQp3T7dJPX3Ba1O5_Q40ovaNI3j", + "y": "AIW9mzs84fyp-YteThL0a5fojqQC4XeGzn_G2RNgno_O303hSwfaxpmlg-PGLvKAVMeNrJBnufX2ZDbSzeR-2lUM", + "d": "AO0EHLlCT4adWFEB1h6ytnGCIrvWXKf9HNIx4Aq1GEmLr1VQg5PmV4aLaxQwVPvgV11TUwYtQOqtTwpqEKF7yqnz" } ~~~~ \ No newline at end of file diff --git a/test/scitt/examples.md b/test/scitt/examples.md index 7a51b9e..e802a8f 100644 --- a/test/scitt/examples.md +++ b/test/scitt/examples.md @@ -11,18 +11,18 @@ const diagnosticOfSecretKey = await cose.key.edn(secretCoseKey) ``` ~~~~ text -urn:ietf:params:oauth:ckt:sha-256:K61ZLN2R7_1IVZ9Fbkm0AJLKdHYi-Hj-c4rU3LPFJv8 +urn:ietf:params:oauth:ckt:sha-256:cpInDXPP6fdwdSMC11uPZ31JYoWPL_EqqakJXX_ZLJE ~~~~ ~~~~ cbor-diag { / COSE Key / 1: 2, / Type / - 2: h'31754f2d...66696159', / Identifier / + 2: h'4930714e...7163316b', / Identifier / 3: -7, / Algorithm / -1: 1, / Curve / - -2: h'ef03e2c9...091a4da1', / x public key component / - -3: h'5d5a53b1...b7942be1', / y public key component / - -4: h'6829a8df...d2b1d2c2', / d private key component / + -2: h'c5ee0e96...77b00ddc', / x public key component / + -3: h'10a3f1c2...580741d9', / y public key component / + -4: h'ba4d123d...bd9a527a', / d private key component / } ~~~~ @@ -35,17 +35,123 @@ const diagnosticOfPublicKey = await cose.key.edn(publicCoseKey) ``` ~~~~ text -urn:ietf:params:oauth:ckt:sha-256:K61ZLN2R7_1IVZ9Fbkm0AJLKdHYi-Hj-c4rU3LPFJv8 +urn:ietf:params:oauth:ckt:sha-256:cpInDXPP6fdwdSMC11uPZ31JYoWPL_EqqakJXX_ZLJE ~~~~ ~~~~ cbor-diag { / COSE Key / 1: 2, / Type / - 2: h'31754f2d...66696159', / Identifier / + 2: h'4930714e...7163316b', / Identifier / 3: -7, / Algorithm / -1: 1, / Curve / - -2: h'ef03e2c9...091a4da1', / x public key component / - -3: h'5d5a53b1...b7942be1', / y public key component / + -2: h'c5ee0e96...77b00ddc', / x public key component / + -3: h'10a3f1c2...580741d9', / y public key component / +} +~~~~ + +## Issue Statement + +``` ts +const statement = Buffer.from(JSON.stringify({ + "spdxVersion": "SPDX-2.2", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "sbom-tool v0.1.2", + "documentNamespace": "https://sbom.microsoft/sbom-tool/v0.1.2/sxs6e--NIEC8xIJRVxEbQQ", + "creationInfo": { + "created": "2022-07-05T22:11:05Z", + "creators": [ + "Organization: Microsoft", + "Tool: Microsoft.SBOMTool-0.0.0-alpha.0.13+build.37" + ] + }, + "documentDescribes": [ + "SPDXRef-RootPackage" + ], + "files": [ + { + "fileName": "./sbom-tool-win-x64.exe", + "SPDXID": "SPDXRef-File--sbom-tool-win-x64.exe-E55F25E239D8D3572D75D5CDC5CA24899FD4993F", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "56624d8ab67ac0e323bcac0ae1ec0656f1721c6bb60640ecf9b30e861062aad5" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "NOASSERTION" + } + ], + "packages": [ + { + "name": "NuGet.Packaging", + "SPDXID": "SPDXRef-Package-F374B589EF5A916D768BC9BDD592C16C2436F9F20F975BCF9458F1FFB2E91504", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseInfoFromFiles": [ + "NOASSERTION" + ], + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "versionInfo": "5.6.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:nuget/NuGet.Packaging%405.6.0" + } + ], + "supplier": "NOASSERTION" + }, + ], + "externalDocumentRefs": [], + "relationships": [ + { + "relationshipType": "DESCRIBES", + "relatedSpdxElement": "SPDXRef-RootPackage", + "spdxElementId": "SPDXRef-DOCUMENT" + }, + { + "relationshipType": "DEPENDS_ON", + "relatedSpdxElement": "SPDXRef-Package-342BA5C11805FDDCAF3A2BF48BFDCAB5C0240793089F89196209A39C580902E6", + "spdxElementId": "SPDXRef-RootPackage" + }, + ] +})) +const signedStatement = await cose.scitt.statement.issue({ + iss: 'software.vendor.example', + sub: 'vendor.product.example', + cty: 'application/spdx+json', + secretCoseKey, + payload: statement +}) +const diagnostic = await cose.scitt.receipt.edn(receipt) +``` + +~~~~ cbor-diag +18( / COSE Sign 1 / + [ + h'a4012603...6d706c65', / Protected / + {}, / Unprotected / + h'', / Detached payload / + h'39a6f0d3...fce67a6c' / Signature / + ] +) +~~~~ + +~~~~ cbor-diag +{ / Protected / + 1: -7, / Algorithm / + 3: application/spdx+json, / Content type / + 4: h'4930714e...7163316b', / Key identifier / + 13: { / CWT Claims / + 1: software.vendor.example, / Issuer / + 2: vendor.product.example, / Subject / + } } ~~~~ @@ -59,8 +165,10 @@ const message3 = cose.cbor.encode({ 3: 3 }) const message4 = cose.cbor.encode(['🔥', 4]) const message5 = cose.cbor.encode({ five: '💀' }) const entries = [message0, message1, message2, message3, message4, message5] -const receipt = await cose.scitt.receipt.issue({ - index: 4, +entries.push(Buffer.from(signedStatement)) +const logIndex = entries.length - 1 // last entry is the signed statement +receipt = await cose.scitt.receipt.issue({ + index: logIndex, entries: entries, secretCoseKey }) @@ -70,16 +178,16 @@ const diagnostic = await cose.scitt.receipt.edn(receipt) ~~~~ cbor-diag 18( / COSE Sign 1 / [ - h'a3012604...392b6601', / Protected / + h'a4012604...6d706c65', / Protected / { / Unprotected / - -22222: { / Proofs / - 1: [ / Inclusion proofs (1) / - h'83060482...32568964', / Inclusion proof 1 / + -222: { / Proofs / + -1: [ / Inclusion proofs (1) / + h'83080783...32568964', / Inclusion proof 1 / ] }, }, h'', / Detached payload / - h'98192c9f...34b4191c' / Signature / + h'2e34df43...8d74d55e' / Signature / ] ) ~~~~ @@ -87,18 +195,23 @@ const diagnostic = await cose.scitt.receipt.edn(receipt) ~~~~ cbor-diag { / Protected / 1: -7, / Algorithm / - 4: h'31754f2d...66696159', / Key identifier / - -11111: 1 / Verifiable data structure / + 4: h'4930714e...7163316b', / Key identifier / + -111: 1, / Verifiable Data Structure / + 13: { / CWT Claims / + 1: transparency.vendor.example, / Issuer / + 2: vendor.product.example, / Subject / + } } ~~~~ ~~~~ cbor-diag [ / Inclusion proof 1 / - 6, / Tree size / - 4, / Leaf index / - [ / Inclusion hashes (2) / - h'aa1a3c19...15a276cc' / Intermediate hash 1 / - h'0bdaaed3...32568964' / Intermediate hash 2 / + 8, / Tree size / + 7, / Leaf index / + [ / Inclusion hashes (3) / + h'2a8d7dfc...15d10b22' / Intermediate hash 1 / + h'75f177fd...2e73a8ab' / Intermediate hash 2 / + h'0bdaaed3...32568964' / Intermediate hash 3 / ] ] ~~~~ @@ -106,8 +219,9 @@ const diagnostic = await cose.scitt.receipt.edn(receipt) ## Verify Receipt ``` ts +const logIndex = entries.length - 1 // last entry is the signed statement const verificaton = await cose.scitt.receipt.verify({ - entry: entries[4], + entry: entries[logIndex], receipt, publicCoseKey }) @@ -117,4 +231,81 @@ console.log({ verificaton }) ~~~~ text { verificaton: true } +~~~~ + +## Transparent Statement + +``` ts +const transparentStatement = await cose.scitt.statement.addReceipt({ + statement: signedStatement, + receipt +}) +``` + +~~~~ cbor-diag +18( / COSE Sign 1 / + [ + h'a4012603...6d706c65', / Protected / + { / Unprotected / + -333: [ / Receipts (1) / + h'd284586c...8d74d55e' / Receipt 1 / + ] + }, + h'', / Detached payload / + h'39a6f0d3...fce67a6c' / Signature / + ] +) +~~~~ + +~~~~ cbor-diag +{ / Protected / + 1: -7, / Algorithm / + 3: application/spdx+json, / Content type / + 4: h'4930714e...7163316b', / Key identifier / + 13: { / CWT Claims / + 1: software.vendor.example, / Issuer / + 2: vendor.product.example, / Subject / + } +} +~~~~ + +~~~~ cbor-diag +18( / COSE Sign 1 / + [ + h'a4012604...6d706c65', / Protected / + { / Unprotected / + -222: { / Proofs / + -1: [ / Inclusion proofs (1) / + h'83080783...32568964', / Inclusion proof 1 / + ] + }, + }, + h'', / Detached payload / + h'2e34df43...8d74d55e' / Signature / + ] +) +~~~~ + +~~~~ cbor-diag +{ / Protected / + 1: -7, / Algorithm / + 4: h'4930714e...7163316b', / Key identifier / + -111: 1, / Verifiable Data Structure / + 13: { / CWT Claims / + 1: transparency.vendor.example, / Issuer / + 2: vendor.product.example, / Subject / + } +} +~~~~ + +~~~~ cbor-diag +[ / Inclusion proof 1 / + 8, / Tree size / + 7, / Leaf index / + [ / Inclusion hashes (3) / + h'2a8d7dfc...15d10b22' / Intermediate hash 1 / + h'75f177fd...2e73a8ab' / Intermediate hash 2 / + h'0bdaaed3...32568964' / Intermediate hash 3 / + ] +] ~~~~ \ No newline at end of file diff --git a/test/scitt/examples.md.test.ts b/test/scitt/examples.md.test.ts index 33e75a3..b40272d 100644 --- a/test/scitt/examples.md.test.ts +++ b/test/scitt/examples.md.test.ts @@ -59,11 +59,183 @@ const message4 = cose.cbor.encode(['🔥', 4]) const message5 = cose.cbor.encode({ five: '💀' }) const entries = [message0, message1, message2, message3, message4, message5] +const statement = Buffer.from(JSON.stringify({ + "spdxVersion": "SPDX-2.2", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "sbom-tool v0.1.2", + "documentNamespace": "https://sbom.microsoft/sbom-tool/v0.1.2/sxs6e--NIEC8xIJRVxEbQQ", + "creationInfo": { + "created": "2022-07-05T22:11:05Z", + "creators": [ + "Organization: Microsoft", + "Tool: Microsoft.SBOMTool-0.0.0-alpha.0.13\u002Bbuild.37" + ] + }, + "documentDescribes": [ + "SPDXRef-RootPackage" + ], + "files": [ + { + "fileName": "./sbom-tool-win-x64.exe", + "SPDXID": "SPDXRef-File--sbom-tool-win-x64.exe-E55F25E239D8D3572D75D5CDC5CA24899FD4993F", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "56624d8ab67ac0e323bcac0ae1ec0656f1721c6bb60640ecf9b30e861062aad5" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "NOASSERTION" + } + ], + "packages": [ + { + "name": "NuGet.Packaging", + "SPDXID": "SPDXRef-Package-F374B589EF5A916D768BC9BDD592C16C2436F9F20F975BCF9458F1FFB2E91504", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseInfoFromFiles": [ + "NOASSERTION" + ], + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "versionInfo": "5.6.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:nuget/NuGet.Packaging%405.6.0" + } + ], + "supplier": "NOASSERTION" + }, + ], + "externalDocumentRefs": [], + "relationships": [ + { + "relationshipType": "DESCRIBES", + "relatedSpdxElement": "SPDXRef-RootPackage", + "spdxElementId": "SPDXRef-DOCUMENT" + }, + { + "relationshipType": "DEPENDS_ON", + "relatedSpdxElement": "SPDXRef-Package-342BA5C11805FDDCAF3A2BF48BFDCAB5C0240793089F89196209A39C580902E6", + "spdxElementId": "SPDXRef-RootPackage" + }, + ] +})) + +let signedStatement: ArrayBuffer +it('issue statement', async () => { + signedStatement = await cose.scitt.statement.issue({ + iss: 'software.vendor.example', + sub: 'vendor.product.example', + cty: 'application/spdx+json', + secretCoseKey, + payload: statement + }) + entries.push(Buffer.from(signedStatement)) + lines.push(`## Issue Statement`) + const diagnostic = await cose.scitt.receipt.edn(signedStatement) + lines.push(` +\`\`\` ts +const statement = Buffer.from(JSON.stringify({ + "spdxVersion": "SPDX-2.2", + "dataLicense": "CC0-1.0", + "SPDXID": "SPDXRef-DOCUMENT", + "name": "sbom-tool v0.1.2", + "documentNamespace": "https://sbom.microsoft/sbom-tool/v0.1.2/sxs6e--NIEC8xIJRVxEbQQ", + "creationInfo": { + "created": "2022-07-05T22:11:05Z", + "creators": [ + "Organization: Microsoft", + "Tool: Microsoft.SBOMTool-0.0.0-alpha.0.13\u002Bbuild.37" + ] + }, + "documentDescribes": [ + "SPDXRef-RootPackage" + ], + "files": [ + { + "fileName": "./sbom-tool-win-x64.exe", + "SPDXID": "SPDXRef-File--sbom-tool-win-x64.exe-E55F25E239D8D3572D75D5CDC5CA24899FD4993F", + "checksums": [ + { + "algorithm": "SHA256", + "checksumValue": "56624d8ab67ac0e323bcac0ae1ec0656f1721c6bb60640ecf9b30e861062aad5" + } + ], + "licenseConcluded": "NOASSERTION", + "licenseInfoInFiles": [ + "NOASSERTION" + ], + "copyrightText": "NOASSERTION" + } + ], + "packages": [ + { + "name": "NuGet.Packaging", + "SPDXID": "SPDXRef-Package-F374B589EF5A916D768BC9BDD592C16C2436F9F20F975BCF9458F1FFB2E91504", + "downloadLocation": "NOASSERTION", + "filesAnalyzed": false, + "licenseConcluded": "NOASSERTION", + "licenseInfoFromFiles": [ + "NOASSERTION" + ], + "licenseDeclared": "NOASSERTION", + "copyrightText": "NOASSERTION", + "versionInfo": "5.6.0", + "externalRefs": [ + { + "referenceCategory": "PACKAGE_MANAGER", + "referenceType": "purl", + "referenceLocator": "pkg:nuget/NuGet.Packaging%405.6.0" + } + ], + "supplier": "NOASSERTION" + }, + ], + "externalDocumentRefs": [], + "relationships": [ + { + "relationshipType": "DESCRIBES", + "relatedSpdxElement": "SPDXRef-RootPackage", + "spdxElementId": "SPDXRef-DOCUMENT" + }, + { + "relationshipType": "DEPENDS_ON", + "relatedSpdxElement": "SPDXRef-Package-342BA5C11805FDDCAF3A2BF48BFDCAB5C0240793089F89196209A39C580902E6", + "spdxElementId": "SPDXRef-RootPackage" + }, + ] +})) +const signedStatement = await cose.scitt.statement.issue({ + iss: 'software.vendor.example', + sub: 'vendor.product.example', + cty: 'application/spdx+json', + secretCoseKey, + payload: statement +}) +const diagnostic = await cose.scitt.receipt.edn(receipt) +\`\`\` + `.trim()) + lines.push(diagnostic.trim()) +}) + let receipt: ArrayBuffer it('issue receipt', async () => { lines.push(`## Issue Receipt`) + entries.push(Buffer.from(signedStatement)) + const logIndex = entries.length - 1 // last entry is the signed statement receipt = await cose.scitt.receipt.issue({ - index: 4, + iss: 'transparency.vendor.example', + sub: 'vendor.product.example', + index: logIndex, entries: entries, secretCoseKey }) @@ -77,8 +249,10 @@ const message3 = cose.cbor.encode({ 3: 3 }) const message4 = cose.cbor.encode(['🔥', 4]) const message5 = cose.cbor.encode({ five: '💀' }) const entries = [message0, message1, message2, message3, message4, message5] -const receipt = await cose.scitt.receipt.issue({ - index: 4, +entries.push(Buffer.from(signedStatement)) +const logIndex = entries.length - 1 // last entry is the signed statement +receipt = await cose.scitt.receipt.issue({ + index: logIndex, entries: entries, secretCoseKey }) @@ -89,17 +263,19 @@ const diagnostic = await cose.scitt.receipt.edn(receipt) }) it('verify receipt', async () => { + const logIndex = entries.length - 1 // last entry is the signed statement lines.push(`## Verify Receipt`) const verificaton = await cose.scitt.receipt.verify({ - entry: entries[4], + entry: entries[logIndex], receipt, publicCoseKey }) expect(verificaton).toBe(true) lines.push(` \`\`\` ts +const logIndex = entries.length - 1 // last entry is the signed statement const verificaton = await cose.scitt.receipt.verify({ - entry: entries[4], + entry: entries[logIndex], receipt, publicCoseKey }) @@ -112,6 +288,25 @@ console.log({ verificaton }) ~~~~`) }) +it('compose transparent statement', async () => { + lines.push(`## Transparent Statement`) + const transparentStatement = cose.scitt.statement.addReceipt({ + statement: signedStatement, + receipt + }) + lines.push(` +\`\`\` ts +const transparentStatement = await cose.scitt.statement.addReceipt({ + statement: signedStatement, + receipt +}) +\`\`\` + `.trim()) + const diagnostic = await cose.scitt.receipt.edn(transparentStatement) + // fs.writeFileSync('test/scitt/ts.cose', transparentStatement) + lines.push(diagnostic.trim()) +}) + afterAll(() => { const final = lines.join('\n\n') fs.writeFileSync('test/scitt/examples.md', final)