commit
8026ab255e
|
@ -166,9 +166,10 @@
|
|||
"dev": true
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "10.14.6",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.6.tgz",
|
||||
"integrity": "sha512-Fvm24+u85lGmV4hT5G++aht2C5I4Z4dYlWZIh62FAfFO/TfzXtPpoLI6I7AuBWkIFqZCnhFOoTT7RjjaIL5Fjg=="
|
||||
"version": "12.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
|
||||
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn": {
|
||||
"version": "6.1.1",
|
||||
|
@ -2169,7 +2170,7 @@
|
|||
"dependencies": {
|
||||
"glob": {
|
||||
"version": "7.1.3",
|
||||
"resolved": false,
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
|
||||
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
|
@ -2485,6 +2486,13 @@
|
|||
"requires": {
|
||||
"@types/node": "^10.12.15",
|
||||
"tslib": "^1.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "10.14.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.7.tgz",
|
||||
"integrity": "sha512-on4MmIDgHXiuJDELPk1NFaKVUxxCFr37tm8E9yN6rAiF5Pzp/9bBfBHkoexqRiY+hk/Z04EJU9kKEb59YqJ82A=="
|
||||
}
|
||||
}
|
||||
},
|
||||
"qs": {
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
"banner": "// Copyright (c) 2019, Peculiar Ventures, All rights reserved.",
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^5.2.6",
|
||||
"@types/node": "^10.14.6",
|
||||
"@types/node": "^12.0.2",
|
||||
"coveralls": "^3.0.3",
|
||||
"mocha": "^6.1.4",
|
||||
"nyc": "^14.1.0",
|
||||
|
|
|
@ -144,10 +144,10 @@ export class RsaCrypto {
|
|||
|
||||
const key = new RsaPrivateKey();
|
||||
key.data = Buffer.from(AsnSerializer.serialize(keyInfo));
|
||||
key.algorithm.publicExponent = new Uint8Array(asnKey.publicExponent);
|
||||
key.algorithm.modulusLength = asnKey.modulus.byteLength << 3;
|
||||
|
||||
key.algorithm = Object.assign({}, algorithm) as RsaHashedKeyAlgorithm;
|
||||
key.algorithm.publicExponent = new Uint8Array(asnKey.publicExponent);
|
||||
key.algorithm.modulusLength = asnKey.modulus.byteLength << 3;
|
||||
key.extractable = extractable;
|
||||
key.usages = keyUsages;
|
||||
|
||||
|
@ -162,10 +162,10 @@ export class RsaCrypto {
|
|||
|
||||
const key = new RsaPublicKey();
|
||||
key.data = Buffer.from(AsnSerializer.serialize(keyInfo));
|
||||
key.algorithm.publicExponent = new Uint8Array(asnKey.publicExponent);
|
||||
key.algorithm.modulusLength = asnKey.modulus.byteLength << 3;
|
||||
|
||||
key.algorithm = Object.assign({}, algorithm) as RsaHashedKeyAlgorithm;
|
||||
key.algorithm.publicExponent = new Uint8Array(asnKey.publicExponent);
|
||||
key.algorithm.modulusLength = asnKey.modulus.byteLength << 3;
|
||||
key.extractable = extractable;
|
||||
key.usages = keyUsages;
|
||||
|
||||
|
|
|
@ -1,9 +1,18 @@
|
|||
import crypto from "crypto";
|
||||
import * as core from "webcrypto-core";
|
||||
import { CryptoKey } from "../../keys";
|
||||
import { ShaCrypto } from "../sha/crypto";
|
||||
import { RsaCrypto } from "./crypto";
|
||||
import { RsaPrivateKey } from "./private_key";
|
||||
import { RsaPublicKey } from "./public_key";
|
||||
|
||||
/**
|
||||
* Source code for decrypt, encrypt, mgf1 functions is from asmcrypto module
|
||||
* https://github.com/asmcrypto/asmcrypto.js/blob/master/src/rsa/pkcs1.ts
|
||||
*
|
||||
* This code can be removed after https://github.com/nodejs/help/issues/1726 fixed
|
||||
*/
|
||||
|
||||
export class RsaOaepProvider extends core.RsaOaepProvider {
|
||||
|
||||
public async onGenerateKey(algorithm: RsaHashedKeyGenParams, extractable: boolean, keyUsages: KeyUsage[]): Promise<CryptoKeyPair> {
|
||||
|
@ -19,11 +28,113 @@ export class RsaOaepProvider extends core.RsaOaepProvider {
|
|||
}
|
||||
|
||||
public async onEncrypt(algorithm: RsaOaepParams, key: RsaPublicKey, data: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
return RsaCrypto.encrypt(algorithm, key, new Uint8Array(data));
|
||||
const dataView = new Uint8Array(data);
|
||||
const keySize = Math.ceil(key.algorithm.modulusLength >> 3);
|
||||
const hashSize = ShaCrypto.size(key.algorithm.hash) >> 3;
|
||||
const dataLength = dataView.byteLength;
|
||||
const psLength = keySize - dataLength - 2 * hashSize - 2;
|
||||
|
||||
if (dataLength > keySize - 2 * hashSize - 2) {
|
||||
throw new Error("Data too large");
|
||||
}
|
||||
|
||||
const message = new Uint8Array(keySize);
|
||||
const seed = message.subarray(1, hashSize + 1);
|
||||
const dataBlock = message.subarray(hashSize + 1);
|
||||
|
||||
dataBlock.set(dataView, hashSize + psLength + 1);
|
||||
|
||||
const labelHash = crypto.createHash(key.algorithm.hash.name.replace("-", ""))
|
||||
.update(core.BufferSourceConverter.toUint8Array(algorithm.label || new Uint8Array(0)))
|
||||
.digest();
|
||||
dataBlock.set(labelHash, 0);
|
||||
dataBlock[hashSize + psLength] = 1;
|
||||
|
||||
crypto.randomFillSync(seed);
|
||||
|
||||
const dataBlockMask = this.mgf1(key.algorithm.hash, seed, dataBlock.length);
|
||||
for (let i = 0; i < dataBlock.length; i++) {
|
||||
dataBlock[i] ^= dataBlockMask[i];
|
||||
}
|
||||
|
||||
const seedMask = this.mgf1(key.algorithm.hash, dataBlock, seed.length);
|
||||
for (let i = 0; i < seed.length; i++) {
|
||||
seed[i] ^= seedMask[i];
|
||||
}
|
||||
|
||||
if (!key.pem) {
|
||||
key.pem = `-----BEGIN PUBLIC KEY-----\n${key.data.toString("base64")}\n-----END PUBLIC KEY-----`;
|
||||
}
|
||||
|
||||
const pkcs0 = crypto.publicEncrypt({
|
||||
key: key.pem,
|
||||
padding: crypto.constants.RSA_NO_PADDING,
|
||||
}, Buffer.from(message));
|
||||
|
||||
return new Uint8Array(pkcs0).buffer;
|
||||
}
|
||||
|
||||
public async onDecrypt(algorithm: RsaOaepParams, key: RsaPrivateKey, data: ArrayBuffer): Promise<ArrayBuffer> {
|
||||
return RsaCrypto.decrypt(algorithm, key, new Uint8Array(data));
|
||||
const keySize = Math.ceil(key.algorithm.modulusLength >> 3);
|
||||
const hashSize = ShaCrypto.size(key.algorithm.hash) >> 3;
|
||||
const dataLength = data.byteLength;
|
||||
|
||||
if (dataLength !== keySize) {
|
||||
throw new Error("Bad data");
|
||||
}
|
||||
|
||||
if (!key.pem) {
|
||||
key.pem = `-----BEGIN PRIVATE KEY-----\n${key.data.toString("base64")}\n-----END PRIVATE KEY-----`;
|
||||
}
|
||||
|
||||
let pkcs0 = crypto.privateDecrypt({
|
||||
key: key.pem,
|
||||
padding: crypto.constants.RSA_NO_PADDING,
|
||||
}, Buffer.from(data));
|
||||
const z = pkcs0[0];
|
||||
const seed = pkcs0.subarray(1, hashSize + 1);
|
||||
const dataBlock = pkcs0.subarray(hashSize + 1);
|
||||
|
||||
if (z !== 0) {
|
||||
throw new Error("Decryption failed");
|
||||
}
|
||||
|
||||
const seedMask = this.mgf1(key.algorithm.hash, dataBlock, seed.length);
|
||||
for (let i = 0; i < seed.length; i++) {
|
||||
seed[i] ^= seedMask[i];
|
||||
}
|
||||
|
||||
const dataBlockMask = this.mgf1(key.algorithm.hash, seed, dataBlock.length);
|
||||
for (let i = 0; i < dataBlock.length; i++) {
|
||||
dataBlock[i] ^= dataBlockMask[i];
|
||||
}
|
||||
|
||||
const labelHash = crypto.createHash(key.algorithm.hash.name.replace("-", ""))
|
||||
.update(core.BufferSourceConverter.toUint8Array(algorithm.label || new Uint8Array(0)))
|
||||
.digest();
|
||||
for (let i = 0; i < hashSize; i++) {
|
||||
if (labelHash[i] !== dataBlock[i]) {
|
||||
throw new Error("Decryption failed");
|
||||
}
|
||||
}
|
||||
|
||||
let psEnd = hashSize;
|
||||
for (; psEnd < dataBlock.length; psEnd++) {
|
||||
const psz = dataBlock[psEnd];
|
||||
if (psz === 1) {
|
||||
break;
|
||||
}
|
||||
if (psz !== 0) {
|
||||
throw new Error("Decryption failed");
|
||||
}
|
||||
}
|
||||
if (psEnd === dataBlock.length) {
|
||||
throw new Error("Decryption failed");
|
||||
}
|
||||
|
||||
pkcs0 = dataBlock.subarray(psEnd + 1);
|
||||
|
||||
return new Uint8Array(pkcs0).buffer;
|
||||
}
|
||||
|
||||
public async onExportKey(format: KeyFormat, key: CryptoKey): Promise<JsonWebKey | ArrayBuffer> {
|
||||
|
@ -42,4 +153,37 @@ export class RsaOaepProvider extends core.RsaOaepProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* RSA MGF1
|
||||
* @param algorithm Hash algorithm
|
||||
* @param seed
|
||||
* @param length
|
||||
*/
|
||||
protected mgf1(algorithm: Algorithm, seed: Uint8Array, length: number = 0) {
|
||||
const hashSize = ShaCrypto.size(algorithm) >> 3;
|
||||
const mask = new Uint8Array(length);
|
||||
const counter = new Uint8Array(4);
|
||||
const chunks = Math.ceil(length / hashSize);
|
||||
for (let i = 0; i < chunks; i++) {
|
||||
counter[0] = i >>> 24;
|
||||
counter[1] = (i >>> 16) & 255;
|
||||
counter[2] = (i >>> 8) & 255;
|
||||
counter[3] = i & 255;
|
||||
|
||||
const submask = mask.subarray(i * hashSize);
|
||||
|
||||
let chunk = crypto.createHash(algorithm.name.replace("-", ""))
|
||||
.update(seed)
|
||||
.update(counter)
|
||||
.digest() as Uint8Array;
|
||||
if (chunk.length > submask.length) {
|
||||
chunk = chunk.subarray(0, submask.length);
|
||||
}
|
||||
|
||||
submask.set(chunk);
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,26 @@ import crypto from "crypto";
|
|||
|
||||
export class ShaCrypto {
|
||||
|
||||
/**
|
||||
* Returns size of the hash algorithm in bits
|
||||
* @param algorithm Hash algorithm
|
||||
* @throws Throws Error if an unrecognized name
|
||||
*/
|
||||
public static size(algorithm: Algorithm) {
|
||||
switch (algorithm.name.toUpperCase()) {
|
||||
case "SHA-1":
|
||||
return 160;
|
||||
case "SHA-256":
|
||||
return 256;
|
||||
case "SHA-384":
|
||||
return 384;
|
||||
case "SHA-512":
|
||||
return 512;
|
||||
default:
|
||||
throw new Error("Unrecognized name");
|
||||
}
|
||||
}
|
||||
|
||||
public static digest(algorithm: Algorithm, data: ArrayBuffer) {
|
||||
const hash = crypto.createHash(algorithm.name.replace("-", ""))
|
||||
.update(Buffer.from(data)).digest();
|
||||
|
|
|
@ -28,7 +28,7 @@ export class SubtleCrypto extends core.SubtleCrypto {
|
|||
//#region RSA
|
||||
this.providers.set(new RsaSsaProvider());
|
||||
this.providers.set(new RsaPssProvider());
|
||||
// this.providers.set(new RsaOaepProvider()); // TODO: Fix encrypt/decrypt
|
||||
this.providers.set(new RsaOaepProvider());
|
||||
//#endregion
|
||||
|
||||
//#region EC
|
||||
|
|
|
@ -197,7 +197,6 @@ context("RSA", () => {
|
|||
generateKey: ["SHA-1", "SHA-256", "SHA-384", "SHA-512"].map((hash) => {
|
||||
return {
|
||||
name: hash,
|
||||
skip: true,
|
||||
algorithm: {
|
||||
name: "RSA-OAEP",
|
||||
hash,
|
||||
|
@ -211,7 +210,6 @@ context("RSA", () => {
|
|||
encrypt: [
|
||||
{
|
||||
name: "with label",
|
||||
skip: true,
|
||||
algorithm: {
|
||||
name: "RSA-OAEP",
|
||||
label: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]),
|
||||
|
@ -257,12 +255,11 @@ context("RSA", () => {
|
|||
},
|
||||
{
|
||||
name: "without label",
|
||||
skip: true,
|
||||
algorithm: {
|
||||
name: "RSA-OAEP",
|
||||
} as RsaOaepParams,
|
||||
data: new Uint8Array([1, 2, 3, 4, 5, 6, 7, 8]),
|
||||
encData: Convert.FromBase64("aHu8PBZuctYecfINKgUdB8gBoLyUUFxTZDTzTHUk9KKxtYywYml48HoijBG5DyaIWUUbOIdPgap9C8pFG2iYShQnE9Aj3gzKLHacBbFw1P79+Ei/Tm0j/THiXqCplBZC4dIp4jhTDepmdrlXZcY0slmjG+h8h8TpSmWKP3pEGGk="),
|
||||
encData: Convert.FromBase64("NcsyyVE/y4Z1K5bWGElWAkvlN+jWpfgPtcytlydWUUz4RqFeW5w6KA1cQMHy3eNh920YXDjsLSYHe6Dz1CEqjIKkHS9HBuOhLA39yUArOu/fmn1lMnwb9N9roTxHDxpgY3y98DXEVkAKU4Py0rlzJLVazDV/+1YcbzFLCSKUNaI="),
|
||||
key: {
|
||||
publicKey: {
|
||||
format: "jwk",
|
||||
|
|
Reference in New Issue