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.

241 lines
7.7 KiB

4 years ago
  1. /**
  2. * Javascript implementation of PKCS#1 PSS signature padding.
  3. *
  4. * @author Stefan Siegl
  5. *
  6. * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
  7. */
  8. var forge = require('./forge');
  9. require('./random');
  10. require('./util');
  11. // shortcut for PSS API
  12. var pss = module.exports = forge.pss = forge.pss || {};
  13. /**
  14. * Creates a PSS signature scheme object.
  15. *
  16. * There are several ways to provide a salt for encoding:
  17. *
  18. * 1. Specify the saltLength only and the built-in PRNG will generate it.
  19. * 2. Specify the saltLength and a custom PRNG with 'getBytesSync' defined that
  20. * will be used.
  21. * 3. Specify the salt itself as a forge.util.ByteBuffer.
  22. *
  23. * @param options the options to use:
  24. * md the message digest object to use, a forge md instance.
  25. * mgf the mask generation function to use, a forge mgf instance.
  26. * [saltLength] the length of the salt in octets.
  27. * [prng] the pseudo-random number generator to use to produce a salt.
  28. * [salt] the salt to use when encoding.
  29. *
  30. * @return a signature scheme object.
  31. */
  32. pss.create = function(options) {
  33. // backwards compatibility w/legacy args: hash, mgf, sLen
  34. if(arguments.length === 3) {
  35. options = {
  36. md: arguments[0],
  37. mgf: arguments[1],
  38. saltLength: arguments[2]
  39. };
  40. }
  41. var hash = options.md;
  42. var mgf = options.mgf;
  43. var hLen = hash.digestLength;
  44. var salt_ = options.salt || null;
  45. if(typeof salt_ === 'string') {
  46. // assume binary-encoded string
  47. salt_ = forge.util.createBuffer(salt_);
  48. }
  49. var sLen;
  50. if('saltLength' in options) {
  51. sLen = options.saltLength;
  52. } else if(salt_ !== null) {
  53. sLen = salt_.length();
  54. } else {
  55. throw new Error('Salt length not specified or specific salt not given.');
  56. }
  57. if(salt_ !== null && salt_.length() !== sLen) {
  58. throw new Error('Given salt length does not match length of given salt.');
  59. }
  60. var prng = options.prng || forge.random;
  61. var pssobj = {};
  62. /**
  63. * Encodes a PSS signature.
  64. *
  65. * This function implements EMSA-PSS-ENCODE as per RFC 3447, section 9.1.1.
  66. *
  67. * @param md the message digest object with the hash to sign.
  68. * @param modsBits the length of the RSA modulus in bits.
  69. *
  70. * @return the encoded message as a binary-encoded string of length
  71. * ceil((modBits - 1) / 8).
  72. */
  73. pssobj.encode = function(md, modBits) {
  74. var i;
  75. var emBits = modBits - 1;
  76. var emLen = Math.ceil(emBits / 8);
  77. /* 2. Let mHash = Hash(M), an octet string of length hLen. */
  78. var mHash = md.digest().getBytes();
  79. /* 3. If emLen < hLen + sLen + 2, output "encoding error" and stop. */
  80. if(emLen < hLen + sLen + 2) {
  81. throw new Error('Message is too long to encrypt.');
  82. }
  83. /* 4. Generate a random octet string salt of length sLen; if sLen = 0,
  84. * then salt is the empty string. */
  85. var salt;
  86. if(salt_ === null) {
  87. salt = prng.getBytesSync(sLen);
  88. } else {
  89. salt = salt_.bytes();
  90. }
  91. /* 5. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt; */
  92. var m_ = new forge.util.ByteBuffer();
  93. m_.fillWithByte(0, 8);
  94. m_.putBytes(mHash);
  95. m_.putBytes(salt);
  96. /* 6. Let H = Hash(M'), an octet string of length hLen. */
  97. hash.start();
  98. hash.update(m_.getBytes());
  99. var h = hash.digest().getBytes();
  100. /* 7. Generate an octet string PS consisting of emLen - sLen - hLen - 2
  101. * zero octets. The length of PS may be 0. */
  102. var ps = new forge.util.ByteBuffer();
  103. ps.fillWithByte(0, emLen - sLen - hLen - 2);
  104. /* 8. Let DB = PS || 0x01 || salt; DB is an octet string of length
  105. * emLen - hLen - 1. */
  106. ps.putByte(0x01);
  107. ps.putBytes(salt);
  108. var db = ps.getBytes();
  109. /* 9. Let dbMask = MGF(H, emLen - hLen - 1). */
  110. var maskLen = emLen - hLen - 1;
  111. var dbMask = mgf.generate(h, maskLen);
  112. /* 10. Let maskedDB = DB \xor dbMask. */
  113. var maskedDB = '';
  114. for(i = 0; i < maskLen; i++) {
  115. maskedDB += String.fromCharCode(db.charCodeAt(i) ^ dbMask.charCodeAt(i));
  116. }
  117. /* 11. Set the leftmost 8emLen - emBits bits of the leftmost octet in
  118. * maskedDB to zero. */
  119. var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
  120. maskedDB = String.fromCharCode(maskedDB.charCodeAt(0) & ~mask) +
  121. maskedDB.substr(1);
  122. /* 12. Let EM = maskedDB || H || 0xbc.
  123. * 13. Output EM. */
  124. return maskedDB + h + String.fromCharCode(0xbc);
  125. };
  126. /**
  127. * Verifies a PSS signature.
  128. *
  129. * This function implements EMSA-PSS-VERIFY as per RFC 3447, section 9.1.2.
  130. *
  131. * @param mHash the message digest hash, as a binary-encoded string, to
  132. * compare against the signature.
  133. * @param em the encoded message, as a binary-encoded string
  134. * (RSA decryption result).
  135. * @param modsBits the length of the RSA modulus in bits.
  136. *
  137. * @return true if the signature was verified, false if not.
  138. */
  139. pssobj.verify = function(mHash, em, modBits) {
  140. var i;
  141. var emBits = modBits - 1;
  142. var emLen = Math.ceil(emBits / 8);
  143. /* c. Convert the message representative m to an encoded message EM
  144. * of length emLen = ceil((modBits - 1) / 8) octets, where modBits
  145. * is the length in bits of the RSA modulus n */
  146. em = em.substr(-emLen);
  147. /* 3. If emLen < hLen + sLen + 2, output "inconsistent" and stop. */
  148. if(emLen < hLen + sLen + 2) {
  149. throw new Error('Inconsistent parameters to PSS signature verification.');
  150. }
  151. /* 4. If the rightmost octet of EM does not have hexadecimal value
  152. * 0xbc, output "inconsistent" and stop. */
  153. if(em.charCodeAt(emLen - 1) !== 0xbc) {
  154. throw new Error('Encoded message does not end in 0xBC.');
  155. }
  156. /* 5. Let maskedDB be the leftmost emLen - hLen - 1 octets of EM, and
  157. * let H be the next hLen octets. */
  158. var maskLen = emLen - hLen - 1;
  159. var maskedDB = em.substr(0, maskLen);
  160. var h = em.substr(maskLen, hLen);
  161. /* 6. If the leftmost 8emLen - emBits bits of the leftmost octet in
  162. * maskedDB are not all equal to zero, output "inconsistent" and stop. */
  163. var mask = (0xFF00 >> (8 * emLen - emBits)) & 0xFF;
  164. if((maskedDB.charCodeAt(0) & mask) !== 0) {
  165. throw new Error('Bits beyond keysize not zero as expected.');
  166. }
  167. /* 7. Let dbMask = MGF(H, emLen - hLen - 1). */
  168. var dbMask = mgf.generate(h, maskLen);
  169. /* 8. Let DB = maskedDB \xor dbMask. */
  170. var db = '';
  171. for(i = 0; i < maskLen; i++) {
  172. db += String.fromCharCode(maskedDB.charCodeAt(i) ^ dbMask.charCodeAt(i));
  173. }
  174. /* 9. Set the leftmost 8emLen - emBits bits of the leftmost octet
  175. * in DB to zero. */
  176. db = String.fromCharCode(db.charCodeAt(0) & ~mask) + db.substr(1);
  177. /* 10. If the emLen - hLen - sLen - 2 leftmost octets of DB are not zero
  178. * or if the octet at position emLen - hLen - sLen - 1 (the leftmost
  179. * position is "position 1") does not have hexadecimal value 0x01,
  180. * output "inconsistent" and stop. */
  181. var checkLen = emLen - hLen - sLen - 2;
  182. for(i = 0; i < checkLen; i++) {
  183. if(db.charCodeAt(i) !== 0x00) {
  184. throw new Error('Leftmost octets not zero as expected');
  185. }
  186. }
  187. if(db.charCodeAt(checkLen) !== 0x01) {
  188. throw new Error('Inconsistent PSS signature, 0x01 marker not found');
  189. }
  190. /* 11. Let salt be the last sLen octets of DB. */
  191. var salt = db.substr(-sLen);
  192. /* 12. Let M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt */
  193. var m_ = new forge.util.ByteBuffer();
  194. m_.fillWithByte(0, 8);
  195. m_.putBytes(mHash);
  196. m_.putBytes(salt);
  197. /* 13. Let H' = Hash(M'), an octet string of length hLen. */
  198. hash.start();
  199. hash.update(m_.getBytes());
  200. var h_ = hash.digest().getBytes();
  201. /* 14. If H = H', output "consistent." Otherwise, output "inconsistent." */
  202. return h === h_;
  203. };
  204. return pssobj;
  205. };