mirror of https://github.com/status-im/js-waku.git
Remove ecies-geth (#598)
* test: specify encryption method Makes debugging easier. * Fix log typo * Remove ecies-geth Start removal of elliptic dependency and move towards exclusive usage to CryptoSubtle.
This commit is contained in:
parent
ad5b3ddc7f
commit
2798376776
|
@ -19,6 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- Handle errors thrown by `bytesToUtf8`.
|
||||
|
||||
### Removed
|
||||
|
||||
- Removed `ecies-geth` dependency.
|
||||
|
||||
## [0.18.0] - 2022-02-24
|
||||
|
||||
### Changed
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
"@ethersproject/rlp": "^5.5.0",
|
||||
"debug": "^4.3.1",
|
||||
"dns-query": "^0.8.0",
|
||||
"ecies-geth": "^1.5.2",
|
||||
"hi-base32": "^0.5.1",
|
||||
"it-concat": "^2.0.0",
|
||||
"it-length-prefixed": "^5.0.2",
|
||||
|
@ -4319,15 +4318,6 @@
|
|||
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
|
||||
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
|
||||
},
|
||||
"node_modules/ecies-geth": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/ecies-geth/-/ecies-geth-1.6.0.tgz",
|
||||
"integrity": "sha512-lTfWFECCaxxtSinZIYRy/HxovXlEHdA6Y2K6qwsdg/l9y2CnZZopKQFQzvnGUUMMBMe0JIlNVYSKbLXpV+Tubw==",
|
||||
"dependencies": {
|
||||
"elliptic": "^6.5.4",
|
||||
"secp256k1": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
|
@ -16178,15 +16168,6 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"ecies-geth": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/ecies-geth/-/ecies-geth-1.6.0.tgz",
|
||||
"integrity": "sha512-lTfWFECCaxxtSinZIYRy/HxovXlEHdA6Y2K6qwsdg/l9y2CnZZopKQFQzvnGUUMMBMe0JIlNVYSKbLXpV+Tubw==",
|
||||
"requires": {
|
||||
"elliptic": "^6.5.4",
|
||||
"secp256k1": "^4.0.2"
|
||||
}
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
|
|
|
@ -63,7 +63,6 @@
|
|||
"@ethersproject/rlp": "^5.5.0",
|
||||
"debug": "^4.3.1",
|
||||
"dns-query": "^0.8.0",
|
||||
"ecies-geth": "^1.5.2",
|
||||
"hi-base32": "^0.5.1",
|
||||
"it-concat": "^2.0.0",
|
||||
"it-length-prefixed": "^5.0.2",
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
import nodeCrypto from "crypto";
|
||||
|
||||
// IE 11
|
||||
declare global {
|
||||
interface Window {
|
||||
msCrypto?: Crypto;
|
||||
}
|
||||
|
||||
interface Crypto {
|
||||
webkitSubtle?: SubtleCrypto;
|
||||
}
|
||||
}
|
||||
|
||||
const crypto = window.crypto || window.msCrypto || nodeCrypto.webcrypto;
|
||||
const subtle: SubtleCrypto = crypto.subtle || crypto.webkitSubtle;
|
||||
|
||||
if (subtle === undefined) {
|
||||
throw new Error("crypto and/or subtle api unavailable");
|
||||
}
|
||||
|
||||
export { crypto, subtle };
|
||||
|
||||
export function randomBytes(size: number): Uint8Array {
|
||||
return crypto.getRandomValues(new Uint8Array(size));
|
||||
}
|
||||
|
||||
export function sha256(msg: ArrayBufferLike): Promise<ArrayBuffer> {
|
||||
return subtle.digest({ name: "SHA-256" }, msg);
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
import * as secp from "@noble/secp256k1";
|
||||
|
||||
import { randomBytes, sha256, subtle } from "../crypto";
|
||||
import { hexToBytes } from "../utils";
|
||||
|
||||
/**
|
||||
* HKDF as implemented in go-ethereum.
|
||||
*/
|
||||
function kdf(secret: Uint8Array, outputLength: number): Promise<Uint8Array> {
|
||||
let ctr = 1;
|
||||
let written = 0;
|
||||
let willBeResult = Promise.resolve(new Uint8Array());
|
||||
while (written < outputLength) {
|
||||
const counters = new Uint8Array([ctr >> 24, ctr >> 16, ctr >> 8, ctr]);
|
||||
const countersSecret = new Uint8Array(counters.length + secret.length);
|
||||
countersSecret.set(counters, 0);
|
||||
countersSecret.set(secret, counters.length);
|
||||
const willBeHashResult = sha256(countersSecret);
|
||||
willBeResult = willBeResult.then((result) =>
|
||||
willBeHashResult.then((hashResult) => {
|
||||
const _hashResult = new Uint8Array(hashResult);
|
||||
const _res = new Uint8Array(result.length + _hashResult.length);
|
||||
_res.set(result, 0);
|
||||
_res.set(_hashResult, result.length);
|
||||
return _res;
|
||||
})
|
||||
);
|
||||
written += 32;
|
||||
ctr += 1;
|
||||
}
|
||||
return willBeResult;
|
||||
}
|
||||
|
||||
function aesCtrEncrypt(
|
||||
counter: Uint8Array,
|
||||
key: ArrayBufferLike,
|
||||
data: ArrayBufferLike
|
||||
): Promise<Uint8Array> {
|
||||
return subtle
|
||||
.importKey("raw", key, "AES-CTR", false, ["encrypt"])
|
||||
.then((cryptoKey) =>
|
||||
subtle.encrypt(
|
||||
{ name: "AES-CTR", counter: counter, length: 128 },
|
||||
cryptoKey,
|
||||
data
|
||||
)
|
||||
)
|
||||
.then((bytes) => new Uint8Array(bytes));
|
||||
}
|
||||
|
||||
function aesCtrDecrypt(
|
||||
counter: Uint8Array,
|
||||
key: ArrayBufferLike,
|
||||
data: ArrayBufferLike
|
||||
): Promise<Uint8Array> {
|
||||
return subtle
|
||||
.importKey("raw", key, "AES-CTR", false, ["decrypt"])
|
||||
.then((cryptoKey) =>
|
||||
subtle.decrypt(
|
||||
{ name: "AES-CTR", counter: counter, length: 128 },
|
||||
cryptoKey,
|
||||
data
|
||||
)
|
||||
)
|
||||
.then((bytes) => new Uint8Array(bytes));
|
||||
}
|
||||
|
||||
function hmacSha256Sign(
|
||||
key: ArrayBufferLike,
|
||||
msg: ArrayBufferLike
|
||||
): PromiseLike<Uint8Array> {
|
||||
const algorithm = { name: "HMAC", hash: { name: "SHA-256" } };
|
||||
return subtle
|
||||
.importKey("raw", key, algorithm, false, ["sign"])
|
||||
.then((cryptoKey) => subtle.sign(algorithm, cryptoKey, msg))
|
||||
.then((bytes) => new Uint8Array(bytes));
|
||||
}
|
||||
|
||||
function hmacSha256Verify(
|
||||
key: ArrayBufferLike,
|
||||
msg: ArrayBufferLike,
|
||||
sig: ArrayBufferLike
|
||||
): Promise<boolean> {
|
||||
const algorithm = { name: "HMAC", hash: { name: "SHA-256" } };
|
||||
const _key = subtle.importKey("raw", key, algorithm, false, ["verify"]);
|
||||
return _key.then((cryptoKey) =>
|
||||
subtle.verify(algorithm, cryptoKey, sig, msg)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive shared secret for given private and public keys.
|
||||
*
|
||||
* @param privateKeyA Sender's private key (32 bytes)
|
||||
* @param publicKeyB Recipient's public key (65 bytes)
|
||||
* @returns A promise that resolves with the derived shared secret (Px, 32 bytes)
|
||||
* @throws Error If arguments are invalid
|
||||
*/
|
||||
function derive(privateKeyA: Uint8Array, publicKeyB: Uint8Array): Uint8Array {
|
||||
if (privateKeyA.length !== 32) {
|
||||
throw new Error(
|
||||
`Bad private key, it should be 32 bytes but it's actually ${privateKeyA.length} bytes long`
|
||||
);
|
||||
} else if (publicKeyB.length !== 65) {
|
||||
throw new Error(
|
||||
`Bad public key, it should be 65 bytes but it's actually ${publicKeyB.length} bytes long`
|
||||
);
|
||||
} else if (publicKeyB[0] !== 4) {
|
||||
throw new Error("Bad public key, a valid public key would begin with 4");
|
||||
} else {
|
||||
const px = secp.getSharedSecret(privateKeyA, publicKeyB, true);
|
||||
// Remove the compression prefix
|
||||
return new Uint8Array(hexToBytes(px).slice(1));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Encrypt message for given recipient's public key.
|
||||
*
|
||||
* @param publicKeyTo Recipient's public key (65 bytes)
|
||||
* @param msg The message being encrypted
|
||||
* @return A promise that resolves with the ECIES structure serialized
|
||||
*/
|
||||
export async function encrypt(
|
||||
publicKeyTo: Uint8Array,
|
||||
msg: Uint8Array
|
||||
): Promise<Uint8Array> {
|
||||
const ephemPrivateKey = randomBytes(32);
|
||||
|
||||
const sharedPx = await derive(ephemPrivateKey, publicKeyTo);
|
||||
|
||||
const hash = await kdf(sharedPx, 32);
|
||||
|
||||
const iv = randomBytes(16);
|
||||
const encryptionKey = hash.slice(0, 16);
|
||||
const cipherText = await aesCtrEncrypt(iv, encryptionKey, msg);
|
||||
|
||||
const ivCipherText = new Uint8Array(iv.length + cipherText.length);
|
||||
ivCipherText.set(iv, 0);
|
||||
ivCipherText.set(cipherText, iv.length);
|
||||
|
||||
const macKey = await sha256(hash.slice(16));
|
||||
const hmac = await hmacSha256Sign(macKey, ivCipherText);
|
||||
const ephemPublicKey = secp.getPublicKey(ephemPrivateKey, false);
|
||||
|
||||
const cipher = new Uint8Array(
|
||||
ephemPublicKey.length + ivCipherText.length + hmac.length
|
||||
);
|
||||
let index = 0;
|
||||
cipher.set(ephemPublicKey, index);
|
||||
index += ephemPublicKey.length;
|
||||
cipher.set(ivCipherText, index);
|
||||
index += ivCipherText.length;
|
||||
cipher.set(hmac, index);
|
||||
return cipher;
|
||||
}
|
||||
|
||||
const metaLength = 1 + 64 + 16 + 32;
|
||||
|
||||
/**
|
||||
* Decrypt message using given private key.
|
||||
*
|
||||
* @param privateKey A 32-byte private key of recipient of the message
|
||||
* @param encrypted ECIES serialized structure (result of ECIES encryption)
|
||||
* @returns The clear text
|
||||
* @throws Error If decryption fails
|
||||
*/
|
||||
export async function decrypt(
|
||||
privateKey: Uint8Array,
|
||||
encrypted: Uint8Array
|
||||
): Promise<Uint8Array> {
|
||||
if (encrypted.length <= metaLength) {
|
||||
throw new Error(
|
||||
`Invalid Ciphertext. Data is too small. It should ba at least ${metaLength} bytes`
|
||||
);
|
||||
} else if (encrypted[0] !== 4) {
|
||||
throw new Error(
|
||||
`Not a valid ciphertext. It should begin with 4 but actually begin with ${encrypted[0]}`
|
||||
);
|
||||
} else {
|
||||
// deserialize
|
||||
const ephemPublicKey = encrypted.slice(0, 65);
|
||||
const cipherTextLength = encrypted.length - metaLength;
|
||||
const iv = encrypted.slice(65, 65 + 16);
|
||||
const cipherAndIv = encrypted.slice(65, 65 + 16 + cipherTextLength);
|
||||
const ciphertext = cipherAndIv.slice(16);
|
||||
const msgMac = encrypted.slice(65 + 16 + cipherTextLength);
|
||||
|
||||
// check HMAC
|
||||
const px = derive(privateKey, ephemPublicKey);
|
||||
const hash = await kdf(px, 32);
|
||||
const [encryptionKey, macKey] = await sha256(hash.slice(16)).then(
|
||||
(macKey) => [hash.slice(0, 16), macKey]
|
||||
);
|
||||
|
||||
if (!(await hmacSha256Verify(macKey, cipherAndIv, msgMac))) {
|
||||
throw new Error("Incorrect MAC");
|
||||
}
|
||||
|
||||
return aesCtrDecrypt(iv, encryptionKey, ciphertext);
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ import {
|
|||
getPublicKey,
|
||||
} from "./version_1";
|
||||
|
||||
import { WakuMessage } from "./index";
|
||||
import { DecryptionMethod, WakuMessage } from "./index";
|
||||
|
||||
const dbg = debug("waku:test:message");
|
||||
|
||||
|
@ -66,7 +66,9 @@ describe("Waku Message [node only]", function () {
|
|||
|
||||
const privateKey = generatePrivateKey();
|
||||
|
||||
waku.relay.addDecryptionKey(privateKey);
|
||||
waku.relay.addDecryptionKey(privateKey, {
|
||||
method: DecryptionMethod.Asymmetric,
|
||||
});
|
||||
|
||||
const receivedMsgPromise: Promise<WakuMessage> = new Promise(
|
||||
(resolve) => {
|
||||
|
@ -132,7 +134,9 @@ describe("Waku Message [node only]", function () {
|
|||
dbg("Generate symmetric key");
|
||||
const symKey = generateSymmetricKey();
|
||||
|
||||
waku.relay.addDecryptionKey(symKey);
|
||||
waku.relay.addDecryptionKey(symKey, {
|
||||
method: DecryptionMethod.Symmetric,
|
||||
});
|
||||
|
||||
const receivedMsgPromise: Promise<WakuMessage> = new Promise(
|
||||
(resolve) => {
|
||||
|
|
|
@ -181,7 +181,7 @@ export class WakuMessage {
|
|||
return await version_1.decryptAsymmetric(payload, key);
|
||||
} catch (e) {
|
||||
dbg(
|
||||
"Failed to decrypt message using symmetric encryption despite decryption method being specified",
|
||||
"Failed to decrypt message using asymmetric encryption despite decryption method being specified",
|
||||
e
|
||||
);
|
||||
return;
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { Buffer } from "buffer";
|
||||
import * as crypto from "crypto";
|
||||
|
||||
import * as ecies from "ecies-geth";
|
||||
import { keccak256 } from "js-sha3";
|
||||
import * as secp256k1 from "secp256k1";
|
||||
|
||||
import { hexToBytes } from "../utils";
|
||||
|
||||
import * as ecies from "./ecies";
|
||||
import { IvSize, symmetric, SymmetricKeySize } from "./symmetric";
|
||||
|
||||
const FlagsLength = 1;
|
||||
|
|
|
@ -200,6 +200,7 @@ describe("Waku Relay [node only]", () => {
|
|||
});
|
||||
|
||||
await waku1.relay.send(encryptedAsymmetricMessage);
|
||||
await delay(200);
|
||||
await waku1.relay.send(encryptedSymmetricMessage);
|
||||
|
||||
while (msgs.length < 2) {
|
||||
|
|
Loading…
Reference in New Issue