Add explicit key type parameter in openpgp.generateKey (#1179)

- Changes `openpgp.generateKey` to accept an explicit `type` parameter,
  instead of inferring its value from the `curve` or `rsaBits` params
- Introduces `config.minRsaBits` to set minimum key size of RSA key generation
This commit is contained in:
larabr 2021-01-04 17:52:38 +01:00 committed by Daniel Huigens
parent 92887a0948
commit 724775816f
12 changed files with 251 additions and 114 deletions

6
openpgp.d.ts vendored
View File

@ -310,6 +310,7 @@ export namespace config {
let ignoreMdcError: boolean; let ignoreMdcError: boolean;
let checksumRequired: boolean; let checksumRequired: boolean;
let rsaBlinding: boolean; let rsaBlinding: boolean;
let minRsaBits: number;
let passwordCollisionCheck: boolean; let passwordCollisionCheck: boolean;
let revocationsExpire: boolean; let revocationsExpire: boolean;
let useNative: boolean; let useNative: boolean;
@ -621,9 +622,10 @@ export type EllipticCurveName = 'ed25519' | 'curve25519' | 'p256' | 'p384' | 'p5
interface KeyOptions { interface KeyOptions {
userIds: UserId[]; // generating a key with no user defined results in error userIds: UserId[]; // generating a key with no user defined results in error
passphrase?: string; passphrase?: string;
numBits?: number; type?: 'ecc' | 'rsa';
keyExpirationTime?: number;
curve?: EllipticCurveName; curve?: EllipticCurveName;
rsaBits?: number;
keyExpirationTime?: number;
date?: Date; date?: Date;
subkeys?: KeyOptions[]; subkeys?: KeyOptions[];
} }

View File

@ -108,6 +108,11 @@ export default {
* @property {Boolean} rsaBlinding * @property {Boolean} rsaBlinding
*/ */
rsaBlinding: true, rsaBlinding: true,
/**
* @memberof module:config
* @property {Number} minRsaBits Minimum RSA key size allowed for key generation
*/
minRsaBits: 2048,
/** /**
* Work-around for rare GPG decryption bug when encrypting with multiple passwords. * Work-around for rare GPG decryption bug when encrypting with multiple passwords.
* **Slower and slightly less secure** * **Slower and slightly less secure**

View File

@ -37,21 +37,16 @@ import { unarmor } from '../encoding/armor';
/** /**
* Generates a new OpenPGP key. Supports RSA and ECC keys. * Generates a new OpenPGP key. Supports RSA and ECC keys.
* Primary and subkey will be of same type. * By default, primary and subkeys will be of same type.
* @param {module:enums.publicKey} [options.keyType=module:enums.publicKey.rsaEncryptSign] * @param {ecc|rsa} options.type The primary key algorithm type: ECC or RSA
* To indicate what type of key to make. * @param {String} options.curve Elliptic curve for ECC keys
* RSA is 1. See {@link https://tools.ietf.org/html/rfc4880#section-9.1} * @param {Integer} options.rsaBits Number of bits for RSA keys
* @param {Integer} options.rsaBits number of bits for the key creation. * @param {Array<String|Object>} options.userIds User IDs as strings or objects: 'Jo Doe <info@jo.com>' or { name:'Jo Doe', email:'info@jo.com' }
* @param {String|Array<String>} options.userIds * @param {String} options.passphrase Passphrase used to encrypt the resulting private key
* Assumes already in form of "User Name <username@email.com>" * @param {Number} options.keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires
* If array is used, the first userId is set as primary user Id * @param {Date} options.date Creation date of the key and the key signatures
* @param {String} options.passphrase The passphrase used to encrypt the resulting private key * @param {Array<Object>} options.subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}]
* @param {Number} [options.keyExpirationTime=0] * sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt
* The number of seconds after the key creation time that the key expires
* @param {String} options.curve (optional) elliptic curve for ECC keys
* @param {Date} options.date Override the creation date of the key and the key signatures
* @param {Array<Object>} options.subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}]
* sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt
* @returns {Promise<module:key.Key>} * @returns {Promise<module:key.Key>}
* @async * @async
* @static * @static
@ -68,16 +63,12 @@ export async function generate(options) {
/** /**
* Reformats and signs an OpenPGP key with a given User ID. Currently only supports RSA keys. * Reformats and signs an OpenPGP key with a given User ID. Currently only supports RSA keys.
* @param {module:key.Key} options.privateKey The private key to reformat * @param {module:key.Key} options.privateKey The private key to reformat
* @param {module:enums.publicKey} [options.keyType=module:enums.publicKey.rsaEncryptSign] * @param {Array<String|Object>} options.userIds User IDs as strings or objects: 'Jo Doe <info@jo.com>' or { name:'Jo Doe', email:'info@jo.com' }
* @param {String|Array<String>} options.userIds * @param {String} options.passphrase Passphrase used to encrypt the resulting private key
* Assumes already in form of "User Name <username@email.com>" * @param {Number} options.keyExpirationTime Number of seconds from the key creation time after which the key expires
* If array is used, the first userId is set as primary user Id * @param {Date} options.date Override the creation date of the key and the key signatures
* @param {String} options.passphrase The passphrase used to encrypt the resulting private key * @param {Array<Object>} options.subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}]
* @param {Number} [options.keyExpirationTime=0]
* The number of seconds after the key creation time that the key expires
* @param {Date} options.date Override the creation date of the key and the key signatures
* @param {Array<Object>} options.subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}]
* *
* @returns {Promise<module:key.Key>} * @returns {Promise<module:key.Key>}
* @async * @async

View File

@ -327,6 +327,7 @@ export async function isAeadSupported(keys, date = new Date(), userIds = []) {
} }
export function sanitizeKeyOptions(options, subkeyDefaults = {}) { export function sanitizeKeyOptions(options, subkeyDefaults = {}) {
options.type = options.type || subkeyDefaults.type;
options.curve = options.curve || subkeyDefaults.curve; options.curve = options.curve || subkeyDefaults.curve;
options.rsaBits = options.rsaBits || subkeyDefaults.rsaBits; options.rsaBits = options.rsaBits || subkeyDefaults.rsaBits;
options.keyExpirationTime = options.keyExpirationTime !== undefined ? options.keyExpirationTime : subkeyDefaults.keyExpirationTime; options.keyExpirationTime = options.keyExpirationTime !== undefined ? options.keyExpirationTime : subkeyDefaults.keyExpirationTime;
@ -335,24 +336,27 @@ export function sanitizeKeyOptions(options, subkeyDefaults = {}) {
options.sign = options.sign || false; options.sign = options.sign || false;
if (options.curve) { switch (options.type) {
try { case 'ecc':
options.curve = enums.write(enums.curve, options.curve); try {
} catch (e) { options.curve = enums.write(enums.curve, options.curve);
throw new Error('Not valid curve.'); } catch (e) {
} throw new Error('Invalid curve');
if (options.curve === enums.curve.ed25519 || options.curve === enums.curve.curve25519) { }
options.curve = options.sign ? enums.curve.ed25519 : enums.curve.curve25519; if (options.curve === enums.curve.ed25519 || options.curve === enums.curve.curve25519) {
} options.curve = options.sign ? enums.curve.ed25519 : enums.curve.curve25519;
if (options.sign) { }
options.algorithm = options.curve === enums.curve.ed25519 ? enums.publicKey.eddsa : enums.publicKey.ecdsa; if (options.sign) {
} else { options.algorithm = options.curve === enums.curve.ed25519 ? enums.publicKey.eddsa : enums.publicKey.ecdsa;
options.algorithm = enums.publicKey.ecdh; } else {
} options.algorithm = enums.publicKey.ecdh;
} else if (options.rsaBits) { }
options.algorithm = enums.publicKey.rsaEncryptSign; break;
} else { case 'rsa':
throw new Error('Unrecognized key type'); options.algorithm = enums.publicKey.rsaEncryptSign;
break;
default:
throw new Error(`Unsupported key type ${options.type}`);
} }
return options; return options;
} }

View File

@ -32,6 +32,7 @@ import {
PublicSubkeyPacket, PublicSubkeyPacket,
SignaturePacket SignaturePacket
} from '../packet'; } from '../packet';
import config from '../config';
import enums from '../enums'; import enums from '../enums';
import util from '../util'; import util from '../util';
import User from './user'; import User from './user';
@ -861,12 +862,12 @@ class Key {
/** /**
* Generates a new OpenPGP subkey, and returns a clone of the Key object with the new subkey added. * Generates a new OpenPGP subkey, and returns a clone of the Key object with the new subkey added.
* Supports RSA and ECC keys. Defaults to the algorithm and bit size/curve of the primary key. * Supports RSA and ECC keys. Defaults to the algorithm and bit size/curve of the primary key. DSA primary keys default to RSA subkeys.
* @param {Integer} options.rsaBits number of bits for the key creation. * @param {ecc|rsa} options.type The subkey algorithm: ECC or RSA
* @param {Number} [options.keyExpirationTime=0] * @param {String} options.curve (optional) Elliptic curve for ECC keys
* The number of seconds after the key creation time that the key expires * @param {Integer} options.rsaBits (optional) Number of bits for RSA subkeys
* @param {String} options.curve (optional) Elliptic curve for ECC keys * @param {Number} options.keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires
* @param {Date} options.date (optional) Override the creation date of the key and the key signatures * @param {Date} options.date (optional) Override the creation date of the key and the key signatures
* @param {Boolean} options.sign (optional) Indicates whether the subkey should sign rather than encrypt. Defaults to false * @param {Boolean} options.sign (optional) Indicates whether the subkey should sign rather than encrypt. Defaults to false
* @returns {Promise<module:key.Key>} * @returns {Promise<module:key.Key>}
* @async * @async
@ -878,14 +879,17 @@ class Key {
if (options.passphrase) { if (options.passphrase) {
throw new Error("Subkey could not be encrypted here, please encrypt whole key"); throw new Error("Subkey could not be encrypted here, please encrypt whole key");
} }
if (util.getWebCryptoAll() && options.rsaBits < 2048) { if (options.rsaBits < config.minRsaBits) {
throw new Error('When using webCrypto rsaBits should be 2048 or 4096, found: ' + options.rsaBits); throw new Error(`rsaBits should be at least ${config.minRsaBits}, got: ${options.rsaBits}`);
} }
const secretKeyPacket = this.primaryKey; const secretKeyPacket = this.primaryKey;
if (!secretKeyPacket.isDecrypted()) { if (!secretKeyPacket.isDecrypted()) {
throw new Error("Key is not decrypted"); throw new Error("Key is not decrypted");
} }
const defaultOptions = secretKeyPacket.getAlgorithmInfo(); const defaultOptions = secretKeyPacket.getAlgorithmInfo();
defaultOptions.type = defaultOptions.curve ? 'ecc' : 'rsa'; // DSA keys default to RSA
defaultOptions.rsaBits = defaultOptions.bits || 4096;
defaultOptions.curve = defaultOptions.curve || 'curve25519';
options = helper.sanitizeKeyOptions(options, defaultOptions); options = helper.sanitizeKeyOptions(options, defaultOptions);
const keyPacket = await helper.generateSecretSubkey(options); const keyPacket = await helper.generateSecretSubkey(options);
const bindingSignature = await helper.createBindingSignature(keyPacket, secretKeyPacket, options); const bindingSignature = await helper.createBindingSignature(keyPacket, secretKeyPacket, options);

View File

@ -62,28 +62,28 @@ if (globalThis.ReadableStream) {
/** /**
* Generates a new OpenPGP key pair. Supports RSA and ECC keys. Primary and subkey will be of same type. * Generates a new OpenPGP key pair. Supports RSA and ECC keys. By default, primary and subkeys will be of same type.
* @param {Array<Object>} userIds array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }] * @param {ecc|rsa} type (optional) The primary key algorithm type: ECC (default) or RSA
* @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key * @param {Array<String|Object>} userIds User IDs as strings or objects: 'Jo Doe <info@jo.com>' or { name:'Jo Doe', email:'info@jo.com' }
* @param {Number} rsaBits (optional) number of bits for RSA keys: 2048 or 4096. * @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key
* @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires * @param {Number} rsaBits (optional) Number of bits for RSA keys, defaults to 4096
* @param {String} curve (optional) elliptic curve for ECC keys: * @param {String} curve (optional) Elliptic curve for ECC keys:
* curve25519, p256, p384, p521, secp256k1, * curve25519 (default), p256, p384, p521, secp256k1,
* brainpoolP256r1, brainpoolP384r1, or brainpoolP512r1. * brainpoolP256r1, brainpoolP384r1, or brainpoolP512r1
* @param {Date} date (optional) override the creation date of the key and the key signatures * @param {Date} date (optional) Override the creation date of the key and the key signatures
* @param {Array<Object>} subkeys (optional) options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}] * @param {Number} keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires
* sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt * @param {Array<Object>} subkeys (optional) Options for each subkey, default to main key options. e.g. [{sign: true, passphrase: '123'}]
* sign parameter defaults to false, and indicates whether the subkey should sign rather than encrypt
* @returns {Promise<Object>} The generated key object in the form: * @returns {Promise<Object>} The generated key object in the form:
* { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String } * { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String }
* @async * @async
* @static * @static
*/ */
export function generateKey({ userIds = [], passphrase = "", rsaBits = null, keyExpirationTime = 0, curve = "curve25519", date = new Date(), subkeys = [{}] }) { export function generateKey({ userIds = [], passphrase = "", type = "ecc", rsaBits = 4096, curve = "curve25519", keyExpirationTime = 0, date = new Date(), subkeys = [{}] }) {
userIds = toArray(userIds); userIds = toArray(userIds);
curve = rsaBits ? "" : curve; const options = { userIds, passphrase, type, rsaBits, curve, keyExpirationTime, date, subkeys };
const options = { userIds, passphrase, rsaBits, keyExpirationTime, curve, date, subkeys }; if (type === "rsa" && rsaBits < config.minRsaBits) {
if (util.getWebCryptoAll() && rsaBits && rsaBits < 2048) { throw new Error(`rsaBits should be at least ${config.minRsaBits}, got: ${rsaBits}`);
throw new Error('rsaBits should be 2048 or 4096, found: ' + rsaBits);
} }
return generate(options).then(async key => { return generate(options).then(async key => {
@ -103,10 +103,10 @@ export function generateKey({ userIds = [], passphrase = "", rsaBits = null, key
/** /**
* Reformats signature packets for a key and rewraps key object. * Reformats signature packets for a key and rewraps key object.
* @param {Key} privateKey private key to reformat * @param {Key} privateKey Private key to reformat
* @param {Array<Object>} userIds array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }] * @param {Array<String|Object>} userIds User IDs as strings or objects: 'Jo Doe <info@jo.com>' or { name:'Jo Doe', email:'info@jo.com' }
* @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key * @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key
* @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires * @param {Number} keyExpirationTime (optional) Number of seconds from the key creation time after which the key expires
* @returns {Promise<Object>} The generated key object in the form: * @returns {Promise<Object>} The generated key object in the form:
* { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String } * { key:Key, privateKeyArmored:String, publicKeyArmored:String, revocationCertificate:String }
* @async * @async

View File

@ -231,14 +231,15 @@ class PublicKeyPacket {
/** /**
* Returns algorithm information * Returns algorithm information
* @returns {Object} An object of the form {algorithm: String, rsaBits:int, curve:String} * @returns {Object} An object of the form {algorithm: String, bits:int, curve:String}
*/ */
getAlgorithmInfo() { getAlgorithmInfo() {
const result = {}; const result = {};
result.algorithm = this.algorithm; result.algorithm = this.algorithm;
if (this.publicParams.n) { // RSA, DSA or ElGamal public modulo
result.rsaBits = this.publicParams.n.length * 8; const modulo = this.publicParams.n || this.publicParams.p;
result.bits = result.rsaBits; // Deprecated. if (modulo) {
result.bits = modulo.length * 8;
} else { } else {
result.curve = this.publicParams.oid.getName(); result.curve = this.publicParams.oid.getName();
} }

View File

@ -405,7 +405,6 @@ class SecretKeyPacket extends PublicKeyPacket {
} }
} }
async generate(bits, curve) { async generate(bits, curve) {
const algo = enums.write(enums.publicKey, this.algorithm); const algo = enums.write(enums.publicKey, this.algorithm);
const { privateParams, publicParams } = await crypto.generateParams(algo, bits, curve); const { privateParams, publicParams } = await crypto.generateParams(algo, bits, curve);

View File

@ -14,7 +14,7 @@ const expect = chai.expect;
const native = util.getWebCrypto() || util.getNodeCrypto(); const native = util.getWebCrypto() || util.getNodeCrypto();
module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptography with native crypto', function () { module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptography with native crypto', function () {
it('generate rsa key', async function() { it('generate rsa key', async function() {
const bits = util.getWebCryptoAll() ? 2048 : 1024; const bits = 1024;
const keyObject = await crypto.publicKey.rsa.generate(bits, 65537); const keyObject = await crypto.publicKey.rsa.generate(bits, 65537);
expect(keyObject.n).to.exist; expect(keyObject.n).to.exist;
expect(keyObject.e).to.exist; expect(keyObject.e).to.exist;
@ -25,7 +25,7 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra
}); });
it('sign and verify using generated key params', async function() { it('sign and verify using generated key params', async function() {
const bits = util.getWebCryptoAll() ? 2048 : 1024; const bits = 1024;
const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits);
const message = await random.getRandomBytes(64); const message = await random.getRandomBytes(64);
const hash_algo = openpgp.enums.write(openpgp.enums.hash, 'sha256'); const hash_algo = openpgp.enums.write(openpgp.enums.hash, 'sha256');
@ -38,7 +38,7 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra
}); });
it('encrypt and decrypt using generated key params', async function() { it('encrypt and decrypt using generated key params', async function() {
const bits = util.getWebCryptoAll() ? 2048 : 1024; const bits = 1024;
const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits);
const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; const { n, e, d, p, q, u } = { ...publicParams, ...privateParams };
const message = await crypto.generateSessionKey('aes256'); const message = await crypto.generateSessionKey('aes256');
@ -72,7 +72,7 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra
}); });
it('compare native crypto and bn math sign', async function() { it('compare native crypto and bn math sign', async function() {
const bits = util.getWebCrypto() ? 2048 : 1024; const bits = 1024;
const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits);
const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; const { n, e, d, p, q, u } = { ...publicParams, ...privateParams };
const message = await random.getRandomBytes(64); const message = await random.getRandomBytes(64);
@ -98,7 +98,7 @@ module.exports = () => (!native ? describe.skip : describe)('basic RSA cryptogra
}); });
it('compare native crypto and bn math verify', async function() { it('compare native crypto and bn math verify', async function() {
const bits = util.getWebCrypto() ? 2048 : 1024; const bits = 1024;
const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits); const { publicParams, privateParams } = await crypto.generateParams(openpgp.enums.publicKey.rsaSign, bits);
const { n, e, d, p, q, u } = { ...publicParams, ...privateParams }; const { n, e, d, p, q, u } = { ...publicParams, ...privateParams };
const message = await random.getRandomBytes(64); const message = await random.getRandomBytes(64);

View File

@ -244,7 +244,7 @@ module.exports = () => {
describe('RSA parameter validation', function() { describe('RSA parameter validation', function() {
let rsaKey; let rsaKey;
before(async () => { before(async () => {
rsaKey = (await openpgp.generateKey({ rsaBits: 2048, userIds: [{ name: 'Test', email: 'test@test.com' }] })).key; rsaKey = (await openpgp.generateKey({ type: 'rsa', rsaBits: 2048, userIds: [{ name: 'Test', email: 'test@test.com' }] })).key;
}); });
it('generated RSA params are valid', async function() { it('generated RSA params are valid', async function() {

View File

@ -1917,6 +1917,34 @@ vqBGKJzmO5q3cECw
=X9kJ =X9kJ
-----END PGP PRIVATE KEY BLOCK-----`; -----END PGP PRIVATE KEY BLOCK-----`;
const dsaPrivateKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
lQNTBF69PO8RCACHP4KLQcYOPGsGV9owTZvxnvHvvrY8W0v8xDUL3y6CLc05srF1
kQp/81iUfP5g57BEiDpJV95kMh+ulBthIOGnuMCkodJjuBICB4K6BtFTV4Fw1Q5S
S7aLC9beCaMvvGHXsK6MbknYl+IVJY7Zmml1qUSrBIQFGp5kqdhIX4o+OrzZ1zYj
ALicqzD7Zx2VRjGNQv7UKv4CkBOC8ncdnq/4/OQeOYFzVbCOf+sJhTgz6yxjHJVC
fLk7w8l2v1zV11VJuc8cQiQ9g8tjbKgLMsbyzy7gl4m9MSCdinG36XZuPibZrSm0
H8gKAdd1FT84a3/qU2rtLLR0y8tCxBj89Xx/AQCv7CDmwoU+/yGpBVVl1mh0ZUkA
/VJUhnJfv5MIOIi3AQf8CS9HrEmYJg/A3z0DcvcwIu/9gqpRLTqH1iT5o4BCg2j+
Cog2ExYkQl1OEPkEQ1lKJSnD8MDwO3BlkJ4cD0VSKxlnwd9dsu9m2+F8T+K1hoA7
PfH89TjD5HrEaGAYIdivLYSwoTNOO+fY8FoVC0RR9pFNOmjiTU5PZZedOxAql5Os
Hp2bYhky0G9trjo8Mt6CGhvgA3dAKyONftLQr9HSM0GKacFV+nRd9TGCPNZidKU8
MDa/SB/08y1bBGX5FK5wwiZ6H5qD8VAUobH3kwKlrg0nL00/EqtYHJqvJ2gkT5/v
h8+z4R4TuYiy4kKF2FLPd5OjdA31IVDoVgCwF0WHLgf/X9AiTr/DPs/5dIYN1+hf
UJwqjzr3dlokRwx3CVDcOVsdkWRwb8cvxubbsIorvUrF02IhYjHJMjIHT/zFt2zA
+VPzO4zabUlawWVepPEwrCtXgvn9aXqjhAYbilG3UZamhfstGUmbmvWVDadALwby
EO8u2pfLhI2lep63V/+KtUOLhfk8jKRSvxvxlYAvMi7sK8kB+lYy17XKN+IMYgf8
gMFV6XGKpdmMSV3jOvat8cI6vnRO0i+g3jANP3PfrFEivat/rVgxo67r4rxezfFn
J29qwB9rgbRgMBGsbDvIlQNV/NWFvHy2uQAEKn5eX4CoLsCZoR2VfK3BwBCxhYDp
/wAA/0GSmI9MlMnLadFNlcX2Bm4i15quZAGF8JxwHbj1dhdUEYq0E1Rlc3QgPHRl
c3RAdGVzdC5pbz6IlAQTEQgAPBYhBAq6lCI5EfrbHP1qZCxnOy/rlEGVBQJevTzv
AhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeBwIXgAAKCRAsZzsv65RBlUPoAP9Q
aTCWpHWZkvZzC8VU64O76fHp31rLWlcZFttuDNLyeAEAhOxkQHk6GR88R+EF5mrn
clr63t9Q4wreqOlO0NR5/9k=
=UW2O
-----END PGP PRIVATE KEY BLOCK-----
`;
const uidlessKey = `-----BEGIN PGP PRIVATE KEY BLOCK----- const uidlessKey = `-----BEGIN PGP PRIVATE KEY BLOCK-----
xcMFBF8/lc8BCACwwWWyNdfZ9Qjz8zc4sFGNfHXITscT7WCMuXgC2BbFwiSD xcMFBF8/lc8BCACwwWWyNdfZ9Qjz8zc4sFGNfHXITscT7WCMuXgC2BbFwiSD
@ -2244,6 +2272,20 @@ function versionSpecificTests() {
}); });
}); });
it('Generate key - default values', function() {
const userId = 'test <a@b.com>';
const opt = { userIds: [userId] };
return openpgp.generateKey(opt).then(function({ key }) {
expect(key.isDecrypted()).to.be.true;
expect(key.getAlgorithmInfo().algorithm).to.equal('eddsa');
expect(key.users.length).to.equal(1);
expect(key.users[0].userId.userid).to.equal(userId);
expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true;
expect(key.subKeys).to.have.length(1);
expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh');
});
});
it('Generate key - two subkeys with default values', function() { it('Generate key - two subkeys with default values', function() {
const userId = 'test <a@b.com>'; const userId = 'test <a@b.com>';
const opt = { userIds: [userId], passphrase: '123', subkeys:[{},{}] }; const opt = { userIds: [userId], passphrase: '123', subkeys:[{},{}] };
@ -2258,20 +2300,24 @@ function versionSpecificTests() {
}); });
}); });
it('Generate RSA key - two subkeys with default values', function() { it('Generate RSA key - two subkeys with default values', async function() {
const userId = 'test <a@b.com>'; const rsaBits = 512;
const opt = { rsaBits: 512, userIds: [userId], passphrase: '123', subkeys:[{},{}] }; const minRsaBits = openpgp.config.minRsaBits;
if (util.getWebCryptoAll()) { opt.rsaBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys openpgp.config.minRsaBits = rsaBits;
return openpgp.generateKey(opt).then(function(key) { const userId = 'test <a@b.com>';
key = key.key; const opt = { type: 'rsa', rsaBits, userIds: [userId], passphrase: '123', subkeys:[{},{}] };
try {
const { key } = await openpgp.generateKey(opt);
expect(key.users.length).to.equal(1); expect(key.users.length).to.equal(1);
expect(key.users[0].userId.userid).to.equal(userId); expect(key.users[0].userId.userid).to.equal(userId);
expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true; expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true;
expect(key.subKeys).to.have.length(2); expect(key.subKeys).to.have.length(2);
expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign');
expect(key.subKeys[1].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); expect(key.subKeys[1].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign');
}); } finally {
openpgp.config.minRsaBits = minRsaBits;
}
}); });
it('Generate key - one signing subkey', function() { it('Generate key - one signing subkey', function() {
@ -2309,20 +2355,24 @@ function versionSpecificTests() {
}); });
}); });
it('Generate key - override main RSA key options for subkey', function() { it('Generate key - override main RSA key options for subkey', async function() {
const rsaBits = 512;
const minRsaBits = openpgp.config.minRsaBits;
openpgp.config.minRsaBits = rsaBits;
const userId = 'test <a@b.com>'; const userId = 'test <a@b.com>';
const opt = { rsaBits: 512, userIds: [userId], passphrase: '123', subkeys:[{ curve: 'curve25519' }] }; const opt = { type: 'rsa', rsaBits, userIds: [userId], passphrase: '123', subkeys:[{ type: 'ecc', curve: 'curve25519' }] };
if (util.getWebCryptoAll()) { opt.rsaBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys try {
return openpgp.generateKey(opt).then(function(key) { const { key } = await openpgp.generateKey(opt);
key = key.key;
expect(key.users.length).to.equal(1); expect(key.users.length).to.equal(1);
expect(key.users[0].userId.userid).to.equal(userId); expect(key.users[0].userId.userid).to.equal(userId);
expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true; expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true;
expect(key.getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign'); expect(key.getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign');
expect(key.getAlgorithmInfo().bits).to.equal(opt.rsaBits); expect(key.getAlgorithmInfo().bits).to.equal(opt.rsaBits);
expect(key.getAlgorithmInfo().rsaBits).to.equal(key.getAlgorithmInfo().bits);
expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh'); expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh');
}); } finally {
openpgp.config.minRsaBits = minRsaBits;
}
}); });
it('Encrypt key with new passphrase', async function() { it('Encrypt key with new passphrase', async function() {
@ -3407,12 +3457,17 @@ VYGdb3eNlV8CfoEC
}); });
describe('addSubkey functionality testing', function() { describe('addSubkey functionality testing', function() {
let rsaBits; const rsaBits = 1024;
let rsaOpt = {}; const rsaOpt = { type: 'rsa' };
if (util.getWebCryptoAll()) { let minRsaBits;
rsaBits = 2048; beforeEach(function() {
rsaOpt = { rsaBits: rsaBits }; minRsaBits = openpgp.config.minRsaBits;
} openpgp.config.minRsaBits = rsaBits;
});
afterEach(function() {
openpgp.config.minRsaBits = minRsaBits;
});
it('create and add a new rsa subkey to stored rsa key', async function() { it('create and add a new rsa subkey to stored rsa key', async function() {
const privateKey = await openpgp.readArmoredKey(priv_key_rsa); const privateKey = await openpgp.readArmoredKey(priv_key_rsa);
await privateKey.decrypt('hello world'); await privateKey.decrypt('hello world');
@ -3425,12 +3480,40 @@ VYGdb3eNlV8CfoEC
expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); expect(newPrivateKey.subKeys.length).to.be.equal(total + 1);
const subkeyN = subKey.keyPacket.publicParams.n; const subkeyN = subKey.keyPacket.publicParams.n;
const pkN = privateKey.primaryKey.publicParams.n; const pkN = privateKey.primaryKey.publicParams.n;
expect(subkeyN.length).to.be.equal(rsaBits ? (rsaBits / 8) : pkN.length); expect(subkeyN.length).to.be.equal(pkN.length);
expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign'); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign');
expect(subKey.getAlgorithmInfo().rsaBits).to.be.equal(rsaBits || privateKey.getAlgorithmInfo().rsaBits); expect(subKey.getAlgorithmInfo().bits).to.be.equal(privateKey.getAlgorithmInfo().bits);
await subKey.verify(newPrivateKey.primaryKey); await subKey.verify(newPrivateKey.primaryKey);
}); });
it('Add a new default subkey to an rsaSign key', async function() {
const userId = 'test <a@b.com>';
const opt = { type: 'rsa', rsaBits, userIds: [userId], subkeys: [] };
const { key } = await openpgp.generateKey(opt);
expect(key.subKeys).to.have.length(0);
key.keyPacket.algorithm = "rsaSign";
const newKey = await key.addSubkey();
expect(newKey.subKeys[0].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign');
});
it('Add a new default subkey to an ecc key', async function() {
const userId = 'test <a@b.com>';
const opt = { type: 'ecc', userIds: [userId], subkeys: [] };
const { key } = await openpgp.generateKey(opt);
expect(key.subKeys).to.have.length(0);
const newKey = await key.addSubkey();
expect(newKey.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh');
expect(newKey.subKeys[0].getAlgorithmInfo().curve).to.equal('curve25519');
});
it('Add a new default subkey to a dsa key', async function() {
const key = await openpgp.readArmoredKey(dsaPrivateKey);
const total = key.subKeys.length;
const newKey = await key.addSubkey();
expect(newKey.subKeys[total].getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign');
expect(newKey.subKeys[total].getAlgorithmInfo().bits).to.equal(Math.max(key.getAlgorithmInfo().bits, openpgp.config.minRsaBits));
});
it('should throw when trying to encrypt a subkey separately from key', async function() { it('should throw when trying to encrypt a subkey separately from key', async function() {
const privateKey = await openpgp.readArmoredKey(priv_key_rsa); const privateKey = await openpgp.readArmoredKey(priv_key_rsa);
await privateKey.decrypt('hello world'); await privateKey.decrypt('hello world');
@ -3454,7 +3537,7 @@ VYGdb3eNlV8CfoEC
await subKey.verify(importedPrivateKey.primaryKey); await subKey.verify(importedPrivateKey.primaryKey);
}); });
it('create and add a new ec subkey to a ec key', async function() { it('create and add a new eddsa subkey to a eddsa key', async function() {
const userId = 'test <a@b.com>'; const userId = 'test <a@b.com>';
const opt = { curve: 'curve25519', userIds: [userId], subkeys:[] }; const opt = { curve: 'curve25519', userIds: [userId], subkeys:[] };
const privateKey = (await openpgp.generateKey(opt)).key; const privateKey = (await openpgp.generateKey(opt)).key;
@ -3478,19 +3561,66 @@ VYGdb3eNlV8CfoEC
await subKey.verify(privateKey.primaryKey); await subKey.verify(privateKey.primaryKey);
}); });
it('create and add a new ec subkey to a rsa key', async function() { it('create and add a new ecdsa subkey to a eddsa key', async function() {
const userId = 'test <a@b.com>';
const opt = { curve: 'ed25519', userIds: [userId], subkeys:[] };
const privateKey = (await openpgp.generateKey(opt)).key;
const total = privateKey.subKeys.length;
let newPrivateKey = await privateKey.addSubkey({ curve: 'p256', sign: true });
newPrivateKey = await openpgp.readArmoredKey(newPrivateKey.armor());
const subKey = newPrivateKey.subKeys[total];
expect(subKey).to.exist;
expect(newPrivateKey.subKeys.length).to.be.equal(total + 1);
expect(newPrivateKey.getAlgorithmInfo().curve).to.be.equal('ed25519');
expect(subKey.getAlgorithmInfo().curve).to.be.equal('p256');
expect(newPrivateKey.getAlgorithmInfo().algorithm).to.be.equal('eddsa');
expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('ecdsa');
await subKey.verify(privateKey.primaryKey);
});
it('create and add a new ecc subkey to a rsa key', async function() {
const privateKey = await openpgp.readArmoredKey(priv_key_rsa); const privateKey = await openpgp.readArmoredKey(priv_key_rsa);
await privateKey.decrypt('hello world'); await privateKey.decrypt('hello world');
const total = privateKey.subKeys.length; const total = privateKey.subKeys.length;
const opt2 = { curve: 'curve25519' }; const opt2 = { type: 'ecc', curve: 'curve25519' };
let newPrivateKey = await privateKey.addSubkey(opt2); let newPrivateKey = await privateKey.addSubkey(opt2);
const armoredKey = newPrivateKey.armor(); const armoredKey = newPrivateKey.armor();
newPrivateKey = await openpgp.readArmoredKey(armoredKey); newPrivateKey = await openpgp.readArmoredKey(armoredKey);
expect(newPrivateKey.subKeys.length).to.be.equal(total + 1);
const subKey = newPrivateKey.subKeys[total];
expect(subKey).to.exist;
expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('ecdh');
expect(subKey.getAlgorithmInfo().curve).to.be.equal(openpgp.enums.curve.curve25519);
await subKey.verify(privateKey.primaryKey);
});
it('create and add a new rsa subkey to a ecc key', async function() {
const userId = 'test <a@b.com>';
const opt = { curve: 'ed25519', userIds: [userId], subkeys:[] };
const privateKey = (await openpgp.generateKey(opt)).key;
const total = privateKey.subKeys.length;
let newPrivateKey = await privateKey.addSubkey({ type: 'rsa' });
const armoredKey = newPrivateKey.armor();
newPrivateKey = await openpgp.readArmoredKey(armoredKey);
const subKey = newPrivateKey.subKeys[total]; const subKey = newPrivateKey.subKeys[total];
expect(subKey).to.exist; expect(subKey).to.exist;
expect(newPrivateKey.subKeys.length).to.be.equal(total + 1); expect(newPrivateKey.subKeys.length).to.be.equal(total + 1);
expect(subKey.keyPacket.publicParams.oid.getName()).to.be.equal(openpgp.enums.curve.curve25519); expect(subKey.getAlgorithmInfo().bits).to.be.equal(4096);
expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('ecdh'); expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign');
await subKey.verify(privateKey.primaryKey);
});
it('create and add a new rsa subkey to a dsa key', async function() {
const privateKey = await openpgp.readArmoredKey(dsaPrivateKey);
const total = privateKey.subKeys.length;
let newPrivateKey = await privateKey.addSubkey({ type: 'rsa', rsaBits: 2048 });
newPrivateKey = await openpgp.readArmoredKey(newPrivateKey.armor());
expect(newPrivateKey.subKeys.length).to.be.equal(total + 1);
const subKey = newPrivateKey.subKeys[total];
expect(subKey).to.exist;
expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign');
expect(subKey.getAlgorithmInfo().bits).to.be.equal(2048);
await subKey.verify(privateKey.primaryKey); await subKey.verify(privateKey.primaryKey);
}); });

View File

@ -1,5 +1,4 @@
const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..'); const openpgp = typeof window !== 'undefined' && window.openpgp ? window.openpgp : require('../..');
const util = require('../../src/util');
const { readArmoredKey, Key, readArmoredCleartextMessage, CleartextMessage, enums, PacketList, SignaturePacket } = openpgp; const { readArmoredKey, Key, readArmoredCleartextMessage, CleartextMessage, enums, PacketList, SignaturePacket } = openpgp;
const key = require('../../src/key'); const key = require('../../src/key');
@ -12,7 +11,8 @@ const expect = chai.expect;
async function generateTestData() { async function generateTestData() {
const victimPrivKey = await key.generate({ const victimPrivKey = await key.generate({
userIds: ['Victim <victim@example.com>'], userIds: ['Victim <victim@example.com>'],
rsaBits: util.getWebCryptoAll() ? 2048 : 1024, type: 'rsa',
rsaBits: 1024,
subkeys: [{ subkeys: [{
sign: true sign: true
}] }]
@ -21,7 +21,8 @@ async function generateTestData() {
const attackerPrivKey = await key.generate({ const attackerPrivKey = await key.generate({
userIds: ['Attacker <attacker@example.com>'], userIds: ['Attacker <attacker@example.com>'],
rsaBits: util.getWebCryptoAll() ? 2048 : 1024, type: 'rsa',
rsaBits: 1024,
subkeys: [], subkeys: [],
sign: false sign: false
}); });