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