summaryrefslogtreecommitdiffstats
path: root/src/ssl_utils.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/ssl_utils.c')
-rw-r--r--src/ssl_utils.c702
1 files changed, 702 insertions, 0 deletions
diff --git a/src/ssl_utils.c b/src/ssl_utils.c
new file mode 100644
index 0000000..4a85b89
--- /dev/null
+++ b/src/ssl_utils.c
@@ -0,0 +1,702 @@
+/*
+ * Utility functions for SSL:
+ * Mostly generic functions that retrieve information from certificates
+ *
+ * Copyright (C) 2012 EXCELIANCE, Emeric Brun <ebrun@exceliance.fr>
+ * Copyright (C) 2020 HAProxy Technologies, William Lallemand <wlallemand@haproxy.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+
+#include <haproxy/api.h>
+#include <haproxy/buf-t.h>
+#include <haproxy/chunk.h>
+#include <haproxy/openssl-compat.h>
+#include <haproxy/ssl_sock.h>
+#include <haproxy/ssl_utils.h>
+
+/* fill a buffer with the algorithm and size of a public key */
+int cert_get_pkey_algo(X509 *crt, struct buffer *out)
+{
+ int bits = 0;
+ int sig = TLSEXT_signature_anonymous;
+ int len = -1;
+ EVP_PKEY *pkey;
+
+ pkey = X509_get_pubkey(crt);
+ if (pkey) {
+ bits = EVP_PKEY_bits(pkey);
+ switch(EVP_PKEY_base_id(pkey)) {
+ case EVP_PKEY_RSA:
+ sig = TLSEXT_signature_rsa;
+ break;
+ case EVP_PKEY_EC:
+ sig = TLSEXT_signature_ecdsa;
+ break;
+ case EVP_PKEY_DSA:
+ sig = TLSEXT_signature_dsa;
+ break;
+ }
+ EVP_PKEY_free(pkey);
+ }
+
+ switch(sig) {
+ case TLSEXT_signature_rsa:
+ len = chunk_printf(out, "RSA%d", bits);
+ break;
+ case TLSEXT_signature_ecdsa:
+ len = chunk_printf(out, "EC%d", bits);
+ break;
+ case TLSEXT_signature_dsa:
+ len = chunk_printf(out, "DSA%d", bits);
+ break;
+ default:
+ return 0;
+ }
+ if (len < 0)
+ return 0;
+ return 1;
+}
+
+/* Extract a serial from a cert, and copy it to a chunk.
+ * Returns 1 if serial is found and copied, 0 if no serial found and
+ * -1 if output is not large enough.
+ */
+int ssl_sock_get_serial(X509 *crt, struct buffer *out)
+{
+ ASN1_INTEGER *serial;
+
+ serial = X509_get_serialNumber(crt);
+ if (!serial)
+ return 0;
+
+ if (out->size < serial->length)
+ return -1;
+
+ memcpy(out->area, serial->data, serial->length);
+ out->data = serial->length;
+ return 1;
+}
+
+/* Extract a cert to der, and copy it to a chunk.
+ * Returns 1 if the cert is found and copied, 0 on der conversion failure
+ * and -1 if the output is not large enough.
+ */
+int ssl_sock_crt2der(X509 *crt, struct buffer *out)
+{
+ int len;
+ unsigned char *p = (unsigned char *) out->area;
+
+ len = i2d_X509(crt, NULL);
+ if (len <= 0)
+ return 1;
+
+ if (out->size < len)
+ return -1;
+
+ i2d_X509(crt, &p);
+ out->data = len;
+ return 1;
+}
+
+
+/* Copy Date in ASN1_UTCTIME format in struct buffer out.
+ * Returns 1 if serial is found and copied, 0 if no valid time found
+ * and -1 if output is not large enough.
+ */
+int ssl_sock_get_time(ASN1_TIME *tm, struct buffer *out)
+{
+ if (tm->type == V_ASN1_GENERALIZEDTIME) {
+ ASN1_GENERALIZEDTIME *gentm = (ASN1_GENERALIZEDTIME *)tm;
+
+ if (gentm->length < 12)
+ return 0;
+ if (gentm->data[0] != 0x32 || gentm->data[1] != 0x30)
+ return 0;
+ if (out->size < gentm->length-2)
+ return -1;
+
+ memcpy(out->area, gentm->data+2, gentm->length-2);
+ out->data = gentm->length-2;
+ return 1;
+ }
+ else if (tm->type == V_ASN1_UTCTIME) {
+ ASN1_UTCTIME *utctm = (ASN1_UTCTIME *)tm;
+
+ if (utctm->length < 10)
+ return 0;
+ if (utctm->data[0] >= 0x35)
+ return 0;
+ if (out->size < utctm->length)
+ return -1;
+
+ memcpy(out->area, utctm->data, utctm->length);
+ out->data = utctm->length;
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Extract an entry from a X509_NAME and copy its value to an output chunk.
+ * Returns 1 if entry found, 0 if entry not found, or -1 if output not large enough.
+ */
+int ssl_sock_get_dn_entry(X509_NAME *a, const struct buffer *entry, int pos,
+ struct buffer *out)
+{
+ X509_NAME_ENTRY *ne;
+ ASN1_OBJECT *obj;
+ ASN1_STRING *data;
+ const unsigned char *data_ptr;
+ int data_len;
+ int i, j, n;
+ int cur = 0;
+ const char *s;
+ char tmp[128];
+ int name_count;
+
+ name_count = X509_NAME_entry_count(a);
+
+ out->data = 0;
+ for (i = 0; i < name_count; i++) {
+ if (pos < 0)
+ j = (name_count-1) - i;
+ else
+ j = i;
+
+ ne = X509_NAME_get_entry(a, j);
+ obj = X509_NAME_ENTRY_get_object(ne);
+ data = X509_NAME_ENTRY_get_data(ne);
+ data_ptr = ASN1_STRING_get0_data(data);
+ data_len = ASN1_STRING_length(data);
+ n = OBJ_obj2nid(obj);
+ if ((n == NID_undef) || ((s = OBJ_nid2sn(n)) == NULL)) {
+ i2t_ASN1_OBJECT(tmp, sizeof(tmp), obj);
+ s = tmp;
+ }
+
+ if (chunk_strcasecmp(entry, s) != 0)
+ continue;
+
+ if (pos < 0)
+ cur--;
+ else
+ cur++;
+
+ if (cur != pos)
+ continue;
+
+ if (data_len > out->size)
+ return -1;
+
+ memcpy(out->area, data_ptr, data_len);
+ out->data = data_len;
+ return 1;
+ }
+
+ return 0;
+
+}
+
+/*
+ * Extract the DN in the specified format from the X509_NAME and copy result to a chunk.
+ * Currently supports rfc2253 for returning LDAP V3 DNs.
+ * Returns 1 if dn entries exist, 0 if no dn entry was found.
+ */
+int ssl_sock_get_dn_formatted(X509_NAME *a, const struct buffer *format, struct buffer *out)
+{
+ BIO *bio = NULL;
+ int ret = 0;
+ int data_len = 0;
+
+ if (chunk_strcmp(format, "rfc2253") == 0) {
+ bio = BIO_new(BIO_s_mem());
+ if (bio == NULL)
+ goto out;
+
+ if (X509_NAME_print_ex(bio, a, 0, XN_FLAG_RFC2253) < 0)
+ goto out;
+
+ if ((data_len = BIO_read(bio, out->area, out->size)) <= 0)
+ goto out;
+
+ out->data = data_len;
+
+ ret = 1;
+ }
+out:
+ if (bio)
+ BIO_free(bio);
+ return ret;
+}
+
+/* Extract and format full DN from a X509_NAME and copy result into a chunk
+ * Returns 1 if dn entries exits, 0 if no dn entry found or -1 if output is not large enough.
+ */
+int ssl_sock_get_dn_oneline(X509_NAME *a, struct buffer *out)
+{
+ X509_NAME_ENTRY *ne;
+ ASN1_OBJECT *obj;
+ ASN1_STRING *data;
+ const unsigned char *data_ptr;
+ int data_len;
+ int i, n, ln;
+ int l = 0;
+ const char *s;
+ char *p;
+ char tmp[128];
+ int name_count;
+
+
+ name_count = X509_NAME_entry_count(a);
+
+ out->data = 0;
+ p = out->area;
+ for (i = 0; i < name_count; i++) {
+ ne = X509_NAME_get_entry(a, i);
+ obj = X509_NAME_ENTRY_get_object(ne);
+ data = X509_NAME_ENTRY_get_data(ne);
+ data_ptr = ASN1_STRING_get0_data(data);
+ data_len = ASN1_STRING_length(data);
+ n = OBJ_obj2nid(obj);
+ if ((n == NID_undef) || ((s = OBJ_nid2sn(n)) == NULL)) {
+ i2t_ASN1_OBJECT(tmp, sizeof(tmp), obj);
+ s = tmp;
+ }
+ ln = strlen(s);
+
+ l += 1 + ln + 1 + data_len;
+ if (l > out->size)
+ return -1;
+ out->data = l;
+
+ *(p++)='/';
+ memcpy(p, s, ln);
+ p += ln;
+ *(p++)='=';
+ memcpy(p, data_ptr, data_len);
+ p += data_len;
+ }
+
+ if (!out->data)
+ return 0;
+
+ return 1;
+}
+
+
+extern int ssl_client_crt_ref_index;
+
+/*
+ * This function fetches the SSL certificate for a specific connection (either
+ * client certificate or server certificate depending on the cert_peer
+ * parameter).
+ * When trying to get the peer certificate from the server side, we first try to
+ * use the dedicated SSL_get_peer_certificate function, but we fall back to
+ * trying to get the client certificate reference that might have been stored in
+ * the SSL structure's ex_data during the verification process.
+ * Returns NULL in case of failure.
+ */
+X509* ssl_sock_get_peer_certificate(SSL *ssl)
+{
+ X509* cert;
+
+ cert = SSL_get_peer_certificate(ssl);
+ /* Get the client certificate reference stored in the SSL
+ * structure's ex_data during the verification process. */
+ if (!cert) {
+ cert = SSL_get_ex_data(ssl, ssl_client_crt_ref_index);
+ if (cert)
+ X509_up_ref(cert);
+ }
+
+ return cert;
+}
+
+/*
+ * This function fetches the x509* for the root CA of client certificate
+ * from the verified chain. We use the SSL_get0_verified_chain and get the
+ * last certificate in the x509 stack.
+ *
+ * Returns NULL in case of failure.
+*/
+#ifdef HAVE_SSL_get0_verified_chain
+X509* ssl_sock_get_verified_chain_root(SSL *ssl)
+{
+ STACK_OF(X509) *chain = NULL;
+ X509 *crt = NULL;
+ int i;
+
+ chain = SSL_get0_verified_chain(ssl);
+ if (!chain)
+ return NULL;
+
+ for (i = 0; i < sk_X509_num(chain); i++) {
+ crt = sk_X509_value(chain, i);
+
+ if (X509_check_issued(crt, crt) == X509_V_OK)
+ break;
+ }
+
+ return crt;
+}
+#endif
+
+/*
+ * Take an OpenSSL version in text format and return a numeric openssl version
+ * Return 0 if it failed to parse the version
+ *
+ * https://www.openssl.org/docs/man1.1.1/man3/OPENSSL_VERSION_NUMBER.html
+ *
+ * MNNFFPPS: major minor fix patch status
+ *
+ * The status nibble has one of the values 0 for development, 1 to e for betas
+ * 1 to 14, and f for release.
+ *
+ * for example
+ *
+ * 0x0090821f 0.9.8zh
+ * 0x1000215f 1.0.2u
+ * 0x30000000 3.0.0-alpha17
+ * 0x30000002 3.0.0-beta2
+ * 0x3000000e 3.0.0-beta14
+ * 0x3000000f 3.0.0
+ */
+unsigned int openssl_version_parser(const char *version)
+{
+ unsigned int numversion;
+ unsigned int major = 0, minor = 0, fix = 0, patch = 0, status = 0;
+ char *p, *end;
+
+ p = (char *)version;
+
+ if (!p || !*p)
+ return 0;
+
+ major = strtol(p, &end, 10);
+ if (*end != '.' || major > 0xf)
+ goto error;
+ p = end + 1;
+
+ minor = strtol(p, &end, 10);
+ if (*end != '.' || minor > 0xff)
+ goto error;
+ p = end + 1;
+
+ fix = strtol(p, &end, 10);
+ if (fix > 0xff)
+ goto error;
+ p = end;
+
+ if (!*p) {
+ /* end of the string, that's a release */
+ status = 0xf;
+ } else if (*p == '-') {
+ /* after the hyphen, only the beta will increment the status
+ * counter, all others versions will be considered as "dev" and
+ * does not increment anything */
+ p++;
+
+ if (!strncmp(p, "beta", 4)) {
+ p += 4;
+ status = strtol(p, &end, 10);
+ if (status > 14)
+ goto error;
+ }
+ } else {
+ /* that's a patch release */
+ patch = 1;
+
+ /* add the value of each letter */
+ while (*p) {
+ patch += (*p & ~0x20) - 'A';
+ p++;
+ }
+ status = 0xf;
+ }
+
+end:
+ numversion = ((major & 0xf) << 28) | ((minor & 0xff) << 20) | ((fix & 0xff) << 12) | ((patch & 0xff) << 4) | (status & 0xf);
+ return numversion;
+
+error:
+ return 0;
+
+}
+
+/* Exclude GREASE (RFC8701) values from input buffer */
+void exclude_tls_grease(char *input, int len, struct buffer *output)
+{
+ int ptr = 0;
+
+ while (ptr < len - 1) {
+ if (input[ptr] != input[ptr+1] || (input[ptr] & 0x0f) != 0x0a) {
+ if (output->data <= output->size - 2) {
+ memcpy(output->area + output->data, input + ptr, 2);
+ output->data += 2;
+ } else
+ break;
+ }
+ ptr += 2;
+ }
+ if (output->size - output->data > 0 && len - ptr > 0)
+ output->area[output->data++] = input[ptr];
+}
+
+/*
+ * The following generates an array <x509_v_codes> in which the X509_V_ERR_*
+ * codes are populated with there string equivalent. Depending on the version
+ * of the SSL library, some code does not exist, these will be populated as
+ * "-1" in the array.
+ *
+ * The list was taken from
+ * https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in
+ * and must be updated when new constant are introduced.
+ */
+
+#undef _Q
+#define _Q(x) (#x)
+#undef V
+#define V(x) { .code = -1, .value = _Q(x), .string = #x }
+
+static struct x509_v_codes {
+ int code; // integer value of the code or -1 if undefined
+ const char *value; // value of the macro as a string or its name
+ const char *string; // name of the macro
+} x509_v_codes[] = {
+ V(X509_V_OK),
+ V(X509_V_ERR_UNSPECIFIED),
+ V(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT),
+ V(X509_V_ERR_UNABLE_TO_GET_CRL),
+ V(X509_V_ERR_UNABLE_TO_DECRYPT_CERT_SIGNATURE),
+ V(X509_V_ERR_UNABLE_TO_DECRYPT_CRL_SIGNATURE),
+ V(X509_V_ERR_UNABLE_TO_DECODE_ISSUER_PUBLIC_KEY),
+ V(X509_V_ERR_CERT_SIGNATURE_FAILURE),
+ V(X509_V_ERR_CRL_SIGNATURE_FAILURE),
+ V(X509_V_ERR_CERT_NOT_YET_VALID),
+ V(X509_V_ERR_CERT_HAS_EXPIRED),
+ V(X509_V_ERR_CRL_NOT_YET_VALID),
+ V(X509_V_ERR_CRL_HAS_EXPIRED),
+ V(X509_V_ERR_ERROR_IN_CERT_NOT_BEFORE_FIELD),
+ V(X509_V_ERR_ERROR_IN_CERT_NOT_AFTER_FIELD),
+ V(X509_V_ERR_ERROR_IN_CRL_LAST_UPDATE_FIELD),
+ V(X509_V_ERR_ERROR_IN_CRL_NEXT_UPDATE_FIELD),
+ V(X509_V_ERR_OUT_OF_MEM),
+ V(X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT),
+ V(X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN),
+ V(X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY),
+ V(X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE),
+ V(X509_V_ERR_CERT_CHAIN_TOO_LONG),
+ V(X509_V_ERR_CERT_REVOKED),
+ V(X509_V_ERR_NO_ISSUER_PUBLIC_KEY),
+ V(X509_V_ERR_PATH_LENGTH_EXCEEDED),
+ V(X509_V_ERR_INVALID_PURPOSE),
+ V(X509_V_ERR_CERT_UNTRUSTED),
+ V(X509_V_ERR_CERT_REJECTED),
+ V(X509_V_ERR_SUBJECT_ISSUER_MISMATCH),
+ V(X509_V_ERR_AKID_SKID_MISMATCH),
+ V(X509_V_ERR_AKID_ISSUER_SERIAL_MISMATCH),
+ V(X509_V_ERR_KEYUSAGE_NO_CERTSIGN),
+ V(X509_V_ERR_UNABLE_TO_GET_CRL_ISSUER),
+ V(X509_V_ERR_UNHANDLED_CRITICAL_EXTENSION),
+ V(X509_V_ERR_KEYUSAGE_NO_CRL_SIGN),
+ V(X509_V_ERR_UNHANDLED_CRITICAL_CRL_EXTENSION),
+ V(X509_V_ERR_INVALID_NON_CA),
+ V(X509_V_ERR_PROXY_PATH_LENGTH_EXCEEDED),
+ V(X509_V_ERR_KEYUSAGE_NO_DIGITAL_SIGNATURE),
+ V(X509_V_ERR_PROXY_CERTIFICATES_NOT_ALLOWED),
+ V(X509_V_ERR_INVALID_EXTENSION),
+ V(X509_V_ERR_INVALID_POLICY_EXTENSION),
+ V(X509_V_ERR_NO_EXPLICIT_POLICY),
+ V(X509_V_ERR_DIFFERENT_CRL_SCOPE),
+ V(X509_V_ERR_UNSUPPORTED_EXTENSION_FEATURE),
+ V(X509_V_ERR_UNNESTED_RESOURCE),
+ V(X509_V_ERR_PERMITTED_VIOLATION),
+ V(X509_V_ERR_EXCLUDED_VIOLATION),
+ V(X509_V_ERR_SUBTREE_MINMAX),
+ V(X509_V_ERR_APPLICATION_VERIFICATION),
+ V(X509_V_ERR_UNSUPPORTED_CONSTRAINT_TYPE),
+ V(X509_V_ERR_UNSUPPORTED_CONSTRAINT_SYNTAX),
+ V(X509_V_ERR_UNSUPPORTED_NAME_SYNTAX),
+ V(X509_V_ERR_CRL_PATH_VALIDATION_ERROR),
+ V(X509_V_ERR_PATH_LOOP),
+ V(X509_V_ERR_SUITE_B_INVALID_VERSION),
+ V(X509_V_ERR_SUITE_B_INVALID_ALGORITHM),
+ V(X509_V_ERR_SUITE_B_INVALID_CURVE),
+ V(X509_V_ERR_SUITE_B_INVALID_SIGNATURE_ALGORITHM),
+ V(X509_V_ERR_SUITE_B_LOS_NOT_ALLOWED),
+ V(X509_V_ERR_SUITE_B_CANNOT_SIGN_P_384_WITH_P_256),
+ V(X509_V_ERR_HOSTNAME_MISMATCH),
+ V(X509_V_ERR_EMAIL_MISMATCH),
+ V(X509_V_ERR_IP_ADDRESS_MISMATCH),
+ V(X509_V_ERR_DANE_NO_MATCH),
+ V(X509_V_ERR_EE_KEY_TOO_SMALL),
+ V(X509_V_ERR_CA_KEY_TOO_SMALL),
+ V(X509_V_ERR_CA_MD_TOO_WEAK),
+ V(X509_V_ERR_INVALID_CALL),
+ V(X509_V_ERR_STORE_LOOKUP),
+ V(X509_V_ERR_NO_VALID_SCTS),
+ V(X509_V_ERR_PROXY_SUBJECT_NAME_VIOLATION),
+ V(X509_V_ERR_OCSP_VERIFY_NEEDED),
+ V(X509_V_ERR_OCSP_VERIFY_FAILED),
+ V(X509_V_ERR_OCSP_CERT_UNKNOWN),
+ V(X509_V_ERR_UNSUPPORTED_SIGNATURE_ALGORITHM),
+ V(X509_V_ERR_SIGNATURE_ALGORITHM_MISMATCH),
+ V(X509_V_ERR_SIGNATURE_ALGORITHM_INCONSISTENCY),
+ V(X509_V_ERR_INVALID_CA),
+ V(X509_V_ERR_PATHLEN_INVALID_FOR_NON_CA),
+ V(X509_V_ERR_PATHLEN_WITHOUT_KU_KEY_CERT_SIGN),
+ V(X509_V_ERR_KU_KEY_CERT_SIGN_INVALID_FOR_NON_CA),
+ V(X509_V_ERR_ISSUER_NAME_EMPTY),
+ V(X509_V_ERR_SUBJECT_NAME_EMPTY),
+ V(X509_V_ERR_MISSING_AUTHORITY_KEY_IDENTIFIER),
+ V(X509_V_ERR_MISSING_SUBJECT_KEY_IDENTIFIER),
+ V(X509_V_ERR_EMPTY_SUBJECT_ALT_NAME),
+ V(X509_V_ERR_EMPTY_SUBJECT_SAN_NOT_CRITICAL),
+ V(X509_V_ERR_CA_BCONS_NOT_CRITICAL),
+ V(X509_V_ERR_AUTHORITY_KEY_IDENTIFIER_CRITICAL),
+ V(X509_V_ERR_SUBJECT_KEY_IDENTIFIER_CRITICAL),
+ V(X509_V_ERR_CA_CERT_MISSING_KEY_USAGE),
+ V(X509_V_ERR_EXTENSIONS_REQUIRE_VERSION_3),
+ V(X509_V_ERR_EC_KEY_EXPLICIT_PARAMS),
+ { 0, NULL, NULL },
+};
+
+/*
+ * Return the X509_V_ERR code corresponding to the name of the constant.
+ * See https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in
+ * If not found, return -1
+ */
+int x509_v_err_str_to_int(const char *str)
+{
+ int i;
+
+ for (i = 0; x509_v_codes[i].string; i++) {
+ if (strcmp(str, x509_v_codes[i].string) == 0) {
+ return x509_v_codes[i].code;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * Return the constant name corresponding to the X509_V_ERR code
+ * See https://github.com/openssl/openssl/blob/master/include/openssl/x509_vfy.h.in
+ * If not found, return NULL;
+ */
+const char *x509_v_err_int_to_str(int code)
+{
+ int i;
+
+ if (code == -1)
+ return NULL;
+
+ for (i = 0; x509_v_codes[i].string; i++) {
+ if (x509_v_codes[i].code == code) {
+ return x509_v_codes[i].string;
+ }
+ }
+ return NULL;
+}
+
+void init_x509_v_err_tab(void)
+{
+ int i;
+
+ for (i = 0; x509_v_codes[i].string; i++) {
+ /* either the macro exists or it's equal to its own name */
+ if (strcmp(x509_v_codes[i].string, x509_v_codes[i].value) == 0)
+ continue;
+ x509_v_codes[i].code = atoi(x509_v_codes[i].value);
+ }
+}
+
+INITCALL0(STG_REGISTER, init_x509_v_err_tab);
+
+
+/*
+ * This function returns the number of seconds elapsed
+ * since the Epoch, 1970-01-01 00:00:00 +0000 (UTC) and the
+ * date presented un ASN1_GENERALIZEDTIME.
+ *
+ * In parsing error case, it returns -1.
+ */
+long asn1_generalizedtime_to_epoch(ASN1_GENERALIZEDTIME *d)
+{
+ long epoch;
+ char *p, *end;
+ const unsigned short month_offset[12] = {
+ 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334
+ };
+ unsigned long year, month;
+
+ if (!d || (d->type != V_ASN1_GENERALIZEDTIME)) return -1;
+
+ p = (char *)d->data;
+ end = p + d->length;
+
+ if (end - p < 4) return -1;
+ year = 1000 * (p[0] - '0') + 100 * (p[1] - '0') + 10 * (p[2] - '0') + p[3] - '0';
+ p += 4;
+ if (end - p < 2) return -1;
+ month = 10 * (p[0] - '0') + p[1] - '0';
+ if (month < 1 || month > 12) return -1;
+ /* Compute the number of seconds since 1 jan 1970 and the beginning of current month
+ We consider leap years and the current month (<marsh or not) */
+ epoch = ( ((year - 1970) * 365)
+ + ((year - (month < 3)) / 4 - (year - (month < 3)) / 100 + (year - (month < 3)) / 400)
+ - ((1970 - 1) / 4 - (1970 - 1) / 100 + (1970 - 1) / 400)
+ + month_offset[month-1]
+ ) * 24 * 60 * 60;
+ p += 2;
+ if (end - p < 2) return -1;
+ /* Add the number of seconds of completed days of current month */
+ epoch += (10 * (p[0] - '0') + p[1] - '0' - 1) * 24 * 60 * 60;
+ p += 2;
+ if (end - p < 2) return -1;
+ /* Add the completed hours of the current day */
+ epoch += (10 * (p[0] - '0') + p[1] - '0') * 60 * 60;
+ p += 2;
+ if (end - p < 2) return -1;
+ /* Add the completed minutes of the current hour */
+ epoch += (10 * (p[0] - '0') + p[1] - '0') * 60;
+ p += 2;
+ if (p == end) return -1;
+ /* Test if there is available seconds */
+ if (p[0] < '0' || p[0] > '9')
+ goto nosec;
+ if (end - p < 2) return -1;
+ /* Add the seconds of the current minute */
+ epoch += 10 * (p[0] - '0') + p[1] - '0';
+ p += 2;
+ if (p == end) return -1;
+ /* Ignore seconds float part if present */
+ if (p[0] == '.') {
+ do {
+ if (++p == end) return -1;
+ } while (p[0] >= '0' && p[0] <= '9');
+ }
+
+nosec:
+ if (p[0] == 'Z') {
+ if (end - p != 1) return -1;
+ return epoch;
+ }
+ else if (p[0] == '+') {
+ if (end - p != 5) return -1;
+ /* Apply timezone offset */
+ return epoch - ((10 * (p[1] - '0') + p[2] - '0') * 60 * 60 + (10 * (p[3] - '0') + p[4] - '0')) * 60;
+ }
+ else if (p[0] == '-') {
+ if (end - p != 5) return -1;
+ /* Apply timezone offset */
+ return epoch + ((10 * (p[1] - '0') + p[2] - '0') * 60 * 60 + (10 * (p[3] - '0') + p[4] - '0')) * 60;
+ }
+
+ return -1;
+}