openpgpjs/src/packet/secret_key.js
2013-05-09 14:25:09 +02:00

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;
}