text: /**
   * PSS signature scheme
   */
  
  var BigInteger = require('../libs/jsbn');
  var crypt = require('crypto');
  
  module.exports = {
      isEncryption: false,
      isSignature: true
  };
  
  var DEFAULT_HASH_FUNCTION = 'sha1';
  var DEFAULT_SALT_LENGTH = 20;
  
  module.exports.makeScheme = function (key, options) {
      var OAEP = require('./schemes').pkcs1_oaep;
  
      /**
       * @param key
       * @param options
       * options    [Object]    An object that contains the following keys that specify certain options for encoding.
       *  └>signingSchemeOptions
       *     ├>hash    [String]    Hash function to use when encoding and generating masks. Must be a string accepted by node's crypto.createHash function. (default = "sha1")
       *     ├>mgf    [function]    The mask generation function to use when encoding. (default = mgf1SHA1)
       *     └>sLen    [uint]        The length of the salt to generate. (default = 20)
       * @constructor
       */
      function Scheme(key, options) {
          this.key = key;
          this.options = options;
      }
  
      Scheme.prototype.sign = function (buffer) {
          var mHash = crypt.createHash(this.options.signingSchemeOptions.hash || DEFAULT_HASH_FUNCTION);
          mHash.update(buffer);
  
          var encoded = this.emsa_pss_encode(mHash.digest(), this.key.keySize - 1);
          return this.key.$doPrivate(new BigInteger(encoded)).toBuffer(this.key.encryptedDataLength);
      };
  
      Scheme.prototype.verify = function (buffer, signature, signature_encoding) {
          if (signature_encoding) {
              signature = new Buffer(signature, signature_encoding);
          }
          signature = new BigInteger(signature);
  
          var emLen = Math.ceil((this.key.keySize - 1) / 8);
          var m = this.key.$doPublic(signature).toBuffer(emLen);
  
          var mHash = crypt.createHash(this.options.signingSchemeOptions.hash || DEFAULT_HASH_FUNCTION);
          mHash.update(buffer);
  
          return this.emsa_pss_verify(mHash.digest(), m, this.key.keySize - 1);
      };
  
      /*
       * https://tools.ietf.org/html/rfc3447#section-9.1.1
       *
       * mHash	[Buffer]	Hashed message to encode
       * emBits	[uint]		Maximum length of output in bits. Must be at least 8hLen + 8sLen + 9 (hLen = Hash digest length in bytes | sLen = length of salt in bytes)
       * @returns {Buffer} The encoded message
       */
      Scheme.prototype.emsa_pss_encode = function (mHash, emBits) {
          var hash = this.options.signingSchemeOptions.hash || DEFAULT_HASH_FUNCTION;
          var mgf = this.options.signingSchemeOptions.mgf || OAEP.eme_oaep_mgf1;
          var sLen = this.options.signingSchemeOptions.saltLength || DEFAULT_SALT_LENGTH;
  
          var hLen = OAEP.digestLength[hash];
          var emLen = Math.ceil(emBits / 8);
  
          if (emLen < hLen + sLen + 2) {
              throw new Error("Output length passed to emBits(" + emBits + ") is too small for the options " +
                  "specified(" + hash + ", " + sLen + "). To fix this issue increase the value of emBits. (minimum size: " +
                  (8 * hLen + 8 * sLen + 9) + ")"
              );
          }
  
          var salt = crypt.randomBytes(sLen);
  
          var Mapostrophe = new Buffer(8 + hLen + sLen);
          Mapostrophe.fill(0, 0, 8);
          mHash.copy(Mapostrophe, 8);
          salt.copy(Mapostrophe, 8 + mHash.length);
  
          var H = crypt.createHash(hash);
          H.update(Mapostrophe);
          H = H.digest();
  
          var PS = new Buffer(emLen - salt.length - hLen - 2);
          PS.fill(0);
  
          var DB = new Buffer(PS.length + 1 + salt.length);
          PS.copy(DB);
          DB[PS.length] = 0x01;
          salt.copy(DB, PS.length + 1);
  
          var dbMask = mgf(H, DB.length, hash);
  
          // XOR DB and dbMask together
          var maskedDB = new Buffer(DB.length);
          for (var i = 0; i < dbMask.length; i++) {
              maskedDB[i] = DB[i] ^ dbMask[i];
          }
  
          var bits = 8 * emLen - emBits;
          var mask = 255 ^ (255 >> 8 - bits << 8 - bits);
          maskedDB[0] = maskedDB[0] & mask;
  
          var EM = new Buffer(maskedDB.length + H.length + 1);
          maskedDB.copy(EM, 0);
          H.copy(EM, maskedDB.length);
          EM[EM.length - 1] = 0xbc;
  
          return EM;
      };
  
      /*
       * https://tools.ietf.org/html/rfc3447#section-9.1.2
       *
       * mHash	[Buffer]	Hashed message
       * EM		[Buffer]	Signature
       * emBits	[uint]		Length of EM in bits. Must be at least 8hLen + 8sLen + 9 to be a valid signature. (hLen = Hash digest length in bytes | sLen = length of salt in bytes)
       * @returns {Boolean} True if signature(EM) matches message(M)
       */
      Scheme.prototype.emsa_pss_verify = function (mHash, EM, emBits) {
          var hash = this.options.signingSchemeOptions.hash || DEFAULT_HASH_FUNCTION;
          var mgf = this.options.signingSchemeOptions.mgf || OAEP.eme_oaep_mgf1;
          var sLen = this.options.signingSchemeOptions.saltLength || DEFAULT_SALT_LENGTH;
  
          var hLen = OAEP.digestLength[hash];
          var emLen = Math.ceil(emBits / 8);
  
          if (emLen < hLen + sLen + 2 || EM[EM.length - 1] != 0xbc) {
              return false;
          }
  
          var DB = new Buffer(emLen - hLen - 1);
          EM.copy(DB, 0, 0, emLen - hLen - 1);
  
          var mask = 0;
          for (var i = 0, bits = 8 * emLen - emBits; i < bits; i++) {
              mask |= 1 << (7 - i);
          }
  
          if ((DB[0] & mask) !== 0) {
              return false;
          }
  
          var H = EM.slice(emLen - hLen - 1, emLen - 1);
          var dbMask = mgf(H, DB.length, hash);
  
          // Unmask DB
          for (i = 0; i < DB.length; i++) {
              DB[i] ^= dbMask[i];
          }
  
          bits = 8 * emLen - emBits;
          mask = 255 ^ (255 >> 8 - bits << 8 - bits);
          DB[0] = DB[0] & mask;
  
          // Filter out padding
          for (i = 0; DB[i] === 0 && i < DB.length; i++);
          if (DB[i] != 1) {
              return false;
          }
  
          var salt = DB.slice(DB.length - sLen);
  
          var Mapostrophe = new Buffer(8 + hLen + sLen);
          Mapostrophe.fill(0, 0, 8);
          mHash.copy(Mapostrophe, 8);
          salt.copy(Mapostrophe, 8 + mHash.length);
  
          var Hapostrophe = crypt.createHash(hash);
          Hapostrophe.update(Mapostrophe);
          Hapostrophe = Hapostrophe.digest();
  
          return H.toString("hex") === Hapostrophe.toString("hex");
      };
  
      return new Scheme(key, options);
  };
  