Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Refactoring prior to IETF 118 #6

Merged
merged 29 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import cose from '@transmute/cose'
const cose = require('@transmute/cose')
```


### Inclusion Proof

```ts
Expand Down
Binary file removed inclusion-proof.cose
Binary file not shown.
44 changes: 0 additions & 44 deletions inclusion-proof.md

This file was deleted.

3 changes: 2 additions & 1 deletion src/cbor.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as web from 'cbor-web'

const cbor = {
web
web,
...web
}

export default cbor
9 changes: 9 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,16 @@ import getContentType from './getContentType'
import utils from './utils'
import rfc from './rfc'

import * as key from './key'

import * as lib from './lib'

import * as scitt from './scitt'

const cose = {
key,
scitt,
lib,
utils,
rfc,
detached,
Expand Down
61 changes: 61 additions & 0 deletions src/key/beautify.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { bufferToTruncatedBstr } from "../rfc/beautify/bufferToTruncatedBstr";
import { CoseKeyMap } from "./types";

import { addComment } from "../rfc/beautify/addComment";

const keySorter = (a: string, b: string) => {
const aTag = parseInt(a.split(':')[0])
const bTag = parseInt(b.split(':')[0])
if (aTag >= 0 && bTag >= 0) {
return aTag >= bTag ? 1 : -1
} else {
return aTag >= bTag ? -1 : 1
}
}

export const beautify = (coseKey: CoseKeyMap): string => {
const lines = [] as string[]
const indentSpaces = ' '.repeat(2);
for (const [key, value] of coseKey.entries()) {
switch (key) {
case 1: {
lines.push(addComment(`${indentSpaces}${key}: ${value},`, 'Type'))
break
}
case 2: {
lines.push(addComment(`${indentSpaces}${key}: ${bufferToTruncatedBstr(value)},`, 'Identifier'))
break
}
case 3: {
lines.push(addComment(`${indentSpaces}${key}: ${value},`, 'Algorithm'))
break
}
case -1: {
lines.push(addComment(`${indentSpaces}${key}: ${value},`, `Curve`))
break
}
case -2: {
lines.push(addComment(`${indentSpaces}${key}: ${bufferToTruncatedBstr(value)},`, 'x public key component'))
break
}
case -3: {
lines.push(addComment(`${indentSpaces}${key}: ${bufferToTruncatedBstr(value)},`, 'y public key component'))
break
}
case -4: {
lines.push(addComment(`${indentSpaces}${key}: ${bufferToTruncatedBstr(value)},`, 'd private key component'))
break
}
default: {
throw new Error('Unsupported cose key value: ' + key)
}
}
}
return `
${addComment('{', 'COSE Key')}
${lines.sort(keySorter).join('\n')}
}
`.trim()
}

export const edn = beautify
60 changes: 60 additions & 0 deletions src/key/exportJWK.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { CoseKeyMap } from "./types";
import * as jose from 'jose'
import keyUtils from './keyUtils'


const textDecoder = new TextDecoder()
const sortJwk = (jwk: any) => {
const { kty, kid, alg, crv, x, y, d, ...rest } = jwk
return JSON.parse(JSON.stringify({ kty, kid, alg, crv, x, y, d, ...rest }))
}

const coseKeyToJwk = (coseKey: CoseKeyMap): Record<string, unknown> => {
const jwk = {} as any;
for (const [key, value] of coseKey.entries()) {
switch (key) {
case 1: {
const kty = keyUtils.types.toJOSE.get(value as number)
jwk.kty = kty
break
}
case 2: {
jwk.kid = textDecoder.decode(value as Buffer)
break
}
case 3: {
const alg = keyUtils.algorithms.toJOSE.get(value as number)
jwk.alg = alg
break
}
case -1: {
const crv = keyUtils.curves.toJOSE.get(value as number)
jwk.crv = crv
break
}
case -2: {
// TODO: Length checks
jwk.x = jose.base64url.encode(value as Buffer)
break
}
case -3: {
// TODO: Length checks
jwk.y = jose.base64url.encode(value as Buffer)
break
}
case -4: {
// TODO: Length checks
jwk.d = jose.base64url.encode(value as Buffer)
break
}
default: {
// throw new Error('Unsupported JWK param: ' + key)
}
}
}
return sortJwk(jwk)
}

export const exportJWK = (coseKey: CoseKeyMap): Record<string, unknown> => {
return coseKeyToJwk(coseKey)
}
15 changes: 15 additions & 0 deletions src/key/generate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as jose from 'jose'
import { CoseKeyMap } from './types';
import { importJWK } from './importJWK'
import keyUtils from './keyUtils'

export const generate = async (alg: number): Promise<CoseKeyMap> => {
const joseAlg = keyUtils.algorithms.toJOSE.get(alg);
if (!joseAlg) {
throw new Error('Unsupported algorithm: ' + alg)
}
const cryptoKeyPair = await jose.generateKeyPair(joseAlg, { extractable: true });
const secretKeyJwk = await jose.exportJWK(cryptoKeyPair.privateKey)
const jwkThumbprint = await jose.calculateJwkThumbprint(secretKeyJwk)
return importJWK({ ...secretKeyJwk, alg: joseAlg, kid: jwkThumbprint })
}
59 changes: 59 additions & 0 deletions src/key/importJWK.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { CoseKeyMap } from "./types";
import * as jose from 'jose'
import { typedArrayToBuffer } from '../utils'
import keyUtils from './keyUtils'


const jwkToCoseKey = (jwk: Record<string, unknown>): CoseKeyMap => {
const coseKey = new Map();
const textEncoder = new TextEncoder()
for (const [key, value] of Object.entries(jwk)) {
const coseKeyParam = keyUtils.parameters.toCOSE.get(key)
switch (key) {
case 'kty': {
const coseKeyValue = keyUtils.types.toCOSE.get(value)
coseKey.set(coseKeyParam, coseKeyValue)
break
}
case 'kid': {
const asBstr = textEncoder.encode(value as string)
coseKey.set(coseKeyParam, typedArrayToBuffer(asBstr))
break
}
case 'alg': {
const coseKeyValue = keyUtils.algorithms.toCOSE.get(value)
coseKey.set(coseKeyParam, coseKeyValue)
break
}
case 'crv': {
const coseKeyValue = keyUtils.curves.toCOSE.get(value)
coseKey.set(coseKeyParam, coseKeyValue)
break
}
case 'x': {
// TODO: Length checks
coseKey.set(coseKeyParam, typedArrayToBuffer(jose.base64url.decode(value as string)))
break
}
case 'y': {
// TODO: Length checks
coseKey.set(coseKeyParam, typedArrayToBuffer(jose.base64url.decode(value as string)))
break
}
case 'd': {
// TODO: Length checks
coseKey.set(coseKeyParam, typedArrayToBuffer(jose.base64url.decode(value as string)))
break
}
default: {
throw new Error('Unsupported JWK param: ' + key)
}
}
}
return coseKey
}


export const importJWK = (jwk: Record<string, unknown>): CoseKeyMap => {
return jwkToCoseKey(jwk)
}
10 changes: 10 additions & 0 deletions src/key/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export * from './generate'
export * from './beautify'

export * from './importJWK'
export * from './exportJWK'
export * from './thumbprint'

import keyUtils from './keyUtils'

export const utils = keyUtils
63 changes: 63 additions & 0 deletions src/key/keyUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@

import { publicFromPrivate } from "./publicFromPrivate";

// https://www.iana.org/assignments/cose/cose.xhtml#algorithms

const reverse = (map: Map<any, any>) => new Map(Array.from(map, (a: any) => a.reverse()))

const algsMap = new Map([
[-7, 'ES256'],
[-35, 'ES384'],
[-36, 'ES512'],
]);

const algorithms = {
toJOSE: algsMap,
toCOSE: reverse(algsMap)
}

const paramsMap = new Map([
[1, 'kty'],
[2, 'kid'],
[3, 'alg'],
[-1, 'crv'],
[-2, 'x'],
[-3, 'y'],
[-4, 'd'],
]);

const parameters = {
toJOSE: paramsMap,
toCOSE: reverse(paramsMap)
}

const curvesMap = new Map([
[1, 'P-256'],
[2, 'P-384'],
[3, 'P-521'],
]);

const curves = {
toJOSE: curvesMap,
toCOSE: reverse(curvesMap)
}

const typesMap = new Map([
[2, 'EC'],
]);

const types = {
toJOSE: typesMap,
toCOSE: reverse(typesMap)
}


export const keyUtils = {
publicFromPrivate,
algorithms,
parameters,
curves,
types
}

export default keyUtils
13 changes: 13 additions & 0 deletions src/key/publicFromPrivate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { SecretCoseKeyMap, PublicCoseKeyMap } from "./types";

export const publicFromPrivate = (secretCoseKeyMap: SecretCoseKeyMap): PublicCoseKeyMap => {
const publicCoseKeyMap = new Map(secretCoseKeyMap)
if (publicCoseKeyMap.get(1) !== 2) {
throw new Error('Only EC2 keys are supported')
}
if (!publicCoseKeyMap.get(-4)) {
throw new Error('secretCoseKeyMap is not a secret / private key (has no d / -4)')
}
publicCoseKeyMap.delete(-4);
return publicCoseKeyMap
}
Loading
Loading