Skip to content

Commit

Permalink
feat: add function for resolving x25519Encrypters
Browse files Browse the repository at this point in the history
  • Loading branch information
oed committed Sep 28, 2020
1 parent af4401d commit d92e3ed
Show file tree
Hide file tree
Showing 3 changed files with 100 additions and 1 deletion.
74 changes: 74 additions & 0 deletions src/__tests__/xc20pEncryption-test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import {
x25519Encrypter,
x25519Decrypter,
resolveX25519Encrypters
} from '../xc20pEncryption'
import { decryptJWE, createJWE, Encrypter } from '../JWE'
import * as u8a from 'uint8arrays'
import { randomBytes } from '@stablelib/random'
import { generateKeyPair } from '@stablelib/x25519'

describe('xc20pEncryption', () => {
describe('resolveX25519Encrypters', () => {
let resolver, did1, did2, did3, did4
let decrypter1, decrypter2

beforeAll(() => {
did1 = 'did:test:1'
did2 = 'did:test:2'
did3 = 'did:test:3'
did4 = 'did:test:4'
const kp1 = generateKeyPair()
const kp2 = generateKeyPair()
decrypter1 = x25519Decrypter(kp1.secretKey)
decrypter2 = x25519Decrypter(kp2.secretKey)
resolver = {
resolve: jest.fn(did => {
if (did === did1) {
return {
publicKey: [{
id: did1 + '#abc',
type: 'X25519KeyAgreementKey2019',
controller: did1,
publicKeyBase58: u8a.toString(kp1.publicKey, 'base58btc')
}],
keyAgreement: [{
id: 'irrelevant key'
},
did1 + '#abc'
]
}
} else if (did === did2) {
return {
publicKey: [],
keyAgreement: [{
id: did2 + '#abc',
type: 'X25519KeyAgreementKey2019',
controller: did2,
publicKeyBase58: u8a.toString(kp2.publicKey, 'base58btc')
}]
}
} else if (did === did3) {
return { publicKey: [] }
} else if (did === did4) {
return { publicKey: [], keyAgreement: [{ type: 'wrong type' }] }
}
})
}
})

it('correctly resolves encrypters for DIDs', async () => {
const encrypters = await resolveX25519Encrypters([did1, did2], resolver)
const cleartext = randomBytes(8)
const jwe = await createJWE(cleartext, encrypters)

expect(await decryptJWE(jwe, decrypter1)).toEqual(cleartext)
expect(await decryptJWE(jwe, decrypter2)).toEqual(cleartext)
})

it('throws error if key is not found', async () => {
await expect(resolveX25519Encrypters([did3], resolver)).rejects.toThrow('Could not find x25519 key for did:test:3')
await expect(resolveX25519Encrypters([did4], resolver)).rejects.toThrow('Could not find x25519 key for did:test:4')
})
})
})
4 changes: 4 additions & 0 deletions src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ export function bytesToBase64(b: Uint8Array): string {
return u8a.toString(b, 'base64pad')
}

export function base58ToBytes(s: string): Uint8Array {
return u8a.fromString(s, 'base58btc')
}

export function encodeBase64url(s: string): string {
return bytesToBase64url(u8a.fromString(s))
}
Expand Down
23 changes: 22 additions & 1 deletion src/xc20pEncryption.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import { XChaCha20Poly1305 } from '@stablelib/xchacha20poly1305'
import { generateKeyPair, sharedKey } from '@stablelib/x25519'
import { randomBytes } from '@stablelib/random'
import { concatKDF } from './Digest'
import { base64urlToBytes, bytesToBase64url, encodeBase64url, toSealed } from './util'
import { base64urlToBytes, bytesToBase64url, base58ToBytes, encodeBase64url, toSealed } from './util'
import { Recipient, EncryptionResult, Encrypter, Decrypter } from './JWE'
import type { PublicKey, Resolver } from 'did-resolver'

function xc20pEncrypter(key: Uint8Array): (cleartext: Uint8Array, aad?: Uint8Array) => EncryptionResult {
const cipher = new XChaCha20Poly1305(key)
Expand Down Expand Up @@ -76,6 +77,26 @@ export function x25519Encrypter(publicKey: Uint8Array): Encrypter {
return { alg, enc: 'XC20P', encrypt, encryptCek }
}

export async function resolveX25519Encrypters(dids: string[], resolver: Resolver): Promise<Encrypter[]> {
return Promise.all(
dids.map(async (did) => {
const didDoc = await resolver.resolve(did)
if (!didDoc.keyAgreement) throw new Error(`Could not find x25519 key for ${did}`)
const agreementKeys: PublicKey[] = didDoc.keyAgreement?.map((key) => {
if (typeof key === 'string') {
return didDoc.publicKey.find((pk) => pk.id === key)
}
return key
})
const b58Key = agreementKeys.find((key) => {
return key.type === 'X25519KeyAgreementKey2019' && Boolean(key.publicKeyBase58)
})
if (!b58Key) throw new Error(`Could not find x25519 key for ${did}`)
return x25519Encrypter(base58ToBytes(b58Key.publicKeyBase58))
})
)
}

function validateHeader(header: Record<string, any>) {
if(!(
header.epk &&
Expand Down

0 comments on commit d92e3ed

Please sign in to comment.