Skip to content

Commit

Permalink
JWS canon
Browse files Browse the repository at this point in the history
  • Loading branch information
oed committed Nov 24, 2023
1 parent 626cf7d commit 8fd01d6
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 51 deletions.
2 changes: 1 addition & 1 deletion packages/varsig/src/canons/eip712.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ export function fromOriginal({
varintes.encode(0xe7)[0], // key type
recoveryBit,
varintes.encode(0x1b)[0], // hash type
varintes.encode(0xe712)[0], // canonicalizer codec
varintes.encode(SIGIL)[0], // canonicalizer codec
metadataLength,
metadataBytes,
signatureBytes,
Expand Down
138 changes: 95 additions & 43 deletions packages/varsig/src/canons/jws.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,95 @@
// import type { GeneralJWS } from 'dids'
// import * as uint8arrays from 'uint8arrays'
// import { toBytes } from '../bytes.js'
// import { ENCODING, HASHING, SIGNING } from '../varsig.js'
//
// export function encode(jws: GeneralJWS) {
// const signature0 = jws.signatures[0]
// // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
// const protectedHeader = JSON.parse(
// uint8arrays.toString(uint8arrays.fromString(signature0.protected, 'base64url'))
// )
// const payload = JSON.parse(uint8arrays.toString(uint8arrays.fromString(jws.payload, 'base64url')))
// const signature = uint8arrays.fromString(signature0.signature, 'base64url')
// // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
// switch (protectedHeader.alg) {
// case 'EdDSA': {
// const sig = toBytes({
// encoding: ENCODING.JWT,
// hashing: HASHING.SHA2_256,
// signing: SIGNING.ED25519,
// signature: signature,
// })
// delete protectedHeader.typ
// delete protectedHeader.alg
// return {
// _header: protectedHeader,
// ...payload,
// _signature: sig,
// }
// }
// case 'ES256': {
// const sig = toBytes({
// encoding: ENCODING.JWT,
// hashing: HASHING.SHA2_256,
// signing: SIGNING.RSA
// })
// }
// }
// return {
// _header: {},
// _sig: {},
// }
// }
import { CanonicalizationAlgo } from '../canonicalization.js'
import { BytesTape } from '../bytes-tape.js'
import { HashingAlgo } from '../hashing'
import { SigningKind } from '../signing'
import * as varintes from 'varintes'
import * as uint8arrays from 'uint8arrays'
import { encode, decode } from '@ipld/dag-json'

type IpldNode = Record<string, any>
type IpldNodeSigned = IpldNode & { _sig: Uint8Array }

const KEY_TYPE_BY_ALG_CRV: Record<string, Record<string, number>> = {
ES256: { default: 0x1200 },
EdDSA: { ed448: 0x1203, ed25519: 0xec, default: 0xec },
ES256K: { default: 0xe7 },
}
const HASH_BY_KEY_TYPE: Record<number, number> = {
0x1200: 0x12,
0xe7: 0x12,
0xec: 0x12,
0x1203: 0x19,
}

const toB64u = (bytes: Uint8Array) => uint8arrays.toString(bytes, 'base64url')
const fromB64u = (b64u: string) => uint8arrays.fromString(b64u, 'base64url')


const SIGIL = 0x7053 // jose

export const JWS = { SIGIL, prepareCanonicalization, fromOriginal }

export function prepareCanonicalization(
tape: BytesTape,
hashType: HashingAlgo,
keyType: SigningKind
): CanonicalizationAlgo {
const protectedLength = tape.readVarint()
const protectedBytes = tape.read(protectedLength)
const protected1 = JSON.parse(uint8arrays.toString(protectedBytes))

const keyTypeFromProtected = findKeyType(protected1)
if (keyType !== keyTypeFromProtected) throw new Error(`Key type missmatch: ${keyType}, ${keyTypeFromProtected}`)
if (hashType !== HASH_BY_KEY_TYPE[keyType]) throw new Error(`Hash type missmatch: ${hashType}, ${HASH_BY_KEY_TYPE[keyType]}`)

const can = (node: IpldNode) => {
// encode node using dag-json from multiformats
const payloadB64u = toB64u(uint8arrays.fromString(JSON.stringify(encode(node))))
const protectedB64u = toB64u(protectedBytes)
return uint8arrays.fromString(`${protectedB64u}.${payloadB64u}`)
}
can.kind = SIGIL
can.original = (node: IpldNode, signature: Uint8Array) => {
const payloadB64u = toB64u(encode(node))
const protectedB64u = toB64u(protectedBytes)
const signatureB64u = toB64u(signature)
return `${protectedB64u}.${payloadB64u}.${signatureB64u}`
}
return can
}

export function fromOriginal(jws: string): IpldNodeSigned {
const [protectedB64u, payloadB64u, signatureB64u] = jws.split('.')
const node = decode(fromB64u(payloadB64u)) as IpldNode
const protectedBytes = fromB64u(protectedB64u)
const protected1 = JSON.parse(uint8arrays.toString(protectedBytes))
const protectedLength = varintes.encode(protectedBytes.length)[0]
const signature = fromB64u(signatureB64u)

const keyType = findKeyType(protected1)
const hashType = HASH_BY_KEY_TYPE[keyType]

// TODO - this doesn't currently support RSA signatures
const varsig = uint8arrays.concat([
new Uint8Array([0x34]), // varsig sigil
varintes.encode(keyType)[0], // key type
varintes.encode(hashType)[0], // hash type
varintes.encode(SIGIL)[0], // canonicalizer codec
protectedLength,
protectedBytes,
signature,
])
return { ...node, _sig: varsig }
}

interface ProtectedHeader {
alg: string
crv?: string
}

function findKeyType({ alg, crv }: ProtectedHeader): number {
if (!alg) throw new Error(`Missing alg in protected header`)
const keyType = KEY_TYPE_BY_ALG_CRV[alg][crv || 'default']
if (!keyType) throw new Error(`Unsupported alg: ${alg}, or crv: ${crv}`)
return keyType
}
22 changes: 15 additions & 7 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 8fd01d6

Please sign in to comment.