fork-openpgpjs/src/crypto/public_key/elliptic/ecdh.js
2021-02-28 21:45:21 +01:00

382 lines
12 KiB
JavaScript

// OpenPGP.js - An OpenPGP implementation in javascript
// Copyright (C) 2015-2016 Decentral
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 3.0 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
/**
* @fileoverview Key encryption and decryption for RFC 6637 ECDH
* @module crypto/public_key/elliptic/ecdh
* @private
*/
import nacl from '@openpgp/tweetnacl/nacl-fast-light.js';
import { Curve, jwkToRawPublic, rawPublicToJwk, privateToJwk, validateStandardParams } from './curves';
import * as aes_kw from '../../aes_kw';
import * as cipher from '../../cipher';
import { getRandomBytes } from '../../random';
import hash from '../../hash';
import enums from '../../../enums';
import util from '../../../util';
import { b64ToUint8Array } from '../../../encoding/base64';
import * as pkcs5 from '../../pkcs5';
import { keyFromPublic, keyFromPrivate, getIndutnyCurve } from './indutnyKey';
const webCrypto = util.getWebCrypto();
const nodeCrypto = util.getNodeCrypto();
/**
* Validate ECDH parameters
* @param {module:type/oid} oid - Elliptic curve object identifier
* @param {Uint8Array} Q - ECDH public point
* @param {Uint8Array} d - ECDH secret scalar
* @returns {Boolean} Whether params are valid.
* @async
*/
export async function validateParams(oid, Q, d) {
return validateStandardParams(enums.publicKey.ecdh, oid, Q, d);
}
// Build Param for ECDH algorithm (RFC 6637)
function buildEcdhParam(public_algo, oid, kdfParams, fingerprint) {
return util.concatUint8Array([
oid.write(),
new Uint8Array([public_algo]),
kdfParams.write(),
util.strToUint8Array("Anonymous Sender "),
fingerprint.subarray(0, 20)
]);
}
// Key Derivation Function (RFC 6637)
async function kdf(hash_algo, X, length, param, stripLeading = false, stripTrailing = false) {
// Note: X is little endian for Curve25519, big-endian for all others.
// This is not ideal, but the RFC's are unclear
// https://tools.ietf.org/html/draft-ietf-openpgp-rfc4880bis-02#appendix-B
let i;
if (stripLeading) {
// Work around old go crypto bug
for (i = 0; i < X.length && X[i] === 0; i++);
X = X.subarray(i);
}
if (stripTrailing) {
// Work around old OpenPGP.js bug
for (i = X.length - 1; i >= 0 && X[i] === 0; i--);
X = X.subarray(0, i + 1);
}
const digest = await hash.digest(hash_algo, util.concatUint8Array([
new Uint8Array([0, 0, 0, 1]),
X,
param
]));
return digest.subarray(0, length);
}
/**
* Generate ECDHE ephemeral key and secret from public key
*
* @param {Curve} curve - Elliptic curve object
* @param {Uint8Array} Q - Recipient public key
* @returns {{publicKey: Uint8Array, sharedKey: Uint8Array}}
* @async
*/
async function genPublicEphemeralKey(curve, Q) {
switch (curve.type) {
case 'curve25519': {
const d = await getRandomBytes(32);
const { secretKey, sharedKey } = await genPrivateEphemeralKey(curve, Q, null, d);
let { publicKey } = nacl.box.keyPair.fromSecretKey(secretKey);
publicKey = util.concatUint8Array([new Uint8Array([0x40]), publicKey]);
return { publicKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
}
case 'web':
if (curve.web && util.getWebCrypto()) {
try {
return await webPublicEphemeralKey(curve, Q);
} catch (err) {
util.printDebugError(err);
}
}
break;
case 'node':
return nodePublicEphemeralKey(curve, Q);
}
return ellipticPublicEphemeralKey(curve, Q);
}
/**
* Encrypt and wrap a session key
*
* @param {module:type/oid} oid - Elliptic curve object identifier
* @param {module:type/kdf_params} kdfParams - KDF params including cipher and algorithm to use
* @param {Uint8Array} data - Unpadded session key data
* @param {Uint8Array} Q - Recipient public key
* @param {Uint8Array} fingerprint - Recipient fingerprint
* @returns {{publicKey: Uint8Array, wrappedKey: Uint8Array}}
* @async
*/
export async function encrypt(oid, kdfParams, data, Q, fingerprint) {
const m = pkcs5.encode(data);
const curve = new Curve(oid);
const { publicKey, sharedKey } = await genPublicEphemeralKey(curve, Q);
const param = buildEcdhParam(enums.publicKey.ecdh, oid, kdfParams, fingerprint);
const cipher_algo = enums.read(enums.symmetric, kdfParams.cipher);
const Z = await kdf(kdfParams.hash, sharedKey, cipher[cipher_algo].keySize, param);
const wrappedKey = aes_kw.wrap(Z, m);
return { publicKey, wrappedKey };
}
/**
* Generate ECDHE secret from private key and public part of ephemeral key
*
* @param {Curve} curve - Elliptic curve object
* @param {Uint8Array} V - Public part of ephemeral key
* @param {Uint8Array} Q - Recipient public key
* @param {Uint8Array} d - Recipient private key
* @returns {{secretKey: Uint8Array, sharedKey: Uint8Array}}
* @async
*/
async function genPrivateEphemeralKey(curve, V, Q, d) {
if (d.length !== curve.payloadSize) {
const privateKey = new Uint8Array(curve.payloadSize);
privateKey.set(d, curve.payloadSize - d.length);
d = privateKey;
}
switch (curve.type) {
case 'curve25519': {
const secretKey = d.slice().reverse();
const sharedKey = nacl.scalarMult(secretKey, V.subarray(1));
return { secretKey, sharedKey }; // Note: sharedKey is little-endian here, unlike below
}
case 'web':
if (curve.web && util.getWebCrypto()) {
try {
return await webPrivateEphemeralKey(curve, V, Q, d);
} catch (err) {
util.printDebugError(err);
}
}
break;
case 'node':
return nodePrivateEphemeralKey(curve, V, d);
}
return ellipticPrivateEphemeralKey(curve, V, d);
}
/**
* Decrypt and unwrap the value derived from session key
*
* @param {module:type/oid} oid - Elliptic curve object identifier
* @param {module:type/kdf_params} kdfParams - KDF params including cipher and algorithm to use
* @param {Uint8Array} V - Public part of ephemeral key
* @param {Uint8Array} C - Encrypted and wrapped value derived from session key
* @param {Uint8Array} Q - Recipient public key
* @param {Uint8Array} d - Recipient private key
* @param {Uint8Array} fingerprint - Recipient fingerprint
* @returns {Uint8Array} Value derived from session key.
* @async
*/
export async function decrypt(oid, kdfParams, V, C, Q, d, fingerprint) {
const curve = new Curve(oid);
const { sharedKey } = await genPrivateEphemeralKey(curve, V, Q, d);
const param = buildEcdhParam(enums.publicKey.ecdh, oid, kdfParams, fingerprint);
const cipher_algo = enums.read(enums.symmetric, kdfParams.cipher);
let err;
for (let i = 0; i < 3; i++) {
try {
// Work around old go crypto bug and old OpenPGP.js bug, respectively.
const Z = await kdf(kdfParams.hash, sharedKey, cipher[cipher_algo].keySize, param, i === 1, i === 2);
return pkcs5.decode(aes_kw.unwrap(Z, C));
} catch (e) {
err = e;
}
}
throw err;
}
/**
* Generate ECDHE secret from private key and public part of ephemeral key using webCrypto
*
* @param {Curve} curve - Elliptic curve object
* @param {Uint8Array} V - Public part of ephemeral key
* @param {Uint8Array} Q - Recipient public key
* @param {Uint8Array} d - Recipient private key
* @returns {{secretKey: Uint8Array, sharedKey: Uint8Array}}
* @async
*/
async function webPrivateEphemeralKey(curve, V, Q, d) {
const recipient = privateToJwk(curve.payloadSize, curve.web.web, Q, d);
let privateKey = webCrypto.importKey(
"jwk",
recipient,
{
name: "ECDH",
namedCurve: curve.web.web
},
true,
["deriveKey", "deriveBits"]
);
const jwk = rawPublicToJwk(curve.payloadSize, curve.web.web, V);
let sender = webCrypto.importKey(
"jwk",
jwk,
{
name: "ECDH",
namedCurve: curve.web.web
},
true,
[]
);
[privateKey, sender] = await Promise.all([privateKey, sender]);
let S = webCrypto.deriveBits(
{
name: "ECDH",
namedCurve: curve.web.web,
public: sender
},
privateKey,
curve.web.sharedSize
);
let secret = webCrypto.exportKey(
"jwk",
privateKey
);
[S, secret] = await Promise.all([S, secret]);
const sharedKey = new Uint8Array(S);
const secretKey = b64ToUint8Array(secret.d, true);
return { secretKey, sharedKey };
}
/**
* Generate ECDHE ephemeral key and secret from public key using webCrypto
*
* @param {Curve} curve - Elliptic curve object
* @param {Uint8Array} Q - Recipient public key
* @returns {{publicKey: Uint8Array, sharedKey: Uint8Array}}
* @async
*/
async function webPublicEphemeralKey(curve, Q) {
const jwk = rawPublicToJwk(curve.payloadSize, curve.web.web, Q);
let keyPair = webCrypto.generateKey(
{
name: "ECDH",
namedCurve: curve.web.web
},
true,
["deriveKey", "deriveBits"]
);
let recipient = webCrypto.importKey(
"jwk",
jwk,
{
name: "ECDH",
namedCurve: curve.web.web
},
false,
[]
);
[keyPair, recipient] = await Promise.all([keyPair, recipient]);
let s = webCrypto.deriveBits(
{
name: "ECDH",
namedCurve: curve.web.web,
public: recipient
},
keyPair.privateKey,
curve.web.sharedSize
);
let p = webCrypto.exportKey(
"jwk",
keyPair.publicKey
);
[s, p] = await Promise.all([s, p]);
const sharedKey = new Uint8Array(s);
const publicKey = new Uint8Array(jwkToRawPublic(p));
return { publicKey, sharedKey };
}
/**
* Generate ECDHE secret from private key and public part of ephemeral key using indutny/elliptic
*
* @param {Curve} curve - Elliptic curve object
* @param {Uint8Array} V - Public part of ephemeral key
* @param {Uint8Array} d - Recipient private key
* @returns {{secretKey: Uint8Array, sharedKey: Uint8Array}}
* @async
*/
async function ellipticPrivateEphemeralKey(curve, V, d) {
const indutnyCurve = await getIndutnyCurve(curve.name);
V = keyFromPublic(indutnyCurve, V);
d = keyFromPrivate(indutnyCurve, d);
const secretKey = new Uint8Array(d.getPrivate());
const S = d.derive(V.getPublic());
const len = indutnyCurve.curve.p.byteLength();
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
return { secretKey, sharedKey };
}
/**
* Generate ECDHE ephemeral key and secret from public key using indutny/elliptic
*
* @param {Curve} curve - Elliptic curve object
* @param {Uint8Array} Q - Recipient public key
* @returns {{publicKey: Uint8Array, sharedKey: Uint8Array}}
* @async
*/
async function ellipticPublicEphemeralKey(curve, Q) {
const indutnyCurve = await getIndutnyCurve(curve.name);
const v = await curve.genKeyPair();
Q = keyFromPublic(indutnyCurve, Q);
const V = keyFromPrivate(indutnyCurve, v.privateKey);
const publicKey = v.publicKey;
const S = V.derive(Q.getPublic());
const len = indutnyCurve.curve.p.byteLength();
const sharedKey = S.toArrayLike(Uint8Array, 'be', len);
return { publicKey, sharedKey };
}
/**
* Generate ECDHE secret from private key and public part of ephemeral key using nodeCrypto
*
* @param {Curve} curve - Elliptic curve object
* @param {Uint8Array} V - Public part of ephemeral key
* @param {Uint8Array} d - Recipient private key
* @returns {{secretKey: Uint8Array, sharedKey: Uint8Array}}
* @async
*/
async function nodePrivateEphemeralKey(curve, V, d) {
const recipient = nodeCrypto.createECDH(curve.node.node);
recipient.setPrivateKey(d);
const sharedKey = new Uint8Array(recipient.computeSecret(V));
const secretKey = new Uint8Array(recipient.getPrivateKey());
return { secretKey, sharedKey };
}
/**
* Generate ECDHE ephemeral key and secret from public key using nodeCrypto
*
* @param {Curve} curve - Elliptic curve object
* @param {Uint8Array} Q - Recipient public key
* @returns {{publicKey: Uint8Array, sharedKey: Uint8Array}}
* @async
*/
async function nodePublicEphemeralKey(curve, Q) {
const sender = nodeCrypto.createECDH(curve.node.node);
sender.generateKeys();
const sharedKey = new Uint8Array(sender.computeSecret(Q));
const publicKey = new Uint8Array(sender.getPublicKey());
return { publicKey, sharedKey };
}