-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
690 additions
and
15 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -41,6 +41,7 @@ | |
}, | ||
"dependencies": { | ||
"ajv": "^8.12.0", | ||
"jose": "^4.14.4" | ||
"jose": "^4.14.4", | ||
"js-yaml": "^4.1.0" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.