Skip to content

Commit

Permalink
feat: update friendship cert to [#168]
Browse files Browse the repository at this point in the history
  • Loading branch information
Jack-Works committed Sep 18, 2019
1 parent e4bbc10 commit 9f15753
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 70 deletions.
30 changes: 16 additions & 14 deletions src/protocols/friendship-discovery/friendship-cert.d.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { PersonIdentifier } from '../../database/type'

/**
* Server guide:
* - On receive `packed`:
Expand All @@ -9,22 +11,22 @@
* @remarks
* ! This object should be encrypted with a NEW RANDOM crypto key !
*/
export interface FriendshipCertificateV1 {
export interface FriendshipCertificateDecryptedV1 {
/**
* Indicates which Social Network this certificate belongs to.
*
* @example "[email protected]"
* Who declared they issued this certificate
* ! DO NOT trust this field !
*/
network: string
certificateIssuer: PersonIdentifier
/**
* Who claim they signed this certificate.
* @remarks
* ! Do not trust this field !
* @example "alice.hamilton.2019"
* ? This key is used to join and decrypt the message in the private channel
*/
myId: string
channelCryptoKey: JsonWebKey
/**
* ? This seed is used to generate the deterministic channel ID
*/
channelSeed: string
}
export interface FriendshipCertificatePackedV1 {
export interface FriendshipCertificateEncryptedV1 {
version: 1
/**
* ! A NEW RANDOM crypto key !
Expand All @@ -38,10 +40,10 @@ export interface FriendshipCertificatePackedV1 {
/**
* timestamp
* ! Server should overwrite it !
* ? If server is some kind of decentralized instance message service,
* ? use the message timestamp
*/
timestamp: number
/**
* iv
*/
/** iv used to decrypt the payload */
iv: string
}
28 changes: 6 additions & 22 deletions src/protocols/friendship-discovery/friendship-client.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,7 @@
import { FriendshipCertificatePackedV1 } from './friendship-cert'
import { FriendshipCertificateEncryptedV1 } from './friendship-cert'

/**
* Publish new certificate
* POST endpoint/friendship-cert
* Get all certificates after a time
* GET endpoint/friendship-cert?after={timestamp}
*/
const endpoint = '?'
export async function publishCertificate(cert: FriendshipCertificatePackedV1) {
const response = await fetch(endpoint + '/friendship-cert', { method: 'POST', body: JSON.stringify(cert) })
return response.ok
}

export async function pullCertificates(after: number = 0) {
const response = await fetch(endpoint + '/friendship-cert?after=' + after)
const result: FriendshipCertificatePackedV1[] = await response.json()
if (!Array.isArray(result)) {
console.warn('Bad output from server')
return []
}
return result.filter(x => x.cryptoKey && x.iv && x.payload && x.timestamp && x.version === 1)
}
declare function publishCertificate(cert: FriendshipCertificateEncryptedV1): Promise<void>
declare function pullCertificates(
afterTimeStamp: number,
partition: string,
): Promise<FriendshipCertificateEncryptedV1[]>
62 changes: 33 additions & 29 deletions src/protocols/friendship-discovery/friendship-pack.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
import { FriendshipCertificateV1, FriendshipCertificatePackedV1 } from './friendship-cert'
import { FriendshipCertificateDecryptedV1, FriendshipCertificateEncryptedV1 } from './friendship-cert'
import { encryptWithAES, decryptWithAES } from '../../crypto/crypto-alpha-40'
import { encodeArrayBuffer, decodeText } from '../../utils/type-transform/String-ArrayBuffer'
import { toECDH } from '../../utils/type-transform/ECDSA-ECDH'
import { PersonIdentifier } from '../../database/type'
import {
generate_ECDH_256k1_KeyPair as generateECDH_256k1_KeyPair,
import_ECDH_256k1_Key,
derive_AES_GCM_256_Key_From_ECDH_256k1_Keys,
} from '../../utils/crypto.subtle'

export async function issueFriendshipCertificate(
whoAmI: PersonIdentifier,
channelKey: CryptoKey,
channelSeed: string,
): Promise<FriendshipCertificateDecryptedV1> {
return {
certificateIssuer: whoAmI,
channelCryptoKey: await crypto.subtle.exportKey('jwk', channelKey),
channelSeed: channelSeed,
}
}

/**
* Pack steps for {@link FriendshipCertificateV1}
* @remakrs
Expand All @@ -14,17 +33,11 @@ import { toECDH } from '../../utils/type-transform/ECDSA-ECDH'
* 6. Send `packed` to server
*/
export async function packFriendshipCertificate(
cert: FriendshipCertificateV1,
cert: FriendshipCertificateDecryptedV1,
targetKey: CryptoKey,
): Promise<FriendshipCertificatePackedV1> {
const key = await crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'K-256' }, true, ['deriveKey'])
const aes = await crypto.subtle.deriveKey(
{ name: 'ECDH', public: targetKey },
key.privateKey,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt'],
)
): Promise<FriendshipCertificateEncryptedV1> {
const key = await generateECDH_256k1_KeyPair()
const aes = await derive_AES_GCM_256_Key_From_ECDH_256k1_Keys(targetKey, key.privateKey)
const { content: payload, iv } = await encryptWithAES({
aesKey: aes,
content: JSON.stringify(cert),
Expand All @@ -45,29 +58,20 @@ export async function packFriendshipCertificate(
* 3. Manual or automatically verify friendship of `cert.myId` on network `cert.network`
*/
export async function unpackFriendshipCertificate(
packed: FriendshipCertificatePackedV1,
packed: FriendshipCertificateEncryptedV1,
privateKey: CryptoKey,
): Promise<null | FriendshipCertificateV1> {
): Promise<null | FriendshipCertificateDecryptedV1> {
const ownPrivateKey = privateKey.usages.find(x => x === 'deriveKey') ? privateKey : await toECDH(privateKey)
const packedCryptoKey = await crypto.subtle.importKey(
'jwk',
packed.cryptoKey,
{ name: 'ECDH', namedCurve: 'K-256' },
false,
['deriveKey'],
)
const aes = await crypto.subtle.deriveKey(
{ name: 'ECDH', public: packedCryptoKey },
ownPrivateKey,
{ name: 'AES-GCM', length: 256 },
false,
['encrypt', 'decrypt'],
)
const packedCryptoKey = await import_ECDH_256k1_Key(packed.cryptoKey)
const aes = await derive_AES_GCM_256_Key_From_ECDH_256k1_Keys(packedCryptoKey, ownPrivateKey)
try {
const certBuffer = await decryptWithAES({ aesKey: aes, encrypted: packed.payload, iv: packed.iv })
const certString = decodeText(certBuffer)
const cert: FriendshipCertificateV1 = JSON.parse(certString)
if (!cert.myId || !cert.network) throw new TypeError('Not a valid cert.')
const cert: FriendshipCertificateDecryptedV1 = JSON.parse(certString)
if (!cert.certificateIssuer || !cert.channelCryptoKey || !cert.channelSeed)
throw new TypeError('Not a valid cert.')
// Recover prototype
Object.setPrototypeOf(cert.certificateIssuer, PersonIdentifier.prototype)
return cert
} catch {
return null
Expand Down
19 changes: 14 additions & 5 deletions src/tests/friendship-discover.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,29 @@
import {
packFriendshipCertificate,
unpackFriendshipCertificate,
issueFriendshipCertificate,
} from '../protocols/friendship-discovery/friendship-pack'
import { PersonIdentifier } from '../database/type'
import { generate_ECDH_256k1_KeyPair, generate_AES_GCM_256_Key } from '../utils/crypto.subtle'

const aliceID = new PersonIdentifier('localhost', 'alice.test')
async function testFriendshipDiscover() {
// Alice don't need a keypair during the process
const bob = await crypto.subtle.generateKey({ name: 'ECDH', namedCurve: 'K-256' }, true, ['deriveKey'])
const bob = await generate_ECDH_256k1_KeyPair()

const rawCert = await issueFriendshipCertificate(
aliceID,
await generate_AES_GCM_256_Key(),
Math.random().toString(),
)
// Alice to bob
const cert = await packFriendshipCertificate({ myId: 'alice', network: 'test-network' }, bob.publicKey)
console.debug('Friendship discover test: bob cert signed by alice', cert)
const encryptedCert = await packFriendshipCertificate(rawCert, bob.publicKey)
console.log('Friendship discover test: Issuer: Alice, to: Bob', rawCert, encryptedCert)

// Bob verify the cert
const unpacked = await unpackFriendshipCertificate(cert, bob.privateKey)
const unpacked = await unpackFriendshipCertificate(encryptedCert, bob.privateKey)
if (!unpacked) throw new Error('Unpack cert failed')
if (unpacked.myId !== 'alice' || unpacked.network !== 'test-network') throw new Error('???')
if (!unpacked.certificateIssuer.equals(aliceID)) throw new Error('???')

// Bob should verify their friendship by other channel.
return true
Expand Down

0 comments on commit 9f15753

Please sign in to comment.