import * as b64 from 'base64-url' ; import * as uuid from 'uuid' ; import { PrivateKey , PublicKey , PublicKeyStatus , Signature , SignatureScheme } from '../crypto' ; import RestClient from '../network/rest/restClient' ; import { buildGetDocumentTx } from '../smartcontract/nativevm/ontidContractTxBuilder' ; import { buildGetDDOTx , buildGetPublicKeyStateTx } from '../smartcontract/nativevm/ontidContractTxBuilder' ; import { DDO } from '../transaction/ddo' ; import { hexstr2str , now , str2hexstr } from '../utils' ; /**
 * Factory method type used for creating concrete instances of Message.
 */ export type MessageFactory < T extends Message > = ( metadata : Metadata , signature : Signature | undefined , useProof : boolean | undefined ) => T ; /**
 * Metadata about the message.
 */ export interface Metadata { /**
     * Message id.
     *
     * Will be assigned if not provided.
     */ messageId ? : string ; /**
     * Issuer.
     */ issuer : string ; /**
     * Recipient.
     */ subject : string ; /**
     * Creation time.
     */ issuedAt : number ; /**
     * Expiration time.
     */ expireAt ? : number ; } /**
 * Common representation of Message in JWT form.
 */ export abstract class Message { param jwt param creator protected static deserializeInternal < T extends Message > ( jwt , creator ) { const parts = jwt . split ( '.' , 4 ) ; if ( parts . length < 2 ) { throw new Error ( 'Invalid message.' ) ; } const header = Message . deserializeHeader ( parts [ 0 ] ) ; const payload = Message . deserializePayload ( parts [ 1 ] ) ; let signature ; if ( parts . length > 2 ) { if ( header . algorithm !== undefined && header . publicKeyId !== undefined ) { signature = Signature . deserializeJWT ( parts [ 2 ] , header . algorithm , header . publicKeyId ) ; } else { throw new Error ( 'Signature scheme was not specified.' ) ; } } let useProof ; if ( parts . length === 4 ) { useProof = true ; } const msg = creator ( payload . metadata , signature , useProof ) ; msg . payloadFromJSON ( payload . rest ) ; return msg ; } param encoded private static deserializePayload ( encoded ) { const stringified = b64 . decode ( encoded ) ; const { jti , iss , sub , iat , exp , ... rest } = JSON . parse ( stringified ) ; return { metadata : { messageId : jti , issuer : iss , subject : sub , issuedAt : iat , expireAt : exp } as Metadata , rest } ; } param encoded private static deserializeHeader ( encoded ) { const stringified = b64 . decode ( encoded ) ; const header = JSON . parse ( stringified ) ; return { algorithm : header . alg !== undefined ? SignatureScheme . fromLabelJWS ( header . alg ) : undefined , publicKeyId : header . kid } ; } metadata : Metadata ; signature ? : Signature ; constructor ( metadata , signature ) { this . metadata = metadata ; this . signature = signature ; if ( this . metadata . messageId === undefined ) { this . metadata . messageId = uuid ( ) ; } } param url param publicKeyId param privateKey param algorithm async sign ( url , publicKeyId , privateKey , algorithm ? ) { await retrievePublicKey ( publicKeyId , url ) ; if ( algorithm === undefined ) { algorithm = privateKey . algorithm . defaultSchema ; } const msg = this . serializeUnsigned ( algorithm , publicKeyId ) ; const msgHex = str2hexstr ( msg ) ; this . signature = await privateKey . signAsync ( msgHex , algorithm , publicKeyId ) ; } param url returns async verify ( url ) { const signature = this . signature ; if ( signature !== undefined && signature . publicKeyId !== undefined ) { try { if ( ! this . verifyKeyOwnership ( ) ) { return false ; } if ( ! this . verifyExpiration ( ) ) { return false ; } const state = await retrievePublicKeyState ( signature . publicKeyId , url ) ; if ( state === PublicKeyStatus . REVOKED ) { return false ; } const publicKey = await retrievePublicKey ( signature . publicKeyId , url ) ; const msg = this . serializeUnsigned ( signature . algorithm , signature . publicKeyId ) ; const msgHex = str2hexstr ( msg ) ; return publicKey . verify ( msgHex , signature ) ; } catch ( e ) { return false ; } } else { return false ; } } param algorithm param publicKeyId serializeUnsigned ( algorithm ? , publicKeyId ? ) { const headerEncoded = this . serializeHeader ( algorithm , publicKeyId ) ; const payloadEncoded = this . serializePayload ( ) ; return headerEncoded + '.' + payloadEncoded ; } /**
     * Serializes the message into JWT format.
     *
     */ serialize ( ) { const signature = this . signature ; if ( signature !== undefined ) { const signatureEncoded = signature . serializeJWT ( ) ; return this . serializeUnsigned ( signature . algorithm , signature . publicKeyId ) + '.' + signatureEncoded ; } else { return this . serializeUnsigned ( ) ; } } param algorithm param publicKeyId protected serializeHeader ( algorithm , publicKeyId ) { let header ; if ( algorithm !== undefined ) { header = { alg : algorithm . labelJWS , typ : 'JWT' , kid : publicKeyId } ; } else { header = { typ : 'JWT' } ; } const stringified = JSON . stringify ( header ) ; return b64 . encode ( stringified , 'utf-8' ) ; } /**
     * Converts claim data to JSON for serialization.
     */ protected abstract payloadToJSON ( ) ; param json protected abstract payloadFromJSON ( json ) ; /**
     * Verifies if the expiration date has passed
     */ private verifyExpiration ( ) { if ( this . metadata . expireAt !== undefined ) { return now ( ) < this . metadata . expireAt ; } else { return true ; } } /**
     * Verifies if the declared public key id belongs to issuer.
     */ private verifyKeyOwnership ( ) { const signature = this . signature ; if ( signature !== undefined && signature . publicKeyId !== undefined ) { const ontId = extractOntId ( signature . publicKeyId ) ; return ontId === this . metadata . issuer ; } else { return false ; } } /**
     * Serializes payload part of JWT message.
     */ private serializePayload ( ) { const metadata = { jti : this . metadata . messageId , iss : this . metadata . issuer , sub : this . metadata . subject , iat : this . metadata . issuedAt , exp : this . metadata . expireAt } ; const rest = this . payloadToJSON ( ) ; const stringified = JSON . stringify ( { ... metadata , ... rest } ) ; return b64 . encode ( stringified , 'utf-8' ) ; } } /**
 * Simple implementation for Message
 */ export class SimpleMessage extends Message { static deserialize ( jwt ) { return super . deserializeInternal ( jwt , ( m , s ) => new SimpleMessage ( m , s ) ) ; } payload : any = { } ; payloadToJSON ( ) { return this . payload ; } payloadFromJSON ( json ) { this . payload = json ; } } param publicKeyId param url export async function retrievePublicKey ( publicKeyId , url ) { const ontId = extractOntId ( publicKeyId ) ; const keyId = extractKeyId ( publicKeyId ) ; const client = new RestClient ( url ) ; const tx = buildGetDocumentTx ( ontId ) ; const response = await client . sendRawTransaction ( tx . serialize ( ) , true ) ; if ( response . Result && response . Result . Result ) { try { const obj = JSON . parse ( hexstr2str ( response . Result . Result ) ) ; const publicKey = obj . publicKey . find ( ( pk ) => pk . id === publicKeyId ) ; if ( publicKey === undefined ) { throw new Error ( 'Not found' ) ; } return new PublicKey ( publicKey . publicKeyHex ) ; } catch ( err ) { throw new Error ( err ) ; } } else { const tx2 = buildGetDDOTx ( ontId ) ; const response2 = await client . sendRawTransaction ( tx2 . serialize ( ) , true ) ; if ( response2 . Result && response2 . Result . Result ) { const ddo = DDO . deserialize ( response2 . Result . Result ) ; const publicKey = ddo . publicKeys . find ( ( pk ) => pk . id === keyId ) ; if ( publicKey === undefined ) { throw new Error ( 'Not found' ) ; } return publicKey . pk ; } else { throw new Error ( 'Not found' ) ; } } } param publicKeyId param url export async function retrievePublicKeyState ( publicKeyId , url ) { const ontId = extractOntId ( publicKeyId ) ; const keyId = extractKeyId ( publicKeyId ) ; const client = new RestClient ( url ) ; const tx = buildGetPublicKeyStateTx ( ontId , keyId ) ; const response = await client . sendRawTransaction ( tx . serialize ( ) , true ) ; if ( response . Result && response . Result . Result ) { return PublicKeyStatus . fromHexLabel ( response . Result . Result ) ; } else { throw new Error ( 'Not found' ) ; } } param publicKeyId export function extractOntId ( publicKeyId ) { const index = publicKeyId . indexOf ( '#keys-' ) ; if ( index === - 1 ) { throw new Error ( 'Is not a publicKeId.' ) ; } return publicKeyId . substr ( 0 , index ) ; } param publicKeyId export function extractKeyId ( publicKeyId ) { const index = publicKeyId . indexOf ( '#keys-' ) ; if ( index === - 1 ) { throw new Error ( 'Is not a publicKeId.' ) ; } return Number ( publicKeyId . substr ( index + '#keys-' . length ) ) ; }