Skip to content

Commit

Permalink
Handle subkey defaults
Browse files Browse the repository at this point in the history
  • Loading branch information
larabr committed Nov 19, 2020
1 parent f7f29c9 commit 403bad7
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 22 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -5,3 +5,4 @@ test/lib/
dist/
openpgp.store/
.nyc_output/
browserify-cache*.json
7 changes: 6 additions & 1 deletion src/key/key.js
Expand Up @@ -864,7 +864,7 @@ class Key {

/**
* 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 {String} options.algorithm The subkey algorithm: ECC or RSA
* @param {String} options.curve (optional) Elliptic curve for ECC keys
* @param {Integer} options.rsaBits (optional) Number of bits for RSA subkeys
Expand All @@ -889,6 +889,11 @@ class Key {
throw new Error("Key is not decrypted");
}
const defaultOptions = secretKeyPacket.getAlgorithmInfo();
defaultOptions.rsaBits = Math.max(defaultOptions.bits || 4096, config.rsaBitsMin);
defaultOptions.curve = defaultOptions.curve || "curve25519";
if (defaultOptions.algorithm === "dsa") {
defaultOptions.algorithm = "rsa";
}
options = helper.sanitizeKeyOptions(options, defaultOptions);
const keyPacket = await helper.generateSecretSubkey(options);
const bindingSignature = await helper.createBindingSignature(keyPacket, secretKeyPacket, options);
Expand Down
5 changes: 3 additions & 2 deletions src/openpgp.js
Expand Up @@ -66,7 +66,7 @@ if (globalThis.ReadableStream) {
* @param {String} algorithm (optional) The primary key algorithm: ECC (default) or RSA
* @param {Array<Object>} userIds Array of user IDs e.g. [{ name:'Phil Zimmermann', email:'phil@openpgp.org' }]
* @param {String} passphrase (optional) The passphrase used to encrypt the resulting private key
* @param {Number} rsaBits (optional) Number of bits for RSA keys, defaults to 4096
* @param {Number} rsaBits (optional) Number of bits for RSA keys, defaults to 4096, or a higher value equal to config.rsaBitsMin
* @param {String} curve (optional) Elliptic curve for ECC keys:
* curve25519 (default), p256, p384, p521, secp256k1,
* brainpoolP256r1, brainpoolP384r1, or brainpoolP512r1
Expand All @@ -79,8 +79,9 @@ if (globalThis.ReadableStream) {
* @async
* @static
*/
export function generateKey({ userIds = [], passphrase = "", algorithm = "ecc", rsaBits = 4096, curve = "curve25519", keyExpirationTime = 0, date = new Date(), subkeys = [{}] }) {
export function generateKey({ userIds = [], passphrase = "", algorithm = "ecc", rsaBits, curve = "curve25519", keyExpirationTime = 0, date = new Date(), subkeys = [{}] }) {
userIds = toArray(userIds);
rsaBits = rsaBits || Math.max(4096, config.rsaBitsMin);
algorithm = algorithm.toLowerCase();
const options = { userIds, passphrase, algorithm, rsaBits, curve, keyExpirationTime, date, subkeys };
if (algorithm === "rsa" && rsaBits < config.rsaBitsMin) {
Expand Down
8 changes: 5 additions & 3 deletions src/packet/public_key.js
Expand Up @@ -231,13 +231,15 @@ class PublicKeyPacket {

/**
* 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() {
const result = {};
result.algorithm = this.algorithm;
if (this.publicParams.n) {
result.rsaBits = this.publicParams.n.length * 8;
// RSA, DSA or ElGamal public modulo
const modulo = this.publicParams.n || this.publicParams.p;
if (modulo) {
result.bits = modulo.length * 8;
} else {
result.curve = this.publicParams.oid.getName();
}
Expand Down
120 changes: 104 additions & 16 deletions test/general/key.js
Expand Up @@ -1916,6 +1916,34 @@ vqBGKJzmO5q3cECw
=X9kJ
-----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-----
xcMFBF8/lc8BCACwwWWyNdfZ9Qjz8zc4sFGNfHXITscT7WCMuXgC2BbFwiSD
Expand Down Expand Up @@ -2314,7 +2342,7 @@ function versionSpecificTests() {
expect(key.users[0].userId.userid).to.equal(userId);
expect(key.users[0].selfCertifications[0].isPrimaryUserID).to.be.true;
expect(key.getAlgorithmInfo().algorithm).to.equal('rsaEncryptSign');
expect(key.getAlgorithmInfo().rsaBits).to.equal(opt.rsaBits);
expect(key.getAlgorithmInfo().bits).to.equal(opt.rsaBits);
expect(key.subKeys[0].getAlgorithmInfo().algorithm).to.equal('ecdh');
} finally {
openpgp.config.rsaBitsMin = rsaBitsMin;
Expand Down Expand Up @@ -3398,23 +3426,36 @@ VYGdb3eNlV8CfoEC
const pkN = privateKey.primaryKey.publicParams.n;
expect(subkeyN.length).to.be.equal(pkN.length);
expect(subKey.getAlgorithmInfo().algorithm).to.be.equal('rsaEncryptSign');
expect(subKey.getAlgorithmInfo().rsaBits).to.be.equal(privateKey.getAlgorithmInfo().rsaBits);
expect(subKey.getAlgorithmInfo().bits).to.be.equal(privateKey.getAlgorithmInfo().bits);
await subKey.verify(newPrivateKey.primaryKey);
});


it('Add a new rsa subkey to an rsaSign key', async function() {
it('Add a new default subkey to an rsaSign key', async function() {
const userId = 'test <a@b.com>';
const opt = { algorithm: "rsa", rsaBits, userIds: [userId], subkeys: [] };
try {
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');
} finally {
openpgp.config.rsaBitsMin = rsaBitsMin;
}
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 = { algorithm: "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.rsaBitsMin));
});

it('should throw when trying to encrypt a subkey separately from key', async function() {
Expand All @@ -3440,7 +3481,7 @@ VYGdb3eNlV8CfoEC
await subKey.verify(importedPrivateKey.primaryKey);
});

it('create and add a new ecc subkey to a ecc key', async function() {
it('create and add a new eddsa subkey to a eddsa key', async function() {
const userId = 'test <a@b.com>';
const opt = { curve: 'curve25519', userIds: [userId], subkeys:[] };
const privateKey = (await openpgp.generateKey(opt)).key;
Expand All @@ -3464,6 +3505,24 @@ VYGdb3eNlV8CfoEC
await subKey.verify(privateKey.primaryKey);
});

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);
await privateKey.decrypt('hello world');
Expand All @@ -3472,11 +3531,40 @@ VYGdb3eNlV8CfoEC
let newPrivateKey = await privateKey.addSubkey(opt2);
const armoredKey = newPrivateKey.armor();
newPrivateKey = await openpgp.readArmoredKey(armoredKey);
expect(newPrivateKey.subKeys.length).to.be.equal(total + 1);
const subKey = newPrivateKey.subKeys[total];
expect(subKey).to.exist;
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().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({ algorithm: "rsa" });
const armoredKey = newPrivateKey.armor();
newPrivateKey = await openpgp.readArmoredKey(armoredKey);
const subKey = newPrivateKey.subKeys[total];
expect(subKey).to.exist;
expect(newPrivateKey.subKeys.length).to.be.equal(total + 1);
expect(subKey.getAlgorithmInfo().bits).to.be.equal(4096);
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({ algorithm: "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);
});

Expand Down

0 comments on commit 403bad7

Please sign in to comment.