Test encrypt/decryptSessionKey and finalize api, review docs

This commit is contained in:
Tankred Hase 2016-02-10 12:27:12 +07:00
parent 128a95ace4
commit 6547b4ef68
4 changed files with 160 additions and 104 deletions

View File

@ -85,22 +85,21 @@ Message.prototype.getSigningKeyIds = function() {
}; };
/** /**
* Decrypt the message * Decrypt the message. Either a private key, a session key, or a password must be specified.
* @param {module:key~Key} privateKey private key with decrypted secret data * @param {Key} privateKey (optional) private key with decrypted secret data
* @param {String} sessionKey session key as a binary string * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String }
* @param {String} password password used to decrypt * @param {String} password (optional) password used to decrypt
* @return {Array<module:message~Message>} new message with decrypted content * @return {Message} new message with decrypted content
*/ */
Message.prototype.decrypt = function(privateKey, sessionKey, password) { Message.prototype.decrypt = function(privateKey, sessionKey, password) {
var keyObj = this.decryptSessionKey(privateKey, sessionKey, password); var keyObj = sessionKey || this.decryptSessionKey(privateKey, password);
if (!keyObj) { if (!keyObj || !util.isUint8Array(keyObj.data) || !util.isString(keyObj.algorithm)) {
// nothing to decrypt return unmodified message throw new Error('Invalid session key for decryption.');
return this;
} }
var symEncryptedPacketlist = this.packets.filterByTag(enums.packet.symmetricallyEncrypted, enums.packet.symEncryptedIntegrityProtected); var symEncryptedPacketlist = this.packets.filterByTag(enums.packet.symmetricallyEncrypted, enums.packet.symEncryptedIntegrityProtected);
if (symEncryptedPacketlist.length !== 0) { if (symEncryptedPacketlist.length !== 0) {
var symEncryptedPacket = symEncryptedPacketlist[0]; var symEncryptedPacket = symEncryptedPacketlist[0];
symEncryptedPacket.decrypt(keyObj.algo, keyObj.key); symEncryptedPacket.decrypt(keyObj.algorithm, keyObj.data);
var resultMsg = new Message(symEncryptedPacket.packets); var resultMsg = new Message(symEncryptedPacket.packets);
// remove packets after decryption // remove packets after decryption
symEncryptedPacket.packets = new packet.List(); symEncryptedPacket.packets = new packet.List();
@ -109,21 +108,22 @@ Message.prototype.decrypt = function(privateKey, sessionKey, password) {
}; };
/** /**
* Decrypt session key * Decrypt an encrypted session key either with a private key or a password.
* @param {module:key~Key} privateKey private key with decrypted secret data * @param {Key} privateKey (optional) private key with decrypted secret data
* @param {String} sessionKey session key as a binary string * @param {String} password (optional) password used to decrypt
* @param {String} password password used to decrypt * @return {Object} object with sessionKey, algorithm in the form:
* @return {Object} object with sessionKey, algo * { data:Uint8Array, algorithm:String }
*/ */
Message.prototype.decryptSessionKey = function(privateKey, sessionKey, password) { Message.prototype.decryptSessionKey = function(privateKey, password) {
var keyPacket; var keyPacket;
if (sessionKey || password) {
if (password) {
var symEncryptedSessionKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey); var symEncryptedSessionKeyPacketlist = this.packets.filterByTag(enums.packet.symEncryptedSessionKey);
var symLength = symEncryptedSessionKeyPacketlist.length; var symLength = symEncryptedSessionKeyPacketlist.length;
for (var i = 0; i < symLength; i++) { for (var i = 0; i < symLength; i++) {
keyPacket = symEncryptedSessionKeyPacketlist[i]; keyPacket = symEncryptedSessionKeyPacketlist[i];
try { try {
keyPacket.decrypt(sessionKey || password); keyPacket.decrypt(password);
break; break;
} }
catch(err) { catch(err) {
@ -160,7 +160,10 @@ Message.prototype.decryptSessionKey = function(privateKey, sessionKey, password)
} }
if (keyPacket) { if (keyPacket) {
return { key:keyPacket.sessionKey, algo:keyPacket.sessionKeyAlgorithm }; return {
data: keyPacket.sessionKey,
algorithm: keyPacket.sessionKeyAlgorithm
};
} }
}; };
@ -196,14 +199,12 @@ Message.prototype.getText = function() {
}; };
/** /**
* Encrypt the message * Encrypt the message either with public keys, passwords, or both at once.
* @param {(Array<module:key~Key>|module:key~Key)} public key(s) for message encryption * @param {Array<Key>} keys (optional) public key(s) for message encryption
* @param {(Array<String>|String)} password(s) for message encryption * @param {Array<String>} passwords (optional) password(s) for message encryption
* @return {Array<module:message~Message>} new message with encrypted content * @return {Message} new message with encrypted content
*/ */
Message.prototype.encrypt = function(keys, passwords) { Message.prototype.encrypt = function(keys, passwords) {
/** Choose symAlgo */
var symAlgo; var symAlgo;
if (keys) { if (keys) {
symAlgo = keyModule.getPreferredSymAlgo(keys); symAlgo = keyModule.getPreferredSymAlgo(keys);
@ -214,7 +215,6 @@ Message.prototype.encrypt = function(keys, passwords) {
} }
var sessionKey = crypto.generateSessionKey(enums.read(enums.symmetric, symAlgo)); var sessionKey = crypto.generateSessionKey(enums.read(enums.symmetric, symAlgo));
var msg = encryptSessionKey(sessionKey, enums.read(enums.symmetric, symAlgo), keys, passwords); var msg = encryptSessionKey(sessionKey, enums.read(enums.symmetric, symAlgo), keys, passwords);
var packetlist = msg.packets; var packetlist = msg.packets;
@ -229,28 +229,21 @@ Message.prototype.encrypt = function(keys, passwords) {
packetlist.push(symEncryptedPacket); packetlist.push(symEncryptedPacket);
// remove packets after encryption // remove packets after encryption
symEncryptedPacket.packets = new packet.List(); symEncryptedPacket.packets = new packet.List();
return msg; return msg;
}; };
/** /**
* Encrypt a session key * Encrypt a session key either with public keys, passwords, or both at once.
* @param {String} session key for encryption * @param {Uint8Array} sessionKey session key for encryption
* @param {String} session key algorithm * @param {String} symAlgo session key algorithm
* @param {(Array<module:key~Key>|module:key~Key)} public key(s) for message encryption * @param {Array<Key>} publicKeys (optional) public key(s) for message encryption
* @param {(Array<String>|String)} password(s) for message encryption * @param {Array<String>} passwords (optional) for message encryption
* @return {Array<module:message~Message>} new message with encrypted content * @return {Message} new message with encrypted content
*/ */
export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords) { export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords) {
/** Convert to arrays if necessary */
if (publicKeys && !util.isArray(publicKeys)) {
publicKeys = [publicKeys];
}
if (passwords && !util.isArray(passwords)) {
passwords = [passwords];
}
var packetlist = new packet.List(); var packetlist = new packet.List();
if (publicKeys) { if (publicKeys) {
publicKeys.forEach(function(key) { publicKeys.forEach(function(key) {
var encryptionKeyPacket = key.getEncryptionKeyPacket(); var encryptionKeyPacket = key.getEncryptionKeyPacket();
@ -267,6 +260,7 @@ export function encryptSessionKey(sessionKey, symAlgo, publicKeys, passwords) {
} }
}); });
} }
if (passwords) { if (passwords) {
passwords.forEach(function(password) { passwords.forEach(function(password) {
var symEncryptedSessionKeyPacket = new packet.SymEncryptedSessionKey(); var symEncryptedSessionKeyPacket = new packet.SymEncryptedSessionKey();

View File

@ -147,8 +147,8 @@ export function decryptKey({ privateKey, passphrase }) {
/** /**
* Encrypts message text/data with keys or passwords. Either public keys or passwords must be specified. * Encrypts message text/data with public keys, passwords or both at once. At least either public keys or passwords
* If private keys are specified those will be used to sign the message. * must be specified. If private keys are specified, those will be used to sign the message.
* @param {String|Uint8Array} data text/data to be encrypted as JavaScript binary string or Uint8Array * @param {String|Uint8Array} data text/data to be encrypted as JavaScript binary string or Uint8Array
* @param {Key|Array<Key>} publicKeys (optional) array of keys or single key, used to encrypt the message * @param {Key|Array<Key>} publicKeys (optional) array of keys or single key, used to encrypt the message
* @param {Key|Array<Key>} privateKeys (optional) private keys for signing. If omitted message will not be signed * @param {Key|Array<Key>} privateKeys (optional) private keys for signing. If omitted message will not be signed
@ -186,12 +186,12 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, ar
} }
/** /**
* Decrypts a message with the user's private key, a session key or a password. * Decrypts a message with the user's private key, a session key or a password. Either a private key,
* Either a private key, a session key or a password must be specified. * a session key or a password must be specified.
* @param {Message} message the message object with the encrypted data * @param {Message} message the message object with the encrypted data
* @param {Key} privateKey (optional) private key with decrypted secret key data or session key * @param {Key} privateKey (optional) private key with decrypted secret key data or session key
* @param {Key|Array<Key>} publicKeys (optional) array of public keys or single key, to verify signatures * @param {Key|Array<Key>} publicKeys (optional) array of public keys or single key, to verify signatures
* @param {String} sessionKey (optional) session key as a binary string * @param {Object} sessionKey (optional) session key in the form: { data:Uint8Array, algorithm:String }
* @param {String} password (optional) single password to decrypt the message * @param {String} password (optional) single password to decrypt the message
* @param {String} format (optional) return data format either as 'utf8' or 'binary' * @param {String} format (optional) return data format either as 'utf8' or 'binary'
* @return {Promise<Object>} decrypted and verified message in the form: * @return {Promise<Object>} decrypted and verified message in the form:
@ -226,7 +226,7 @@ export function decrypt({ message, privateKey, publicKeys, sessionKey, password,
/** /**
* Signs a cleartext message * Signs a cleartext message.
* @param {String} data cleartext input to be signed * @param {String} data cleartext input to be signed
* @param {Key|Array<Key>} privateKeys array of keys or single key with decrypted secret key data to sign cleartext * @param {Key|Array<Key>} privateKeys array of keys or single key with decrypted secret key data to sign cleartext
* @param {Boolean} armor (optional) if the return value should be ascii armored or the message object * @param {Boolean} armor (optional) if the return value should be ascii armored or the message object
@ -291,44 +291,48 @@ export function verify({ message, publicKeys }) {
/** /**
* Encrypts session key with public keys or passwords. Either public keys or password must be specified. * Encrypt a symmetric session key with public keys, passwords, or both at once. At least either public keys
* @param {String} sessionKey session key as a binary string * or passwords must be specified.
* @param {String} algo algorithm of sessionKey * @param {Uint8Array} data the session key to be encrypted e.g. 16 random bytes (for aes128)
* @param {String} algorithm algorithm of the symmetric session key e.g. 'aes128' or 'aes256'
* @param {Key|Array<Key>} publicKeys (optional) array of public keys or single key, used to encrypt the key * @param {Key|Array<Key>} publicKeys (optional) array of public keys or single key, used to encrypt the key
* @param {String|Array<String>} passwords (optional) passwords for the message * @param {String|Array<String>} passwords (optional) passwords for the message
* @return {Promise<Message>} Message object containing encrypted key packets * @return {Promise<Message>} the encrypted session key packets contained in a message object
* @static * @static
*/ */
export function encryptSessionKey({ sessionKey, algo, publicKeys, passwords }) { export function encryptSessionKey({ data, algorithm, publicKeys, passwords }) {
checkbinary(data); checkString(algorithm, 'algorithm'); publicKeys = toArray(publicKeys); passwords = toArray(passwords);
if (asyncProxy) { // use web worker if available if (asyncProxy) { // use web worker if available
return asyncProxy.delegate('encryptSessionKey', { sessionKey, algo, publicKeys, passwords }); return asyncProxy.delegate('encryptSessionKey', { data, algorithm, publicKeys, passwords });
} }
return execute(() => ({ return execute(() => ({
data: messageLib.encryptSessionKey(sessionKey, algo, publicKeys, passwords).packets.write() message: messageLib.encryptSessionKey(data, algorithm, publicKeys, passwords)
}), 'Error encrypting session key'); }), 'Error encrypting session key');
} }
/** /**
* Decrypts session key with a private key, a session key or password. * Decrypt a symmetric session key with a private key or password. Either a private key or
* Either a private key, session key or a password must be specified. * a password must be specified.
* @param {Message} message the message object with the encrypted session key packets * @param {Message} message a message object containing the encrypted session key packets
* @param {Key} privateKey (optional) private key with decrypted secret key data * @param {Key} privateKey (optional) private key with decrypted secret key data
* @param {String} sessionKey (optional) session key as a binary string * @param {String} password (optional) a single password to decrypt the session key
* @param {String} password (optional) a single password to decrypt the session key * @return {Promise<Object|undefined>} decrypted session key and algorithm in object form:
* @return {Promise<Object|null>} decrypted session key and algorithm in object form: * { data:Uint8Array, algorithm:String }
* { key:String, algo:String } * or 'undefined' if no key packets found
* or null if no key packets found
* @static * @static
*/ */
export function decryptSessionKey({ message, privateKey, sessionKey, password }) { export function decryptSessionKey({ message, privateKey, password }) {
checkMessage(message);
if (asyncProxy) { // use web worker if available if (asyncProxy) { // use web worker if available
return asyncProxy.delegate('decryptSessionKey', { message, privateKey, sessionKey, password }); return asyncProxy.delegate('decryptSessionKey', { message, privateKey, password });
} }
return execute(() => message.decryptSessionKey(privateKey, sessionKey, password), 'Error decrypting session key'); return execute(() => message.decryptSessionKey(privateKey, password), 'Error decrypting session key');
} }
@ -347,6 +351,11 @@ function checkString(data, name) {
throw new Error('Parameter [' + (name || 'data') + '] must be of type String'); throw new Error('Parameter [' + (name || 'data') + '] must be of type String');
} }
} }
function checkbinary(data, name) {
if (!util.isUint8Array(data)) {
throw new Error('Parameter [' + (name || 'data') + '] must be of type Uint8Array');
}
}
function checkData(data, name) { function checkData(data, name) {
if (!util.isUint8Array(data) && !util.isString(data)) { if (!util.isUint8Array(data) && !util.isString(data)) {
throw new Error('Parameter [' + (name || 'data') + '] must be of type String or Uint8Array'); throw new Error('Parameter [' + (name || 'data') + '] must be of type String or Uint8Array');

View File

@ -116,7 +116,7 @@ SymEncryptedIntegrityProtected.prototype.encrypt = function (sessionKeyAlgorithm
* *
* @param {module:enums.symmetric} sessionKeyAlgorithm * @param {module:enums.symmetric} sessionKeyAlgorithm
* The selected symmetric encryption algorithm to be used * The selected symmetric encryption algorithm to be used
* @param {String} key The key of cipher blocksize length to be used * @param {Uint8Array} key The key of cipher blocksize length to be used
* @return {String} The decrypted data of this packet * @return {String} The decrypted data of this packet
*/ */
SymEncryptedIntegrityProtected.prototype.decrypt = function (sessionKeyAlgorithm, key) { SymEncryptedIntegrityProtected.prototype.decrypt = function (sessionKeyAlgorithm, key) {

View File

@ -418,6 +418,94 @@ describe('OpenPGP.js public api tests', function() {
}); });
}); });
describe('encryptSessionKey, decryptSessionKey', function() {
var sk = new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]);
beforeEach(function() {
expect(privateKey.keys[0].decrypt(passphrase)).to.be.true;
});
it('should encrypt with public key', function(done) {
openpgp.encryptSessionKey({
data: sk,
algorithm: 'aes128',
publicKeys: publicKey.keys
}).then(function(encrypted) {
return openpgp.decryptSessionKey({
message: encrypted.message,
privateKey: privateKey.keys[0]
});
}).then(function(decrypted) {
expect(decrypted.data).to.deep.equal(sk);
done();
});
});
it('should encrypt with password', function(done) {
openpgp.encryptSessionKey({
data: sk,
algorithm: 'aes128',
passwords: password1
}).then(function(encrypted) {
return openpgp.decryptSessionKey({
message: encrypted.message,
password: password1
});
}).then(function(decrypted) {
expect(decrypted.data).to.deep.equal(sk);
done();
});
});
it('roundtrip workflow: encrypt, decryptSessionKey, decrypt with pgp key pair', function(done) {
var msgAsciiArmored;
openpgp.encrypt({
data: plaintext,
publicKeys: publicKey.keys
}).then(function(encrypted) {
msgAsciiArmored = encrypted.data;
return openpgp.decryptSessionKey({
message: openpgp.message.readArmored(msgAsciiArmored),
privateKey: privateKey.keys[0]
});
}).then(function(decryptedSessionKey) {
return openpgp.decrypt({
sessionKey: decryptedSessionKey,
message: openpgp.message.readArmored(msgAsciiArmored)
});
}).then(function(decrypted) {
expect(decrypted.data).to.equal(plaintext);
done();
});
});
it('roundtrip workflow: encrypt, decryptSessionKey, decrypt with password', function(done) {
var msgAsciiArmored;
openpgp.encrypt({
data: plaintext,
passwords: password1
}).then(function(encrypted) {
msgAsciiArmored = encrypted.data;
return openpgp.decryptSessionKey({
message: openpgp.message.readArmored(msgAsciiArmored),
password: password1
});
}).then(function(decryptedSessionKey) {
return openpgp.decrypt({
sessionKey: decryptedSessionKey,
message: openpgp.message.readArmored(msgAsciiArmored)
});
}).then(function(decrypted) {
expect(decrypted.data).to.equal(plaintext);
done();
});
});
});
describe('with pgp key pair', function() { describe('with pgp key pair', function() {
var wrong_pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n' + var wrong_pubkey = '-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n' +
'Version: OpenPGP.js v0.9.0\r\n' + 'Version: OpenPGP.js v0.9.0\r\n' +
@ -607,50 +695,15 @@ describe('OpenPGP.js public api tests', function() {
}); });
}); });
it('should encrypt and decrypt with one session key', function(done) {
var encOpt = {
data: plaintext,
passwords: password1
};
var decOpt = {
sessionKey: password1
};
openpgp.encrypt(encOpt).then(function(encrypted) {
decOpt.message = openpgp.message.readArmored(encrypted.data);
return openpgp.decrypt(decOpt);
}).then(function(decrypted) {
expect(decrypted.data).to.equal(plaintext);
done();
});
});
it('should encrypt and decrypt with two session keys and not ascii armor', function(done) {
var encOpt = {
data: plaintext,
passwords: [password1, password2],
armor: false
};
var decOpt = {
sessionKey: password2
};
openpgp.encrypt(encOpt).then(function(encrypted) {
decOpt.message = encrypted.message;
return openpgp.decrypt(decOpt);
}).then(function(decrypted) {
expect(decrypted.data).to.equal(plaintext);
done();
});
});
it('should encrypt and decrypt with binary data and transferable objects', function(done) { it('should encrypt and decrypt with binary data and transferable objects', function(done) {
openpgp.config.zeroCopy = true; // activate transferable objects openpgp.config.zeroCopy = true; // activate transferable objects
var encOpt = { var encOpt = {
data: new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]), data: new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]),
passwords: [password1, password2], passwords: password1,
armor: false armor: false
}; };
var decOpt = { var decOpt = {
sessionKey: password2, password: password1,
format: 'binary' format: 'binary'
}; };
openpgp.encrypt(encOpt).then(function(encrypted) { openpgp.encrypt(encOpt).then(function(encrypted) {