Skip to content

Commit

Permalink
fix: Using local version of DIDComm lib
Browse files Browse the repository at this point in the history
  • Loading branch information
simonas-notcat committed Feb 11, 2020
1 parent 1ea064d commit d09a326
Show file tree
Hide file tree
Showing 9 changed files with 723 additions and 24 deletions.
5 changes: 3 additions & 2 deletions packages/daf-ethr-did-fs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"build": "tsc"
},
"dependencies": {
"DIDComm-js": "github:decentralized-identity/DIDComm-js",
"base-58": "^0.0.1",
"base64url": "^3.0.1",
"daf-core": "^1.4.1",
"daf-resolver": "^1.1.0",
Expand All @@ -17,7 +17,8 @@
"ethjs-provider-signer": "^0.1.4",
"ethjs-signer": "^0.1.1",
"ethr-did": "^1.1.0",
"js-sha3": "^0.8.0"
"js-sha3": "^0.8.0",
"libsodium-wrappers": "^0.7.6"
},
"devDependencies": {
"@types/debug": "^4.1.5",
Expand Down
331 changes: 331 additions & 0 deletions packages/daf-ethr-did-fs/src/didcomm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,331 @@
const Base58 = require('base-58')
import sodium from 'libsodium-wrappers'

interface IUnpackedMsg {
message: string
recipientKey: any
senderKey: string
nonRepudiableVerification: boolean
}

interface JWSUnpacked {
content: string
verkey: string
verified: boolean
}

export class DIDComm {
public readonly ready: Promise<undefined>
private sodium: any

/**
* Creates a new PackUnpack object. The returned object contains a .Ready property:
* a promise that must be resolved before the object can be used. You can
* simply `await` the resolution of the .Ready property.
*
* Example:
* let packUnpack = new PackUnpack
* (async () => {
* await packUnpack.Ready
* }())
*/
constructor() {
this.ready = new Promise(async (res, rej) => {
try {
await sodium.ready
this.sodium = sodium
res()
} catch (err) {
rej(err)
}
})
}

/**
* Uses libsodium to generate a key pair, you may pass these keys into the pack/unpack functions
* @
*/
public async generateKeyPair(): Promise<sodium.KeyPair> {
return this.sodium.crypto_sign_keypair()
}

/**
* Used to encrypt or encrypt and sign a message for one or many recipients so the recipients can authenticate the
* sender in both repudiable and non repudiable formats. By default messages should use repudiable authentication.
* This should be the most common API used.
* @param msg the message to be encrypted or encrypted and signed
* @param recipientKeys the keys which the message will be encrypted for
* @param senderKeys the keys used to encrypted or encrypt and sign a message
* @param nonRepudiable determines whether a message is encrypted only or signed and encrypted
* @returns if nonRepudiable == true returns the msg encrypted and signed as follows JWE(JWS(msg))
* if nonRepudiable == false returns the msg encrypted as follows JWE(msg)
*/
public async pack_auth_msg_for_recipients(
msg: string,
recipientKeys: Uint8Array[],
senderKeys: sodium.KeyPair,
nonRepudiable: Boolean = false,
): Promise<string> {
if (nonRepudiable) {
//return JWE(JWS(msg))
let signedMsg = await this.signContent(msg, senderKeys)
return this.packMessage(signedMsg, recipientKeys, senderKeys)
} else {
// return (JWE(msg))
return this.packMessage(msg, recipientKeys, senderKeys)
}
}

/**
*
* @param msg this is the message which will be anonymously encrypted for one or many recipients
* @param recipientKeys a list of the recipients keys
* @returns a JWE with an ephemeral sender key
*/
public async pack_anon_msg_for_recipients(msg: string, recipientKeys: Uint8Array[]): Promise<string> {
return this.packMessage(msg, recipientKeys, null)
}

/**
*
* @param msg the message to signed with non-repudiation but not encrypted
* @param senderKeys the key used to sign the
* @returns a compact JWS
*/
public async pack_nonrepudiable_msg_for_anyone(msg: string, senderKeys: sodium.KeyPair): Promise<string> {
return this.signContent(msg, senderKeys)
}

/**
* Unpacks a message
* @param encMsg message to be decrypted
* @param toKeys key pair of party decrypting the message
*/
public async unpackMessage(packedMsg: string, toKeys: sodium.KeyPair): Promise<IUnpackedMsg> {
try {
return await this.unpackEncrypted(packedMsg, toKeys)
} catch (err) {
let jwsChecked = this.verifyContent(packedMsg)
return {
message: jwsChecked.content,
recipientKey: null,
senderKey: jwsChecked.verkey,
nonRepudiableVerification: jwsChecked.verified,
}
}
}

/**
*
* Packs a message.
* @param message string message to be encrypted
* @param toKeys public key of the entity encrypting message for
* @param fromKeys keypair of person encrypting message
*/
private async packMessage(
msg: string,
recipientKeys: Uint8Array[],
fromKeys: sodium.KeyPair | null = null,
): Promise<string> {
let [recipsJson, cek] = this.prepareRecipientKeys(recipientKeys, fromKeys)
let recipsB64 = this.b64url(recipsJson)

let [ciphertext, tag, iv] = this.encryptPlaintext(msg, recipsB64, cek)

return JSON.stringify({
ciphertext: this.b64url(ciphertext),
iv: this.b64url(iv),
protected: recipsB64,
tag: this.b64url(tag),
})
}

private async unpackEncrypted(encMsg: string, toKeys: sodium.KeyPair): Promise<IUnpackedMsg> {
let wrapper
if (typeof encMsg === 'string') {
wrapper = JSON.parse(encMsg)
} else {
wrapper = encMsg
}
if (typeof toKeys.publicKey === 'string') {
toKeys.publicKey = Base58.decode(toKeys.publicKey)
}
if (typeof toKeys.privateKey === 'string') {
toKeys.privateKey = Base58.decode(toKeys.privateKey)
}
let recipsJson = this.strB64dec(wrapper.protected)
let recipsOuter = JSON.parse(recipsJson)

let alg = recipsOuter.alg
let isAuthcrypt = alg === 'Authcrypt'
if (!isAuthcrypt && alg !== 'Anoncrypt') {
throw new Error('Unsupported pack algorithm: ' + alg)
}
let [cek, senderVk, recipVk] = this.locateRecKey(recipsOuter.recipients, toKeys)
if (!senderVk && isAuthcrypt) {
throw new Error('Sender public key not provided in Authcrypt message')
}
let ciphertext = this.b64dec(wrapper.ciphertext)
let nonce = this.b64dec(wrapper.iv)
let tag = this.b64dec(wrapper.tag)

let message = this.decryptPlaintext(ciphertext, tag, wrapper.protected, nonce, cek)
try {
let jwsVerified = this.verifyContent(message)
return {
message: jwsVerified.content,
recipientKey: recipVk,
senderKey: senderVk,
nonRepudiableVerification: senderVk === jwsVerified.verkey ? true : false,
}
} catch (err) {
return {
message,
recipientKey: recipVk,
senderKey: senderVk,
nonRepudiableVerification: false,
}
}
}

private async signContent(msg: string, signerKeyPair: sodium.KeyPair): Promise<string> {
// get public key base58 encoded
let senderVk = Base58.encode(signerKeyPair.publicKey)

// generate jose header, b64url encode it, and concat to b64url encoded payload
let joseHeader = {
alg: 'EdDSA',
kid: senderVk,
}
let joseString = JSON.stringify(joseHeader)
let b64JoseStr = this.b64url(joseString)
let b64Payload = this.b64url(msg)
let headerAndPayloadConcat = `${b64JoseStr}.${b64Payload}`

//sign data and return compact JWS
let signature = this.b64url(sodium.crypto_sign(headerAndPayloadConcat, signerKeyPair.privateKey))
return `${headerAndPayloadConcat}.${signature}`
}

private verifyContent(jws: string): JWSUnpacked {
let jwsSplit = jws.split('.')
let joseHeader = JSON.parse(this.strB64dec(jwsSplit[0]))
if (joseHeader.alg != 'EdDSA') {
throw 'Cryptographic algorithm unidentifiable'
}
let sigMsg = sodium.crypto_sign_open(this.b64dec(jwsSplit[2]), Base58.decode(joseHeader.kid))

return {
content: this.strB64dec(jwsSplit[1]),
verkey: joseHeader.kid,
verified: sodium.to_string(sigMsg) === `${jwsSplit[0]}.${jwsSplit[1]}` ? true : false,
}
}

b64url(input: any) {
return this.sodium.to_base64(input, this.sodium.base64_variants.URLSAFE)
}

b64dec(input: any) {
return this.sodium.from_base64(input, this.sodium.base64_variants.URLSAFE)
}

private strB64dec(input: any) {
return this.sodium.to_string(this.sodium.from_base64(input, this.sodium.base64_variants.URLSAFE))
}

private encryptPlaintext(message: any, addData: any, key: any) {
let iv = this.sodium.randombytes_buf(this.sodium.crypto_aead_chacha20poly1305_ietf_NPUBBYTES)
let out = this.sodium.crypto_aead_chacha20poly1305_ietf_encrypt_detached(message, addData, null, iv, key)
return [out.ciphertext, out.mac, iv]
}

private decryptPlaintext(ciphertext: any, mac: any, recipsBin: any, nonce: any, key: any) {
return this.sodium.to_string(
this.sodium.crypto_aead_chacha20poly1305_ietf_decrypt_detached(
null, // nsec
ciphertext,
mac,
recipsBin, // ad
nonce, // npub
key,
),
)
}

private prepareRecipientKeys(toKeys: any, fromKeys: any = null) {
let cek = this.sodium.crypto_aead_chacha20poly1305_ietf_keygen()
let recips: any[] = []

toKeys.forEach((targetVk: any) => {
let encCek = null
let encSender = null
let nonce = null

let targetPk = this.sodium.crypto_sign_ed25519_pk_to_curve25519(targetVk)

if (fromKeys) {
let senderVk = Base58.encode(fromKeys.publicKey)
let senderSk = this.sodium.crypto_sign_ed25519_sk_to_curve25519(fromKeys.privateKey)
encSender = this.sodium.crypto_box_seal(senderVk, targetPk)

nonce = this.sodium.randombytes_buf(this.sodium.crypto_box_NONCEBYTES)
encCek = this.sodium.crypto_box_easy(cek, nonce, targetPk, senderSk)
} else {
encCek = this.sodium.crypto_box_seal(cek, targetPk)
}

recips.push({
encrypted_key: this.b64url(encCek),
header: {
iv: nonce ? this.b64url(nonce) : null,
kid: Base58.encode(targetVk),
sender: encSender ? this.b64url(encSender) : null,
},
})
})

let data = {
alg: fromKeys ? 'Authcrypt' : 'Anoncrypt',
enc: 'chacha20poly1305_ietf',
recipients: recips,
typ: 'JWM/1.0',
}
return [JSON.stringify(data), cek]
}

private locateRecKey(recipients: any, keys: any) {
let notFound = []
/* tslint:disable */
for (let index in recipients) {
let recip = recipients[index]
if (!('header' in recip) || !('encrypted_key' in recip)) {
throw new Error('Invalid recipient header')
}

let recipVk = Base58.decode(recip.header.kid)
if (!this.sodium.memcmp(recipVk, keys.publicKey)) {
notFound.push(recip.header.kid)
}
let pk = this.sodium.crypto_sign_ed25519_pk_to_curve25519(keys.publicKey)
let sk = this.sodium.crypto_sign_ed25519_sk_to_curve25519(keys.privateKey)

let encrytpedKey = this.b64dec(recip.encrypted_key)
let nonce = recip.header.iv ? this.b64dec(recip.header.iv) : null
let encSender = recip.header.sender ? this.b64dec(recip.header.sender) : null

let senderVk = null
let cek = null
if (nonce && encSender) {
senderVk = this.sodium.to_string(this.sodium.crypto_box_seal_open(encSender, pk, sk))
let senderPk = this.sodium.crypto_sign_ed25519_pk_to_curve25519(Base58.decode(senderVk))
cek = this.sodium.crypto_box_open_easy(encrytpedKey, nonce, senderPk, sk)
} else {
cek = this.sodium.crypto_box_seal_open(encrytpedKey, pk, sk)
}
return [cek, senderVk, recip.header.kid]
}

throw new Error('No corresponding recipient key found in recipients')
}
}
2 changes: 1 addition & 1 deletion packages/daf-ethr-did-fs/src/identity-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Debug from 'debug'
const debug = Debug('daf:ethr-did-fs:identity-provider')
const EC = require('elliptic').ec
const secp256k1 = new EC('secp256k1')
import { DIDComm } from 'DIDComm-js'
import { DIDComm } from './didcomm'
const didcomm = new DIDComm()

import { keccak_256 } from 'js-sha3'
Expand Down
2 changes: 1 addition & 1 deletion packages/daf-ethr-did-fs/src/identity.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { AbstractIdentity, Resolver } from 'daf-core'
import { SimpleSigner } from 'did-jwt'
import { Key } from './identity-provider'
import { DIDComm } from 'DIDComm-js'
import { DIDComm } from './didcomm'
const didcomm = new DIDComm()

export class Identity extends AbstractIdentity {
Expand Down
Loading

0 comments on commit d09a326

Please sign in to comment.