You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					242 lines
				
				7.7 KiB
			
		
		
			
		
	
	
					242 lines
				
				7.7 KiB
			| 
								 
											2 years ago
										 
									 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Javascript implementation of PKCS#1 PSS signature padding.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @author Stefan Siegl
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								var forge = require('./forge');
							 | 
						||
| 
								 | 
							
								require('./random');
							 | 
						||
| 
								 | 
							
								require('./util');
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// shortcut for PSS API
							 | 
						||
| 
								 | 
							
								var pss = module.exports = forge.pss = forge.pss || {};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * Creates a PSS signature scheme object.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * There are several ways to provide a salt for encoding:
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * 1. Specify the saltLength only and the built-in PRNG will generate it.
							 | 
						||
| 
								 | 
							
								 * 2. Specify the saltLength and a custom PRNG with 'getBytesSync' defined that
							 | 
						||
| 
								 | 
							
								 *   will be used.
							 | 
						||
| 
								 | 
							
								 * 3. Specify the salt itself as a forge.util.ByteBuffer.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @param options the options to use:
							 | 
						||
| 
								 | 
							
								 *          md the message digest object to use, a forge md instance.
							 | 
						||
| 
								 | 
							
								 *          mgf the mask generation function to use, a forge mgf instance.
							 | 
						||
| 
								 | 
							
								 *          [saltLength] the length of the salt in octets.
							 | 
						||
| 
								 | 
							
								 *          [prng] the pseudo-random number generator to use to produce a salt.
							 | 
						||
| 
								 | 
							
								 *          [salt] the salt to use when encoding.
							 | 
						||
| 
								 | 
							
								 *
							 | 
						||
| 
								 | 
							
								 * @return a signature scheme object.
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								pss.create = function(options) {
							 | 
						||
| 
								 | 
							
								  // backwards compatibility w/legacy args: hash, mgf, sLen
							 | 
						||
| 
								 | 
							
								  if(arguments.length === 3) {
							 | 
						||
| 
								 | 
							
								    options = {
							 | 
						||
| 
								 | 
							
								      md: arguments[0],
							 | 
						||
| 
								 | 
							
								      mgf: arguments[1],
							 | 
						||
| 
								 | 
							
								      saltLength: arguments[2]
							 | 
						||
| 
								 | 
							
								    };
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var hash = options.md;
							 | 
						||
| 
								 | 
							
								  var mgf = options.mgf;
							 | 
						||
| 
								 | 
							
								  var hLen = hash.digestLength;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var salt_ = options.salt || null;
							 | 
						||
| 
								 | 
							
								  if(typeof salt_ === 'string') {
							 | 
						||
| 
								 | 
							
								    // assume binary-encoded string
							 | 
						||
| 
								 | 
							
								    salt_ = forge.util.createBuffer(salt_);
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var sLen;
							 | 
						||
| 
								 | 
							
								  if('saltLength' in options) {
							 | 
						||
| 
								 | 
							
								    sLen = options.saltLength;
							 | 
						||
| 
								 | 
							
								  } else if(salt_ !== null) {
							 | 
						||
| 
								 | 
							
								    sLen = salt_.length();
							 | 
						||
| 
								 | 
							
								  } else {
							 | 
						||
| 
								 | 
							
								    throw new Error('Salt length not specified or specific salt not given.');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  if(salt_ !== null && salt_.length() !== sLen) {
							 | 
						||
| 
								 | 
							
								    throw new Error('Given salt length does not match length of given salt.');
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var prng = options.prng || forge.random;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  var pssobj = {};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Encodes a PSS signature.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * This function implements EMSA-PSS-ENCODE as per RFC 3447, section 9.1.1.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param md the message digest object with the hash to sign.
							 | 
						||
| 
								 | 
							
								   * @param modsBits the length of the RSA modulus in bits.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @return the encoded message as a binary-encoded string of length
							 | 
						||
| 
								 | 
							
								   *           ceil((modBits - 1) / 8).
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  pssobj.encode = function(md, modBits) {
							 | 
						||
| 
								 | 
							
								    var i;
							 | 
						||
| 
								 | 
							
								    var emBits = modBits - 1;
							 | 
						||
| 
								 | 
							
								    var emLen = Math.ceil(emBits / 8);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 2. Let mHash = Hash(M), an octet string of length hLen. */
							 | 
						||
| 
								 | 
							
								    var mHash = md.digest().getBytes();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 3. If emLen < hLen + sLen + 2, output "encoding error" and stop. */
							 | 
						||
| 
								 | 
							
								    if(emLen < hLen + sLen + 2) {
							 | 
						||
| 
								 | 
							
								      throw new Error('Message is too long to encrypt.');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 4. Generate a random octet string salt of length sLen; if sLen = 0,
							 | 
						||
| 
								 | 
							
								     *    then salt is the empty string. */
							 | 
						||
| 
								 | 
							
								    var salt;
							 | 
						||
| 
								 | 
							
								    if(salt_ === null) {
							 | 
						||
| 
								 | 
							
								      salt = prng.getBytesSync(sLen);
							 | 
						||
| 
								 | 
							
								    } else {
							 | 
						||
| 
								 | 
							
								      salt = salt_.bytes();
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 5. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt; */
							 | 
						||
| 
								 | 
							
								    var m_ = new forge.util.ByteBuffer();
							 | 
						||
| 
								 | 
							
								    m_.fillWithByte(0, 8);
							 | 
						||
| 
								 | 
							
								    m_.putBytes(mHash);
							 | 
						||
| 
								 | 
							
								    m_.putBytes(salt);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 6. Let H = Hash(M'), an octet string of length hLen. */
							 | 
						||
| 
								 | 
							
								    hash.start();
							 | 
						||
| 
								 | 
							
								    hash.update(m_.getBytes());
							 | 
						||
| 
								 | 
							
								    var h = hash.digest().getBytes();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2
							 | 
						||
| 
								 | 
							
								     *    zero octets.  The length of PS may be 0. */
							 | 
						||
| 
								 | 
							
								    var ps = new forge.util.ByteBuffer();
							 | 
						||
| 
								 | 
							
								    ps.fillWithByte(0, emLen - sLen - hLen - 2);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
							 | 
						||
| 
								 | 
							
								     *    emLen - hLen - 1. */
							 | 
						||
| 
								 | 
							
								    ps.putByte(0x01);
							 | 
						||
| 
								 | 
							
								    ps.putBytes(salt);
							 | 
						||
| 
								 | 
							
								    var db = ps.getBytes();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 9. Let dbMask = MGF(H, emLen - hLen - 1). */
							 | 
						||
| 
								 | 
							
								    var maskLen = emLen - hLen - 1;
							 | 
						||
| 
								 | 
							
								    var dbMask = mgf.generate(h, maskLen);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 10. Let maskedDB = DB \xor dbMask. */
							 | 
						||
| 
								 | 
							
								    var maskedDB = '';
							 | 
						||
| 
								 | 
							
								    for(i = 0; i < maskLen; i++) {
							 | 
						||
| 
								 | 
							
								      maskedDB += String.fromCharCode(db.charCodeAt(i) ^ dbMask.charCodeAt(i));
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 11. Set the leftmost 8emLen - emBits bits of the leftmost octet in
							 | 
						||
| 
								 | 
							
								     *     maskedDB to zero. */
							 | 
						||
| 
								 | 
							
								    var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
							 | 
						||
| 
								 | 
							
								    maskedDB = String.fromCharCode(maskedDB.charCodeAt(0) & ~mask) +
							 | 
						||
| 
								 | 
							
								      maskedDB.substr(1);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 12. Let EM = maskedDB || H || 0xbc.
							 | 
						||
| 
								 | 
							
								     * 13. Output EM. */
							 | 
						||
| 
								 | 
							
								    return maskedDB + h + String.fromCharCode(0xbc);
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  /**
							 | 
						||
| 
								 | 
							
								   * Verifies a PSS signature.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * This function implements EMSA-PSS-VERIFY as per RFC 3447, section 9.1.2.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @param mHash the message digest hash, as a binary-encoded string, to
							 | 
						||
| 
								 | 
							
								   *         compare against the signature.
							 | 
						||
| 
								 | 
							
								   * @param em the encoded message, as a binary-encoded string
							 | 
						||
| 
								 | 
							
								   *          (RSA decryption result).
							 | 
						||
| 
								 | 
							
								   * @param modsBits the length of the RSA modulus in bits.
							 | 
						||
| 
								 | 
							
								   *
							 | 
						||
| 
								 | 
							
								   * @return true if the signature was verified, false if not.
							 | 
						||
| 
								 | 
							
								   */
							 | 
						||
| 
								 | 
							
								  pssobj.verify = function(mHash, em, modBits) {
							 | 
						||
| 
								 | 
							
								    var i;
							 | 
						||
| 
								 | 
							
								    var emBits = modBits - 1;
							 | 
						||
| 
								 | 
							
								    var emLen = Math.ceil(emBits / 8);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* c. Convert the message representative m to an encoded message EM
							 | 
						||
| 
								 | 
							
								     *    of length emLen = ceil((modBits - 1) / 8) octets, where modBits
							 | 
						||
| 
								 | 
							
								     *    is the length in bits of the RSA modulus n */
							 | 
						||
| 
								 | 
							
								    em = em.substr(-emLen);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. */
							 | 
						||
| 
								 | 
							
								    if(emLen < hLen + sLen + 2) {
							 | 
						||
| 
								 | 
							
								      throw new Error('Inconsistent parameters to PSS signature verification.');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 4. If the rightmost octet of EM does not have hexadecimal value
							 | 
						||
| 
								 | 
							
								     *    0xbc, output "inconsistent" and stop. */
							 | 
						||
| 
								 | 
							
								    if(em.charCodeAt(emLen - 1) !== 0xbc) {
							 | 
						||
| 
								 | 
							
								      throw new Error('Encoded message does not end in 0xBC.');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and
							 | 
						||
| 
								 | 
							
								     *    let H be the next hLen octets. */
							 | 
						||
| 
								 | 
							
								    var maskLen = emLen - hLen - 1;
							 | 
						||
| 
								 | 
							
								    var maskedDB = em.substr(0, maskLen);
							 | 
						||
| 
								 | 
							
								    var h = em.substr(maskLen, hLen);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 6. If the leftmost 8emLen - emBits bits of the leftmost octet in
							 | 
						||
| 
								 | 
							
								     *    maskedDB are not all equal to zero, output "inconsistent" and stop. */
							 | 
						||
| 
								 | 
							
								    var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
							 | 
						||
| 
								 | 
							
								    if((maskedDB.charCodeAt(0) & mask) !== 0) {
							 | 
						||
| 
								 | 
							
								      throw new Error('Bits beyond keysize not zero as expected.');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 7. Let dbMask = MGF(H, emLen - hLen - 1). */
							 | 
						||
| 
								 | 
							
								    var dbMask = mgf.generate(h, maskLen);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 8. Let DB = maskedDB \xor dbMask. */
							 | 
						||
| 
								 | 
							
								    var db = '';
							 | 
						||
| 
								 | 
							
								    for(i = 0; i < maskLen; i++) {
							 | 
						||
| 
								 | 
							
								      db += String.fromCharCode(maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i));
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 9. Set the leftmost 8emLen - emBits bits of the leftmost octet
							 | 
						||
| 
								 | 
							
								     * in DB to zero. */
							 | 
						||
| 
								 | 
							
								    db = String.fromCharCode(db.charCodeAt(0) & ~mask) + db.substr(1);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero
							 | 
						||
| 
								 | 
							
								     * or if the octet at position emLen - hLen - sLen - 1 (the leftmost
							 | 
						||
| 
								 | 
							
								     * position is "position 1") does not have hexadecimal value 0x01,
							 | 
						||
| 
								 | 
							
								     * output "inconsistent" and stop. */
							 | 
						||
| 
								 | 
							
								    var checkLen = emLen - hLen - sLen - 2;
							 | 
						||
| 
								 | 
							
								    for(i = 0; i < checkLen; i++) {
							 | 
						||
| 
								 | 
							
								      if(db.charCodeAt(i) !== 0x00) {
							 | 
						||
| 
								 | 
							
								        throw new Error('Leftmost octets not zero as expected');
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    if(db.charCodeAt(checkLen) !== 0x01) {
							 | 
						||
| 
								 | 
							
								      throw new Error('Inconsistent PSS signature, 0x01 marker not found');
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 11. Let salt be the last sLen octets of DB. */
							 | 
						||
| 
								 | 
							
								    var salt = db.substr(-sLen);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 12.  Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt */
							 | 
						||
| 
								 | 
							
								    var m_ = new forge.util.ByteBuffer();
							 | 
						||
| 
								 | 
							
								    m_.fillWithByte(0, 8);
							 | 
						||
| 
								 | 
							
								    m_.putBytes(mHash);
							 | 
						||
| 
								 | 
							
								    m_.putBytes(salt);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 13. Let H' = Hash(M'), an octet string of length hLen. */
							 | 
						||
| 
								 | 
							
								    hash.start();
							 | 
						||
| 
								 | 
							
								    hash.update(m_.getBytes());
							 | 
						||
| 
								 | 
							
								    var h_ = hash.digest().getBytes();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    /* 14. If H = H', output "consistent." Otherwise, output "inconsistent." */
							 | 
						||
| 
								 | 
							
								    return h === h_;
							 | 
						||
| 
								 | 
							
								  };
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  return pssobj;
							 | 
						||
| 
								 | 
							
								};
							 |