OpenSSL Valhalla Rampage

26 juin 2014, Pas Sage en Seine


  1. du #LOL
  2. du #WTF
  3. du #WAT
  4. du #Facepalm
  5. du #fun


Do you notice something ?

— What do you do during party ?

— Crypto, what else ?

Happy new year !!!! (…Hips…)


typedef struct ssl3_record_st {
  unsigned int length;	/* How many bytes available */
  unsigned char *data;	/* pointer to the record data */

int dtls1_process_heartbeat(SSL *s) {
  unsigned char *p = &s->s3->[0], *pl;
  unsigned int payload;
  unsigned int padding = 16; /* Use minimum padding */

  /* Read type and payload length first */
  hbtype = *p++; n2s(p, payload); pl = p;

  unsigned char *buffer, *bp;
  /* Allocate memory for the response, size is 1 byte
   * message type, plus 2 bytes payload length, plus
   * payload, plus padding
  buffer = OPENSSL_malloc(1 + 2 + payload + padding); bp = buffer;

  /* Enter response type, length and copy payload */
  *bp++ = TLS1_HB_RESPONSE; s2n(payload, bp); memcpy(bp, pl, payload);
Never ever trust user input…

Not invented here syndrom

System malloc ? Not enough secure !

static void *(*malloc_func)(size_t)
  = malloc

static void *default_malloc_ex(size_t num, const char *file, int line) {
  return malloc_func(num);

static void *(*malloc_ex_func)(size_t, const char *file, int line)
  = default_malloc_ex;

#define OPENSSL_malloc(num)	CRYPTO_malloc((int)num,__FILE__,__LINE__)

System malloc ? Not enough secure !

unsigned char cleanse_ctr = 0
void *CRYPTO_malloc(int num, const char *file, int line) {
  void *ret = NULL;
  if (num <= 0) return NULL;
  allow_customize = 0;
  if (malloc_debug_func != NULL) {
	allow_customize_debug = 0;
	malloc_debug_func(NULL, num, file, line, 0);
  ret = malloc_ex_func(num,file,line);
  fprintf(stderr, "LEVITTE_DEBUG_MEM:		 > 0x%p (%d)\n", ret, num);
  if (malloc_debug_func != NULL)
	malloc_debug_func(ret, num, file, line, 1);
	/* Create a dependency on the value of 'cleanse_ctr' so our memory
	 * sanitisation function can't be optimised out. NB: We only do
	 * this for >2Kb so the overhead doesn't bother us. */
	if(ret && (num > 2048)) {
	  extern unsigned char cleanse_ctr;
	  ((unsigned char *)ret)[0] = cleanse_ctr;
  return ret;

System malloc ? Not enough secure !

  • Remove all standard heap/stack protection

Parameters : by reference, by value or by copy ?

None of them, by mem alloc !

int ssl3_setup_read_buffer(SSL *s) {
    if (s->s3->rbuf.buf == NULL) {
        if ((p=freelist_extract(s->ctx, 1, len)) == NULL)
            goto err;
        s->s3->rbuf.buf = p;

int ssl3_release_read_buffer(SSL *s) {
    if (s->s3->rbuf.buf != NULL) {
        freelist_insert(s->ctx, 1, s->s3->rbuf.len, s->s3->rbuf.buf);
        s->s3->rbuf.buf = NULL;

int ssl3_read_bytes(SSL *s, int type, unsigned char *buf, int len, int peek) {
    if (s->s3->rbuf.buf == NULL) /* Not initialized yet */
        if (!ssl3_setup_read_buffer(s))
    if (!peek) {
        if (s->mode & SSL_MODE_RELEASE_BUFFERS)

OpenSSL thought that they were clever enough

to over-engineer their own bzero

unsigned char cleanse_ctr = 0;
void OPENSSL_cleanse(void *ptr, size_t len) {
  unsigned char *p = ptr;
  size_t loop = len, ctr = cleanse_ctr;
  while (loop--) {
	  *(p++) = (unsigned char)ctr;
	  ctr += (17 + ((size_t)p & 0xF));
  p = memchr(ptr, (unsigned char)ctr, len);
  if (p)
	ctr += (63 + (size_t)p);
  cleanse_ctr = (unsigned char)ctr;
void OPENSSL_cleanse(void *ptr, size_t len) {
  explicit_bzero(ptr, len);

memcmp & strcasecmp are just like malloc : too « slow » and safe

int OPENSSL_strcasecmp(const char *str1, const char *str2) {
#if defined(OPENSSL_IMPLEMENTS_strncasecmp)
  return OPENSSL_strncasecmp(str1, str2, (size_t) - 1);
  return strcasecmp(str1, str2);

int OPENSSL_memcmp(const void *v1, const void *v2, size_t n) {
  const unsigned char *c1 = v1, *c2 = v2;
  int ret = 0;
  while (n && (ret = *c1 - *c2) == 0) n--, c1++, c2++;
  return ret;

int OPENSSL_strncasecmp(const char *str1, const char *str2, size_t n) {
#if defined(OPENSSL_IMPLEMENTS_strncasecmp)
  while (*str1 && *str2 && n) {
	int res = toupper(*str1) - toupper(*str2);
	if (res) return res < 0 ? -1 : 1;
	str1++; str2++; n--;
  if (n == 0)
	return 0;
  if (*str1)
	return 1;
  if (*str2)
	return -1;
  return 0;
  /* Recursion hazard warning! Whenever strncasecmp is #defined as
   * OPENSSL_strncasecmp, OPENSSL_IMPLEMENTS_strncasecmp must be
   * defined as well. */
   return strncasecmp(str1, str2, n);
Note the hazard warning comment…

Security ?

What is that ?

Hey Dude, my PRNG is not yet initialized !

Well, even if time() isn't random

your RSA private key is probably pretty random…

BN_BLINDING *RSA_setup_blinding(RSA *rsa, BN_CTX *in_ctx) {
  if ((RAND_status() == 0) && rsa->d != NULL && rsa->d->d != NULL) {
	/* if PRNG is not properly seeded, resort to secret
	 * exponent as unpredictable seed */
	RAND_add(rsa->d->d, rsa->d->dmax * sizeof rsa->d->d[0], 0.0);

Digests are probably pretty random too;

let's toss those into the rng too !

int DSA_sign(int type, const unsigned char *dgst, int unsigned int *siglen, DSA *dsa) {
  RAND_seed(dgst, dlen);
PRNG is pluggable

Erasing AES cipher context ?

Headers is enough, bro !

void aes_gcm_cleanup(EVP_CIPHER_CTX *c) {
	EVP_AES_GCM_CTX *gctx = c->cipher_data;
	OPENSSL_cleanse(&gctx->gcm, sizeof(gctx->gcm));
Doesn't clean up the AES key !
void aes_gcm_cleanup(EVP_CIPHER_CTX *c) {
	EVP_AES_GCM_CTX *gctx = c->cipher_data;
	OPENSSL_cleanse(gctx, sizeof(*gctx));

No one will use OpenSSL beyond 2038…

ssl/ssl.h:	  long time;
ssl/ssl.h:long  SSL_SESSION_get_time(const SSL_SESSION *s);
ssl/ssl.h:long  SSL_SESSION_set_time(SSL_SESSION *s, long t);
ssl/ssl_sess.c: ss->time=(unsigned long)time(NULL);
ssl/ssl_sess.c: if (ret->timeout < (long)(time(NULL) - ret->time)) /* timeout */
ssl/ssl_sess.c:long SSL_SESSION_get_time(const SSL_SESSION *s)
ssl/ssl_sess.c:long SSL_SESSION_set_time(SSL_SESSION *s, long t)
ssl/ssl_sess.c: long time;
crypto/o_time.c:		long time_jd;
crypto/o_time.c:		long time_jd;
apps/s_time.c:  long finishtime=0;
apps/s_time.c:  finishtime=(long)time(NULL)+maxTime;
apps/s_time.c:		  if (finishtime < (long)time(NULL)) break;
apps/s_time.c:  i=(int)((long)time(NULL)-finishtime+maxTime);
apps/s_time.c:  printf( "%d connections in %ld real seconds,
	 %ld bytes read per connection\n",nConn,
apps/s_time.c:  finishtime=(long)time(NULL)+maxTime;
apps/s_time.c:		  if (finishtime < (long)time(NULL)) break;
apps/s_time.c:  printf( "%d connections in %ld real seconds,
	 %ld bytes read per connection\n",nConn,



  • « strncpy(dest, src, strlen(src)) is safe, right ? »
    • char* is confusing, 0-terminated string, random string, binary buffer ?
    • → memcpy(dest, src, len)
  • « When printf() just isn't good enough… implement your own ! »
    • 842 lignes…
    • Float math
  • /* HAS BUGS! DON'T USE - this is only present for use in des.c */
  • 3DES, 168 bits strength declared, 112 bits in real !
  • #ifndef OPENSSL_NO_TLS

Yeah, I'm sure I'll have an account on everybox this software gets used on !

$ rgrep /home/eay
test/methtest.c:		METH_arg(tmp2,METH_TYPE_DIR,"/home/eay/.CAcerts");
test/methtest.c:		METH_arg(tmp2,METH_TYPE_DIR,"/home/eay/SSLeay/certs");
test/methtest.c:		METH_arg(tmp,METH_TYPE_DIR,"/home/eay/.mycerts");
test/methtest.c:		METH_arg(tmp,METH_TYPE_FILE,"/home/eay/.mycerts/primary.pem");
I wonder when these tests were last used…


12 years ago, old_des.h was used to provide compatibility with libdes.

The man page says “Compatibility des_ functions are provided for a short while” and indeed even the original commit message says “The compatibility functions will be removed in some future release, at the latest in version 1.0.” So here we are, a short while later.

Now I’ve only been an OpenBSD developer for 11 years, one year less than this header has existed, but in that brief time, I’ve learned a thing or two about deleting obsolete code. It doesn’t delete itself. And worse, people will continue using it until you force them onto a better path.

— tedu (Ted Unangst)

So the OpenSSL codebase does “get the time, add it as a random seed” in a bunch of places inside the TLS engine, to try to keep entropy high. I wonder if their moto is “If you can’t solve a problem, at least try to do it badly

– theo (Theo de Raadt)

Ok, there was a need for OPENSSL_cleanse() instead of bzero() to prevent supposedly smart compilers from optimizing memory cleanups away. Understood.

Ok, in case of an hypothetically super smart compiler, OPENSSL_cleanse() had to be convoluted enough for the compiler not to recognize that this was actually bzero() in disguise. Understood.

But then why there had been optimized assembler versions of OPENSSL_cleanse() is beyond me. Did someone not trust the C obfuscation?

– miod (Miod Vallat)

Remove unused ssl utils

This code is the reason perl has a name as a write only language.

– afresh1 (Andrew Fresh)

Do you really want to build OpenSSL for 16-bit Windows? Well, we don’t.

– jsing (Joel Sing)

Remove non-posix support. Why is OPENSSL_isservice even here? Is this a crypto library or a generic platform abstraction library? “A hack to make Visual C++ 5.0 work correctly” … time to upgrade.

— tedu (Ted Unangst)

Nuke OPENSSL_NO_SOCK since any half sane operating system has sockets.

— jsing (Joel Sing)

T.61 was proposed in 93. Utf8 later the same year. utf8 was recommended from 94. 2004 OpenSSL caught up with the recommendation, and decided to go against it to be compatible with Netscape Navigator. Which at that time had a massive 2% of the market. 2005 The behaviour of the openssl binaries were "fixed" by changing the config file. 2014 the default still hasn't been changed, 20 years after the original deprecation of T.61 in x509 standards. I love the speed with which this evolves.

— spider (D. Spindel)

Thanks OpenSSL !

We love you anyway ♥

Somewhere else : Tor

.onion address specification
H  = SHA1(DER(public_key))
H' = H[0..10]
address = base32(H')[0..16]

Somewhere else : Tor

.onion address naive implementation
H  = SHA1(DER(public_key))
H' = H[0..10]
address = base32(H')[0..16]

Somewhere else : Tor

.onion address real implementation…
int crypto_digest(char *digest, const char *m, size_t len) {
  return (SHA1((const unsigned char*)m,len,(unsigned char*)digest) == NULL);
void base32_encode(char *dest, size_t destlen, const char *src, size_t srclen) {
    unsigned int i, v, u;
    size_t nbits = srclen * 8, bit;

    tor_assert(srclen < SIZE_T_CEILING/8);
    tor_assert((nbits%5) == 0); /* We need an even multiple of 5 bits. */
    tor_assert((nbits/5)+1 <= destlen); /* We need enough space. */
    tor_assert(destlen < SIZE_T_CEILING);

    for (i=0,bit=0; bit < nbits; ++i, bit+=5) {
	  /* set v to the 16-bit value starting at src[bits/8], 0-padded. */
	  v = ((uint8_t)src[bit/8]) << 8;
	  if (bit+5 < nbits) v += (uint8_t)src[(bit/8)+1];
	  /* set u to the 5-bit value at the bit'th bit of src. */
	  u = (v >> (11-(bit%8))) & 0x1F;
	  dest[i] = BASE32_CHARS[u];
    dest[i] = '\0';
int crypto_pk_get_digest(crypto_pk_t *pk, char *digest_out) {
    unsigned char *buf = NULL;
    int len;

    len = i2d_RSAPublicKey(pk->key, &buf);
    if (len < 0 || buf == NULL)
        return -1;
    if (crypto_digest(digest_out, (char*)buf, len) < 0) {
        return -1;
    return 0;
#define DIGEST_LEN 20

int rend_get_service_id(crypto_pk_t *pk, char *out) {
  char buf[DIGEST_LEN];
  if (crypto_pk_get_digest(pk, buf) < 0)
    return -1;
  base32_encode(out, REND_SERVICE_ID_LEN_BASE32+1, buf, REND_SERVICE_ID_LEN);
  return 0;