Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
OR13 committed Jul 9, 2023
1 parent 5e93a92 commit 7905d13
Show file tree
Hide file tree
Showing 17 changed files with 690 additions and 15 deletions.
11 changes: 4 additions & 7 deletions package-lock.json

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

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
},
"dependencies": {
"ajv": "^8.12.0",
"jose": "^4.14.4"
"jose": "^4.14.4",
"js-yaml": "^4.1.0"
}
}
190 changes: 190 additions & 0 deletions src/controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import * as jose from 'jose'


import joseApi from './jose'

export type RequestGenerateKey = {
alg: string
crv?: string
}

export const createPrivateKey = async (
{ crv, alg }: RequestGenerateKey,
extractable = true,
) => {
// https://media.defense.gov/2022/Sep/07/2003071834/-1/-1/0/CSA_CNSA_2.0_ALGORITHMS_.PDF
if (alg === 'ECDH-ES+A256KW' && crv === undefined) {
crv = 'P-384'
}
const { publicKey, privateKey } = await jose.generateKeyPair(alg, {
extractable,
crv,
})
const publicKeyJwk = await jose.exportJWK(publicKey)
const privateKeyJwk = await jose.exportJWK(privateKey)
privateKeyJwk.alg = alg
privateKeyJwk.kid = await jose.calculateJwkThumbprintUri(publicKeyJwk)
return formatJwk(privateKeyJwk)
}

const formatJwk = (jwk: any) => {
const {
kid,
x5u,
x5c,
x5t,
kty,
crv,
alg,
use,
key_ops,
x,
y,
d,
...rest
} = structuredClone(jwk)
return JSON.parse(
JSON.stringify({
kid,
kty,
crv,
alg,
use,
key_ops,
x,
y,
d,
x5u,
x5c,
x5t,
...rest,
}),
)
}

export const publicKeyToUri = async (publicKeyJwk: any) => {
return jose.calculateJwkThumbprintUri(publicKeyJwk)
}

export const publicFromPrivate = (privateKeyJwk: any) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { d, p, q, dp, dq, qi, key_ops, ...publicKeyJwk } = privateKeyJwk
return formatJwk(publicKeyJwk)
}

export const encryptToKey = async ({ publicKey, plaintext }: any) => {
const jwe = await new jose.FlattenedEncrypt(plaintext)
.setProtectedHeader({ alg: publicKey.alg, enc: 'A256GCM' })
.encrypt(await jose.importJWK(publicKey))
return jwe
}

export const decryptWithKey = async ({ privateKey, ciphertext }: any) => {
return jose.flattenedDecrypt(ciphertext, await jose.importJWK(privateKey))
}



export const formatVerificationMethod = (vm: any) => {
const formatted = {
id: vm.id,
type: vm.type,
controller: vm.controller,
publicKeyJwk: vm.publicKeyJwk,
}
return JSON.parse(JSON.stringify(formatted))
}

export const createVerificationMethod = async (publicKeyJwk: any) => {
const holder = await jose.calculateJwkThumbprintUri(publicKeyJwk)
return {
id: holder,
type: 'JsonWebKey',
controller: holder,
publicKeyJwk: formatJwk(publicKeyJwk),
}
}

export const dereferencePublicKey = async (didUrl: string) =>
jose.importJWK(
JSON.parse(
new TextDecoder().decode(
jose.base64url.decode(didUrl.split(':')[2].split('#')[0]),
),
),
)

export const publicKeyToVerificationMethod = async (publicKeyJwk: any) => {
return '#' + publicKeyToUri(publicKeyJwk)
}


export const publicKeyToDid = (publicKeyJwk: any) => {
const id = `did:jwk:${jose.base64url.encode(
JSON.stringify(formatJwk(publicKeyJwk)),
)}`
return id
}

const signatures = ['authentication', 'assertionMethod']
const encryptions = ['keyAgreement']
const both = [...signatures, ...encryptions]
const relationships: any = {
ES256: both,
ES384: both,
EdDSA: signatures,
X25519: encryptions,
ES256K: signatures,
}

const did = {
document: {
create: async (publicKeyJwk: any) => {
const id = publicKeyToDid(publicKeyJwk)
const vm = await createVerificationMethod(publicKeyJwk)
const ddoc: any = {
'@context': [
'https://www.w3.org/ns/did/v1',
{ '@vocab': 'https://www.iana.org/assignments/jose#' },
],
id,
verificationMethod: [
formatVerificationMethod({
...vm,
id: '#0',
controller: id,
}),
],
}
relationships[publicKeyJwk.alg].forEach((vmr: any) => {
ddoc[vmr] = ['#0']
})
return ddoc
},
identifier: {
replace: (doc: any, source: any, target: any) => {
return JSON.parse(
JSON.stringify(doc, function replacer(key, value) {
if (value === source) {
return target
}
return value
}),
)
},
},
},
}




const key = {
...joseApi,
createPrivateKey,
publicFromPrivate
}

const controller = { did, key }

export default controller
5 changes: 4 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import vc from './vc'
import controller from './controller'

export * from './vc/types'

export { vc }

const api = { vc }
const api = { controller, vc }

export default api
42 changes: 42 additions & 0 deletions src/jose/attached.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@


import detached, { RequestSigner, RequestVerifier, VerifiedFlattenedJws, RequestFlattenedJws } from './detached'

export type AttachedSigner = {
sign: ({ protectedHeader, payload }: RequestFlattenedJws) => Promise<string>
}

export const signer = async ({ privateKey }: RequestSigner): Promise<AttachedSigner> => {
const signer = await detached.signer({ privateKey })
return {
sign: async ({ protectedHeader, payload }) => {
const sig = await signer.sign({
protectedHeader,
payload
})
return `${sig.protected}.${sig.payload}.${sig.signature}`
}
}
}

export type AttachedVerifier = {
verify: (jws: string) => Promise<VerifiedFlattenedJws>
}

export const verifier = async ({ publicKey }: RequestVerifier): Promise<AttachedVerifier> => {
const verifier = await detached.verifier({ publicKey })
return {
verify: async (jws: string) => {
const [protectedHeader, payload, signature] = jws.split('.')
const result = await verifier.verify({
protected: protectedHeader, payload, signature
})
return result
}
}
}


const api = { signer, verifier }

export default api
50 changes: 50 additions & 0 deletions src/jose/detached.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as jose from 'jose'

import { getKey } from './getKey'

// TODO Remote KMS.

export type FlattenedJwsInput = {
protectedHeader: jose.ProtectedHeaderParameters
payload: Uint8Array
}

export type RequestFlattenedJws = FlattenedJwsInput
export type VerifiedFlattenedJws = FlattenedJwsInput

export type RequestSigner = {
privateKey: jose.KeyLike
}

export const signer = async ({ privateKey }: RequestSigner) => {
const key = await getKey(privateKey)
return {
sign: async ({ protectedHeader, payload }: RequestFlattenedJws): Promise<jose.FlattenedJWS> => {
const jws = await new jose.FlattenedSign(payload)
.setProtectedHeader(protectedHeader)
.sign(key)
return jws
},
}
}

export type RequestVerifier = {
publicKey: jose.KeyLike
}

export const verifier = async ({ publicKey }: RequestVerifier) => {
const key = await getKey(publicKey)
return {
verify: async (jws: jose.FlattenedJWS): Promise<VerifiedFlattenedJws> => {
const { protectedHeader, payload } = await jose.flattenedVerify(
jws,
key,
)
return { protectedHeader, payload } as VerifiedFlattenedJws
},
}
}

const api = { signer, verifier }

export default api
5 changes: 5 additions & 0 deletions src/jose/getKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { KeyLike, importJWK } from 'jose'

export const getKey = async (data: any): Promise<KeyLike> => {
return data.kty ? importJWK(data) : data;
};
7 changes: 7 additions & 0 deletions src/jose/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import attached from './attached'
import detached from './detached'
import { getKey } from './getKey'

const api = { attached, detached, getKey }

export default api
Loading

0 comments on commit 7905d13

Please sign in to comment.