mirror of
				https://github.com/openpgpjs/openpgpjs.git
				synced 2025-10-14 00:59:29 +00:00 
			
		
		
		
	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 |  * @static | ||||||
|  */ |  */ | ||||||
| export function generate(options) { | export function generate(options) { | ||||||
|   var packetlist, secretKeyPacket, userIdPacket, dataToSign, signaturePacket, secretSubkeyPacket, subkeySignaturePacket; |   var secretKeyPacket, secretSubkeyPacket; | ||||||
|   return Promise.resolve().then(() => { |   return Promise.resolve().then(() => { | ||||||
|     options.keyType = options.keyType || enums.publicKey.rsa_encrypt_sign; |     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
 |     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]; |       options.userIds = [options.userIds]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     return Promise.all([generateSecretKey(), generateSecretSubkey()]).then(wrapKeyObject); |     return Promise.all([generateSecretKey(), generateSecretSubkey()]).then(() => { | ||||||
|  |       return wrapKeyObject(secretKeyPacket, secretSubkeyPacket, options); | ||||||
|  |     }); | ||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   function generateSecretKey() { |   function generateSecretKey() { | ||||||
| @ -977,84 +979,123 @@ export function generate(options) { | |||||||
|     secretSubkeyPacket.algorithm = enums.read(enums.publicKey, options.keyType); |     secretSubkeyPacket.algorithm = enums.read(enums.publicKey, options.keyType); | ||||||
|     return secretSubkeyPacket.generate(options.numBits); |     return secretSubkeyPacket.generate(options.numBits); | ||||||
|   } |   } | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
|   function wrapKeyObject() { | /** | ||||||
|     // set passphrase protection
 |  * Reformats and signs an OpenPGP with a given User ID. Currently only supports RSA keys. | ||||||
|     if (options.passphrase) { |  * @param {module:key~Key} options.privateKey   The private key to reformat | ||||||
|       secretKeyPacket.encrypt(options.passphrase); |  * @param {module:enums.publicKey} [options.keyType=module:enums.publicKey.rsa_encrypt_sign] | ||||||
|       secretSubkeyPacket.encrypt(options.passphrase); |  * @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(); |     if (!options.passphrase) { // Key without passphrase is unlocked by definition
 | ||||||
| 
 |       options.unlocked = true; | ||||||
|     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 (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')); |   })).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. |  * Unlock a private key with your passphrase. | ||||||
|  * @param  {Key} privateKey      the private key that is to be decrypted |  * @param  {Key} privateKey      the private key that is to be decrypted | ||||||
|  | |||||||
| @ -890,6 +890,69 @@ var pgp_desktop_priv = | |||||||
|       done(); |       done(); | ||||||
|     }).catch(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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Bart Butler
						Bart Butler