mirror of
https://github.com/openpgpjs/openpgpjs.git
synced 2025-03-30 15:08:32 +00:00
342 lines
10 KiB
JavaScript
342 lines
10 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
|
|
|
|
/**
|
|
* @class
|
|
* @classdesc Implementation of the Key Material Packet (Tag 5,6,7,14)
|
|
*
|
|
* RFC4480 5.5:
|
|
* A key material packet contains all the information about a public or
|
|
* private key. There are four variants of this packet type, and two
|
|
* major versions. Consequently, this section is complex.
|
|
*/
|
|
function openpgp_packet_secret_key() {
|
|
this.tag = 5;
|
|
this.public_key = new openpgp_packet_public_key();
|
|
this.mpi = [];
|
|
this.encrypted = null;
|
|
|
|
|
|
function get_hash_len(hash) {
|
|
if(hash == openpgp.hash.sha1)
|
|
return 20;
|
|
else
|
|
return 2;
|
|
}
|
|
|
|
function get_hash_fn(hash) {
|
|
if(hash == openpgp.hash.sha1)
|
|
return str_sha1;
|
|
else
|
|
return function(c) {
|
|
return openpgp_packet_number_write(util.calc_checksum(c), 2);
|
|
}
|
|
}
|
|
|
|
// Helper function
|
|
function parse_cleartext_mpi(hash_algorithm, cleartext, algorithm) {
|
|
var hashlen = get_hash_len(hash_algorithm),
|
|
hashfn = get_hash_fn(hash_algorithm);
|
|
|
|
var hashtext = cleartext.substr(cleartext.length - hashlen);
|
|
cleartext = cleartext.substr(0, cleartext.length - hashlen);
|
|
|
|
var hash = hashfn(cleartext);
|
|
|
|
if(hash != hashtext)
|
|
throw new Error("Hash mismatch.");
|
|
|
|
var mpis = openpgp_crypto_getPrivateMpiCount(algorithm);
|
|
|
|
var j = 0;
|
|
var mpi = [];
|
|
for(var i = 0; i < mpis && j < cleartext.length; i++) {
|
|
mpi[i] = new openpgp_type_mpi();
|
|
j += mpi[i].read(cleartext.substr(j));
|
|
}
|
|
|
|
return mpi;
|
|
}
|
|
|
|
function write_cleartext_mpi(hash_algorithm, mpi) {
|
|
var bytes= '';
|
|
for(var i in mpi) {
|
|
bytes += mpi[i].write();
|
|
}
|
|
|
|
|
|
bytes += get_hash_fn(hash_algorithm)(bytes);
|
|
|
|
return bytes;
|
|
}
|
|
|
|
|
|
// 5.5.3. Secret-Key Packet Formats
|
|
|
|
/**
|
|
* Internal parser for private keys as specified in RFC 4880 section 5.5.3
|
|
* @param {String} bytes Input string to read the packet from
|
|
* @param {Integer} position Start position for the parser
|
|
* @param {Integer} len Length of the packet or remaining length of bytes
|
|
* @return {Object} This object with attributes set by the parser
|
|
*/
|
|
this.read = function(bytes) {
|
|
// - A Public-Key or Public-Subkey packet, as described above.
|
|
var len = this.public_key.read(bytes);
|
|
|
|
bytes = bytes.substr(len);
|
|
|
|
|
|
// - One octet indicating string-to-key usage conventions. Zero
|
|
// indicates that the secret-key data is not encrypted. 255 or 254
|
|
// indicates that a string-to-key specifier is being given. Any
|
|
// other value is a symmetric-key encryption algorithm identifier.
|
|
var isEncrypted = bytes[0].charCodeAt();
|
|
|
|
if(isEncrypted) {
|
|
this.encrypted = bytes;
|
|
} else {
|
|
|
|
// - Plain or encrypted multiprecision integers comprising the secret
|
|
// key data. These algorithm-specific fields are as described
|
|
// below.
|
|
|
|
this.mpi = parse_cleartext_mpi('mod', bytes.substr(1),
|
|
this.public_key.algorithm);
|
|
}
|
|
|
|
}
|
|
|
|
/*
|
|
* Creates an OpenPGP key packet for the given key. much
|
|
* TODO in regards to s2k, subkeys.
|
|
* @param {Integer} keyType Follows the OpenPGP algorithm standard,
|
|
* IE 1 corresponds to RSA.
|
|
* @param {RSA.keyObject} key
|
|
* @param passphrase
|
|
* @param s2kHash
|
|
* @param symmetricEncryptionAlgorithm
|
|
* @param timePacket
|
|
* @return {Object} {body: [string]OpenPGP packet body contents,
|
|
header: [string] OpenPGP packet header, string: [string] header+body}
|
|
*/
|
|
this.write = function() {
|
|
var bytes = this.public_key.write();
|
|
|
|
if(!this.encrypted) {
|
|
bytes += String.fromCharCode(0);
|
|
|
|
bytes += write_cleartext_mpi('mod', this.mpi);
|
|
} else {
|
|
bytes += this.encrypted;
|
|
}
|
|
|
|
return bytes;
|
|
}
|
|
|
|
|
|
|
|
|
|
/** Encrypt the payload. By default, we use aes256 and iterated, salted string
|
|
* to key specifier
|
|
* @param {String} passphrase
|
|
*/
|
|
this.encrypt = function(passphrase) {
|
|
|
|
var s2k = new openpgp_type_s2k(),
|
|
symmetric = openpgp.symmetric.aes256,
|
|
cleartext = write_cleartext_mpi(openpgp.hash.sha1, this.mpi),
|
|
key = produceEncryptionKey(s2k, passphrase, symmetric),
|
|
blockLen = openpgp_crypto_getBlockLength(symmetric),
|
|
iv = openpgp_crypto_getRandomBytes(blockLen);
|
|
|
|
|
|
this.encrypted = '';
|
|
this.encrypted += String.fromCharCode(254);
|
|
this.encrypted += String.fromCharCode(symmetric);
|
|
this.encrypted += s2k.write();
|
|
this.encrypted += iv;
|
|
|
|
console.log(cleartext);
|
|
|
|
switch(symmetric) {
|
|
case 3:
|
|
this.encrypted += normal_cfb_encrypt(function(block, key) {
|
|
var cast5 = new openpgp_symenc_cast5();
|
|
cast5.setKey(key);
|
|
return cast5.encrypt(util.str2bin(block));
|
|
}, iv.length, key, cleartext, iv);
|
|
break;
|
|
case 7:
|
|
case 8:
|
|
case 9:
|
|
var fn = function(block,key) {
|
|
return AESencrypt(util.str2bin(block),key);
|
|
}
|
|
this.encrypted += normal_cfb_encrypt(fn,
|
|
iv.length, new keyExpansion(key), cleartext, iv);
|
|
break;
|
|
default:
|
|
throw new Error("Unsupported symmetric encryption algorithm.");
|
|
}
|
|
}
|
|
|
|
function produceEncryptionKey(s2k, passphrase, algorithm) {
|
|
return s2k.produce_key(passphrase,
|
|
openpgp_crypto_getKeyLength(algorithm));
|
|
}
|
|
|
|
/**
|
|
* Decrypts the private key MPIs which are needed to use the key.
|
|
* openpgp_packet_keymaterial.hasUnencryptedSecretKeyData should be
|
|
* false otherwise
|
|
* a call to this function is not needed
|
|
*
|
|
* @param {String} str_passphrase The passphrase for this private key
|
|
* as string
|
|
* @return {Boolean} True if the passphrase was correct; false if not
|
|
*/
|
|
this.decrypt = function(passphrase) {
|
|
if (!this.encrypted)
|
|
return;
|
|
|
|
var i = 0,
|
|
symmetric,
|
|
key;
|
|
|
|
var s2k_usage = this.encrypted[i++].charCodeAt();
|
|
|
|
// - [Optional] If string-to-key usage octet was 255 or 254, a one-
|
|
// octet symmetric encryption algorithm.
|
|
if (s2k_usage == 255 || s2k_usage == 254) {
|
|
symmetric = this.encrypted[i++].charCodeAt();
|
|
|
|
// - [Optional] If string-to-key usage octet was 255 or 254, a
|
|
// string-to-key specifier. The length of the string-to-key
|
|
// specifier is implied by its type, as described above.
|
|
var s2k = new openpgp_type_s2k();
|
|
i += s2k.read(this.encrypted.substr(i));
|
|
|
|
key = produceEncryptionKey(s2k, passphrase, symmetric);
|
|
} else {
|
|
symmetric = s2k_usage;
|
|
key = MD5(passphrase);
|
|
}
|
|
|
|
// - [Optional] If secret data is encrypted (string-to-key usage octet
|
|
// not zero), an Initial Vector (IV) of the same length as the
|
|
// cipher's block size.
|
|
var iv = this.encrypted.substr(i,
|
|
openpgp_crypto_getBlockLength(symmetric));
|
|
|
|
i += iv.length;
|
|
|
|
var cleartext,
|
|
ciphertext = this.encrypted.substr(i);
|
|
|
|
|
|
switch (symmetric) {
|
|
case 1: // - IDEA [IDEA]
|
|
throw new Error("IDEA is not implemented.");
|
|
return false;
|
|
case 2: // - TripleDES (DES-EDE, [SCHNEIER] [HAC] - 168 bit key derived from 192)
|
|
cleartext = normal_cfb_decrypt(function(block, key) {
|
|
return des(key, block,1,null,0);
|
|
}, iv.length, key, ciphertext, iv);
|
|
break;
|
|
case 3: // - CAST5 (128 bit key, as per [RFC2144])
|
|
cleartext = normal_cfb_decrypt(function(block, key) {
|
|
var cast5 = new openpgp_symenc_cast5();
|
|
cast5.setKey(key);
|
|
return cast5.encrypt(util.str2bin(block));
|
|
}, iv.length, util.str2bin(key.substring(0,16)), ciphertext, iv);
|
|
break;
|
|
case 4: // - Blowfish (128 bit key, 16 rounds) [BLOWFISH]
|
|
cleartext = normal_cfb_decrypt(function(block, key) {
|
|
var blowfish = new Blowfish(key);
|
|
return blowfish.encrypt(block);
|
|
}, iv.length, key, ciphertext, iv);
|
|
break;
|
|
case 7: // - AES with 128-bit key [AES]
|
|
case 8: // - AES with 192-bit key
|
|
case 9: // - AES with 256-bit key
|
|
cleartext = normal_cfb_decrypt(function(block,key){
|
|
return AESencrypt(util.str2bin(block),key);
|
|
},
|
|
iv.length, new keyExpansion(key),
|
|
ciphertext, iv);
|
|
break;
|
|
case 10: // - Twofish with 256-bit key [TWOFISH]
|
|
throw new Error("Twofish is not implemented.");
|
|
return false;
|
|
case 5: // - Reserved
|
|
case 6: // - Reserved
|
|
default:
|
|
throw new Error("Unknown symmetric algorithm.");
|
|
return false;
|
|
}
|
|
|
|
var hash;
|
|
if(s2k_usage == 254)
|
|
hash = openpgp.hash.sha1;
|
|
else
|
|
hash = 'mod';
|
|
|
|
|
|
this.mpi = parse_cleartext_mpi(hash, cleartext,
|
|
this.public_key.algorithm);
|
|
}
|
|
|
|
/**
|
|
* Calculates the key id of they key
|
|
* @return {String} A 8 byte key id
|
|
*/
|
|
this.getKeyId = function() {
|
|
if (this.version == 4) {
|
|
var f = this.getFingerprint();
|
|
return f.substring(12,20);
|
|
} else if (this.version == 3 && this.publicKeyAlgorithm > 0 && this.publicKeyAlgorithm < 4) {
|
|
var key_id = this.MPIs[0].substring((this.MPIs[0].mpiByteLength-8));
|
|
util.print_debug("openpgp.msg.publickey read_nodes:\n"+"V3 key ID: "+key_id);
|
|
return key_id;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Calculates the fingerprint of the key
|
|
* @return {String} A string containing the fingerprint
|
|
*/
|
|
this.getFingerprint = function() {
|
|
if (this.version == 4) {
|
|
tohash = String.fromCharCode(0x99)+ String.fromCharCode(((this.packetdata.length) >> 8) & 0xFF)
|
|
+ String.fromCharCode((this.packetdata.length) & 0xFF)+this.packetdata;
|
|
util.print_debug("openpgp.msg.publickey creating subkey fingerprint by hashing:"+util.hexstrdump(tohash)+"\npublickeyalgorithm: "+this.publicKeyAlgorithm);
|
|
return str_sha1(tohash, tohash.length);
|
|
} else if (this.version == 3 && this.publicKeyAlgorithm > 0 && this.publicKeyAlgorithm < 4) {
|
|
return MD5(this.MPIs[0].MPI);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
function openpgp_packet_secret_subkey() {
|
|
openpgp_packet_secret_key.call(this);
|
|
this.tag = 7;
|
|
}
|
|
|
|
|