diff --git a/src/encoding/armor.js b/src/encoding/armor.js index 70af9a78..099fd074 100644 --- a/src/encoding/armor.js +++ b/src/encoding/armor.js @@ -62,10 +62,7 @@ function getType(text) { return enums.armor.multipart_last; } else - // BEGIN PGP SIGNATURE - // Used for detached signatures, OpenPGP/MIME signatures, and - // cleartext signatures. Note that PGP 2.x uses BEGIN PGP MESSAGE - // for detached signatures. + // BEGIN PGP SIGNED MESSAGE if (/SIGNED MESSAGE/.test(header[1])) { return enums.armor.signed; @@ -86,6 +83,14 @@ function getType(text) { // Used for armoring private keys. if (/PRIVATE KEY BLOCK/.test(header[1])) { return enums.armor.private_key; + + } else + // BEGIN PGP SIGNATURE + // Used for detached signatures, OpenPGP/MIME signatures, and + // cleartext signatures. Note that PGP 2.x uses BEGIN PGP MESSAGE + // for detached signatures. + if (/SIGNATURE/.test(header[1])) { + return enums.armor.signature; } } @@ -397,6 +402,13 @@ function armor(messagetype, body, partindex, parttotal) { result.push("\r\n=" + getCheckSum(body) + "\r\n"); result.push("-----END PGP PRIVATE KEY BLOCK-----\r\n"); break; + case enums.armor.signature: + result.push("-----BEGIN PGP SIGNATURE-----\r\n"); + result.push(addheader()); + result.push(base64.encode(body)); + result.push("\r\n=" + getCheckSum(body) + "\r\n"); + result.push("-----END PGP SIGNATURE-----\r\n"); + break; } return result.join(''); diff --git a/src/enums.js b/src/enums.js index 837c6649..861f2f01 100644 --- a/src/enums.js +++ b/src/enums.js @@ -297,7 +297,8 @@ export default { signed: 2, message: 3, public_key: 4, - private_key: 5 + private_key: 5, + signature: 6 }, /** Asserts validity and converts from string/integer to integer. */ diff --git a/src/message.js b/src/message.js index 31b09c75..387e0b59 100644 --- a/src/message.js +++ b/src/message.js @@ -32,6 +32,7 @@ import enums from './enums.js'; import armor from './encoding/armor.js'; import config from './config'; import crypto from './crypto'; +import signature from './signature.js'; import * as keyModule from './key.js'; /** @@ -344,19 +345,81 @@ Message.prototype.sign = function(privateKeys) { return new Message(packetlist); }; +/** + * Create a detached signature for the message (the literal data packet of the message) + * @param {Array} privateKey private keys with decrypted secret key data for signing + * @return {module:signature~Signature} new detached signature of message content + */ +Message.prototype.signDetached = function(privateKeys) { + + var packetlist = new packet.List(); + + var literalDataPacket = this.packets.findPacket(enums.packet.literal); + if (!literalDataPacket) { + throw new Error('No literal data packet to sign.'); + } + + var literalFormat = enums.write(enums.literal, literalDataPacket.format); + var signatureType = literalFormat === enums.literal.binary ? + enums.signature.binary : enums.signature.text; + + for (var i = 0; i < privateKeys.length; i++) { + var signingKeyPacket = privateKeys[i].getSigningKeyPacket(); + var signaturePacket = new packet.Signature(); + signaturePacket.signatureType = signatureType; + signaturePacket.hashAlgorithm = config.prefer_hash_algorithm; + signaturePacket.publicKeyAlgorithm = signingKeyPacket.algorithm; + if (!signingKeyPacket.isDecrypted) { + throw new Error('Private key is not decrypted.'); + } + signaturePacket.sign(signingKeyPacket, literalDataPacket); + packetlist.push(signaturePacket); + } + + return new signature.Signature(packetlist); +}; + + /** * Verify message signatures * @param {Array} keys array of keys to verify signatures * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature */ Message.prototype.verify = function(keys) { - var result = []; var msg = this.unwrapCompressed(); var literalDataList = msg.packets.filterByTag(enums.packet.literal); if (literalDataList.length !== 1) { throw new Error('Can only verify message with one literal data packet.'); } var signatureList = msg.packets.filterByTag(enums.packet.signature); + return createVerificationObjects(signatureList, literalDataList, keys); +}; + +/** + * Verify detached message signature + * @param {Array} keys array of keys to verify signatures + * @param {Signature} + * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature + */ +Message.prototype.verifyDetached = function(signature, keys) { + var msg = this.unwrapCompressed(); + var literalDataList = msg.packets.filterByTag(enums.packet.literal); + if (literalDataList.length !== 1) { + throw new Error('Can only verify message with one literal data packet.'); + } + var signatureList = signature.packets; + return createVerificationObjects(signatureList, literalDataList, keys); +}; + +/** + * Create list of objects containing signer's keyid and validity of signature + * @param {Array} signatureList array of signature packets + * @param {Array} literalDataList array of literal data packets + * @param {Array} keys array of keys to verify signatures + * @return {Array<({keyid: module:type/keyid, valid: Boolean})>} list of signer's keyid and validity of signature + */ +function createVerificationObjects(signatureList, literalDataList, keys) { + var result = []; for (var i = 0; i < signatureList.length; i++) { var keyPacket = null; for (var j = 0; j < keys.length; j++) { @@ -377,7 +440,7 @@ Message.prototype.verify = function(keys) { result.push(verifiedSig); } return result; -}; +} /** * Unwrap compressed message diff --git a/src/signature.js b/src/signature.js new file mode 100644 index 00000000..342d7e68 --- /dev/null +++ b/src/signature.js @@ -0,0 +1,78 @@ +// 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 3.0 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 + +/** + * @requires config + * @requires crypto + * @requires encoding/armor + * @requires enums + * @requires packet + * @module signature + */ + +'use strict'; + +import packet from './packet'; +import enums from './enums.js'; +import armor from './encoding/armor.js'; + +/** + * @class + * @classdesc Class that represents an OpenPGP message. + * Can be an encrypted message, signed message, compressed message or literal message + * @param {module:packet/packetlist} packetlist The packets that form this message + * See {@link http://tools.ietf.org/html/rfc4880#section-11.3} + */ + +export function Signature(packetlist) { + if (!(this instanceof Signature)) { + return new Signature(packetlist); + } + this.packets = packetlist || new packet.List(); +} + + +/** + * Returns ASCII armored text of signature + * @return {String} ASCII armor + */ +Signature.prototype.armor = function() { + return armor.encode(enums.armor.signature, this.packets.write()); +}; + +/** + * reads an OpenPGP armored signature and returns a signature object + * @param {String} armoredText text to be parsed + * @return {module:signature~Signature} new signature object + * @static + */ +export function readArmored(armoredText) { + var input = armor.decode(armoredText).data; + return read(input); +} + +/** + * reads an OpenPGP signature as byte array and returns a signature object + * @param {Uint8Array} input binary signature + * @return {Signature} new signature object + * @static + */ +export function read(input) { + var packetlist = new packet.List(); + packetlist.read(input); + return new Signature(packetlist); +}