Merge pull request #517 from openpgpjs/userID
Key reformatting and resigning
This commit is contained in:
commit
6be9ddde59
189
src/key.js
189
src/key.js
|
@ -949,7 +949,7 @@ export function readArmored(armoredText) {
|
|||
* @static
|
||||
*/
|
||||
export function generate(options) {
|
||||
var packetlist, secretKeyPacket, userIdPacket, dataToSign, signaturePacket, secretSubkeyPacket, subkeySignaturePacket;
|
||||
var secretKeyPacket, secretSubkeyPacket;
|
||||
return Promise.resolve().then(() => {
|
||||
options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign;
|
||||
if (options.keyType !== enums.publicKey.rsa_encrypt_sign) { // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated
|
||||
|
@ -963,7 +963,9 @@ export function generate(options) {
|
|||
options.userIds = [options.userIds];
|
||||
}
|
||||
|
||||
return Promise.all([generateSecretKey(), generateSecretSubkey()]).then(wrapKeyObject);
|
||||
return Promise.all([generateSecretKey(), generateSecretSubkey()]).then(() => {
|
||||
return wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options);
|
||||
});
|
||||
});
|
||||
|
||||
function generateSecretKey() {
|
||||
|
@ -977,84 +979,123 @@ export function generate(options) {
|
|||
secretSubkeyPacket.algorithm = enums.read(enums.publicKey, options.keyType);
|
||||
return secretSubkeyPacket.generate(options.numBits);
|
||||
}
|
||||
}
|
||||
|
||||
function wrapKeyObject() {
|
||||
// set passphrase protection
|
||||
if (options.passphrase) {
|
||||
secretKeyPacket.encrypt(options.passphrase);
|
||||
secretSubkeyPacket.encrypt(options.passphrase);
|
||||
/**
|
||||
* Reformats and signs an OpenPGP with a given User ID. Currently only supports RSA keys.
|
||||
* @param {module:key~Key} options.privateKey The private key to reformat
|
||||
* @param {module:enums.publicKey} [options.keyType=module:enums.publicKey.rsa_encrypt_sign]
|
||||
* @param {String|Array<String>} options.userIds assumes already in form of "User Name <username@email.com>"
|
||||
If array is used, the first userId is set as primary user Id
|
||||
* @param {String} options.passphrase The passphrase used to encrypt the resulting private key
|
||||
* @param {Boolean} [options.unlocked=false] The secret part of the generated key is unlocked
|
||||
* @param {Number} [options.keyExpirationTime=0] The number of seconds after the key creation time that the key expires
|
||||
* @return {module:key~Key}
|
||||
* @static
|
||||
*/
|
||||
export function reformat(options) {
|
||||
var secretKeyPacket, secretSubkeyPacket;
|
||||
return Promise.resolve().then(() => {
|
||||
|
||||
options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign;
|
||||
if (options.keyType !== enums.publicKey.rsa_encrypt_sign) { // RSA Encrypt-Only and RSA Sign-Only are deprecated and SHOULD NOT be generated
|
||||
throw new Error('Only RSA Encrypt or Sign supported');
|
||||
}
|
||||
|
||||
packetlist = new packet.List();
|
||||
|
||||
packetlist.push(secretKeyPacket);
|
||||
|
||||
options.userIds.forEach(function(userId, index) {
|
||||
|
||||
userIdPacket = new packet.Userid();
|
||||
userIdPacket.read(util.str2Uint8Array(userId));
|
||||
|
||||
dataToSign = {};
|
||||
dataToSign.userid = userIdPacket;
|
||||
dataToSign.key = secretKeyPacket;
|
||||
signaturePacket = new packet.Signature();
|
||||
signaturePacket.signatureType = enums.signature.cert_generic;
|
||||
signaturePacket.publicKeyAlgorithm = options.keyType;
|
||||
signaturePacket.hashAlgorithm = config.prefer_hash_algorithm;
|
||||
signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data];
|
||||
signaturePacket.preferredSymmetricAlgorithms = [];
|
||||
// prefer aes256, aes128, then aes192 (no WebCrypto support: https://www.chromium.org/blink/webcrypto#TOC-AES-support)
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes256);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes128);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes192);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.cast5);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.tripledes);
|
||||
signaturePacket.preferredHashAlgorithms = [];
|
||||
// prefer fast asm.js implementations (SHA-256, SHA-1)
|
||||
signaturePacket.preferredHashAlgorithms.push(enums.hash.sha256);
|
||||
signaturePacket.preferredHashAlgorithms.push(enums.hash.sha1);
|
||||
signaturePacket.preferredHashAlgorithms.push(enums.hash.sha512);
|
||||
signaturePacket.preferredCompressionAlgorithms = [];
|
||||
signaturePacket.preferredCompressionAlgorithms.push(enums.compression.zlib);
|
||||
signaturePacket.preferredCompressionAlgorithms.push(enums.compression.zip);
|
||||
if (index === 0) {
|
||||
signaturePacket.isPrimaryUserID = true;
|
||||
}
|
||||
if (config.integrity_protect) {
|
||||
signaturePacket.features = [];
|
||||
signaturePacket.features.push(1); // Modification Detection
|
||||
}
|
||||
if (options.keyExpirationTime > 0) {
|
||||
signaturePacket.keyExpirationTime = options.keyExpirationTime;
|
||||
signaturePacket.keyNeverExpires = false;
|
||||
}
|
||||
signaturePacket.sign(secretKeyPacket, dataToSign);
|
||||
|
||||
packetlist.push(userIdPacket);
|
||||
packetlist.push(signaturePacket);
|
||||
|
||||
});
|
||||
|
||||
dataToSign = {};
|
||||
dataToSign.key = secretKeyPacket;
|
||||
dataToSign.bind = secretSubkeyPacket;
|
||||
subkeySignaturePacket = new packet.Signature();
|
||||
subkeySignaturePacket.signatureType = enums.signature.subkey_binding;
|
||||
subkeySignaturePacket.publicKeyAlgorithm = options.keyType;
|
||||
subkeySignaturePacket.hashAlgorithm = config.prefer_hash_algorithm;
|
||||
subkeySignaturePacket.keyFlags = [enums.keyFlags.encrypt_communication | enums.keyFlags.encrypt_storage];
|
||||
subkeySignaturePacket.sign(secretKeyPacket, dataToSign);
|
||||
|
||||
packetlist.push(secretSubkeyPacket);
|
||||
packetlist.push(subkeySignaturePacket);
|
||||
|
||||
if (!options.unlocked) {
|
||||
secretKeyPacket.clearPrivateMPIs();
|
||||
secretSubkeyPacket.clearPrivateMPIs();
|
||||
if (!options.passphrase) { // Key without passphrase is unlocked by definition
|
||||
options.unlocked = true;
|
||||
}
|
||||
if (String.prototype.isPrototypeOf(options.userIds) || typeof options.userIds === 'string') {
|
||||
options.userIds = [options.userIds];
|
||||
}
|
||||
var packetlist = options.privateKey.toPacketlist();
|
||||
for (var i = 0; i < packetlist.length; i++) {
|
||||
if (packetlist[i].tag === enums.packet.secretKey) {
|
||||
secretKeyPacket = packetlist[i];
|
||||
} else if (packetlist[i].tag === enums.packet.secretSubkey) {
|
||||
secretSubkeyPacket = packetlist[i];
|
||||
}
|
||||
}
|
||||
return wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options);
|
||||
});
|
||||
}
|
||||
|
||||
return new Key(packetlist);
|
||||
function wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options) {
|
||||
// set passphrase protection
|
||||
if (options.passphrase) {
|
||||
secretKeyPacket.encrypt(options.passphrase);
|
||||
secretSubkeyPacket.encrypt(options.passphrase);
|
||||
}
|
||||
|
||||
var packetlist = new packet.List();
|
||||
|
||||
packetlist.push(secretKeyPacket);
|
||||
|
||||
options.userIds.forEach(function(userId, index) {
|
||||
|
||||
var userIdPacket = new packet.Userid();
|
||||
userIdPacket.read(util.str2Uint8Array(userId));
|
||||
|
||||
var dataToSign = {};
|
||||
dataToSign.userid = userIdPacket;
|
||||
dataToSign.key = secretKeyPacket;
|
||||
var signaturePacket = new packet.Signature();
|
||||
signaturePacket.signatureType = enums.signature.cert_generic;
|
||||
signaturePacket.publicKeyAlgorithm = options.keyType;
|
||||
signaturePacket.hashAlgorithm = config.prefer_hash_algorithm;
|
||||
signaturePacket.keyFlags = [enums.keyFlags.certify_keys | enums.keyFlags.sign_data];
|
||||
signaturePacket.preferredSymmetricAlgorithms = [];
|
||||
// prefer aes256, aes128, then aes192 (no WebCrypto support: https://www.chromium.org/blink/webcrypto#TOC-AES-support)
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes256);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes128);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.aes192);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.cast5);
|
||||
signaturePacket.preferredSymmetricAlgorithms.push(enums.symmetric.tripledes);
|
||||
signaturePacket.preferredHashAlgorithms = [];
|
||||
// prefer fast asm.js implementations (SHA-256, SHA-1)
|
||||
signaturePacket.preferredHashAlgorithms.push(enums.hash.sha256);
|
||||
signaturePacket.preferredHashAlgorithms.push(enums.hash.sha1);
|
||||
signaturePacket.preferredHashAlgorithms.push(enums.hash.sha512);
|
||||
signaturePacket.preferredCompressionAlgorithms = [];
|
||||
signaturePacket.preferredCompressionAlgorithms.push(enums.compression.zlib);
|
||||
signaturePacket.preferredCompressionAlgorithms.push(enums.compression.zip);
|
||||
if (index === 0) {
|
||||
signaturePacket.isPrimaryUserID = true;
|
||||
}
|
||||
if (config.integrity_protect) {
|
||||
signaturePacket.features = [];
|
||||
signaturePacket.features.push(1); // Modification Detection
|
||||
}
|
||||
if (options.keyExpirationTime > 0) {
|
||||
signaturePacket.keyExpirationTime = options.keyExpirationTime;
|
||||
signaturePacket.keyNeverExpires = false;
|
||||
}
|
||||
signaturePacket.sign(secretKeyPacket, dataToSign);
|
||||
|
||||
packetlist.push(userIdPacket);
|
||||
packetlist.push(signaturePacket);
|
||||
|
||||
});
|
||||
|
||||
var dataToSign = {};
|
||||
dataToSign.key = secretKeyPacket;
|
||||
dataToSign.bind = secretSubkeyPacket;
|
||||
var subkeySignaturePacket = new packet.Signature();
|
||||
subkeySignaturePacket.signatureType = enums.signature.subkey_binding;
|
||||
subkeySignaturePacket.publicKeyAlgorithm = options.keyType;
|
||||
subkeySignaturePacket.hashAlgorithm = config.prefer_hash_algorithm;
|
||||
subkeySignaturePacket.keyFlags = [enums.keyFlags.encrypt_communication | enums.keyFlags.encrypt_storage];
|
||||
subkeySignaturePacket.sign(secretKeyPacket, dataToSign);
|
||||
|
||||
packetlist.push(secretSubkeyPacket);
|
||||
packetlist.push(subkeySignaturePacket);
|
||||
|
||||
if (!options.unlocked) {
|
||||
secretKeyPacket.clearPrivateMPIs();
|
||||
secretSubkeyPacket.clearPrivateMPIs();
|
||||
}
|
||||
|
||||
return new Key(packetlist);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -113,6 +113,32 @@ export function generateKey({ userIds=[], passphrase, numBits=2048, unlocked=fal
|
|||
})).catch(onError.bind(null, 'Error generating keypair'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new OpenPGP key pair. Currently only supports RSA keys. Primary and subkey will be of same type.
|
||||
* @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 {Boolean} unlocked (optional) If the returned secret part of the generated key is unlocked
|
||||
* @param {Number} keyExpirationTime (optional) The number of seconds after the key creation time that the key expires
|
||||
* @return {Promise<Object>} The generated key object in the form:
|
||||
* { key:Key, privateKeyArmored:String, publicKeyArmored:String }
|
||||
* @static
|
||||
*/
|
||||
export function reformatKey({ privateKey, userIds=[], passphrase="", unlocked=false, keyExpirationTime=0 } = {}) {
|
||||
const options = formatUserIds({ privateKey, userIds, passphrase, unlocked, keyExpirationTime });
|
||||
|
||||
if (!util.getWebCryptoAll() && asyncProxy) { // use web worker if web crypto apis are not supported
|
||||
return asyncProxy.delegate('reformatKey', options);
|
||||
}
|
||||
|
||||
return key.reformat(options).then(newKey => ({
|
||||
|
||||
key: newKey,
|
||||
privateKeyArmored: newKey.armor(),
|
||||
publicKeyArmored: newKey.toPublic().armor()
|
||||
|
||||
})).catch(onError.bind(null, 'Error reformatting keypair'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock a private key with your passphrase.
|
||||
* @param {Key} privateKey the private key that is to be decrypted
|
||||
|
|
|
@ -890,6 +890,69 @@ var pgp_desktop_priv =
|
|||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('Reformat key without passphrase', function(done) {
|
||||
var userId1 = 'test1 <a@b.com>';
|
||||
var userId2 = 'test2 <b@a.com>';
|
||||
var opt = {numBits: 512, userIds: userId1};
|
||||
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
|
||||
openpgp.generateKey(opt).then(function(key) {
|
||||
key = key.key
|
||||
expect(key.users.length).to.equal(1);
|
||||
expect(key.users[0].userId.userid).to.equal(userId1);
|
||||
expect(key.primaryKey.isDecrypted).to.be.true;
|
||||
opt.privateKey = key;
|
||||
opt.userIds = userId2;
|
||||
openpgp.reformatKey(opt).then(function(newKey) {
|
||||
newKey = newKey.key
|
||||
expect(newKey.users.length).to.equal(1);
|
||||
expect(newKey.users[0].userId.userid).to.equal(userId2);
|
||||
expect(newKey.primaryKey.isDecrypted).to.be.true;
|
||||
done();
|
||||
}).catch(done);
|
||||
}).catch(done);
|
||||
});
|
||||
it('Reformat and encrypt key', function(done) {
|
||||
var userId1 = 'test1 <a@b.com>';
|
||||
var userId2 = 'test2 <b@c.com>';
|
||||
var userId3 = 'test3 <c@d.com>';
|
||||
var opt = {numBits: 512, userIds: userId1};
|
||||
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
|
||||
openpgp.generateKey(opt).then(function(key) {
|
||||
key = key.key
|
||||
opt.privateKey = key;
|
||||
opt.userIds = [userId2, userId3];
|
||||
opt.passphrase = '123';
|
||||
openpgp.reformatKey(opt).then(function(newKey) {
|
||||
newKey = newKey.key
|
||||
expect(newKey.users.length).to.equal(2);
|
||||
expect(newKey.users[0].userId.userid).to.equal(userId2);
|
||||
expect(newKey.primaryKey.isDecrypted).to.be.false;
|
||||
newKey.decrypt('123');
|
||||
expect(newKey.primaryKey.isDecrypted).to.be.true;
|
||||
done();
|
||||
}).catch(done);
|
||||
}).catch(done);
|
||||
});
|
||||
it('Sign and encrypt with reformatted key', function(done) {
|
||||
var userId1 = 'test1 <a@b.com>';
|
||||
var userId2 = 'test2 <b@a.com>';
|
||||
var opt = {numBits: 512, userIds: userId1};
|
||||
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
|
||||
openpgp.generateKey(opt).then(function(key) {
|
||||
key = key.key
|
||||
opt.privateKey = key;
|
||||
opt.userIds = userId2;
|
||||
openpgp.reformatKey(opt).then(function(newKey) {
|
||||
newKey = newKey.key
|
||||
openpgp.encrypt({data: 'hello', publicKeys: newKey.toPublic(), privateKeys: newKey, armor: true}).then(function(encrypted) {
|
||||
openpgp.decrypt({message: openpgp.message.readArmored(encrypted.data), privateKey: newKey, publicKeys: newKey.toPublic()}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal('hello');
|
||||
expect(decrypted.signatures[0].valid).to.be.true;
|
||||
done();
|
||||
}).catch(done);
|
||||
}).catch(done);
|
||||
}).catch(done);
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user