// 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 Signature Packet (Tag 2) * * RFC4480 5.2: * A Signature packet describes a binding between some public key and * some data. The most common signatures are a signature of a file or a * block of text, and a signature that is a certification of a User ID. */ function openpgp_packet_signature() { this.tag = 2; this.signatureType = null; this.hashAlgorithm = null; this.publicKeyAlgorithm = null; this.version = 4; this.signatureData = null; this.signedHashValue = null; this.mpi = null; this.created = null; this.signatureExpirationTime = null; this.signatureNeverExpires = null; this.exportable = null; this.trustLevel = null; this.trustAmount = null; this.regularExpression = null; this.revocable = null; this.keyExpirationTime = null; this.keyNeverExpires = null; this.preferredSymmetricAlgorithms = null; this.revocationKeyClass = null; this.revocationKeyAlgorithm = null; this.revocationKeyFingerprint = null; this.issuerKeyId = null; this.notation = {}; this.preferredHashAlgorithms = null; this.preferredCompressionAlgorithms = null; this.keyServerPreferences = null; this.preferredKeyServer = null; this.isPrimaryUserID = null; this.policyURI = null; this.keyFlags = null; this.signersUserId = null; this.reasonForRevocationFlag = null; this.reasonForRevocationString = null; this.signatureTargetPublicKeyAlgorithm = null; this.signatureTargetHashAlgorithm = null; this.signatureTargetHash = null; this.embeddedSignature = null; this.verified = false; /** * parsing function for a signature packet (tag 2). * @param {String} bytes payload of a tag 2 packet * @param {Integer} position position to start reading from the bytes string * @param {Integer} len length of the packet or the remaining length of bytes at position * @return {openpgp_packet_encrypteddata} object representation */ this.read = function(bytes) { var i = 0; this.version = bytes[i++].charCodeAt(); // switch on version (3 and 4) switch (this.version) { case 3: // One-octet length of following hashed material. MUST be 5. if (bytes[i++].charCodeAt() != 5) util.print_debug("openpgp.packet.signature.js\n"+ 'invalid One-octet length of following hashed material.' + 'MUST be 5. @:'+(i-1)); var sigpos = i; // One-octet signature type. this.signatureType = bytes[i++].charCodeAt(); // Four-octet creation time. this.created = openpgp_packet_time_read(bytes.substr(i, 4)); i += 4; // storing data appended to data which gets verified this.signatureData = bytes.substring(position, i); // Eight-octet Key ID of signer. this.issuerKeyId = bytes.substring(i, i +8); i += 8; // One-octet public-key algorithm. this.publicKeyAlgorithm = bytes[i++].charCodeAt(); // One-octet hash algorithm. this.hashAlgorithm = bytes[i++].charCodeAt(); break; case 4: this.signatureType = bytes[i++].charCodeAt(); this.publicKeyAlgorithm = bytes[i++].charCodeAt(); this.hashAlgorithm = bytes[i++].charCodeAt(); function subpackets(bytes, signed) { // Two-octet scalar octet count for following hashed subpacket // data. var subpacket_length = openpgp_packet_number_read( bytes.substr(0, 2)); var i = 2; // Hashed subpacket data set (zero or more subpackets) var subpacked_read = 0; while (i < 2 + subpacket_length) { var len = openpgp_packet.read_simple_length(bytes.substr(i)); i += len.offset; // Since it is trivial to add data to the unhashed portion of // the packet we simply ignore all unauthenticated data. if(signed) this.read_sub_packet(bytes.substr(i, len.len)); i += len.len; } return i; } i += subpackets.call(this, bytes.substr(i), true); // A V4 signature hashes the packet body // starting from its first field, the version number, through the end // of the hashed subpacket data. Thus, the fields hashed are the // signature version, the signature type, the public-key algorithm, the // hash algorithm, the hashed subpacket length, and the hashed // subpacket body. this.signatureData = bytes.substr(0, i); i += subpackets.call(this, bytes.substr(i), false); break; default: util.print_error("openpgp.packet.signature.js\n"+ 'unknown signature packet version'+this.version); break; } // Two-octet field holding left 16 bits of signed hash value. this.signedHashValue = bytes.substr(i, 2); i += 2; this.signature = bytes.substr(i); } this.write = function() { return this.signatureData + openpgp_packet_number_write(0, 2) + // Number of unsigned subpackets. this.signedHashValue + this.signature; } /** * Signs provided data. This needs to be done prior to serialization. * @param {Object} data Contains packets to be signed. * @param {openpgp_msg_privatekey} privatekey private key used to sign the message. */ this.sign = function(privatekey, data) { var publickey = privatekey.public_key; var result = String.fromCharCode(4); result += String.fromCharCode(this.signatureType); result += String.fromCharCode(this.publicKeyAlgorithm); result += String.fromCharCode(this.hashAlgorithm); // Add subpackets here result += openpgp_packet_number_write(0, 2); this.signatureData = result; var trailer = this.calculateTrailer(); var toHash = this.toSign(this.signatureType, data) + this.signatureData + trailer; var hash = openpgp_crypto_hashData(this.hashAlgorithm, toHash); this.signedHashValue = hash.substr(0, 2); this.signature = openpgp_crypto_signData(this.hashAlgorithm, this.publicKeyAlgorithm, publickey.mpi, privatekey.mpi, toHash); } /** * creates a string representation of a sub signature packet (See RFC 4880 5.2.3.1) * @param {Integer} type subpacket signature type. Signature types as described * in RFC4880 Section 5.2.3.2 * @param {String} data data to be included * @return {String} a string-representation of a sub signature packet (See RFC 4880 5.2.3.1) */ function write_sub_packet(type, data) { var result = ""; result += openpgp_packet.encode_length(data.length+1); result += String.fromCharCode(type); result += data; return result; } // V4 signature sub packets this.read_sub_packet = function(bytes) { var mypos = 0; function read_array(prop, bytes) { this[prop] = []; for (var i = 0; i < bytes.length; i++) { this[prop].push(bytes[i].charCodeAt()); } } // The leftwost bit denotes a "critical" packet, but we ignore it. var type = bytes[mypos++].charCodeAt() & 0x7F; // subpacket type switch (type) { case 2: // Signature Creation Time this.created = openpgp_packet_time_read(bytes.substr(mypos)); break; case 3: // Signature Expiration Time var time = openpgp_packet_time_read(bytes.substr(mypos)); this.signatureNeverExpires = time.getTime() == 0; this.signatureExpirationTime = time; break; case 4: // Exportable Certification this.exportable = bytes[mypos++].charCodeAt() == 1; break; case 5: // Trust Signature this.trustLevel = bytes[mypos++].charCodeAt(); this.trustAmount = bytes[mypos++].charCodeAt(); break; case 6: // Regular Expression this.regularExpression = bytes.substr(mypos); break; case 7: // Revocable this.revocable = bytes[mypos++].charCodeAt() == 1; break; case 9: // Key Expiration Time var time = openpgp_packet_time_read(bytes.substr(mypos)); this.keyExpirationTime = time; this.keyNeverExpires = time.getTime() == 0; break; case 11: // Preferred Symmetric Algorithms this.preferredSymmetricAlgorithms = []; while(mypos != bytes.length) { this.preferredSymmetricAlgorithms.push(bytes[mypos++].charCodeAt()); } break; case 12: // Revocation Key // (1 octet of class, 1 octet of public-key algorithm ID, 20 // octets of // fingerprint) this.revocationKeyClass = bytes[mypos++].charCodeAt(); this.revocationKeyAlgorithm = bytes[mypos++].charCodeAt(); this.revocationKeyFingerprint = bytes.substr(mypos, 20); break; case 16: // Issuer this.issuerKeyId = bytes.substr(mypos, 8); break; case 20: // Notation Data // We don't know how to handle anything but a text flagged data. if(bytes[mypos].charCodeAt() == 0x80) { mypos += 4; var m = openpgp_packet_number_read(bytes.substr(mypos, 2)); mypos += 2 var n = openpgp_packet_number_read(bytes.substr(mypos, 2)); mypos += 2 var name = bytes.substr(mypos, m), value = bytes.substr(mypos + m, n); this.notation[name] = value; } break; case 21: // Preferred Hash Algorithms read_array.call(this, 'preferredHashAlgorithms', bytes.substr(mypos)); break; case 22: // Preferred Compression Algorithms read_array.call(this, 'preferredCompressionAlgorithms ', bytes.substr(mypos)); break; case 23: // Key Server Preferences read_array.call(this, 'keyServerPreferencess', bytes.substr(mypos)); break; case 24: // Preferred Key Server this.preferredKeyServer = bytes.substr(mypos); break; case 25: // Primary User ID this.isPrimaryUserID = bytes[mypos++] != 0; break; case 26: // Policy URI this.policyURI = bytes.substr(mypos); break; case 27: // Key Flags read_array.call(this, 'keyFlags', bytes.substr(mypos)); break; case 28: // Signer's User ID this.signersUserId += bytes.substr(mypos); break; case 29: // Reason for Revocation this.reasonForRevocationFlag = bytes[mypos++].charCodeAt(); this.reasonForRevocationString = bytes.substr(mypos); break; case 30: // Features read_array.call(this, 'features', bytes.substr(mypos)); break; case 31: // Signature Target // (1 octet public-key algorithm, 1 octet hash algorithm, N octets hash) this.signatureTargetPublicKeyAlgorithm = bytes[mypos++].charCodeAt(); this.signatureTargetHashAlgorithm = bytes[mypos++].charCodeAt(); var len = openpgp_crypto_getHashByteLength(this.signatureTargetHashAlgorithm); this.signatureTargetHash = bytes.substr(mypos, len); break; case 32: // Embedded Signature this.embeddedSignature = new openpgp_packet_signature(); this.embeddedSignature.read(bytes.substr(mypos)); break; default: util.print_error("openpgp.packet.signature.js\n"+ 'unknown signature subpacket type '+type+" @:"+mypos+ " subplen:"+subplen+" len:"+len); break; } }; this.toSign = function(type, data) { var t = openpgp_packet_signature.type; switch(type) { case t.binary: return data.literal.get_data_bytes(); case t.text: return toSign(t.binary, data) .replace(/\r\n/g, '\n') .replace(/\n/g, '\r\n'); case t.standalone: return '' case t.cert_generic: case t.cert_persona: case t.cert_casual: case t.cert_positive: case t.cert_revocation: { var packet, tag; if(data.userid != undefined) { tag = 0xB4; packet = data.userid; } else if(data.userattribute != undefined) { tag = 0xD1 packet = data.userattribute; } else throw new Error('Either a userid or userattribute packet needs to be ' + 'supplied for certification.'); var bytes = packet.write(); return this.toSign(t.key, data) + String.fromCharCode(tag) + openpgp_packet_number_write(bytes.length, 4) + bytes; } case t.subkey_binding: case t.key_binding: { return this.toSign(t.key, data) + this.toSign(t.key, { key: data.bind }); } case t.key: { if(data.key == undefined) throw new Error('Key packet is required for this sigtature.'); var bytes = data.key.write(); return String.fromCharCode(0x99) + openpgp_packet_number_write(bytes.length, 2) + bytes; } case t.key_revocation: case t.subkey_revocation: return this.toSign(t.key, data); case t.timestamp: return ''; case t.thrid_party: throw new Error('Not implemented'); break; default: throw new Error('Unknown signature type.') } } this.calculateTrailer = function() { // calculating the trailer var trailer = ''; trailer += String.fromCharCode(this.version); trailer += String.fromCharCode(0xFF); trailer += openpgp_packet_number_write(this.signatureData.length, 4); return trailer } /** * verifys the signature packet. Note: not signature types are implemented * @param {String} data data which on the signature applies * @param {openpgp_msg_privatekey} key the public key to verify the signature * @return {boolean} True if message is verified, else false. */ this.verify = function(key, data) { var bytes = this.toSign(this.signatureType, data), trailer = this.calculateTrailer(); var mpicount = 0; // Algorithm-Specific Fields for RSA signatures: // - multiprecision number (MPI) of RSA signature value m**d mod n. if (this.publicKeyAlgorithm > 0 && this.publicKeyAlgorithm < 4) mpicount = 1; // Algorithm-Specific Fields for DSA signatures: // - MPI of DSA value r. // - MPI of DSA value s. else if (this.publicKeyAlgorithm == 17) mpicount = 2; var mpi = [], i = 0; for (var j = 0; j < mpicount; j++) { mpi[j] = new openpgp_type_mpi(); i += mpi[j].read(this.signature.substr(i)); } this.verified = openpgp_crypto_verifySignature(this.publicKeyAlgorithm, this.hashAlgorithm, mpi, key.mpi, bytes + this.signatureData + trailer); return this.verified; } } /** One pass signature packet type * @enum {Integer} */ openpgp_packet_signature.type = { /** 0x00: Signature of a binary document. */ binary: 0, /** 0x01: Signature of a canonical text document. * Canonicalyzing the document by converting line endings. */ text: 1, /** 0x02: Standalone signature. * This signature is a signature of only its own subpacket contents. * It is calculated identically to a signature over a zero-lengh * binary document. Note that it doesn't make sense to have a V3 * standalone signature. */ standalone: 2, /** 0x10: Generic certification of a User ID and Public-Key packet. * The issuer of this certification does not make any particular * assertion as to how well the certifier has checked that the owner * of the key is in fact the person described by the User ID. */ cert_generic: 16, /** 0x11: Persona certification of a User ID and Public-Key packet. * The issuer of this certification has not done any verification of * the claim that the owner of this key is the User ID specified. */ cert_persona: 17, /** 0x12: Casual certification of a User ID and Public-Key packet. * The issuer of this certification has done some casual * verification of the claim of identity. */ cert_casual: 18, /** 0x13: Positive certification of a User ID and Public-Key packet. * The issuer of this certification has done substantial * verification of the claim of identity. * * Most OpenPGP implementations make their "key signatures" as 0x10 * certifications. Some implementations can issue 0x11-0x13 * certifications, but few differentiate between the types. */ cert_positive: 19, /** 0x30: Certification revocation signature * This signature revokes an earlier User ID certification signature * (signature class 0x10 through 0x13) or direct-key signature * (0x1F). It should be issued by the same key that issued the * revoked signature or an authorized revocation key. The signature * is computed over the same data as the certificate that it * revokes, and should have a later creation date than that * certificate. */ cert_revocation: 48, /** 0x18: Subkey Binding Signature * This signature is a statement by the top-level signing key that * indicates that it owns the subkey. This signature is calculated * directly on the primary key and subkey, and not on any User ID or * other packets. A signature that binds a signing subkey MUST have * an Embedded Signature subpacket in this binding signature that * contains a 0x19 signature made by the signing subkey on the * primary key and subkey. */ subkey_binding: 24, /** 0x19: Primary Key Binding Signature * This signature is a statement by a signing subkey, indicating * that it is owned by the primary key and subkey. This signature * is calculated the same way as a 0x18 signature: directly on the * primary key and subkey, and not on any User ID or other packets. * When a signature is made over a key, the hash data starts with the * octet 0x99, followed by a two-octet length of the key, and then body * of the key packet. (Note that this is an old-style packet header for * a key packet with two-octet length.) A subkey binding signature * (type 0x18) or primary key binding signature (type 0x19) then hashes * the subkey using the same format as the main key (also using 0x99 as * the first octet). */ key_binding: 25, /** 0x1F: Signature directly on a key * This signature is calculated directly on a key. It binds the * information in the Signature subpackets to the key, and is * appropriate to be used for subpackets that provide information * about the key, such as the Revocation Key subpacket. It is also * appropriate for statements that non-self certifiers want to make * about the key itself, rather than the binding between a key and a * name. */ key: 31, /** 0x20: Key revocation signature * The signature is calculated directly on the key being revoked. A * revoked key is not to be used. Only revocation signatures by the * key being revoked, or by an authorized revocation key, should be * considered valid revocation signatures.a */ key_revocation: 32, /** 0x28: Subkey revocation signature * The signature is calculated directly on the subkey being revoked. * A revoked subkey is not to be used. Only revocation signatures * by the top-level signature key that is bound to this subkey, or * by an authorized revocation key, should be considered valid * revocation signatures. * Key revocation signatures (types 0x20 and 0x28) * hash only the key being revoked. */ subkey_revocation: 40, /** 0x40: Timestamp signature. * This signature is only meaningful for the timestamp contained in * it. */ timestamp: 64, /** 0x50: Third-Party Confirmation signature. * This signature is a signature over some other OpenPGP Signature * packet(s). It is analogous to a notary seal on the signed data. * A third-party signature SHOULD include Signature Target * subpacket(s) to give easy identification. Note that we really do * mean SHOULD. There are plausible uses for this (such as a blind * party that only sees the signature, not the key or source * document) that cannot include a target subpacket. */ third_party: 80 }