iamthechad/sslinfo

View on GitHub
lib/cipher.js

Summary

Maintainability
C
1 day
Test Coverage
var tls = require('tls'),
  Q = require('q'),
  _ = require('lodash'),
  method = require('./method');

module.exports = {
  /**
   * Determine the ciphers supported by a host for specific SSL/TLS methods.
   * @param hostData {object}
   * @return {promise}
   */
  getCipherResults: function(hostData) {
    var allTasks = [];
    hostData.protocols.forEach(function(item) {
      if (item.enabled) {
        var tasks = getCiphersSuitesForProtocol(item.protocol).map(function(d) {
          return trySSLCipher({
            host: hostData.host,
            port: hostData.port,
            servername: hostData.servername,
            secureProtocol: item.protocol,
            protocolCommonName: item.name,
            ciphers: d
          });
        });
        allTasks.push(tasks);
      }
    });
    return Q.all(_.flatten(allTasks)).then(function(results) {
      hostData.ciphers = {};
      results.forEach(function(item) {
        if (!hostData.ciphers[item.protocol]) {
          hostData.ciphers[item.protocol] = {
            name: item.protocolCommonName,
            enabled: [],
            disabled: [],
            unsupported: []
          };
        }
        var cipherRoot = hostData.ciphers[item.protocol];
        if (item.enabled) {
          cipherRoot.enabled.push(item.cipher);
        } else {
          if (item.unsupported) {
            cipherRoot.unsupported.push(item.cipher);
          } else {
            cipherRoot.disabled.push(item.cipher);
          }
        }
      });
      return hostData;
    });
  },
  /**
   * Get the list of all cipher suites known by this tool.
   * @returns {string[]}
   */
  cipherSuites: function() {
    return _.union(commonBase, sslV3CipherSuites, tlsCipherSuites, tls12CipherSuites, extraCipherSuites);
  },
  badCipherSuites: function() {
    return badCipherSuites;
  }
};

/**
 * Determine if a host supports a specific cipher/SSL method combination.
 * @param options {object}
 * @returns {promise}
 * @private
 */
function trySSLCipher(options) {
  var deferred = Q.defer();

  var fullOptions = {
    rejectUnauthorized: false
  };
  fullOptions = _.extend(fullOptions, options);

  var connected = false;

  var baseResponse = {
    protocol: options.secureProtocol,
    protocolCommonName: options.protocolCommonName,
    cipher: options.ciphers
  };

  var socket = tls.connect(fullOptions, function() {
    connected = true;
    socket.end();
    deferred.resolve(_.assign({}, baseResponse, {enabled: true}));
  });
  socket.setEncoding('utf8');
  socket.on('error', function(error) {
    var disabledObj = _.assign({}, baseResponse, {enabled: false});
    var errorString = error.toString();
    if ((errorString.indexOf('socket hang up') !== -1) ||
      (errorString.indexOf('handshake failure') !== -1) ||
      (errorString.indexOf('ECONNREFUSED') !== -1)) {
      deferred.resolve(disabledObj);
    } else if (errorString.indexOf('no ciphers available') !== -1) {
      deferred.resolve(_.assign({}, disabledObj, {unsupported: true}));
    } else if (errorString.indexOf('ECONNRESET') !== -1) {
      // Some servers just terminate the connection on socket.end() instead of sending the ACK
      if (!connected) {
        deferred.resolve(disabledObj);
      }
    } else {
      deferred.reject({
        host: options.host,
        port: options.port,
        protocol: options.secureProtocol,
        protocolCommonName: options.protocolCommonName,
        cipher: options.ciphers,
        error: error
      });
    }
    socket.end();
  });
  return deferred.promise;
}

function getCiphersSuitesForProtocol(protocol) {
  switch (protocol) {
    case method.methods().SSLv2_method.name:
      return [];
    case method.methods().SSLv3_method.name:
      return _.union(commonBase, sslV3CipherSuites);
    case method.methods().TLSv1_method.name:
    case method.methods().TLSv1_1_method.name:
      return _.union(commonBase, tlsCipherSuites);
    case method.methods().TLSv1_2_method.name:
      return _.union(commonBase, tlsCipherSuites, tls12CipherSuites);
    default:
      /* eslint-disable-next-line no-console */
      console.log('Invalid protocol specified: ' + protocol);
      return ['INVALID_PROTOCOL_' + protocol];
  }
}

var commonBase = ['NULL-MD5',
  'NULL-SHA',
  'EXP-RC4-MD5',
  'RC4-MD5',
  'RC4-SHA',
  'EXP-RC2-CBC-MD5',
  'IDEA-CBC-SHA',
  'EXP-DES-CBC-SHA',
  'DES-CBC-SHA',
  'DES-CBC3-SHA',
  'EXP-DHE-DSS-DES-CBC-SHA',
  'DHE-DSS-CBC-SHA',
  'DHE-DSS-DES-CBC3-SHA',
  'EXP-DHE-RSA-DES-CBC-SHA',
  'DHE-RSA-DES-CBC-SHA',
  'DHE-RSA-DES-CBC3-SHA',
  'EXP-ADH-RC4-MD5',
  'ADH-RC4-MD5',
  'EXP-ADH-DES-CBC-SHA',
  'ADH-DES-CBC-SHA',
  'ADH-DES-CBC3-SHA',
  'EXP1024-DES-CBC-SHA',
  'EXP1024-RC4-SHA',
  'EXP1024-DHE-DSS-DES-CBC-SHA',
  'EXP1024-DHE-DSS-RC4-SHA',
  'DHE-DSS-RC4-SHA'];

var sslV3CipherSuites = [
  'EXP-DH-DSS-DES-CBC-SHA',
  'DH-DSS-DES-CBC-SHA',
  'DH-DSS-DES-CBC3-SHA',
  'EXP-DH-RSA-DES-CBC-SHA',
  'DH-RSA-DES-CBC-SHA',
  'DH-RSA-DES-CBC3-SHA'
];

var tlsCipherSuites = [
  'AES128-SHA',
  'AES256-SHA',
  'DH-DSS-AES128-SHA',
  'DH-DSS-AES256-SHA',
  'DH-RSA-AES128-SHA',
  'DH-RSA-AES256-SHA',
  'DHE-DSS-AES128-SHA',
  'DHE-DSS-AES256-SHA',
  'DHE-RSA-AES128-SHA',
  'DHE-RSA-AES256-SHA',
  'ADH-AES128-SHA',
  'ADH-AES256-SHA',
  'CAMELLIA128-SHA',
  'CAMELLIA256-SHA',
  'DH-DSS-CAMELLIA128-SHA',
  'DH-DSS-CAMELLIA256-SHA',
  'DH-RSA-CAMELLIA128-SHA',
  'DH-RSA-CAMELLIA256-SHA',
  'DHE-DSS-CAMELLIA128-SHA',
  'DHE-DSS-CAMELLIA256-SHA',
  'DHE-RSA-CAMELLIA128-SHA',
  'DHE-RSA-CAMELLIA256-SHA',
  'ADH-CAMELLIA128-SHA',
  'ADH-CAMELLIA256-SHA',
  'SEED-SHA',
  'DH-DSS-SEED-SHA',
  'DH-RSA-SEED-SHA',
  'DHE-DSS-SEED-SHA',
  'DHE-RSA-SEED-SHA',
  'ADH-SEED-SHA',
  'ECDH-RSA-NULL-SHA',
  'ECDH-RSA-RC4-SHA',
  'ECDH-RSA-DES-CBC3-SHA',
  'ECDH-RSA-AES128-SHA',
  'ECDH-RSA-AES256-SHA',
  'ECDH-ECDSA-NULL-SHA',
  'ECDH-ECDSA-RC4-SHA',
  'ECDH-ECDSA-DES-CBC3-SHA',
  'ECDH-ECDSA-AES128-SHA',
  'ECDH-ECDSA-AES256-SHA',
  'ECDHE-RSA-NULL-SHA',
  'ECDHE-RSA-RC4-SHA',
  'ECDHE-RSA-DES-CBC3-SHA',
  'ECDHE-RSA-AES128-SHA',
  'ECDHE-RSA-AES256-SHA',
  'ECDHE-ECDSA-NULL-SHA',
  'ECDHE-ECDSA-RC4-SHA',
  'ECDHE-ECDSA-DES-CBC3-SHA',
  'ECDHE-ECDSA-AES128-SHA',
  'ECDHE-ECDSA-AES256-SHA',
  'AECDH-NULL-SHA',
  'AECDH-RC4-SHA',
  'AECDH-DES-CBC3-SHA',
  'AECDH-AES128-SHA',
  'AECDH-AES256-SHA'
];

var tls12CipherSuites = [
  'NULL-SHA256',
  'AES128-SHA256',
  'AES256-SHA256',
  'AES128-GCM-SHA256',
  'AES256-GCM-SHA384',
  'DH-RSA-AES128-SHA256',
  'DH-RSA-AES256-SHA256',
  'DH-RSA-AES128-GCM-SHA256',
  'DH-RSA-AES256-GCM-SHA384',
  'DH-DSS-AES128-SHA256',
  'DH-DSS-AES256-SHA256',
  'DH-DSS-AES128-GCM-SHA256',
  'DH-DSS-AES256-GCM-SHA384',
  'DHE-RSA-AES128-SHA256',
  'DHE-RSA-AES256-SHA256',
  'DHE-RSA-AES128-GCM-SHA256',
  'DHE-RSA-AES256-GCM-SHA384',
  'DHE-DSS-AES128-SHA256',
  'DHE-DSS-AES256-SHA256',
  'DHE-DSS-AES128-GCM-SHA256',
  'DHE-DSS-AES256-GCM-SHA384',
  'ECDH-RSA-AES128-SHA256',
  'ECDH-RSA-AES256-SHA384',
  'ECDH-RSA-AES128-GCM-SHA256',
  'ECDH-RSA-AES256-GCM-SHA384',
  'ECDH-ECDSA-AES128-SHA256',
  'ECDH-ECDSA-AES256-SHA384',
  'ECDH-ECDSA-AES128-GCM-SHA256',
  'ECDH-ECDSA-AES256-GCM-SHA384',
  'ECDHE-RSA-AES128-SHA256',
  'ECDHE-RSA-AES256-SHA384',
  'ECDHE-RSA-AES128-GCM-SHA256',
  'ECDHE-RSA-AES256-GCM-SHA384',
  'ECDHE-ECDSA-AES128-SHA256',
  'ECDHE-ECDSA-AES256-SHA384',
  'ECDHE-ECDSA-AES128-GCM-SHA256',
  'ECDHE-ECDSA-AES256-GCM-SHA384',
  'ADH-AES128-SHA256',
  'ADH-AES256-SHA256',
  'ADH-AES128-GCM-SHA256',
  'ADH-AES256-GCM-SHA384',
  'ECDHE-ECDSA-CAMELLIA128-SHA256',
  'ECDHE-ECDSA-CAMELLIA256-SHA384',
  'ECDH-ECDSA-CAMELLIA128-SHA256',
  'ECDH-ECDSA-CAMELLIA256-SHA384',
  'ECDHE-RSA-CAMELLIA128-SHA256',
  'ECDHE-RSA-CAMELLIA256-SHA384',
  'ECDH-RSA-CAMELLIA128-SHA256',
  'ECDH-RSA-CAMELLIA256-SHA384'
];

var extraCipherSuites = ['SRP-DSS-AES-256-CBC-SHA',
  'SRP-RSA-AES-256-CBC-SHA',
  'SRP-AES-256-CBC-SHA',
  'PSK-AES256-CBC-SHA',
  'SRP-DSS-AES-128-CBC-SHA',
  'SRP-RSA-AES-128-CBC-SHA',
  'SRP-AES-128-CBC-SHA',
  'PSK-AES128-CBC-SHA',
  'PSK-RC4-SHA',
  'SRP-DSS-3DES-EDE-CBC-SHA',
  'SRP-RSA-3DES-EDE-CBC-SHA',
  'SRP-3DES-EDE-CBC-SHA',
  'EDH-RSA-DES-CBC3-SHA',
  'EDH-DSS-DES-CBC3-SHA',
  'PSK-3DES-EDE-CBC-SHA',
  'EDH-RSA-DES-CBC-SHA',
  'EDH-DSS-DES-CBC-SHA',
  'EXP-EDH-RSA-DES-CBC-SHA',
  'EXP-EDH-DSS-DES-CBC-SHA'];

var badCipherSuites = ['ECDHE-RSA-DES-CBC3-SHA',
  'ECDHE-ECDSA-DES-CBC3-SHA',
  'EDH-RSA-DES-CBC3-SHA',
  'EDH-DSS-DES-CBC3-SHA',
  'DH-RSA-DES-CBC3-SHA',
  'DH-DSS-DES-CBC3-SHA',
  'ECDH-RSA-DES-CBC3-SHA',
  'ECDH-ECDSA-DES-CBC3-SHA',
  'DES-CBC3-SHA',
  'EDH-RSA-DES-CBC-SHA',
  'EDH-DSS-DES-CBC-SHA',
  'DH-RSA-DES-CBC-SHA',
  'DH-DSS-DES-CBC-SHA',
  'DES-CBC-SHA',
  'EXP-EDH-RSA-DES-CBC-SHA',
  'EXP-EDH-DSS-DES-CBC-SHA',
  'EXP-DH-RSA-DES-CBC-SHA',
  'EXP-DH-DSS-DES-CBC-SHA',
  'EXP-DES-CBC-SHA',
  'EXP-EDH-RSA-DES-CBC-SHA',
  'EXP-EDH-DSS-DES-CBC-SHA',
  'EXP-DH-RSA-DES-CBC-SHA',
  'EXP-DH-DSS-DES-CBC-SHA',
  'EXP-DES-CBC-SHA',
  'EXP-RC2-CBC-MD5',
  'EXP-RC4-MD5',
  'RC4-MD5',
  'EXP-RC2-CBC-MD5',
  'EXP-RC4-MD5',
  'ECDHE-RSA-RC4-SHA',
  'ECDHE-ECDSA-RC4-SHA',
  'ECDH-RSA-RC4-SHA',
  'ECDH-ECDSA-RC4-SHA',
  'RC4-SHA',
  'RC4-MD5',
  'PSK-RC4-SHA',
  'EXP-RC4-MD5',
  'ECDHE-RSA-NULL-SHA',
  'ECDHE-ECDSA-NULL-SHA',
  'AECDH-NULL-SHA',
  'RC4-SHA',
  'RC4-MD5',
  'ECDH-RSA-NULL-SHA',
  'ECDH-ECDSA-NULL-SHA',
  'NULL-SHA256',
  'NULL-SHA',
  'NULL-MD5'];