import * as forge from "node-forge" ; import { CryptoFunctionService } from "../abstractions/cryptoFunction.service" ; import { PlatformUtilsService } from "../abstractions/platformUtils.service" ; import { Utils } from "../misc/utils" ; import { DecryptParameters } from "../models/domain/decryptParameters" ; import { SymmetricCryptoKey } from "../models/domain/symmetricCryptoKey" ; export class WebCryptoFunctionService implements CryptoFunctionService { private crypto : Crypto ; private subtle : SubtleCrypto ; private isIE : boolean ; private isOldSafari : boolean ; constructor ( private win , private platformUtilsService ) { this . crypto = typeof win . crypto !== "undefined" ? win . crypto : null ; this . subtle = ! ! this . crypto && typeof win . crypto . subtle !== "undefined" ? win . crypto . subtle : null ; this . isIE = platformUtilsService . isIE ( ) ; const ua = win . navigator . userAgent ; this . isOldSafari = platformUtilsService . isSafari ( ) && ( ua . indexOf ( " Version/10." ) > - 1 || ua . indexOf ( " Version/9." ) > - 1 ) ; } async pbkdf2 ( password , salt , algorithm , iterations ) { if ( this . isIE || this . isOldSafari ) { const forgeLen = algorithm === "sha256" ? 32 : 64 ; const passwordBytes = this . toByteString ( password ) ; const saltBytes = this . toByteString ( salt ) ; const derivedKeyBytes = ( forge as any ) . pbkdf2 ( passwordBytes , saltBytes , iterations , forgeLen , algorithm ) ; return Utils . fromByteStringToArray ( derivedKeyBytes ) . buffer ; } const wcLen = algorithm === "sha256" ? 256 : 512 ; const passwordBuf = this . toBuf ( password ) ; const saltBuf = this . toBuf ( salt ) ; const pbkdf2Params = { name : "PBKDF2" , salt : saltBuf , iterations : iterations , hash : { name : this . toWebCryptoAlgorithm ( algorithm ) } , } ; const impKey = await this . subtle . importKey ( "raw" , passwordBuf , { name : "PBKDF2" } as any , false , [ "deriveBits" ] ) ; return await this . subtle . deriveBits ( pbkdf2Params , impKey , wcLen ) ; } async hkdf ( ikm , salt , info , outputByteSize , algorithm ) { const saltBuf = this . toBuf ( salt ) ; const infoBuf = this . toBuf ( info ) ; const hkdfParams = { name : "HKDF" , salt : saltBuf , info : infoBuf , hash : { name : this . toWebCryptoAlgorithm ( algorithm ) } , } ; const impKey = await this . subtle . importKey ( "raw" , ikm , { name : "HKDF" } as any , false , [ "deriveBits" , ] ) ; return await this . subtle . deriveBits ( hkdfParams as any , impKey , outputByteSize * 8 ) ; } async hkdfExpand ( prk , info , outputByteSize , algorithm ) { const hashLen = algorithm === "sha256" ? 32 : 64 ; if ( outputByteSize > 255 * hashLen ) { throw new Error ( "outputByteSize is too large." ) ; } const prkArr = new Uint8Array ( prk ) ; if ( prkArr . length < hashLen ) { throw new Error ( "prk is too small." ) ; } const infoBuf = this . toBuf ( info ) ; const infoArr = new Uint8Array ( infoBuf ) ; let runningOkmLength = 0 ; let previousT = new Uint8Array ( 0 ) ; const n = Math . ceil ( outputByteSize / hashLen ) ; const okm = new Uint8Array ( n * hashLen ) ; for ( let i = 0 ; i < n ; i ++ ) { const t = new Uint8Array ( previousT . length + infoArr . length + 1 ) ; t . set ( previousT ) ; t . set ( infoArr , previousT . length ) ; t . set ( [ i + 1 ] , t . length - 1 ) ; previousT = new Uint8Array ( await this . hmac ( t . buffer , prk , algorithm ) ) ; okm . set ( previousT , runningOkmLength ) ; runningOkmLength += previousT . length ; if ( runningOkmLength >= outputByteSize ) { break ; } } return okm . slice ( 0 , outputByteSize ) . buffer ; } async hash ( value , algorithm ) { if ( ( this . isIE && algorithm === "sha1" ) || algorithm === "md5" ) { const md = algorithm === "md5" ? forge . md . md5 . create ( ) : forge . md . sha1 . create ( ) ; const valueBytes = this . toByteString ( value ) ; md . update ( valueBytes , "raw" ) ; return Utils . fromByteStringToArray ( md . digest ( ) . data ) . buffer ; } const valueBuf = this . toBuf ( value ) ; return await this . subtle . digest ( { name : this . toWebCryptoAlgorithm ( algorithm ) } , valueBuf ) ; } async hmac ( value , key , algorithm ) { if ( this . isIE && algorithm === "sha512" ) { const hmac = ( forge as any ) . hmac . create ( ) ; const keyBytes = this . toByteString ( key ) ; const valueBytes = this . toByteString ( value ) ; hmac . start ( algorithm , keyBytes ) ; hmac . update ( valueBytes , "raw" ) ; return Utils . fromByteStringToArray ( hmac . digest ( ) . data ) . buffer ; } const signingAlgorithm = { name : "HMAC" , hash : { name : this . toWebCryptoAlgorithm ( algorithm ) } , } ; const impKey = await this . subtle . importKey ( "raw" , key , signingAlgorithm , false , [ "sign" ] ) ; return await this . subtle . sign ( signingAlgorithm , impKey , value ) ; } async compare ( a , b ) { const macKey = await this . randomBytes ( 32 ) ; const signingAlgorithm = { name : "HMAC" , hash : { name : "SHA-256" } , } ; const impKey = await this . subtle . importKey ( "raw" , macKey , signingAlgorithm , false , [ "sign" ] ) ; const mac1 = await this . subtle . sign ( signingAlgorithm , impKey , a ) ; const mac2 = await this . subtle . sign ( signingAlgorithm , impKey , b ) ; if ( mac1 . byteLength !== mac2 . byteLength ) { return false ; } const arr1 = new Uint8Array ( mac1 ) ; const arr2 = new Uint8Array ( mac2 ) ; for ( let i = 0 ; i < arr2 . length ; i ++ ) { if ( arr1 [ i ] !== arr2 [ i ] ) { return false ; } } return true ; } hmacFast ( value , key , algorithm ) { const hmac = ( forge as any ) . hmac . create ( ) ; hmac . start ( algorithm , key ) ; hmac . update ( value ) ; const bytes = hmac . digest ( ) . getBytes ( ) ; return Promise . resolve ( bytes ) ; } async compareFast ( a , b ) { const rand = await this . randomBytes ( 32 ) ; const bytes = new Uint32Array ( rand ) ; const buffer = forge . util . createBuffer ( ) ; for ( let i = 0 ; i < bytes . length ; i ++ ) { buffer . putInt32 ( bytes [ i ] ) ; } const macKey = buffer . getBytes ( ) ; const hmac = ( forge as any ) . hmac . create ( ) ; hmac . start ( "sha256" , macKey ) ; hmac . update ( a ) ; const mac1 = hmac . digest ( ) . getBytes ( ) ; hmac . start ( null , null ) ; hmac . update ( b ) ; const mac2 = hmac . digest ( ) . getBytes ( ) ; const equals = mac1 === mac2 ; return equals ; } async aesEncrypt ( data , iv , key ) { const impKey = await this . subtle . importKey ( "raw" , key , { name : "AES-CBC" } as any , false , [ "encrypt" , ] ) ; return await this . subtle . encrypt ( { name : "AES-CBC" , iv : iv } , impKey , data ) ; } aesDecryptFastParameters ( data , iv , mac , key ) { const p = new DecryptParameters < string > ( ) ; if ( key . meta != null ) { p . encKey = key . meta . encKeyByteString ; p . macKey = key . meta . macKeyByteString ; } if ( p . encKey == null ) { p . encKey = forge . util . decode64 ( key . encKeyB64 ) ; } p . data = forge . util . decode64 ( data ) ; p . iv = forge . util . decode64 ( iv ) ; p . macData = p . iv + p . data ; if ( p . macKey == null && key . macKeyB64 != null ) { p . macKey = forge . util . decode64 ( key . macKeyB64 ) ; } if ( mac != null ) { p . mac = forge . util . decode64 ( mac ) ; } if ( key . meta == null ) { key . meta = { } ; } if ( key . meta . encKeyByteString == null ) { key . meta . encKeyByteString = p . encKey ; } if ( p . macKey != null && key . meta . macKeyByteString == null ) { key . meta . macKeyByteString = p . macKey ; } return p ; } aesDecryptFast ( parameters ) { const dataBuffer = ( forge as any ) . util . createBuffer ( parameters . data ) ; const decipher = ( forge as any ) . cipher . createDecipher ( "AES-CBC" , parameters . encKey ) ; decipher . start ( { iv : parameters . iv } ) ; decipher . update ( dataBuffer ) ; decipher . finish ( ) ; const val = decipher . output . toString ( "utf8" ) ; return Promise . resolve ( val ) ; } async aesDecrypt ( data , iv , key ) { const impKey = await this . subtle . importKey ( "raw" , key , { name : "AES-CBC" } as any , false , [ "decrypt" , ] ) ; return await this . subtle . decrypt ( { name : "AES-CBC" , iv : iv } , impKey , data ) ; } async rsaEncrypt ( data , publicKey , algorithm ) { const rsaParams = { name : "RSA-OAEP" , hash : { name : this . toWebCryptoAlgorithm ( algorithm ) } , } ; const impKey = await this . subtle . importKey ( "spki" , publicKey , rsaParams , false , [ "encrypt" ] ) ; return await this . subtle . encrypt ( rsaParams , impKey , data ) ; } async rsaDecrypt ( data , privateKey , algorithm ) { const rsaParams = { name : "RSA-OAEP" , hash : { name : this . toWebCryptoAlgorithm ( algorithm ) } , } ; const impKey = await this . subtle . importKey ( "pkcs8" , privateKey , rsaParams , false , [ "decrypt" ] ) ; return await this . subtle . decrypt ( rsaParams , impKey , data ) ; } async rsaExtractPublicKey ( privateKey ) { const rsaParams = { name : "RSA-OAEP" , hash : { name : this . toWebCryptoAlgorithm ( "sha1" ) } , } ; const impPrivateKey = await this . subtle . importKey ( "pkcs8" , privateKey , rsaParams , true , [ "decrypt" , ] ) ; const jwkPrivateKey = await this . subtle . exportKey ( "jwk" , impPrivateKey ) ; const jwkPublicKeyParams = { kty : "RSA" , e : jwkPrivateKey . e , n : jwkPrivateKey . n , alg : "RSA-OAEP" , ext : true , } ; const impPublicKey = await this . subtle . importKey ( "jwk" , jwkPublicKeyParams , rsaParams , true , [ "encrypt" , ] ) ; return await this . subtle . exportKey ( "spki" , impPublicKey ) ; } async rsaGenerateKeyPair ( length ) { const rsaParams = { name : "RSA-OAEP" , modulusLength : length , publicExponent : new Uint8Array ( [ 0x01 , 0x00 , 0x01 ] ) , hash : { name : this . toWebCryptoAlgorithm ( "sha1" ) } , } ; const keyPair = ( await this . subtle . generateKey ( rsaParams , true , [ "encrypt" , "decrypt" , ] ) ) as CryptoKeyPair ; const publicKey = await this . subtle . exportKey ( "spki" , keyPair . publicKey ) ; const privateKey = await this . subtle . exportKey ( "pkcs8" , keyPair . privateKey ) ; return [ publicKey , privateKey ] ; } randomBytes ( length ) { const arr = new Uint8Array ( length ) ; this . crypto . getRandomValues ( arr ) ; return Promise . resolve ( arr . buffer ) ; } private toBuf ( value ) { let buf ; if ( typeof value === "string" ) { buf = Utils . fromUtf8ToArray ( value ) . buffer ; } else { buf = value ; } return buf ; } private toByteString ( value ) { let bytes ; if ( typeof value === "string" ) { bytes = forge . util . encodeUtf8 ( value ) ; } else { bytes = Utils . fromBufferToByteString ( value ) ; } return bytes ; } private toWebCryptoAlgorithm ( algorithm ) { if ( algorithm === "md5" ) { throw new Error ( "MD5 is not supported in WebCrypto." ) ; } return algorithm === "sha1" ? "SHA-1" : algorithm === "sha256" ? "SHA-256" : "SHA-512" ; } }