void check_key_length (ssl_context *ssl) { uint32_t key_bits; const x509_cert *certificate; const rsa_context *public_key; char buf[1024]; certificate = ssl_get_peer_cert (ssl); if (NULL == certificate) { die ("Getting certificate failed"); } x509parse_dn_gets(buf, 1024, &certificate->subject); verb_debug ("V: Certificate for subject '%s'", buf); public_key = &certificate->rsa; if (NULL == public_key) { die ("public key extraction failure"); } else { verb_debug ("V: public key is ready for inspection"); } key_bits = mpi_msb (&public_key->N); if (MIN_PUB_KEY_LEN >= key_bits) { die ("Unsafe public key size: %d bits", key_bits); } else { verb_debug ("V: key length appears safe"); } }
uint32_t dns_label_count(char *label, char *delim) { char *label_tmp; char *saveptr; char *saveptr_tmp; uint32_t label_count; label_tmp = strdup(label); label_count = 0; saveptr = NULL; saveptr_tmp = NULL; saveptr = strtok_r(label_tmp, delim, &saveptr); if (NULL != saveptr) { // Did we find our first label? if (saveptr[0] != delim[0]) { label_count++; } do { // Find all subsequent labels label_count++; saveptr_tmp = strtok_r(NULL, delim, &saveptr); } while (NULL != saveptr_tmp); } verb_debug ("V: label found; total label count: %d", label_count); free(label_tmp); return label_count; }
static int read_http_date_from_bio(BIO *bio, uint32_t *result) { int n; char buf[MAX_HTTP_HEADERS_SIZE]; int buf_len=0; char *dateline, *endofline; while (buf_len < sizeof(buf)-1) { n = BIO_read(bio, buf+buf_len, sizeof(buf)-buf_len-1); if (n <= 0) return 0; buf_len += n; buf[buf_len] = 0; verb_debug ("V: read %d bytes.", n, buf); dateline = memmem(buf, buf_len, "\r\nDate: ", 8); if (NULL == dateline) continue; endofline = memmem(dateline+2, buf_len - (dateline-buf+2), "\r\n", 2); if (NULL == endofline) continue; *endofline = 0; return handle_date_line(dateline, result); } return -2; }
/* Run tlsdate and redirects stdout to the monitor_fd */ int tlsdate (struct state *state) { char **new_argv; pid_t pid; switch ((pid = fork())) { case 0: /* child! */ break; case -1: perror ("fork() failed!"); return -1; default: verb_debug ("[tlsdate-monitor] spawned tlsdate: %d", pid); state->tlsdate_pid = pid; return 0; } if (!(new_argv = build_argv (&state->opts))) fatal ("out of memory building argv"); /* Replace stdout with the pipe back to tlsdated */ if (dup2 (state->tlsdate_monitor_fd, STDOUT_FILENO) < 0) { perror ("dup2 failed"); _exit (2); } execve (new_argv[0], new_argv, state->envp); perror ("execve() failed"); _exit (1); }
void check_key_length (SSL *ssl) { uint32_t key_bits; X509 *certificate; EVP_PKEY *public_key; certificate = SSL_get_peer_certificate (ssl); if (NULL == certificate) { die ("Getting certificate failed"); } public_key = X509_get_pubkey (certificate); if (NULL == public_key) { die ("public key extraction failure"); } else { verb_debug ("V: public key is ready for inspection"); } key_bits = get_certificate_keybits (public_key); if (MIN_PUB_KEY_LEN >= key_bits && public_key->type != EVP_PKEY_EC) { die ("Unsafe public key size: %d bits", key_bits); } else { if (public_key->type == EVP_PKEY_EC) if(key_bits >= MIN_ECC_PUB_KEY_LEN && key_bits <= MAX_ECC_PUB_KEY_LEN) { verb_debug ("V: ECC key length appears safe"); } else { die ("Unsafe ECC key size: %d bits", key_bits); } else { verb_debug ("V: key length appears safe"); } } EVP_PKEY_free (public_key); }
/** Search for a hostname match in the SubjectAlternativeNames. */ uint32_t check_san (SSL *ssl, const char *hostname) { X509 *cert; int extcount, ok = 0; /* What an OpenSSL mess ... */ if (NULL == (cert = SSL_get_peer_certificate(ssl))) { die ("Getting certificate failed"); } if ((extcount = X509_get_ext_count(cert)) > 0) { int i; for (i = 0; i < extcount; ++i) { const char *extstr; X509_EXTENSION *ext; ext = X509_get_ext(cert, i); extstr = OBJ_nid2sn(OBJ_obj2nid(X509_EXTENSION_get_object(ext))); if (!strcmp(extstr, "subjectAltName")) { int j; void *extvalstr; const unsigned char *tmp; STACK_OF(CONF_VALUE) *val; CONF_VALUE *nval; #if OPENSSL_VERSION_NUMBER >= 0x10000000L const #endif X509V3_EXT_METHOD *method; if (!(method = X509V3_EXT_get(ext))) { break; } tmp = ext->value->data; if (method->it) { extvalstr = ASN1_item_d2i(NULL, &tmp, ext->value->length, ASN1_ITEM_ptr(method->it)); } else { extvalstr = method->d2i(NULL, &tmp, ext->value->length); } if (!extvalstr) { break; } if (method->i2v) { val = method->i2v(method, extvalstr, NULL); for (j = 0; j < sk_CONF_VALUE_num(val); ++j) { nval = sk_CONF_VALUE_value(val, j); if ((!strcasecmp(nval->name, "DNS") && !strcasecmp(nval->value, hostname) ) || (!strcasecmp(nval->name, "iPAddress") && !strcasecmp(nval->value, hostname))) { verb ("V: subjectAltName matched: %s, type: %s", nval->value, nval->name); // We matched this; so it's safe to print ok = 1; break; } // Attempt to match subjectAltName DNS names if (!strcasecmp(nval->name, "DNS")) { ok = check_wildcard_match_rfc2595(hostname, nval->value); if (ok) { break; } } verb_debug ("V: subjectAltName found but not matched: %s, type: %s", nval->value, sanitize_string(nval->name)); } } } else { verb_debug ("V: found non subjectAltName extension"); } if (ok) { break; } } } else { verb_debug ("V: no X509_EXTENSION field(s) found"); } X509_free(cert); return ok; }
// first we split strings on '.' // then we call each split string a 'label' // Do not allow '*' for the top level domain label; eg never allow *.*.com // Do not allow '*' for subsequent subdomains; eg never allow *.foo.example.com // Do allow *.example.com uint32_t check_wildcard_match_rfc2595 (const char *orig_hostname, const char *orig_cert_wild_card) { char *hostname; char *hostname_to_free; char *cert_wild_card; char *cert_wild_card_to_free; char *expected_label; char *wildcard_label; char *delim; char *wildchar; uint32_t ok; uint32_t wildcard_encountered; uint32_t label_count; // First we copy the original strings hostname = strndup(orig_hostname, strlen(orig_hostname)); cert_wild_card = strndup(orig_cert_wild_card, strlen(orig_cert_wild_card)); hostname_to_free = hostname; cert_wild_card_to_free = cert_wild_card; delim = strdup("."); wildchar = strdup("*"); verb_debug ("V: Inspecting '%s' for possible wildcard match against '%s'", hostname, cert_wild_card); // By default we have not processed any labels label_count = dns_label_count(cert_wild_card, delim); // By default we have no match ok = 0; wildcard_encountered = 0; // First - do we have labels? If not, we refuse to even try to match if ((NULL != strpbrk(cert_wild_card, delim)) && (NULL != strpbrk(hostname, delim)) && (label_count <= ((uint32_t)RFC2595_MIN_LABEL_COUNT))) { if (wildchar[0] == cert_wild_card[0]) { verb_debug ("V: Found wildcard in at start of provided certificate name"); do { // Skip over the bytes between the first char and until the next label wildcard_label = strsep(&cert_wild_card, delim); expected_label = strsep(&hostname, delim); if (NULL != wildcard_label && NULL != expected_label && NULL != hostname && NULL != cert_wild_card) { // Now we only consider this wildcard valid if the rest of the // hostnames match verbatim verb_debug ("V: Attempting match of '%s' against '%s'", expected_label, wildcard_label); // This is the case where we have a label that begins with wildcard // Furthermore, we only allow this for the first label if (wildcard_label[0] == wildchar[0] && 0 == wildcard_encountered && 0 == ok) { verb ("V: Forced match of '%s' against '%s'", expected_label, wildcard_label); wildcard_encountered = 1; } else { verb_debug ("V: Attempting match of '%s' against '%s'", hostname, cert_wild_card); if (0 == strcasecmp (expected_label, wildcard_label) && label_count >= ((uint32_t)RFC2595_MIN_LABEL_COUNT)) { ok = 1; verb_debug ("V: remaining labels match!"); break; } else { ok = 0; verb_debug ("V: remaining labels do not match!"); break; } } } else { // We hit this case when we have a mismatched number of labels verb_debug ("V: NULL label; no wildcard here"); break; } } while (0 != wildcard_encountered && label_count <= RFC2595_MIN_LABEL_COUNT); } else { verb_debug ("V: Not a RFC 2595 wildcard"); } } else { verb_debug ("V: Not a valid wildcard certificate"); ok = 0; } // Free our copies free(wildchar); free(delim); free(hostname_to_free); free(cert_wild_card_to_free); if (wildcard_encountered & ok && label_count >= RFC2595_MIN_LABEL_COUNT) { verb_debug ("V: wildcard match of %s against %s", orig_hostname, orig_cert_wild_card); return (wildcard_encountered & ok); } else { verb_debug ("V: wildcard match failure of %s against %s", orig_hostname, orig_cert_wild_card); return 0; } }
/** * Run SSL handshake and store the resulting time value in the * 'time_map'. * * @param time_map where to store the current time * @param time_is_an_illusion * @param http whether to do an http request and take the date from that * instead. */ static void run_ssl (uint32_t *time_map, int time_is_an_illusion, int http) { BIO *s_bio; SSL_CTX *ctx; SSL *ssl; struct stat statbuf; uint32_t result_time; SSL_load_error_strings(); SSL_library_init(); ctx = NULL; if (0 == strcmp("sslv23", protocol)) { verb ("V: using SSLv23_client_method()"); ctx = SSL_CTX_new(SSLv23_client_method()); } else if (0 == strcmp("sslv3", protocol)) { verb ("V: using SSLv3_client_method()"); ctx = SSL_CTX_new(SSLv3_client_method()); } else if (0 == strcmp("tlsv1", protocol)) { verb ("V: using TLSv1_client_method()"); ctx = SSL_CTX_new(TLSv1_client_method()); } else die("Unsupported protocol `%s'", protocol); if (ctx == NULL) die("OpenSSL failed to support protocol `%s'", protocol); verb("V: Using OpenSSL for SSL"); if (ca_racket) { if (-1 == stat(ca_cert_container, &statbuf)) { die("Unable to stat CA certficate container %s", ca_cert_container); } else { switch (statbuf.st_mode & S_IFMT) { case S_IFREG: if (1 != SSL_CTX_load_verify_locations(ctx, ca_cert_container, NULL)) fprintf(stderr, "SSL_CTX_load_verify_locations failed"); break; case S_IFDIR: if (1 != SSL_CTX_load_verify_locations(ctx, NULL, ca_cert_container)) fprintf(stderr, "SSL_CTX_load_verify_locations failed"); break; default: if (1 != SSL_CTX_load_verify_locations(ctx, NULL, ca_cert_container)) { fprintf(stderr, "SSL_CTX_load_verify_locations failed"); die("Unable to load CA certficate container %s", ca_cert_container); } } } } if (NULL == (s_bio = make_ssl_bio(ctx))) die ("SSL BIO setup failed"); BIO_get_ssl(s_bio, &ssl); if (NULL == ssl) die ("SSL setup failed"); if (time_is_an_illusion) { SSL_set_info_callback(ssl, openssl_time_callback); } SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY); verb("V: opening socket to %s:%s", host, port); if ( (1 != BIO_set_conn_hostname(s_bio, host)) || (1 != BIO_set_conn_port(s_bio, port)) ) die ("Failed to initialize connection to `%s:%s'", host, port); if (NULL == BIO_new_fp(stdout, BIO_NOCLOSE)) die ("BIO_new_fp returned error, possibly: %s", strerror(errno)); // This should run in seccomp // eg: prctl(PR_SET_SECCOMP, 1); if (1 != BIO_do_connect(s_bio)) // XXX TODO: BIO_should_retry() later? die ("SSL connection failed"); if (1 != BIO_do_handshake(s_bio)) die ("SSL handshake failed"); // from /usr/include/openssl/ssl3.h // ssl->s3->server_random is an unsigned char of 32 bits memcpy(&result_time, ssl->s3->server_random, sizeof (uint32_t)); verb("V: In TLS response, T=%lu", (unsigned long)ntohl(result_time)); if (http) { char buf[1024]; verb_debug ("V: Starting HTTP"); if (snprintf(buf, sizeof(buf), HTTP_REQUEST, HTTPS_USER_AGENT, hostname_to_verify) >= 1024) die("hostname too long"); buf[1023]='\0'; /* Unneeded. */ verb_debug ("V: Writing HTTP request"); if (1 != write_all_to_bio(s_bio, buf)) die ("write all to bio failed."); verb_debug ("V: Reading HTTP response"); if (1 != read_http_date_from_bio(s_bio, &result_time)) die ("read all from bio failed."); verb ("V: Received HTTP response. T=%lu", (unsigned long)result_time); result_time = htonl(result_time); } // Verify the peer certificate against the CA certs on the local system if (ca_racket) { inspect_key (ssl, hostname_to_verify); } else { verb ("V: Certificate verification skipped!"); } check_key_length(ssl); memcpy(time_map, &result_time, sizeof (uint32_t)); SSL_free(ssl); SSL_CTX_free(ctx); }