/**
 * Copyright 2007-2016, Kaazing Corporation. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kaazing.gateway.util.ssl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class SslCipherSuites {

    private enum CipherStrength {
        HIGH("HIGH"),
        MEDIUM("MEDIUM"),
        LOW("LOW"),
        EXPORT40("EXPORT40"),

        // OpenSSL provides this token, but it is an empty list
        EXPORT56("EXPORT56"),

        NONE("NONE");

        private final String tokenName;

        CipherStrength(String tokenName) {
            this.tokenName = tokenName;
        }
    }

    private enum CipherKeyExchange {
        RSA("kRSA"),
        DH("kDH"),
        DHE("kEDH"),
        ECDH_ECDSA("kECDHe"),
        ECDH_RSA("kECDHr"),
        ECDHE("kEECDHe"),
        KRB5("kKRB5");

        private final String tokenName;

        CipherKeyExchange(String tokenName) {
            this.tokenName = tokenName;
        }
    }

    private enum CipherAuthentication {
        RSA("aRSA"),
        DSS("aDSS"),
        ECDH("aECDH"),
        ECDSA("aECDSA"),
        KRB5("aKRB5"),
        NULL("aNULL");

        private final String tokenName;

        CipherAuthentication(String tokenName) {
            this.tokenName = tokenName;
        }
    }

    private enum CipherEncryption {
        RC4("RC4"),
        AES128("AES128"),
        AES256("AES256"),
        AESGCM128("AESGCM128"),
        AESGCM256("AESGCM256"),
        TripleDES("3DES"),
        DES("DES"),
        NULL("eNULL");

        private final String tokenName;

        CipherEncryption(String tokenName) {
            this.tokenName = tokenName;
        }
    }

    private enum CipherMAC {
        MD5("MD5"),
        SHA1("SHA1"),
        SHA256("SHA256"),
        SHA384("SHA384"),
        AEAD("AEAD");

        private final String tokenName;

        CipherMAC(String tokenName) {
            this.tokenName = tokenName;
        }
    }

    private enum CipherProtocolVersion {
        SSLV3("SSLv3"),
        TLSV1("TLSv1"),
        TLSV1_1("TLSv1.1"),
        TLSV1_2("TLSv1.2");

        private final String tokenName;

        CipherProtocolVersion(String tokenName) {
            this.tokenName = tokenName;
        }
    }

    private static class SslCipher implements Comparable<SslCipher> {
        final String name;
        private final String nickname;
        private final Integer strengthBits;
        private final CipherStrength strength;
        private final CipherKeyExchange keyExchange;
        private final CipherAuthentication authentication;
        private final CipherEncryption encryption;
        private final CipherMAC mac;
        private final CipherProtocolVersion protocolVersion;
        private final boolean fips;

        SslCipher(String name,
                  String nickname,
                  int strengthBits,
                  CipherStrength strength,
                  CipherKeyExchange keyExchange,
                  CipherAuthentication authentication,
                  CipherEncryption encryption,
                  CipherMAC mac,
                  CipherProtocolVersion protocolVersion,
                  boolean fips) {
            this.name = name;
            this.nickname = nickname;
            this.strengthBits = strengthBits;
            this.strength = strength;
            this.keyExchange = keyExchange;
            this.authentication = authentication;
            this.encryption = encryption;
            this.mac = mac;
            this.protocolVersion = protocolVersion;
            this.fips = fips;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }

            if (o instanceof SslCipher) {
                SslCipher c = (SslCipher) o;
                return name.equals(c.name);
            }
            return false;
        }

        @Override
        public int hashCode() {
            return name.hashCode();
        }

        @Override
        public int compareTo(SslCipher o) {
            // To sort the ciphers in DESCENDING order of bits, we need to
            // invert the normal comparison
            return strengthBits.compareTo(o.strengthBits) * -1;
        }

        @Override
        public String toString() {
            return String.format("%s:[%s/%s/%s/%s/%s/%s(%d)%s]", name,
                keyExchange, authentication, encryption,
                mac, protocolVersion, strength,
                strengthBits, fips ? "/FIPS" : "");
        }
    }

    // Used as a placeholder for non-SunJSSEProvider ciphers; we override
    // compareTo() so that sorting by strength keeps these ciphers in their
    // configured place in the list.
    private static class OtherSslCipher
        extends SslCipher {

        OtherSslCipher(String name) {
            super(name, null, 0, null, null, null, null, null, null, false);
        }

        @Override
        public int compareTo(SslCipher o) {
            return 0;
        }
    }

    private enum CipherOp {
        ADD,        // default
        KILL,        // '!' prefix
        RIGHT_SHIFT,    // '+' prefix
        REMOVE        // '-' prefix
    }

    // These mappings are based on the groupings used by OpenSSL; see
    //   openssl/ssl/ssl.h
    //   openssl/ssl/tls1.h
    //   openssl/ssl/s3_lib.c.
    //   openssl/ssl/ssl_locl.h

    // Map of the ciphersuite name (as appearing in IETF specs) to
    // various details about that ciphersuite
    private static final Map<String, SslCipher> CIPHERS;

    // Map of the ciphersuite name (as defined as a nickname in OpenSSL)
    private static final Map<String, SslCipher> CIPHER_NICKNAMES;
    static {
        Map<String, SslCipher> ciphers = new HashMap<>();
        Map<String, SslCipher> cipherNicknames = new HashMap<>();

        initJava8SunDefaultEnabledCipherSuites(ciphers, cipherNicknames);
        initJava8SunDefaultDisabledCipherSuites(ciphers, cipherNicknames);

        CIPHERS = Collections.unmodifiableMap(ciphers);
        CIPHER_NICKNAMES = Collections.unmodifiableMap(cipherNicknames);
    }

    private static final Map<String, List<SslCipher>> CIPHER_STRENGTHS;
    static {
        Map<String, List<SslCipher>> cipherStrengths = CIPHERS.values()
                .stream()
                .collect(Collectors.groupingBy(s -> s.strength.tokenName));

        Stream.of(CipherStrength.values())
                .forEach(s -> cipherStrengths.putIfAbsent(s.tokenName, Collections.emptyList()));

        CIPHER_STRENGTHS = Collections.unmodifiableMap(cipherStrengths);
    }

    private static final Map<String, List<SslCipher>> CIPHER_KEY_EXCHANGES;
    static {
        Map<String, List<SslCipher>> cipherKeyExchanges = CIPHERS.values()
                .stream()
                .collect(Collectors.groupingBy(s -> s.keyExchange.tokenName));

        Stream.of(CipherKeyExchange.values())
                .forEach(s -> cipherKeyExchanges.putIfAbsent(s.tokenName, Collections.emptyList()));

        CIPHER_KEY_EXCHANGES = Collections.unmodifiableMap(cipherKeyExchanges);
    }

    private static final Map<String, List<SslCipher>> CIPHER_AUTHNS;
    static {
        Map<String, List<SslCipher>> cipherAuthns = CIPHERS.values()
                .stream()
                .collect(Collectors.groupingBy(s -> s.authentication.tokenName));

        Stream.of(CipherAuthentication.values())
                .forEach(s -> cipherAuthns.putIfAbsent(s.tokenName, Collections.emptyList()));

        CIPHER_AUTHNS = Collections.unmodifiableMap(cipherAuthns);
    }

    private static final Map<String, List<SslCipher>> CIPHER_ENCRYPTS;
    static {
        Map<String, List<SslCipher>> cipherEncrypts = CIPHERS.values()
                .stream()
                .collect(Collectors.groupingBy(s -> s.encryption.tokenName));

        Stream.of(CipherEncryption.values())
                .forEach(s -> cipherEncrypts.putIfAbsent(s.tokenName, Collections.emptyList()));

        CIPHER_ENCRYPTS = Collections.unmodifiableMap(cipherEncrypts);
    }

    private static final Map<String, List<SslCipher>> CIPHER_MACS;
    static {
        Map<String, List<SslCipher>> cipherMacs = CIPHERS.values()
                .stream()
                .collect(Collectors.groupingBy(s -> s.mac.tokenName));

        Stream.of(CipherMAC.values())
                .forEach(s -> cipherMacs.putIfAbsent(s.tokenName, Collections.emptyList()));

        CIPHER_MACS = Collections.unmodifiableMap(cipherMacs);
    }

    private static final Map<String, List<SslCipher>> CIPHER_PROTOCOLS;
    static {
        Map<String, List<SslCipher>> cipherProtocols = CIPHERS.values()
                .stream()
                .collect(Collectors.groupingBy(s -> s.protocolVersion.tokenName));

        Stream.of(CipherProtocolVersion.values())
                .forEach(s -> cipherProtocols.putIfAbsent(s.tokenName, Collections.emptyList()));

        CIPHER_PROTOCOLS = Collections.unmodifiableMap(cipherProtocols);
    }

    private static final List<SslCipher> CIPHER_FIPS;
    static {
        List<SslCipher> fips = CIPHERS.values()
                .stream()
                .filter(s -> s.fips)
                .collect(Collectors.toList());

        CIPHER_FIPS = Collections.unmodifiableList(fips);
    }

    // Java 8 Sun provider's default enabled cipher suites
    // https://docs.oracle.com/javase/8/docs/technotes/guides/security/SunProviders.html
    private static void initJava8SunDefaultEnabledCipherSuites(Map<String, SslCipher> ciphers,
                                                 Map<String, SslCipher> ciphersByNickname) {
        SslCipher cipher;

        // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
        cipher = new SslCipher("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384",
                "ECDHE-ECDSA-AES256-SHA384", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDHE,
                CipherAuthentication.ECDSA,
                CipherEncryption.AES256,
                CipherMAC.SHA384,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
        cipher = new SslCipher("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384",
                "ECDHE-RSA-AES256-SHA384", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDHE,
                CipherAuthentication.RSA,
                CipherEncryption.AES256,
                CipherMAC.SHA384,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_RSA_WITH_AES_256_CBC_SHA256
        cipher = new SslCipher("TLS_RSA_WITH_AES_256_CBC_SHA256",
                "AES256-SHA256", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.RSA,
                CipherAuthentication.RSA,
                CipherEncryption.AES256,
                CipherMAC.SHA256,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
        cipher = new SslCipher("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384",
                "ECDH-ECDSA-AES256-SHA384", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDH_ECDSA,
                CipherAuthentication.ECDH,
                CipherEncryption.AES256,
                CipherMAC.SHA384,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
        cipher = new SslCipher("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384",
                "ECDH-RSA-AES256-SHA384", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDH_RSA,
                CipherAuthentication.ECDH,
                CipherEncryption.AES256,
                CipherMAC.SHA384,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
        cipher = new SslCipher("TLS_DHE_RSA_WITH_AES_256_CBC_SHA256",
                "DHE-RSA-AES256-SHA256", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.DHE,
                CipherAuthentication.RSA,
                CipherEncryption.AES256,
                CipherMAC.SHA256,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
        cipher = new SslCipher("TLS_DHE_DSS_WITH_AES_256_CBC_SHA256",
                "DHE-DSS-AES256-SHA256", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.DHE,
                CipherAuthentication.DSS,
                CipherEncryption.AES256,
                CipherMAC.SHA256,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
        cipher = new SslCipher("TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
                "ECDHE-ECDSA-AES256-SHA", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDHE,
                CipherAuthentication.ECDSA,
                CipherEncryption.AES256,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
        cipher = new SslCipher("TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
                "ECDHE-RSA-AES256-SHA", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDHE,
                CipherAuthentication.RSA,
                CipherEncryption.AES256,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_RSA_WITH_AES_256_CBC_SHA
        cipher = new SslCipher("TLS_RSA_WITH_AES_256_CBC_SHA",
                "AES256-SHA", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.RSA,
                CipherAuthentication.RSA,
                CipherEncryption.AES256,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
        cipher = new SslCipher("TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA",
                "ECDH-ECDSA-AES256-SHA", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDH_ECDSA,
                CipherAuthentication.ECDH,
                CipherEncryption.AES256,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
        cipher = new SslCipher("TLS_ECDH_RSA_WITH_AES_256_CBC_SHA",
                "ECDH-RSA-AES256-SHA", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDH_RSA,
                CipherAuthentication.ECDH,
                CipherEncryption.AES256,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_DHE_RSA_WITH_AES_256_CBC_SHA
        cipher = new SslCipher("TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
                "DHE-RSA-AES256-SHA", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.DHE,
                CipherAuthentication.RSA,
                CipherEncryption.AES256,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_DHE_DSS_WITH_AES_256_CBC_SHA
        cipher = new SslCipher("TLS_DHE_DSS_WITH_AES_256_CBC_SHA",
                "DHE-DSS-AES256-SHA", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.DHE,
                CipherAuthentication.DSS,
                CipherEncryption.AES256,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
        cipher = new SslCipher("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256",
                "ECDHE-ECDSA-AES128-SHA256", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDHE,
                CipherAuthentication.ECDSA,
                CipherEncryption.AES128,
                CipherMAC.SHA256,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
        cipher = new SslCipher("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256",
                "ECDHE-RSA-AES128-SHA256", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDHE,
                CipherAuthentication.RSA,
                CipherEncryption.AES128,
                CipherMAC.SHA256,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_RSA_WITH_AES_128_CBC_SHA256
        cipher = new SslCipher("TLS_RSA_WITH_AES_128_CBC_SHA256",
                "AES128-SHA256", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.RSA,
                CipherAuthentication.RSA,
                CipherEncryption.AES128,
                CipherMAC.SHA256,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
        cipher = new SslCipher("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256",
                "ECDH-ECDSA-AES128-SHA256", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDH_ECDSA,
                CipherAuthentication.ECDH,
                CipherEncryption.AES128,
                CipherMAC.SHA256,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
        cipher = new SslCipher("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256",
                "ECDH-RSA-AES128-SHA256", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDH_RSA,
                CipherAuthentication.ECDH,
                CipherEncryption.AES128,
                CipherMAC.SHA256,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
        cipher = new SslCipher("TLS_DHE_RSA_WITH_AES_128_CBC_SHA256",
                "DHE-RSA-AES128-SHA256", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.DHE,
                CipherAuthentication.RSA,
                CipherEncryption.AES128,
                CipherMAC.SHA256,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
        cipher = new SslCipher("TLS_DHE_DSS_WITH_AES_128_CBC_SHA256",
                "DHE-DSS-AES128-SHA256", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.DHE,
                CipherAuthentication.DSS,
                CipherEncryption.AES128,
                CipherMAC.SHA256,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
        cipher = new SslCipher("TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
                "ECDHE-ECDSA-AES128-SHA", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDHE,
                CipherAuthentication.ECDSA,
                CipherEncryption.AES128,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
        cipher = new SslCipher("TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
                "ECDHE-RSA-AES128-SHA", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDHE,
                CipherAuthentication.RSA,
                CipherEncryption.AES128,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);


        // TLS_RSA_WITH_AES_128_CBC_SHA
        cipher = new SslCipher("TLS_RSA_WITH_AES_128_CBC_SHA",
                "AES128-SHA", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.RSA,
                CipherAuthentication.RSA,
                CipherEncryption.AES128,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);


        // TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
        cipher = new SslCipher("TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA",
                "ECDH-ECDSA-AES128-SHA", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDH_ECDSA,
                CipherAuthentication.ECDH,
                CipherEncryption.AES128,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
        cipher = new SslCipher("TLS_ECDH_RSA_WITH_AES_128_CBC_SHA",
                "ECDH-RSA-AES128-SHA", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDH_RSA,
                CipherAuthentication.ECDH,
                CipherEncryption.AES128,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_DHE_RSA_WITH_AES_128_CBC_SHA
        cipher = new SslCipher("TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
                "DHE-RSA-AES128-SHA", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.DHE,
                CipherAuthentication.RSA,
                CipherEncryption.AES128,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_DHE_DSS_WITH_AES_128_CBC_SHA
        cipher = new SslCipher("TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
                "DHE-DSS-AES128-SHA", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.DHE,
                CipherAuthentication.DSS,
                CipherEncryption.AES128,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDHE_ECDSA_WITH_RC4_128_SHA
        cipher = new SslCipher("TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
                "ECDHE-ECDSA-RC4-SHA", 128,
                CipherStrength.MEDIUM,
                CipherKeyExchange.ECDHE,
                CipherAuthentication.ECDSA,
                CipherEncryption.RC4,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                false);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDHE_RSA_WITH_RC4_128_SHA
        cipher = new SslCipher("TLS_ECDHE_RSA_WITH_RC4_128_SHA",
                "ECDHE-RSA-RC4-SHA", 128,
                CipherStrength.MEDIUM,
                CipherKeyExchange.ECDHE,
                CipherAuthentication.RSA,
                CipherEncryption.RC4,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                false);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // SSL_RSA_WITH_RC4_128_SHA
        cipher = new SslCipher("SSL_RSA_WITH_RC4_128_SHA",
                "RC4-SHA", 128,
                CipherStrength.MEDIUM,
                CipherKeyExchange.RSA,
                CipherAuthentication.RSA,
                CipherEncryption.RC4,
                CipherMAC.SHA1,
                CipherProtocolVersion.SSLV3,
                false);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDH_ECDSA_WITH_RC4_128_SHA
        cipher = new SslCipher("TLS_ECDH_ECDSA_WITH_RC4_128_SHA",
                "ECDH-ECDSA-RC4-SHA", 128,
                CipherStrength.MEDIUM,
                CipherKeyExchange.ECDH_ECDSA,
                CipherAuthentication.ECDH,
                CipherEncryption.RC4,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                false);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDH_RSA_WITH_RC4_128_SHA
        cipher = new SslCipher("TLS_ECDH_RSA_WITH_RC4_128_SHA",
                "ECDH-RSA-RC4-SHA", 128,
                CipherStrength.MEDIUM,
                CipherKeyExchange.ECDH_RSA,
                CipherAuthentication.ECDH,
                CipherEncryption.RC4,
                CipherMAC.SHA1,
                CipherProtocolVersion.TLSV1,
                false);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
        cipher = new SslCipher("TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384",
                "ECDHE-ECDSA-AES256-GCM-SHA384", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDHE,
                CipherAuthentication.ECDSA,
                CipherEncryption.AESGCM256,
                CipherMAC.AEAD,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
        cipher = new SslCipher("TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256",
                "ECDHE-ECDSA-AES128-GCM-SHA256", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDHE,
                CipherAuthentication.ECDSA,
                CipherEncryption.AESGCM128,
                CipherMAC.SHA256,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
        cipher = new SslCipher("TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
                "ECDHE-RSA-AES256-GCM-SHA384", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDHE,
                CipherAuthentication.RSA,
                CipherEncryption.AESGCM256,
                CipherMAC.AEAD,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_RSA_WITH_AES_256_GCM_SHA384
        cipher = new SslCipher("TLS_RSA_WITH_AES_256_GCM_SHA384",
                "AES256-GCM-SHA384", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.RSA,
                CipherAuthentication.RSA,
                CipherEncryption.AESGCM256,
                CipherMAC.AEAD,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384
        // openssl doesn't have it

        // TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384
        // openssl doesn't have it

        // TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
        cipher = new SslCipher("TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
                "DHE-RSA-AES256-GCM-SHA384", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.DHE,
                CipherAuthentication.RSA,
                CipherEncryption.AESGCM256,
                CipherMAC.AEAD,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
        cipher = new SslCipher("TLS_DHE_DSS_WITH_AES_256_GCM_SHA384",
                "DHE-DSS-AES256-GCM-SHA384", 256,
                CipherStrength.HIGH,
                CipherKeyExchange.DHE,
                CipherAuthentication.DSS,
                CipherEncryption.AESGCM256,
                CipherMAC.AEAD,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
        cipher = new SslCipher("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
                "ECDHE-RSA-AES128-GCM-SHA256", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.ECDHE,
                CipherAuthentication.RSA,
                CipherEncryption.AESGCM128,
                CipherMAC.AEAD,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_RSA_WITH_AES_128_GCM_SHA256
        cipher = new SslCipher("TLS_RSA_WITH_AES_128_GCM_SHA256",
                "AES128-GCM-SHA256", 128,
                CipherStrength.HIGH,
                CipherKeyExchange.RSA,
                CipherAuthentication.RSA,
                CipherEncryption.AESGCM128,
                CipherMAC.AEAD,
                CipherProtocolVersion.TLSV1_2,
                true);
        ciphers.putIfAbsent(cipher.name, cipher);
        ciphersByNickname.putIfAbsent(cipher.nickname, cipher);

        // TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256
