From 4dbf32fd6a14fad24a850f9e48ee46902157afb5 Mon Sep 17 00:00:00 2001 From: Zhi Guan Date: Tue, 10 Jan 2023 00:08:13 +0800 Subject: [PATCH] Update X.509 Path Validation To be continue ... --- include/gmssl/oid.h | 1 + include/gmssl/x509.h | 11 +++ include/gmssl/x509_ext.h | 9 +- include/gmssl/x509_oid.h | 1 + src/tlcp.c | 1 + src/tls12.c | 2 + src/x509_cer.c | 57 ++++++++++- src/x509_ext.c | 202 +++++++++++++++++++++++++++++++++++++++ src/x509_oid.c | 3 + 9 files changed, 285 insertions(+), 2 deletions(-) diff --git a/include/gmssl/oid.h b/include/gmssl/oid.h index 90774b26..977dc50c 100644 --- a/include/gmssl/oid.h +++ b/include/gmssl/oid.h @@ -103,6 +103,7 @@ enum { OID_ce_certificate_issuer, // X.509 KeyPropuseID + OID_any_extended_key_usage, OID_kp_server_auth, OID_kp_client_auth, OID_kp_code_signing, diff --git a/include/gmssl/x509.h b/include/gmssl/x509.h index 342592e5..0e1021e6 100644 --- a/include/gmssl/x509.h +++ b/include/gmssl/x509.h @@ -346,6 +346,7 @@ typedef enum { } X509_VERIFY_ERR; int x509_certs_verify(const uint8_t *certs, size_t certslen, + int server, const uint8_t *rootcerts, size_t rootcertslen, int depth, int *verify_result); int x509_certs_verify_tlcp(const uint8_t *certs, size_t certslen, const uint8_t *rootcerts, size_t rootcertslen, int depth, int *verify_result); @@ -357,6 +358,16 @@ int x509_cert_new_from_file(uint8_t **out, size_t *outlen, const char *file); int x509_certs_new_from_file(uint8_t **out, size_t *outlen, const char *file); +typedef enum { + X509_cert_server_auth, + X509_cert_client_auth, + X509_cert_server_key_encipher, + X509_cert_client_key_encipher, + X509_cert_ca, + X509_cert_root_ca, + X509_cert_crl_sign, +} X509_CERT_TYPE; + #ifdef __cplusplus diff --git a/include/gmssl/x509_ext.h b/include/gmssl/x509_ext.h index 4f9ef23e..06f791bc 100644 --- a/include/gmssl/x509_ext.h +++ b/include/gmssl/x509_ext.h @@ -203,6 +203,7 @@ const char *x509_key_usage_name(int flag); int x509_key_usage_from_name(int *flag, const char *name); #define x509_key_usage_to_der(bits,out,outlen) asn1_bits_to_der(bits,out,outlen) #define x509_key_usage_from_der(bits,in,inlen) asn1_bits_from_der(bits,in,inlen) +int x509_key_usage_validate(int bits, int cert_type); int x509_key_usage_print(FILE *fp, int fmt, int ind, const char *label, int bits); /* @@ -355,6 +356,7 @@ BasicConstraints ::= SEQUENCE { */ int x509_basic_constraints_to_der(int ca, int path_len_cons, uint8_t **out, size_t *outlen); int x509_basic_constraints_from_der(int *ca, int *path_len_cons, const uint8_t **in, size_t *inlen); +int x509_basic_constraints_validate(int ca, int path_len_cons, int cert_type); int x509_basic_constraints_print(FILE *fp, int fmt, int ind, const char *label, const uint8_t *d, size_t dlen); /* @@ -417,6 +419,7 @@ int x509_policy_constraints_print(FILE *fp, int fmt, int ind, const char *label, ExtKeyUsageSyntax ::= SEQUENCE SIZE (1..MAX) OF KeyPurposeId KeyPurposeId: + OID_any_extended_key_usage OID_kp_server_auth OID_kp_client_auth OID_kp_code_signing @@ -424,9 +427,10 @@ KeyPurposeId: OID_kp_time_stamping OID_kp_ocsp_signing */ -#define X509_MAX_KEY_PURPOSES 6 +#define X509_MAX_KEY_PURPOSES 7 int x509_ext_key_usage_to_der(const int *oids, size_t oids_cnt, uint8_t **out, size_t *outlen); int x509_ext_key_usage_from_der(int *oids, size_t *oids_cnt, size_t max_cnt, const uint8_t **in, size_t *inlen); +int x509_ext_key_usage_validate(const int *oids, size_t oids_cnt, int cert_type); int x509_ext_key_usage_print(FILE *fp, int fmt, int ind, const char *label, const uint8_t *d, size_t dlen); /* @@ -537,6 +541,9 @@ NetscapeCertComment ::= IA5String */ int x509_netscape_cert_type_print(FILE *fp, int fmt, int ind, const char *label, int bits); +int x509_exts_validate(const uint8_t *exts, size_t extslen, int cert_type, + int *path_len_constraints); + #ifdef __cplusplus } #endif diff --git a/include/gmssl/x509_oid.h b/include/gmssl/x509_oid.h index 14ffa644..9140699a 100644 --- a/include/gmssl/x509_oid.h +++ b/include/gmssl/x509_oid.h @@ -91,6 +91,7 @@ int x509_cert_policy_id_from_der(int *oid, uint32_t *nodes, size_t *nodes_cnt, c int x509_cert_policy_id_to_der(int oid, const uint32_t *nodes, size_t nodes_cnt, uint8_t **out, size_t *outlen); /* + OID_any_extended_key_usage id-kp OID_kp_server_auth OID_kp_client_auth diff --git a/src/tlcp.c b/src/tlcp.c index 943b429d..2da93033 100644 --- a/src/tlcp.c +++ b/src/tlcp.c @@ -792,6 +792,7 @@ int tlcp_do_accept(TLS_CONNECT *conn) goto end; } if (x509_certs_verify(conn->client_certs, conn->client_certs_len, + 0, // client conn->ca_certs, conn->ca_certs_len, verify_depth, &verify_result) != 1) { error_print(); tls_send_alert(conn, TLS_alert_bad_certificate); diff --git a/src/tls12.c b/src/tls12.c index ec876ab4..4b3e5252 100644 --- a/src/tls12.c +++ b/src/tls12.c @@ -345,6 +345,7 @@ int tls12_do_connect(TLS_CONNECT *conn) // verify ServerCertificate if (x509_certs_verify(conn->server_certs, conn->server_certs_len, + 1, // server conn->ca_certs, conn->ca_certs_len, depth, &verify_result) != 1) { error_print(); tls_send_alert(conn, alert); @@ -883,6 +884,7 @@ int tls12_do_accept(TLS_CONNECT *conn) goto end; } if (x509_certs_verify(conn->client_certs, conn->client_certs_len, + 0, // client conn->ca_certs, conn->ca_certs_len, verify_depth, &verify_result) != 1) { error_print(); tls_send_alert(conn, TLS_alert_bad_certificate); diff --git a/src/x509_cer.c b/src/x509_cer.c index f78ed867..e26c1441 100644 --- a/src/x509_cer.c +++ b/src/x509_cer.c @@ -1519,7 +1519,54 @@ int x509_cert_check(const uint8_t *cert, size_t certlen) return 1; } +int x509_cert_validate(const uint8_t *cert, size_t certlen, int cert_type, + int *path_len_constraints) +{ + time_t now; + time_t not_before; + time_t not_after; + const uint8_t *exts; + size_t extslen; + + x509_cert_get_details(cert, certlen, + NULL, // version + NULL, NULL, // serial + NULL, // signature_algor + NULL, NULL, // issuer + ¬_before, ¬_after, // validity + NULL, NULL, // subject + NULL, // subject_public_key + NULL, NULL, // issuer_unique_id + NULL, NULL, // subject_unique_id + &exts, &extslen, // extensions + NULL, // signature_algor + NULL, NULL); // signature + + // not_before < now < not_after + time(&now); + if (not_before >= not_after) { + error_print(); + return -1; + } + if (now < not_before) { + error_print(); + return X509_verify_err_cert_not_yet_valid; + } + if (not_after < now) { + error_print(); + return X509_verify_err_cert_has_expired; + } + + if (x509_exts_validate(exts, extslen, cert_type, path_len_constraints) != 1) { + error_print(); + return -1; //TODO: return X509_verify_err_xxx + } + + return 1; +} + int x509_certs_verify(const uint8_t *certs, size_t certslen, + int server, const uint8_t *rootcerts, size_t rootcertslen, int depth, int *verify_result) { const uint8_t *cert; @@ -1530,16 +1577,24 @@ int x509_certs_verify(const uint8_t *certs, size_t certslen, size_t namelen; *verify_result = -1; + int cert_type = server ? X509_cert_server_auth : X509_cert_client_auth; + int path_len_constraints; + if (x509_cert_from_der(&cert, &certlen, &certs, &certslen) != 1) { error_print(); return -1; } while (certslen) { - if ((*verify_result = x509_cert_check(cert, certlen)) < 0) { + if ((*verify_result = x509_cert_validate(cert, certlen, cert_type, &path_len_constraints)) < 0) { error_print(); return -1; } + + cert_type = X509_cert_ca; + + // FIXME: check path_len_constraints + if (x509_cert_from_der(&cacert, &cacertlen, &certs, &certslen) != 1) { error_print(); return -1; diff --git a/src/x509_ext.c b/src/x509_ext.c index ee9ad29c..4c579243 100644 --- a/src/x509_ext.c +++ b/src/x509_ext.c @@ -733,6 +733,70 @@ int x509_key_usage_from_name(int *flag, const char *name) return -1; } +int x509_key_usage_validate(int bits, int cert_type) +{ + switch (cert_type) { + case X509_cert_server_auth: + case X509_cert_client_auth: + if (!(bits & X509_KU_DIGITAL_SIGNATURE) + //&& !(bits & X509_KU_NON_REPUDIATION) // un-comment for compatibility + ) { + error_print(); + return -1; + } + if ((bits & X509_KU_KEY_CERT_SIGN) + || (bits & X509_KU_CRL_SIGN)) { + error_print(); + return -1; + } + break; + + case X509_cert_server_key_encipher: + case X509_cert_client_key_encipher: + if (!(bits & X509_KU_KEY_ENCIPHERMENT) + //&& !(bits & X509_KU_KEY_AGREEMENT) // un-comment for compatibility + ) { + error_print(); + return -1; + } + if ((bits & X509_KU_KEY_CERT_SIGN) + || (bits & X509_KU_CRL_SIGN)) { + error_print(); + return -1; + } + break; + + case X509_cert_ca: + if (!(bits & X509_KU_KEY_CERT_SIGN)) { + error_print(); + return -1; + } + if ((bits & X509_KU_DIGITAL_SIGNATURE) + || (bits & X509_KU_NON_REPUDIATION)) { + error_print(); + //return -1; // comment to print warning + } + break; + case X509_cert_crl_sign: + if (!(bits & X509_KU_CRL_SIGN)) { + error_print(); + return -1; + } + if ((bits & X509_KU_DIGITAL_SIGNATURE) + || (bits & X509_KU_NON_REPUDIATION)) { + error_print(); + //return -1; // comment to print warning + } + break; + + default: + error_print(); + return -1; + } + + return 1; +} + int x509_key_usage_print(FILE *fp, int fmt, int ind, const char *label, int bits) { return asn1_bits_print(fp, fmt, ind, label, x509_key_usages, x509_key_usages_count, bits); @@ -1234,6 +1298,26 @@ int x509_basic_constraints_from_der(int *ca, int *path_len_cons, const uint8_t * return 1; } +int x509_basic_constraints_validate(int ca, int path_len_cons, int cert_type) +{ + if (cert_type == X509_cert_ca) { + if (ca != 1) { + error_print(); + return -1; + } + if (path_len_cons < 0 || path_len_cons > 6) { + error_print(); + return -1; + } + } else { + if (ca == 1 || path_len_cons >= 0) { + error_print(); + return -1; // comment to only warning + } + } + return 1; +} + int x509_basic_constraints_print(FILE *fp, int fmt, int ind, const char *label, const uint8_t *d, size_t dlen) { int ret, val; @@ -1522,6 +1606,40 @@ int x509_ext_key_usage_from_der(int *oids, size_t *oids_cnt, size_t max_cnt, con return 1; } +int x509_ext_key_usage_validate(const int *oids, size_t oids_cnt, int cert_type) +{ + int ret = -1; + size_t i; + + for (i = 0; i < oids_cnt; i++) { + // anyExtendedKeyUsage might not acceptable for strict validation + if (oids[i] == OID_any_extended_key_usage) { + ret = 0; + } + + switch (cert_type) { + case X509_cert_server_auth: + case X509_cert_server_key_encipher: + if (oids[i] == OID_kp_server_auth) { + return 1; + } + break; + + case X509_cert_client_auth: + case X509_cert_client_key_encipher: + if (oids[i] == OID_kp_client_auth) { + return 1; + } + break; + + default: + error_print(); + return -1; + } + } + return ret; +} + int x509_ext_key_usage_print(FILE *fp, int fmt, int ind, const char *label, const uint8_t *d, size_t dlen) { int oid; @@ -1791,3 +1909,87 @@ int x509_netscape_cert_type_print(FILE *fp, int fmt, int ind, const char *label, sizeof(netscape_cert_types)/sizeof(netscape_cert_types[0]), bits); } +int x509_exts_validate(const uint8_t *exts, size_t extslen, int cert_type, + int *path_len_constraints) +{ + int oid; + uint32_t nodes[32]; + size_t nodes_cnt; + int critical; + const uint8_t *val; + size_t vlen; + + int ca = -1; + int path_len = -1; + int key_usage; + int ext_key_usages[X509_MAX_KEY_PURPOSES]; + size_t ext_key_usages_cnt; + + *path_len_constraints = -1; + + while (extslen) { + if (x509_ext_from_der(&oid, nodes, &nodes_cnt, &critical, &val, &vlen, &exts, &extslen) != 1) { + error_print(); + return -1; + } + + switch (oid) { + case OID_ce_basic_constraints: + if (x509_basic_constraints_from_der(&ca, &path_len, &val, &vlen) != 1 + || x509_basic_constraints_validate(ca, path_len, cert_type) != 1) { + error_print(); + return -1; + } + break; + + case OID_ce_key_usage: + if (asn1_bits_from_der(&key_usage, &val, &vlen) != 1 + || x509_key_usage_validate(key_usage, cert_type) != 1) { + error_print(); + return -1; + } + break; + + case OID_ce_ext_key_usage: + if (x509_ext_key_usage_from_der(ext_key_usages, &ext_key_usages_cnt, + sizeof(ext_key_usages)/sizeof(ext_key_usages[0]), &val, &vlen) != 1 + || x509_ext_key_usage_validate(ext_key_usages, ext_key_usages_cnt, cert_type) != 1) { + error_print(); + return -1; + } + break; + + case OID_ce_authority_key_identifier: + case OID_ce_subject_key_identifier: + case OID_ce_certificate_policies: + case OID_ce_policy_mappings: + case OID_ce_subject_alt_name: + case OID_ce_issuer_alt_name: + case OID_ce_subject_directory_attributes: + case OID_ce_name_constraints: + case OID_ce_policy_constraints: + case OID_ce_crl_distribution_points: + case OID_ce_inhibit_any_policy: + case OID_ce_freshest_crl: + default: + if (critical) { + error_print(); + return -1; + } + } + } + + switch (cert_type) { + case X509_cert_ca: + if (ca != 1 || path_len < 0) { + error_print(); + return -1; + } + *path_len_constraints = path_len; + break; + } + + return 1; +} + + diff --git a/src/x509_oid.c b/src/x509_oid.c index fa670bcf..af26cc28 100644 --- a/src/x509_oid.c +++ b/src/x509_oid.c @@ -332,6 +332,8 @@ int x509_cert_policy_id_from_der(int *oid, uint32_t *nodes, size_t *nodes_cnt, c } +static uint32_t oid_any_extended_key_usage[] = { oid_ce,37,0 }; + #define oid_kp oid_pkix,3 static uint32_t oid_kp_server_auth[] = { oid_kp,1 }; @@ -343,6 +345,7 @@ static uint32_t oid_kp_ocsp_signing[] = { oid_kp,9 }; #define OID_KP_CNT sizeof(oid_kp_server_auth)/sizeof(int) static const ASN1_OID_INFO x509_key_purposes[] = { + { OID_any_extended_key_usage, "anyExtendedKeyUsage", oid_any_extended_key_usage, sizeof(oid_any_extended_key_usage)/sizeof(uint32_t), 0, "Any Extended Key Usage" }, { OID_kp_server_auth, "serverAuth", oid_kp_server_auth, OID_KP_CNT, 0, "TLS WWW server authentication" }, { OID_kp_client_auth, "clientAuth", oid_kp_client_auth, OID_KP_CNT, 0, "TLS WWW client authentication" }, { OID_kp_code_signing, "codeSigning", oid_kp_code_signing, OID_KP_CNT, 0, "Signing of downloadable executable code" },