Recently at work we were looking into Forward Secrecy (FS). We were using Qualys SSL Server Test and noticed that Forward Secrecy was showing as NO. We decided to look into this because we want to use the most robust security we can.

What we found was none of the supported cipher suites showed Diffie–Hellman (DH) or Elliptic curve Diffie–Hellman (ECDH) which is required for FS. We had a proper cipher suite in place and we were asking for ciphers that support FS. Yet the SSL Server Test was not showing those ciphers as available.

Further investigation revealed that OpenSSL does not by default enable DH or ECDH. You must manually enable these otherwise DH and ECDH ciphers will be ignored. There is no error or warning when this happens.

Finally, we found a wiki page with the answer. For DH the key is to pre-compute a DH parameters and set the ssl contexts dh to the parameters. For ECDH you need to load and set a compatible curve.

Here is an example for generating and loading DH and ECDH.

/* Generated by "openssl dhparam -C 2236" */
static DH *M_net_ssl_get_dh2236(void)
{
    static unsigned char dh2236_p[] = {
        0x09,0x5B,0xED,0x9D,0x7B,0xA5,0xB8,0xF7,0xAE,0x67,0x01,0xCD,
        0xE9,0x48,0x9A,0xAD,0x97,0xE6,0x38,0x6C,0x66,0x33,0x93,0xBD,
        0x3E,0x2C,0x59,0x1E,0xB4,0x34,0x3C,0xDB,0xE3,0x3E,0xC2,0x4F,
        0xFB,0xC4,0x5F,0x91,0x07,0x1A,0xF2,0xDB,0xDB,0xFC,0xA4,0x5D,
        0x75,0xBB,0x28,0x72,0x98,0xFE,0x65,0x75,0x9B,0x44,0xB3,0xE1,
        0x20,0xB2,0x40,0xA1,0xE6,0x20,0x4E,0x0F,0x8B,0xD2,0x8A,0xAB,
        0x1A,0xB7,0x23,0x57,0xF1,0x44,0xBC,0x82,0xB9,0x5A,0x3E,0x56,
        0xEE,0x15,0x90,0x2E,0x23,0xF3,0x30,0x12,0x42,0xE9,0x24,0x34,
        0x48,0xF0,0x37,0x97,0x96,0x5C,0xBA,0x63,0x85,0x35,0x66,0xCB,
        0xF9,0x85,0x8E,0xDD,0xEA,0xE2,0xD0,0x5B,0xA3,0xDB,0x7E,0x6F,
        0x44,0x2F,0x54,0xBC,0x99,0x8F,0x85,0xD2,0x0A,0x50,0xE3,0x74,
        0x40,0xEF,0xDF,0x2E,0x85,0xC8,0x1B,0x68,0xDE,0x38,0x12,0x9A,
        0xE7,0x63,0x32,0x73,0x1F,0x7D,0xB1,0xCB,0xCF,0x9A,0xDA,0xCE,
        0xD2,0x02,0xC7,0xC1,0xE3,0xE8,0xFA,0xFD,0x2E,0xAF,0xE5,0x7E,
        0xD3,0x7B,0xD8,0xFC,0x0D,0x2E,0x40,0xC4,0x4F,0xB3,0xD9,0xFB,
        0xF4,0x79,0x3E,0xA9,0xF5,0xEC,0xC3,0xE0,0x88,0xC6,0x90,0xC0,
        0x53,0x40,0xBF,0x7C,0xA5,0xEF,0x29,0xFE,0xBD,0x2E,0x27,0xC7,
        0x5A,0xFB,0xD6,0x21,0x5E,0xEB,0x50,0xF6,0x98,0x7E,0x1E,0x19,
        0x1D,0x1D,0xF3,0xE5,0x9E,0x5A,0x1C,0x43,0x92,0x0C,0x55,0xF0,
        0x5B,0xAA,0x96,0x7A,0x4C,0x1E,0xF0,0xF6,0xDC,0x5C,0xF2,0xCE,
        0x95,0xC1,0x4B,0x92,0x34,0xBC,0x99,0xAB,0x33,0xFF,0x80,0x78,
        0xA8,0x47,0x24,0xEF,0xE8,0x7B,0x4F,0x50,0xCE,0x42,0x9D,0x47,
        0x2D,0x36,0x99,0xC2,0x1D,0x9B,0x36,0x34,0xA7,0xFC,0xFC,0x50,
        0xCD,0x41,0xC8,0x8B,
    };
    static unsigned char dh2236_g[] = {
        0x02,
    };
    DH *dh;

    if ((dh=DH_new()) == NULL)
        return NULL;
    dh->p = BN_bin2bn(dh2236_p, sizeof(dh2236_p), NULL);
    dh->g = BN_bin2bn(dh2236_g, sizeof(dh2236_g), NULL);
    if (dh->p == NULL || dh->g == NULL) {
        DH_free(dh);
        return NULL;
    }
    return dh;
}

create_ctx(void)
{
    SSL_CTX *ctx;
    EC_KEY  *ecdh;
    DH      *dh;
...
    ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);
    if (ecdh == NULL) {
        printf("Failed to get EC curve");
        SSL_CTX_free(ctx);
        return NULL;
    }
    SSL_CTX_set_tmp_ecdh(ctx, ecdh);
    EC_KEY_free(ecdh); /* Safe because of reference counts */

    /* Use static DH parameters.  This logic comes from:
     * http://en.wikibooks.org/wiki/OpenSSL/Diffie-Hellman_parameters
     * And OpenSSL's docs say "Application authors may compile in DH parameters."
     */
    dh = M_net_ssl_get_dh2236();
    if (dh == NULL) {
        printf("Failed to get DH params");
        SSL_CTX_free(ctx);
        return NULL;
    }
    SSL_CTX_set_tmp_dh(ctx, dh);
    DH_free(dh); /* Safe because of reference counts */
...
    return ctx;
}

It’s acceptable to generate random DH params and embed them into the resultant binary as above. Also realize that the above generates 2236 bit DH params. This is acceptable for secure deployments but the bit length is important. Java 6 for example cannot handle a bit length above 1024. That said, Java 6 (while still in use) is 2 major versions behind and no longer publicly supported by Oracle so for most projects it’s acceptable not to reduce security to support it.