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) {
|
export async function reformat(options) {
|
||||||
options = sanitize(options);
|
options = sanitize(options);
|
||||||
|
|
||||||
try {
|
if (options.privateKey.primaryKey.isDummy()) {
|
||||||
const isDecrypted = options.privateKey.getKeys().every(key => key.isDecrypted());
|
throw new Error('Cannot reformat a gnu-dummy primary key');
|
||||||
if (!isDecrypted) {
|
}
|
||||||
await options.privateKey.decrypt();
|
|
||||||
}
|
const isDecrypted = options.privateKey.getKeys().every(({ keyPacket }) => keyPacket.isDecrypted());
|
||||||
} catch (err) {
|
if (!isDecrypted) {
|
||||||
throw new Error('Key not decrypted');
|
throw new Error('Key is not decrypted');
|
||||||
}
|
}
|
||||||
|
|
||||||
const packetlist = options.privateKey.toPacketlist();
|
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
|
* @returns {module:packet/signature} signature packet
|
||||||
*/
|
*/
|
||||||
export async function createSignaturePacket(dataToSign, privateKey, signingKeyPacket, signatureProperties, date, userId, detached = false, streaming = false) {
|
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()) {
|
if (!signingKeyPacket.isDecrypted()) {
|
||||||
throw new Error('Private key is not decrypted.');
|
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#hasSameFingerprintAs as Key#hasSameFingerprintAs
|
||||||
* @borrows PublicKeyPacket#getAlgorithmInfo as Key#getAlgorithmInfo
|
* @borrows PublicKeyPacket#getAlgorithmInfo as Key#getAlgorithmInfo
|
||||||
* @borrows PublicKeyPacket#getCreationTime as Key#getCreationTime
|
* @borrows PublicKeyPacket#getCreationTime as Key#getCreationTime
|
||||||
* @borrows PublicKeyPacket#isDecrypted as Key#isDecrypted
|
|
||||||
*/
|
*/
|
||||||
class Key {
|
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
|
* Check whether the private and public primary key parameters correspond
|
||||||
* Together with verification of binding signatures, this guarantees key integrity
|
* 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}`);
|
throw new Error(`rsaBits should be at least ${config.minRsaBits}, got: ${options.rsaBits}`);
|
||||||
}
|
}
|
||||||
const secretKeyPacket = this.primaryKey;
|
const secretKeyPacket = this.primaryKey;
|
||||||
|
if (secretKeyPacket.isDummy()) {
|
||||||
|
throw new Error("Cannot add subkey to gnu-dummy primary key");
|
||||||
|
}
|
||||||
if (!secretKeyPacket.isDecrypted()) {
|
if (!secretKeyPacket.isDecrypted()) {
|
||||||
throw new Error("Key is not decrypted");
|
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] =
|
Key.prototype[name] =
|
||||||
SubKey.prototype[name];
|
SubKey.prototype[name];
|
||||||
});
|
});
|
||||||
|
|
|
@ -208,7 +208,7 @@ export class Message {
|
||||||
// do not check key expiration to allow decryption of old messages
|
// do not check key expiration to allow decryption of old messages
|
||||||
const privateKeyPackets = (await privateKey.getDecryptionKeys(keyPacket.publicKeyId, null)).map(key => key.keyPacket);
|
const privateKeyPackets = (await privateKey.getDecryptionKeys(keyPacket.publicKeyId, null)).map(key => key.keyPacket);
|
||||||
await Promise.all(privateKeyPackets.map(async function(privateKeyPacket) {
|
await Promise.all(privateKeyPackets.map(async function(privateKeyPacket) {
|
||||||
if (!privateKeyPacket) {
|
if (!privateKeyPacket || privateKeyPacket.isDummy()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!privateKeyPacket.isDecrypted()) {
|
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}
|
* @returns {Boolean|null}
|
||||||
*/
|
*/
|
||||||
isDecrypted() {
|
isDecrypted() {
|
||||||
|
@ -244,20 +245,18 @@ class SecretKeyPacket extends PublicKeyPacket {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove private key material, converting the key to a dummy one
|
* 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
|
* The resulting key cannot be used for signing/decrypting but can still verify signatures.
|
||||||
*/
|
*/
|
||||||
makeDummy() {
|
makeDummy() {
|
||||||
if (this.isDummy()) {
|
if (this.isDummy()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.isDecrypted()) {
|
if (this.isDecrypted()) {
|
||||||
// this is technically not needed, but makes the conversion simpler
|
this.clearPrivateParams();
|
||||||
throw new Error("Key is not decrypted");
|
|
||||||
}
|
}
|
||||||
this.clearPrivateParams();
|
this.isEncrypted = null;
|
||||||
this.keyMaterial = null;
|
this.keyMaterial = null;
|
||||||
this.isEncrypted = false;
|
|
||||||
this.s2k = new type_s2k();
|
this.s2k = new type_s2k();
|
||||||
this.s2k.algorithm = 0;
|
this.s2k.algorithm = 0;
|
||||||
this.s2k.c = 0;
|
this.s2k.c = 0;
|
||||||
|
@ -325,8 +324,7 @@ class SecretKeyPacket extends PublicKeyPacket {
|
||||||
*/
|
*/
|
||||||
async decrypt(passphrase) {
|
async decrypt(passphrase) {
|
||||||
if (this.isDummy()) {
|
if (this.isDummy()) {
|
||||||
this.isEncrypted = false;
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isDecrypted()) {
|
if (this.isDecrypted()) {
|
||||||
|
@ -418,7 +416,6 @@ class SecretKeyPacket extends PublicKeyPacket {
|
||||||
*/
|
*/
|
||||||
clearPrivateParams() {
|
clearPrivateParams() {
|
||||||
if (this.isDummy()) {
|
if (this.isDummy()) {
|
||||||
this.isEncrypted = true;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2616,7 +2616,7 @@ function versionSpecificTests() {
|
||||||
return openpgp.reformatKey({ privateKey: original.key, userIds: { name: 'test2', email: 'a@b.com' }, passphrase: '1234' }).then(function() {
|
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');
|
throw new Error('reformatKey should result in error when key not decrypted');
|
||||||
}).catch(function(error) {
|
}).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');
|
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() {
|
it('makeDummy() - the converted key can be parsed', async function() {
|
||||||
const { key } = await openpgp.generateKey({ userIds: { name: 'dummy', email: 'dummy@alice.com' } });
|
const { key } = await openpgp.generateKey({ userIds: { name: 'dummy', email: 'dummy@alice.com' } });
|
||||||
key.primaryKey.makeDummy();
|
key.primaryKey.makeDummy();
|
||||||
|
@ -2950,7 +2970,7 @@ module.exports = () => describe('Key', function() {
|
||||||
key.primaryKey.makeDummy();
|
key.primaryKey.makeDummy();
|
||||||
expect(key.primaryKey.isDummy()).to.be.true;
|
expect(key.primaryKey.isDummy()).to.be.true;
|
||||||
await key.validate();
|
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() {
|
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;
|
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() {
|
it('clearPrivateParams() - check that private key can no longer be used', async function() {
|
||||||
const key = await openpgp.readArmoredKey(priv_key_rsa);
|
const key = await openpgp.readArmoredKey(priv_key_rsa);
|
||||||
await key.decrypt('hello world');
|
await key.decrypt('hello world');
|
||||||
|
|
|
@ -878,9 +878,9 @@ hUhMKMuiM3pRwdIyDOItkUWQmjEEw7/XmhgInkXsCw==
|
||||||
expect(msg.signatures).to.have.length(1);
|
expect(msg.signatures).to.have.length(1);
|
||||||
expect(msg.signatures[0].valid).to.be.true;
|
expect(msg.signatures[0].valid).to.be.true;
|
||||||
expect(msg.signatures[0].signature.packets.length).to.equal(1);
|
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.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(/Missing key parameters/);
|
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(/Missing key parameters/);
|
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");
|
await priv_key_gnupg_ext.encrypt("abcd");
|
||||||
expect(priv_key_gnupg_ext.isDecrypted()).to.be.false;
|
expect(priv_key_gnupg_ext.isDecrypted()).to.be.false;
|
||||||
const primaryKey_packet2 = priv_key_gnupg_ext.primaryKey.write();
|
const primaryKey_packet2 = priv_key_gnupg_ext.primaryKey.write();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user