CVE-2018-16150: Improper Verification of Cryptographic Signature (CWE-347)
value: In sig_verify() in x509.c in axTLS version 2.1.3 and before, the PKCS#1 v1.5 signature verification does not reject excess data after the hash value. Consequently, a remote attacker can forge signatures when small public exponents are being used, which could lead to impersonation through fake X.509 certificates. This is a variant of CVE-2006-4340.
CWE description: The product does not verify, or incorrectly verifies, the cryptographic signature for data.
CVSS v3 base score: 5.9
Published: 2018-11-07T20:29Z
Repository: https://github.com/igrr/axtls-8266
Language: C
Files changed: ssl/os_port.h, ssl/x509.c
Commit date: 2018-10-23 06:57:00 +0800
Commit message: Apply CVE fixes for X509 parsing

Apply patches developed by Sze Yiu which correct a vulnerability in
X509 parsing.  See CVE-2018-16150 and CVE-2018-16149 for more info.

Diff: diff --git a/ssl/os_port.h b/ssl/os_port.h
index b67066c..e0b9e46 100644
--- a/ssl/os_port.h
+++ b/ssl/os_port.h
@@ -140,10 +140,22 @@ static inline void* memcpy_P(void* dest, PGM_VOID_P src, size_t count) {
 static inline int strlen_P(const char *str) {
     int cnt = 0;
     while (pgm_read_byte(str++)) cnt++;
     return cnt;
 }
+static inline int memcmp_P(const void *a1, const void *b1, size_t len) {
+    const uint8_t* a = (const uint8_t*)(a1);
+    uint8_t* b = (uint8_t*)(b1);
+    for (size_t i=0; i<len; i++) {
+        uint8_t d = pgm_read_byte(a) - pgm_read_byte(b);
+        if (d) return d;
+        a++;
+        b++;
+    }
+    return 0;
+}
+
 #define printf(fmt, ...) do { static const char fstr[] PROGMEM = fmt; char rstr[sizeof(fmt)]; memcpy_P(rstr, fstr, sizeof(rstr)); ets_printf(rstr, ##__VA_ARGS__); } while (0)
 #define strcpy_P(dst, src) do { static const char fstr[] PROGMEM = src; memcpy_P(dst, fstr, sizeof(src)); } while (0)
 
 // Copied from ets_sys.h to avoid compile warnings
 extern int ets_printf(const char *format, ...)  __attribute__ ((format (printf, 1, 2)));
diff --git a/ssl/x509.c b/ssl/x509.c
index 3338c58..a51b948 100644
--- a/ssl/x509.c
+++ b/ssl/x509.c
@@ -47,32 +47,10 @@ static int x509_v3_subject_alt_name(const uint8_t *cert, int offset,
         X509_CTX *x509_ctx);
 static int x509_v3_basic_constraints(const uint8_t *cert, int offset, 
         X509_CTX *x509_ctx);
 static int x509_v3_key_usage(const uint8_t *cert, int offset, 
         X509_CTX *x509_ctx);
-
-/**
- * Retrieve the signature from a certificate.
- */
-static const uint8_t *get_signature(const uint8_t *asn1_sig, int *len)
-{
-    int offset = 0;
-    const uint8_t *ptr = NULL;
-
-    if (asn1_next_obj(asn1_sig, &offset, ASN1_SEQUENCE) < 0 || 
-            asn1_skip_obj(asn1_sig, &offset, ASN1_SEQUENCE))
-        goto end_get_sig;
-
-    if (asn1_sig[offset++] != ASN1_OCTET_STRING)
-        goto end_get_sig;
-    *len = get_asn1_length(asn1_sig, &offset);
-    ptr = &asn1_sig[offset];          /* all ok */
-
-end_get_sig:
-    return ptr;
-}
-
 #endif
 
 /**
  * Construct a new x509 object.
  * @return 0 if ok. < 0 if there was a problem.
@@ -410,46 +388,94 @@ void x509_free(X509_CTX *x509_ctx)
     free(x509_ctx);
     x509_free(next);        /* clear the chain */
 }
 
 #ifdef CONFIG_SSL_CERT_VERIFICATION
+static const uint8_t sig_prefix_md5[] PROGMEM = {0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10};
+static const uint8_t sig_prefix_sha1[] PROGMEM = {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14};
+static const uint8_t sig_prefix_sha256[] PROGMEM = {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
+static const uint8_t sig_prefix_sha384[] PROGMEM = {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30};
+static const uint8_t sig_prefix_sha512[] PROGMEM = {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40};
+
 /**
  * Take a signature and decrypt it.
  */
-static bigint *sig_verify(BI_CTX *ctx, const uint8_t *sig, int sig_len,
+static bigint *sig_verify(BI_CTX *ctx, const uint8_t *sig, int sig_len, uint8_t sig_type,
         bigint *modulus, bigint *pub_exp)
 {
-    int i, size;
+    int i;
     bigint *decrypted_bi, *dat_bi;
     bigint *bir = NULL;
     uint8_t *block = (uint8_t *)malloc(sig_len);
 
+    const uint8_t *sig_prefix = NULL;
+    uint8_t sig_prefix_size = 0, hash_len = 0;
+    /* adjust our expections */
+    switch (sig_type)
+    {
+        case SIG_TYPE_MD5:
+            sig_prefix = sig_prefix_md5;
+            sig_prefix_size = sizeof(sig_prefix_md5);
+        break;
+        case SIG_TYPE_SHA1:
+            sig_prefix = sig_prefix_sha1;
+            sig_prefix_size = sizeof(sig_prefix_sha1);
+        break;
+        case SIG_TYPE_SHA256:
+            sig_prefix = sig_prefix_sha256;
+            sig_prefix_size = sizeof(sig_prefix_sha256);
+        break;
+        case SIG_TYPE_SHA384:
+            sig_prefix = sig_prefix_sha384;
+            sig_prefix_size = sizeof(sig_prefix_sha384);
+        break;
+        case SIG_TYPE_SHA512:
+            sig_prefix = sig_prefix_sha512;
+            sig_prefix_size = sizeof(sig_prefix_sha512);
+        break;
+    }
+    if (sig_prefix)
+        hash_len = sig_prefix[sig_prefix_size - 1];
+
+    /* check length (#A) */
+    if (sig_len < 2 + 8 + 1 + sig_prefix_size + hash_len)
+        goto err;
+
     /* decrypt */
     dat_bi = bi_import(ctx, sig, sig_len);
     ctx->mod_offset = BIGINT_M_OFFSET;
 
     /* convert to a normal block */
     decrypted_bi = bi_mod_power2(ctx, dat_bi, modulus, pub_exp);
 
     bi_export(ctx, decrypted_bi, block, sig_len);
     ctx->mod_offset = BIGINT_M_OFFSET;
 
-    i = 10; /* start at the first possible non-padded byte */
-    while (block[i++] && i < sig_len);
-    size = sig_len - i;
-
-    /* get only the bit we want */
-    if (size > 0)
-    {
-        int len;
-        const uint8_t *sig_ptr = get_signature(&block[i], &len);
+    /* check the first 2 bytes */
+    if (block[0] != 0 || block[1] != 1)
+        goto err;
 
-        if (sig_ptr)
-        {
-            bir = bi_import(ctx, sig_ptr, len);
-        }
+    /* check the padding */
+    i = 2; /* start at the first padding byte */
+    while (i < sig_len - 1 - sig_prefix_size - hash_len)
+    { /* together with (#A), we require at least 8 bytes of padding */
+        if (block[i++] != 0xFF)
+            goto err;
     }
+
+    /* check end of padding */
+    if (block[i++] != 0)
+        goto err;
+
+    /* check the ASN.1 metadata */
+    if (memcmp_P(block+i, sig_prefix, sig_prefix_size))
+        goto err;
+
+    /* now we can get the hash we need */
+    bir = bi_import(ctx, block + i + sig_prefix_size, hash_len);
+
+err:
     free(block);
     /* save a few bytes of memory */
     bi_clear_cache(ctx);
     return bir;
 }
@@ -598,11 +624,11 @@ int x509_verify(const CA_CERT_CTX *ca_cert_ctx, const X509_CTX *cert,
         ret = X509_VFY_ERROR_SELF_SIGNED;
         goto end_verify;
     }
 
     /* check the signature */
-    cert_sig = sig_verify(ctx, cert->signature, cert->sig_len, 
+    cert_sig = sig_verify(ctx, cert->signature, cert->sig_len, cert->sig_type,
                         bi_clone(ctx, mod), bi_clone(ctx, expn));
 
     if (cert_sig && cert->digest)
     {
         if (bi_compare(cert_sig, cert->digest) != 0)

Vulnerable code: 
/**
 * Retrieve the signature from a certificate.
 */
static const uint8_t *get_signature(const uint8_t *asn1_sig, int *len)
{
    int offset = 0;
    const uint8_t *ptr = NULL;

    if (asn1_next_obj(asn1_sig, &offset, ASN1_SEQUENCE) < 0 || 
            asn1_skip_obj(asn1_sig, &offset, ASN1_SEQUENCE))
        goto end_get_sig;

    if (asn1_sig[offset++] != ASN1_OCTET_STRING)
        goto end_get_sig;
    *len = get_asn1_length(asn1_sig, &offset);
    ptr = &asn1_sig[offset];          /* all ok */

end_get_sig:
    return ptr;
}

static bigint *sig_verify(BI_CTX *ctx, const uint8_t *sig, int sig_len,
    int i, size;
    i = 10; /* start at the first possible non-padded byte */
    while (block[i++] && i < sig_len);
    size = sig_len - i;

    /* get only the bit we want */
    if (size > 0)
    {
        int len;
        const uint8_t *sig_ptr = get_signature(&block[i], &len);
        if (sig_ptr)
        {
            bir = bi_import(ctx, sig_ptr, len);
        }
    cert_sig = sig_verify(ctx, cert->signature, cert->sig_len, 
Fixed code: static inline int memcmp_P(const void *a1, const void *b1, size_t len) {
    const uint8_t* a = (const uint8_t*)(a1);
    uint8_t* b = (uint8_t*)(b1);
    for (size_t i=0; i<len; i++) {
        uint8_t d = pgm_read_byte(a) - pgm_read_byte(b);
        if (d) return d;
        a++;
        b++;
    }
    return 0;
}

static const uint8_t sig_prefix_md5[] PROGMEM = {0x30, 0x20, 0x30, 0x0C, 0x06, 0x08, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05, 0x05, 0x00, 0x04, 0x10};
static const uint8_t sig_prefix_sha1[] PROGMEM = {0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0E, 0x03, 0x02, 0x1A, 0x05, 0x00, 0x04, 0x14};
static const uint8_t sig_prefix_sha256[] PROGMEM = {0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20};
static const uint8_t sig_prefix_sha384[] PROGMEM = {0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05, 0x00, 0x04, 0x30};
static const uint8_t sig_prefix_sha512[] PROGMEM = {0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05, 0x00, 0x04, 0x40};

static bigint *sig_verify(BI_CTX *ctx, const uint8_t *sig, int sig_len, uint8_t sig_type,
    int i;
    const uint8_t *sig_prefix = NULL;
    uint8_t sig_prefix_size = 0, hash_len = 0;
    /* adjust our expections */
    switch (sig_type)
    {
        case SIG_TYPE_MD5:
            sig_prefix = sig_prefix_md5;
            sig_prefix_size = sizeof(sig_prefix_md5);
        break;
        case SIG_TYPE_SHA1:
            sig_prefix = sig_prefix_sha1;
            sig_prefix_size = sizeof(sig_prefix_sha1);
        break;
        case SIG_TYPE_SHA256:
            sig_prefix = sig_prefix_sha256;
            sig_prefix_size = sizeof(sig_prefix_sha256);
        break;
        case SIG_TYPE_SHA384:
            sig_prefix = sig_prefix_sha384;
            sig_prefix_size = sizeof(sig_prefix_sha384);
        break;
        case SIG_TYPE_SHA512:
            sig_prefix = sig_prefix_sha512;
            sig_prefix_size = sizeof(sig_prefix_sha512);
        break;
    }
    if (sig_prefix)
        hash_len = sig_prefix[sig_prefix_size - 1];

    /* check length (#A) */
    if (sig_len < 2 + 8 + 1 + sig_prefix_size + hash_len)
        goto err;

    /* check the first 2 bytes */
    if (block[0] != 0 || block[1] != 1)
        goto err;
    /* check the padding */
    i = 2; /* start at the first padding byte */
    while (i < sig_len - 1 - sig_prefix_size - hash_len)
    { /* together with (#A), we require at least 8 bytes of padding */
        if (block[i++] != 0xFF)
            goto err;

    /* check end of padding */
    if (block[i++] != 0)
        goto err;

    /* check the ASN.1 metadata */
    if (memcmp_P(block+i, sig_prefix, sig_prefix_size))
        goto err;

    /* now we can get the hash we need */
    bir = bi_import(ctx, block + i + sig_prefix_size, hash_len);

err:
    cert_sig = sig_verify(ctx, cert->signature, cert->sig_len, cert->sig_type,