368 lines
15 KiB
JavaScript
368 lines
15 KiB
JavaScript
// 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
|
|
|
|
/**
|
|
* @fileoverview The openpgp base class should provide all of the functionality
|
|
* to consume the openpgp.js library. All additional classes are documented
|
|
* for extending and developing on top of the base library.
|
|
*/
|
|
|
|
var armor = require('./encoding/armor.js');
|
|
var packet = require('./packet');
|
|
var util = require('./util');
|
|
var enums = require('./enums.js');
|
|
var crypto = require('./crypto');
|
|
var key = require('./key.js');
|
|
var config = require('./config');
|
|
|
|
/**
|
|
* GPG4Browsers Core interface. A single instance is hold
|
|
* from the beginning. To use this library call "openpgp.init()"
|
|
* @alias openpgp
|
|
* @class
|
|
* @classdesc Main Openpgp.js class. Use this to initiate and make all calls to this library.
|
|
*/
|
|
function _openpgp() {
|
|
this.tostring = "";
|
|
|
|
/**
|
|
* reads message packets out of an OpenPGP armored text and
|
|
* returns an array of message objects
|
|
* @param {String} armoredText text to be parsed
|
|
* @return {openpgp_msg_message[]} on error the function
|
|
* returns null
|
|
*/
|
|
function readArmoredPackets(armoredText) {
|
|
//TODO how do we want to handle bad text? Exception throwing
|
|
var input = armor.decode(armoredText.replace(/\r/g, '')).openpgp;
|
|
return readDearmoredPackets(input);
|
|
}
|
|
|
|
/**
|
|
* reads message packets out of an OpenPGP armored text and
|
|
* returns an array of message objects. Can be called externally or internally.
|
|
* External call will parse a de-armored messaged and return messages found.
|
|
* Internal will be called to read packets wrapped in other packets (i.e. compressed)
|
|
* @param {String} input dearmored text of OpenPGP packets, to be parsed
|
|
* @return {openpgp_msg_message[]} on error the function
|
|
* returns null
|
|
*/
|
|
function readDearmoredPackets(input) {
|
|
var packetList = new packet.list();
|
|
packetList.read(input);
|
|
return packetList;
|
|
}
|
|
|
|
function encryptMessage(publicKeys, message) {
|
|
|
|
var packetList = new packet.list();
|
|
|
|
var literalDataPacket = new packet.literal();
|
|
literalDataPacket.set(message, 'utf8');
|
|
|
|
//TODO get preferred algo from signature
|
|
var sessionKey = crypto.generateSessionKey(enums.read(enums.symmetric, config.encryption_cipher));
|
|
|
|
publicKeys.forEach(function(publicKeyPacketlist) {
|
|
var pubKey = new key();
|
|
pubKey.packets = publicKeyPacketlist;
|
|
var encryptionKey = pubKey.getEncryptionKey();
|
|
if (encryptionKey) {
|
|
var pkESKeyPacket = new packet.public_key_encrypted_session_key();
|
|
pkESKeyPacket.publicKeyId = encryptionKey.getKeyId();
|
|
pkESKeyPacket.publicKeyAlgorithm = encryptionKey.algorithm;
|
|
pkESKeyPacket.sessionKey = sessionKey;
|
|
//TODO get preferred algo from signature
|
|
pkESKeyPacket.sessionKeyAlgorithm = enums.read(enums.symmetric, config.encryption_cipher);
|
|
pkESKeyPacket.encrypt(encryptionKey);
|
|
packetList.push(pkESKeyPacket);
|
|
}
|
|
});
|
|
|
|
var symEncryptedPacket;
|
|
if (config.integrity_protect) {
|
|
symEncryptedPacket = new packet.sym_encrypted_integrity_protected();
|
|
} else {
|
|
symEncryptedPacket = new packet.symmetrically_encrypted();
|
|
}
|
|
symEncryptedPacket.packets = literalDataPacket;
|
|
//TODO get preferred algo from signature
|
|
symEncryptedPacket.encrypt(enums.read(enums.symmetric, config.encryption_cipher), sessionKey);
|
|
packetList.push(symEncryptedPacket);
|
|
|
|
var armored = armor.encode(3, packetList.write(), config);
|
|
return armored;
|
|
|
|
}
|
|
|
|
function encryptAndSignMessage(publicKeyPacketlist, privateKeyPacketlist, message) {
|
|
|
|
}
|
|
|
|
function decryptMessage(privateKeyPacketlist, messagePacketlist) {
|
|
|
|
}
|
|
|
|
function decryptAndVerifyMessage(privateKeyPacketlist, publicKeyPacketlist, messagePacketlist) {
|
|
|
|
}
|
|
|
|
function verifyMessage(publicKeyPacketlist, messagePacketlist) {
|
|
|
|
}
|
|
|
|
function signMessage(privateKeyPacketlist, messagePacketlist) {
|
|
|
|
}
|
|
|
|
function generateKeyPair(keyType, numBits, userId, passphrase) {
|
|
debugger;
|
|
var packetlist = new packet.list();
|
|
|
|
var secretKeyPacket = new packet.secret_key();
|
|
secretKeyPacket.algorithm = enums.read(enums.publicKey, keyType);
|
|
secretKeyPacket.generate(numBits);
|
|
secretKeyPacket.encrypt(passphrase);
|
|
|
|
var userIdPacket = new packet.userid();
|
|
userIdPacket.read(userId);
|
|
|
|
var dataToSign = {};
|
|
dataToSign.userid = userIdPacket;
|
|
dataToSign.key = secretKeyPacket;
|
|
var signaturePacket = new packet.signature();
|
|
signaturePacket.issuerKeyId = secretKeyPacket.getKeyId();
|
|
signaturePacket.signatureType = enums.signature.cert_generic;
|
|
signaturePacket.publicKeyAlgorithm = keyType;
|
|
//TODO we should load preferred hash from config, or as input to this function
|
|
signaturePacket.hashAlgorithm = enums.hash.sha256;
|
|
signaturePacket.sign(secretKeyPacket, dataToSign);
|
|
|
|
var secretSubkeyPacket = new packet.secret_subkey();
|
|
secretSubkeyPacket.algorithm = enums.read(enums.publicKey, keyType);
|
|
secretSubkeyPacket.generate(numBits);
|
|
secretSubkeyPacket.encrypt(passphrase);
|
|
|
|
dataToSign = {};
|
|
dataToSign.key = secretKeyPacket;
|
|
dataToSign.bind = secretSubkeyPacket;
|
|
var subkeySignaturePacket = new packet.signature();
|
|
subkeySignaturePacket.issuerKeyId = secretSubkeyPacket.getKeyId();
|
|
subkeySignaturePacket.signatureType = enums.signature.subkey_binding;
|
|
subkeySignaturePacket.publicKeyAlgorithm = keyType;
|
|
//TODO we should load preferred hash from config, or as input to this function
|
|
subkeySignaturePacket.hashAlgorithm = enums.hash.sha256;
|
|
subkeySignaturePacket.sign(secretSubkeyPacket, dataToSign);
|
|
|
|
packetlist.push(secretKeyPacket);
|
|
packetlist.push(userIdPacket);
|
|
packetlist.push(signaturePacket);
|
|
packetlist.push(secretSubkeyPacket);
|
|
packetlist.push(subkeySignaturePacket);
|
|
|
|
var armored = armor.encode(5, packetlist.write(), this.config);
|
|
}
|
|
|
|
/**
|
|
* creates a binary string representation of an encrypted and signed message.
|
|
* The message will be encrypted with the public keys specified and signed
|
|
* with the specified private key.
|
|
* @param {Object} privatekey {obj: [openpgp_msg_privatekey]} Private key
|
|
* to be used to sign the message
|
|
* @param {Object[]} publickeys An arraf of {obj: [openpgp_msg_publickey]}
|
|
* - public keys to be used to encrypt the message
|
|
* @param {String} messagetext message text to encrypt and sign
|
|
* @return {String} a binary string representation of the message which
|
|
* can be OpenPGP armored
|
|
*/
|
|
function write_signed_and_encrypted_message(privatekey, publickeys, messagetext) {
|
|
var result = "";
|
|
var i;
|
|
var literal = new openpgp_packet_literaldata().write_packet(messagetext.replace(/\r\n/g, "\n").replace(/\n/g,
|
|
"\r\n"));
|
|
util.print_debug_hexstr_dump("literal_packet: |" + literal + "|\n", literal);
|
|
for (i = 0; i < publickeys.length; i++) {
|
|
var onepasssignature = new openpgp_packet_onepasssignature();
|
|
var onepasssigstr = "";
|
|
if (i === 0)
|
|
onepasssigstr = onepasssignature.write_packet(1, openpgp.config.config.prefer_hash_algorithm, privatekey, false);
|
|
else
|
|
onepasssigstr = onepasssignature.write_packet(1, openpgp.config.config.prefer_hash_algorithm, privatekey, false);
|
|
util.print_debug_hexstr_dump("onepasssigstr: |" + onepasssigstr + "|\n", onepasssigstr);
|
|
var datasignature = new openpgp_packet_signature().write_message_signature(1, messagetext.replace(/\r\n/g, "\n").replace(
|
|
/\n/g, "\r\n"), privatekey);
|
|
util.print_debug_hexstr_dump("datasignature: |" + datasignature.openpgp + "|\n", datasignature.openpgp);
|
|
if (i === 0) {
|
|
result = onepasssigstr + literal + datasignature.openpgp;
|
|
} else {
|
|
result = onepasssigstr + result + datasignature.openpgp;
|
|
}
|
|
}
|
|
|
|
util.print_debug_hexstr_dump("signed packet: |" + result + "|\n", result);
|
|
// signatures done.. now encryption
|
|
var sessionkey = openpgp_crypto_generateSessionKey(openpgp.config.config.encryption_cipher);
|
|
var result2 = "";
|
|
|
|
// creating session keys for each recipient
|
|
for (i = 0; i < publickeys.length; i++) {
|
|
var pkey = publickeys[i].getEncryptionKey();
|
|
if (pkey === null) {
|
|
util.print_error("no encryption key found! Key is for signing only.");
|
|
return null;
|
|
}
|
|
result2 += new openpgp_packet_encryptedsessionkey().
|
|
write_pub_key_packet(
|
|
pkey.getKeyId(),
|
|
pkey.MPIs,
|
|
pkey.publicKeyAlgorithm,
|
|
openpgp.config.config.encryption_cipher,
|
|
sessionkey);
|
|
}
|
|
if (openpgp.config.config.integrity_protect) {
|
|
result2 += new openpgp_packet_encryptedintegrityprotecteddata().write_packet(openpgp.config.config.encryption_cipher,
|
|
sessionkey, result);
|
|
} else {
|
|
result2 += new openpgp_packet_encrypteddata().write_packet(openpgp.config.config.encryption_cipher, sessionkey,
|
|
result);
|
|
}
|
|
return armor.encode(3, result2, null, null);
|
|
}
|
|
/**
|
|
* creates a binary string representation of an encrypted message.
|
|
* The message will be encrypted with the public keys specified
|
|
* @param {Object[]} publickeys An array of {obj: [openpgp_msg_publickey]}
|
|
* -public keys to be used to encrypt the message
|
|
* @param {String} messagetext message text to encrypt
|
|
* @return {String} a binary string representation of the message
|
|
* which can be OpenPGP armored
|
|
*/
|
|
function write_encrypted_message(publickeys, messagetext) {
|
|
var result = "";
|
|
var literal = new openpgp_packet_literaldata().write_packet(messagetext.replace(/\r\n/g, "\n").replace(/\n/g,
|
|
"\r\n"));
|
|
util.print_debug_hexstr_dump("literal_packet: |" + literal + "|\n", literal);
|
|
result = literal;
|
|
|
|
// signatures done.. now encryption
|
|
var sessionkey = openpgp_crypto_generateSessionKey(openpgp.config.config.encryption_cipher);
|
|
var result2 = "";
|
|
|
|
// creating session keys for each recipient
|
|
for (var i = 0; i < publickeys.length; i++) {
|
|
var pkey = publickeys[i].getEncryptionKey();
|
|
if (pkey === null) {
|
|
util.print_error("no encryption key found! Key is for signing only.");
|
|
return null;
|
|
}
|
|
result2 += new openpgp_packet_encryptedsessionkey().
|
|
write_pub_key_packet(
|
|
pkey.getKeyId(),
|
|
pkey.MPIs,
|
|
pkey.publicKeyAlgorithm,
|
|
openpgp.config.config.encryption_cipher,
|
|
sessionkey);
|
|
}
|
|
if (openpgp.config.config.integrity_protect) {
|
|
result2 += new openpgp_packet_encryptedintegrityprotecteddata().write_packet(openpgp.config.config.encryption_cipher,
|
|
sessionkey, result);
|
|
} else {
|
|
result2 += new openpgp_packet_encrypteddata().write_packet(openpgp.config.config.encryption_cipher, sessionkey,
|
|
result);
|
|
}
|
|
return armor.encode(3, result2, null, null);
|
|
}
|
|
|
|
/**
|
|
* creates a binary string representation a signed message.
|
|
* The message will be signed with the specified private key.
|
|
* @param {Object} privatekey {obj: [openpgp_msg_privatekey]}
|
|
* - the private key to be used to sign the message
|
|
* @param {String} messagetext message text to sign
|
|
* @return {Object} {Object: text [String]}, openpgp: {String} a binary
|
|
* string representation of the message which can be OpenPGP
|
|
* armored(openpgp) and a text representation of the message (text).
|
|
* This can be directly used to OpenPGP armor the message
|
|
*/
|
|
function write_signed_message(privatekey, messagetext) {
|
|
var sig = new openpgp_packet_signature().write_message_signature(1, messagetext.replace(/\r\n/g, "\n").replace(/\n/,
|
|
"\r\n"), privatekey);
|
|
var result = {
|
|
text: messagetext.replace(/\r\n/g, "\n").replace(/\n/, "\r\n"),
|
|
openpgp: sig.openpgp,
|
|
hash: sig.hash
|
|
};
|
|
return armor.encode(2, result, null, null);
|
|
}
|
|
|
|
/**
|
|
* generates a new key pair for openpgp. Beta stage. Currently only
|
|
* supports RSA keys, and no subkeys.
|
|
* @param {Integer} keyType to indicate what type of key to make.
|
|
* RSA is 1. Follows algorithms outlined in OpenPGP.
|
|
* @param {Integer} numBits number of bits for the key creation. (should
|
|
* be 1024+, generally)
|
|
* @param {String} userId assumes already in form of "User Name
|
|
* <username@email.com>"
|
|
* @param {String} passphrase The passphrase used to encrypt the resulting private key
|
|
* @return {Object} {privateKey: [openpgp_msg_privatekey],
|
|
* privateKeyArmored: [string], publicKeyArmored: [string]}
|
|
*/
|
|
function generate_key_pair(keyType, numBits, userId, passphrase) {
|
|
var userIdPacket = new openpgp_packet_userid();
|
|
var userIdString = userIdPacket.write_packet(userId);
|
|
|
|
var keyPair = openpgp_crypto_generateKeyPair(keyType, numBits, passphrase, openpgp.config.config.prefer_hash_algorithm,
|
|
3);
|
|
var privKeyString = keyPair.privateKey;
|
|
var privKeyPacket = new openpgp_packet_keymaterial().read_priv_key(privKeyString.string, 3, privKeyString.string.length);
|
|
if (!privKeyPacket.decryptSecretMPIs(passphrase))
|
|
util.print_error('Issue creating key. Unable to read resulting private key');
|
|
var privKey = new openpgp_msg_privatekey();
|
|
privKey.privateKeyPacket = privKeyPacket;
|
|
privKey.getPreferredSignatureHashAlgorithm = function() {
|
|
return openpgp.config.config.prefer_hash_algorithm
|
|
}; //need to override this to solve catch 22 to generate signature. 8 is value for SHA256
|
|
|
|
var publicKeyString = privKey.privateKeyPacket.publicKey.data;
|
|
var hashData = String.fromCharCode(0x99) + String.fromCharCode(((publicKeyString.length) >> 8) & 0xFF) + String.fromCharCode((
|
|
publicKeyString.length) & 0xFF) + publicKeyString + String.fromCharCode(0xB4) +
|
|
String.fromCharCode((userId.length) >> 24) + String.fromCharCode(((userId.length) >> 16) & 0xFF) + String.fromCharCode(((
|
|
userId.length) >> 8) & 0xFF) + String.fromCharCode((userId.length) & 0xFF) + userId;
|
|
var signature = new openpgp_packet_signature();
|
|
signature = signature.write_message_signature(16, hashData, privKey);
|
|
var publicArmored = armor.encode(4, keyPair.publicKey.string + userIdString + signature.openpgp);
|
|
|
|
var privArmored = armor.encode(5, privKeyString.string + userIdString + signature.openpgp);
|
|
|
|
return {
|
|
privateKey: privKey,
|
|
privateKeyArmored: privArmored,
|
|
publicKeyArmored: publicArmored
|
|
};
|
|
}
|
|
|
|
this.generateKeyPair = generateKeyPair;
|
|
this.write_signed_message = write_signed_message;
|
|
this.write_signed_and_encrypted_message = write_signed_and_encrypted_message;
|
|
this.encryptMessage = encryptMessage;
|
|
this.readArmoredPackets = readArmoredPackets;
|
|
this.readDearmoredPackets = readDearmoredPackets;
|
|
}
|
|
|
|
module.exports = new _openpgp();
|