import { BigInteger , parseBigInt , RSAKey } from "../internal/crypto-jsbn-2012-08-09_1.js" ; import type { Base64 , Hex } from "@tutao/tutanota-utils" ; import { arrayEquals , base64ToHex , base64ToUint8Array , concat , int8ArrayToBase64 , uint8ArrayToBase64 , uint8ArrayToHex , } from "@tutao/tutanota-utils" ; import { random } from "../random/Randomizer.js" ; import type { PrivateKey , PublicKey , RsaKeyPair } from "./RsaKeyPair.js" ; import { CryptoError } from "../misc/CryptoError.js" ; import { sha256Hash } from "../hashes/Sha256.js" ; const RSA_KEY_LENGTH_BITS = 2048 ; const RSA_PUBLIC_EXPONENT = 65537 ; export function generateRsaKey ( ) { try { let rsa = new RSAKey ( ) ; rsa . generate ( RSA_KEY_LENGTH_BITS , RSA_PUBLIC_EXPONENT . toString ( 16 ) ) ; return { publicKey : { version : 0 , keyLength : RSA_KEY_LENGTH_BITS , modulus : uint8ArrayToBase64 ( new Uint8Array ( rsa . n . toByteArray ( ) ) ) , publicExponent : RSA_PUBLIC_EXPONENT , } , privateKey : { version : 0 , keyLength : RSA_KEY_LENGTH_BITS , modulus : uint8ArrayToBase64 ( new Uint8Array ( rsa . n . toByteArray ( ) ) ) , privateExponent : uint8ArrayToBase64 ( new Uint8Array ( rsa . d . toByteArray ( ) ) ) , primeP : uint8ArrayToBase64 ( new Uint8Array ( rsa . p . toByteArray ( ) ) ) , primeQ : uint8ArrayToBase64 ( new Uint8Array ( rsa . q . toByteArray ( ) ) ) , primeExponentP : uint8ArrayToBase64 ( new Uint8Array ( rsa . dmp1 . toByteArray ( ) ) ) , primeExponentQ : uint8ArrayToBase64 ( new Uint8Array ( rsa . dmq1 . toByteArray ( ) ) ) , crtCoefficient : uint8ArrayToBase64 ( new Uint8Array ( rsa . coeff . toByteArray ( ) ) ) , } , } ; } catch ( e ) { throw new CryptoError ( "failed RSA key generation" , e as Error ) ; } } export function rsaEncrypt ( publicKey , bytes , seed ) { const rsa = new RSAKey ( ) ; rsa . n = new BigInteger ( new Int8Array ( base64ToUint8Array ( publicKey . modulus ) ) ) ; rsa . e = publicKey . publicExponent ; const paddedBytes = oaepPad ( bytes , publicKey . keyLength , seed ) ; const paddedHex = uint8ArrayToHex ( paddedBytes ) ; const bigInt = parseBigInt ( paddedHex , 16 ) ; let encrypted ; try { encrypted = new Uint8Array ( rsa . doPublic ( bigInt ) . toByteArray ( ) ) ; } catch ( e ) { throw new CryptoError ( "failed RSA encryption" , e as Error ) ; } return _padAndUnpadLeadingZeros ( publicKey . keyLength / 8 , encrypted ) ; } export function rsaDecrypt ( privateKey , bytes ) { try { const rsa = new RSAKey ( ) ; rsa . n = new BigInteger ( new Int8Array ( base64ToUint8Array ( privateKey . modulus ) ) ) ; rsa . d = new BigInteger ( new Int8Array ( base64ToUint8Array ( privateKey . privateExponent ) ) ) ; rsa . p = new BigInteger ( new Int8Array ( base64ToUint8Array ( privateKey . primeP ) ) ) ; rsa . q = new BigInteger ( new Int8Array ( base64ToUint8Array ( privateKey . primeQ ) ) ) ; rsa . dmp1 = new BigInteger ( new Int8Array ( base64ToUint8Array ( privateKey . primeExponentP ) ) ) ; rsa . dmq1 = new BigInteger ( new Int8Array ( base64ToUint8Array ( privateKey . primeExponentQ ) ) ) ; rsa . coeff = new BigInteger ( new Int8Array ( base64ToUint8Array ( privateKey . crtCoefficient ) ) ) ; const hex = uint8ArrayToHex ( bytes ) ; const bigInt = parseBigInt ( hex , 16 ) ; const decrypted = new Uint8Array ( rsa . doPrivate ( bigInt ) . toByteArray ( ) ) ; const paddedDecrypted = _padAndUnpadLeadingZeros ( privateKey . keyLength / 8 - 1 , decrypted ) ; return oaepUnpad ( paddedDecrypted , privateKey . keyLength ) ; } catch ( e ) { throw new CryptoError ( "failed RSA decryption" , e as Error ) ; } } /**
 * Adds leading 0's to the given byte array until targeByteLength bytes are reached. Removes leading 0's if byteArray is longer than targetByteLength.
 */ export function _padAndUnpadLeadingZeros ( targetByteLength , byteArray ) { const result = new Uint8Array ( targetByteLength ) ; if ( byteArray . length > result . length ) { const lastExtraByte = byteArray [ byteArray . length - result . length - 1 ] ; if ( lastExtraByte !== 0 ) { throw new CryptoError ( `leading byte is not 0 but ${ lastExtraByte }, encrypted length: ${ byteArray . length }` ) ; } byteArray = byteArray . slice ( byteArray . length - result . length ) ; } result . set ( byteArray , result . length - byteArray . length ) ; return result ; } see http param privateKey param bytes return export function sign ( privateKey , bytes ) { try { let rsa = new RSAKey ( ) ; rsa . n = new BigInteger ( new Int8Array ( base64ToUint8Array ( privateKey . modulus ) ) ) ; rsa . d = new BigInteger ( new Int8Array ( base64ToUint8Array ( privateKey . privateExponent ) ) ) ; rsa . p = new BigInteger ( new Int8Array ( base64ToUint8Array ( privateKey . primeP ) ) ) ; rsa . q = new BigInteger ( new Int8Array ( base64ToUint8Array ( privateKey . primeQ ) ) ) ; rsa . dmp1 = new BigInteger ( new Int8Array ( base64ToUint8Array ( privateKey . primeExponentP ) ) ) ; rsa . dmq1 = new BigInteger ( new Int8Array ( base64ToUint8Array ( privateKey . primeExponentQ ) ) ) ; rsa . coeff = new BigInteger ( new Int8Array ( base64ToUint8Array ( privateKey . crtCoefficient ) ) ) ; var salt = random . generateRandomData ( 32 ) ; let paddedBytes = encode ( bytes , privateKey . keyLength , salt ) ; let paddedHex = uint8ArrayToHex ( paddedBytes ) ; let bigInt = parseBigInt ( paddedHex , 16 ) ; let signed = new Uint8Array ( rsa . doPrivate ( bigInt ) . toByteArray ( ) ) ; let result = _padAndUnpadLeadingZeros ( privateKey . keyLength / 8 , signed ) ; return result ; } catch ( e ) { throw new CryptoError ( "failed RSA sign" , e as Error ) ; } } param publicKey param bytes param signature throws export function verifySignature ( publicKey , bytes , signature ) { try { let rsa = new RSAKey ( ) ; rsa . n = new BigInteger ( new Int8Array ( base64ToUint8Array ( publicKey . modulus ) ) ) ; rsa . e = publicKey . publicExponent ; let signatureHex = uint8ArrayToHex ( signature ) ; let bigInt = parseBigInt ( signatureHex , 16 ) ; let unpadded = new Uint8Array ( rsa . doPublic ( bigInt ) . toByteArray ( ) ) ; let padded = _padAndUnpadLeadingZeros ( publicKey . keyLength / 8 , unpadded ) ; _verify ( bytes , padded , publicKey . keyLength - 1 ) ; } catch ( e ) { throw new CryptoError ( "failed RSA verify sign" , e as Error ) ; } } /********************************* OAEP *********************************/ see https param value param keyLength param seed return export function oaepPad ( value , keyLength , seed ) { let hashLength = 32 ; if ( seed . length !== hashLength ) { throw new CryptoError ( "invalid seed length: " + seed . length + ". expected: " + hashLength + " bytes!" ) ; } if ( value . length > keyLength / 8 - hashLength - 1 ) { throw new CryptoError ( "invalid value length: " + value . length + ". expected: max. " + ( keyLength / 8 - hashLength - 1 ) ) ; } let block = _getPSBlock ( value , keyLength ) ; let dbMask = mgf1 ( seed , block . length - hashLength ) ; for ( let i = hashLength ; i < block . length ; i ++ ) { block [ i ] ^= dbMask [ i - hashLength ] ; } let seedMask = mgf1 ( block . slice ( hashLength , block . length ) , hashLength ) ; for ( let i = 0 ; i < seedMask . length ; i ++ ) { block [ i ] = seed [ i ] ^ seedMask [ i ] ; } return block ; } param value param keyLength return export function oaepUnpad ( value , keyLength ) { let hashLength = 32 ; if ( value . length !== keyLength / 8 - 1 ) { throw new CryptoError ( "invalid value length: " + value . length + ". expected: " + ( keyLength / 8 - 1 ) + " bytes!" ) ; } let seedMask = mgf1 ( value . slice ( hashLength , value . length ) , hashLength ) ; let seed = new Uint8Array ( hashLength ) ; for ( let i = 0 ; i < seedMask . length ; i ++ ) { seed [ i ] = value [ i ] ^ seedMask [ i ] ; } let dbMask = mgf1 ( seed , value . length - hashLength ) ; for ( let i = hashLength ; i < value . length ; i ++ ) { value [ i ] ^= dbMask [ i - hashLength ] ; } for ( var index = 2 * hashLength ; index < value . length ; index ++ ) { if ( value [ index ] === 1 ) { break ; } else if ( value [ index ] !== 0 || index === value . length ) { throw new CryptoError ( "invalid padding" ) ; } } return value . slice ( index + 1 , value . length ) ; } /**
 * Provides a block of keyLength / 8 - 1 bytes with the following format:
 * [ zeros ] [ label hash ] [ zeros ] [ 1 ] [ value ]
 *    32           32    keyLen-2*32-2  1  value.length
 * The label is the hash of an empty string like defined in PKCS#1 v2.1
 */ export function _getPSBlock ( value , keyLength ) { let hashLength = 32 ; let blockLength = keyLength / 8 - 1 ; let block = new Uint8Array ( blockLength ) ; let defHash = sha256Hash ( new Uint8Array ( [ ] ) ) ; let nbrOfZeros = block . length - ( 1 + value . length ) ; for ( let i = 0 ; i < block . length ; i ++ ) { if ( i >= hashLength && i < 2 * hashLength ) { block [ i ] = defHash [ i - hashLength ] ; } else if ( i < nbrOfZeros ) { block [ i ] = 0 ; } else if ( i === nbrOfZeros ) { block [ i ] = 1 ; } else { block [ i ] = value [ i - nbrOfZeros - 1 ] ; } } return block ; } /********************************* PSS *********************************/ param message param keyLength param salt return export function encode ( message , keyLength , salt ) { let hashLength = 32 ; let emLen = Math . ceil ( keyLength / 8 ) ; if ( salt . length !== hashLength ) { throw new Error ( "invalid _salt length: " + salt . length + ". expected: " + hashLength + " bytes!" ) ; } let length = hashLength + salt . length + 2 ; if ( emLen < length ) { throw new Error ( "invalid hash/_salt length: " + length + ". expected: max. " + emLen ) ; } let emBits = keyLength - 1 ; let minEmBitsLength = 8 * hashLength + 8 * salt . length + 9 ; if ( emBits < minEmBitsLength ) { throw new Error ( "invalid maximum emBits length. Was " + emBits + ", expected: " + minEmBitsLength ) ; } let messageHash = sha256Hash ( message ) ; let message2 = concat ( new Uint8Array ( [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ) , messageHash , salt ) ; let message2Hash = sha256Hash ( message2 ) ; let ps = new Uint8Array ( emLen - salt . length - hashLength - 2 ) ; for ( let i = 0 ; i < ps . length ; i ++ ) { ps [ i ] = 0 ; } let db = concat ( ps , new Uint8Array ( [ 1 ] ) , salt ) ; _clear ( ps ) ; let expectedDbLength = emLen - hashLength - 1 ; if ( db . length !== expectedDbLength ) { throw new Error ( "unexpected length of block: " + db . length + ". Expected: " + expectedDbLength ) ; } let dbMask = mgf1 ( message2Hash , emLen - message2Hash . length - 1 ) ; let maskedDb = new Uint8Array ( dbMask . length ) ; for ( let i = 0 ; i < dbMask . length ; i ++ ) { maskedDb [ i ] = db [ i ] ^ dbMask [ i ] ; } _clear ( db ) ; maskedDb [ 0 ] &= 0xff >> ( 8 * emLen - emBits ) ; let em = concat ( maskedDb , message2Hash , new Uint8Array ( [ 188 ] ) ) ; _clear ( maskedDb ) ; return em ; } see https param message param encodedMessage param keyLength throws export function _verify ( message , encodedMessage , keyLength ) { let hashLength = 32 ; let saltLength = hashLength ; let emLen = Math . ceil ( keyLength / 8 ) ; let minEncodedLength = hashLength + saltLength + 2 ; try { if ( encodedMessage . length < minEncodedLength ) { throw new Error ( "invalid length of encoded message: " + encodedMessage . length + ". expected: > " + minEncodedLength + " bytes!" ) ; } if ( encodedMessage [ encodedMessage . length - 1 ] !== 188 ) { throw new Error ( "rightmost octet of EM must be 188 (0xbc) but was " + encodedMessage [ encodedMessage . length - 1 ] ) ; } var maskedDB = encodedMessage . slice ( 0 , emLen - hashLength - 1 ) ; var hashed = encodedMessage . slice ( emLen - hashLength - 1 , emLen - hashLength - 1 + hashLength ) ; if ( maskedDB [ 0 ] >> ( 8 - ( 8 * emLen - keyLength ) ) !== 0 ) { throw new Error ( "inconsistent leftmost octet in maskedDB." ) ; } var dbMask = mgf1 ( hashed , emLen - hashLength - 1 ) ; var db = new Uint8Array ( dbMask . length ) ; for ( let i = 0 ; i < dbMask . length ; i ++ ) { db [ i ] = maskedDB [ i ] ^ dbMask [ i ] ; } db [ 0 ] &= 0xff >> ( 8 * emLen - keyLength ) ; for ( let i = 0 ; i < emLen - hashLength - saltLength - 2 ; i ++ ) { if ( db [ i ] !== 0 ) { throw new Error ( "inconsistent leftmost octets of db." ) ; } } if ( db [ emLen - hashLength - saltLength - 2 ] !== 1 ) { throw new Error ( "inconsistent octet value in db. Expected 1 (0x01) but was " + db [ emLen - hashLength - saltLength - 1 ] ) ; } var salt = db . slice ( db . length - saltLength ) ; var messageHash = sha256Hash ( message ) ; var message2 = concat ( new Uint8Array ( [ 0 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ) , messageHash , salt ) ; var message2Hash = sha256Hash ( message2 ) ; if ( ! arrayEquals ( hashed , message2Hash ) ) { throw new CryptoError ( "Hashes do not match" ) ; } } finally { _clear ( message ) ; _clear ( encodedMessage ) ; _clear ( maskedDB ) ; _clear ( hashed ) ; _clear ( dbMask ) ; _clear ( db ) ; _clear ( salt ) ; _clear ( messageHash ) ; _clear ( message2 ) ; _clear ( message2Hash ) ; } } /**
 * clears an array to contain only zeros (0)
 */ function _clear ( array ) { if ( ! array ) { return ; } array . fill ( 0 ) ; } /********************************* RSA utils *********************************/ param seed param length export function mgf1 ( seed , length ) { let C = null ; let counter = 0 ; let T = new Uint8Array ( 0 ) ; do { C = i2osp ( counter ) ; T = concat ( T , sha256Hash ( concat ( seed , C ) ) ) ; } while ( ++ counter < Math . ceil ( length / ( 256 / 8 ) ) ) ; return T . slice ( 0 , length ) ; } /**
 * converts an integer to a 4 byte array
 */ export function i2osp ( i ) { return new Uint8Array ( [ ( i >> 24 ) & 255 , ( i >> 16 ) & 255 , ( i >> 8 ) & 255 , ( i >> 0 ) & 255 ] ) ; } /********************************* Key conversion *********************************/ param publicKey returns private function _publicKeyToArray ( publicKey ) { return [ _base64ToBigInt ( publicKey . modulus ) ] ; } param privateKey returns private function _privateKeyToArray ( privateKey ) { return [ _base64ToBigInt ( privateKey . modulus ) , _base64ToBigInt ( privateKey . privateExponent ) , _base64ToBigInt ( privateKey . primeP ) , _base64ToBigInt ( privateKey . primeQ ) , _base64ToBigInt ( privateKey . primeExponentP ) , _base64ToBigInt ( privateKey . primeExponentQ ) , _base64ToBigInt ( privateKey . crtCoefficient ) , ] ; } function _arrayToPublicKey ( publicKey ) { return { version : 0 , keyLength : RSA_KEY_LENGTH_BITS , modulus : int8ArrayToBase64 ( new Int8Array ( publicKey [ 0 ] . toByteArray ( ) ) ) , publicExponent : RSA_PUBLIC_EXPONENT , } ; } function _arrayToPrivateKey ( privateKey ) { return { version : 0 , keyLength : RSA_KEY_LENGTH_BITS , modulus : int8ArrayToBase64 ( new Int8Array ( privateKey [ 0 ] . toByteArray ( ) ) ) , privateExponent : int8ArrayToBase64 ( new Int8Array ( privateKey [ 1 ] . toByteArray ( ) ) ) , primeP : int8ArrayToBase64 ( new Int8Array ( privateKey [ 2 ] . toByteArray ( ) ) ) , primeQ : int8ArrayToBase64 ( new Int8Array ( privateKey [ 3 ] . toByteArray ( ) ) ) , primeExponentP : int8ArrayToBase64 ( new Int8Array ( privateKey [ 4 ] . toByteArray ( ) ) ) , primeExponentQ : int8ArrayToBase64 ( new Int8Array ( privateKey [ 5 ] . toByteArray ( ) ) ) , crtCoefficient : int8ArrayToBase64 ( new Int8Array ( privateKey [ 6 ] . toByteArray ( ) ) ) , } ; } function _base64ToBigInt ( base64 ) { return parseBigInt ( base64ToHex ( base64 ) , 16 ) ; } param { string } string return { string } function _hexLen ( string ) { var hexLen = string . length . toString ( 16 ) ; while ( hexLen . length < 4 ) { hexLen = "0" + hexLen ; } return hexLen ; } export function _keyArrayToHex ( key ) { var hex = "" ; for ( var i = 0 ; i < key . length ; i ++ ) { var param = key [ i ] . toString ( 16 ) ; if ( param . length % 2 === 1 ) { param = "0" + param ; } hex += _hexLen ( param ) + param ; } return hex ; } function _hexToKeyArray ( hex ) { try { var key = [ ] ; var pos = 0 ; while ( pos < hex . length ) { var nextParamLen = parseInt ( hex . substring ( pos , pos + 4 ) , 16 ) ; pos += 4 ; key . push ( parseBigInt ( hex . substring ( pos , pos + nextParamLen ) , 16 ) ) ; pos += nextParamLen ; } _validateKeyLength ( key ) ; return key ; } catch ( e ) { throw new CryptoError ( "hex to rsa key failed" , e as Error ) ; } } function _validateKeyLength ( key ) { if ( key . length !== 1 && key . length !== 7 ) { throw new Error ( "invalid key params" ) ; } if ( key [ 0 ] . bitLength ( ) < RSA_KEY_LENGTH_BITS - 1 || key [ 0 ] . bitLength ( ) > RSA_KEY_LENGTH_BITS ) { throw new Error ( "invalid key length, expected: around " + RSA_KEY_LENGTH_BITS + ", but was: " + key [ 0 ] . bitLength ( ) ) ; } } export function privateKeyToHex ( privateKey ) { return _keyArrayToHex ( _privateKeyToArray ( privateKey ) ) ; } export function publicKeyToHex ( publicKey ) { return _keyArrayToHex ( _publicKeyToArray ( publicKey ) ) ; } export function hexToPrivateKey ( privateKeyHex ) { return _arrayToPrivateKey ( _hexToKeyArray ( privateKeyHex ) ) ; } export function hexToPublicKey ( publicKeyHex ) { return _arrayToPublicKey ( _hexToKeyArray ( publicKeyHex ) ) ; }