Skip to content

Commit

Permalink
updates
Browse files Browse the repository at this point in the history
  • Loading branch information
OR13 committed Jul 7, 2024
1 parent 8c45a12 commit 36e2abf
Show file tree
Hide file tree
Showing 9 changed files with 117 additions and 28 deletions.
4 changes: 3 additions & 1 deletion src/jose-hpke/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ import * as jwk from './jwk'
import * as jwe from './jwe'
import * as jwt from './jwt'

export { jwk, jwe, jwt }
import * as modes from './modes'

export { jwk, jwe, jwt, modes }
22 changes: 6 additions & 16 deletions src/jose-hpke/jwe/compact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,28 +6,18 @@ import { isKeyAlgorithmSupported } from "../jwk";
import { AeadId, CipherSuite, KdfId, KemId, RecipientContextParams, SenderContextParams } from "hpke-js";

import { HPKE_JWT_DECRYPT_OPTIONS, HPKE_JWT_ENCRYPT_OPTIONS } from '../types'
import { prepareSenderContext } from "../prepareSenderContext";
import { prepareRecipientHeader } from "../prepareRecipientHeader";

const decoder = new TextDecoder()
import { modes } from "..";

export const encrypt = async (plaintext: Uint8Array, publicKeyJwk: any, options?: HPKE_JWT_ENCRYPT_OPTIONS): Promise<string> => {
if (!isKeyAlgorithmSupported(publicKeyJwk)) {
throw new Error('Public key algorithm is not supported')
}

const sender = await prepareSenderContext(publicKeyJwk, options)
const header = await prepareRecipientHeader(publicKeyJwk, options)
const encodedEncapsulatedKey = base64url.encode(new Uint8Array(sender.enc))
const protectedHeader = base64url.encode(JSON.stringify(header))
const aad = new TextEncoder().encode(protectedHeader)
// apu / apv are protected by aad, not as part of kdf
const ciphertext = base64url.encode(new Uint8Array(await sender.seal(plaintext, aad)));
const encrypted_key = encodedEncapsulatedKey
const iv = ``
const tag = ``
if (options?.additionalAuthenticatedData) {
throw new Error('AdditionalAuthenticatedData is not supported in compact mode')
}
const encrypted = await modes.integrated.encrypt(plaintext, publicKeyJwk, options as any)
// https://datatracker.ietf.org/doc/html/rfc7516#section-3.1
const jwe = `${protectedHeader}.${encrypted_key}.${iv}.${ciphertext}.${tag}`
const jwe = `${encrypted.protected}.${encrypted.encrypted_key}.${encrypted.iv || ''}.${encrypted.ciphertext}.${encrypted.tag || ''}`
return jwe
}

Expand Down
4 changes: 3 additions & 1 deletion src/jose-hpke/jwe/json.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,9 @@ export const decrypt = async (jwe: any, recipients: any, options: HPKE_JWT_DECRY
return r.header.kid === privateKeyId
})
// setup hpke
const context = await prepareRecipientContext(recipientPrivateKeyJwk, recipient.header, options)

const ek = jose.base64url.decode(recipient.header.ek)
const context = await prepareRecipientContext(recipientPrivateKeyJwk, ek, options)
const encryptedContentEncryptionKey = jose.base64url.decode(recipient.encrypted_key)
// decrypt cek
const decryptedContentEncryptionKey = new Uint8Array(
Expand Down
3 changes: 3 additions & 0 deletions src/jose-hpke/modes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import * as integrated from './integrated-encryption'

export { integrated }
41 changes: 41 additions & 0 deletions src/jose-hpke/modes/integrated-encryption.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import * as jose from "jose";

import { prepareSenderContext } from "../prepareSenderContext";
import { prepareRecipientHeader } from "../prepareRecipientHeader";

import * as aead from '../jwe/aead'

import { prepareRecipientContext } from '../prepareRecipientContext'

export const encrypt = async (plaintext: Uint8Array, publicKeyJwk: any, options?: any): Promise<any> => {
const sender = await prepareSenderContext(publicKeyJwk, options)
const header = await prepareRecipientHeader(publicKeyJwk, options)
const encrypted_key = jose.base64url.encode(new Uint8Array(sender.enc))
const protectedHeader = jose.base64url.encode(JSON.stringify(header))
const encodedAad = options.additionalAuthenticatedData ? jose.base64url.encode(options.additionalAuthenticatedData) : undefined
const aad = aead.prepareJweAad(protectedHeader, encodedAad)
const ciphertext = jose.base64url.encode(new Uint8Array(await sender.seal(plaintext, aad)));
const encrypted = {
protected: protectedHeader,
encrypted_key,
ciphertext,
} as any
if (options.additionalAuthenticatedData){
encrypted.aad = encodedAad
}
return encrypted
}


export const decrypt = async (encrypted: any, privateKeyJwk: any, options?: any): Promise<any> => {
const header = JSON.parse(new TextDecoder().decode(jose.base64url.decode(encrypted.protected)))
const ek = jose.base64url.decode(encrypted.encrypted_key)
const context = await prepareRecipientContext(privateKeyJwk, ek, options)
const aad = aead.prepareJweAad(encrypted.protected, encrypted.aad)
const plaintext = await context.open(jose.base64url.decode(encrypted.ciphertext), aad)
return {
protectedHeader: header,
plaintext: new Uint8Array(plaintext),
additionalAuthenticatedData: jose.base64url.decode(encrypted.aad)
}
}
18 changes: 8 additions & 10 deletions src/jose-hpke/prepareRecipientContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@
import { AeadId, CipherSuite, KdfId, KemId, RecipientContextParams } from "hpke-js";
import { publicKeyFromJwk, privateKeyFromJwk } from '../crypto/keys'

import * as jose from 'jose'

export const prepareRecipientContext = async (recipientPrivateKey: any, recipientHeader: any, options: any)=>{
export const prepareRecipientContext = async (recipientPrivateKey: any, encapsulatedKey: any, options: any)=>{

const suite = new CipherSuite({
kem: KemId.DhkemP256HkdfSha256,
kdf: KdfId.HkdfSha256,
aead: AeadId.Aes128Gcm,
})

const recipientParams = {
recipientKey: await privateKeyFromJwk(recipientPrivateKey),
enc: jose.base64url.decode(recipientHeader.ek)
enc: encapsulatedKey
} as RecipientContextParams

if (options.keyManagementParameters){
Expand All @@ -25,13 +30,6 @@ export const prepareRecipientContext = async (recipientPrivateKey: any, recipien
recipientParams.senderPublicKey = await publicKeyFromJwk(options.senderPublicKey)
}

const suite = new CipherSuite({
kem: KemId.DhkemP256HkdfSha256,
kdf: KdfId.HkdfSha256,
aead: AeadId.Aes128Gcm,
})


return suite.createRecipientContext(recipientParams)

}
49 changes: 49 additions & 0 deletions tests/integrated-encryption.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { jose as hpke } from '../src'
// import * as jose from 'jose'

it('HPKE Integrated Encryption, Auth Mode, PSK and AAD', async () => {
const privateKey = {
"kid": "urn:ietf:params:oauth:jwk-thumbprint:sha-256:S6AXfdU_6Yfzvu0KDDJb0sFuwnIWPk6LMTErYhPb32s",
"alg": "HPKE-P256-SHA256-A128GCM",
"kty": "EC",
"crv": "P-256",
"x": "wt36K06T4T4APWfGtioqDBXCvRN9evqkZjNydib9MaM",
"y": "eupgedeE_HAmVJ62kpSt2_EOoXb6e0y2YF1JPlfr1-I",
"d": "O3KznUTAxw-ov-9ZokwNaJ289RgP9VxQc7GJthaXzWY"
}
const publicKey = await hpke.jwk.publicFromPrivate(privateKey)
const pskid = new TextEncoder().encode("our-pre-shared-key-id")
const psk = new TextEncoder().encode("jugemujugemugokounosurikirekaija")
const plaintext = new TextEncoder().encode(`🖤 this plaintext!`)
const additionalAuthenticatedData = new TextEncoder().encode('🏴‍☠️ beware the aad!')
const commonOptions = {
keyManagementParameters: {
psk: {
id: pskid,
key: psk,
}
}
}
const encryptOptions = {
additionalAuthenticatedData,
senderPrivateKey: privateKey,
recipientPublicKey: publicKey,
...commonOptions
}
const encrypted = await hpke.modes.integrated.encrypt(plaintext, publicKey, encryptOptions)
const decryptOptions = {
senderPublicKey: publicKey,
recipientPrivateKey: privateKey,
...commonOptions
}
const decrypted = await hpke.modes.integrated.decrypt(encrypted, privateKey, decryptOptions)
expect(new TextDecoder().decode(decrypted.plaintext)).toBe('🖤 this plaintext!')
expect(new TextDecoder().decode(decrypted.additionalAuthenticatedData)).toBe('🏴‍☠️ beware the aad!')
expect(decrypted.protectedHeader).toBeDefined()
// console.log(JSON.stringify({
// "kty":"oct",
// "kid": "our-pre-shared-key-id",
// k: jose.base64url.encode(psk)
// }, null, 2))
// console.log(JSON.stringify(encrypted, null, 2))
})
2 changes: 2 additions & 0 deletions tests/json.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,7 @@ it('JSON serialized HPKE JWE to Compact', async () => {
}
const jwe = await hpke.jwe.json.encrypt(plaintext, recipients, senderOptions)
const jwt = hpke.jwe.json.toCompactSerialization(jwe)
// console.log(jwt)
// console.log(JSON.stringify(jwe, null, 2))
expect(jwt.split(".").length).toBe(5) // unprotected headers destroyed in the process
})
2 changes: 2 additions & 0 deletions tests/jwt-auth-psk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ it('Encrypted JWT with HPKE-P256-SHA256-A128GCM (auth and psk)', async () => {
}
})
// protected.encapsulated_key.<no iv>.ciphertext.<no tag>
// console.log(jwe)
// console.log(JSON.stringify(hpke.jwe.compact.toJsonSerialization(jwe)))
const result = await hpke.jwt.decryptJWT(jwe, {
senderPublicKey: publicKey,
recipientPrivateKey: privateKey,
Expand Down

0 comments on commit 36e2abf

Please sign in to comment.