UNPKG

@jovian/type-tools

Version:

TypeTools is a Typescript library for providing extensible tooling runtime validations and type helpers.

584 lines (546 loc) 25.7 kB
/* * Copyright 2014-2021 Jovian, all rights reserved. */ import { FourQ, DiffieHellmanKeyPair, CryptoScheme } from '@jovian/fourq'; import * as crypto from 'crypto'; import { AccessHeaderObject, AuthHeaderObject, AuthSignatureData, AuthSigning, ResolvableEntry, SecureChannelBaseParams, SecureChannelPayload, SecureChannelPeer, SecureChannelResponse, SecureChannelResponseExtraInfo, SecureChannelTypes } from '../../src/common/security/security.common'; import { ok, passthru, Result, ReturnCodeFamily, Unum } from '../../src/common/util/enum.util'; import { punchGrab } from '../../src/type-transform'; const verticalBarBuffer = Buffer.from('|', 'ascii'); enum SecureChannelCodeEnum { CONTACT_INITIATION_FAILURE, CONTACT_INITIATION_TOKEN_NOT_FOUND, CONTACT_INITIATION_BAD_RESULT, CONTACT_INITIATION_NULL_RESPONSE, BAD_CHANNEL_ID_CLIENT, TRUSTED_PEER_MISMATCH_CLIENT, TRUSTED_PEER_MISMATCH_SERVER, TRUST_STAMP_VERIFICACTION_FAILURE_CLIENT, TRUST_STAMP_VERIFICACTION_FAILURE_SERVER, ACCESSOR_TIME_WINDOW_EXCEEDED, ACCESSOR_TOKEN_HASH_MISMATCH, SYM_NONCE_TIME_HASH_TIME_WINDOW_EXCEEDED, SYM_NONCE_TIME_HASH_TOKEN_HASH_MISMATCH, AUTH_NO_SIGNING_KEY, AUTH_NO_PUBLIC_KEY, AUTH_NO_SIG_DATA_OBJECT, AUTH_NO_SIG_DATA, AUTH_NO_SIG_PAYLOAD, AUTH_BAD_PUBLIC_KEY, AUTH_BAD_SIG_DATA, AUTH_BAD_SIGNATURE, AUTH_BAD_SIG_PAYLOAD, AUTH_BAD_SIGNING_KEY, } export const SecureChannelCode = ReturnCodeFamily('SecureChannelCode', SecureChannelCodeEnum); export interface SecureChannelInitFlow extends SecureChannelBaseParams { channelId?: string; initiateContact: (authHeader: string) => Promise<Result<SecureChannelResponse>>, } export interface SecureChannelAnswerFlow extends SecureChannelBaseParams { channelId?: string; authHeader: string; timeWindow?: number; } export async function resolveEntry<T = string>(context: string, resolver: ResolvableEntry<T>, key?: string, returnNullOnNotFound = false): Promise<T> { const resolverAny: any = resolver; if (!resolverAny) { return null; } if (typeof resolverAny === 'string') { if (key === null || key === undefined) { return resolverAny as any as T; } else { throw new Error(`Unable to resolve entry ${context} from direct string value entry when key '${key}' is given`); } } if (!resolverAny.getter && !resolverAny.list) { if (key === null || key === undefined) { return resolver as T; } else { throw new Error(`Unable to resolve entry ${context} from direct object value entry when key '${key}' is given`); } } if (!key && resolverAny?.list) { const keys = Object.keys(resolverAny.list); if (keys.length > 0) { const entry = resolverAny.list[keys[0]]; if (entry?.value === undefined) { return entry; } else { return entry.value; } } } if (!key) { key = 'default'; } if (resolverAny.getter) { return await punchGrab(resolverAny.getter(key)); } else if (resolverAny.list[key]) { if (resolverAny.list[key].getter) { return await punchGrab(resolverAny.list[key].getter(key)); } else { if (resolverAny.list[key].value === undefined) { return (resolverAny.list[key] as any as T); } else { return resolverAny.list[key].value; } } } if (returnNullOnNotFound) { return null; } else { throw new Error(`Unable to resolve entry '${context}' with key '${key}' from entry mapping: ${JSON.stringify(resolver, null, 4)}`); } } export class SecureHandshake { static getIdentityFrom(privateKey: string) { return FourQ.generateIdentity(Buffer.from(privateKey, 'base64')); } static getPublicKeyFrom(privateKey: string) { return FourQ.generateIdentity(Buffer.from(privateKey, 'base64')).publicKeySchnorr.toString('base64'); } static fromJSON(json: string) { const a = new SecureChannel(); return a.fromJSONObject(JSON.parse(json)); } static getLengthBytesUInt32LE(length: number) { const lengthBytes = Buffer.alloc(4); lengthBytes.writeUInt32LE(length, 0); return lengthBytes; } static parseLengthBytesUInt32LE(lengthBytes: Buffer) { return lengthBytes.readUInt32LE(); } static getAccessorBody(user: string, baseToken: string | Buffer, nonceHex?: string, joiner = '.') { let timeHex = Date.now().toString(16); if (timeHex.length % 2 === 1) { timeHex = '0' + timeHex; } if (!nonceHex) { nonceHex = randHex(16); } if (!user) { user = 'internal'; } const accessorParts = [user, timeHex, nonceHex]; const accessorBuffer = Buffer.from(user, 'ascii'); const nonceBuffer = Buffer.from(nonceHex, 'hex'); const timeBuffer = Buffer.from(timeHex, 'hex'); const baseTokenBuffer = typeof baseToken === 'string' ? Buffer.from(baseToken, 'ascii') : baseToken; const accessorSecret = saltedSha512(baseTokenBuffer, accessorBuffer); const oneTimeHash = saltedSha512(accessorSecret, timeBuffer, nonceBuffer).toString('base64'); accessorParts.push(oneTimeHash); return accessorParts.join(joiner); } static getAccessorHeader(user: string, ecdhPubKeyB64: string, baseToken: string | Buffer, signing?: AuthSigning, channelExpire?: number, nonceHex?: string): Result<string> { let sigPart = ''; if (signing) { const signingResult = SecureHandshake.signStamp(signing.private); if (signingResult.bad) { return passthru(signingResult); } sigPart = Buffer.from([ signing.type, signingResult.data.payload, signing.public, signingResult.data.sig ].join('_'), 'utf8').toString('base64'); } const accessorHeader = [ `Accessor !!!!.${SecureHandshake.getAccessorBody(user, baseToken, nonceHex)}`, ecdhPubKeyB64, channelExpire ? channelExpire : 0, sigPart, ].join('_'); let lengthRemainder = accessorHeader.length; const totalContentLength3 = lengthRemainder % 256; lengthRemainder = (lengthRemainder - totalContentLength3) / 256; const totalContentLength2 = lengthRemainder % 256; lengthRemainder = (lengthRemainder - totalContentLength2) / 256; const totalContentLength1 = lengthRemainder % 256; const contentLength = Buffer.from([totalContentLength1, totalContentLength2, totalContentLength3]).toString('base64'); const accessorHeaderWithLengthBytes = accessorHeader.replace('!!!!', contentLength); return ok(accessorHeaderWithLengthBytes); } static parseAccessor(accessorExpression: string): AccessHeaderObject { const accessorData = accessorExpression.split('.'); const headerLength = getLengthFromBytesOfHashContent(accessorData[0], 'Accessor'); const accessor = accessorData[1]; const accessorBuffer = Buffer.from(accessor, 'ascii'); const timeHex = accessorData[2]; const timestamp = parseInt(timeHex, 16); const nonceHex = accessorData[3]; const nonceBuffer = Buffer.from(nonceHex, 'hex'); const timeBuffer = Buffer.from(timeHex, 'hex'); const oneTimeHash = accessorData[4]; return { headerLength, accessorBuffer, timeHex, timeBuffer, timestamp, nonceHex, nonceBuffer, oneTimeHash }; } static parseAuthHeader(authHeader: string): AuthHeaderObject { const authChunks = authHeader.split('_'); const accessor = SecureHandshake.parseAccessor(authChunks[0]); const [ payloadB64, signerPubKeyB64, signatureB64, ] = authChunks[3] ? Buffer.from(authChunks[3], 'base64').toString('utf8').split('_') : [null, null, null]; return { accessorExpression: authChunks[0], peerEcdhPublicKey: authChunks[1], expires: authChunks[2] ? parseInt(authChunks[2], 10) : 0, sigPart: authChunks[3], peerSignaturePublicKey: signerPubKeyB64, peerSignature: signatureB64, peerSignaturePayload: payloadB64, ...accessor, }; } static symNonceTimeHash(baseToken: string | Buffer, user: string = 'internal') { const hashedContent = `!!!!_${this.getAccessorBody(user, baseToken, null, '_')}`; let lengthRemainder = hashedContent.length; const totalContentLength3 = lengthRemainder % 256; lengthRemainder = (lengthRemainder - totalContentLength3) / 256; const totalContentLength2 = lengthRemainder % 256; lengthRemainder = (lengthRemainder - totalContentLength2) / 256; const totalContentLength1 = lengthRemainder % 256; const contentLength = Buffer.from([totalContentLength1, totalContentLength2, totalContentLength3]).toString('base64'); const encryptedContentWithLengthBytes = hashedContent.replace('!!!!', contentLength); return encryptedContentWithLengthBytes; } static symNonceTimeHashHeader(baseToken: string | Buffer, user: string = 'internal') { return `Bearer SYMNT_HASH.${this.symNonceTimeHash(baseToken, user)}`; } static symNonceTimeVerify(hashedText: string, baseToken: string | Buffer, timeWindow = 5000) { const accessorData = hashedText.split('_'); const headerLength = getLengthFromBytesOfHashContent(accessorData[0]); const hashedContent = accessorData[1]; const hashedContentBuffer = Buffer.from(hashedContent, 'ascii'); const timeHex = accessorData[2]; const nonceHex = accessorData[3]; const nonceBuffer = Buffer.from(nonceHex, 'hex'); const timeBuffer = Buffer.from(timeHex, 'hex'); const oneTimeHash = accessorData[4]; const t = parseInt(timeHex, 16); if (Math.abs(Date.now() - t) > timeWindow) { return SecureChannelCode.error('SYM_NONCE_TIME_HASH_TIME_WINDOW_EXCEEDED'); } const baseTokenBuffer = typeof baseToken === 'string' ? Buffer.from(baseToken, 'ascii') : baseToken; const accessorSecret = saltedSha512(baseTokenBuffer, hashedContentBuffer); const expectedHash = saltedSha512(accessorSecret, timeBuffer, nonceBuffer).toString('base64'); if (oneTimeHash !== expectedHash) { return SecureChannelCode.error('SYM_NONCE_TIME_HASH_TOKEN_HASH_MISMATCH'); } return ok(true); } static verifyAuthHeaderSignature(sigObj: {peerSignaturePublicKey: string; peerSignature: string; peerSignaturePayload: string;}) { return SecureHandshake.verifyStamp({ payload: sigObj.peerSignaturePayload, sig: sigObj.peerSignature }, sigObj.peerSignaturePublicKey); } static verifyAccessor(accessorExpression: string, baseToken: string | Buffer, timeWindow = 5000): Result<AccessHeaderObject> { const accessor = SecureHandshake.parseAccessor(accessorExpression); const t = parseInt(accessor.timeHex, 16); if (Math.abs(Date.now() - t) > timeWindow) { return SecureChannelCode.error('ACCESSOR_TIME_WINDOW_EXCEEDED'); } const baseTokenBuffer = typeof baseToken === 'string' ? Buffer.from(baseToken, 'ascii') : baseToken; const accessorSecret = saltedSha512(baseTokenBuffer, accessor.accessorBuffer); const expectedHash = saltedSha512(accessorSecret, accessor.timeBuffer, accessor.nonceBuffer).toString('base64'); if (accessor.oneTimeHash !== expectedHash) { return SecureChannelCode.error('ACCESSOR_TOKEN_HASH_MISMATCH'); } return ok(accessor); } static timeAuth() { return 'proof-of-authenticity__t:gaia:ms:' + Date.now(); } static signStamp(signingKey: string | Buffer): Result<AuthSignatureData> { if (!signingKey) { return SecureChannelCode.error('AUTH_NO_SIGNING_KEY'); } const payload = SecureHandshake.timeAuth(); const payloadBuffer = Buffer.from(payload, 'ascii'); const signingKeyBuffer = typeof signingKey === 'string' ? Buffer.from(signingKey, 'base64') : signingKey; if (!signingKeyBuffer || signingKeyBuffer.length !== 32) { return SecureChannelCode.error('AUTH_BAD_SIGNING_KEY', `Signing key must be 32 bytes`); } const signature = FourQ.sign(payloadBuffer, signingKeyBuffer); return ok({ payload: payloadBuffer.toString('base64'), sig: signature.data.toString('base64') }); } static verifyStamp(sigData: AuthSignatureData, publicKey: string | Buffer): Result<boolean> { if (!sigData) { return SecureChannelCode.error('AUTH_NO_SIG_DATA_OBJECT', 'No signature data object'); } if (!publicKey) { return SecureChannelCode.error('AUTH_NO_PUBLIC_KEY', 'No public key'); } if (!sigData.sig) { return SecureChannelCode.error('AUTH_NO_SIG_DATA', 'No signature'); } if (!sigData.payload) { return SecureChannelCode.error('AUTH_NO_SIG_PAYLOAD', 'No signed message'); } const publicKeyBuffer = typeof publicKey === 'string' ? Buffer.from(publicKey, 'base64') : publicKey; if (!publicKeyBuffer || publicKeyBuffer.length !== 32) { return SecureChannelCode.error('AUTH_BAD_PUBLIC_KEY', 'Public key must be 32 bytes'); } const sigBuffer = Buffer.from(sigData.sig, 'base64'); if (!sigBuffer || sigBuffer.length !== 64) { return SecureChannelCode.error('AUTH_BAD_SIG_DATA', 'Signature must be 64 bytes'); } const payloadBuffer = Buffer.from(sigData.payload, 'base64'); if (!payloadBuffer) { return SecureChannelCode.error('AUTH_BAD_SIG_PAYLOAD', 'Malformed payload; base64 decode failed'); } const valid = FourQ.verify(sigBuffer, payloadBuffer, publicKeyBuffer); return ok(valid); } } export class SecureChannel { type: SecureChannelTypes; channelId: string; channelIdLength: number; peerInfo: SecureChannelPeer = { ecdhPublicKey: null }; signing: AuthSigning = null; localKeyPair: DiffieHellmanKeyPair; sharedSecret: Buffer = null; expires: number = 0; pregeneratedNonce: Buffer; lastActive: number; constructor( type?: SecureChannelTypes, channelId?: string, peerInfo?: SecureChannelPeer, localKeypair?: DiffieHellmanKeyPair, signing?: AuthSigning ) { this.type = type ? type : SecureChannelTypes.DEFAULT; this.channelId = !channelId || channelId === 'generate' ? FourQ.randomBytes(16).toString('base64') : channelId; this.channelIdLength = this.channelId.length; this.signing = signing; if (this.signing && this.signing.private && !this.signing.public) { this.signing.public = FourQ.ecdhGenerateFromSeed(Buffer.from(this.signing.private, 'base64')).publicKey.toString('base64'); } this.refreshNonce(); if (peerInfo) { this.fromPeerPublicKey(peerInfo, localKeypair); } this.lastActive = Date.now(); } fromPeerPublicKey(peerInfo: SecureChannelPeer, localKeypair?: DiffieHellmanKeyPair) { this.peerInfo = peerInfo; // Buffer.from(peerPubKeyB64, 'base64'); this.localKeyPair = localKeypair ? localKeypair : FourQ.ecdhGenerateKeyPair(); if (this.peerInfo.ecdhPublicKey.length === 32) { this.sharedSecret = FourQ.getSharedSecret(this.localKeyPair.secretKey, this.peerInfo.ecdhPublicKey); } return this; } getSecureChannelResponse(extraInfo?: SecureChannelResponseExtraInfo): Result<SecureChannelResponse> { const sigResult = SecureHandshake.signStamp(this.signing.private); if (sigResult.bad) { return passthru(sigResult); } return ok({ ...sigResult.data, channelId: this.channelId, ecdhPublicKey: this.localKeyPair.publicKey.toString('base64'), signaturePublicKey: this.signing.public, extra: extraInfo, } as SecureChannelResponse); } refreshNonce() { this.pregeneratedNonce = FourQ.randomBytes(32); } fromJSONObject(src: any) { this.type = src.type; this.channelId = src.channelId; this.peerInfo = { ecdhPublicKey: src.peerPublicKeyB64 ? Buffer.from(src.peerPublicKeyB64, 'base64') : null, signaturePublicKey: src.peerSignaturePublicKeyB64 ? Buffer.from(src.peerSignaturePublicKeyB64, 'base64') : null, iden: src.peerIden, data: src.peerData, }; this.localKeyPair = { isDH: true, type: CryptoScheme.FourQ, publicKey: src.localKeyPairPublicKeyB64 ? Buffer.from(src.localKeyPairPublicKeyB64, 'base64') : null, secretKey: src.localKeyPairPublicKeyB64 ? Buffer.from(src.localKeyPairSecretKeyB64, 'base64') : null, }; if (src.sharedSecretB64) { this.sharedSecret = Buffer.from(src.sharedSecretB64, 'base64'); } else { this.fromPeerPublicKey(this.peerInfo, this.localKeyPair.publicKey ? this.localKeyPair : null); } this.expires = src.expires; return this; } toJSON() { return JSON.stringify({ type: this.type, channelId: this.channelId, peerIden: this.peerInfo.iden, peerData: this.peerInfo.data, peerPublicKeyB64: this.peerInfo.ecdhPublicKey ? this.peerInfo.ecdhPublicKey.toString('base64') : null, peerSignaturePublicKeyB64: this.peerInfo.signaturePublicKey ? this.peerInfo.signaturePublicKey.toString('base64') : null, localKeyPairPublicKeyB64: this.localKeyPair ? this.localKeyPair.publicKey.toString('base64') : null, localKeyPairSecretKeyB64: this.localKeyPair ? this.localKeyPair.secretKey.toString('base64') : null, sharedSecretB64: this.sharedSecret ? this.sharedSecret.toString('base64') : null, expires: this.expires, }); } createTcpPayload(payload: Buffer) { const sharedKey64Bytes = Buffer.concat([this.pregeneratedNonce, this.sharedSecret]); const keyHash = crypto.createHash('sha512').update(sharedKey64Bytes).digest(); const payloadLength = 4 + 32 + payload.length; const lengthBytes = SecureHandshake.getLengthBytesUInt32LE(payloadLength); const tcpPayload = Buffer.concat([lengthBytes, this.pregeneratedNonce, FourQ.xorCryptSHA512(keyHash, payload)]); // const tcpPayload = Buffer.concat([lengthBytes, this.pregeneratedNonce, payload]); this.refreshNonce(); return tcpPayload; } decryptTcpPayload(tcpPayload: Buffer) { const nonce = tcpPayload.slice(4, 36); const encryptedPayload = tcpPayload.slice(36); return this.decryptPayload(encryptedPayload, nonce); } createWrappedPayloadFromBuffer(payload: Buffer) { this.lastActive = Date.now(); const sharedKey64Bytes = Buffer.concat([this.pregeneratedNonce, this.sharedSecret]); const keyHash = crypto.createHash('sha512').update(sharedKey64Bytes).digest(); const wrapped: SecureChannelPayload = { __scp: true, c: this.channelId, n: this.pregeneratedNonce.toString('base64'), p: FourQ.xorCryptSHA512(keyHash, payload).toString('base64'), }; this.refreshNonce(); return wrapped; } createWrappedPayload(payload: string | Buffer) { if (typeof payload === 'string') { return this.createWrappedPayloadFromBuffer(Buffer.from(payload, 'utf8')); } else { return this.createWrappedPayloadFromBuffer(payload); } } createWrappedPayloadObject(obj: any) { return this.createWrappedPayloadFromBuffer(Buffer.from(JSON.stringify(obj), 'utf8')); } createWrappedPayloadString(obj: any) { return JSON.stringify(this.createWrappedPayloadObject(obj)); } createWrappedPayloadBase64(obj: any) { return Buffer.from(this.createWrappedPayloadString(obj), 'utf8').toString('base64'); } decryptPayload(payloadBytes: Buffer, nonce: Buffer) { this.lastActive = Date.now(); const sharedKey64Bytes = Buffer.concat([nonce, this.sharedSecret]); const keyHash = crypto.createHash('sha512').update(sharedKey64Bytes).digest(); return FourQ.xorCryptSHA512(keyHash, payloadBytes); } decryptSecureChannelPayloadObject(wrapped: SecureChannelPayload, outputEncoding = 'utf8') { const nonce = Buffer.from(wrapped.n, 'base64'); const payloadEnc = Buffer.from(wrapped.p, 'base64'); const decoded = this.decryptPayload(payloadEnc, nonce); switch (outputEncoding) { case 'utf8': { return decoded.toString('utf8'); } default: { return decoded; } } } decryptSecureChannelPayload(wrapped: SecureChannelPayload) { return this.decryptSecureChannelPayloadObject(wrapped) as Buffer; } decryptSecureChannelPayloadIntoString(wrapped: SecureChannelPayload) { return this.decryptSecureChannelPayloadObject(wrapped, 'utf8') as string; } decryptPayloadBase64(payloadStrB64: string) { const payload = this.parseWrappedPayloadString(Buffer.from(payloadStrB64, 'base64').toString('utf8')); return this.parseSecureChannelPayloadIntoObject(payload); } parseSecureChannelPayloadIntoObject(wrapped: SecureChannelPayload) { return JSON.parse(this.decryptSecureChannelPayloadIntoString(wrapped)); } parseWrappedPayloadString(payloadStr: string) { return JSON.parse(payloadStr) as SecureChannelPayload; } parseWrappedPayloadBase64(payloadStrB64: string) { return this.parseWrappedPayloadString(Buffer.from(payloadStrB64, 'base64').toString('utf8')); } } export function saltedXorCrypt(payload: Buffer, secret: Buffer, ...salts: Buffer[]) { return FourQ.xorCryptSHA512(saltedSha512(secret, ...salts), payload); } export function saltedSha512(message: Buffer, ...salts: Buffer[]) { const sharedKey64Bytes = Buffer.concat([...salts, message]); return crypto.createHash('sha512').update(sharedKey64Bytes).digest() as Buffer; } export function saltedDomainSha512(message: Buffer, domain: Buffer, ...salts: Buffer[]) { const sharedKey64Bytes = Buffer.concat([domain, verticalBarBuffer, ...salts, verticalBarBuffer, message]); return crypto.createHash('sha512').update(sharedKey64Bytes).digest() as Buffer; } export function randHex(length: number) { const characters = '0123456789abcdef'; const str = []; for (let i = 0; i < length; ++i) { str.push(characters[Math.floor(Math.random() * 16)]); } return str.join(''); } export async function initiateSecureChannel(initiatorFlow: SecureChannelInitFlow): Promise<Result<SecureChannel>> { const ecdhKeypair = FourQ.ecdhGenerateKeyPair(); const channelMyPubkey = ecdhKeypair.publicKey.toString('base64'); const user = initiatorFlow.user ? initiatorFlow.user : 'user'; let token: string; try { token = await resolveEntry('token', initiatorFlow.token); } catch (e) { return SecureChannelCode.error('CONTACT_INITIATION_TOKEN_NOT_FOUND', e); } const authHeader = SecureHandshake.getAccessorHeader(user, channelMyPubkey, token, initiatorFlow.signing, initiatorFlow.expire); let initiateContactResult: Result<SecureChannelResponse>; try { initiateContactResult = await initiatorFlow.initiateContact(authHeader.data); } catch (e) { return SecureChannelCode.error('CONTACT_INITIATION_FAILURE', e); } if (initiateContactResult.bad) { return SecureChannelCode.error('CONTACT_INITIATION_BAD_RESULT', initiateContactResult.error); } const response = initiateContactResult.data; if (!response) { return SecureChannelCode.error('CONTACT_INITIATION_NULL_RESPONSE'); } if (!response.channelId || !response.channelId.length || response.channelId.length > 128) { return SecureChannelCode.error('BAD_CHANNEL_ID_CLIENT', response.channelId); } let trusted = await resolveEntry('trust', initiatorFlow.trust); if (!trusted) { trusted = await resolveEntry('trust', initiatorFlow.trust, response.signaturePublicKey);} if (trusted) { if (trusted.publicKey !== response.signaturePublicKey) { return SecureChannelCode.error('TRUSTED_PEER_MISMATCH_CLIENT', `Peer with public key '${response.signaturePublicKey}' is not found on trust list.`); } const valid = SecureHandshake.verifyStamp(response, response.signaturePublicKey); if (!valid) { return SecureChannelCode.error('TRUST_STAMP_VERIFICACTION_FAILURE_CLIENT'); } } const peerInfo: SecureChannelPeer = { ecdhPublicKey: Buffer.from(response.ecdhPublicKey, 'base64'), signaturePublicKey: Buffer.from(response.signaturePublicKey, 'base64'), }; const channel = new SecureChannel(SecureChannelTypes.ECC_4Q, initiatorFlow.channelId, peerInfo, ecdhKeypair, initiatorFlow.signing); return ok(channel); } export async function answerSecureChannel(answererFlow: SecureChannelAnswerFlow): Promise<Result<SecureChannel>> { const timeWindow = answererFlow.timeWindow ? answererFlow.timeWindow : 5000; const ecdhKeypair = FourQ.ecdhGenerateKeyPair(); const authInfo = SecureHandshake.parseAuthHeader(answererFlow.authHeader); const token = await resolveEntry('token', answererFlow.token); const accessData = SecureHandshake.verifyAccessor(authInfo.accessorExpression, token, timeWindow); if (accessData.bad) { return passthru(accessData); } if (answererFlow.trust) { const trusted = await resolveEntry('trust', answererFlow.trust, authInfo.peerSignaturePublicKey); if (!trusted) { return SecureChannelCode.error('TRUSTED_PEER_MISMATCH_SERVER', `Peer with public key '${authInfo.peerSignaturePublicKey}' is not found on trust list.`); } if (!SecureHandshake.verifyAuthHeaderSignature(authInfo)) { return SecureChannelCode.error('TRUST_STAMP_VERIFICACTION_FAILURE_SERVER'); } } const peerEcdhPublicKey = Buffer.from(authInfo.peerEcdhPublicKey, 'base64'); const peerInfo: SecureChannelPeer = { ecdhPublicKey: peerEcdhPublicKey, signaturePublicKey: Buffer.from(authInfo.peerSignaturePublicKey, 'base64') }; const channel = new SecureChannel(SecureChannelTypes.ECC_4Q, answererFlow.channelId, peerInfo, ecdhKeypair, answererFlow.signing); return ok(channel); } function getLengthFromBytesOfHashContent(chunk: string, front = '') { const lengthBytes = front ? chunk.split(`${front} `)[1].substring(0, 4) : chunk.substring(0, 4); let headerLength; if (lengthBytes === '!!!!') { headerLength = 0; } else { const lengthBuffer = Buffer.from(lengthBytes, 'base64'); headerLength = lengthBuffer[0] * 65536 + lengthBuffer[1] * 256 + lengthBuffer[2]; } return headerLength; }