/*
 * Copyright 2018 Scytl Secure Electronic Voting SA
 *
 * All rights reserved
 *
 * See our extended copyright notice in *file 'Copyright.txt' which is part of this source code package
 */
/**
 * @global
 * @description This object describes the configuration scheme for this
 *              cryptographic library. The following modules are considered:
 *              <ul>
 *              <li> Asymmetric
 *              <li> Primitives
 *              <li> Stores
 *              <li> Symmetric
 *              <li> Digital Envelope
 *              </ul>
 * @readonly
 * @property {object} asymmetric.keypair - Policies for generating keypairs.
 * @property {object} asymmetric.keypair.encryption - Policies for generating
 *           encryption keypairs.
 * @property {object} asymmetric.keypair.encryption.algorithm - Algorithm.
 * @property {string} asymmetric.keypair.encryption.algorithm.RSA - RSA
 *           algorithm.
 * @property {object} asymmetric.keypair.encryption.provider - Provider.
 * @property {string} asymmetric.keypair.encryption.provider.FORGE - Forge
 *           provider.
 * @property {object} asymmetric.keypair.encryption.keyLength - Key length.
 * @property {number} asymmetric.keypair.encryption.keyLength.KL_2048 - 2048
 *           bits.
 * @property {number} asymmetric.keypair.encryption.keyLength.KL_3072 - 3072
 *           bits.
 * @property {number} asymmetric.keypair.encryption.keyLength.KL_4096 - 4096
 *           bits.
 * @property {object} asymmetric.keypair.encryption.publicExponent - Public
 *           exponent for generating encryption keypairs.
 * @property {number} asymmetric.keypair.encryption.publicExponent.F4 - 65537.
 *
 * @property {object} asymmetric.signer - Policies for signing and verifying
 *           data.
 * @property {object} asymmetric.signer.algorithm - Algorithm.
 * @property {string} asymmetric.signer.algorithm.RSA - RSA algorithm.
 * @property {object} asymmetric.signer.provider - Provider.
 * @property {string} asymmetric.signer.provider.FORGE - Forge provider.
 * @property {object} asymmetric.signer.hash - Hash algorithm.
 * @property {string} asymmetric.signer.hash.SHA256 - SHA-256 algorithm.
 * @property {object} asymmetric.signer.padding - Padding scheme.
 * @property {object} asymmetric.signer.padding.PSS - PSS padding scheme.
 * @property {string} asymmetric.signer.padding.PSS.name - PSS.
 * @property {object} asymmetric.signer.padding.PSS.hash - Hash for the PSS
 *           padding scheme.
 * @property {string} asymmetric.signer.padding.PSS.hash.SHA256 - SHA-256
 *           algorithm.
 * @property {object} asymmetric.signer.padding.PSS.mask - Mask generating
 *           function for the PSS padding scheme.
 * @property {object} asymmetric.signer.padding.PSS.mask.MGF1 - MGF1 for the PSS
 *           padding scheme.
 * @property {string} asymmetric.signer.padding.PSS.mask.MGF1.name - MGF1.
 * @property {object} asymmetric.signer.padding.PSS.mask.MGF1.hash - Hash
 *           algorithm for the MGF1 for the PSS padding scheme.
 * @property {string} asymmetric.signer.padding.PSS.mask.MGF1.hash.SHA256 -
 *           SHA-256 algorithm.
 * @property {object} asymmetric.signer.padding.PSS.saltLengthBytes - Salt
 *           length for the PSS padding scheme.
 * @property {number} asymmetric.signer.padding.PSS.saltLengthBytes.SL_32 - 32
 *           bytes.
 * @property {object} asymmetric.signer.publicExponent - Public exponent.
 * @property {number} asymmetric.signer.publicExponent.F4 - 65537.
 * @property {object} primitives.securerandom - Policies for generating
 *           securerandom.
 * @property {object} primitives.securerandom.provider - Provider.
 * @property {string} primitives.securerandom.provider.SCYTL - Scytl provider.
 * @property {object} primitives.messagedigest - Policies for generating message
 *           digests.
 * @property {object} primitives.messagedigest.algorithm - Algorithm.
 * @property {string} primitives.messagedigest.algorithm.SHA256 - SHA-256
 *           algorithm.
 * @property {object} primitives.messagedigest.provider - Provider.
 * @property {string} primitives.messagedigest.provider.FORGE - Forge provider.
 * @property {object} symmetric.secretkey - Policies to generate secret keys.
 * @property {number} symmetric.secretkey.length - 128 bits.
 * @property {object} symmetric.cipher - Policies to encrypt and decrypt
 *           symmetrically.
 * @property {object} symmetric.cipher.algorithm - Algorithm.
 * @property {object} symmetric.cipher.algorithm.AES128_GCM - AES128-GCM
 *           algorithm.
 * @property {string} symmetric.cipher.algorithm.AES128_GCM.name - AES128-GCM.
 * @property {number} symmetric.cipher.algorithm.AES128_GCM.keyLengthBytes - 16
 *           bytes.
 * @property {number} symmetric.cipher.algorithm.AES128_GCM.tagLengthBytes - 16
 *           bytes.
 * @property {object} symmetric.cipher.provider - Provider.
 * @property {object} symmetric.cipher.provider.FORGE - Forge provider.
 * @property {object} symmetric.cipher.initializationVectorLengthBytes -
 *           Initialization vector length.
 * @property {number} symmetric.cipher.initializationVectorLengthBytes.IV_12 -
 *           12 bytes.
 */
var Config = {
  homomorphic: {cipher: {secureRandom: {provider: {SCYTL: 'Scytl'}}}},
  asymmetric: {
    keypair: {
      encryption: {
        algorithm: {RSA: 'RSA'},
        provider: {FORGE: 'Forge'},
        keyLength: {KL_2048: 2048, KL_3072: 3072, KL_4096: 4096},
        publicExponent: {F4: 65537}
      },
      secureRandom: {provider: {SCYTL: 'Scytl'}}
    },
    signer: {
      algorithm: {RSA: 'RSA'},
      provider: {FORGE: 'Forge'},
      hash: {SHA256: 'SHA256'},
      padding: {
        PSS: {
          name: 'PSS',
          hash: {SHA256: 'SHA256'},
          mask: {MGF1: {name: 'MGF1', hash: {SHA256: 'SHA256'}}},
          saltLengthBytes: {SL_32: 32}
        }
      },
      publicExponent: {F4: 65537},
      secureRandom: {provider: {SCYTL: 'Scytl'}}
    },
    cipher: {
      algorithm: {
        RSA_OAEP: {
          name: 'RSA-OAEP',
          hash: 'SHA256',
          mask: {MGF1: {name: 'MGF1', hash: 'SHA1'}}
        },
        RSA_KEM: {
          name: 'RSA-KEM',
          secretKeyLengthBytes: {KL_16: 16, KL_24: 24, KL_32: 32},
          ivLengthBytes: {IVL_12: 12, IVL_16: 16, IVL_32: 32, IVL_64: 64},
          tagLengthBytes: {TL_12: 12, TL_16: 16, TL_32: 32, TL_64: 64},
          deriver: {
            name: {KDF1: 'kdf1', KDF2: 'kdf2', MGF1: 'mgf1'},
            messagedigest: {algorithm: {SHA256: 'SHA256'}}
          },
          cipher: {AESGCM: 'AES-GCM'}
        }
      },
      provider: {FORGE: 'Forge'},
      secureRandom: {provider: {SCYTL: 'Scytl'}}
    }
  },
  primitives: {
    secureRandom: {provider: {SCYTL: 'Scytl'}},
    messagedigest: {
      algorithm: {SHA256: 'SHA256', SHA512_224: 'SHA512/224'},
      provider: {FORGE: 'Forge'}
    },
    derivation: {
      pbkdf: {
        provider: {FORGE: 'Forge'},
        hash: {SHA256: 'SHA256'},
        saltLengthBytes: {SL_20: 20, SL_32: 32},
        keyLengthBytes: {KL_16: 16, KL_32: 32},
        iterations: {
          I_1: 1,
          I_8000: 8000,
          I_16000: 16000,
          I_32000: 32000,
          I_64000: 64000
        }
      }
    }
  },
  stores: {},
  symmetric: {
    secretkey: {
      encryption: {lengthBytes: {SK_16: 16}},
      mac: {lengthBytes: {SK_32: 32}},
      secureRandom: {provider: {SCYTL: 'Scytl'}}
    },
    cipher: {
      provider: {FORGE: 'Forge'},
      algorithm: {
        AES128_GCM: {name: 'AES-GCM', keyLengthBytes: 16, tagLengthBytes: 16}
      },
      initializationVectorLengthBytes: {IV_12: 12},
      secureRandom: {provider: {SCYTL: 'Scytl'}}
    },
    mac: {hash: {SHA256: 'SHA256'}, provider: {FORGE: 'Forge'}}
  },
  proofs: {
    secureRandom: {provider: {SCYTL: 'Scytl'}},
    messagedigest: {
      algorithm: {SHA256: 'SHA256', SHA512_224: 'SHA512/224'},
      provider: {FORGE: 'Forge'}
    },
    charset: {UTF8: 'UTF-8'}
  },
  digitalenvelope: {
    symmetric: {
      secretkey: {
        encryption: {lengthBytes: {SK_16: 16}},
        mac: {lengthBytes: {SK_32: 32}},
        secureRandom: {provider: {SCYTL: 'Scytl'}}
      },
      cipher: {
        provider: {FORGE: 'Forge'},
        algorithm: {
          AES128_GCM:
              {name: 'AES-GCM', keyLengthBytes: 16, tagLengthBytes: 16}
        },
        initializationVectorLengthBytes: {IV_12: 12},
        secureRandom: {provider: {SCYTL: 'Scytl'}}
      },
      mac: {hash: {SHA256: 'SHA256'}, provider: {FORGE: 'Forge'}}
    },
    asymmetric: {
      cipher: {
        algorithm: {
          RSA_OAEP: {
            name: 'RSA-OAEP',
            hash: 'SHA256',
            mask: {MGF1: {name: 'MGF1', hash: 'SHA1'}}
          },
          RSA_KEM: {
            name: 'RSA-KEM',
            secretKeyLengthBytes: {KL_16: 16, KL_24: 24, KL_32: 32},
            ivLengthBytes: {IVL_12: 12, IVL_16: 16, IVL_32: 32, IVL_64: 64},
            tagLengthBytes: {TL_12: 12, TL_16: 16, TL_32: 32, TL_64: 64},
            deriver: {
              name: {KDF1: 'kdf1', KDF2: 'kdf2', MGF1: 'mgf1'},
              messagedigest: {algorithm: {SHA256: 'SHA256'}}
            },
            cipher: {AESGCM: 'AES-GCM'}
          }
        },
        provider: {FORGE: 'Forge'},
        secureRandom: {provider: {SCYTL: 'Scytl'}}
      }
    }
  }
};


if (Object.freeze) {
  /**
   * Freezes an object and all its contents.
   * @param {Object} target the object to freeze.
   */
  var recursiveFreeze = function(target) {
    'use strict';
    // Get all property names
    var propNames = Object.getOwnPropertyNames(target);

    // Freeze the object's properties.
    propNames.forEach(function(name) {
      var property = target[name];

      // Freeze the properties' properties.
      if (typeof property === 'object' && property !== null) {
        recursiveFreeze(property);
      }
    });

    // Freeze the object itself.
    return Object.freeze(target);
  };

  // Lock down the configuration
  recursiveFreeze(Config);
}


var cryptolibPolicies;

/**
 * @global
 * @function cryptolib
 *
 * @description Core of the library, this module is used to sandbox the
 *              requested modules.
 *
 * @example // how to load all modules cryptolib('*', function(box) { // any
 *          module can be used from here. //
 *          box.examplemodule.service.getExample(); });
 *
 *
 * @param modules
 *            {...modules} modules to be loaded. '*' or none to load all the
 *            modules.
 * @param function
 *            {cryptolibSandbox} a closure for the cryptolib object.
 * @tutorial workers
 */
function cryptolib() {
  'use strict';

  /* Class to keep the references to polices and prng. */
  function Cryptolib(modules, callback) {
    function getAllModules(parent) {
      var modules = [];
      for (var module in parent) {
        if (parent.hasOwnProperty(module)) {
          modules.push(module);
        }
      }
      return modules;
    }

    function loadModules(box, modules, parentModule) {
      if (!modules || modules.toString() === '*'.toString()) {
        modules = getAllModules(parentModule);
      }

      for (var i = 0; i < modules.length; i += 1) {
        var firstDotIndex = modules[i].indexOf('.');
        if (firstDotIndex > -1) {
          var subModule = modules[i].substr(firstDotIndex + 1);
          var root = modules[i].substr(0, firstDotIndex);

          loadModules(box, [subModule], parentModule[root]);
        } else if (typeof parentModule[modules[i]] === 'function') {
          parentModule[modules[i]](box);
        } else if (typeof parentModule[modules[i]] === 'object') {
          loadModules(box, '*', parentModule[modules[i]]);
        }
      }
    }

    this.policies = callback.policies || cryptolibPolicies;
    cryptolibPolicies = cryptolibPolicies || this.policies;
    this.prng = callback.prng || cryptoPRNG.getPRNG();
    loadModules(this, modules, cryptolib.modules);
    callback(this);
  }

  Cryptolib.prototype = {
    getKeys: function(obj) {
      var keys = [];
      for (var key in obj) {
        if (typeof key !== 'undefined') {
          keys.push(key);
        }
      }
      return keys;
    },

    getModuleNames: function() {
      var moduleNames = '';
      for (var module in cryptolib.modules) {
        if (typeof module !== 'undefined') {
          if (moduleNames.length > 0) {
            moduleNames += ', ';
          }
          moduleNames += module;
        }
      }

      return '[' + moduleNames + ']';
    },
    forge: forge,  // redefines forge global variable
    sjcl: sjcl,    // redefines sjcl global variable
    // Constants.
    /** @constant */
    NUMBER_OF_BITS_PER_BYTE: 8,
    /** @constant */
    MAXIMUM_LENGTH_TO_GENERATE_DATA: 512,
    /** @constant */
    BASE_2_RADIX: 2,
    /** @constant */
    BASE_10_RADIX: 10,
    /** @constant */
    BASE_16_RADIX: 16,
    /** @constant */
    EPOCH_TIME_LENGTH: 10,
    /** @constant */
    RSA_LINE_LENGTH: 64,
    /** @constant */
    SHA_256_LENGTH: 32,
    /** @constant */
    HMAC_SHA256_KEY_LENGTH: 32,
    /** @constant */
    AES_KEY_LENGTH: 16,
    /** @constant */
    AES_CBC_IV_LENGTH: 16,
    /** @constant */
    DES3_KEY_LENGTH: 24,
    /** @constant */
    DES3_CBC_IV_LENGTH: 8,
    EQUALS: ' = ',
    NEWLINE: '\n',
    CRYPTO_SPEC_DELIM: '/',
    /** @constant */
    SHA_256: 'SHA256',
    /** @constant */
    RSA_ECB_PKCS1PADDING: 'RSA/ECB/PKCS1PADDING',
    /** @constant */
    RSA_ECB_OAEP_WITH_SHA256_AND_MGF1PADDING:
        'RSA/ECB/OAEPWITHSHA-256ANDMGF1PADDING',
    /** @constant */
    FORGE_RSA_OAEP: 'RSA-OAEP',
    /** @constant */
    FORGE_RSAES_PKCS1_V1_5: 'RSAES-PKCS1-V1_5',
    /** @constant */
    MINIMUM_PRIME_CERTAINTY_LEVEL: 80,
    /** @constant */
    SHORT_EXPONENT_BIT_LENGTH: 256,
    /** @constant */
    EXPONENTIATION_PROOF_AUXILIARY_DATA: 'ExponentiationProof',
    /** @constant */
    PLAINTEXT_PROOF_AUXILIARY_DATA: 'PlaintextProof',
    /** @constant */
    SIMPLE_PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA:
        'SimplePlaintextEqualityProof',
    /** @constant */
    PLAINTEXT_EQUALITY_PROOF_AUXILIARY_DATA: 'PlaintextEqualityProof',
    /** @constant */
    PLAINTEXT_EXPONENT_EQUALITY_PROOF_AUXILIARY_DATA:
        'PlaintextExponentEqualityProof'
  };

  var args = Array.prototype.slice.call(arguments);
  var callback = args.pop();
  var modules = (args[0] && typeof args[0] === 'string') ? args : args[0];

  return new Cryptolib(modules, callback);
}

cryptolib.modules = {};
