Implement cleartext signed messages
This commit is contained in:
parent
be96de5188
commit
7e711510cc
File diff suppressed because one or more lines are too long
141
src/cleartext.js
Normal file
141
src/cleartext.js
Normal 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;
|
|
@ -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');
|
||||||
|
|
|
@ -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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue
Block a user