2024-10-27 04:15:10 +08:00
import { Crypto , CryptoBuffer , Digest , CipherAlgorithm , EncryptionResult , EncryptionParameters } from '@joplin/lib/services/e2ee/types' ;
import QuickCrypto from 'react-native-quick-crypto' ;
import type { CipherGCMOptions , CipherGCM , DecipherGCM } from 'crypto' ;
import {
generateNonce as generateNonceShared ,
increaseNonce as increaseNonceShared ,
setRandomBytesImplementation ,
} from '@joplin/lib/services/e2ee/cryptoShared' ;
type DigestNameMap = Record < Digest , string >;
const digestNameMap : DigestNameMap = {
[ Digest . sha1 ] : 'sha1' ,
[ Digest . sha256 ] : 'sha256' ,
[ Digest . sha384 ] : 'sha384' ,
[ Digest . sha512 ] : 'sha512' ,
};
const pbkdf2Raw = ( password : string , salt : CryptoBuffer , iterations : number , keylen : number , digest : Digest ) : Promise < CryptoBuffer > => {
return new Promise (( resolve , reject ) => {
2024-10-30 13:47:59 -07:00
QuickCrypto . pbkdf2 ( password , salt , iterations , keylen , digest , ( error , result ) => {
2024-10-27 04:15:10 +08:00
if ( error ) {
reject ( error );
} else {
resolve ( result );
}
});
});
};
const encryptRaw = ( data : CryptoBuffer , algorithm : CipherAlgorithm , key : CryptoBuffer , iv : CryptoBuffer , authTagLength : number , associatedData : CryptoBuffer ) => {
const cipher = QuickCrypto . createCipheriv ( algorithm , key , iv , { authTagLength : authTagLength } as CipherGCMOptions ) as CipherGCM ;
cipher . setAAD ( associatedData , { plaintextLength : Buffer.byteLength ( data ) });
const encryptedData = [ cipher . update ( data ), cipher . final ()];
const authTag = cipher . getAuthTag ();
return Buffer . concat ([ encryptedData [ 0 ], encryptedData [ 1 ], authTag ]);
};
const decryptRaw = ( data : CryptoBuffer , algorithm : CipherAlgorithm , key : CryptoBuffer , iv : CryptoBuffer , authTagLength : number , associatedData : CryptoBuffer ) => {
const decipher = QuickCrypto . createDecipheriv ( algorithm , key , iv , { authTagLength : authTagLength } as CipherGCMOptions ) as DecipherGCM ;
const authTag = data . subarray ( - authTagLength );
const encryptedData = data . subarray ( 0 , data . byteLength - authTag . byteLength );
decipher . setAuthTag ( authTag );
decipher . setAAD ( associatedData , { plaintextLength : Buffer.byteLength ( data ) });
try {
return Buffer . concat ([ decipher . update ( encryptedData ), decipher . final ()]);
} catch ( error ) {
throw new Error ( `Authentication failed! ${ error } ` );
}
};
const crypto : Crypto = {
randomBytes : async ( size : number ) => {
return new Promise (( resolve , reject ) => {
QuickCrypto . randomBytes ( size , ( error , result ) => {
if ( error ) {
reject ( error );
} else {
resolve ( result );
}
});
});
},
digest : async ( algorithm : Digest , data : Uint8Array ) => {
const hash = QuickCrypto . createHash ( digestNameMap [ algorithm ]);
hash . update ( data );
return hash . digest ();
},
encrypt : async ( password : string , salt : CryptoBuffer , data : CryptoBuffer , encryptionParameters : EncryptionParameters ) => {
// Parameters in EncryptionParameters won't appear in result
const result : EncryptionResult = {
salt : salt.toString ( 'base64' ),
iv : '' ,
ct : '' , // cipherText
};
// 96 bits IV
// "For IVs, it is recommended that implementations restrict support to the length of 96 bits, to promote interoperability, efficiency, and simplicity of design." - NIST SP 800-38D
const iv = await crypto . randomBytes ( 12 );
const key = await pbkdf2Raw ( password , salt , encryptionParameters . iterationCount , encryptionParameters . keyLength , encryptionParameters . digestAlgorithm );
const encrypted = encryptRaw ( data , encryptionParameters . cipherAlgorithm , key , iv , encryptionParameters . authTagLength , encryptionParameters . associatedData );
result . iv = iv . toString ( 'base64' );
result . ct = encrypted . toString ( 'base64' );
return result ;
},
decrypt : async ( password : string , data : EncryptionResult , encryptionParameters : EncryptionParameters ) => {
const salt = Buffer . from ( data . salt , 'base64' );
const iv = Buffer . from ( data . iv , 'base64' );
const key = await pbkdf2Raw ( password , salt , encryptionParameters . iterationCount , encryptionParameters . keyLength , encryptionParameters . digestAlgorithm );
const decrypted = decryptRaw ( Buffer . from ( data . ct , 'base64' ), encryptionParameters . cipherAlgorithm , key , iv , encryptionParameters . authTagLength , encryptionParameters . associatedData );
return decrypted ;
},
encryptString : async ( password : string , salt : CryptoBuffer , data : string , encoding : BufferEncoding , encryptionParameters : EncryptionParameters ) => {
return crypto . encrypt ( password , salt , Buffer . from ( data , encoding ), encryptionParameters );
},
generateNonce : generateNonceShared ,
increaseNonce : increaseNonceShared ,
};
setRandomBytesImplementation ( crypto . randomBytes );
export default crypto ;