# Source: packet/signature.js

    // 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

    /** @module packet/signature */

    var util = require('../util'),
      packet = require('./packet.js'),
      enums = require('../enums.js'),
      crypto = require('../crypto'),
      type_mpi = require('../type/mpi.js'),
      type_keyid = require('../type/keyid.js');

    /**
     * @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.
     */
    module.exports = function packet_signature() {

      this.version = 4;
      this.signatureType = null;
      this.hashAlgorithm = null;
      this.publicKeyAlgorithm = null;

      this.signatureData = null;
      this.signedHashValue = null;
      this.mpi = null;

      this.created = new Date();
      this.signatureExpirationTime = null;
      this.signatureNeverExpires = true;
      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 = new type_keyid();
      this.notation = null;
      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.features = 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 {module:packet/signature} object representation
       */
      this.read = function(bytes) {
        var i = 0;

        this.version = bytes.charCodeAt(i++);
        // switch on version (3 and 4)
        switch (this.version) {
          case 3:
            // One-octet length of following hashed material. MUST be 5.
            if (bytes.charCodeAt(i++) != 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.charCodeAt(i++);

            // Four-octet creation time.
            this.created = util.readDate(bytes.substr(i, 4));
            i += 4;

            // storing data appended to data which gets verified
            this.signatureData = bytes.substring(sigpos, i);

            // Eight-octet Key ID of signer.
            this.issuerKeyId.read(bytes.substring(i, i + 8));
            i += 8;

            // One-octet public-key algorithm.
            this.publicKeyAlgorithm = bytes.charCodeAt(i++);

            // One-octet hash algorithm.
            this.hashAlgorithm = bytes.charCodeAt(i++);
            break;
          case 4:
            this.signatureType = bytes.charCodeAt(i++);
            this.publicKeyAlgorithm = bytes.charCodeAt(i++);
            this.hashAlgorithm = bytes.charCodeAt(i++);

            function subpackets(bytes) {
              // Two-octet scalar octet count for following subpacket data.
              var subpacket_length = util.readNumber(
                bytes.substr(0, 2));

              var i = 2;

              // subpacket data set (zero or more subpackets)
              var subpacked_read = 0;
              while (i < 2 + subpacket_length) {

                var len = packet.readSimpleLength(bytes.substr(i));
                i += len.offset;

                this.read_sub_packet(bytes.substr(i, len.len));

                i += len.len;
              }

              return i;
            }

            // hashed subpackets
            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);

            // unhashed subpackets
            i += subpackets.call(this, bytes.substr(i), false);

            break;
          default:
            throw new Error('Version ' + version + ' of the signature is unsupported.');
            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 +
          util.writeNumber(0, 2) + // Number of unsigned subpackets.
          this.signedHashValue +
          this.signature;
      };

      /**
       * Signs provided data. This needs to be done prior to serialization.
       * @param {module:packet/secret_key} key private key used to sign the message.
       * @param {Object} data Contains packets to be signed.
       */
      this.sign = function(key, data) {
        var signatureType = enums.write(enums.signature, this.signatureType),
          publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm),
          hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm);

        var result = String.fromCharCode(4);
        result += String.fromCharCode(signatureType);
        result += String.fromCharCode(publicKeyAlgorithm);
        result += String.fromCharCode(hashAlgorithm);

        this.issuerKeyId = key.getKeyId();

        // Add hashed subpackets
        result += this.write_all_sub_packets();

        this.signatureData = result;

        var trailer = this.calculateTrailer();

        var toHash = this.toSign(signatureType, data) +
          this.signatureData + trailer;

        var hash = crypto.hash.digest(hashAlgorithm, toHash);

        this.signedHashValue = hash.substr(0, 2);

        this.signature = crypto.signature.sign(hashAlgorithm,
          publicKeyAlgorithm, key.mpi, toHash);
      };

      /**
       * Creates string of bytes with all subpacket data
       * @return {String} a string-representation of a all subpacket data
       */
      this.write_all_sub_packets = function() {
        var sub = enums.signatureSubpacket;
        var result = '';
        var bytes = '';
        if (this.created !== null) {
          result += write_sub_packet(sub.signature_creation_time, util.writeDate(this.created));
        }
        if (this.signatureExpirationTime !== null) {
          result += write_sub_packet(sub.signature_expiration_time, util.writeNumber(this.signatureExpirationTime, 4));
        }
        if (this.exportable !== null) {
          result += write_sub_packet(sub.exportable_certification, String.fromCharCode(this.exportable ? 1 : 0));
        }
        if (this.trustLevel !== null) {
          bytes = String.fromCharCode(this.trustLevel) + String.fromCharCode(this.trustAmount);
          result += write_sub_packet(sub.trust_signature, bytes);
        }
        if (this.regularExpression !== null) {
          result += write_sub_packet(sub.regular_expression, this.regularExpression);
        }
        if (this.revocable !== null) {
          result += write_sub_packet(sub.revocable, String.fromCharCode(this.revocable ? 1 : 0));
        }
        if (this.keyExpirationTime !== null) {
          result += write_sub_packet(sub.key_expiration_time, util.writeNumber(this.keyExpirationTime, 4));
        }
        if (this.preferredSymmetricAlgorithms !== null) {
          bytes = util.bin2str(this.preferredSymmetricAlgorithms);
          result += write_sub_packet(sub.preferred_symmetric_algorithms, bytes);
        }
        if (this.revocationKeyClass !== null) {
          bytes = String.fromCharCode(this.revocationKeyClass);
          bytes += String.fromCharCode(this.revocationKeyAlgorithm);
          bytes += this.revocationKeyFingerprint;
          result += write_sub_packet(sub.revocation_key, bytes);
        }
        if (!this.issuerKeyId.isNull()) {
          result += write_sub_packet(sub.issuer, this.issuerKeyId.write());
        }
        if (this.notation !== null) {
          for (var name in this.notation) {
            if (this.notation.hasOwnProperty(name)) {
              var value = this.notation[name];
              bytes = String.fromCharCode(0x80);
              bytes += String.fromCharCode(0);
              bytes += String.fromCharCode(0);
              bytes += String.fromCharCode(0);
              // 2 octets of name length
              bytes += util.writeNumber(name.length, 2);
              // 2 octets of value length
              bytes += util.writeNumber(value.length, 2);
              bytes += name + value;
              result += write_sub_packet(sub.notation_data, bytes);
            }
          }
        }
        if (this.preferredHashAlgorithms !== null) {
          bytes = util.bin2str(this.preferredHashAlgorithms);
          result += write_sub_packet(sub.preferred_hash_algorithms, bytes);
        }
        if (this.preferredCompressionAlgorithms !== null) {
          bytes = util.bin2str(this.preferredCompressionAlgorithms);
          result += write_sub_packet(sub.preferred_hash_algorithms, bytes);
        }
        if (this.keyServerPreferences !== null) {
          bytes = util.bin2str(this.keyServerPreferences);
          result += write_sub_packet(sub.key_server_preferences, bytes);
        }
        if (this.preferredKeyServer !== null) {
          result += write_sub_packet(sub.preferred_key_server, this.preferredKeyServer);
        }
        if (this.isPrimaryUserID !== null) {
          result += write_sub_packet(sub.primary_user_id, String.fromCharCode(this.isPrimaryUserID ? 1 : 0));
        }
        if (this.policyURI !== null) {
          result += write_sub_packet(sub.policy_uri, this.policyURI);
        }
        if (this.keyFlags !== null) {
          bytes = util.bin2str(this.keyFlags);
          result += write_sub_packet(sub.key_flags, bytes);
        }
        if (this.signersUserId !== null) {
          result += write_sub_packet(sub.signers_user_id, this.signersUserId);
        }
        if (this.reasonForRevocationFlag !== null) {
          bytes = String.fromCharCode(this.reasonForRevocationFlag);
          bytes += this.reasonForRevocationString;
          result += write_sub_packet(sub.reason_for_revocation, bytes);
        }
        if (this.features !== null) {
          bytes = util.bin2str(this.features);
          result += write_sub_packet(sub.features, bytes);
        }
        if (this.signatureTargetPublicKeyAlgorithm !== null) {
          bytes = String.fromCharCode(this.signatureTargetPublicKeyAlgorithm);
          bytes += String.fromCharCode(this.signatureTargetHashAlgorithm);
          bytes += this.signatureTargetHash;
          result += write_sub_packet(sub.signature_target, bytes);
        }
        if (this.embeddedSignature !== null) {
          result += write_sub_packet(sub.embedded_signature, this.embeddedSignature.write());
        }
        result = util.writeNumber(result.length, 2) + result;
        return result;
      };

      /**
       * 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 += packet.writeSimpleLength(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.charCodeAt(i));
          }
        }

        // The leftwost bit denotes a "critical" packet, but we ignore it.
        var type = bytes.charCodeAt(mypos++) & 0x7F;

        // subpacket type
        switch (type) {
          case 2:
            // Signature Creation Time
            this.created = util.readDate(bytes.substr(mypos));
            break;
          case 3:
            // Signature Expiration Time in seconds
            var seconds = util.readNumber(bytes.substr(mypos));

            this.signatureNeverExpires = seconds == 0;
            this.signatureExpirationTime = seconds;

            break;
          case 4:
            // Exportable Certification
            this.exportable = bytes.charCodeAt(mypos++) == 1;
            break;
          case 5:
            // Trust Signature
            this.trustLevel = bytes.charCodeAt(mypos++);
            this.trustAmount = bytes.charCodeAt(mypos++);
            break;
          case 6:
            // Regular Expression
            this.regularExpression = bytes.substr(mypos);
            break;
          case 7:
            // Revocable
            this.revocable = bytes.charCodeAt(mypos++) == 1;
            break;
          case 9:
            // Key Expiration Time in seconds
            var seconds = util.readNumber(bytes.substr(mypos));

            this.keyExpirationTime = seconds;
            this.keyNeverExpires = seconds == 0;

            break;
          case 11:
            // Preferred Symmetric Algorithms
            this.preferredSymmetricAlgorithms = [];

            while (mypos != bytes.length) {
              this.preferredSymmetricAlgorithms.push(bytes.charCodeAt(mypos++));
            }

            break;
          case 12:
            // Revocation Key
            // (1 octet of class, 1 octet of public-key algorithm ID, 20
            // octets of
            // fingerprint)
            this.revocationKeyClass = bytes.charCodeAt(mypos++);
            this.revocationKeyAlgorithm = bytes.charCodeAt(mypos++);
            this.revocationKeyFingerprint = bytes.substr(mypos, 20);
            break;

          case 16:
            // Issuer
            this.issuerKeyId.read(bytes.substr(mypos));
            break;

          case 20:
            // Notation Data
            // We don't know how to handle anything but a text flagged data.
            if (bytes.charCodeAt(mypos) == 0x80) {

              // We extract key/value tuple from the byte stream.
              mypos += 4;
              var m = util.readNumber(bytes.substr(mypos, 2));
              mypos += 2
              var n = util.readNumber(bytes.substr(mypos, 2));
              mypos += 2

              var name = bytes.substr(mypos, m),
                value = bytes.substr(mypos + m, n);

              this.notation = this.notation || {};
              this.notation[name] = value;
            } else throw new Error("Unsupported notation flag.");
            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.charCodeAt(mypos++);
            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.charCodeAt(mypos++);
            this.signatureTargetHashAlgorithm = bytes.charCodeAt(mypos++);

            var len = crypto.getHashByteLength(this.signatureTargetHashAlgorithm);

            this.signatureTargetHash = bytes.substr(mypos, len);
            break;
          case 32:
            // Embedded Signature
            this.embeddedSignature = new packet_signature();
            this.embeddedSignature.read(bytes.substr(mypos));
            break;
          default:
            throw new Error("Unknown signature subpacket type " + type + " @:" + mypos);
            break;
        }
      };

      // Produces data to produce signature on
      this.toSign = function(type, data) {
        var t = enums.signature;

        switch (type) {
          case t.binary:
          case t.text:
            return data.getBytes();

          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();

            if (this.version == 4) {
              return this.toSign(t.key, data) +
              String.fromCharCode(tag) +
              util.writeNumber(bytes.length, 4) +
              bytes;
            } else if (this.version == 3) {
              return this.toSign(t.key, data) +
              bytes;
            }
            break;

          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.');

            return data.key.writeOld();

          case t.key_revocation:
          case t.subkey_revocation:
            return this.toSign(t.key, data);
          case t.timestamp:
            return '';
          case t.third_party:
            throw new Error('Not implemented');
            break;
          default:
            throw new Error('Unknown signature type.')
        }
      }

      this.calculateTrailer = function() {
        // calculating the trailer
        var trailer = '';
        // V3 signatures don't have a trailer
        if (this.version == 3) return trailer;
        trailer += String.fromCharCode(4); // Version
        trailer += String.fromCharCode(0xFF);
        trailer += util.writeNumber(this.signatureData.length, 4);
        return trailer
      }

      /**
       * verifys the signature packet. Note: not signature types are implemented
       * @param {String|Object} data data which on the signature applies
       * @param {module:packet/public_subkey|module:packet/public_key} key the public key to verify the signature
       * @return {boolean} True if message is verified, else false.
       */
      this.verify = function(key, data) {
        var signatureType = enums.write(enums.signature, this.signatureType),
          publicKeyAlgorithm = enums.write(enums.publicKey, this.publicKeyAlgorithm),
          hashAlgorithm = enums.write(enums.hash, this.hashAlgorithm);

        var bytes = this.toSign(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 (publicKeyAlgorithm > 0 && publicKeyAlgorithm < 4)
          mpicount = 1;
        //    Algorithm-Specific Fields for DSA signatures:
        //      - MPI of DSA value r.
        //      - MPI of DSA value s.
        else if (publicKeyAlgorithm == 17)
          mpicount = 2;

        var mpi = [],
          i = 0;
        for (var j = 0; j < mpicount; j++) {
          mpi[j] = new type_mpi();
          i += mpi[j].read(this.signature.substr(i));
        }

        this.verified = crypto.signature.verify(publicKeyAlgorithm,
          hashAlgorithm, mpi, key.mpi,
          bytes + this.signatureData + trailer);

        return this.verified;
      }

      /**
       * Verifies signature expiration date
       * @return {Boolean} true if expired
       */
      this.isExpired = function() {
        if (!this.signatureNeverExpires) {
          return Date.now() > (this.created.getTime() + this.signatureExpirationTime*1000);
        }
        return false;
      }
    }