Implement cleartext signed messages

This commit is contained in:
Thomas Oberndörfer 2013-12-02 20:11:21 +01:00
parent be96de5188
commit 7e711510cc
7 changed files with 750 additions and 139 deletions

File diff suppressed because one or more lines are too long

141
src/cleartext.js Normal file
View File

@ -0,0 +1,141 @@
// GPG4Browsers - An OpenPGP implementation in javascript
// Copyright (C) 2011 Recurity Labs GmbH
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
var config = require('./config');
var packet = require('./packet');
var enums = require('./enums.js');
var armor = require('./encoding/armor.js');
/**
* @class
* @classdesc Class that represents an OpenPGP cleartext signed message.
* See http://tools.ietf.org/html/rfc4880#section-7
* @param {String} text The cleartext of the signed message
* @param {packetlist} packetlist The packetlist with signature packets or undefined
* if message not yet signed
*/
function CleartextMessage(text, packetlist) {
if (!(this instanceof CleartextMessage)) {
return new CleartextMessage(packetlist);
}
this.text = text;
this.packets = packetlist || new packet.list();
}
/**
* Returns the key IDs of the keys that signed the cleartext message
* @return {[keyId]} array of keyid objects
*/
CleartextMessage.prototype.getSigningKeyIds = function() {
var keyIds = [];
var signatureList = this.packets.filterByTag(enums.packet.signature);
signatureList.forEach(function(packet) {
keyIds.push(packet.issuerKeyId);
});
return keyIds;
};
/**
* Sign the cleartext message
* @param {[key]} privateKeys private keys with decrypted secret key data for signing
*/
CleartextMessage.prototype.sign = function(privateKeys) {
var packetlist = new packet.list();
for (var i = 0; i < privateKeys.length; i++) {
var signaturePacket = new packet.signature();
signaturePacket.signatureType = enums.signature.text;
signaturePacket.hashAlgorithm = config.prefer_hash_algorithm;
var signingKeyPacket = privateKeys[i].getSigningKeyPacket();
signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm;
if (!signingKeyPacket.isDecrypted) throw new Error('Private key is not decrypted.');
// use literal data packet to convert to canonical <CR><LF> line endings
var literalDataPacket = new packet.literal();
literalDataPacket.setBytes(this.text, enums.read(enums.literal, enums.literal.text));
signaturePacket.sign(signingKeyPacket, literalDataPacket);
packetlist.push(signaturePacket);
}
this.packets = packetlist;
};
/**
* Verify signatures of cleartext signed message
* @param {[key]} publicKeys public keys to verify signatures
* @return {[{'keyid': keyid, 'valid': Boolean}]} list of signer's keyid and validity of signature
*/
CleartextMessage.prototype.verify = function(publicKeys) {
var result = [];
var signatureList = this.packets.filterByTag(enums.packet.signature);
var that = this;
publicKeys.forEach(function(pubKey) {
for (var i = 0; i < signatureList.length; i++) {
var publicKeyPacket = pubKey.getPublicKeyPacket([signatureList[i].issuerKeyId]);
if (publicKeyPacket) {
var verifiedSig = {};
verifiedSig.keyid = signatureList[i].issuerKeyId;
// use literal data packet to convert to canonical <CR><LF> line endings
var literalDataPacket = new packet.literal();
literalDataPacket.setBytes(that.text, enums.read(enums.literal, enums.literal.text));
verifiedSig.status = signatureList[i].verify(publicKeyPacket, literalDataPacket);
result.push(verifiedSig);
break;
}
}
});
return result;
};
/**
* Get cleartext
* @return {String} cleartext of message
*/
CleartextMessage.prototype.getText = function() {
return this.text;
};
/**
* Returns ASCII armored text of cleartext signed message
* @return {String} ASCII armor
*/
CleartextMessage.prototype.armor = function() {
var body = {
hash: enums.read(enums.hash, config.prefer_hash_algorithm).toUpperCase(),
text: this.text,
data: this.packets.write()
}
return armor.encode(enums.armor.signed, body);
};
/**
* reads an OpenPGP cleartext signed message and returns a CleartextMessage object
* @param {String} armoredText text to be parsed
* @return {CleartextMessage} new cleartext message object
*/
function readArmored(armoredText) {
var input = armor.decode(armoredText);
if (input.type !== enums.armor.signed) {
throw new Error('No cleartext signed message.');
}
var packetlist = new packet.list();
packetlist.read(input.data);
var newMessage = new CleartextMessage(input.text, packetlist);
return newMessage;
}
exports.CleartextMessage = CleartextMessage;
exports.readArmored = readArmored;

View File

@ -2,6 +2,7 @@
module.exports = require('./openpgp.js'); module.exports = require('./openpgp.js');
module.exports.key = require('./key.js'); module.exports.key = require('./key.js');
module.exports.message = require('./message.js'); module.exports.message = require('./message.js');
module.exports.cleartext = require('./cleartext.js');
module.exports.util = require('./util'); module.exports.util = require('./util');
module.exports.packet = require('./packet'); module.exports.packet = require('./packet');
module.exports.mpi = require('./type/mpi.js'); module.exports.mpi = require('./type/mpi.js');

View File

@ -26,6 +26,7 @@ var util = require('./util');
* @class * @class
* @classdesc Class that represents an OpenPGP message. * @classdesc Class that represents an OpenPGP message.
* Can be an encrypted message, signed message, compressed message or literal message * Can be an encrypted message, signed message, compressed message or literal message
* @param {packetlist} packetlist The packets that form this message
* See http://tools.ietf.org/html/rfc4880#section-11.3 * See http://tools.ietf.org/html/rfc4880#section-11.3
*/ */

View File

@ -26,6 +26,7 @@ var packet = require('./packet');
var enums = require('./enums.js'); var enums = require('./enums.js');
var config = require('./config'); var config = require('./config');
var message = require('./message.js'); var message = require('./message.js');
var cleartext = require('./cleartext.js');
/** /**
@ -88,16 +89,35 @@ function decryptAndVerifyMessage(privateKey, publicKeys, message) {
return null; return null;
} }
/**
* Signs a cleartext message
* @param {[Key]} privateKeys private key with decrypted secret key data to sign cleartext
* @param {String} text cleartext
* @return {String} ASCII armored message
*/
function signClearMessage(privateKeys, text) { function signClearMessage(privateKeys, text) {
var cleartextMessage = new cleartext.CleartextMessage(text);
cleartextMessage.sign(privateKeys);
return cleartextMessage.armor();
} }
/**
* Verifies signatures of cleartext signed message
* @param {[Key]} publicKeys public keys to verify signatures
* @param {CleartextMessage} message cleartext message object with signatures
* @return {{'text': String, signatures: [{'keyid': keyid, 'status': Boolean}]}}
* cleartext with status of verified signatures
*/
function verifyClearSignedMessage(publicKeys, message) { function verifyClearSignedMessage(publicKeys, message) {
var result = {};
if (!(message instanceof cleartext.CleartextMessage)) {
throw new Error('Parameter [message] needs to be of type CleartextMessage.');
}
result.text = message.getText();
result.signatures = message.verify(publicKeys);
return result;
} }
/** /**
* TODO: update this doc * TODO: update this doc
* generates a new key pair for openpgp. Beta stage. Currently only * generates a new key pair for openpgp. Beta stage. Currently only

View File

@ -349,7 +349,7 @@ var pub_key_arm3 =
var keyids = sMsg.getSigningKeyIds(); var keyids = sMsg.getSigningKeyIds();
var verified = pubKey2.getPublicKeyPacket(keyids) && pubKey3.getPublicKeyPacket(keyids); var verified = pubKey2.getPublicKeyPacket(keyids) !== null && pubKey3.getPublicKeyPacket(keyids) !== null;
verified = verified && sMsg.getText() == plaintext; verified = verified && sMsg.getText() == plaintext;
@ -358,6 +358,65 @@ var pub_key_arm3 =
verified = verified && verifiedSig[0].status && verifiedSig[1].status; verified = verified && verifiedSig[0].status && verifiedSig[1].status;
return new unit.result("Verify signed message with two one pass signatures", verified); return new unit.result("Verify signed message with two one pass signatures", verified);
}, function() {
var msg_armor =
['-----BEGIN PGP SIGNED MESSAGE-----',
'Hash: SHA256',
'',
'short message',
'next line',
'한국어/조선말',
'-----BEGIN PGP SIGNATURE-----',
'Version: GnuPG v2.0.19 (GNU/Linux)',
'',
'iJwEAQEIAAYFAlKcju8ACgkQ4IT3RGwgLJci6gP/dCmIraUa6AGpJxzGfK+jYpjl',
'G0KunFyGmyPxeJVnPi2bBp3EPIbiayQ71CcDe9DKpF046tora07AA9eo+/YbvJ9P',
'PWeScw3oj/ejsmKQoDBGzyDMFUphevnhgc5lENjovJqmiu6FKjNmADTxcZ/qFTOq',
'44EWTgdW3IqXFkNpKjeJARwEAQEIAAYFAlKcju8ACgkQ2/Ij6HBTTfQi6gf9HxhE',
'ycLDhQ8iyC090TaYwsDytScU2vOMiI5rJCy2tfDV0pfn+UekYGMnKxZTpwtmno1j',
'mVOlieENszz5IcehS5TYwk4lmRFjoba+Z8qwPEYhYxP29GMbmRIsH811sQHFTigo',
'LI2t4pSSSUpAiXd9y6KtvkWcGGn8IfkNHCEHPh1ov28QvH0+ByIiKYK5N6ZB8hEo',
'0uMYhKQPVJdPCvMkAxQCRPw84EvmxuJ0HMCeSB9tHQXpz5un2m8D9yiGpBQPnqlW',
'vCCq7fgaUz8ksxvQ9bSwv0iIIbbBdTP7Z8y2c1Oof6NDl7irH+QCeNT7IIGs8Smn',
'BEzv/FqkQAhjy3Krxg==',
'=3Pkl',
'-----END PGP SIGNATURE-----'].join('\n');
var plaintext = 'short message\nnext line\n한국어/조선말';
var csMsg = openpgp.cleartext.readArmored(msg_armor);
var pubKey2 = openpgp.key.readArmored(pub_key_arm2);
var pubKey3 = openpgp.key.readArmored(pub_key_arm3);
var keyids = csMsg.getSigningKeyIds();
var verified = pubKey2.getPublicKeyPacket(keyids) !== null && pubKey3.getPublicKeyPacket(keyids) !== null;
var cleartextSig = openpgp.verifyClearSignedMessage([pubKey2, pubKey3], csMsg);
verified = verified && cleartextSig.text == plaintext;
verified = verified && cleartextSig.signatures[0].status && cleartextSig.signatures[1].status;
return new unit.result("Verify cleartext signed message with two signatures with openpgp.verifyClearSignedMessage", verified);
}, function() {
var plaintext = 'short message\nnext line\n한국어/조선말';
var pubKey = openpgp.key.readArmored(pub_key_arm2);
var privKey = openpgp.key.readArmored(priv_key_arm2);
privKey.getSigningKeyPacket().decrypt('hello world');
var clearSignedArmor = openpgp.signClearMessage([privKey], plaintext);
var csMsg = openpgp.cleartext.readArmored(clearSignedArmor);
var cleartextSig = openpgp.verifyClearSignedMessage([pubKey], csMsg);
var verified = cleartextSig.text == plaintext;
verified = verified && cleartextSig.signatures[0].status;
return new unit.result("Sign text with openpgp.signClearMessage and verify with openpgp.verifyClearSignedMessage leads to same cleartext and valid signatures", verified);
}]; }];
var results = []; var results = [];

File diff suppressed because one or more lines are too long