Skip to content

Commit

Permalink
Cleaning
Browse files Browse the repository at this point in the history
  • Loading branch information
OR13 committed Aug 24, 2024
1 parent 9d8bcc1 commit e7a630e
Show file tree
Hide file tree
Showing 20 changed files with 182 additions and 96 deletions.
28 changes: 17 additions & 11 deletions src/cose/Params.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
/* eslint-disable @typescript-eslint/no-explicit-any */

// This module is just just a limited set of the IANA registries,
// exposed to make Map initialization more readable

Expand Down Expand Up @@ -116,28 +118,32 @@ export const Direct = {
'HPKE-Base-P256-SHA256-AES128GCM': 35
}

export const EC2 = 2

export const KeyTypes = {
EC2
}

export const KeyType = 1
export const KeyAlg = 3
export const KeyCurve = -1
export const KeyId = 2

export const Epk = {
export const Key = {
Kty: KeyType,
Crv: KeyCurve,
Alg: KeyAlg,
Kid: KeyId
}

export const Key = {
Kty: KeyType,
Crv: KeyCurve,
Alg: KeyAlg
export const Epk = {
...Key
}

export const KeyTypes = {
EC2: 2
}

export const EC2 = {
...Key,
Crv: -1,
X: -2,
Y: -3,
D: -4
}

export const Curves = {
Expand Down
27 changes: 12 additions & 15 deletions src/cose/key/convertCoseKeyToJsonWebKey.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,24 @@
import { base64url, calculateJwkThumbprint } from "jose";
import { CoseKey } from ".";


import { IANACOSEAlgorithms } from '../algorithms';
import { IANACOSEEllipticCurves } from '../elliptic-curves';

const algorithms = Object.values(IANACOSEAlgorithms)
const curves = Object.values(IANACOSEEllipticCurves)

import { formatJwk } from "./formatJwk";
import { iana } from "../../iana";
import { EC2, Key, KeyTypes } from "../Params";

export const convertCoseKeyToJsonWebKey = async <T>(coseKey: CoseKey): Promise<T> => {
const kty = coseKey.get(1) as number
const kid = coseKey.get(2)
const alg = coseKey.get(3)
const crv = coseKey.get(-1)
// kty EC, kty: EK
if (![2, 5].includes(kty)) {
const kty = coseKey.get(Key.Kty) as number
// kty EC2
if (![KeyTypes.EC2].includes(kty)) {
throw new Error('This library requires does not support the given key type')
}
const foundAlgorithm = algorithms.find((param) => {
return param.Value === `${alg}`
})
const kid = coseKey.get(Key.Kid)
const alg = coseKey.get(Key.Alg)
const crv = coseKey.get(EC2.Crv)
const foundAlgorithm = iana["COSE Algorithms"].getByValue(alg as number)
if (!foundAlgorithm) {
throw new Error('This library requires keys to use fully specified algorithms')
}
Expand All @@ -36,9 +33,9 @@ export const convertCoseKeyToJsonWebKey = async <T>(coseKey: CoseKey): Promise<T
alg: foundAlgorithm.Name,
crv: foundCurve.Name
} as any
const x = coseKey.get(-2) as any
const y = coseKey.get(-3) as any
const d = coseKey.get(-4) as any
const x = coseKey.get(EC2.X) as any
const y = coseKey.get(EC2.Y) as any
const d = coseKey.get(EC2.D) as any
if (x) {
jwk.x = base64url.encode(x)
}
Expand Down
8 changes: 2 additions & 6 deletions src/cose/key/convertJsonWebKeyToCoseKey.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,12 @@ import { base64url } from 'jose'
import { toArrayBuffer } from '../../cbor'

import { IANACOSEKeyCommonParameters } from '../key-common-parameters';
import { IANACOSEAlgorithms } from '../algorithms';
import { IANACOSEKeyTypeParameters, IANACOSEKeyTypeParameter } from '../key-type-parameters';
import { IANACOSEKeyTypes } from '../key-type';
import { IANACOSEEllipticCurves } from '../elliptic-curves';
import { PublicKeyJwk, PrivateKeyJwk } from '../sign1';
import { iana } from '../../iana';


const algorithms = Object.values(IANACOSEAlgorithms)
const commonParams = Object.values(IANACOSEKeyCommonParameters)
const keyTypeParams = Object.values(IANACOSEKeyTypeParameters)
const keyTypes = Object.values(IANACOSEKeyTypes)
Expand Down Expand Up @@ -82,9 +80,7 @@ export const convertJsonWebKeyToCoseKey = async <T>(jwk: PublicKeyJwk | PrivateK
}
case 'alg': {
if (foundCommonParam) {
const foundAlgorithm = algorithms.find((param) => {
return param.Name === value
})
const foundAlgorithm = iana['COSE Algorithms'].getByName(value)
if (foundAlgorithm) {
coseKey.set(label, parseInt(foundAlgorithm.Value, 10))
} else {
Expand Down
13 changes: 10 additions & 3 deletions src/cose/key/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { IANACOSEAlgorithms } from "../algorithms"

import { CoseKey } from '.'
export type CoseKeyAgreementAlgorithms = 'ECDH-ES+A128KW'
export type CoseSignatureAlgorithms = 'ES256' | 'ES384' | 'ES512'
export type CoseSignatureAlgorithms = 'ES256' | 'ES384' | 'ES512' | 'ESP256'
export type ContentTypeOfJsonWebKey = 'application/jwk+json'
export type ContentTypeOfCoseKey = 'application/cose-key'
export type PrivateKeyContentType = ContentTypeOfCoseKey | ContentTypeOfJsonWebKey
Expand All @@ -18,17 +18,24 @@ import { thumbprint } from "./thumbprint"

import { formatJwk } from './formatJwk'

import { iana } from '../../iana'

export const generate = async <T>(alg: CoseSignatureAlgorithms, contentType: PrivateKeyContentType = 'application/jwk+json'): Promise<T> => {
const knownAlgorithm = Object.values(IANACOSEAlgorithms).find((
let knownAlgorithm = Object.values(IANACOSEAlgorithms).find((
entry
) => {
return entry.Name === alg
})
if (!knownAlgorithm) {
knownAlgorithm = iana["COSE Algorithms"].getByName(alg)
}
if (!knownAlgorithm) {
throw new Error('Algorithm is not supported.')
}
const cryptoKeyPair = await generateKeyPair(knownAlgorithm.Name, { extractable: true });
const cryptoKeyPair = await generateKeyPair(
iana["COSE Algorithms"]["less-specified"](knownAlgorithm.Name),
{ extractable: true }
);
const privateKeyJwk = await exportJWK(cryptoKeyPair.privateKey)
const jwkThumbprint = await calculateJwkThumbprint(privateKeyJwk)
privateKeyJwk.kid = jwkThumbprint
Expand Down
7 changes: 4 additions & 3 deletions src/cose/key/publicFromPrivate.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { CoseKey } from ".";
import { EC2, Key, KeyTypes } from "../Params";
import { PrivateKeyJwk } from "../sign1";


Expand All @@ -13,13 +14,13 @@ export const extractPublicKeyJwk = (privateKeyJwk: PrivateKeyJwk) => {

export const extractPublicCoseKey = (secretKey: CoseKey) => {
const publicCoseKeyMap = new Map(secretKey)
if (publicCoseKeyMap.get(1) !== 2) {
if (publicCoseKeyMap.get(Key.Kty) !== KeyTypes.EC2) {
throw new Error('Only EC2 keys are supported')
}
if (!publicCoseKeyMap.get(-4)) {
if (!publicCoseKeyMap.get(EC2.D)) {
throw new Error('privateKey is not a secret / private key (has no d / -4)')
}
publicCoseKeyMap.delete(-4);
publicCoseKeyMap.delete(EC2.D);
return publicCoseKeyMap
}

Expand Down
13 changes: 9 additions & 4 deletions src/cose/key/thumbprint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ import { calculateJwkThumbprint, calculateJwkThumbprintUri, base64url } from "jo
import { encodeCanonical } from "../../cbor";

import subtleCryptoProvider from "../../crypto/subtleCryptoProvider";
import { EC2, Key, KeyTypes } from "../Params";
import { CoseKey } from ".";

// https://www.ietf.org/archive/id/draft-ietf-cose-key-thumbprint-01.html#section-6
const calculateCoseKeyThumbprint = async (coseKey: Map<any, any>): Promise<ArrayBuffer> => {
const calculateCoseKeyThumbprint = async (coseKey: CoseKey): Promise<ArrayBuffer> => {
if (coseKey.get(Key.Kty) !== KeyTypes.EC2) {
throw new Error('Unsupported key type (Only EC2 are supported')
}
const onlyRequiredMap = new Map()
const requriedKeys = [1, -1, -2, -3]
const requiredKeys = [EC2.Kty, EC2.Crv, EC2.X, EC2.Y]
for (const [key, value] of coseKey.entries()) {
if (requriedKeys.includes(key as number)) {
if (requiredKeys.includes(key as number)) {
onlyRequiredMap.set(key, value)
}
}
Expand All @@ -19,7 +24,7 @@ const calculateCoseKeyThumbprint = async (coseKey: Map<any, any>): Promise<Array
return digest
}

const calculateCoseKeyThumbprintUri = async (coseKey: Map<any, any>): Promise<string> => {
const calculateCoseKeyThumbprintUri = async (coseKey: CoseKey): Promise<string> => {
const prefix = `urn:ietf:params:oauth:ckt:sha-256`
const digest = await calculateCoseKeyThumbprint(coseKey)
return `${prefix}:${base64url.encode(new Uint8Array(digest))}`
Expand Down
16 changes: 0 additions & 16 deletions src/cose/sign1/getAlgFromVerificationKey.ts

This file was deleted.

5 changes: 5 additions & 0 deletions src/cose/sign1/getDigestFromVerificationKey.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@

const joseToCose = new Map<string, string>()


joseToCose.set('ES256', `SHA-256`)
joseToCose.set('ES384', `SHA-384`)
joseToCose.set('ES512', `SHA-512`)

// fully specified
joseToCose.set('ESP256', `SHA-256`)
joseToCose.set('ESP384', `SHA-384`)

const getDigestFromVerificationKey = (alg: string): string => {
const digestAlg = joseToCose.get(alg)
if (!digestAlg) {
Expand Down
6 changes: 2 additions & 4 deletions src/cose/sign1/hashEnvelopeSigner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,15 @@ import { RequestCoseSign1Signer, RequestCoseSign1 } from "./types"

// https://datatracker.ietf.org/doc/draft-steele-cose-hash-envelope/


import { Protected } from "../Params";

import { Hash, Protected } from "../Params";

export const hash = {
signer: ({ remote }: RequestCoseSign1Signer) => {
return {
sign: async ({ protectedHeader, unprotectedHeader, payload }: RequestCoseSign1): Promise<Uint8Array> => {
const subtle = await subtleCryptoProvider();
const hashEnvelopeAlgorithm = protectedHeader.get(Protected.PayloadHashAlgorithm)
if (hashEnvelopeAlgorithm !== -16) {
if (hashEnvelopeAlgorithm !== -Hash.SHA256) {
throw new Error('Unsupported hash envelope algorithm (-16 is only one supported)')
}
const payloadHash = await subtle.digest("SHA-256", payload)
Expand Down
4 changes: 2 additions & 2 deletions src/cose/sign1/payload.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@

import { decodeFirst, decodeFirstSync, encode, EMPTY_BUFFER } from '../../cbor'
import { DecodedToBeSigned, ProtectedHeaderMap } from './types'
import { decodeFirst, encode } from '../../cbor'



export const attach = async (coseSign1Bytes: ArrayBuffer, payload: ArrayBuffer) => {
Expand Down
9 changes: 6 additions & 3 deletions src/cose/sign1/verifier.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@

import { decodeFirst, decodeFirstSync, encode, EMPTY_BUFFER } from '../../cbor'
import { RequestCoseSign1Verifier, RequestCoseSign1Verify } from './types'
import getAlgFromVerificationKey from './getAlgFromVerificationKey'

import { DecodedToBeSigned, ProtectedHeaderMap } from './types'
import rawVerifier from '../../crypto/verifier'

import { iana } from '../../iana'
import { Protected } from '../Params'

const verifier = ({ resolver }: RequestCoseSign1Verifier) => {
return {
verify: async ({ coseSign1, externalAAD }: RequestCoseSign1Verify): Promise<ArrayBuffer> => {
const publicKeyJwk = await resolver.resolve(coseSign1)
const algInPublicKey = getAlgFromVerificationKey(`${publicKeyJwk.alg}`)
const algInPublicKey = parseInt(`${iana['COSE Algorithms'].getByName(`${publicKeyJwk.alg}`)?.Value}`, 10)
const ecdsa = rawVerifier({ publicKeyJwk })
const obj = await decodeFirst(coseSign1);
const signatureStructure = obj.value;
Expand All @@ -21,7 +24,7 @@ const verifier = ({ resolver }: RequestCoseSign1Verifier) => {
}
const [protectedHeaderBytes, _, payload, signature] = signatureStructure;
const protectedHeaderMap: ProtectedHeaderMap = (!protectedHeaderBytes.length) ? new Map() : decodeFirstSync(protectedHeaderBytes);
const algInHeader = protectedHeaderMap.get(1)
const algInHeader = protectedHeaderMap.get(Protected.Alg)
if (algInHeader !== algInPublicKey) {
throw new Error('Verification key does not support algorithm: ' + algInHeader);
}
Expand Down
6 changes: 4 additions & 2 deletions src/crypto/signer.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/* eslint-disable @typescript-eslint/no-unused-vars */

import { toArrayBuffer } from '../cbor'
import { PrivateKeyJwk } from '../cose/sign1'
Expand All @@ -8,15 +9,16 @@ import getDigestFromVerificationKey from '../cose/sign1/getDigestFromVerificatio

const signer = ({ privateKeyJwk }: { privateKeyJwk: PrivateKeyJwk }) => {
const digest = getDigestFromVerificationKey(`${privateKeyJwk.alg}`)
const { alg, ...withoutAlg } = privateKeyJwk
return {
sign: async (toBeSigned: ArrayBuffer): Promise<ArrayBuffer> => {
const subtle = await subtleCryptoProvider()
const signingKey = await subtle.importKey(
"jwk",
privateKeyJwk,
withoutAlg,
{
name: "ECDSA",
namedCurve: privateKeyJwk.crv,
namedCurve: withoutAlg.crv,
},
true,
["sign"],
Expand Down
6 changes: 4 additions & 2 deletions src/crypto/verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,17 @@ import { PublicKeyJwk } from '../cose/sign1'

const verifier = ({ publicKeyJwk }: { publicKeyJwk: PublicKeyJwk }) => {
const digest = getDigestFromVerificationKey(`${publicKeyJwk.alg}`)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { alg, ...withoutAlg } = publicKeyJwk
return {
verify: async (toBeSigned: ArrayBuffer, signature: ArrayBuffer): Promise<ArrayBuffer> => {
const subtle = await subtleCryptoProvider()
const verificationKey = await subtle.importKey(
"jwk",
publicKeyJwk,
withoutAlg,
{
name: "ECDSA",
namedCurve: publicKeyJwk.crv,
namedCurve: withoutAlg.crv,
},
true,
["verify"],
Expand Down
Loading

0 comments on commit e7a630e

Please sign in to comment.