From a70da5b3ecc3160368529677006801c58cb369db Mon Sep 17 00:00:00 2001 From: "Dr. Stephen Henson" Date: Mon, 8 Oct 2012 15:10:07 +0000 Subject: [PATCH] New functions to check a hostname email or IP address against a certificate. Add options to s_client, s_server and x509 utilities to print results of checks. --- CHANGES | 5 ++ apps/apps.c | 29 ++++++++++ apps/apps.h | 5 ++ apps/s_apps.h | 4 ++ apps/s_cb.c | 13 +++++ apps/s_client.c | 20 +++++++ apps/s_server.c | 21 +++++++ apps/x509.c | 19 ++++++ crypto/x509v3/v3_utl.c | 128 +++++++++++++++++++++++++++++++++++++++++ crypto/x509v3/x509v3.h | 12 ++++ 10 files changed, 256 insertions(+) diff --git a/CHANGES b/CHANGES index be586a2f8a..79d31f2d00 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,11 @@ Changes between 1.0.x and 1.1.0 [xx XXX xxxx] + *) New functions to check a hostname email or IP address against a + certificate. Add options to s_client, s_server and x509 utilities + to print results of checks against a certificate. + [Steve Henson] + *) Add -rev test option to s_server to just reverse order of characters received by client and send back to server. Also prints an abbreviated summary of the connection parameters. diff --git a/apps/apps.c b/apps/apps.c index 490ae3b61b..0ce0af5505 100644 --- a/apps/apps.c +++ b/apps/apps.c @@ -2791,6 +2791,35 @@ unsigned char *next_protos_parse(unsigned short *outlen, const char *in) } #endif /* !OPENSSL_NO_TLSEXT && !OPENSSL_NO_NEXTPROTONEG */ +void print_cert_checks(BIO *bio, X509 *x, + const unsigned char *checkhost, + const unsigned char *checkemail, + const char *checkip) + { + if (x == NULL) + return; + if (checkhost) + { + BIO_printf(bio, "Hostname %s does%s match certificate\n", + checkhost, X509_check_host(x, checkhost, 0, 0) + ? "" : " NOT"); + } + + if (checkemail) + { + BIO_printf(bio, "Email %s does%s match certificate\n", + checkemail, X509_check_email(x, checkemail, 0, + 0) ? "" : " NOT"); + } + + if (checkip) + { + BIO_printf(bio, "IP %s does%s match certificate\n", + checkip, X509_check_ip_asc(x, checkip, + 0) ? "" : " NOT"); + } + } + /* * Platform-specific sections */ diff --git a/apps/apps.h b/apps/apps.h index c1ca99da12..4c9f95a1ce 100644 --- a/apps/apps.h +++ b/apps/apps.h @@ -335,6 +335,11 @@ void jpake_server_auth(BIO *out, BIO *conn, const char *secret); unsigned char *next_protos_parse(unsigned short *outlen, const char *in); #endif /* !OPENSSL_NO_TLSEXT && !OPENSSL_NO_NEXTPROTONEG */ +void print_cert_checks(BIO *bio, X509 *x, + const unsigned char *checkhost, + const unsigned char *checkemail, + const char *checkip); + #define FORMAT_UNDEF 0 #define FORMAT_ASN1 1 #define FORMAT_TEXT 2 diff --git a/apps/s_apps.h b/apps/s_apps.h index b1ae531367..31b89234f1 100644 --- a/apps/s_apps.h +++ b/apps/s_apps.h @@ -191,3 +191,7 @@ int args_excert(char ***pargs, int *pargc, int *badarg, BIO *err, SSL_EXCERT **pexc); int load_excert(SSL_EXCERT **pexc, BIO *err); void print_ssl_summary(BIO *bio, SSL *s); +void print_ssl_cert_checks(BIO *bio, SSL *s, + const unsigned char *checkhost, + const unsigned char *checkemail, + const char *checkip); diff --git a/apps/s_cb.c b/apps/s_cb.c index e339a6ce5d..5a2222ea6b 100644 --- a/apps/s_cb.c +++ b/apps/s_cb.c @@ -1533,3 +1533,16 @@ void print_ssl_summary(BIO *bio, SSL *s) ssl_print_tmp_key(bio, s); } +void print_ssl_cert_checks(BIO *bio, SSL *s, + const unsigned char *checkhost, + const unsigned char *checkemail, + const char *checkip) + { + X509 *peer; + peer = SSL_get_peer_certificate(s); + if (peer) + { + print_cert_checks(bio, peer, checkhost, checkemail, checkip); + X509_free(peer); + } + } diff --git a/apps/s_client.c b/apps/s_client.c index a7b150b02a..5ecc957302 100644 --- a/apps/s_client.c +++ b/apps/s_client.c @@ -634,6 +634,9 @@ int MAIN(int argc, char **argv) #endif SSL_EXCERT *exc = NULL; + unsigned char *checkhost = NULL, *checkemail = NULL; + char *checkip = NULL; + meth=SSLv23_client_method(); apps_startup(); @@ -993,6 +996,21 @@ int MAIN(int argc, char **argv) client_sigalgs= *(++argv); } #endif + else if (strcmp(*argv,"-checkhost") == 0) + { + if (--argc < 1) goto bad; + checkhost=(unsigned char *)*(++argv); + } + else if (strcmp(*argv,"-checkemail") == 0) + { + if (--argc < 1) goto bad; + checkemail=(unsigned char *)*(++argv); + } + else if (strcmp(*argv,"-checkip") == 0) + { + if (--argc < 1) goto bad; + checkip=*(++argv); + } #ifndef OPENSSL_NO_JPAKE else if (strcmp(*argv,"-jpake") == 0) { @@ -1628,6 +1646,8 @@ SSL_set_tlsext_status_ids(con, ids); "CONNECTION ESTABLISHED\n"); print_ssl_summary(bio_err, con); } + print_ssl_cert_checks(bio_err, con, checkhost, + checkemail, checkip); print_stuff(bio_c_out,con,full_log); if (full_log > 0) full_log--; diff --git a/apps/s_server.c b/apps/s_server.c index e89f057cca..00dc219eb7 100644 --- a/apps/s_server.c +++ b/apps/s_server.c @@ -1003,6 +1003,10 @@ int MAIN(int argc, char *argv[]) char *srp_verifier_file = NULL; #endif SSL_EXCERT *exc = NULL; + + unsigned char *checkhost = NULL, *checkemail = NULL; + char *checkip = NULL; + meth=SSLv23_server_method(); local_argc=argc; @@ -1260,6 +1264,21 @@ int MAIN(int argc, char *argv[]) client_sigalgs= *(++argv); } #endif + else if (strcmp(*argv,"-checkhost") == 0) + { + if (--argc < 1) goto bad; + checkhost=(unsigned char *)*(++argv); + } + else if (strcmp(*argv,"-checkemail") == 0) + { + if (--argc < 1) goto bad; + checkemail=(unsigned char *)*(++argv); + } + else if (strcmp(*argv,"-checkip") == 0) + { + if (--argc < 1) goto bad; + checkip=*(++argv); + } else if (strcmp(*argv,"-msg") == 0) { s_msg=1; } else if (strcmp(*argv,"-msgfile") == 0) @@ -2661,6 +2680,8 @@ static int init_ssl_connection(SSL *con) if (s_brief) print_ssl_summary(bio_err, con); + print_ssl_cert_checks(bio_err, con, checkhost, checkemail, checkip); + PEM_write_bio_SSL_SESSION(bio_s_out,SSL_get_session(con)); peer=SSL_get_peer_certificate(con); diff --git a/apps/x509.c b/apps/x509.c index e9f1163088..788eb7b3d0 100644 --- a/apps/x509.c +++ b/apps/x509.c @@ -208,6 +208,8 @@ int MAIN(int argc, char **argv) int need_rand = 0; int checkend=0,checkoffset=0; unsigned long nmflag = 0, certflag = 0; + unsigned char *checkhost = NULL, *checkemail = NULL; + char *checkip = NULL; #ifndef OPENSSL_NO_ENGINE char *engine=NULL; #endif @@ -456,6 +458,21 @@ int MAIN(int argc, char **argv) checkoffset=atoi(*(++argv)); checkend=1; } + else if (strcmp(*argv,"-checkhost") == 0) + { + if (--argc < 1) goto bad; + checkhost=(unsigned char *)*(++argv); + } + else if (strcmp(*argv,"-checkemail") == 0) + { + if (--argc < 1) goto bad; + checkemail=(unsigned char *)*(++argv); + } + else if (strcmp(*argv,"-checkip") == 0) + { + if (--argc < 1) goto bad; + checkip=*(++argv); + } else if (strcmp(*argv,"-noout") == 0) noout= ++num; else if (strcmp(*argv,"-trustout") == 0) @@ -1061,6 +1078,8 @@ bad: goto end; } + print_cert_checks(STDout, x, checkhost, checkemail, checkip); + if (noout) { ret=0; diff --git a/crypto/x509v3/v3_utl.c b/crypto/x509v3/v3_utl.c index e030234540..db9f05ebb9 100644 --- a/crypto/x509v3/v3_utl.c +++ b/crypto/x509v3/v3_utl.c @@ -568,6 +568,134 @@ void X509_email_free(STACK_OF(OPENSSL_STRING) *sk) sk_OPENSSL_STRING_pop_free(sk, str_free); } +/* Compare an ASN1_STRING to a supplied string. If they match + * return 1. If cmp_type > 0 only compare if string matches the + * type, otherwise convert it to UTF8. + */ + +static int do_check_string(ASN1_STRING *a, int cmp_type, + const unsigned char *b, size_t blen) + { + if (!a->data || !a->length) + return 0; + if (cmp_type > 0) + { + if (cmp_type != a->type) + return 0; + if (a->length == (int)blen && !memcmp(a->data, b, blen)) + return 1; + else + return 0; + } + else + { + int astrlen, rv; + unsigned char *astr; + astrlen = ASN1_STRING_to_UTF8(&astr, a); + if (astrlen < 0) + return 0; + if (astrlen == (int)blen && !memcmp(astr, b, blen)) + rv = 1; + else + rv = 0; + OPENSSL_free(astr); + return rv; + } + } + +static int do_x509_check(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags, int check_type) + { + GENERAL_NAMES *gens = NULL; + X509_NAME *name = NULL; + int i; + int cnid; + if (check_type == GEN_EMAIL) + cnid = NID_pkcs9_emailAddress; + else if (check_type == GEN_DNS) + cnid = NID_commonName; + else + cnid = 0; + + if (chklen == 0) + chklen = strlen((const char *)chk); + + gens = X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL); + if (gens) + { + int rv = 0; + int alt_type; + if (cnid) + alt_type = V_ASN1_IA5STRING; + else + alt_type = V_ASN1_OCTET_STRING; + for (i = 0; i < sk_GENERAL_NAME_num(gens); i++) + { + GENERAL_NAME *gen; + ASN1_STRING *cstr; + gen = sk_GENERAL_NAME_value(gens, i); + if(gen->type != check_type) + continue; + if (check_type == GEN_EMAIL) + cstr = gen->d.rfc822Name; + else if (check_type == GEN_DNS) + cstr = gen->d.dNSName; + else + cstr = gen->d.iPAddress; + if (do_check_string(cstr, alt_type, chk, chklen)) + { + rv = 1; + break; + } + } + GENERAL_NAMES_free(gens); + if (rv) + return 1; + if (!(flags & X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT) || !cnid) + return 0; + } + i = -1; + name = X509_get_subject_name(x); + while((i = X509_NAME_get_index_by_NID(name, cnid, i)) >= 0) + { + X509_NAME_ENTRY *ne; + ASN1_STRING *str; + ne = X509_NAME_get_entry(name, i); + str = X509_NAME_ENTRY_get_data(ne); + if (do_check_string(str, -1, chk, chklen)) + return 1; + } + return 0; + } + +int X509_check_host(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags) + { + return do_x509_check(x, chk, chklen, flags, GEN_DNS); + } + +int X509_check_email(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags) + { + return do_x509_check(x, chk, chklen, flags, GEN_EMAIL); + } + +int X509_check_ip(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags) + { + return do_x509_check(x, chk, chklen, flags, GEN_IPADD); + } + +int X509_check_ip_asc(X509 *x, const char *ipasc, unsigned int flags) + { + unsigned char ipout[16]; + int iplen; + iplen = a2i_ipadd(ipout, ipasc); + if (iplen == 0) + return 0; + return do_x509_check(x, ipout, (size_t)iplen, flags, GEN_IPADD); + } + /* Convert IP addresses both IPv4 and IPv6 into an * OCTET STRING compatible with RFC3280. */ diff --git a/crypto/x509v3/x509v3.h b/crypto/x509v3/x509v3.h index bf409997e7..23f7091db0 100644 --- a/crypto/x509v3/x509v3.h +++ b/crypto/x509v3/x509v3.h @@ -700,6 +700,18 @@ STACK_OF(OPENSSL_STRING) *X509_get1_email(X509 *x); STACK_OF(OPENSSL_STRING) *X509_REQ_get1_email(X509_REQ *x); void X509_email_free(STACK_OF(OPENSSL_STRING) *sk); STACK_OF(OPENSSL_STRING) *X509_get1_ocsp(X509 *x); +/* Flags for X509_check_* functions */ + +/* Always check subject name for host match even if subject alt names present */ +#define X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT 0x1 + +int X509_check_host(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags); +int X509_check_email(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags); +int X509_check_ip(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags); +int X509_check_ip_asc(X509 *x, const char *ipasc, unsigned int flags); ASN1_OCTET_STRING *a2i_IPADDRESS(const char *ipasc); ASN1_OCTET_STRING *a2i_IPADDRESS_NC(const char *ipasc); -- 2.40.0