padloc/packages/core/src/stub-crypto-provider.ts

146 lines
5.2 KiB
TypeScript

import {
CryptoProvider,
PBKDF2Params,
AESKey,
RSAPublicKey,
RSAPrivateKey,
HMACKey,
SymmetricKey,
AESKeyParams,
RSAKeyParams,
HMACParams,
HMACKeyParams,
AESEncryptionParams,
RSAEncryptionParams,
HashParams,
RSASigningParams,
} from "./crypto";
import { concatBytes as concat, equalBytes as equal } from "./encoding";
import { Err, ErrorCode } from "./error";
/**
* StubCryptoProvider is a stub implementation of the [[CryptoProvider]]
* interface mainly used for testing. All methods merely emulate the behavior
* of an actual implementation in a way that makes it compatible for use
* with the rest of the **@padloc/core** package. Needless to say, this
* class is **NOT SECURE AND SHOULD NEVER BE USED IN A PRODUCTION ENVIRONMENT**.
*/
export class StubCryptoProvider implements CryptoProvider {
async randomBytes(n: number): Promise<Uint8Array> {
const bytes = new Uint8Array(n);
for (let i = 0; i < n; i++) {
bytes[i] = Math.random() * 255;
}
return bytes;
}
async hash(input: Uint8Array, _params: HashParams): Promise<Uint8Array> {
return input.slice(0, 32);
}
generateKey(params: AESKeyParams): Promise<AESKey>;
generateKey(params: RSAKeyParams): Promise<{ privateKey: RSAPrivateKey; publicKey: RSAPublicKey }>;
generateKey(params: HMACKeyParams): Promise<HMACKey>;
async generateKey(params: AESKeyParams | RSAKeyParams | HMACKeyParams) {
switch (params.algorithm) {
case "AES":
case "HMAC":
return this.randomBytes(params.keySize / 8);
case "RSA":
const key = await this.randomBytes(32);
return {
publicKey: key,
privateKey: key,
};
}
}
async deriveKey(password: Uint8Array, params: PBKDF2Params): Promise<SymmetricKey> {
const bytes = new Uint8Array(params.keySize);
bytes.set(concat([password, params.salt]));
return bytes.slice(0, 32);
}
encrypt(key: AESKey, data: Uint8Array, params: AESEncryptionParams): Promise<Uint8Array>;
encrypt(publicKey: RSAPublicKey, data: Uint8Array, params: RSAEncryptionParams): Promise<Uint8Array>;
async encrypt(
key: AESKey | RSAPublicKey,
data: Uint8Array,
params: AESEncryptionParams | RSAEncryptionParams
): Promise<Uint8Array> {
switch (params.algorithm) {
case "AES-GCM":
return concat([key, params.iv, params.additionalData, data]);
case "RSA-OAEP":
return concat([key, data]);
default:
throw new Err(ErrorCode.NOT_SUPPORTED);
}
}
decrypt(key: AESKey, data: Uint8Array, params: AESEncryptionParams): Promise<Uint8Array>;
decrypt(publicKey: RSAPublicKey, data: Uint8Array, params: RSAEncryptionParams): Promise<Uint8Array>;
async decrypt(
key: AESKey | RSAPublicKey,
data: Uint8Array,
params: AESEncryptionParams | RSAEncryptionParams
): Promise<Uint8Array> {
if (params.algorithm.startsWith("AES")) {
params = params as AESEncryptionParams;
const keyLength = key.length;
const ivLength = params.iv.length;
const adataLength = params.additionalData.length;
const extractedKey = data.slice(0, keyLength);
const iv = data.slice(keyLength, keyLength + ivLength);
const adata = data.slice(keyLength + ivLength, keyLength + ivLength + adataLength);
if (!equal(key, extractedKey) || !equal(iv, params.iv) || !equal(adata, params.additionalData)) {
throw new Err(ErrorCode.DECRYPTION_FAILED);
}
return data.slice(keyLength + adataLength + ivLength);
} else {
const keyLength = key.length;
const extractedKey = data.slice(0, keyLength);
if (!equal(key, extractedKey)) {
throw new Err(ErrorCode.DECRYPTION_FAILED);
}
return data.slice(keyLength);
}
}
async fingerprint(key: RSAPublicKey): Promise<Uint8Array> {
return key;
}
async sign(key: HMACKey, data: Uint8Array, params: HMACParams): Promise<Uint8Array>;
async sign(key: RSAPrivateKey, data: Uint8Array, params: RSASigningParams): Promise<Uint8Array>;
async sign(
key: HMACKey | RSAPrivateKey,
data: Uint8Array,
_params: HMACParams | RSASigningParams
): Promise<Uint8Array> {
return concat([key, data]);
}
async verify(key: HMACKey, signature: Uint8Array, data: Uint8Array, params: HMACParams): Promise<boolean>;
async verify(
key: RSAPrivateKey,
signature: Uint8Array,
data: Uint8Array,
params: RSASigningParams
): Promise<boolean>;
async verify(
key: HMACKey | RSAPrivateKey,
signature: Uint8Array,
data: Uint8Array,
_params: HMACParams | RSASigningParams
): Promise<boolean> {
const keyLength = key.length;
const extractedKey = signature.slice(0, keyLength);
const extractedData = signature.slice(keyLength);
return equal(key, extractedKey) && equal(data, extractedData);
}
}