commit
e925331a99
|
@ -29,23 +29,26 @@ import config from './config';
|
|||
import packet from './packet';
|
||||
import enums from './enums.js';
|
||||
import armor from './encoding/armor.js';
|
||||
import * as sigModule from './signature.js';
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @classdesc Class that represents an OpenPGP cleartext signed message.
|
||||
* See {@link http://tools.ietf.org/html/rfc4880#section-7}
|
||||
* @param {String} text The cleartext of the signed message
|
||||
* @param {module:packet/packetlist} packetlist The packetlist with signature packets or undefined
|
||||
* if message not yet signed
|
||||
* @param {module:Signature} signature The detached signature or an empty signature if message not yet signed
|
||||
*/
|
||||
|
||||
export function CleartextMessage(text, packetlist) {
|
||||
export function CleartextMessage(text, signature) {
|
||||
if (!(this instanceof CleartextMessage)) {
|
||||
return new CleartextMessage(text, packetlist);
|
||||
return new CleartextMessage(text, signature);
|
||||
}
|
||||
// normalize EOL to canonical form <CR><LF>
|
||||
this.text = text.replace(/\r/g, '').replace(/[\t ]+\n/g, "\n").replace(/\n/g,"\r\n");
|
||||
this.packets = packetlist || new packet.List();
|
||||
if (signature && !(signature instanceof sigModule.Signature)) {
|
||||
throw new Error('Invalid signature input');
|
||||
}
|
||||
this.signature = signature || new sigModule.Signature(new packet.List());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,7 +57,7 @@ export function CleartextMessage(text, packetlist) {
|
|||
*/
|
||||
CleartextMessage.prototype.getSigningKeyIds = function() {
|
||||
var keyIds = [];
|
||||
var signatureList = this.packets.filterByTag(enums.packet.signature);
|
||||
var signatureList = this.signature.packets;
|
||||
signatureList.forEach(function(packet) {
|
||||
keyIds.push(packet.issuerKeyId);
|
||||
});
|
||||
|
@ -66,6 +69,15 @@ CleartextMessage.prototype.getSigningKeyIds = function() {
|
|||
* @param {Array<module:key~Key>} privateKeys private keys with decrypted secret key data for signing
|
||||
*/
|
||||
CleartextMessage.prototype.sign = function(privateKeys) {
|
||||
this.signature = this.signDetached(privateKeys);
|
||||
};
|
||||
|
||||
/**
|
||||
* Sign the cleartext message
|
||||
* @param {Array<module:key~Key>} privateKeys private keys with decrypted secret key data for signing
|
||||
* @return {module:signature~Signature} new detached signature of message content
|
||||
*/
|
||||
CleartextMessage.prototype.signDetached = function(privateKeys) {
|
||||
var packetlist = new packet.List();
|
||||
var literalDataPacket = new packet.Literal();
|
||||
literalDataPacket.setText(this.text);
|
||||
|
@ -84,7 +96,7 @@ CleartextMessage.prototype.sign = function(privateKeys) {
|
|||
signaturePacket.sign(signingKeyPacket, literalDataPacket);
|
||||
packetlist.push(signaturePacket);
|
||||
}
|
||||
this.packets = packetlist;
|
||||
return new sigModule.Signature(packetlist);
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -93,8 +105,17 @@ CleartextMessage.prototype.sign = function(privateKeys) {
|
|||
* @return {Array<{keyid: module:type/keyid, valid: Boolean}>} list of signer's keyid and validity of signature
|
||||
*/
|
||||
CleartextMessage.prototype.verify = function(keys) {
|
||||
return this.verifyDetached(this.signature, keys);
|
||||
};
|
||||
|
||||
/**
|
||||
* Verify signatures of cleartext signed message
|
||||
* @param {Array<module:key~Key>} keys array of keys to verify signatures
|
||||
* @return {Array<{keyid: module:type/keyid, valid: Boolean}>} list of signer's keyid and validity of signature
|
||||
*/
|
||||
CleartextMessage.prototype.verifyDetached = function(signature, keys) {
|
||||
var result = [];
|
||||
var signatureList = this.packets.filterByTag(enums.packet.signature);
|
||||
var signatureList = signature.packets;
|
||||
var literalDataPacket = new packet.Literal();
|
||||
// we assume that cleartext signature is generated based on UTF8 cleartext
|
||||
literalDataPacket.setText(this.text);
|
||||
|
@ -115,6 +136,8 @@ CleartextMessage.prototype.verify = function(keys) {
|
|||
verifiedSig.keyid = signatureList[i].issuerKeyId;
|
||||
verifiedSig.valid = null;
|
||||
}
|
||||
verifiedSig.signature = new sigModule.Signature([signatureList[i]]);
|
||||
|
||||
result.push(verifiedSig);
|
||||
}
|
||||
return result;
|
||||
|
@ -137,7 +160,7 @@ CleartextMessage.prototype.armor = function() {
|
|||
var body = {
|
||||
hash: enums.read(enums.hash, config.prefer_hash_algorithm).toUpperCase(),
|
||||
text: this.text,
|
||||
data: this.packets.write()
|
||||
data: this.signature.packets.write()
|
||||
};
|
||||
return armor.encode(enums.armor.signed, body);
|
||||
};
|
||||
|
@ -157,7 +180,8 @@ export function readArmored(armoredText) {
|
|||
var packetlist = new packet.List();
|
||||
packetlist.read(input.data);
|
||||
verifyHeaders(input.headers, packetlist);
|
||||
var newMessage = new CleartextMessage(input.text, packetlist);
|
||||
var signature = new sigModule.Signature(packetlist);
|
||||
var newMessage = new CleartextMessage(input.text, signature);
|
||||
return newMessage;
|
||||
}
|
||||
|
||||
|
|
|
@ -38,6 +38,7 @@ import config from '../config';
|
|||
* 3 = PGP MESSAGE
|
||||
* 4 = PUBLIC KEY BLOCK
|
||||
* 5 = PRIVATE KEY BLOCK
|
||||
* 6 = SIGNATURE
|
||||
*/
|
||||
function getType(text) {
|
||||
var reHeader = /^-----BEGIN PGP (MESSAGE, PART \d+\/\d+|MESSAGE, PART \d+|SIGNED MESSAGE|MESSAGE|PUBLIC KEY BLOCK|PRIVATE KEY BLOCK|SIGNATURE)-----$\n/m;
|
||||
|
@ -62,10 +63,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 +84,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 +403,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('');
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -26,6 +26,13 @@ export * from './openpgp';
|
|||
import * as keyMod from './key';
|
||||
export const key = keyMod;
|
||||
|
||||
/**
|
||||
* @see module:signature
|
||||
* @name module:openpgp.signature
|
||||
*/
|
||||
import * as signatureMod from './signature';
|
||||
export const signature = signatureMod;
|
||||
|
||||
/**
|
||||
* @see module:message
|
||||
* @name module:openpgp.message
|
||||
|
|
|
@ -32,6 +32,7 @@ import enums from './enums.js';
|
|||
import armor from './encoding/armor.js';
|
||||
import config from './config';
|
||||
import crypto from './crypto';
|
||||
import * as sigModule 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<module:key~Key>} 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 sigModule.Signature(packetlist);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Verify message signatures
|
||||
* @param {Array<module:key~Key>} 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<module:key~Key>} 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<module:packet/signature>} signatureList array of signature packets
|
||||
* @param {Array<module:packet/literal>} literalDataList array of literal data packets
|
||||
* @param {Array<module:key~Key>} 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++) {
|
||||
|
@ -368,16 +431,19 @@ Message.prototype.verify = function(keys) {
|
|||
|
||||
var verifiedSig = {};
|
||||
if (keyPacket) {
|
||||
//found a key packet that matches keyId of signature
|
||||
verifiedSig.keyid = signatureList[i].issuerKeyId;
|
||||
verifiedSig.valid = signatureList[i].verify(keyPacket, literalDataList[0]);
|
||||
} else {
|
||||
verifiedSig.keyid = signatureList[i].issuerKeyId;
|
||||
verifiedSig.valid = null;
|
||||
}
|
||||
verifiedSig.signature = new sigModule.Signature([signatureList[i]]);
|
||||
|
||||
result.push(verifiedSig);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Unwrap compressed message
|
||||
|
|
|
@ -178,36 +178,44 @@ export function decryptKey({ privateKey, passphrase }) {
|
|||
* @param {Key|Array<Key>} privateKeys (optional) private keys for signing. If omitted message will not be signed
|
||||
* @param {String|Array<String>} passwords (optional) array of passwords or a single password to encrypt the message
|
||||
* @param {String} filename (optional) a filename for the literal data packet
|
||||
* @param {Boolean} armor (optional) if the return value should be ascii armored or the message object
|
||||
* @return {Promise<String|Message>} encrypted ASCII armored message, or the full Message object if 'armor' is false
|
||||
* @param {Boolean} armor (optional) if the return values should be ascii armored or the message/signature objects
|
||||
* @param {Boolean} detached (optional) if the signature should be detached (if true, signature will be added to returned object)
|
||||
* @return {Promise<Object>} encrypted (and optionally signed message) in the form:
|
||||
* {data: ASCII armored message if 'armor' is true,
|
||||
* message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true}
|
||||
* @static
|
||||
*/
|
||||
export function encrypt({ data, publicKeys, privateKeys, passwords, filename, armor=true }) {
|
||||
export function encrypt({ data, publicKeys, privateKeys, passwords, filename, armor=true, detached=false }) {
|
||||
checkData(data); publicKeys = toArray(publicKeys); privateKeys = toArray(privateKeys); passwords = toArray(passwords);
|
||||
|
||||
if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported
|
||||
return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, filename, armor });
|
||||
return asyncProxy.delegate('encrypt', { data, publicKeys, privateKeys, passwords, filename, armor, detached });
|
||||
}
|
||||
|
||||
var result = {};
|
||||
return Promise.resolve().then(() => {
|
||||
|
||||
let message = createMessage(data, filename);
|
||||
if (privateKeys) { // sign the message only if private keys are specified
|
||||
if (detached) {
|
||||
var signature = message.signDetached(privateKeys);
|
||||
if (armor) {
|
||||
result.signature = signature.armor();
|
||||
} else {
|
||||
result.signature = signature;
|
||||
}
|
||||
} else {
|
||||
message = message.sign(privateKeys);
|
||||
}
|
||||
}
|
||||
return message.encrypt(publicKeys, passwords);
|
||||
|
||||
}).then(message => {
|
||||
|
||||
if (armor) {
|
||||
return {
|
||||
data: message.armor()
|
||||
};
|
||||
result.data = message.armor();
|
||||
} else {
|
||||
result.message = message;
|
||||
}
|
||||
return {
|
||||
message: message
|
||||
};
|
||||
|
||||
return result;
|
||||
}).catch(onError.bind(null, 'Error encrypting message'));
|
||||
}
|
||||
|
||||
|
@ -220,23 +228,29 @@ export function encrypt({ data, publicKeys, privateKeys, passwords, filename, ar
|
|||
* @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} format (optional) return data format either as 'utf8' or 'binary'
|
||||
* @param {Signature} signature (optional) detached signature for verification
|
||||
* @return {Promise<Object>} decrypted and verified message in the form:
|
||||
* { data:Uint8Array|String, filename:String, signatures:[{ keyid:String, valid:Boolean }] }
|
||||
* @static
|
||||
*/
|
||||
export function decrypt({ message, privateKey, publicKeys, sessionKey, password, format='utf8' }) {
|
||||
export function decrypt({ message, privateKey, publicKeys, sessionKey, password, format='utf8', signature=null }) {
|
||||
checkMessage(message); publicKeys = toArray(publicKeys);
|
||||
|
||||
if (!nativeAEAD() && asyncProxy) { // use web worker if web crypto apis are not supported
|
||||
return asyncProxy.delegate('decrypt', { message, privateKey, publicKeys, sessionKey, password, format });
|
||||
return asyncProxy.delegate('decrypt', { message, privateKey, publicKeys, sessionKey, password, format, signature });
|
||||
}
|
||||
|
||||
return message.decrypt(privateKey, sessionKey, password).then(message => {
|
||||
|
||||
const result = parseMessage(message, format);
|
||||
if (publicKeys && result.data) { // verify only if publicKeys are specified
|
||||
if (result.data) { // verify
|
||||
if (signature) {
|
||||
//detached signature
|
||||
result.signatures = message.verifyDetached(signature, publicKeys);
|
||||
} else {
|
||||
result.signatures = message.verify(publicKeys);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
}).catch(onError.bind(null, 'Error decrypting message'));
|
||||
|
@ -255,30 +269,41 @@ export function decrypt({ message, privateKey, publicKeys, sessionKey, password,
|
|||
* @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 {Boolean} armor (optional) if the return value should be ascii armored or the message object
|
||||
* @return {Promise<String|CleartextMessage>} ASCII armored message or the message of type CleartextMessage
|
||||
* @return {Promise<Object>} signed cleartext in the form:
|
||||
* {data: ASCII armored message if 'armor' is true,
|
||||
* message: full Message object if 'armor' is false, signature: detached signature if 'detached' is true}
|
||||
* @static
|
||||
*/
|
||||
export function sign({ data, privateKeys, armor=true }) {
|
||||
export function sign({ data, privateKeys, armor=true, detached=false}) {
|
||||
checkString(data);
|
||||
privateKeys = toArray(privateKeys);
|
||||
|
||||
if (asyncProxy) { // use web worker if available
|
||||
return asyncProxy.delegate('sign', { data, privateKeys, armor });
|
||||
return asyncProxy.delegate('sign', { data, privateKeys, armor, detached });
|
||||
}
|
||||
|
||||
var result = {};
|
||||
return execute(() => {
|
||||
|
||||
const cleartextMessage = new cleartext.CleartextMessage(data);
|
||||
|
||||
if (detached) {
|
||||
var signature = cleartextMessage.signDetached(privateKeys);
|
||||
if (armor) {
|
||||
result.signature = signature.armor();
|
||||
} else {
|
||||
result.signature = signature;
|
||||
}
|
||||
} else {
|
||||
cleartextMessage.sign(privateKeys);
|
||||
}
|
||||
|
||||
if (armor) {
|
||||
return {
|
||||
data: cleartextMessage.armor()
|
||||
};
|
||||
result.data = cleartextMessage.armor();
|
||||
} else {
|
||||
result.message = cleartextMessage;
|
||||
}
|
||||
return {
|
||||
message: cleartextMessage
|
||||
};
|
||||
return result;
|
||||
|
||||
}, 'Error signing cleartext message');
|
||||
}
|
||||
|
@ -287,24 +312,32 @@ export function sign({ data, privateKeys, armor=true }) {
|
|||
* Verifies signatures of cleartext signed message
|
||||
* @param {Key|Array<Key>} publicKeys array of publicKeys or single key, to verify signatures
|
||||
* @param {CleartextMessage} message cleartext message object with signatures
|
||||
* @param {Signature} signature (optional) detached signature for verification
|
||||
* @return {Promise<Object>} cleartext with status of verified signatures in the form of:
|
||||
* { data:String, signatures: [{ keyid:String, valid:Boolean }] }
|
||||
* @static
|
||||
*/
|
||||
export function verify({ message, publicKeys }) {
|
||||
export function verify({ message, publicKeys, signature=null }) {
|
||||
checkCleartextMessage(message);
|
||||
publicKeys = toArray(publicKeys);
|
||||
|
||||
if (asyncProxy) { // use web worker if available
|
||||
return asyncProxy.delegate('verify', { message, publicKeys });
|
||||
return asyncProxy.delegate('verify', { message, publicKeys, signature });
|
||||
}
|
||||
|
||||
return execute(() => ({
|
||||
var result = {};
|
||||
return execute(() => {
|
||||
result.data = message.getText();
|
||||
|
||||
data: message.getText(),
|
||||
signatures: message.verify(publicKeys)
|
||||
if (signature) {
|
||||
//detached signature
|
||||
result.signatures = message.verifyDetached(signature, publicKeys);
|
||||
} else {
|
||||
result.signatures = message.verify(publicKeys);
|
||||
}
|
||||
return result;
|
||||
|
||||
}), 'Error verifying cleartext signed message');
|
||||
}, 'Error verifying cleartext signed message');
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
import * as key from '../key.js';
|
||||
import * as message from '../message.js';
|
||||
import * as cleartext from '../cleartext.js';
|
||||
import * as signature from '../signature.js'
|
||||
import Packetlist from './packetlist.js';
|
||||
import type_keyid from '../type/keyid.js';
|
||||
|
||||
|
@ -55,9 +56,27 @@ export function clonePackets(options) {
|
|||
if (options.key) {
|
||||
options.key = options.key.toPacketlist();
|
||||
}
|
||||
if (options.message) {
|
||||
//could be either a Message or CleartextMessage object
|
||||
if (options.message instanceof message.Message) {
|
||||
options.message = options.message.packets;
|
||||
} else if (options.message instanceof cleartext.CleartextMessage) {
|
||||
options.message.signature = options.message.signature.packets;
|
||||
}
|
||||
}
|
||||
if (options.signature && (options.signature instanceof signature.Signature)) {
|
||||
options.signature = options.signature.packets;
|
||||
}
|
||||
if (options.signatures) {
|
||||
options.signatures = options.signatures.map(sig => verificationObjectToClone(sig));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function verificationObjectToClone(verObject) {
|
||||
verObject.signature = verObject.signature.packets;
|
||||
return verObject;
|
||||
}
|
||||
|
||||
//////////////////////////////
|
||||
// //
|
||||
|
@ -91,7 +110,10 @@ export function parseClonedPackets(options, method) {
|
|||
options.message = packetlistCloneToMessage(options.message);
|
||||
}
|
||||
if (options.signatures) {
|
||||
options.signatures = options.signatures.map(packetlistCloneToSignature);
|
||||
options.signatures = options.signatures.map(packetlistCloneToSignatures);
|
||||
}
|
||||
if (options.signature) {
|
||||
options.signature = packetlistCloneToSignature(options.signature);
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
@ -102,16 +124,27 @@ function packetlistCloneToKey(clone) {
|
|||
}
|
||||
|
||||
function packetlistCloneToMessage(clone) {
|
||||
const packetlist = Packetlist.fromStructuredClone(clone.packets);
|
||||
const packetlist = Packetlist.fromStructuredClone(clone);
|
||||
return new message.Message(packetlist);
|
||||
}
|
||||
|
||||
function packetlistCloneToCleartextMessage(clone) {
|
||||
var packetlist = Packetlist.fromStructuredClone(clone.packets);
|
||||
return new cleartext.CleartextMessage(clone.text, packetlist);
|
||||
var packetlist = Packetlist.fromStructuredClone(clone.signature);
|
||||
return new cleartext.CleartextMessage(clone.text, new signature.Signature(packetlist));
|
||||
}
|
||||
|
||||
//verification objects
|
||||
function packetlistCloneToSignatures(clone) {
|
||||
clone.keyid = type_keyid.fromClone(clone.keyid);
|
||||
clone.signature = new signature.Signature(clone.signature);
|
||||
return clone;
|
||||
}
|
||||
|
||||
function packetlistCloneToSignature(clone) {
|
||||
clone.keyid = type_keyid.fromClone(clone.keyid);
|
||||
if (typeof clone === "string") {
|
||||
//signature is armored
|
||||
return clone;
|
||||
}
|
||||
var packetlist = Packetlist.fromStructuredClone(clone);
|
||||
return new signature.Signature(packetlist);
|
||||
}
|
||||
|
|
76
src/signature.js
Normal file
76
src/signature.js
Normal file
|
@ -0,0 +1,76 @@
|
|||
// 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 signature.
|
||||
* @param {module:packet/packetlist} packetlist The signature packets
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
|
@ -637,7 +637,8 @@ describe('OpenPGP.js public api tests', function() {
|
|||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures).to.not.exist;
|
||||
expect(decrypted.signatures).to.exist;
|
||||
expect(decrypted.signatures.length).to.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -659,6 +660,31 @@ describe('OpenPGP.js public api tests', function() {
|
|||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures[0].valid).to.be.true;
|
||||
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should encrypt/sign and decrypt/verify with detached signatures', function(done) {
|
||||
var encOpt = {
|
||||
data: plaintext,
|
||||
publicKeys: publicKey.keys,
|
||||
privateKeys: privateKey.keys,
|
||||
detached: true
|
||||
};
|
||||
var decOpt = {
|
||||
privateKey: privateKey.keys[0],
|
||||
publicKeys: publicKey.keys
|
||||
};
|
||||
openpgp.encrypt(encOpt).then(function(encrypted) {
|
||||
decOpt.message = openpgp.message.readArmored(encrypted.data);
|
||||
decOpt.signature = openpgp.signature.readArmored(encrypted.signature);
|
||||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures[0].valid).to.be.true;
|
||||
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -680,6 +706,31 @@ describe('OpenPGP.js public api tests', function() {
|
|||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures[0].valid).to.be.null;
|
||||
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should fail to verify decrypted data with wrong public pgp key with detached signatures', function(done) {
|
||||
var encOpt = {
|
||||
data: plaintext,
|
||||
publicKeys: publicKey.keys,
|
||||
privateKeys: privateKey.keys,
|
||||
detached: true
|
||||
};
|
||||
var decOpt = {
|
||||
privateKey: privateKey.keys[0],
|
||||
publicKeys: openpgp.key.readArmored(wrong_pubkey).keys
|
||||
};
|
||||
openpgp.encrypt(encOpt).then(function(encrypted) {
|
||||
decOpt.message = openpgp.message.readArmored(encrypted.data);
|
||||
decOpt.signature = openpgp.signature.readArmored(encrypted.signature);
|
||||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures[0].valid).to.be.null;
|
||||
expect(decrypted.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -700,6 +751,29 @@ describe('OpenPGP.js public api tests', function() {
|
|||
expect(verified.data).to.equal(plaintext);
|
||||
expect(verified.signatures[0].valid).to.be.true;
|
||||
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(verified.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should sign and verify cleartext data with detached signatures', function(done) {
|
||||
var signOpt = {
|
||||
data: plaintext,
|
||||
privateKeys: privateKey.keys,
|
||||
detached: true
|
||||
};
|
||||
var verifyOpt = {
|
||||
publicKeys: publicKey.keys
|
||||
};
|
||||
openpgp.sign(signOpt).then(function(signed) {
|
||||
verifyOpt.message = openpgp.cleartext.readArmored(signed.data);
|
||||
verifyOpt.signature = openpgp.signature.readArmored(signed.signature);
|
||||
return openpgp.verify(verifyOpt);
|
||||
}).then(function(verified) {
|
||||
expect(verified.data).to.equal(plaintext);
|
||||
expect(verified.signatures[0].valid).to.be.true;
|
||||
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(verified.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -719,6 +793,29 @@ describe('OpenPGP.js public api tests', function() {
|
|||
expect(verified.data).to.equal(plaintext);
|
||||
expect(verified.signatures[0].valid).to.be.null;
|
||||
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(verified.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should sign and fail to verify cleartext data with wrong public pgp key with detached signature', function(done) {
|
||||
var signOpt = {
|
||||
data: plaintext,
|
||||
privateKeys: privateKey.keys,
|
||||
detached: true
|
||||
};
|
||||
var verifyOpt = {
|
||||
publicKeys: openpgp.key.readArmored(wrong_pubkey).keys
|
||||
};
|
||||
openpgp.sign(signOpt).then(function(signed) {
|
||||
verifyOpt.message = openpgp.cleartext.readArmored(signed.data);
|
||||
verifyOpt.signature = openpgp.signature.readArmored(signed.signature);
|
||||
return openpgp.verify(verifyOpt);
|
||||
}).then(function(verified) {
|
||||
expect(verified.data).to.equal(plaintext);
|
||||
expect(verified.signatures[0].valid).to.be.null;
|
||||
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(verified.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -739,6 +836,30 @@ describe('OpenPGP.js public api tests', function() {
|
|||
expect(verified.data).to.equal(plaintext);
|
||||
expect(verified.signatures[0].valid).to.be.true;
|
||||
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(verified.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should sign and verify cleartext data and not armor with detached signatures', function(done) {
|
||||
var signOpt = {
|
||||
data: plaintext,
|
||||
privateKeys: privateKey.keys,
|
||||
detached: true,
|
||||
armor: false
|
||||
};
|
||||
var verifyOpt = {
|
||||
publicKeys: publicKey.keys
|
||||
};
|
||||
openpgp.sign(signOpt).then(function(signed) {
|
||||
verifyOpt.message = signed.message;
|
||||
verifyOpt.signature = signed.signature;
|
||||
return openpgp.verify(verifyOpt);
|
||||
}).then(function(verified) {
|
||||
expect(verified.data).to.equal(plaintext);
|
||||
expect(verified.signatures[0].valid).to.be.true;
|
||||
expect(verified.signatures[0].keyid.toHex()).to.equal(privateKey.keys[0].getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(verified.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -762,6 +883,9 @@ describe('OpenPGP.js public api tests', function() {
|
|||
}).then(function(encrypted) {
|
||||
expect(encrypted.data).to.exist;
|
||||
expect(encrypted.data).to.equal(plaintext);
|
||||
expect(encrypted.signatures[0].valid).to.be.true;
|
||||
expect(encrypted.signatures[0].keyid.toHex()).to.equal(privKeyDE.getSigningKeyPacket().getKeyId().toHex());
|
||||
expect(encrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -828,6 +952,7 @@ describe('OpenPGP.js public api tests', function() {
|
|||
|
||||
openpgp.decrypt({ privateKey:privKey, message:message }).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal('hello 3des\n');
|
||||
expect(decrypted.signatures.length).to.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -847,6 +972,7 @@ describe('OpenPGP.js public api tests', function() {
|
|||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures.length).to.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -864,6 +990,7 @@ describe('OpenPGP.js public api tests', function() {
|
|||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures.length).to.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -882,6 +1009,7 @@ describe('OpenPGP.js public api tests', function() {
|
|||
return openpgp.decrypt(decOpt);
|
||||
}).then(function(decrypted) {
|
||||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures.length).to.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -905,6 +1033,7 @@ describe('OpenPGP.js public api tests', function() {
|
|||
expect(encOpt.data.byteLength).to.equal(0); // transfered buffer should be empty
|
||||
}
|
||||
expect(decrypted.data).to.deep.equal(new Uint8Array([0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x01]));
|
||||
expect(decrypted.signatures.length).to.equal(0);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -265,6 +265,7 @@ describe("Signature", function() {
|
|||
openpgp.decrypt({ privateKey: priv_key, publicKeys:[pub_key], message:msg }).then(function(decrypted) {
|
||||
expect(decrypted.data).to.exist;
|
||||
expect(decrypted.signatures[0].valid).to.be.true;
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -309,6 +310,7 @@ describe("Signature", function() {
|
|||
expect(verified).to.exist;
|
||||
expect(verified).to.have.length(1);
|
||||
expect(verified[0].valid).to.be.true;
|
||||
expect(verified[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -333,6 +335,7 @@ describe("Signature", function() {
|
|||
expect(verified).to.exist;
|
||||
expect(verified).to.have.length(1);
|
||||
expect(verified[0].valid).to.be.true;
|
||||
expect(verified[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
|
||||
|
@ -356,6 +359,7 @@ describe("Signature", function() {
|
|||
expect(verified).to.exist;
|
||||
expect(verified).to.have.length(1);
|
||||
expect(verified[0].valid).to.be.true;
|
||||
expect(verified[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
|
||||
|
@ -390,6 +394,7 @@ describe("Signature", function() {
|
|||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures).to.have.length(1);
|
||||
expect(decrypted.signatures[0].valid).to.be.true;
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -426,6 +431,7 @@ describe("Signature", function() {
|
|||
expect(decrypted.data).to.equal(plaintext);
|
||||
expect(decrypted.signatures).to.have.length(1);
|
||||
expect(decrypted.signatures[0].valid).to.be.true;
|
||||
expect(decrypted.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
|
||||
|
@ -469,6 +475,8 @@ describe("Signature", function() {
|
|||
expect(verifiedSig).to.have.length(2);
|
||||
expect(verifiedSig[0].valid).to.be.true;
|
||||
expect(verifiedSig[1].valid).to.be.true;
|
||||
expect(verifiedSig[0].signature.packets.length).to.equal(1);
|
||||
expect(verifiedSig[1].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
|
||||
|
@ -513,6 +521,8 @@ describe("Signature", function() {
|
|||
expect(cleartextSig.signatures).to.have.length(2);
|
||||
expect(cleartextSig.signatures[0].valid).to.be.true;
|
||||
expect(cleartextSig.signatures[1].valid).to.be.true;
|
||||
expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
|
||||
expect(cleartextSig.signatures[1].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
@ -533,6 +543,7 @@ describe("Signature", function() {
|
|||
expect(cleartextSig.data).to.equal(plaintext.replace(/\r/g,''));
|
||||
expect(cleartextSig.signatures).to.have.length(1);
|
||||
expect(cleartextSig.signatures[0].valid).to.be.true;
|
||||
expect(cleartextSig.signatures[0].signature.packets.length).to.equal(1);
|
||||
done();
|
||||
});
|
||||
|
||||
|
@ -595,7 +606,7 @@ describe("Signature", function() {
|
|||
expect(pubKey.users[0].selfCertifications).to.eql(pubKey2.users[0].selfCertifications);
|
||||
});
|
||||
|
||||
it('Verify a detached signature', function() {
|
||||
it('Verify a detached signature using readSignedContent', function() {
|
||||
var detachedSig = ['-----BEGIN PGP SIGNATURE-----',
|
||||
'Version: GnuPG v1.4.13 (Darwin)',
|
||||
'Comment: GPGTools - https://gpgtools.org',
|
||||
|
@ -641,6 +652,42 @@ describe("Signature", function() {
|
|||
expect(result[0].valid).to.be.true;
|
||||
});
|
||||
|
||||
it('Detached signature signing and verification cleartext', function () {
|
||||
var msg = openpgp.message.fromText('hello');
|
||||
var pubKey2 = openpgp.key.readArmored(pub_key_arm2).keys[0];
|
||||
var privKey2 = openpgp.key.readArmored(priv_key_arm2).keys[0];
|
||||
privKey2.decrypt('hello world');
|
||||
|
||||
var opt = {numBits: 512, userIds: { name:'test', email:'a@b.com' }, passphrase: null};
|
||||
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
|
||||
openpgp.generateKey(opt).then(function(gen) {
|
||||
var generatedKey = gen.key;
|
||||
var detachedSig = msg.signDetached([generatedKey, privKey2]);
|
||||
var result = msg.verifyDetached(detachedSig, [generatedKey.toPublic(), pubKey2]);
|
||||
expect(result[0].valid).to.be.true;
|
||||
expect(result[1].valid).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
it('Detached signature signing and verification encrypted', function () {
|
||||
var msg = openpgp.message.fromText('hello');
|
||||
var pubKey2 = openpgp.key.readArmored(pub_key_arm2).keys[0];
|
||||
var privKey2 = openpgp.key.readArmored(priv_key_arm2).keys[0];
|
||||
privKey2.decrypt('hello world');
|
||||
msg.encrypt({keys: [pubKey2] });
|
||||
|
||||
var detachedSig = msg.signDetached([privKey2]);
|
||||
|
||||
var opt = {numBits: 512, userIds: { name:'test', email:'a@b.com' }, passphrase: null};
|
||||
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
|
||||
openpgp.generateKey(opt).then(function(gen) {
|
||||
var key = gen.key;
|
||||
var result = msg.verifyDetached(detachedSig, [pubKey2, key.toPublic()]);
|
||||
expect(result[0].valid).to.be.true;
|
||||
expect(result[0].valid).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
it('Sign message with key without password', function(done) {
|
||||
var opt = {numBits: 512, userIds: { name:'test', email:'a@b.com' }, passphrase: null};
|
||||
if (openpgp.util.getWebCryptoAll()) { opt.numBits = 2048; } // webkit webcrypto accepts minimum 2048 bit keys
|
||||
|
|
Loading…
Reference in New Issue
Block a user