mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 05:43:41 +00:00
Replace webcrypto RSA with PureCrypto RSA (#17742)
This commit is contained in:
@@ -91,7 +91,7 @@ export abstract class CryptoFunctionService {
|
|||||||
abstract rsaEncrypt(
|
abstract rsaEncrypt(
|
||||||
data: Uint8Array,
|
data: Uint8Array,
|
||||||
publicKey: Uint8Array,
|
publicKey: Uint8Array,
|
||||||
algorithm: "sha1" | "sha256",
|
algorithm: "sha1",
|
||||||
): Promise<Uint8Array>;
|
): Promise<Uint8Array>;
|
||||||
/**
|
/**
|
||||||
* @deprecated HAZMAT WARNING: DO NOT USE THIS FOR NEW CODE. Implement low-level crypto operations
|
* @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(
|
abstract rsaDecrypt(
|
||||||
data: Uint8Array,
|
data: Uint8Array,
|
||||||
privateKey: Uint8Array,
|
privateKey: Uint8Array,
|
||||||
algorithm: "sha1" | "sha256",
|
algorithm: "sha1",
|
||||||
): Promise<Uint8Array>;
|
): Promise<Uint8Array>;
|
||||||
abstract rsaExtractPublicKey(privateKey: Uint8Array): 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
|
* 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.");
|
throw new Error("[Encrypt service] rsaDecrypt: No data provided for decryption.");
|
||||||
}
|
}
|
||||||
|
|
||||||
let algorithm: "sha1" | "sha256";
|
|
||||||
switch (data.encryptionType) {
|
switch (data.encryptionType) {
|
||||||
case EncryptionType.Rsa2048_OaepSha1_B64:
|
case EncryptionType.Rsa2048_OaepSha1_B64:
|
||||||
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
|
case EncryptionType.Rsa2048_OaepSha1_HmacSha256_B64:
|
||||||
algorithm = "sha1";
|
|
||||||
break;
|
|
||||||
case EncryptionType.Rsa2048_OaepSha256_B64:
|
|
||||||
case EncryptionType.Rsa2048_OaepSha256_HmacSha256_B64:
|
|
||||||
algorithm = "sha256";
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
throw new Error("Invalid encryption type.");
|
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.");
|
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", () => {
|
describe("rsaGenerateKeyPair", () => {
|
||||||
testRsaGenerateKeyPair(1024);
|
|
||||||
testRsaGenerateKeyPair(2048);
|
testRsaGenerateKeyPair(2048);
|
||||||
|
|
||||||
// Generating 4096 bit keys can be slow. Commenting it out to save CI.
|
// 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(
|
it(
|
||||||
"should successfully generate a " + length + " bit key pair",
|
"should successfully generate a " + length + " bit key pair",
|
||||||
async () => {
|
async () => {
|
||||||
|
|||||||
@@ -263,33 +263,19 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
|||||||
async rsaEncrypt(
|
async rsaEncrypt(
|
||||||
data: Uint8Array,
|
data: Uint8Array,
|
||||||
publicKey: Uint8Array,
|
publicKey: Uint8Array,
|
||||||
algorithm: "sha1" | "sha256",
|
_algorithm: "sha1",
|
||||||
): Promise<Uint8Array> {
|
): Promise<Uint8Array> {
|
||||||
// Note: Edge browser requires that we specify name and hash for both key import and decrypt.
|
await SdkLoadService.Ready;
|
||||||
// We cannot use the proper types here.
|
return PureCrypto.rsa_encrypt_data(data, publicKey);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async rsaDecrypt(
|
async rsaDecrypt(
|
||||||
data: Uint8Array,
|
data: Uint8Array,
|
||||||
privateKey: Uint8Array,
|
privateKey: Uint8Array,
|
||||||
algorithm: "sha1" | "sha256",
|
_algorithm: "sha1",
|
||||||
): Promise<Uint8Array> {
|
): Promise<Uint8Array> {
|
||||||
// Note: Edge browser requires that we specify name and hash for both key import and decrypt.
|
await SdkLoadService.Ready;
|
||||||
// We cannot use the proper types here.
|
return PureCrypto.rsa_decrypt_data(data, privateKey);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async rsaExtractPublicKey(privateKey: Uint8Array): Promise<UnsignedPublicKey> {
|
async rsaExtractPublicKey(privateKey: Uint8Array): Promise<UnsignedPublicKey> {
|
||||||
@@ -297,6 +283,13 @@ export class WebCryptoFunctionService implements CryptoFunctionService {
|
|||||||
return PureCrypto.rsa_extract_public_key(privateKey) as UnsignedPublicKey;
|
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> {
|
async aesGenerateKey(bitLength = 128 | 192 | 256 | 512): Promise<CsprngArray> {
|
||||||
if (bitLength === 512) {
|
if (bitLength === 512) {
|
||||||
// 512 bit keys are not supported in WebCrypto, so we concat two 256 bit keys
|
// 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;
|
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> {
|
randomBytes(length: number): Promise<CsprngArray> {
|
||||||
const arr = new Uint8Array(length);
|
const arr = new Uint8Array(length);
|
||||||
this.crypto.getRandomValues(arr);
|
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 { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import { EcbDecryptParameters } from "@bitwarden/common/platform/models/domain/decrypt-parameters";
|
import { EcbDecryptParameters } from "@bitwarden/common/platform/models/domain/decrypt-parameters";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
|
|
||||||
import { NodeCryptoFunctionService } from "./node-crypto-function.service";
|
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 =
|
const RsaPublicKey =
|
||||||
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP" +
|
"MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAl0Vawl/toXzkEvB82FEtqHP" +
|
||||||
"4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP" +
|
"4xlU2ab/v0crqIfXfIoWF/XXdHGIdrZeilnRXPPJT1B9dTsasttEZNnua/0Rek/cjNDHtzT52irfoZYS7X6HNIfOi54Q+egP" +
|
||||||
@@ -37,6 +45,10 @@ const Sha512Mac =
|
|||||||
"5ea7817a0b7c5d4d9b00364ccd214669131fc17fe4aca";
|
"5ea7817a0b7c5d4d9b00364ccd214669131fc17fe4aca";
|
||||||
|
|
||||||
describe("NodeCrypto Function Service", () => {
|
describe("NodeCrypto Function Service", () => {
|
||||||
|
beforeAll(async () => {
|
||||||
|
await new TestSdkLoadService().loadAndInit();
|
||||||
|
});
|
||||||
|
|
||||||
describe("pbkdf2", () => {
|
describe("pbkdf2", () => {
|
||||||
const regular256Key = "pj9prw/OHPleXI6bRdmlaD+saJS4awrMiQsQiDjeu2I=";
|
const regular256Key = "pj9prw/OHPleXI6bRdmlaD+saJS4awrMiQsQiDjeu2I=";
|
||||||
const utf8256Key = "yqvoFXgMRmHR3QPYr5pyR4uVuoHkltv9aHUP63p8n7I=";
|
const utf8256Key = "yqvoFXgMRmHR3QPYr5pyR4uVuoHkltv9aHUP63p8n7I=";
|
||||||
@@ -279,7 +291,6 @@ describe("NodeCrypto Function Service", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe("rsaGenerateKeyPair", () => {
|
describe("rsaGenerateKeyPair", () => {
|
||||||
testRsaGenerateKeyPair(1024);
|
|
||||||
testRsaGenerateKeyPair(2048);
|
testRsaGenerateKeyPair(2048);
|
||||||
|
|
||||||
// Generating 4096 bit keys is really slow with Forge lib.
|
// 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(
|
it(
|
||||||
"should successfully generate a " + length + " bit key pair",
|
"should successfully generate a " + length + " bit key pair",
|
||||||
async () => {
|
async () => {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import * as forge from "node-forge";
|
|||||||
|
|
||||||
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
import { CryptoFunctionService } from "@bitwarden/common/key-management/crypto/abstractions/crypto-function.service";
|
||||||
import { UnsignedPublicKey } from "@bitwarden/common/key-management/types";
|
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 { EncryptionType } from "@bitwarden/common/platform/enums";
|
||||||
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
import { Utils } from "@bitwarden/common/platform/misc/utils";
|
||||||
import {
|
import {
|
||||||
@@ -12,6 +13,7 @@ import {
|
|||||||
} from "@bitwarden/common/platform/models/domain/decrypt-parameters";
|
} from "@bitwarden/common/platform/models/domain/decrypt-parameters";
|
||||||
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
import { SymmetricCryptoKey } from "@bitwarden/common/platform/models/domain/symmetric-crypto-key";
|
||||||
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
import { CsprngArray } from "@bitwarden/common/types/csprng";
|
||||||
|
import { PureCrypto } from "@bitwarden/sdk-internal";
|
||||||
|
|
||||||
export class NodeCryptoFunctionService implements CryptoFunctionService {
|
export class NodeCryptoFunctionService implements CryptoFunctionService {
|
||||||
pbkdf2(
|
pbkdf2(
|
||||||
@@ -205,72 +207,34 @@ export class NodeCryptoFunctionService implements CryptoFunctionService {
|
|||||||
return Promise.resolve(this.toUint8Buffer(decBuf));
|
return Promise.resolve(this.toUint8Buffer(decBuf));
|
||||||
}
|
}
|
||||||
|
|
||||||
rsaEncrypt(
|
async rsaEncrypt(
|
||||||
data: Uint8Array,
|
data: Uint8Array,
|
||||||
publicKey: Uint8Array,
|
publicKey: Uint8Array,
|
||||||
algorithm: "sha1" | "sha256",
|
_algorithm: "sha1",
|
||||||
): Promise<Uint8Array> {
|
): Promise<Uint8Array> {
|
||||||
if (algorithm === "sha256") {
|
await SdkLoadService.Ready;
|
||||||
throw new Error("Node crypto does not support RSA-OAEP SHA-256");
|
return PureCrypto.rsa_encrypt_data(data, publicKey);
|
||||||
}
|
|
||||||
|
|
||||||
const pem = this.toPemPublicKey(publicKey);
|
|
||||||
const decipher = crypto.publicEncrypt(pem, this.toNodeBuffer(data));
|
|
||||||
return Promise.resolve(this.toUint8Buffer(decipher));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
rsaDecrypt(
|
async rsaDecrypt(
|
||||||
data: Uint8Array,
|
data: Uint8Array,
|
||||||
privateKey: Uint8Array,
|
privateKey: Uint8Array,
|
||||||
algorithm: "sha1" | "sha256",
|
_algorithm: "sha1",
|
||||||
): Promise<Uint8Array> {
|
): Promise<Uint8Array> {
|
||||||
if (algorithm === "sha256") {
|
await SdkLoadService.Ready;
|
||||||
throw new Error("Node crypto does not support RSA-OAEP SHA-256");
|
return PureCrypto.rsa_decrypt_data(data, privateKey);
|
||||||
}
|
|
||||||
|
|
||||||
const pem = this.toPemPrivateKey(privateKey);
|
|
||||||
const decipher = crypto.privateDecrypt(pem, this.toNodeBuffer(data));
|
|
||||||
return Promise.resolve(this.toUint8Buffer(decipher));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async rsaExtractPublicKey(privateKey: Uint8Array): Promise<UnsignedPublicKey> {
|
async rsaExtractPublicKey(privateKey: Uint8Array): Promise<UnsignedPublicKey> {
|
||||||
const privateKeyByteString = Utils.fromBufferToByteString(privateKey);
|
await SdkLoadService.Ready;
|
||||||
const privateKeyAsn1 = forge.asn1.fromDer(privateKeyByteString);
|
return PureCrypto.rsa_extract_public_key(privateKey) as UnsignedPublicKey;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async rsaGenerateKeyPair(length: 1024 | 2048 | 4096): Promise<[UnsignedPublicKey, Uint8Array]> {
|
async rsaGenerateKeyPair(_length: 2048): Promise<[UnsignedPublicKey, Uint8Array]> {
|
||||||
return new Promise<[UnsignedPublicKey, Uint8Array]>((resolve, reject) => {
|
await SdkLoadService.Ready;
|
||||||
forge.pki.rsa.generateKeyPair(
|
const privateKey = PureCrypto.rsa_generate_keypair();
|
||||||
{
|
const publicKey = await this.rsaExtractPublicKey(privateKey);
|
||||||
bits: length,
|
return [publicKey, privateKey];
|
||||||
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]);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
aesGenerateKey(bitLength: 128 | 192 | 256 | 512): Promise<CsprngArray> {
|
aesGenerateKey(bitLength: 128 | 192 | 256 | 512): Promise<CsprngArray> {
|
||||||
|
|||||||
Reference in New Issue
Block a user