-
Notifications
You must be signed in to change notification settings - Fork 0
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
24 changed files
with
582 additions
and
602 deletions.
There are no files selected for viewing
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
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
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
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
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,49 @@ | ||
|
||
import { AeadId, KdfId, KemId, CipherSuite, } from 'hpke-js' | ||
import * as jose from 'jose' | ||
|
||
export type Suite0 = `HPKE-Base-P256-SHA256-AES128GCM` | ||
export const Suite0 = 'HPKE-Base-P256-SHA256-AES128GCM' as Suite0 // aka APPLE-HPKE-v1 | ||
|
||
export type PublicCoseKeyMap = Map<string | number, string | number | Buffer | ArrayBuffer> | ||
export type SecretCoseKeyMap = Map<string | number, string | number | Buffer | ArrayBuffer> | ||
|
||
export const encapsulated_key_header_label = -22222; | ||
export const example_suite_label = -55555; | ||
|
||
const suite0 = new CipherSuite({ | ||
kem: KemId.DhkemP256HkdfSha256, | ||
kdf: KdfId.HkdfSha256, | ||
aead: AeadId.Aes128Gcm, | ||
}) | ||
|
||
export const coseSuites = { | ||
[example_suite_label]: suite0, | ||
} as Record<number, CipherSuite> | ||
|
||
|
||
|
||
export const joseSuites = { | ||
[Suite0]: suite0, | ||
} as Record<string, CipherSuite> | ||
|
||
|
||
export type Suite0CurveName = `P-256` | ||
|
||
export type SuiteNames = Suite0 | ||
export type CurveNames = Suite0CurveName | ||
|
||
|
||
export const algToCrv = { | ||
[Suite0]: 'P-256', | ||
} as Record<SuiteNames, CurveNames> | ||
|
||
|
||
// https://datatracker.ietf.org/doc/html/draft-rha-jose-hpke-encrypt-01#section-4.1.1 | ||
// In both modes, the sender MUST specify the 'alg' parameter in the protected header to indicate the use of HPKE. | ||
|
||
export const craftProtectedHeader = ({ alg, enc, kid }: { alg: Suite0, enc?: string, kid?: string }) => { | ||
return jose.base64url.encode(JSON.stringify({ | ||
alg, enc, kid | ||
})) | ||
} |
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,30 @@ | ||
|
||
import generate from '../jose/generate' | ||
import direct from './direct' | ||
import * as coseKey from '../../../src/key' | ||
import alternateDiagnostic from '../../../src/diagnostic' | ||
|
||
import { Suite0 } from '../common' | ||
|
||
import directExample from '../ecdh-direct-example.json' | ||
|
||
it('sanity', async () => { | ||
const k = await generate(Suite0) | ||
const pt = 'hello world' | ||
const m = new TextEncoder().encode(pt) | ||
const k2 = { | ||
cosePublicKey: coseKey.importJWK(k.publicKeyJwk), | ||
cosePrivateKey: coseKey.importJWK(k.privateKeyJwk) | ||
} | ||
const c3 = await direct.encrypt(m, k2.cosePublicKey) | ||
const c3Diagnostic = await alternateDiagnostic(c3) | ||
console.log('/ COSE HPKE Direct /\n' + c3Diagnostic) | ||
// https://github.com/cose-wg/Examples/blob/3221310e2cf50ad13213daa7ca278209a8bc85fd/ecdh-direct-examples/p256-hkdf-256-01.json | ||
// Compare to direct mode ecdh | ||
const ecdhDirect = await alternateDiagnostic(Buffer.from(directExample.output.cbor, 'hex')) | ||
console.log('/ COSE ECDH Direct /\n' + ecdhDirect) | ||
const d3 = await direct.decrypt(c3, k2.cosePrivateKey) | ||
const rpt3 = new TextDecoder().decode(d3) | ||
expect(rpt3).toBe(pt) | ||
|
||
}) |
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,82 @@ | ||
import crypto from 'crypto' | ||
import * as cbor from 'cbor-web' | ||
|
||
import * as coseKey from '../../../src/key' | ||
// eslint-disable-next-line @typescript-eslint/no-var-requires | ||
|
||
import { coseSuites, example_suite_label, encapsulated_key_header_label, PublicCoseKeyMap, SecretCoseKeyMap } from '../common' | ||
|
||
|
||
const directMode = { | ||
// todo: use jwks instead... | ||
encrypt: async (plaintext: Uint8Array, recipientPublic: PublicCoseKeyMap) => { | ||
const alg = recipientPublic.get(3) || example_suite_label | ||
const kid = recipientPublic.get(2) | ||
if (alg !== example_suite_label) { | ||
throw new Error('Unsupported algorithm') | ||
} | ||
const publicKeyJwk = coseKey.exportJWK(recipientPublic); | ||
const publicKey = await crypto.subtle.importKey( | ||
'jwk', | ||
publicKeyJwk, | ||
{ | ||
name: 'ECDH', | ||
namedCurve: 'P-256', | ||
}, | ||
true, | ||
[], | ||
) | ||
const sender = await coseSuites[alg].createSenderContext({ | ||
recipientPublicKey: publicKey, | ||
}) | ||
const protectedHeaderMap = new Map(); | ||
protectedHeaderMap.set(1, alg) // alg : TBD / restrict alg by recipient key / | ||
const encodedProtectedHeader = cbor.encode(protectedHeaderMap) | ||
const unprotectedHeaderMap = new Map(); | ||
unprotectedHeaderMap.set(4, kid) // kid : ... | ||
unprotectedHeaderMap.set(encapsulated_key_header_label, sender.enc) // https://datatracker.ietf.org/doc/html/draft-ietf-cose-hpke-07#section-3.1 | ||
const external_aad = Buffer.from(new Uint8Array()) | ||
const Enc_structure = ["Encrypt0", encodedProtectedHeader, external_aad] | ||
const internal_aad = cbor.encode(Enc_structure) | ||
const ciphertext = await sender.seal(plaintext, internal_aad) | ||
return cbor.encode([ | ||
encodedProtectedHeader, | ||
unprotectedHeaderMap, | ||
ciphertext | ||
]) | ||
|
||
// cbor.encodeAsync(new Tagged(Sign1Tag, coseSign1Structure), { canonical: true }) | ||
|
||
}, | ||
decrypt: async (coseEnc: ArrayBuffer, recipientPrivate: SecretCoseKeyMap) => { | ||
const decoded = await cbor.decode(coseEnc) | ||
const alg = recipientPrivate.get(3) || example_suite_label | ||
if (alg !== example_suite_label) { | ||
throw new Error('Unsupported algorithm') | ||
} | ||
const privateKeyJwk = coseKey.exportJWK(recipientPrivate) as any; | ||
const privateKey = await crypto.subtle.importKey( | ||
'jwk', | ||
privateKeyJwk, | ||
{ | ||
name: 'ECDH', | ||
namedCurve: privateKeyJwk.crv, | ||
}, | ||
true, | ||
['deriveBits'], | ||
) | ||
const [encodedProtectedHeader, unprotectedHeaderMap, ciphertext] = decoded | ||
const external_aad = Buffer.from(new Uint8Array()) | ||
const Enc_structure = ["Encrypt0", encodedProtectedHeader, external_aad] | ||
const internal_aad = cbor.encode(Enc_structure) | ||
const enc = unprotectedHeaderMap.get(encapsulated_key_header_label) | ||
const recipient = await coseSuites[alg].createRecipientContext({ | ||
recipientKey: privateKey, // rkp (CryptoKeyPair) is also acceptable. | ||
enc | ||
}) | ||
const pt = await recipient.open(ciphertext, internal_aad) | ||
return new Uint8Array(pt) | ||
} | ||
} | ||
|
||
export default directMode |
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,9 @@ | ||
import direct from './direct' | ||
import wrap from './wrap' | ||
|
||
const hpke = { | ||
direct, | ||
wrap | ||
} | ||
const api = { hpke } | ||
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,19 @@ | ||
|
||
import generate from '../jose/generate' | ||
import wrap from './wrap' | ||
import * as coseKey from '../../../src/key' | ||
import { Suite0 } from '../common' | ||
|
||
it('sanity', async () => { | ||
const k = await generate(Suite0) | ||
const pt = 'hello world' | ||
const m = new TextEncoder().encode(pt) | ||
const k2 = { | ||
cosePublicKey: coseKey.importJWK(k.publicKeyJwk), | ||
cosePrivateKey: coseKey.importJWK(k.privateKeyJwk) | ||
} | ||
const c4 = await wrap.encrypt(m, k2.cosePublicKey) | ||
const d4 = await wrap.decrypt(c4, k2.cosePrivateKey) | ||
const rpt4 = new TextDecoder().decode(d4) | ||
expect(rpt4).toBe(pt) | ||
}) |
Oops, something went wrong.