Check if any (sub)key is decrypted in Key.prototype.isDecrypted (#1182)
`key.isDecrypted()` now returns true if either the primary key or any subkey is decrypted. Additionally, implement `SecretKeyPacket.prototype.makeDummy` for encrypted keys.
This commit is contained in:
parent
c23ed58387
commit
66c06dab3e
|
@ -77,13 +77,13 @@ export async function generate(options) {
|
|||
export async function reformat(options) {
|
||||
options = sanitize(options);
|
||||
|
||||
try {
|
||||
const isDecrypted = options.privateKey.getKeys().every(key => key.isDecrypted());
|
||||
if (!isDecrypted) {
|
||||
await options.privateKey.decrypt();
|
||||
}
|
||||
} catch (err) {
|
||||
throw new Error('Key not decrypted');
|
||||
if (options.privateKey.primaryKey.isDummy()) {
|
||||
throw new Error('Cannot reformat a gnu-dummy primary key');
|
||||
}
|
||||
|
||||
const isDecrypted = options.privateKey.getKeys().every(({ keyPacket }) => keyPacket.isDecrypted());
|
||||
if (!isDecrypted) {
|
||||
throw new Error('Key is not decrypted');
|
||||
}
|
||||
|
||||
const packetlist = options.privateKey.toPacketlist();
|
||||
|
|
|
@ -210,6 +210,9 @@ export async function getPreferredAlgo(type, keys, date = new Date(), userIds =
|
|||
* @returns {module:packet/signature} signature packet
|
||||
*/
|
||||
export async function createSignaturePacket(dataToSign, privateKey, signingKeyPacket, signatureProperties, date, userId, detached = false, streaming = false) {
|
||||
if (signingKeyPacket.isDummy()) {
|
||||
throw new Error('Cannot sign with a gnu-dummy key.');
|
||||
}
|
||||
if (!signingKeyPacket.isDecrypted()) {
|
||||
throw new Error('Private key is not decrypted.');
|
||||
}
|
||||
|
|
|
@ -47,7 +47,6 @@ import * as helper from './helper';
|
|||
* @borrows PublicKeyPacket#hasSameFingerprintAs as Key#hasSameFingerprintAs
|
||||
* @borrows PublicKeyPacket#getAlgorithmInfo as Key#getAlgorithmInfo
|
||||
* @borrows PublicKeyPacket#getCreationTime as Key#getCreationTime
|
||||
* @borrows PublicKeyPacket#isDecrypted as Key#isDecrypted
|
||||
*/
|
||||
class Key {
|
||||
/**
|
||||
|
@ -457,6 +456,14 @@ class Key {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the primary key or any subkey is decrypted.
|
||||
* A dummy key is considered encrypted.
|
||||
*/
|
||||
isDecrypted() {
|
||||
return this.getKeys().some(({ keyPacket }) => keyPacket.isDecrypted());
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the private and public primary key parameters correspond
|
||||
* Together with verification of binding signatures, this guarantees key integrity
|
||||
|
@ -883,6 +890,9 @@ class Key {
|
|||
throw new Error(`rsaBits should be at least ${config.minRsaBits}, got: ${options.rsaBits}`);
|
||||
}
|
||||
const secretKeyPacket = this.primaryKey;
|
||||
if (secretKeyPacket.isDummy()) {
|
||||
throw new Error("Cannot add subkey to gnu-dummy primary key");
|
||||
}
|
||||
if (!secretKeyPacket.isDecrypted()) {
|
||||
throw new Error("Key is not decrypted");
|
||||
}
|
||||
|
@ -900,7 +910,7 @@ class Key {
|
|||
}
|
||||
}
|
||||
|
||||
['getKeyId', 'getFingerprint', 'getAlgorithmInfo', 'getCreationTime', 'isDecrypted', 'hasSameFingerprintAs'].forEach(name => {
|
||||
['getKeyId', 'getFingerprint', 'getAlgorithmInfo', 'getCreationTime', 'hasSameFingerprintAs'].forEach(name => {
|
||||
Key.prototype[name] =
|
||||
SubKey.prototype[name];
|
||||
});
|
||||
|
|
|
@ -208,7 +208,7 @@ export class Message {
|
|||
// do not check key expiration to allow decryption of old messages
|
||||
const privateKeyPackets = (await privateKey.getDecryptionKeys(keyPacket.publicKeyId, null)).map(key => key.keyPacket);
|
||||
await Promise.all(privateKeyPackets.map(async function(privateKeyPacket) {
|
||||
if (!privateKeyPacket) {
|
||||
if (!privateKeyPacket || privateKeyPacket.isDummy()) {
|
||||
return;
|
||||
}
|
||||
if (!privateKeyPacket.isDecrypted()) {
|
||||
|
|
|
@ -228,7 +228,8 @@ class SecretKeyPacket extends PublicKeyPacket {
|
|||
}
|
||||
|
||||
/**
|
||||
* Check whether secret-key data is available in decrypted form. Returns null for public keys.
|
||||
* Check whether secret-key data is available in decrypted form.
|
||||
* Returns false for gnu-dummy keys and null for public keys.
|
||||
* @returns {Boolean|null}
|
||||
*/
|
||||
isDecrypted() {
|
||||
|
@ -244,20 +245,18 @@ class SecretKeyPacket extends PublicKeyPacket {
|
|||
}
|
||||
|
||||
/**
|
||||
* Remove private key material, converting the key to a dummy one
|
||||
* The resulting key cannot be used for signing/decrypting but can still verify signatures
|
||||
* Remove private key material, converting the key to a dummy one.
|
||||
* The resulting key cannot be used for signing/decrypting but can still verify signatures.
|
||||
*/
|
||||
makeDummy() {
|
||||
if (this.isDummy()) {
|
||||
return;
|
||||
}
|
||||
if (!this.isDecrypted()) {
|
||||
// this is technically not needed, but makes the conversion simpler
|
||||
throw new Error("Key is not decrypted");
|
||||
if (this.isDecrypted()) {
|
||||
this.clearPrivateParams();
|
||||
}
|
||||
this.clearPrivateParams();
|
||||
this.isEncrypted = null;
|
||||
this.keyMaterial = null;
|
||||
this.isEncrypted = false;
|
||||
this.s2k = new type_s2k();
|
||||
this.s2k.algorithm = 0;
|
||||
this.s2k.c = 0;
|
||||
|
@ -325,8 +324,7 @@ class SecretKeyPacket extends PublicKeyPacket {
|
|||
*/
|
||||
async decrypt(passphrase) {
|
||||
if (this.isDummy()) {
|
||||
this.isEncrypted = false;
|
||||
return;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.isDecrypted()) {
|
||||
|
@ -418,7 +416,6 @@ class SecretKeyPacket extends PublicKeyPacket {
|
|||
*/
|
||||
clearPrivateParams() {
|
||||
if (this.isDummy()) {
|
||||
this.isEncrypted = true;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -2616,7 +2616,7 @@ function versionSpecificTests() {
|
|||
return openpgp.reformatKey({ privateKey: original.key, userIds: { name: 'test2', email: 'a@b.com' }, passphrase: '1234' }).then(function() {
|
||||
throw new Error('reformatKey should result in error when key not decrypted');
|
||||
}).catch(function(error) {
|
||||
expect(error.message).to.equal('Error reformatting keypair: Key not decrypted');
|
||||
expect(error.message).to.equal('Error reformatting keypair: Key is not decrypted');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -2925,6 +2925,26 @@ module.exports = () => describe('Key', function() {
|
|||
await expect(key.validate()).to.be.rejectedWith('Key is invalid');
|
||||
});
|
||||
|
||||
it("isDecrypted() - should reflect whether all (sub)keys are encrypted", async function() {
|
||||
const passphrase = '12345678';
|
||||
const { key } = await openpgp.generateKey({ userIds: {}, curve: 'ed25519', passphrase });
|
||||
expect(key.isDecrypted()).to.be.false;
|
||||
await key.decrypt(passphrase, key.subKeys[0].getKeyId());
|
||||
expect(key.isDecrypted()).to.be.true;
|
||||
});
|
||||
|
||||
it("isDecrypted() - gnu-dummy primary key", async function() {
|
||||
const key = await openpgp.readArmoredKey(gnuDummyKeySigningSubkey);
|
||||
expect(key.isDecrypted()).to.be.true;
|
||||
await key.encrypt('12345678');
|
||||
expect(key.isDecrypted()).to.be.false;
|
||||
});
|
||||
|
||||
it("isDecrypted() - all-gnu-dummy key", async function() {
|
||||
const key = await openpgp.readArmoredKey(gnuDummyKey);
|
||||
expect(key.isDecrypted()).to.be.false;
|
||||
});
|
||||
|
||||
it('makeDummy() - the converted key can be parsed', async function() {
|
||||
const { key } = await openpgp.generateKey({ userIds: { name: 'dummy', email: 'dummy@alice.com' } });
|
||||
key.primaryKey.makeDummy();
|
||||
|
@ -2950,7 +2970,7 @@ module.exports = () => describe('Key', function() {
|
|||
key.primaryKey.makeDummy();
|
||||
expect(key.primaryKey.isDummy()).to.be.true;
|
||||
await key.validate();
|
||||
await expect(openpgp.reformatKey({ privateKey: key, userIds: { name: 'test', email: 'a@b.com' } })).to.be.rejectedWith(/Missing key parameters/);
|
||||
await expect(openpgp.reformatKey({ privateKey: key, userIds: { name: 'test', email: 'a@b.com' } })).to.be.rejectedWith(/Cannot reformat a gnu-dummy primary key/);
|
||||
});
|
||||
|
||||
it('makeDummy() - subkeys of the converted key can still sign', async function() {
|
||||
|
@ -2962,6 +2982,25 @@ module.exports = () => describe('Key', function() {
|
|||
await expect(openpgp.sign({ message: openpgp.Message.fromText('test'), privateKeys: [key] })).to.be.fulfilled;
|
||||
});
|
||||
|
||||
it('makeDummy() - should work for encrypted keys', async function() {
|
||||
const key = await openpgp.readArmoredKey(priv_key_rsa);
|
||||
expect(key.primaryKey.isDummy()).to.be.false;
|
||||
expect(key.primaryKey.makeDummy()).to.not.throw;
|
||||
expect(key.primaryKey.isDummy()).to.be.true;
|
||||
// dummy primary key should always be marked as not decrypted
|
||||
await expect(key.decrypt('hello world')).to.be.fulfilled;
|
||||
expect(key.primaryKey.isDummy()).to.be.true;
|
||||
expect(key.primaryKey.isEncrypted === null);
|
||||
expect(key.primaryKey.isDecrypted()).to.be.false;
|
||||
await expect(key.encrypt('hello world')).to.be.fulfilled;
|
||||
expect(key.primaryKey.isDummy()).to.be.true;
|
||||
expect(key.primaryKey.isEncrypted === null);
|
||||
expect(key.primaryKey.isDecrypted()).to.be.false;
|
||||
// confirm that the converted key can be parsed
|
||||
const parsedKeys = (await openpgp.readArmoredKey(key.armor())).keys;
|
||||
expect(parsedKeys).to.be.undefined;
|
||||
});
|
||||
|
||||
it('clearPrivateParams() - check that private key can no longer be used', async function() {
|
||||
const key = await openpgp.readArmoredKey(priv_key_rsa);
|
||||
await key.decrypt('hello world');
|
||||
|
|
|
@ -878,9 +878,9 @@ hUhMKMuiM3pRwdIyDOItkUWQmjEEw7/XmhgInkXsCw==
|
|||
expect(msg.signatures).to.have.length(1);
|
||||
expect(msg.signatures[0].valid).to.be.true;
|
||||
expect(msg.signatures[0].signature.packets.length).to.equal(1);
|
||||
await expect(openpgp.sign({ message: openpgp.Message.fromText('test'), privateKeys: [priv_key_gnupg_ext] })).to.eventually.be.rejectedWith(/Missing key parameters/);
|
||||
await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext })).to.eventually.be.rejectedWith(/Missing key parameters/);
|
||||
await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext_2, passphrase: 'test' })).to.eventually.be.rejectedWith(/Missing key parameters/);
|
||||
await expect(openpgp.sign({ message: openpgp.Message.fromText('test'), privateKeys: [priv_key_gnupg_ext] })).to.eventually.be.rejectedWith(/Cannot sign with a gnu-dummy key/);
|
||||
await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext })).to.eventually.be.rejectedWith(/Cannot reformat a gnu-dummy primary key/);
|
||||
await expect(openpgp.reformatKey({ userIds: { name: 'test' }, privateKey: priv_key_gnupg_ext_2, passphrase: 'test' })).to.eventually.be.rejectedWith(/Cannot reformat a gnu-dummy primary key/);
|
||||
await priv_key_gnupg_ext.encrypt("abcd");
|
||||
expect(priv_key_gnupg_ext.isDecrypted()).to.be.false;
|
||||
const primaryKey_packet2 = priv_key_gnupg_ext.primaryKey.write();
|
||||
|
|
Loading…
Reference in New Issue
Block a user