(function() {

function Tests(ASSERT, PKI, RSA, MD, MGF, PSS, RANDOM, UTIL) {
  var _pem = {
    privateKey: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
      'MIICXQIBAAKBgQDL0EugUiNGMWscLAVM0VoMdhDZEJOqdsUMpx9U0YZI7szokJqQ\r\n' +
      'NIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMGTkP3VF29vXBo+dLq5e+8VyAy\r\n' +
      'Q3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGtvnM+z0MYDdKo80efzwIDAQAB\r\n' +
      'AoGAIzkGONi5G+JifmXlLJdplom486p3upf4Ce2/7mqfaG9MnkyPSairKD/JXvfh\r\n' +
      'NNWkkN8DKKDKBcVVElPgORYT0qwrWc7ueLBMUCbRXb1ZyfEulimG0R3kjUh7NYau\r\n' +
      'DaIkVgfykXGSQMZx8FoaT6L080zd+0emKDDYRrb+/kgJNJECQQDoUZoiC2K/DWNY\r\n' +
      'h3/ppZ0ane2y4SBmJUHJVMPQ2CEgxsrJTxet668ckNCKaOP/3VFPoWC41f17DvKq\r\n' +
      'noYINNntAkEA4JbZBZBVUrQFhHlrpXT4jzqtO2RlKZzEq8qmFZfEErxOT1WMyyCi\r\n' +
      'lAQ5gUKardo1Kf0omC8Xq/uO9ZYdED55KwJBALs6cJ65UFaq4oLJiQPzLd7yokuE\r\n' +
      'dcj8g71PLBTW6jPxIiMFNA89nz3FU9wIVp+xbMNhSoMMKqIPVPC+m0Rn260CQQDA\r\n' +
      'I83fWK/mZWUjBM33a68KumRiH238v8XyQxj7+C8i6D8G2GXvkigFAehAkb7LZZd+\r\n' +
      'KLuGFyPlWv3fVWHf99KpAkBQFKk3MRMl6IGJZUEFQe4l5whm8LkGU4acSqv9B3xt\r\n' +
      'qROkCrsFrMPqjuuzEmyHoQZ64r2PLJg7FOuyhBnQUOt4\r\n' +
      '-----END RSA PRIVATE KEY-----\r\n',
    privateKeyInfo: '-----BEGIN PRIVATE KEY-----\r\n' +
      'MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAMvQS6BSI0Yxaxws\r\n' +
      'BUzRWgx2ENkQk6p2xQynH1TRhkjuzOiQmpA0jCiSJDoSic2dZIyUi/LjoCGeVFif\r\n' +
      '57N5N5Tt4wZOQ/dUXb29cGj50url77xXIDJDcXMzXAji2ziFEAIXzDqarKBdDuL9\r\n' +
      'IO7z+tepEa2+cz7PQxgN0qjzR5/PAgMBAAECgYAjOQY42Lkb4mJ+ZeUsl2mWibjz\r\n' +
      'qne6l/gJ7b/uap9ob0yeTI9JqKsoP8le9+E01aSQ3wMooMoFxVUSU+A5FhPSrCtZ\r\n' +
      'zu54sExQJtFdvVnJ8S6WKYbRHeSNSHs1hq4NoiRWB/KRcZJAxnHwWhpPovTzTN37\r\n' +
      'R6YoMNhGtv7+SAk0kQJBAOhRmiILYr8NY1iHf+mlnRqd7bLhIGYlQclUw9DYISDG\r\n' +
      'yslPF63rrxyQ0Ipo4//dUU+hYLjV/XsO8qqehgg02e0CQQDgltkFkFVStAWEeWul\r\n' +
      'dPiPOq07ZGUpnMSryqYVl8QSvE5PVYzLIKKUBDmBQpqt2jUp/SiYLxer+471lh0Q\r\n' +
      'PnkrAkEAuzpwnrlQVqrigsmJA/Mt3vKiS4R1yPyDvU8sFNbqM/EiIwU0Dz2fPcVT\r\n' +
      '3AhWn7Fsw2FKgwwqog9U8L6bRGfbrQJBAMAjzd9Yr+ZlZSMEzfdrrwq6ZGIfbfy/\r\n' +
      'xfJDGPv4LyLoPwbYZe+SKAUB6ECRvstll34ou4YXI+Va/d9VYd/30qkCQFAUqTcx\r\n' +
      'EyXogYllQQVB7iXnCGbwuQZThpxKq/0HfG2pE6QKuwWsw+qO67MSbIehBnrivY8s\r\n' +
      'mDsU67KEGdBQ63g=\r\n' +
      '-----END PRIVATE KEY-----\r\n',
    publicKey: '-----BEGIN PUBLIC KEY-----\r\n' +
      'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDL0EugUiNGMWscLAVM0VoMdhDZ\r\n' +
      'EJOqdsUMpx9U0YZI7szokJqQNIwokiQ6EonNnWSMlIvy46AhnlRYn+ezeTeU7eMG\r\n' +
      'TkP3VF29vXBo+dLq5e+8VyAyQ3FzM1wI4ts4hRACF8w6mqygXQ7i/SDu8/rXqRGt\r\n' +
      'vnM+z0MYDdKo80efzwIDAQAB\r\n' +
      '-----END PUBLIC KEY-----\r\n'
  };
  var _signature =
    '9200ece65cdaed36bcc20b94c65af852e4f88f0b4fe5b249d54665f815992ac4' +
    '3a1399e65d938c6a7f16dd39d971a53ca66523209dbbfbcb67afa579dbb0c220' +
    '672813d9e6f4818f29b9becbb29da2032c5e422da97e0c39bfb7a2e7d568615a' +
    '5073af0337ff215a8e1b2332d668691f4fb731440055420c24ac451dd3c913f4';

  describe('rsa', function() {
    it('should generate 512 bit key pair', function() {
      var pair = RSA.generateKeyPair(512);
      ASSERT.equal(PKI.privateKeyToPem(pair.privateKey).indexOf('-----BEGIN RSA PRIVATE KEY-----'), 0);
      ASSERT.equal(PKI.publicKeyToPem(pair.publicKey).indexOf('-----BEGIN PUBLIC KEY-----'), 0);

      // sign and verify
      var md = MD.sha1.create();
      md.update('0123456789abcdef');
      var signature = pair.privateKey.sign(md);
      ASSERT.ok(pair.publicKey.verify(md.digest().getBytes(), signature));
    });

    it('should generate the same 512 bit key pair', function() {
      var prng = RANDOM.createInstance();
      prng.seedFileSync = function(needed) {
        return UTIL.fillString('a', needed);
      };
      var pair = RSA.generateKeyPair(512, {prng: prng});
      var pem = {
        privateKey: PKI.privateKeyToPem(pair.privateKey),
        publicKey: PKI.publicKeyToPem(pair.publicKey)
      };
      ASSERT.equal(pem.privateKey.indexOf('-----BEGIN RSA PRIVATE KEY-----'), 0);
      ASSERT.equal(pem.publicKey.indexOf('-----BEGIN PUBLIC KEY-----'), 0);

      // sign and verify
      var md = MD.sha1.create();
      md.update('0123456789abcdef');
      var signature = pair.privateKey.sign(md);
      ASSERT.ok(pair.publicKey.verify(md.digest().getBytes(), signature));

      // create same key pair by using same PRNG
      prng = RANDOM.createInstance();
      prng.seedFileSync = function(needed) {
        return UTIL.fillString('a', needed);
      };
      var pair2 = RSA.generateKeyPair(512, {prng: prng});
      var pem2 = {
        privateKey: PKI.privateKeyToPem(pair2.privateKey),
        publicKey: PKI.publicKeyToPem(pair2.publicKey)
      };
      ASSERT.equal(pem.privateKey, pem2.privateKey);
      ASSERT.equal(pem.publicKey, pem2.publicKey);
    });

    it('should convert private key to/from PEM', function() {
      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
      ASSERT.equal(PKI.privateKeyToPem(privateKey), _pem.privateKey);
    });

    it('should convert public key to/from PEM', function() {
      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
      ASSERT.equal(PKI.publicKeyToPem(publicKey), _pem.publicKey);
    });

    it('should convert a PKCS#8 PrivateKeyInfo to/from PEM', function() {
      var privateKey = PKI.privateKeyFromPem(_pem.privateKeyInfo);
      var rsaPrivateKey = PKI.privateKeyToAsn1(privateKey);
      var pki = PKI.wrapRsaPrivateKey(rsaPrivateKey);
      ASSERT.equal(PKI.privateKeyInfoToPem(pki), _pem.privateKeyInfo);
    });

    (function() {
      var algorithms = ['aes128', 'aes192', 'aes256', '3des', 'des'];
      for(var i = 0; i < algorithms.length; ++i) {
        var algorithm = algorithms[i];
        it('should PKCS#8 encrypt and decrypt private key with ' + algorithm, function() {
          var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
          var encryptedPem = PKI.encryptRsaPrivateKey(
             privateKey, 'password', {algorithm: algorithm});
          privateKey = PKI.decryptRsaPrivateKey(encryptedPem, 'password');
          ASSERT.equal(PKI.privateKeyToPem(privateKey), _pem.privateKey);
        });
      }
    })();

    (function() {
      var algorithms = ['aes128', 'aes192', 'aes256', '3des', 'des'];
      for(var i = 0; i < algorithms.length; ++i) {
        var algorithm = algorithms[i];
        it('should legacy (OpenSSL style) encrypt and decrypt private key with ' + algorithm, function() {
          var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
          var encryptedPem = PKI.encryptRsaPrivateKey(
             privateKey, 'password', {algorithm: algorithm, legacy: true});
          privateKey = PKI.decryptRsaPrivateKey(encryptedPem, 'password');
          ASSERT.equal(PKI.privateKeyToPem(privateKey), _pem.privateKey);
        });
      }
    })();

    it('should verify signature', function() {
      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
      var md = MD.sha1.create();
      md.update('0123456789abcdef');
      var signature = UTIL.hexToBytes(_signature);
      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
    });

    it('should sign and verify', function() {
      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);
      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
      var md = MD.sha1.create();
      md.update('0123456789abcdef');
      var signature = privateKey.sign(md);
      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
    });

    it('should generate missing CRT parameters, sign, and verify', function() {
      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);

      // remove dQ, dP, and qInv
      privateKey = RSA.setPrivateKey(
        privateKey.n, privateKey.e, privateKey.d,
        privateKey.p, privateKey.q);

      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
      var md = MD.sha1.create();
      md.update('0123456789abcdef');
      var signature = privateKey.sign(md);
      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
    });

    it('should sign and verify with a private key containing only e, n, and d parameters', function() {
      var privateKey = PKI.privateKeyFromPem(_pem.privateKey);

      // remove all CRT parameters from private key, so that it consists
      // only of e, n and d (which make a perfectly valid private key, but its
      // operations are slower)
      privateKey = RSA.setPrivateKey(
        privateKey.n, privateKey.e, privateKey.d);

      var publicKey = PKI.publicKeyFromPem(_pem.publicKey);
      var md = MD.sha1.create();
      md.update('0123456789abcdef');
      var signature = privateKey.sign(md);
      ASSERT.ok(publicKey.verify(md.digest().getBytes(), signature));
    });

    (function() {
      var tests = [{
        keySize: 1024,
        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
          'MIICWwIBAAKBgQDCjvkkLWNTeYXqEsqGiVCW/pDt3/qAodNMHcU9gOU2rxeWwiRu\r\n' +
          'OhhLqmMxXHLi0oP5Xmg0m7zdOiLMEyzzyRzdp21aqp3k5qtuSDkZcf1prsp1jpYm\r\n' +
          '6z9EGpaSHb64BCuUsQGmUPKutd5RERKHGZXtiRuvvIyue7ETq6VjXrOUHQIDAQAB\r\n' +
          'AoGAOKeBjTNaVRhyEnNeXkbmHNIMSfiK7aIx8VxJ71r1ZDMgX1oxWZe5M29uaxVM\r\n' +
          'rxg2Lgt7tLYVDSa8s0hyMptBuBdy3TJUWruDx85uwCrWnMerCt/iKVBS22fv5vm0\r\n' +
          'LEq/4gjgIVTZwgqbVxGsBlKcY2VzxAfYqYzU8EOZBeNhZdECQQDy+PJAPcUN2xOs\r\n' +
          '6qy66S91x6y3vMjs900OeX4+bgT4VSVKmLpqRTPizzcL07tT4+Y+pAAOX6VstZvZ\r\n' +
          '6iFDL5rPAkEAzP1+gaRczboKoJWKJt0uEMUmztcY9NXJFDmjVLqzKwKjcAoGgIal\r\n' +
          'h+uBFT9VJ16QajC7KxTRLlarzmMvspItUwJAeUMNhEpPwm6ID1DADDi82wdgiALM\r\n' +
          'NJfn+UVhYD8Ac//qsKQwxUDseFH6owh1AZVIIBMxg/rwUKUCt2tGVoW3uQJAIt6M\r\n' +
          'Aml/D8+xtxc45NuC1n9y1oRoTl1/Ut1rFyKbD5nnS0upR3uf9LruvjqDtaq0Thvz\r\n' +
          '+qQT4RoFJ5pfprSO2QJAdMkfNWRqECfAhZyQuUrapeWU3eQ0wjvktIynCIwiBDd2\r\n' +
          'MfjmVXzBJhMk6dtINt+vBEITVQEOdtyTgDt0y3n2Lw==\r\n' +
          '-----END RSA PRIVATE KEY-----\r\n',
        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
          'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDCjvkkLWNTeYXqEsqGiVCW/pDt\r\n' +
          '3/qAodNMHcU9gOU2rxeWwiRuOhhLqmMxXHLi0oP5Xmg0m7zdOiLMEyzzyRzdp21a\r\n' +
          'qp3k5qtuSDkZcf1prsp1jpYm6z9EGpaSHb64BCuUsQGmUPKutd5RERKHGZXtiRuv\r\n' +
          'vIyue7ETq6VjXrOUHQIDAQAB\r\n' +
          '-----END PUBLIC KEY-----\r\n',
        encrypted: 'jsej3OoacmJ1VjWrlw68F+drnQORAuKAqVu6RMbz1xSXjzA355vctrJZXolRU0mvzuu/6VuNynkKGGyRJ6DHt85CvwTMChw4tOMV4Dy6bgnUt3j+DZA2sWTwFhOlpzvNQMK70QpuqrXtOZmAO59EwoDeJkW/iH6t4YzNOVYo9Jg=',
        signature: 'GT0/3EV2zrXxPd1ydijJq3R7lkI4c0GtcprgpG04dSECv/xyXtikuzivxv7XzUdHpu6QiYmM0xE4D4i7LK3Mzy+f7aB4o/dg8XXO3htLiBzVI+ZJCRh06RdYctPtclAWmyZikZ8Etw3NnA/ldKuG4jApbwRb21UFm5gYLrJ4SP4=',
        signaturePss: 'F4xffaANDBjhFxeSJx8ANuBbdhaWZjUHRQh4ueYQMPPCaR2mpwdqxE04sbgNgIiZzBuLIAI4HpTMMoDk3Rruhjefx3+9UhzTxgB0hRI+KzRChRs+ToltWWDZdYzt9T8hfTlELeqT4V8HgjDuteO/IAvIVlRIBwMNv53Iebu1FY4=',
        signatureWithAbcSalt: 'GYA/Zp8G+jqG2Fu7Um+XP7Cr/yaVdzJN8lyt57Lw6gFflia2CPbOVMLyqLzD7fKoE8UD0Rc6DF8k04xhEu60sudw2nxGHeDvpL4M9du0uYra/WSr9kv7xNjAW62NyNerDngHD2J7O8gQ07TZiTXkrfS724vQab5xZL/+FhvisMY=',
        signatureWithCustomPrng: 'LzWcUpUYK+URDp72hJbz1GVEp0rG0LHjd+Pdh2w5rfQFbUThbmXDl3X6DUT5UZr5RjUSHtc2usvH+w49XskyIJJO929sUk9EkMJMK/6QAnYYEp5BA+48pdGNNMZyjIbhyl9Y4lInzFPX8XYMM8o+tdSK+hj+dW5OPdnwWbDtR7U='
      }, {
        keySize: 1025,
        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
          'MIICXgIBAAKBgQGIkej4PDlAigUh5fbbHp1WXuTHhOdQfAke+LoH0TM4uzn0QmgK\r\n' +
          'SJqxzB1COJ5o0DwZw/NR+CNy7NUrly+vmh2YPwsaqN+AsYBF9qsF93oN8/TBtaL/\r\n' +
          'GRoRGpDcCglkj1kZnDaWR79NsG8mC0TrvQCkcCLOP0c2Ux1hRbntOetGXwIDAQAB\r\n' +
          'AoGBAIaJWsoX+ZcAthmT8jHOICXFh6pJBe0zVPzkSPz82Q0MPSRUzcsYbsuYJD7Z\r\n' +
          'oJBTLQW3feANpjhwqe2ydok7y//ONm3Th53Bcu8jLfoatg4KYxNFIwXEO10mPOld\r\n' +
          'VuDIGrBkTABe6q2P5PeUKGCKLT6i/u/2OTXTrQiJbQ0gU8thAkEBjqcFivWMXo34\r\n' +
          'Cb9/EgfWCCtv9edRMexgvcFMysRsbHJHDK9JjRLobZltwtAv3cY7F3a/Cu1afg+g\r\n' +
          'jAzm5E3gowJBAPwYFHTLzaZToxFKNQztWrPsXF6YfqHpPUUIpT4UzL6DhGG0M00U\r\n' +
          'qMyhkYRRqmGOSrSovjg2hjM2643MUUWxUxUCQDPkk/khu5L3YglKzyy2rmrD1MAq\r\n' +
          'y0v3XCR3TBq89Ows+AizrJxbkLvrk/kfBowU6M5GG9o9SWFNgXWZnFittocCQQDT\r\n' +
          'e1P1419DUFi1UX6NuLTlybx3sxBQvf0jY6xUF1jn3ib5XBXJbTJqcIRF78iyjI9J\r\n' +
          'XWIugDc20bTsQOJRSAA9AkEBU8kpueHBaiXTikqqlK9wvc2Lp476hgyKVmVyBGye\r\n' +
          '9TLTWkTCzDPtManLy47YtXkXnmyazS+DlKFU61XAGEnZfg==\r\n' +
          '-----END RSA PRIVATE KEY-----\r\n',
        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
          'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQGIkej4PDlAigUh5fbbHp1WXuTH\r\n' +
          'hOdQfAke+LoH0TM4uzn0QmgKSJqxzB1COJ5o0DwZw/NR+CNy7NUrly+vmh2YPwsa\r\n' +
          'qN+AsYBF9qsF93oN8/TBtaL/GRoRGpDcCglkj1kZnDaWR79NsG8mC0TrvQCkcCLO\r\n' +
          'P0c2Ux1hRbntOetGXwIDAQAB\r\n' +
          '-----END PUBLIC KEY-----\r\n',
        encrypted: 'AOVeCUN8BOVkZvt4mxyNn/yCYE1MZ40A3e/osh6EvCBcJ09hyYbx7bzKSrdkhRnDyW0pGtgP352CollasllQZ9HlfI2Wy9zKM0aYZZn8OHBA+60Tc3xHHDGznLZqggUKuhoNpj+faVZ1uzb285eTpQQa+4mLUue2svJD4ViM8+ng',
        signature: 'AFSx0axDYXlF2rO3ofgUhYSI8ZlIWtJUUZ62PhgdBp9O5zFqMX3DXoiov1e7NenSOz1khvTSMctFWzKP3GU3F0yewe+Yd3UAZE0dM8vAxigSSfAchUkBDmp9OFuszUie63zwWwpG+gXtvyfueZs1RniBvW1ZmXJvS+HFgX4ouzwd',
        signaturePss: 'AQvBdhAXDpu+7RpcybMgwuTUk6w+qa08Lcq3G1xHY4kC7ZUzauZd/Jn9e0ePKApDqs7eDNAOV+dQkU2wiH/uBg6VGelzb0hFwcpSLyBW92Vw0q3GlzY7myWn8qnNzasrt110zFflWQa1GiuzH/C8f+Z82/MzlWDxloJIYbq2PRC8',
        signatureWithAbcSalt: 'AW4bKnG/0TGvAZgqX5Dk+fXpUNgX7INFelE46d3m+spaMTG5XalY0xP1sxWfaE/+Zl3FmZcfTNtfOCo0eNRO1h1+GZZfp32ZQZmZvkdUG+dUQp318LNzgygrVf/5iIX+QKV5/soSDuAHBzS7yDfMgzJfnXNpFE/zPLOgZIoOIuLq',
        signatureWithCustomPrng: 'AVxfCyGC/7Y3kz//eYFEuWQijjR7eR05AM36CwDlLsVkDRtXoeVzz2yTFBdP+i+QgQ73C/I3lLtvXTwfleorvIX9YncVBeGDQXssmULxzqsM3izaLfJXCRAGx9ErL1Az10+fAqPZpq954OVSDqrR/61Q7CsMY7CiQO3nfIIaxgVL'
      }, {
        keySize: 1031,
        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
          'MIICXwIBAAKBgWyeKqA2oA4klYrKT9hjjutYQksJNN0cxwaQwIm9AYiLxOsYtT/C\r\n' +
          'ovJx5Oy1EvkbYQbfvYsGISUx9bW8yasZkTHR55IbW3+UptvQjTDtdxBQTgQOpsAh\r\n' +
          'BJtZYY3OmyH9Sj3F3oB//oyriNoj0QYyfsvlO8UsMmLzpnf6qfZBDHA/9QIDAQAB\r\n' +
          'AoGBBj/3ne5muUmbnTfU7lOUNrCGaADonMx6G0ObAJHyk6PPOePbEgcmDyNEk+Y7\r\n' +
          'aEAODjIzmttIbvZ39/Qb+o9nDmCSZC9VxiYPP+rjOzPglCDT5ks2Xcjwzd3If6Ya\r\n' +
          'Uw6P31Y760OCYeTb4Ib+8zz5q51CkjkdX5Hq/Yu+lZn0Vx7BAkENo83VfL+bwxTm\r\n' +
          'V7vR6gXqTD5IuuIGHL3uTmMNNURAP6FQDHu//duipys83iMChcOeXtboE16qYrO0\r\n' +
          '9KC0cqL4JQJBB/aYo/auVUGZA6f50YBp0b2slGMk9TBQG0iQefuuSyH4kzKnt2e3\r\n' +
          'Q40SBmprcM+DfttWJ11bouec++goXjz+95ECQQyiTWYRxulgKVuyqCYnvpLnTEnR\r\n' +
          '0MoYlVTHBriVPkLErYaYCYgse+SNM1+N4p/Thv6KmkUcq/Lmuc5DSRfbl1iBAkEE\r\n' +
          '7GKtJQvd7EO1bfpXnARQx+tWhwHHkgpFBBVHReMZ0rQEFhJ5o2c8HZEiZFNvGO2c\r\n' +
          '1fErP14zlu2JFZ03vpCI8QJBCQz9HL28VNjafSAF2mon/SNjKablRjoGGKSoSdyA\r\n' +
          'DHDZ/LeRsTp2dg8+bSiG1R+vPqw0f/BT+ux295Sy9ocGEM8=\r\n' +
          '-----END RSA PRIVATE KEY-----\r\n',
        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
          'MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgWyeKqA2oA4klYrKT9hjjutYQksJ\r\n' +
          'NN0cxwaQwIm9AYiLxOsYtT/CovJx5Oy1EvkbYQbfvYsGISUx9bW8yasZkTHR55Ib\r\n' +
          'W3+UptvQjTDtdxBQTgQOpsAhBJtZYY3OmyH9Sj3F3oB//oyriNoj0QYyfsvlO8Us\r\n' +
          'MmLzpnf6qfZBDHA/9QIDAQAB\r\n' +
          '-----END PUBLIC KEY-----\r\n',
        encrypted: 'ShSS4/fEAkuS6XiQakhOpWp82IXaaCaDNtsndU4uokvriqgCGZyqc+IkIk3eVmZ8bn4vVIRR43ydFuvGgsptVjizOdLGZudph3TJ1clcYEMcCXk4z5HaEu0bx5SW9jmzHhE/z+WV8PB48q7y7C2qtmPmfttG2NMsNLBvkiaDopRO',
        signature: 'Z3vYgRdezrWmdA3NC1Uz2CcHRTcE+/C2idGZA1FjUGqFztAHQ31k0QW/F5zuJdKvg8LQU45S3KxW+OQpbGPL98QbzJLhml88mFGe6OinLXJbi7UQWrtXwamc2jMdiXwovSLbXaXy6PX2QW089iC8XuAZftVi3T/IKV0458FQQprg',
        signaturePss: 'R6QsK6b3QinIPZPamm/dP0Zndqti1TzAkFTRSZJaRSa1u2zuvZC5QHF4flDjEtHosWeDyxrBE7PHGQZ0b1bHv9qgHGsJCMwaQPj3AWj9fjYmx7b86KM2vHr8q/vqDaa9pTvVRSSwvD6fwoZPc9twQEfdjdDBAiy23yLDzk/zZiwM',
        signatureWithAbcSalt: 'Ep9qx4/FPNcWTixWhvL2IAyJR69o5I4MIJi3cMAhDmpuTvAaL/ThQwFWkBPPOPT4Jbumnu6ELjPNjo72wa00e5k64qnZgy1pauBPMlXRlKehRc9UJZ6+xot642z8Qs+rt89OgbYTsvlyr8lzXooUHz/lPpfawYCqd7maRMs8YlYM',
        signatureWithCustomPrng: 'NHAwyn2MdM5ez/WbDNbu2A2JNS+cRiWk/zBoh0lg3aq/RsBS0nrYr4AGiC5jt6KWVcN4AIVOomYtX2k+MhLoemN2t2rDj/+LXOeU7kgCAz0q0ED2NFQz7919JU+PuYXMy03qTMfl5jbvStdi/00eQHjJKGEH+xAgrDcED2lrhtCu'
      }, {
        keySize: 1032,
        privateKeyPem: '-----BEGIN RSA PRIVATE KEY-----\r\n' +
          'MIICYQIBAAKBggDPhzn5I3GecxWt5DKbP+VhM2AFNSOL0+VbYEOR1hnlZdLbxGK4\r\n' +
          'cPQzMr2qT6dyttJcsgWr3xKobPkz7vsTZzQATSiekm5Js5dGpaj5lrq/x2+WTZvn\r\n' +
          '55x9M5Y5dlpusDMKcC3KaIX/axc+MbvPFzo6Eli7JLCWdBg01eKo30knil0CAwEA\r\n' +
          'AQKBggCNl/sjFF7SOD1jbt5kdL0hi7cI9o+xOLs1lEGmAEmc7dNnZN/ibhb/06/6\r\n' +
          'wuxB5aEz47bg5IvLZMbG+1hNjc26D0J6Y3Ltwrg8f4ZMdDrh4v0DZ8hy/HbEpMrJ\r\n' +
          'Td5dk3mtw9FLow10MB5udPLTDKhfDpTcWiObKm2STtFeBk3xeEECQQ6Cx6bZxQJ1\r\n' +
          'zCxflV5Xi8BgAQaUKMqygugte+HpOLflL0j1fuZ0rPosUyDOEFkTzOsPxBYYOU8i\r\n' +
          'Gzan1GvW3WwRAkEOTTRt849wpgC9xx2pF0IrYEVmv5gEMy3IiRfCNgEoBwpTWVf4\r\n' +
          'QFpN3V/9GFz0WQEEYo6OTmkNcC3Of5zbHhu1jQJBBGxXAYQ2KnbP4uLL/DMBdYWO\r\n' +
          'Knw1JvxdLPrYXVejI2MoE7xJj2QXajbirAhEMXL4rtpicj22EmoaE4H7HVgkrJEC\r\n' +
          'QQq2V5w4AGwvW4TLHXNnYX/eB33z6ujScOuxjGNDUlBqHZja5iKkCUAjnl+UnSPF\r\n' +
          'exaOwBrlrpiLOzRer94MylKNAkEBmI58bqfkI5OCGDArAsJ0Ih58V0l1UW35C1SX\r\n' +
          '4yDoXSM5A/xQu2BJbXO4jPe3PnDvCVCEyKpbCK6bWbe26Y7zuw==\r\n' +
          '-----END RSA PRIVATE KEY-----\r\n',
        publicKeyPem: '-----BEGIN PUBLIC KEY-----\r\n' +
          'MIGgMA0GCSqGSIb3DQEBAQUAA4GOADCBigKBggDPhzn5I3GecxWt5DKbP+VhM2AF\r\n' +
          'NSOL0+VbYEOR1hnlZdLbxGK4cPQzMr2qT6dyttJcsgWr3xKobPkz7vsTZzQATSie\r\n' +
          'km5Js5dGpaj5lrq/x2+WTZvn55x9M5Y5dlpusDMKcC3KaIX/axc+MbvPFzo6Eli7\r\n' +
          'JLCWdBg01eKo30knil0CAwEAAQ==\r\n' +
          '-----END PUBLIC KEY-----\r\n',
        encrypted: 'pKTbv+xgXPDc+wbjsANFu1/WTcmy4aZFKXKnxddHbU5S0Dpdj2OqCACiBwu1oENPMgPAJ27XRbFtKG+eS8tX47mKP2Fo0Bi+BPFtzuQ1bj3zUzTwzjemT+PU+a4Tho/eKjPhm6xrwGAoQH2VEDEpvcYf+SRmGFJpJ/zPUrSxgffj',
        signature: 'R9WBFprCfcIC4zY9SmBpEM0E+cr5j4gMn3Ido5mktoR9VBoJqC6eR6lubIPvZZUz9e4yUSYX0squ56Q9Y0yZFQjTHgsrlmhB2YW8kpv4h8P32Oz2TLcMJK9R2tIh9vvyxwBkd/Ml1qG60GnOFUFzxUad9VIlzaF1PFR6EfnkgBUW',
        signaturePss: 'v9UBd4XzBxSRz8yhWKjUkFpBX4Fr2G+ImjqbePL4sAZvYw1tWL+aUQpzG8eOyMxxE703VDh9nIZULYI/uIb9HYHQoGYQ3WoUaWqtZg1x8pZP+Ad7ilUWk5ImRl57fTznNQiVdwlkS5Wgheh1yJCES570a4eujiK9OyB0ba4rKIcM',
        signatureWithAbcSalt: 'HCm0FI1jE6wQgwwi0ZwPTkGjssxAPtRh6tWXhNd2J2IoJYj9oQMMjCEElnvQFBa/l00sIsw2YV1tKyoTABaSTGV4vlJcDF+K0g/wiAf30TRUZo72DZKDNdyffDlH0wBDkNVW+F6uqdciJqBC6zz+unNh7x+FRwYaY8xhudIPXdyP',
        signatureWithCustomPrng: 'AGyN8xu+0yfCR1tyB9mCXcTGb2vdLnsX9ro2Qy5KV6Hw5YMVNltAt65dKR4Y8pfu6D4WUyyJRUtJ8td2ZHYzIVtWY6bG1xFt5rkjTVg4v1tzQgUQq8AHvRE2qLzwDXhazJ1e6Id2Nuxb1uInFyRC6/gLmiPga1WRDEVvFenuIA48'
      }];
      for(var i = 0; i < tests.length; ++i) {
        createTests(tests[i]);
      }

      it('should ensure maximum message length for a 1024-bit key is exceeded', function() {
        /* For PKCS#1 v1.5, the message must be padded with at least eight bytes,
          two zero bytes and one byte telling what the block type is. This is 11
          extra bytes are added to the message. The test uses a message of 118
          bytes.Together with the 11 extra bytes the encryption block needs to be
          at least 129 bytes long. This requires a key of 1025-bits. */
        var key = PKI.publicKeyFromPem(tests[0].publicKeyPem);
        var message = UTIL.createBuffer().fillWithByte(0, 118);
        ASSERT.throws(function() {
          key.encrypt(message.getBytes());
        });
      });

      it('should ensure maximum message length for a 1025-bit key is not exceeded', function() {
        var key = PKI.publicKeyFromPem(tests[1].publicKeyPem);
        var message = UTIL.createBuffer().fillWithByte(0, 118);
        ASSERT.doesNotThrow(function() {
          key.encrypt(message.getBytes());
        });
      });

      /**
       * Creates RSA encryption & decryption tests.
       *
       * Uses different key sizes (1024, 1025, 1031, 1032). The test functions are
       * generated from "templates" below, one for each key size to provide sensible
       * output.
       *
       * Key material in was created with OpenSSL using these commands:
       *
       * openssl genrsa -out rsa_1024_private.pem 1024
       * openssl rsa -in rsa_1024_private.pem -out rsa_1024_public.pem \
       *   -outform PEM -pubout
       * echo 'too many secrets' | openssl rsautl -encrypt \
       *   -inkey rsa_1024_public.pem -pubin -out rsa_1024_encrypted.bin
       *
       * echo -n 'just testing' | openssl dgst -sha1 -binary > tosign.sha1
       * openssl pkeyutl -sign -in tosign.sha1 -inkey rsa_1024_private.pem \
       *   -out rsa_1024_sig.bin -pkeyopt digest:sha1
       * openssl pkeyutl -sign -in tosign.sha1 -inkey rsa_1024_private.pem \
       *   -out rsa_1024_sigpss.bin -pkeyopt digest:sha1 \
       *   -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:20
       *
       * OpenSSL commands for signature verification:
       *
       * openssl pkeyutl -verify -in tosign.sha1 -sigfile rsa_1024_sig.bin \
       *   -pubin -inkey rsa_1024_public.pem -pkeyopt digest:sha1
       * openssl pkeyutl -verify -in tosign.sha1 -sigfile rsa_1025_sigpss.bin \
       *   -pubin -inkey rsa_1025_public.pem -pkeyopt digest:sha1 \
       *   -pkeyopt rsa_padding_mode:pss -pkeyopt rsa_pss_saltlen:20
       */
      function createTests(params) {
        var keySize = params.keySize;

        it('should rsa encrypt using a ' + keySize + '-bit key', function() {
          var message = "it need's to be about 20% cooler"; // it need's better grammar too

          /* First step, do public key encryption */
          var key = PKI.publicKeyFromPem(params.publicKeyPem);
          var data = key.encrypt(message);

          /* Second step, use private key decryption to verify successful
            encryption. The encrypted message differs every time, since it is
            padded with random data. Therefore just rely on the decryption
            routine to work, which is tested seperately against an externally
            provided encrypted message. */
          key = PKI.privateKeyFromPem(params.privateKeyPem);
          ASSERT.equal(key.decrypt(data), message);
        });

        it('should rsa decrypt using a ' + keySize + '-bit key', function() {
          var data = UTIL.decode64(params.encrypted);
          var key = PKI.privateKeyFromPem(params.privateKeyPem);
          ASSERT.equal(key.decrypt(data), 'too many secrets\n');
        });

        it('should rsa sign using a ' + keySize + '-bit key and PKCS#1 v1.5 padding', function() {
          var key = PKI.privateKeyFromPem(params.privateKeyPem);

          var md = MD.sha1.create();
          md.start();
          md.update('just testing');

          var signature = UTIL.decode64(params.signature);
          ASSERT.equal(key.sign(md), signature);
        });

        it('should verify an rsa signature using a ' + keySize + '-bit key and PKCS#1 v1.5 padding', function() {
          var signature = UTIL.decode64(params.signature);
          var key = PKI.publicKeyFromPem(params.publicKeyPem);

          var md = MD.sha1.create();
          md.start();
          md.update('just testing');

          ASSERT.equal(key.verify(md.digest().getBytes(), signature), true);
        });

        /* Note: signatures are *not* deterministic (the point of RSASSA-PSS),
          so they can't be compared easily -- instead they are just verified
          using the verify() function which is tested against OpenSSL-generated
          signatures. */
        it('should rsa sign using a ' + keySize + '-bit key and PSS padding', function() {
          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);
          var publicKey = PKI.publicKeyFromPem(params.publicKeyPem);

          var md = MD.sha1.create();
          md.start();
          md.update('just testing');

          // create signature
          var pss = PSS.create(
            MD.sha1.create(), MGF.mgf1.create(MD.sha1.create()), 20);
          var signature = privateKey.sign(md, pss);

          // verify signature
          md.start();
          md.update('just testing');
          ASSERT.equal(
            publicKey.verify(md.digest().getBytes(), signature, pss), true);
        });

        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding', function() {
          var signature = UTIL.decode64(params.signaturePss);
          var key = PKI.publicKeyFromPem(params.publicKeyPem);

          var md = MD.sha1.create();
          md.start();
          md.update('just testing');

          var pss = PSS.create(
            MD.sha1.create(), MGF.mgf1.create(MD.sha1.create()), 20);
          ASSERT.equal(
            key.verify(md.digest().getBytes(), signature, pss), true);
        });

        it('should rsa sign using a ' + keySize + '-bit key and PSS padding using pss named-param API', function() {
          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);
          var publicKey = PKI.publicKeyFromPem(params.publicKeyPem);

          var md = MD.sha1.create();
          md.start();
          md.update('just testing');

          // create signature
          var pss = PSS.create({
            md: MD.sha1.create(),
            mgf: MGF.mgf1.create(MD.sha1.create()),
            saltLength: 20
          });
          var signature = privateKey.sign(md, pss);

          // verify signature
          md.start();
          md.update('just testing');
          ASSERT.equal(
            publicKey.verify(md.digest().getBytes(), signature, pss), true);
        });

        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding using pss named-param API', function() {
          var signature = UTIL.decode64(params.signaturePss);
          var key = PKI.publicKeyFromPem(params.publicKeyPem);

          var md = MD.sha1.create();
          md.start();
          md.update('just testing');

          var pss = PSS.create({
            md: MD.sha1.create(),
            mgf: MGF.mgf1.create(MD.sha1.create()),
            saltLength: 20
          });
          ASSERT.equal(
            key.verify(md.digest().getBytes(), signature, pss), true);
        });

        it('should rsa sign using a ' + keySize + '-bit key and PSS padding using salt "abc"', function() {
          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);

          var md = MD.sha1.create();
          md.start();
          md.update('just testing');

          // create signature
          var pss = PSS.create({
            md: MD.sha1.create(),
            mgf: MGF.mgf1.create(MD.sha1.create()),
            salt: UTIL.createBuffer('abc')
          });
          var signature = privateKey.sign(md, pss);
          var b64 = UTIL.encode64(signature);
          ASSERT.equal(b64, params.signatureWithAbcSalt);
        });

        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding using salt "abc"', function() {
          var signature = UTIL.decode64(params.signatureWithAbcSalt);
          var key = PKI.publicKeyFromPem(params.publicKeyPem);

          var md = MD.sha1.create();
          md.start();
          md.update('just testing');

          var pss = PSS.create({
            md: MD.sha1.create(),
            mgf: MGF.mgf1.create(MD.sha1.create()),
            saltLength: 3
          });
          ASSERT.equal(
            key.verify(md.digest().getBytes(), signature, pss), true);
        });

        it('should rsa sign using a ' + keySize + '-bit key and PSS padding using custom PRNG', function() {
          var prng = RANDOM.createInstance();
          prng.seedFileSync = function(needed) {
            return UTIL.fillString('a', needed);
          };
          var privateKey = PKI.privateKeyFromPem(params.privateKeyPem);

          var md = MD.sha1.create();
          md.start();
          md.update('just testing');

          // create signature
          var pss = PSS.create({
            md: MD.sha1.create(),
            mgf: MGF.mgf1.create(MD.sha1.create()),
            saltLength: 20,
            prng: prng
          });
          var signature = privateKey.sign(md, pss);
          var b64 = UTIL.encode64(signature);
          ASSERT.equal(b64, params.signatureWithCustomPrng);
        });

        it('should verify an rsa signature using a ' + keySize + '-bit key and PSS padding using custom PRNG', function() {
          var prng = RANDOM.createInstance();
          prng.seedFileSync = function(needed) {
            return UTIL.fillString('a', needed);
          };
          var signature = UTIL.decode64(params.signatureWithCustomPrng);
          var key = PKI.publicKeyFromPem(params.publicKeyPem);

          var md = MD.sha1.create();
          md.start();
          md.update('just testing');

          var pss = PSS.create({
            md: MD.sha1.create(),
            mgf: MGF.mgf1.create(MD.sha1.create()),
            saltLength: 20,
            prng: prng
          });
          ASSERT.equal(
            key.verify(md.digest().getBytes(), signature, pss), true);
        });
      }
    })();
  });
}

// check for AMD
if(typeof define === 'function') {
  define([
    '../../js/pki',
    '../../js/rsa',
    '../../js/md',
    '../../js/mgf',
    '../../js/pss',
    '../../js/random',
    '../../js/util'
  ], function(PKI, RSA, MD, MGF, PSS, RANDOM, UTIL) {
    Tests(
      // Global provided by test harness
      ASSERT,
      PKI,
      RSA,
      MD,
      MGF,
      PSS,
      RANDOM,
      UTIL
    );
  });
} else if(typeof module === 'object' && module.exports) {
  // assume NodeJS
  Tests(
    require('assert'),
    require('../../js/pki'),
    require('../../js/rsa'),
    require('../../js/md'),
    require('../../js/mgf'),
    require('../../js/pss'),
    require('../../js/random'),
    require('../../js/util'));
}

})();
