mirror of
https://github.com/bitwarden/browser
synced 2025-12-06 00:13:28 +00:00
Replace webcrypto RSA with PureCrypto RSA (#17742)
This commit is contained in:
@@ -91,7 +91,7 @@ export abstract class CryptoFunctionService {
|
||||
abstract rsaEncrypt(
|
||||
data: Uint8Array,
|
||||
publicKey: Uint8Array,
|
||||
algorithm: "sha1" | "sha256",
|
||||
algorithm: "sha1",
|
||||
): Promise<Uint8Array>;
|
||||
/**
|
||||
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
|
||||
@@ -100,10 +100,10 @@ export abstract class CryptoFunctionService {
|
||||
abstract rsaDecrypt(
|
||||
data: Uint8Array,
|
||||
privateKey: Uint8Array,
|
||||
algorithm: "sha1" | "sha256",
|
||||
algorithm: "sha1",
|
||||
): Promise<Uint8Array>;
|
||||
abstract rsaExtractPublicKey(privateKey: Uint8Array): Promise<Uint8Array>;
|
||||
abstract rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[Uint8Array, Uint8Array]>;
|
||||
abstract rsaGenerateKeyPair(length: 2048): Promise<[Uint8Array, Uint8Array]>;
|
||||
/**
|
||||
* Generates a key of the given length suitable for use in AES encryption
|
||||
*/
|
||||
|
||||
@@ -252,15 +252,9 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
throw new Error("[Encrypt service] rsaDecrypt: No data provided for decryption.");
|
||||
}
|
||||
|
||||
let algorithm: "sha1" | "sha256";
|
||||
switch (data.encryptionType) {
|
||||
case EncryptionType.Rsa2048_OaepSha1_B64:
|
||||
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
|
||||
algorithm = "sha1";
|
||||
break;
|
||||
case EncryptionType.Rsa2048_OaepSha256_B64:
|
||||
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
|
||||
algorithm = "sha256";
|
||||
break;
|
||||
default:
|
||||
throw new Error("Invalid encryption type.");
|
||||
@@ -270,6 +264,6 @@ export class EncryptServiceImplementation implements EncryptService {
|
||||
throw new Error("[Encrypt service] rsaDecrypt: No private key provided for decryption.");
|
||||
}
|
||||
|
||||
return this.cryptoFunctionService.rsaDecrypt(data.dataBytes, privateKey, algorithm);
|
||||
return this.cryptoFunctionService.rsaDecrypt(data.dataBytes, privateKey, "sha1");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -299,7 +299,6 @@ describe("WebCrypto Function Service", () => {
|
||||
});
|
||||
|
||||
describe("rsaGenerateKeyPair", () => {
|
||||
testRsaGenerateKeyPair(1024);
|
||||
testRsaGenerateKeyPair(2048);
|
||||
|
||||
// Generating 4096 bit keys can be slow. Commenting it out to save CI.
|
||||
@@ -495,7 +494,7 @@ function testHmac(algorithm: "sha1" | "sha256" | "sha512", mac: string) {
|
||||
});
|
||||
}
|
||||
|
||||
function testRsaGenerateKeyPair(length: 1024 | 2048 | 4096) {
|
||||
function testRsaGenerateKeyPair(length: 2048) {
|
||||
it(
|
||||
"should successfully generate a " + length + " bit key pair",
|
||||
async () => {
|
||||
|
||||
@@ -263,33 +263,19 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
async rsaEncrypt(
|
||||
data: Uint8Array,
|
||||
publicKey: Uint8Array,
|
||||
algorithm: "sha1" | "sha256",
|
||||
_algorithm: "sha1",
|
||||
): Promise<Uint8Array> {
|
||||
// Note: Edge browser requires that we specify name and hash for both key import and decrypt.
|
||||
// We cannot use the proper types here.
|
||||
const rsaParams = {
|
||||
name: "RSA-OAEP",
|
||||
hash: { name: this.toWebCryptoAlgorithm(algorithm) },
|
||||
};
|
||||
const impKey = await this.subtle.importKey("spki", publicKey, rsaParams, false, ["encrypt"]);
|
||||
const buffer = await this.subtle.encrypt(rsaParams, impKey, data);
|
||||
return new Uint8Array(buffer);
|
||||
await SdkLoadService.Ready;
|
||||
return PureCrypto.rsa_encrypt_data(data, publicKey);
|
||||
}
|
||||
|
||||
async rsaDecrypt(
|
||||
data: Uint8Array,
|
||||
privateKey: Uint8Array,
|
||||
algorithm: "sha1" | "sha256",
|
||||
_algorithm: "sha1",
|
||||
): Promise<Uint8Array> {
|
||||
// Note: Edge browser requires that we specify name and hash for both key import and decrypt.
|
||||
// We cannot use the proper types here.
|
||||
const rsaParams = {
|
||||
name: "RSA-OAEP",
|
||||
hash: { name: this.toWebCryptoAlgorithm(algorithm) },
|
||||
};
|
||||
const impKey = await this.subtle.importKey("pkcs8", privateKey, rsaParams, false, ["decrypt"]);
|
||||
const buffer = await this.subtle.decrypt(rsaParams, impKey, data);
|
||||
return new Uint8Array(buffer);
|
||||
await SdkLoadService.Ready;
|
||||
return PureCrypto.rsa_decrypt_data(data, privateKey);
|
||||
}
|
||||
|
||||
async rsaExtractPublicKey(privateKey: Uint8Array): Promise<UnsignedPublicKey> {
|
||||
@@ -297,6 +283,13 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
return PureCrypto.rsa_extract_public_key(privateKey) as UnsignedPublicKey;
|
||||
}
|
||||
|
||||
async rsaGenerateKeyPair(_length: 2048): Promise<[UnsignedPublicKey, Uint8Array]> {
|
||||
await SdkLoadService.Ready;
|
||||
const privateKey = PureCrypto.rsa_generate_keypair();
|
||||
const publicKey = await this.rsaExtractPublicKey(privateKey);
|
||||
return [publicKey, privateKey];
|
||||
}
|
||||
|
||||
async aesGenerateKey(bitLength = 128 | 192 | 256 | 512): Promise<CsprngArray> {
|
||||
if (bitLength === 512) {
|
||||
// 512 bit keys are not supported in WebCrypto, so we concat two 256 bit keys
|
||||
@@ -314,20 +307,6 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
||||
return new Uint8Array(rawKey) as CsprngArray;
|
||||
}
|
||||
|
||||
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[Uint8Array, Uint8Array]> {
|
||||
const rsaParams = {
|
||||
name: "RSA-OAEP",
|
||||
modulusLength: length,
|
||||
publicExponent: new Uint8Array([0x01, 0x00, 0x01]), // 65537
|
||||
// Have to specify some algorithm
|
||||
hash: { name: this.toWebCryptoAlgorithm("sha1") },
|
||||
};
|
||||
const keyPair = await this.subtle.generateKey(rsaParams, true, ["encrypt", "decrypt"]);
|
||||
const publicKey = await this.subtle.exportKey("spki", keyPair.publicKey);
|
||||
const privateKey = await this.subtle.exportKey("pkcs8", keyPair.privateKey);
|
||||
return [new Uint8Array(publicKey), new Uint8Array(privateKey)];
|
||||
}
|
||||
|
||||
randomBytes(length: number): Promise<CsprngArray> {
|
||||
const arr = new Uint8Array(length);
|
||||
this.crypto.getRandomValues(arr);
|
||||
|
||||
@@ -1,9 +1,17 @@
|
||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import { EcbDecryptParameters } from "@bitwarden/common/platform/models/domain/decrypt-parameters";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
|
||||
import { NodeCryptoFunctionService } from "./node-crypto-function.service";
|
||||
|
||||
class TestSdkLoadService extends SdkLoadService {
|
||||
protected override load(): Promise<void> {
|
||||
// Simulate successful WASM load
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
const RsaPublicKey =
|
||||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP" +
|
||||
"4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP" +
|
||||
@@ -37,6 +45,10 @@ const Sha512Mac =
|
||||
"5ea7817a0b7c5d4d9b00364ccd214669131fc17fe4aca";
|
||||
|
||||
describe("NodeCrypto Function Service", () => {
|
||||
beforeAll(async () => {
|
||||
await new TestSdkLoadService().loadAndInit();
|
||||
});
|
||||
|
||||
describe("pbkdf2", () => {
|
||||
const regular256Key = "pj9prw/OHPleXI6bRdmlaD+saJS4awrMiQsQiDjeu2I=";
|
||||
const utf8256Key = "yqvoFXgMRmHR3QPYr5pyR4uVuoHkltv9aHUP63p8n7I=";
|
||||
@@ -279,7 +291,6 @@ describe("NodeCrypto Function Service", () => {
|
||||
});
|
||||
|
||||
describe("rsaGenerateKeyPair", () => {
|
||||
testRsaGenerateKeyPair(1024);
|
||||
testRsaGenerateKeyPair(2048);
|
||||
|
||||
// Generating 4096 bit keys is really slow with Forge lib.
|
||||
@@ -514,7 +525,7 @@ function testCompare(fast = false) {
|
||||
});
|
||||
}
|
||||
|
||||
function testRsaGenerateKeyPair(length: 1024 | 2048 | 4096) {
|
||||
function testRsaGenerateKeyPair(length: 2048) {
|
||||
it(
|
||||
"should successfully generate a " + length + " bit key pair",
|
||||
async () => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as forge from "node-forge";
|
||||
|
||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||
import { UnsignedPublicKey } from "@bitwarden/common/key-management/types";
|
||||
import { SdkLoadService } from "@bitwarden/common/platform/abstractions/sdk/sdk-load.service";
|
||||
import { EncryptionType } from "@bitwarden/common/platform/enums";
|
||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||
import {
|
||||
@@ -12,6 +13,7 @@ import {
|
||||
} from "@bitwarden/common/platform/models/domain/decrypt-parameters";
|
||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||
import { PureCrypto } from "@bitwarden/sdk-internal";
|
||||
|
||||
export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||
pbkdf2(
|
||||
@@ -205,72 +207,34 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||
return Promise.resolve(this.toUint8Buffer(decBuf));
|
||||
}
|
||||
|
||||
rsaEncrypt(
|
||||
async rsaEncrypt(
|
||||
data: Uint8Array,
|
||||
publicKey: Uint8Array,
|
||||
algorithm: "sha1" | "sha256",
|
||||
_algorithm: "sha1",
|
||||
): Promise<Uint8Array> {
|
||||
if (algorithm === "sha256") {
|
||||
throw new Error("Node crypto does not support RSA-OAEP SHA-256");
|
||||
}
|
||||
|
||||
const pem = this.toPemPublicKey(publicKey);
|
||||
const decipher = crypto.publicEncrypt(pem, this.toNodeBuffer(data));
|
||||
return Promise.resolve(this.toUint8Buffer(decipher));
|
||||
await SdkLoadService.Ready;
|
||||
return PureCrypto.rsa_encrypt_data(data, publicKey);
|
||||
}
|
||||
|
||||
rsaDecrypt(
|
||||
async rsaDecrypt(
|
||||
data: Uint8Array,
|
||||
privateKey: Uint8Array,
|
||||
algorithm: "sha1" | "sha256",
|
||||
_algorithm: "sha1",
|
||||
): Promise<Uint8Array> {
|
||||
if (algorithm === "sha256") {
|
||||
throw new Error("Node crypto does not support RSA-OAEP SHA-256");
|
||||
}
|
||||
|
||||
const pem = this.toPemPrivateKey(privateKey);
|
||||
const decipher = crypto.privateDecrypt(pem, this.toNodeBuffer(data));
|
||||
return Promise.resolve(this.toUint8Buffer(decipher));
|
||||
await SdkLoadService.Ready;
|
||||
return PureCrypto.rsa_decrypt_data(data, privateKey);
|
||||
}
|
||||
|
||||
async rsaExtractPublicKey(privateKey: Uint8Array): Promise<UnsignedPublicKey> {
|
||||
const privateKeyByteString = Utils.fromBufferToByteString(privateKey);
|
||||
const privateKeyAsn1 = forge.asn1.fromDer(privateKeyByteString);
|
||||
const forgePrivateKey: any = forge.pki.privateKeyFromAsn1(privateKeyAsn1);
|
||||
const forgePublicKey = (forge.pki as any).setRsaPublicKey(forgePrivateKey.n, forgePrivateKey.e);
|
||||
const publicKeyAsn1 = forge.pki.publicKeyToAsn1(forgePublicKey);
|
||||
const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).data;
|
||||
const publicKeyArray = Utils.fromByteStringToArray(publicKeyByteString);
|
||||
return publicKeyArray as UnsignedPublicKey;
|
||||
await SdkLoadService.Ready;
|
||||
return PureCrypto.rsa_extract_public_key(privateKey) as UnsignedPublicKey;
|
||||
}
|
||||
|
||||
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[UnsignedPublicKey, Uint8Array]> {
|
||||
return new Promise<[UnsignedPublicKey, Uint8Array]>((resolve, reject) => {
|
||||
forge.pki.rsa.generateKeyPair(
|
||||
{
|
||||
bits: length,
|
||||
workers: -1,
|
||||
e: 0x10001, // 65537
|
||||
},
|
||||
(error, keyPair) => {
|
||||
if (error != null) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const publicKeyAsn1 = forge.pki.publicKeyToAsn1(keyPair.publicKey);
|
||||
const publicKeyByteString = forge.asn1.toDer(publicKeyAsn1).getBytes();
|
||||
const publicKey = Utils.fromByteStringToArray(publicKeyByteString);
|
||||
|
||||
const privateKeyAsn1 = forge.pki.privateKeyToAsn1(keyPair.privateKey);
|
||||
const privateKeyPkcs8 = forge.pki.wrapRsaPrivateKey(privateKeyAsn1);
|
||||
const privateKeyByteString = forge.asn1.toDer(privateKeyPkcs8).getBytes();
|
||||
const privateKey = Utils.fromByteStringToArray(privateKeyByteString);
|
||||
|
||||
resolve([publicKey as UnsignedPublicKey, privateKey]);
|
||||
},
|
||||
);
|
||||
});
|
||||
async rsaGenerateKeyPair(_length: 2048): Promise<[UnsignedPublicKey, Uint8Array]> {
|
||||
await SdkLoadService.Ready;
|
||||
const privateKey = PureCrypto.rsa_generate_keypair();
|
||||
const publicKey = await this.rsaExtractPublicKey(privateKey);
|
||||
return [publicKey, privateKey];
|
||||
}
|
||||
|
||||
aesGenerateKey(bitLength: 128 | 192 | 256 | 512): Promise<CsprngArray> {
|
||||
|
||||
Reference in New Issue
Block a user