/** * Copyright (C) 2021 Graham Leggett * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ /* * redwax_openssl - OpenSSL routines for munching certificates * */ #include #include #include #include "config.h" #include "redwax-tool.h" #include "redwax_util.h" #if HAVE_OPENSSL_PEM_H #include #include #include #include #include #include #include #include #include #include #if HAVE_OPENSSL_CORE_NAMES_H #include #endif #if HAVE_OPENSSL_CT_H #include #endif #include #define REDWAX_OPENSSL_SEARCH "search" #define REDWAX_OPENSSL_VERIFY "verify" #define REDWAX_ORDER_ALL_TEXT "all" #define REDWAX_ORDER_KEY_FIRST_TEXT "key-first" #define REDWAX_ORDER_KEY_LAST_TEXT "key-last" #define REDWAX_EXPIRY_CHECK_TEXT "check" #define REDWAX_EXPIRY_IGNORE_TEXT "ignore" #define REDWAX_EXPIRY_IGNORE_LEAF_TEXT "ignore-leaf" #define REDWAX_EXPIRY_IGNORE_CHAIN_TEXT "ignore-chain" #define REDWAX_DANE_CHECK_TEXT "check" #define REDWAX_DANE_IGNORE_TEXT "ignore" #define REDWAX_PKCS12_MIN 8 #define REDWAX_PKCS12_MAX HUGE_STRING_LEN module openssl_module; typedef struct { SSL_CTX *dane_ctx; SSL *dane_ssl; } openssl_config_t; typedef struct { /* verification result code */ int verification; } openssl_certificate_config_t; typedef struct { /* nothing yet */ } openssl_key_config_t; // move to config above static STACK_OF(X509) *cert_index; static STACK_OF(X509) *chain_index; static STACK_OF(X509) *trusted_index; static STACK_OF(X509_CRL) *crl_index; static int redwax_x509_idx = -1; static int redwax_x509_store_ctx_idx = -1; static int redwax_get_x509_index() { if (redwax_x509_idx == -1) { redwax_x509_idx = X509_get_ex_new_index(0, "Application Data for X509", NULL, NULL, NULL); } return redwax_x509_idx; } static int redwax_get_x509_store_ctx_index() { if (redwax_x509_store_ctx_idx == -1) { redwax_x509_store_ctx_idx = X509_STORE_CTX_get_ex_new_index(0, "Application Data for X509_STORE_CTX", NULL, NULL, NULL); } return redwax_x509_store_ctx_idx; } /* * Work around new APIs that don't exist on openssl 1.0.x. * * Also work around new APIs that were backported to openssl 1.0.x but * not added to the headers, breaking the autoconf detection and causing * havoc. */ #if !HAVE_PKCS12_SAFEBAG_GET0_SAFES #define PKCS12_SAFEBAG_get0_safes(bag) bag->value.safes #endif #if !HAVE_PKCS12_SAFEBAG_GET_BAG_NID #define PKCS12_SAFEBAG_get_bag_nid M_PKCS12_cert_bag_type #endif #if !HAVE_PKCS12_SAFEBAG_GET_NID #define PKCS12_SAFEBAG_get_nid M_PKCS12_bag_type #endif #if !HAVE_PKCS12_SAFEBAG_GET0_ATTR #define PKCS12_SAFEBAG_get0_attr PKCS12_get_attr #endif #if !HAVE_PKCS12_SAFEBAG_GET0_P8INF #define PKCS12_SAFEBAG_get0_p8inf(bag) bag->value.keybag #endif #if !HAVE_PKCS12_SAFEBAG_GET1_CERT #define PKCS12_SAFEBAG_get1_cert PKCS12_certbag2x509 #endif #if !HAVE_PKCS12_SAFEBAG_GET1_CRL #define PKCS12_SAFEBAG_get1_crl PKCS12_certbag2x509crl #endif #if !HAVE_ASN1_TIME_DIFF || !HAVE_ASN1_TIME_PRINT_EX #define SECS_PER_DAY (24 * 60 * 60) /* * Convert date to and from julian day Uses Fliegel & Van Flandern algorithm */ static long date_to_julian(int y, int m, int d) { return (1461 * (y + 4800 + (m - 14) / 12)) / 4 + (367 * (m - 2 - 12 * ((m - 14) / 12))) / 12 - (3 * ((y + 4900 + (m - 14) / 12) / 100)) / 4 + d - 32075; } static void julian_to_date(long jd, int *y, int *m, int *d) { long L = jd + 68569; long n = (4 * L) / 146097; long i, j; L = L - (146097 * n + 3) / 4; i = (4000 * (L + 1)) / 1461001; L = L - (1461 * i) / 4 + 31; j = (80 * L) / 2447; *d = L - (2447 * j) / 80; L = j / 11; *m = j + 2 - (12 * L); *y = 100 * (n - 49) + i + L; } /* Convert tm structure and offset into julian day and seconds */ static int julian_adj(const struct tm *tm, int off_day, long offset_sec, long *pday, int *psec) { int offset_hms, offset_day; long time_jd; int time_year, time_month, time_day; /* split offset into days and day seconds */ offset_day = offset_sec / SECS_PER_DAY; /* Avoid sign issues with % operator */ offset_hms = offset_sec - (offset_day * SECS_PER_DAY); offset_day += off_day; /* Add current time seconds to offset */ offset_hms += tm->tm_hour * 3600 + tm->tm_min * 60 + tm->tm_sec; /* Adjust day seconds if overflow */ if (offset_hms >= SECS_PER_DAY) { offset_day++; offset_hms -= SECS_PER_DAY; } else if (offset_hms < 0) { offset_day--; offset_hms += SECS_PER_DAY; } /* * Convert date of time structure into a Julian day number. */ time_year = tm->tm_year + 1900; time_month = tm->tm_mon + 1; time_day = tm->tm_mday; time_jd = date_to_julian(time_year, time_month, time_day); /* Work out Julian day of new date */ time_jd += offset_day; if (time_jd < 0) return 0; *pday = time_jd; *psec = offset_hms; return 1; } int redwax_OPENSSL_gmtime_adj(struct tm *tm, int off_day, long offset_sec) { int time_sec, time_year, time_month, time_day; long time_jd; /* Convert time and offset into Julian day and seconds */ if (!julian_adj(tm, off_day, offset_sec, &time_jd, &time_sec)) return 0; /* Convert Julian day back to date */ julian_to_date(time_jd, &time_year, &time_month, &time_day); if (time_year < 1900 || time_year > 9999) return 0; /* Update tm structure */ tm->tm_year = time_year - 1900; tm->tm_mon = time_month - 1; tm->tm_mday = time_day; tm->tm_hour = time_sec / 3600; tm->tm_min = (time_sec / 60) % 60; tm->tm_sec = time_sec % 60; return 1; } struct tm *redwax_OPENSSL_gmtime(const time_t *timer, struct tm *result) { struct tm *ts = NULL; #if defined(OPENSSL_THREADS) && defined(OPENSSL_SYS_VMS) { /* * On VMS, gmtime_r() takes a 32-bit pointer as second argument. * Since we can't know that |result| is in a space that can easily * translate to a 32-bit pointer, we must store temporarily on stack * and copy the result. The stack is always reachable with 32-bit * pointers. */ #if defined(OPENSSL_SYS_VMS) && __INITIAL_POINTER_SIZE # pragma pointer_size save # pragma pointer_size 32 #endif struct tm data, *ts2 = &data; #if defined OPENSSL_SYS_VMS && __INITIAL_POINTER_SIZE # pragma pointer_size restore #endif if (gmtime_r(timer, ts2) == NULL) return NULL; memcpy(result, ts2, sizeof(struct tm)); ts = result; } #elif defined(OPENSSL_THREADS) && !defined(OPENSSL_SYS_WIN32) && !defined(OPENSSL_SYS_MACOSX) if (gmtime_r(timer, result) == NULL) return NULL; ts = result; #elif defined (OPENSSL_SYS_WINDOWS) && defined(_MSC_VER) && _MSC_VER >= 1400 if (gmtime_s(result, timer)) return NULL; ts = result; #else ts = gmtime(timer); if (ts == NULL) return NULL; memcpy(result, ts, sizeof(struct tm)); ts = result; #endif return ts; } #ifndef ASN1_STRING_FLAG_X509_TIME #define ASN1_STRING_FLAG_X509_TIME 0x100 #endif static int leap_year(const int year) { if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)) return 1; return 0; } int ascii_isdigit(const char inchar) { if (inchar > 0x2F && inchar < 0x3A) return 1; return 0; } /* * Compute the day of the week and the day of the year from the year, month * and day. The day of the year is straightforward, the day of the week uses * a form of Zeller's congruence. For this months start with March and are * numbered 4 through 15. */ static void determine_days(struct tm *tm) { static const int ydays[12] = { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 }; int y = tm->tm_year + 1900; int m = tm->tm_mon; int d = tm->tm_mday; int c; tm->tm_yday = ydays[m] + d - 1; if (m >= 2) { /* March and onwards can be one day further into the year */ tm->tm_yday += leap_year(y); m += 2; } else { /* Treat January and February as part of the previous year */ m += 14; y--; } c = y / 100; y %= 100; /* Zeller's congruence */ tm->tm_wday = (d + (13 * m) / 5 + y + y / 4 + c / 4 + 5 * c + 6) % 7; } int asn1_time_to_tm(struct tm *tm, const ASN1_TIME *d) { static const int min[9] = { 0, 0, 1, 1, 0, 0, 0, 0, 0 }; static const int max[9] = { 99, 99, 12, 31, 23, 59, 59, 12, 59 }; static const int mdays[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; char *a; int n, i, i2, l, o, min_l = 11, strict = 0, end = 6, btz = 5, md; struct tm tmp; #if defined(CHARSET_EBCDIC) const char upper_z = 0x5A, num_zero = 0x30, period = 0x2E, minus = 0x2D, plus = 0x2B; #else const char upper_z = 'Z', num_zero = '0', period = '.', minus = '-', plus = '+'; #endif /* * ASN1_STRING_FLAG_X509_TIME is used to enforce RFC 5280 * time string format, in which: * * 1. "seconds" is a 'MUST' * 2. "Zulu" timezone is a 'MUST' * 3. "+|-" is not allowed to indicate a time zone */ if (d->type == V_ASN1_UTCTIME) { if (d->flags & ASN1_STRING_FLAG_X509_TIME) { min_l = 13; strict = 1; } } else if (d->type == V_ASN1_GENERALIZEDTIME) { end = 7; btz = 6; if (d->flags & ASN1_STRING_FLAG_X509_TIME) { min_l = 15; strict = 1; } else { min_l = 13; } } else { return 0; } l = d->length; a = (char *)d->data; o = 0; memset(&tmp, 0, sizeof(tmp)); /* * GENERALIZEDTIME is similar to UTCTIME except the year is represented * as YYYY. This stuff treats everything as a two digit field so make * first two fields 00 to 99 */ if (l < min_l) goto err; for (i = 0; i < end; i++) { if (!strict && (i == btz) && ((a[o] == upper_z) || (a[o] == plus) || (a[ o] == minus))) { i++; break; } if (!ascii_isdigit(a[o])) goto err; n = a[o] - num_zero; /* incomplete 2-digital number */ if (++o == l) goto err; if (!ascii_isdigit(a[o])) goto err; n = (n * 10) + a[o] - num_zero; /* no more bytes to read, but we haven't seen time-zone yet */ if (++o == l) goto err; i2 = (d->type == V_ASN1_UTCTIME) ? i + 1 : i; if ((n < min[i2]) || (n > max[i2])) goto err; switch (i2) { case 0: /* UTC will never be here */ tmp.tm_year = n * 100 - 1900; break; case 1: if (d->type == V_ASN1_UTCTIME) tmp.tm_year = n < 50 ? n + 100 : n; else tmp.tm_year += n; break; case 2: tmp.tm_mon = n - 1; break; case 3: /* check if tm_mday is valid in tm_mon */ if (tmp.tm_mon == 1) { /* it's February */ md = mdays[1] + leap_year(tmp.tm_year + 1900); } else { md = mdays[tmp.tm_mon]; } if (n > md) goto err; tmp.tm_mday = n; determine_days(&tmp); break; case 4: tmp.tm_hour = n; break; case 5: tmp.tm_min = n; break; case 6: tmp.tm_sec = n; break; } } /* * Optional fractional seconds: decimal point followed by one or more * digits. */ if (d->type == V_ASN1_GENERALIZEDTIME && a[o] == period) { if (strict) /* RFC 5280 forbids fractional seconds */ goto err; if (++o == l) goto err; i = o; while ((o < l) && ascii_isdigit(a[o])) o++; /* Must have at least one digit after decimal point */ if (i == o) goto err; /* no more bytes to read, but we haven't seen time-zone yet */ if (o == l) goto err; } /* * 'o' will never point to '\0' at this point, the only chance * 'o' can point to '\0' is either the subsequent if or the first * else if is true. */ if (a[o] == upper_z) { o++; } else if (!strict && ((a[o] == plus) || (a[o] == minus))) { int offsign = a[o] == minus ? 1 : -1; int offset = 0; o++; /* * if not equal, no need to do subsequent checks * since the following for-loop will add 'o' by 4 * and the final return statement will check if 'l' * and 'o' are equal. */ if (o + 4 != l) goto err; for (i = end; i < end + 2; i++) { if (!ascii_isdigit(a[o])) goto err; n = a[o] - num_zero; o++; if (!ascii_isdigit(a[o])) goto err; n = (n * 10) + a[o] - num_zero; i2 = (d->type == V_ASN1_UTCTIME) ? i + 1 : i; if ((n < min[i2]) || (n > max[i2])) goto err; /* if tm is NULL, no need to adjust */ if (tm != NULL) { if (i == end) offset = n * 3600; else if (i == end + 1) offset += n * 60; } o++; } if (offset && !redwax_OPENSSL_gmtime_adj(&tmp, 0, offset * offsign)) goto err; } else { /* not Z, or not +/- in non-strict mode */ goto err; } if (o == l) { /* success, check if tm should be filled */ if (tm != NULL) *tm = tmp; return 1; } err: return 0; } #endif #if !HAVE_ASN1_TIME_DIFF int redwax_ASN1_TIME_to_tm(const ASN1_TIME *s, struct tm *tm) { if (s == NULL) { time_t now_t; time(&now_t); memset(tm, 0, sizeof(*tm)); if (redwax_OPENSSL_gmtime(&now_t, tm) != NULL) return 1; return 0; } return asn1_time_to_tm(tm, s); } int redwax_OPENSSL_gmtime_diff(int *pday, int *psec, const struct tm *from, const struct tm *to) { int from_sec, to_sec, diff_sec; long from_jd, to_jd, diff_day; if (!julian_adj(from, 0, 0, &from_jd, &from_sec)) return 0; if (!julian_adj(to, 0, 0, &to_jd, &to_sec)) return 0; diff_day = to_jd - from_jd; diff_sec = to_sec - from_sec; /* Adjust differences so both positive or both negative */ if (diff_day > 0 && diff_sec < 0) { diff_day--; diff_sec += SECS_PER_DAY; } if (diff_day < 0 && diff_sec > 0) { diff_day++; diff_sec -= SECS_PER_DAY; } if (pday) *pday = (int)diff_day; if (psec) *psec = diff_sec; return 1; } int ASN1_TIME_diff(int *pday, int *psec, const ASN1_TIME *from, const ASN1_TIME *to) { struct tm tm_from, tm_to; if (!redwax_ASN1_TIME_to_tm(from, &tm_from)) return 0; if (!redwax_ASN1_TIME_to_tm(to, &tm_to)) return 0; return redwax_OPENSSL_gmtime_diff(pday, psec, &tm_from, &tm_to); } #endif #if !HAVE_ASN1_TIME_PRINT_EX /* Lower 8 bits are reserved as an output type specifier */ # define ASN1_DTFLGS_TYPE_MASK 0x0FUL # define ASN1_DTFLGS_RFC822 0x00UL # define ASN1_DTFLGS_ISO8601 0x01UL static const char _asn1_mon[12][4] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; static int ossl_ascii_isdigit(const char inchar) { if (inchar > 0x2F && inchar < 0x3A) return 1; return 0; } /* prints the time with the date format of ISO 8601 */ /* returns 0 on BIO write error, else -1 in case of parse failure, else 1 */ int ossl_asn1_time_print_ex(BIO *bp, const ASN1_TIME *tm, unsigned long flags) { char *v; int gmt = 0, l; struct tm stm; const char upper_z = 0x5A, period = 0x2E; /* asn1_time_to_tm will check the time type */ if (!asn1_time_to_tm(&stm, tm)) return BIO_write(bp, "Bad time value", 14) ? -1 : 0; l = tm->length; v = (char *)tm->data; if (v[l - 1] == upper_z) gmt = 1; if (tm->type == V_ASN1_GENERALIZEDTIME) { char *f = NULL; int f_len = 0; /* * Try to parse fractional seconds. '14' is the place of * 'fraction point' in a GeneralizedTime string. */ if (tm->length > 15 && v[14] == period) { f = &v[14]; f_len = 1; while (14 + f_len < l && ossl_ascii_isdigit(f[f_len])) ++f_len; } if ((flags & ASN1_DTFLGS_TYPE_MASK) == ASN1_DTFLGS_ISO8601) { return BIO_printf(bp, "%4d-%02d-%02d %02d:%02d:%02d%.*s%s", stm.tm_year + 1900, stm.tm_mon + 1, stm.tm_mday, stm.tm_hour, stm.tm_min, stm.tm_sec, f_len, f, (gmt ? "Z" : "")) > 0; } else { return BIO_printf(bp, "%s %2d %02d:%02d:%02d%.*s %d%s", _asn1_mon[stm.tm_mon], stm.tm_mday, stm.tm_hour, stm.tm_min, stm.tm_sec, f_len, f, stm.tm_year + 1900, (gmt ? " GMT" : "")) > 0; } } else { if ((flags & ASN1_DTFLGS_TYPE_MASK) == ASN1_DTFLGS_ISO8601) { return BIO_printf(bp, "%4d-%02d-%02d %02d:%02d:%02d%s", stm.tm_year + 1900, stm.tm_mon + 1, stm.tm_mday, stm.tm_hour, stm.tm_min, stm.tm_sec, (gmt ? "Z" : "")) > 0; } else { return BIO_printf(bp, "%s %2d %02d:%02d:%02d %d%s", _asn1_mon[stm.tm_mon], stm.tm_mday, stm.tm_hour, stm.tm_min, stm.tm_sec, stm.tm_year + 1900, (gmt ? " GMT" : "")) > 0; } } } /* returns 1 on success, 0 on BIO write error or parse failure */ int ASN1_TIME_print_ex(BIO *bp, const ASN1_TIME *tm, unsigned long flags) { return ossl_asn1_time_print_ex(bp, tm, flags) > 0; } #endif #if !HAVE_X509_STORE_CTX_GET_NUM_UNTRUSTED int X509_STORE_CTX_get_num_untrusted(X509_STORE_CTX *ctx) { return ctx->last_untrusted; } #endif #if !HAVE_X509_STORE_GET0_PARAM X509_VERIFY_PARAM *X509_STORE_get0_param(X509_STORE *ctx) { return ctx->param; } #endif #if !HAVE_X509_STORE_CTX_SET0_TRUSTED_STACK #define X509_STORE_CTX_set0_trusted_stack X509_STORE_CTX_trusted_stack #endif #if !HAVE_X509_GET0_NOTBEFORE #define X509_get0_notBefore X509_get_notBefore #endif #if !HAVE_X509_GET0_NOTAFTER #define X509_get0_notAfter X509_get_notAfter #endif #if !HAVE_X509_GET0_TBS_SIGALG const X509_ALGOR *X509_get0_tbs_sigalg(const X509 *x) { X509_CINF *ci = x->cert_info; return ci->signature; } #endif #if !HAVE_X509_GET0_EXTENSIONS const STACK_OF(X509_EXTENSION) *X509_get0_extensions(const X509 *x) { X509_CINF *ci = x->cert_info; return ci->extensions; } #endif #if !HAVE_X509_GET0_UIDS void X509_get0_uids(const X509 *x, const ASN1_BIT_STRING **piuid, const ASN1_BIT_STRING **psuid) { X509_CINF *ci = x->cert_info; if (piuid != NULL) *piuid = ci->issuerUID; if (psuid != NULL) *psuid = ci->subjectUID; } #endif #if !HAVE_X509_GET0_SIGNATURE void X509_get0_signature(const ASN1_BIT_STRING **psig, const X509_ALGOR **palg, const X509 *x) { if (psig) *psig = &x->signature; if (palg) *palg = &x->sig_alg; } #endif #if !HAVE_X509_GET_EXTENSION_FLAGS uint32_t X509_get_extension_flags(X509 *x) { /* Call for side-effect of computing hash and caching extensions */ X509_check_purpose(x, -1, 0); return x->ex_flags; } #endif #if !HAVE_X509_UP_REF void X509_up_ref(X509 *x) { CRYPTO_add(&x->references, 1, CRYPTO_LOCK_X509); } #endif #if !HAVE_EVP_PKEY_GET_BN_PARAM #if !HAVE_RSA_GET0_N const BIGNUM *RSA_get0_n(const RSA *r) { return r->n; } #endif #if !HAVE_RSA_GET0_E const BIGNUM *RSA_get0_e(const RSA *r) { return r->e; } #endif #if !HAVE_RSA_GET0_D const BIGNUM *RSA_get0_d(const RSA *r) { return r->d; } #endif #if !HAVE_RSA_GET0_P const BIGNUM *RSA_get0_p(const RSA *r) { return r->p; } #endif #if !HAVE_RSA_GET0_Q const BIGNUM *RSA_get0_q(const RSA *r) { return r->q; } #endif #if !HAVE_RSA_GET0_DMP1 const BIGNUM *RSA_get0_dmp1(const RSA *r) { return r->dmp1; } #endif #if !HAVE_RSA_GET0_DMQ1 const BIGNUM *RSA_get0_dmq1(const RSA *r) { return r->dmq1; } #endif #if !HAVE_RSA_GET0_IQMP const BIGNUM *RSA_get0_iqmp(const RSA *r) { return r->iqmp; } #endif #endif static void redwax_openssl_print_errors(redwax_tool_t *r) { if (!r->quiet && !r->complete) { ERR_print_errors_fp(stderr); } else { ERR_clear_error(); } } static const char *redwax_openssl_x509_text(apr_pool_t *p, X509 *x, unsigned long flags) { BIO *bio; char *buf = NULL; int len = 0; if ((bio = BIO_new(BIO_s_mem())) == NULL) { return NULL; } if (x) { X509_print_ex(bio, x, XN_FLAG_RFC2253, flags); len = BIO_get_mem_data(bio, &buf); } buf = apr_pstrndup(p, buf, len); BIO_free(bio); return buf; } static const char *redwax_openssl_x509_pem(apr_pool_t *p, X509 *x) { BIO *bio; char *buf = NULL; int len = 0; if ((bio = BIO_new(BIO_s_mem())) == NULL) { return NULL; } if (x) { PEM_write_bio_X509(bio, x); len = BIO_get_mem_data(bio, &buf); } buf = apr_pstrndup(p, buf, len); BIO_free(bio); return buf; } static const char *redwax_openssl_name(apr_pool_t *p, X509_NAME *name) { BIO *bio; char *buf = NULL; int len = 0; if ((bio = BIO_new(BIO_s_mem())) == NULL) { return NULL; } if (name) { X509_NAME_print_ex(bio, name, 0, XN_FLAG_RFC2253); len = BIO_get_mem_data(bio, &buf); } buf = apr_pstrndup(p, buf, len); BIO_free(bio); return buf; } static const char* redwax_openssl_time(apr_pool_t *p, const ASN1_GENERALIZEDTIME *tm) { BIO *bio; char *buf = NULL; int len = 0; if ((bio = BIO_new(BIO_s_mem())) == NULL) { return NULL; } if (tm) { ASN1_TIME_print_ex(bio, tm, ASN1_DTFLGS_ISO8601); len = BIO_get_mem_data(bio, &buf); } buf = apr_pstrndup(p, buf, len); BIO_free(bio); return buf; } static apr_status_t cleanup_alloc(void *dummy) { if (dummy) { OPENSSL_free(dummy); } return APR_SUCCESS; } #if 0 static apr_status_t cleanup_bn(void *dummy) { if (dummy) { BN_free(dummy); } return APR_SUCCESS; } #endif static apr_status_t cleanup_bio(void *dummy) { if (dummy) { free(dummy); } return APR_SUCCESS; } static apr_status_t cleanup_x509(void *dummy) { if (dummy) { X509_free(dummy); } return APR_SUCCESS; } #if 0 static apr_status_t cleanup_x509_crl(void *dummy) { if (dummy) { X509_CRL_free(dummy); } return APR_SUCCESS; } #endif static apr_status_t cleanup_pkcs12(void *dummy) { if (dummy) { PKCS12_free(dummy); } return APR_SUCCESS; } static apr_status_t cleanup_evp_pkey(void *dummy) { if (dummy) { EVP_PKEY_free(dummy); } return APR_SUCCESS; } static apr_status_t cleanup_p8inf(void *dummy) { if (dummy) { PKCS8_PRIV_KEY_INFO_free(dummy); } return APR_SUCCESS; } static apr_status_t cleanup_ui(void *dummy) { if (dummy) { UI_free(dummy); } return APR_SUCCESS; } static apr_status_t cleanup_sk_x509(void *dummy) { if (dummy) { sk_X509_pop_free(dummy, X509_free); } return APR_SUCCESS; } static apr_status_t cleanup_sk_x509_crl(void *dummy) { if (dummy) { sk_X509_CRL_pop_free(dummy, X509_CRL_free); } return APR_SUCCESS; } static apr_status_t cleanup_sk_pkcs7(void *dummy) { if (dummy) { sk_PKCS7_pop_free(dummy, PKCS7_free); } return APR_SUCCESS; } static apr_status_t cleanup_sk_pkcs2_safebag(void *dummy) { if (dummy) { sk_PKCS12_SAFEBAG_pop_free(dummy, PKCS12_SAFEBAG_free); } return APR_SUCCESS; } static apr_status_t cleanup_x509_store(void *dummy) { if (dummy) { X509_STORE_free(dummy); } return APR_SUCCESS; } static apr_status_t cleanup_x509_store_ctx(void *dummy) { if (dummy) { X509_STORE_CTX_free(dummy); } return APR_SUCCESS; } static apr_status_t cleanup_key(void *dummy) { if (dummy) { redwax_key_t *key = dummy; if (key->keys_index) { apr_hash_set(key->keys_index, key->common.subjectpublickeyinfo_der, key->common.subjectpublickeyinfo_len, NULL); } } return APR_SUCCESS; } static apr_status_t cleanup_SSL_CTX(void *dummy) { if (dummy) { SSL_CTX_free(dummy); } return APR_SUCCESS; } static apr_status_t cleanup_SSL(void *dummy) { if (dummy) { SSL_free(dummy); } return APR_SUCCESS; } static apr_status_t redwax_openssl_initialise(redwax_tool_t *r) { openssl_config_t *config; config = apr_pcalloc(r->pool, sizeof(openssl_config_t)); redwax_set_module_config(r->per_module, &openssl_module, config); config->dane_ctx = SSL_CTX_new(TLS_client_method()); SSL_CTX_dane_enable(config->dane_ctx); config->dane_ssl = SSL_new(config->dane_ctx); apr_pool_cleanup_register(r->pool, config->dane_ctx, cleanup_SSL_CTX, apr_pool_cleanup_null); apr_pool_cleanup_register(r->pool, config->dane_ssl, cleanup_SSL, apr_pool_cleanup_null); cert_index = sk_X509_new_null(); chain_index = sk_X509_new_null(); trusted_index = sk_X509_new_null(); crl_index = sk_X509_CRL_new_null(); apr_pool_cleanup_register(r->pool, cert_index, cleanup_sk_x509, apr_pool_cleanup_null); apr_pool_cleanup_register(r->pool, chain_index, cleanup_sk_x509, apr_pool_cleanup_null); apr_pool_cleanup_register(r->pool, trusted_index, cleanup_sk_x509, apr_pool_cleanup_null); apr_pool_cleanup_register(r->pool, crl_index, cleanup_sk_x509_crl, apr_pool_cleanup_null); redwax_get_x509_index(); redwax_get_x509_store_ctx_index(); return OK; } static apr_status_t redwax_openssl_process_pem_in(redwax_tool_t *r, const char *file, const char *secret) { BIO *bio; char *name = NULL, *header = NULL; unsigned char *data = NULL; long len, error = 0; int label_len, id_len; if (!strcmp(file, "-")) { if (r->complete) { return APR_ENOENT; } if ((bio = BIO_new_fp(stdin, BIO_NOCLOSE)) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } } else if ((bio = BIO_new(BIO_s_file())) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } else if (BIO_read_filename(bio, file) <= 0) { redwax_openssl_print_errors(r); BIO_free(bio); return APR_ENOENT; } apr_pool_cleanup_register(r->pool, bio, cleanup_bio, apr_pool_cleanup_null); for (;;) { const unsigned char *der; PKCS8_PRIV_KEY_INFO *p8inf = NULL; EVP_PKEY *pkey = NULL; apr_hash_t *index; /* header is deprecated, for details see PEM_get_EVP_CIPHER_INFO * and PEM_do_header in OpenSSL. We do not (yet) support it here. */ if (!PEM_read_bio(bio, &name, &header, &data, &len)) { error = ERR_GET_REASON(ERR_peek_last_error()); if (error == PEM_R_NO_START_LINE) { ERR_clear_error(); } break; } apr_pool_cleanup_register(r->pool, name, cleanup_alloc, apr_pool_cleanup_null); apr_pool_cleanup_register(r->pool, header, cleanup_alloc, apr_pool_cleanup_null); apr_pool_cleanup_register(r->pool, data, cleanup_alloc, apr_pool_cleanup_null); der = data; /* is this a duplicate? if so, skip */ if ((strcmp(name, PEM_STRING_X509_TRUSTED) == 0)) { index = r->trust_duplicates_index; } else { index = r->duplicates_index; } if (apr_hash_get(index, der, len)) { continue; } else { apr_hash_set(index, der, len, der); } if ((strcmp(name, PEM_STRING_X509) == 0) || (strcmp(name, PEM_STRING_X509_OLD) == 0)) { redwax_certificate_t *cert; X509 *x = d2i_X509(NULL, &der, len); if (!x) { redwax_print_error(r, "Could not read certificate from '%s', skipping.\n", file); redwax_openssl_print_errors(r); continue; } if (X509_check_ca(x)) { cert = apr_array_push(r->intermediates_in); apr_pool_create(&cert->pool, r->pool); cert->common.type = REDWAX_CERTIFICATE_X509; cert->common.category = REDWAX_CERTIFICATE_INTERMEDIATE; redwax_print_error(r, "pem-in: intermediate: %s\n", redwax_openssl_name(cert->pool, X509_get_subject_name(x))); } else { cert = apr_array_push(r->certs_in); apr_pool_create(&cert->pool, r->pool); cert->common.type = REDWAX_CERTIFICATE_X509; cert->common.category = REDWAX_CERTIFICATE_END_ENTITY; redwax_print_error(r, "pem-in: certificate: %s\n", redwax_openssl_name(cert->pool, X509_get_subject_name(x))); } cert->per_module = redwax_create_module_config(cert->pool); cert->header = header; cert->label = (const char *)X509_alias_get0(x, &label_len); cert->label_len = label_len; cert->der = data; cert->len = len; cert->origin = file; rt_run_normalise_certificate(r, cert, 1); } else if ((strcmp(name, PEM_STRING_X509_TRUSTED) == 0)) { redwax_certificate_t *cert; X509 *x = d2i_X509_AUX(NULL, &der, len); if (!x) { redwax_print_error(r, "Could not read certificate from '%s', skipping.\n", file); redwax_openssl_print_errors(r); continue; } else { unsigned char *der = NULL; cert = apr_array_push(r->trusted_in); apr_pool_create(&cert->pool, r->pool); cert->per_module = redwax_create_module_config(cert->pool); cert->common.type = REDWAX_CERTIFICATE_X509; cert->common.category = REDWAX_CERTIFICATE_TRUSTED; cert->header = header; cert->id_der = (unsigned char *)X509_keyid_get0(x, &id_len); cert->id_len = id_len; cert->label = (const char *)X509_alias_get0(x, &label_len); cert->label_len = label_len; cert->len = i2d_X509(x, &der); cert->der = der; cert->origin = file; rt_run_normalise_certificate(r, cert, 1); redwax_print_error(r, "pem-in: trusted: %s\n", redwax_openssl_name(cert->pool, X509_get_subject_name(x))); } } else if (strcmp(name, PEM_STRING_X509_CRL) == 0) { redwax_crl_t *crl; X509_CRL *c = d2i_X509_CRL(NULL, &der, len); if (c) { redwax_print_error(r, "pem-in: crl: %s\n", redwax_openssl_name(r->pool, X509_CRL_get_issuer(c))); sk_X509_CRL_push(crl_index, c); crl = apr_array_push(r->crls_in); apr_pool_create(&crl->pool, r->pool); crl->per_module = redwax_create_module_config(crl->pool); crl->header = header; crl->der = data; crl->len = len; crl->origin = file; /* no cleanup because of sk_X509_CRL_push() */ #if 0 apr_pool_cleanup_register(r->pool, c, cleanup_x509_crl, apr_pool_cleanup_null); #endif } } else if (r->key_in && strcmp(name, PEM_STRING_RSA) == 0) { if (!(pkey = d2i_PrivateKey(EVP_PKEY_RSA, NULL, &der, len)) || !(p8inf = EVP_PKEY2PKCS8(pkey))) { redwax_print_error(r, "Could not read RSA private key from '%s', skipping.\n", file); redwax_openssl_print_errors(r); } } else if (r->key_in && strcmp(name, PEM_STRING_DSA) == 0) { if (!(pkey = d2i_PrivateKey(EVP_PKEY_DSA, NULL, &der, len)) || !(p8inf = EVP_PKEY2PKCS8(pkey))) { redwax_print_error(r, "Could not read DSA private key from '%s', skipping.\n", file); redwax_openssl_print_errors(r); } } else if (r->key_in && strcmp(name, PEM_STRING_ECPRIVATEKEY) == 0) { if (!(pkey = d2i_PrivateKey(EVP_PKEY_EC, NULL, &der, len)) || !(p8inf = EVP_PKEY2PKCS8(pkey))) { redwax_print_error(r, "Could not read EC private key from '%s', skipping.\n", file); redwax_openssl_print_errors(r); } } else if (r->key_in && strcmp(name, PEM_STRING_PKCS8INF) == 0) { BIO *kbio; if ((kbio = BIO_new_mem_buf(der, len)) == NULL) { return APR_ENOMEM; } if (!(p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(kbio, NULL)) || !(pkey = EVP_PKCS82PKEY(p8inf))) { redwax_print_error(r, "Could not read private key from '%s', skipping.\n", file); redwax_openssl_print_errors(r); } BIO_free(kbio); } if (p8inf) { apr_pool_cleanup_register(r->pool, p8inf, cleanup_p8inf, apr_pool_cleanup_null); } if (pkey) { apr_pool_cleanup_register(r->pool, pkey, cleanup_evp_pkey, apr_pool_cleanup_null); } /* handle keys */ if (p8inf && pkey) { redwax_key_t *key; #if 0 openssl_key_config_t *key_config; #endif BIO *kbio; #if HAVE_EVP_PKEY_GET0_DESCRIPTION redwax_print_error(r, "pem-in: private key: %s\n", EVP_PKEY_get0_description(pkey)); #else redwax_print_error(r, "pem-in: private key\n"); #endif if ((kbio = BIO_new(BIO_s_mem())) == NULL) { return APR_ENOMEM; } apr_pool_cleanup_register(r->pool, kbio, cleanup_bio, apr_pool_cleanup_null); i2d_PKCS8_PRIV_KEY_INFO_bio(kbio, p8inf); key = apr_array_push(r->keys_in); apr_pool_create(&key->pool, r->pool); key->per_module = redwax_create_module_config(key->pool); #if 0 key_config = apr_pcalloc(key->pool, sizeof(openssl_key_config_t)); redwax_set_module_config(key->per_module, &openssl_module, key_config); #endif key->header = header; key->len = BIO_get_mem_data(kbio, &key->der); key->origin = file; rt_run_normalise_key(r, key, 1); } } return APR_SUCCESS; } static apr_status_t redwax_openssl_process_trust_pem_in(redwax_tool_t *r, const char *file, const char *secret) { BIO *bio; char *name = NULL, *header = NULL; unsigned char *data = NULL; long len, error = 0; int label_len, id_len; if (!strcmp(file, "-")) { if (r->complete) { return APR_ENOENT; } if ((bio = BIO_new_fp(stdin, BIO_NOCLOSE)) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } } else if ((bio = BIO_new(BIO_s_file())) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } else if (BIO_read_filename(bio, file) <= 0) { redwax_openssl_print_errors(r); BIO_free(bio); return APR_ENOENT; } apr_pool_cleanup_register(r->pool, bio, cleanup_bio, apr_pool_cleanup_null); for (;;) { const unsigned char *der; if (!PEM_read_bio(bio, &name, &header, &data, &len)) { error = ERR_GET_REASON(ERR_peek_last_error()); if (error == PEM_R_NO_START_LINE) { ERR_clear_error(); } break; } apr_pool_cleanup_register(r->pool, name, cleanup_alloc, apr_pool_cleanup_null); apr_pool_cleanup_register(r->pool, header, cleanup_alloc, apr_pool_cleanup_null); apr_pool_cleanup_register(r->pool, data, cleanup_alloc, apr_pool_cleanup_null); der = data; /* is this a duplicate? if so, skip */ if (apr_hash_get(r->trust_duplicates_index, der, len)) { continue; } else { apr_hash_set(r->trust_duplicates_index, der, len, der); } if ((strcmp(name, PEM_STRING_X509) == 0) || (strcmp(name, PEM_STRING_X509_OLD) == 0)) { redwax_certificate_t *cert; X509 *x = d2i_X509(NULL, &der, len); if (!x) { redwax_print_error(r, "Could not read certificate from '%s', skipping.\n", file); redwax_openssl_print_errors(r); continue; } cert = apr_array_push(r->trusted_in); apr_pool_create(&cert->pool, r->pool); cert->common.type = REDWAX_CERTIFICATE_X509; cert->common.category = REDWAX_CERTIFICATE_TRUSTED; redwax_print_error(r, "trust-pem-in: certificate: %s\n", redwax_openssl_name(cert->pool, X509_get_subject_name(x))); cert->header = header; cert->label = (const char *)X509_alias_get0(x, &label_len); cert->label_len = label_len; cert->der = data; cert->len = len; cert->origin = file; rt_run_normalise_certificate(r, cert, 1); } else if ((strcmp(name, PEM_STRING_X509_TRUSTED) == 0)) { redwax_certificate_t *cert; X509 *x = d2i_X509_AUX(NULL, &der, len); if (!x) { redwax_print_error(r, "Could not read certificate from '%s', skipping.\n", file); redwax_openssl_print_errors(r); continue; } else { unsigned char *der = NULL; cert = apr_array_push(r->trusted_in); apr_pool_create(&cert->pool, r->pool); cert->common.type = REDWAX_CERTIFICATE_X509; cert->common.category = REDWAX_CERTIFICATE_TRUSTED; cert->header = header; cert->id_der = (unsigned char *)X509_keyid_get0(x, &id_len); cert->id_len = id_len; cert->label = (const char *)X509_alias_get0(x, &label_len); cert->label_len = label_len; cert->len = i2d_X509(x, &der); cert->der = der; cert->origin = file; rt_run_normalise_certificate(r, cert, 1); redwax_print_error(r, "trust-pem-in: trusted: %s\n", redwax_openssl_name(cert->pool, X509_get_subject_name(x))); } } else if (strcmp(name, PEM_STRING_X509_CRL) == 0) { redwax_crl_t *crl; X509_CRL *c = d2i_X509_CRL(NULL, &der, len); if (c) { redwax_print_error(r, "trust-pem-in: crl: %s\n", redwax_openssl_name(r->pool, X509_CRL_get_issuer(c))); sk_X509_CRL_push(crl_index, c); crl = apr_array_push(r->crls_in); apr_pool_create(&crl->pool, r->pool); crl->header = header; crl->der = data; crl->len = len; crl->origin = file; /* no cleanup because of sk_X509_CRL_push() */ #if 0 apr_pool_cleanup_register(r->pool, c, cleanup_x509_crl, apr_pool_cleanup_null); #endif } } } return APR_SUCCESS; } static apr_status_t redwax_openssl_set_verify_param(redwax_tool_t *r, const char *arg) { const X509_VERIFY_PARAM *param; if (!(param = X509_VERIFY_PARAM_lookup(arg))) { redwax_print_error(r, "Verify parameter not found: %s\n", arg); return APR_ENOENT; } r->verify_param = arg; return APR_SUCCESS; } static apr_status_t redwax_openssl_complete_verify_param(redwax_tool_t *r, apr_hash_t *params) { int i; int count = X509_VERIFY_PARAM_get_count(); for (i = 0; i < count; i++) { const char *name = X509_VERIFY_PARAM_get0_name(X509_VERIFY_PARAM_get0(i)); apr_hash_set(params, name, APR_HASH_KEY_STRING, name); } return APR_SUCCESS; } static apr_status_t redwax_openssl_set_verify_date(redwax_tool_t *r, const char *arg) { if (!ASN1_TIME_set_string(NULL, arg)) { redwax_print_error(r, "Verify date could not be parsed: %s\n", arg); return APR_EINVAL; } r->verify_date = arg; return APR_SUCCESS; } apr_status_t redwax_openssl_complete_verify_expiry(redwax_tool_t *r, apr_hash_t *expiries) { apr_hash_set(expiries, REDWAX_EXPIRY_CHECK_TEXT, strlen(REDWAX_EXPIRY_CHECK_TEXT), REDWAX_EXPIRY_CHECK_TEXT); apr_hash_set(expiries, REDWAX_EXPIRY_IGNORE_TEXT, strlen(REDWAX_EXPIRY_IGNORE_TEXT), REDWAX_EXPIRY_IGNORE_TEXT); apr_hash_set(expiries, REDWAX_EXPIRY_IGNORE_LEAF_TEXT, strlen(REDWAX_EXPIRY_IGNORE_LEAF_TEXT), REDWAX_EXPIRY_IGNORE_LEAF_TEXT); apr_hash_set(expiries, REDWAX_EXPIRY_IGNORE_CHAIN_TEXT, strlen(REDWAX_EXPIRY_IGNORE_CHAIN_TEXT), REDWAX_EXPIRY_IGNORE_CHAIN_TEXT); return APR_SUCCESS; } static apr_status_t redwax_openssl_set_verify_expiry(redwax_tool_t *r, const char *arg) { if (!strcmp(arg, REDWAX_EXPIRY_CHECK_TEXT)) { r->expiry = REDWAX_EXPIRY_CHECK; } else if (!strcmp(arg, REDWAX_EXPIRY_IGNORE_TEXT)) { r->expiry = REDWAX_EXPIRY_IGNORE; } else if (!strcmp(arg, REDWAX_EXPIRY_IGNORE_LEAF_TEXT)) { r->expiry = REDWAX_EXPIRY_IGNORE_LEAF; } else if (!strcmp(arg, REDWAX_EXPIRY_IGNORE_CHAIN_TEXT)) { r->expiry = REDWAX_EXPIRY_IGNORE_CHAIN; } else { redwax_print_error(r, "Verify expiry not one of 'check', 'ignore', 'ignore-leaf' or 'ignore-chain': %s\n", arg); return APR_EINVAL; } return APR_SUCCESS; } apr_status_t redwax_openssl_complete_verify_dane(redwax_tool_t *r, apr_hash_t *danes) { apr_hash_set(danes, REDWAX_DANE_CHECK_TEXT, strlen(REDWAX_DANE_CHECK_TEXT), REDWAX_DANE_CHECK_TEXT); apr_hash_set(danes, REDWAX_DANE_IGNORE_TEXT, strlen(REDWAX_DANE_IGNORE_TEXT), REDWAX_DANE_IGNORE_TEXT); return APR_SUCCESS; } static apr_status_t redwax_openssl_set_verify_dane(redwax_tool_t *r, const char *arg) { if (!strcmp(arg, "check")) { r->dane = REDWAX_DANE_CHECK; } else if (!strcmp(arg, "ignore")) { r->dane = REDWAX_DANE_IGNORE; } else { redwax_print_error(r, "Verify dane not one of 'check' or 'ignore': %s\n", arg); return APR_EINVAL; } return APR_SUCCESS; } static apr_status_t redwax_openssl_set_purpose(redwax_tool_t *r, const char *arg) { int purpose; if (0 > (purpose = X509_PURPOSE_get_by_sname(arg))) { int i, nelts; int count = X509_PURPOSE_get_count(); struct iovec *purposes = apr_palloc(r->pool, count * sizeof(struct iovec) * 2); for (i = 0, nelts = 0; i < count; i++) { X509_PURPOSE *purpose = X509_PURPOSE_get0(i); char *name = X509_PURPOSE_get0_sname(purpose); if (nelts) { purposes[nelts].iov_base = ", "; purposes[nelts].iov_len = 2; nelts++; } purposes[nelts].iov_base = name; purposes[nelts].iov_len = strlen(name); nelts++; } redwax_print_error(r, "Purpose '%s' not found, must be one of: %s\n", arg, apr_pstrcatv(r->pool, purposes, nelts, NULL)); return APR_ENOENT; } r->purpose = arg; return APR_SUCCESS; } static apr_status_t redwax_openssl_complete_purpose(redwax_tool_t *r, apr_hash_t *params) { int i; int count = X509_PURPOSE_get_count(); for (i = 0; i < count; i++) { X509_PURPOSE *purpose = X509_PURPOSE_get0(i); char *name = X509_PURPOSE_get0_sname(purpose); apr_hash_set(params, name, APR_HASH_KEY_STRING, name); } return APR_SUCCESS; } static apr_status_t redwax_openssl_complete_filter_search(redwax_tool_t *r, apr_hash_t *filters) { apr_hash_set(filters, REDWAX_OPENSSL_SEARCH, strlen(REDWAX_OPENSSL_SEARCH), REDWAX_OPENSSL_SEARCH); return APR_SUCCESS; } static int search_cert_match(redwax_tool_t *r, const redwax_certificate_t *cert) { apr_hash_index_t *hi; const void *key; apr_ssize_t klen; const unsigned char *der = cert->der; X509 *x = d2i_X509(NULL, &der, cert->len); if (x) { if (r->purpose) { X509_PURPOSE *xptmp; int purpose = X509_PURPOSE_get_by_sname(r->purpose); if (purpose == -1) { redwax_print_error(r, "When searchinging, purpose was not recognised: %s\n", r->purpose); return APR_EINVAL; } /* purpose index -> purpose object */ xptmp = X509_PURPOSE_get0(purpose); /* purpose object -> purpose value */ purpose = X509_PURPOSE_get_id(xptmp); if (!X509_check_purpose(x, purpose, 0)) { X509_free(x); return 0; } } if (apr_hash_count(r->emails)) { int match = 0; for (hi = apr_hash_first(r->pool, r->emails); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, &key, &klen, NULL); if (X509_check_email(x, key, klen, 0) == 1) { match = 1; } } if (!match) { X509_free(x); return 0; } } if (apr_hash_count(r->hostnames)) { int match = 0; for (hi = apr_hash_first(r->pool, r->hostnames); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, &key, &klen, NULL); if (X509_check_host(x, key, klen, 0, NULL) == 1) { match = 1; } } if (!match) { X509_free(x); return 0; } } if (apr_hash_count(r->ips)) { int match = 0; for (hi = apr_hash_first(r->pool, r->ips); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, &key, &klen, NULL); if (X509_check_ip_asc(x, key, 0) == 1) { match = 1; } } if (!match) { X509_free(x); return 0; } } X509_free(x); } return 1; } static apr_status_t redwax_openssl_set_tlsa(redwax_tool_t *r, const char *arg) { openssl_config_t *config = redwax_get_module_config(r->per_module, &openssl_module); if (r->dane_basename) { if (SSL_dane_enable(config->dane_ssl, r->dane_basename) <= 0) { redwax_openssl_print_errors(r); redwax_print_error(r, "Warning: Could not enable dane for '%s'\n", r->dane_basename); return APR_EINVAL; } } return APR_SUCCESS; } static apr_status_t redwax_openssl_process_tlsa(redwax_tool_t *r, redwax_dns_t *dns) { openssl_config_t *config = redwax_get_module_config(r->per_module, &openssl_module); if (dns->bogus) { redwax_print_error(r, "filter-verify-tlsa: DNS TLSA response for '%s' failed " "DNSSEC validation, ignoring: %s.\n", dns->qname, dns->why_bogus); r->dane_bogus++; return APR_SUCCESS; } if (!dns->secure) { redwax_print_error(r, "filter-verify-tlsa: DNS TLSA response for '%s' is " "not DNSSEC secured, ignoring.\n", dns->qname); r->dane_insecure++; return APR_SUCCESS; } if (dns->nxdomain) { redwax_print_error(r, "filter-verify-tlsa: DNS TLSA record for '%s' does " "not exist, ignoring.\n", dns->qname); r->dane_nxdomain++; return APR_SUCCESS; } if (dns->rdata) { int i; for (i = 0; i < dns->rdata->nelts; i++) { const redwax_rdata_t *rdata = &APR_ARRAY_IDX(dns->rdata, i, const redwax_rdata_t); if (SSL_dane_tlsa_add(config->dane_ssl, rdata->rr.tlsa.usage, rdata->rr.tlsa.selector, rdata->rr.tlsa.mtype, rdata->rr.tlsa.data, rdata->rr.tlsa.len) <= 0) { redwax_openssl_print_errors(r); redwax_print_error(r, "filter-verify-tlsa: DNS TLSA record for '%s' not " "accepted, ignoring.\n", dns->qname); r->dane_malformed++; } else { redwax_print_error(r, "filter-verify-tlsa: DNS TLSA record for '%s' found " "(usage: %s[%d], selector: %s[%d], matching: %s[%d], digest: %d bytes)\n", dns->qname, rdata->rr.tlsa.usage_name, rdata->rr.tlsa.usage, rdata->rr.tlsa.selector_name, rdata->rr.tlsa.selector, rdata->rr.tlsa.mtype_name, rdata->rr.tlsa.mtype, rdata->rr.tlsa.len); r->dane_record++; } } } return APR_SUCCESS; } static apr_status_t redwax_openssl_process_filter_search(redwax_tool_t *r, const char *arg) { int i; if (strcmp(arg, REDWAX_OPENSSL_SEARCH)) { return DECLINED; } for (i = 0; i < r->certs_in->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_in, i, const redwax_certificate_t); if (search_cert_match(r, cert)) { redwax_certificate_t *ncert; redwax_certificate_t tcert = { 0 }; memcpy(&tcert, cert, sizeof(tcert)); if (r->current && r->certs_out->nelts) { const redwax_certificate_t *ocert = &APR_ARRAY_IDX(r->certs_out, 0, const redwax_certificate_t); int diff = rt_run_compare_certificate(r, ocert, &tcert); /* no compare module, give up */ if (diff == DECLINED) { redwax_print_error(r, "When searching, there was no implementation " "to compare certificates. Giving up.\n"); return APR_ENOENT; } /* new cert is better, blow away previous results */ else if (diff == RIGHT) { apr_array_clear(r->certs_out); apr_array_clear(r->intermediates_out); apr_array_clear(r->trusted_out); apr_array_clear(r->keys_out); } /* original cert is better, ignore the new cert */ else { continue; } } ncert = apr_array_push(r->certs_out); memcpy(ncert, &tcert, sizeof(redwax_certificate_t)); rt_run_search_chain(r, cert, NULL); rt_run_search_key(r, cert); } } for (i = 0; i < r->intermediates_in->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->intermediates_in, i, const redwax_certificate_t); if (search_cert_match(r, cert)) { redwax_certificate_t *ncert = apr_array_push(r->intermediates_out); memcpy(ncert, cert, sizeof(redwax_certificate_t)); rt_run_search_chain(r, cert, NULL); rt_run_search_key(r, cert); } } for (i = 0; i < r->trusted_in->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->trusted_in, i, const redwax_certificate_t); if (search_cert_match(r, cert)) { redwax_certificate_t *ncert = apr_array_push(r->trusted_out); memcpy(ncert, cert, sizeof(redwax_certificate_t)); rt_run_search_chain(r, cert, NULL); rt_run_search_key(r, cert); } } return APR_SUCCESS; } static apr_status_t redwax_openssl_complete_filter_verify(redwax_tool_t *r, apr_hash_t *filters) { apr_hash_set(filters, REDWAX_OPENSSL_VERIFY, strlen(REDWAX_OPENSSL_VERIFY), REDWAX_OPENSSL_VERIFY); return APR_SUCCESS; } static int verify_cb(int ok, X509_STORE_CTX *ctx) { BIO *bio; X509 *current_cert = X509_STORE_CTX_get_current_cert(ctx); char *buf = NULL; int len = 0; int depth = X509_STORE_CTX_get_error_depth(ctx); redwax_tool_t *r = X509_STORE_CTX_get_ex_data(ctx, redwax_get_x509_store_ctx_index()); redwax_certificate_t *cert = X509_get_ex_data(current_cert, redwax_get_x509_index()); if ((bio = BIO_new(BIO_s_mem())) == NULL) { return 0; } if (current_cert != NULL) { X509_NAME_print_ex(bio, X509_get_subject_name(current_cert), 0, XN_FLAG_RFC2253); len = BIO_get_mem_data(bio, &buf); } openssl_certificate_config_t *config = redwax_get_module_config(cert->per_module, &openssl_module); if (!config) { config = apr_pcalloc(cert->pool, sizeof(openssl_certificate_config_t)); redwax_set_module_config(cert->per_module, &openssl_module, config); } /* set verification result */ config->verification = X509_STORE_CTX_get_error(ctx); if ((r->expiry == REDWAX_EXPIRY_IGNORE || (r->expiry == REDWAX_EXPIRY_IGNORE_LEAF && depth == 0) || (r->expiry == REDWAX_EXPIRY_IGNORE_CHAIN && depth > 0)) && X509_STORE_CTX_get_error(ctx) == X509_V_ERR_CERT_HAS_EXPIRED) { X509_STORE_CTX_set_error(ctx, X509_V_OK); ok = 1; redwax_print_error(r, "verify-filter: %d: %.*s: certificate expired, accepting anyway\n", X509_STORE_CTX_get_error_depth(ctx), len, buf); } else if ((r->dane == REDWAX_DANE_IGNORE) && X509_STORE_CTX_get_error(ctx) == X509_V_ERR_DANE_NO_MATCH) { X509_STORE_CTX_set_error(ctx, X509_V_OK); ok = 1; redwax_print_error(r, "verify-filter: %d: %.*s: dane mismatch, accepting anyway\n", X509_STORE_CTX_get_error_depth(ctx), len, buf); } else if (!ok) { redwax_print_error(r, "verify-filter: %d: %.*s: verify failed: %s\n", X509_STORE_CTX_get_error_depth(ctx), len, buf, X509_verify_cert_error_string( X509_STORE_CTX_get_error(ctx))); } else { redwax_print_error(r, "verify-filter: %d: %.*s: verify ok\n", X509_STORE_CTX_get_error_depth(ctx), len, buf); } BIO_free(bio); return ok; } static apr_status_t redwax_openssl_process_filter_verify(redwax_tool_t *r, const char *arg) { openssl_config_t *config = redwax_get_module_config(r->per_module, &openssl_module); X509_STORE_CTX *ctx; X509_STORE *store; apr_hash_index_t *hi; const void *key; apr_ssize_t klen; int i, j; int found = 0; if (strcmp(arg, REDWAX_OPENSSL_VERIFY)) { return DECLINED; } if (r->dane_basename && !r->dane_record) { redwax_print_error(r, "verify-filter: DANE TLSA record not found " "(%d bogus, %d insecure, %d nxdomain, %d malformed): %s\n", r->dane_bogus, r->dane_insecure, r->dane_nxdomain, r->dane_malformed, r->dane_basename); return APR_ENOENT; } store = X509_STORE_new(); apr_pool_cleanup_register(r->pool, store, cleanup_x509_store, apr_pool_cleanup_null); ctx = X509_STORE_CTX_new(); apr_pool_cleanup_register(r->pool, ctx, cleanup_x509_store_ctx, apr_pool_cleanup_null); if (r->verify_param) { const X509_VERIFY_PARAM *param; if (!(param = X509_VERIFY_PARAM_lookup(r->verify_param))) { redwax_print_error(r, "verify-filter: verify parameter not found: %s\n", r->verify_param); return APR_ENOENT; } X509_VERIFY_PARAM_inherit(X509_STORE_get0_param(store), param); } if (apr_hash_count(r->emails) <= 1) { for (hi = apr_hash_first(r->pool, r->emails); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, &key, &klen, NULL); X509_VERIFY_PARAM_set1_email(X509_STORE_get0_param(store), (const char *)key, klen); } } else { redwax_print_error(r, "verify-filter: email address can only be specified once\n"); return APR_ENOENT; } for (hi = apr_hash_first(r->pool, r->hostnames); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, &key, &klen, NULL); X509_VERIFY_PARAM_add1_host(X509_STORE_get0_param(store), (const char *)key, klen); } if (apr_hash_count(r->ips) <= 1) { for (hi = apr_hash_first(r->pool, r->ips); hi; hi = apr_hash_next(hi)) { apr_hash_this(hi, &key, &klen, NULL); X509_VERIFY_PARAM_set1_ip_asc(X509_STORE_get0_param(store), (const char *)key); } } else { redwax_print_error(r, "verify-filter: ip address can only be specified once\n"); return APR_ENOENT; } if (r->purpose) { X509_PURPOSE *xptmp; int purpose = X509_PURPOSE_get_by_sname(r->purpose); if (purpose == -1) { redwax_print_error(r, "verify-filter: purpose was not recognised: %s\n", r->purpose); return APR_EINVAL; } /* purpose index -> purpose object */ xptmp = X509_PURPOSE_get0(purpose); /* purpose object -> purpose value */ purpose = X509_PURPOSE_get_id(xptmp); if (!X509_VERIFY_PARAM_set_purpose(X509_STORE_get0_param(store), purpose)) { redwax_print_error(r, "verify-filter: purpose could not be specifed\n"); redwax_openssl_print_errors(r); return APR_ENOENT; } } for (i = 0; i < r->certs_in->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_in, i, const redwax_certificate_t); const unsigned char *der = cert->der; X509 *x = d2i_X509(NULL, &der, cert->len); if (x) { if (!X509_STORE_CTX_init(ctx, store, x, chain_index)) { return APR_ENOMEM; } X509_STORE_CTX_set_ex_data(ctx, redwax_get_x509_store_ctx_index(), (void *)r); X509_set_ex_data(x, redwax_get_x509_index(), (void *)cert); X509_STORE_CTX_set0_trusted_stack(ctx, trusted_index); X509_STORE_CTX_set0_crls(ctx, crl_index); if (r->verify_date) { time_t now_t; struct tm now_tm; ASN1_TIME *now = ASN1_TIME_new(); ASN1_TIME_set_string(now, r->verify_date); ASN1_TIME_normalize(now); ASN1_TIME_to_tm(now, &now_tm); now_t = mktime(&now_tm); X509_STORE_CTX_set_time(ctx, 0, now_t); } if (r->dane_basename) { X509_STORE_CTX_set0_dane(ctx, SSL_get0_dane(config->dane_ssl)); } X509_STORE_CTX_set_verify_cb(ctx, &verify_cb); if (X509_verify_cert(ctx) > 0 && X509_STORE_CTX_get_error(ctx) == X509_V_OK) { redwax_certificate_t *ncert; redwax_certificate_t tcert = { 0 }; found++; memcpy(&tcert, cert, sizeof(redwax_certificate_t)); if (r->current && r->certs_out->nelts) { const redwax_certificate_t *ocert = &APR_ARRAY_IDX(r->certs_out, 0, const redwax_certificate_t); int diff = rt_run_compare_certificate(r, ocert, &tcert); /* no compare module, give up */ if (diff == DECLINED) { redwax_print_error(r, "verify-filter: there was no implementation " "to compare certificates, giving up.\n"); return APR_ENOENT; } /* new cert is better, blow away previous results */ else if (diff == RIGHT) { apr_array_clear(r->certs_out); apr_array_clear(r->intermediates_out); apr_array_clear(r->trusted_out); apr_array_clear(r->keys_out); } /* original cert is better, ignore the new cert */ else { continue; } } ncert = apr_array_push(r->certs_out); memcpy(ncert, &tcert, sizeof(redwax_certificate_t)); rt_run_search_key(r, cert); STACK_OF(X509) *xs = X509_STORE_CTX_get1_chain(ctx); int num_untrusted = X509_STORE_CTX_get_num_untrusted(ctx); for (j = 0; j < sk_X509_num(xs); j++) { X509 *x = sk_X509_value(xs, j); redwax_certificate_t *chain = X509_get_ex_data(x, redwax_get_x509_index()); if (j == 0) { /* leaf cert, we're already handled above */ } else if (j < num_untrusted) { ncert = apr_array_push(r->intermediates_out); memcpy(ncert, chain, sizeof(redwax_certificate_t)); rt_run_search_key(r, chain); } else { ncert = apr_array_push(r->trusted_out); memcpy(ncert, chain, sizeof(redwax_certificate_t)); rt_run_search_key(r, chain); } } sk_X509_pop_free(xs, X509_free); } else { /* verification failed */ } X509_STORE_CTX_cleanup(ctx); X509_free(x); } } if (found) { return APR_SUCCESS; } else { return APR_ENOENT; } } static apr_status_t redwax_openssl_process_der_out(redwax_tool_t *r, const char *file, const char *secret) { BIO *bio; int i; if (r->cert_out) { for (i = 0; i < r->certs_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_out, i, const redwax_certificate_t); const unsigned char *der = cert->der; X509 *x = d2i_X509(NULL, &der, cert->len); if (x) { redwax_print_error(r, "der-out: certificate: %s\n", redwax_openssl_name(r->pool, X509_get_subject_name(x))); if (!strcmp(file, "-")) { if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } } else if ((bio = BIO_new(BIO_s_file())) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } else if (BIO_write_filename(bio, (char *)apr_psprintf(r->pool, "%s.cert.%d", file, i)) <= 0) { redwax_openssl_print_errors(r); BIO_free(bio); return APR_ENOENT; } if (BIO_write(bio, cert->der, cert->len) < 0) { redwax_openssl_print_errors(r); X509_free(x); BIO_free(bio); return APR_EGENERAL; } BIO_flush(bio); X509_free(x); BIO_free(bio); } } } if (r->chain_out) { for (i = 0; i < r->intermediates_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->intermediates_out, i, const redwax_certificate_t); const unsigned char *der = cert->der; X509 *x = d2i_X509(NULL, &der, cert->len); if (x) { redwax_print_error(r, "der-out: intermediate: %s\n", redwax_openssl_name(r->pool, X509_get_subject_name(x))); if (!strcmp(file, "-")) { if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } } else if ((bio = BIO_new(BIO_s_file())) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } else if (BIO_write_filename(bio, (char *)apr_psprintf(r->pool, "%s.chain.%d", file, i)) <= 0) { redwax_openssl_print_errors(r); BIO_free(bio); return APR_ENOENT; } if (BIO_write(bio, cert->der, cert->len) < 0) { redwax_openssl_print_errors(r); X509_free(x); BIO_free(bio); return APR_EGENERAL; } BIO_flush(bio); X509_free(x); BIO_free(bio); } } } if (r->root_out || r->trust_out) { for (i = 0; i < r->trusted_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->trusted_out, i, const redwax_certificate_t); const unsigned char *der = cert->der; X509 *x = d2i_X509_AUX(NULL, &der, cert->len); if (x) { redwax_print_error(r, "der-out: trusted: %s\n", redwax_openssl_name(r->pool, X509_get_subject_name(x))); if (!strcmp(file, "-")) { if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } } else if ((bio = BIO_new(BIO_s_file())) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } else if (BIO_write_filename(bio, (char *)apr_psprintf(r->pool, "%s.ca.%d", file, i)) <= 0) { redwax_openssl_print_errors(r); BIO_free(bio); return APR_ENOENT; } if (BIO_write(bio, cert->der, cert->len) < 0) { redwax_openssl_print_errors(r); X509_free(x); BIO_free(bio); return APR_EGENERAL; } BIO_flush(bio); X509_free(x); BIO_free(bio); } } } if (r->crl_out) { for (i = 0; i < r->crls_out->nelts; i++) { const redwax_crl_t *crl = &APR_ARRAY_IDX(r->crls_out, i, const redwax_crl_t); const unsigned char *der = crl->der; X509_CRL *c = d2i_X509_CRL(NULL, &der, crl->len); if (c) { redwax_print_error(r, "der-out: crl: %s\n", redwax_openssl_name(r->pool, X509_CRL_get_issuer(c))); if (!strcmp(file, "-")) { if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) { redwax_openssl_print_errors(r); X509_CRL_free(c); return APR_ENOMEM; } } else if ((bio = BIO_new(BIO_s_file())) == NULL) { redwax_openssl_print_errors(r); X509_CRL_free(c); return APR_ENOMEM; } else if (BIO_write_filename(bio, (char *)apr_psprintf(r->pool, "%s.crl.%d", file, i)) <= 0) { redwax_openssl_print_errors(r); X509_CRL_free(c); BIO_free(bio); return APR_ENOENT; } if (BIO_write(bio, crl->der, crl->len) < 0) { redwax_openssl_print_errors(r); X509_CRL_free(c); BIO_free(bio); return APR_EGENERAL; } BIO_flush(bio); X509_CRL_free(c); BIO_free(bio); } } } if (r->key_out) { for (i = 0; i < r->keys_out->nelts; i++) { const redwax_key_t *key = &APR_ARRAY_IDX(r->keys_out, i, const redwax_key_t); BIO *kbio; PKCS8_PRIV_KEY_INFO *p8inf; EVP_PKEY *pkey; if (!key->der) { redwax_print_error(r, "der-out: non-extractable private key, skipping\n"); continue; } if ((kbio = BIO_new_mem_buf(key->der, key->len)) == NULL) { return APR_ENOMEM; } apr_pool_cleanup_register(r->pool, kbio, cleanup_bio, apr_pool_cleanup_null); if (!(p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(kbio, NULL))) { redwax_openssl_print_errors(r); return APR_ENOENT; } if (!(pkey = EVP_PKCS82PKEY(p8inf))) { redwax_openssl_print_errors(r); PKCS8_PRIV_KEY_INFO_free(p8inf); return APR_ENOENT; } #if HAVE_EVP_PKEY_GET0_DESCRIPTION redwax_print_error(r, "der-out: private key: %s\n", EVP_PKEY_get0_description(pkey)); #else redwax_print_error(r, "der-out: private key\n"); #endif if (!strcmp(file, "-")) { if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) { redwax_openssl_print_errors(r); EVP_PKEY_free(pkey); PKCS8_PRIV_KEY_INFO_free(p8inf); return APR_ENOMEM; } } else if ((bio = BIO_new(BIO_s_file())) == NULL) { redwax_openssl_print_errors(r); EVP_PKEY_free(pkey); PKCS8_PRIV_KEY_INFO_free(p8inf); return APR_ENOMEM; } else if (BIO_write_filename(bio, (char *)apr_psprintf(r->pool, "%s.key.%d", file, i)) <= 0) { redwax_openssl_print_errors(r); EVP_PKEY_free(pkey); PKCS8_PRIV_KEY_INFO_free(p8inf); BIO_free(bio); return APR_ENOENT; } if (BIO_write(bio, key->der, key->len) < 0) { redwax_openssl_print_errors(r); EVP_PKEY_free(pkey); PKCS8_PRIV_KEY_INFO_free(p8inf); BIO_free(bio); return APR_EGENERAL; } BIO_flush(bio); EVP_PKEY_free(pkey); PKCS8_PRIV_KEY_INFO_free(p8inf); BIO_free(bio); } } return APR_SUCCESS; } static apr_status_t redwax_openssl_process_pem_out_all(redwax_tool_t *r, const char *file, const char *secret) { BIO *bio; int i; if (!strcmp(file, "-")) { if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } } else if ((bio = BIO_new(BIO_s_file())) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } else if (BIO_write_filename(bio, (char *)file) <= 0) { redwax_openssl_print_errors(r); BIO_free(bio); return APR_ENOENT; } apr_pool_cleanup_register(r->pool, bio, cleanup_bio, apr_pool_cleanup_null); if (r->cert_out) { for (i = 0; i < r->certs_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_out, i, const redwax_certificate_t); const unsigned char *der = cert->der; X509 *x = d2i_X509(NULL, &der, cert->len); if (x) { redwax_print_error(r, "pem-out: certificate: %s\n", redwax_openssl_name(r->pool, X509_get_subject_name(x))); if ((r->text && !X509_print_ex(bio, x, 0, 0)) || !PEM_write_bio_X509(bio, x)) { redwax_openssl_print_errors(r); X509_free(x); return APR_ENOENT; } X509_free(x); } } } if (r->chain_out) { for (i = 0; i < r->intermediates_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->intermediates_out, i, const redwax_certificate_t); const unsigned char *der = cert->der; X509 *x = d2i_X509(NULL, &der, cert->len); if (x) { redwax_print_error(r, "pem-out: intermediate: %s\n", redwax_openssl_name(r->pool, X509_get_subject_name(x))); if ((r->text && !X509_print_ex(bio, x, 0, 0)) || !PEM_write_bio_X509(bio, x)) { redwax_openssl_print_errors(r); X509_free(x); return APR_ENOENT; } X509_free(x); } } } if (r->root_out || r->trust_out) { for (i = 0; i < r->trusted_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->trusted_out, i, const redwax_certificate_t); const unsigned char *der = cert->der; X509 *x = d2i_X509_AUX(NULL, &der, cert->len); if (x) { redwax_print_error(r, "pem-out: trusted: %s\n", redwax_openssl_name(r->pool, X509_get_subject_name(x))); if (r->trust_out) { if ((r->text && !X509_print_ex(bio, x, 0, 0)) || !PEM_write_bio_X509_AUX(bio, x)) { redwax_openssl_print_errors(r); X509_free(x); return APR_ENOENT; } } else { if ((r->text && !X509_print_ex(bio, x, 0, 0)) || !PEM_write_bio_X509(bio, x)) { redwax_openssl_print_errors(r); X509_free(x); return APR_ENOENT; } } X509_free(x); } } } if (r->crl_out) { for (i = 0; i < r->crls_out->nelts; i++) { const redwax_crl_t *crl = &APR_ARRAY_IDX(r->crls_out, i, const redwax_crl_t); const unsigned char *der = crl->der; X509_CRL *c = d2i_X509_CRL(NULL, &der, crl->len); if (c) { redwax_print_error(r, "pem-out: crl: %s\n", redwax_openssl_name(r->pool, X509_CRL_get_issuer(c))); if ((r->text && !X509_CRL_print(bio, c)) || !PEM_write_bio_X509_CRL(bio, c)) { redwax_openssl_print_errors(r); X509_CRL_free(c); return APR_ENOENT; } X509_CRL_free(c); } } } if (r->key_out) { for (i = 0; i < r->keys_out->nelts; i++) { const redwax_key_t *key = &APR_ARRAY_IDX(r->keys_out, i, const redwax_key_t); BIO *kbio; PKCS8_PRIV_KEY_INFO *p8inf; EVP_PKEY *pkey; if (!key->der) { redwax_print_error(r, "pem-out: non-extractable private key, skipping\n"); continue; } if ((kbio = BIO_new_mem_buf(key->der, key->len)) == NULL) { return APR_ENOMEM; } apr_pool_cleanup_register(r->pool, kbio, cleanup_bio, apr_pool_cleanup_null); if (!(p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(kbio, NULL)) || !(pkey = EVP_PKCS82PKEY(p8inf))) { redwax_openssl_print_errors(r); return APR_ENOENT; } #if HAVE_EVP_PKEY_GET0_DESCRIPTION redwax_print_error(r, "pem-out: private key: %s\n", EVP_PKEY_get0_description(pkey)); #else redwax_print_error(r, "pem-out: private key\n"); #endif if ((r->text && !EVP_PKEY_print_private(bio, pkey, 0, NULL))) { redwax_openssl_print_errors(r); return APR_ENOENT; } if (r->param_out) { PEM_write_bio_Parameters(bio, pkey); } if (!PEM_write_bio_PKCS8_PRIV_KEY_INFO(bio, p8inf)) { redwax_openssl_print_errors(r); return APR_ENOENT; } } } return APR_SUCCESS; } static apr_status_t process_pem_out_key(redwax_tool_t *r, BIO *bio, const redwax_key_t *key) { BIO *kbio; PKCS8_PRIV_KEY_INFO *p8inf; EVP_PKEY *pkey; if (!key->der) { redwax_print_error(r, "pem-out: non-extractable private key, skipping\n"); return APR_SUCCESS; } if ((kbio = BIO_new_mem_buf(key->der, key->len)) == NULL) { return APR_ENOMEM; } apr_pool_cleanup_register(r->pool, kbio, cleanup_bio, apr_pool_cleanup_null); if (!(p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(kbio, NULL)) || !(pkey = EVP_PKCS82PKEY(p8inf))) { redwax_openssl_print_errors(r); return APR_ENOENT; } #if HAVE_EVP_PKEY_GET0_DESCRIPTION redwax_print_error(r, "pem-out: private key: %s\n", EVP_PKEY_get0_description(pkey)); #else redwax_print_error(r, "pem-out: private key\n"); #endif if ((r->text && !EVP_PKEY_print_private(bio, pkey, 0, NULL))) { redwax_openssl_print_errors(r); return APR_ENOENT; } if (r->param_out) { PEM_write_bio_Parameters(bio, pkey); } if (!PEM_write_bio_PKCS8_PRIV_KEY_INFO(bio, p8inf)) { redwax_openssl_print_errors(r); return APR_ENOENT; } return APR_SUCCESS; } static apr_status_t process_pem_out_chain(redwax_tool_t *r, BIO *bio, const redwax_certificate_t *cert, const char *type) { apr_status_t status; int i; const unsigned char *der; X509 *x, *xi; der = cert->der; x = d2i_X509(NULL, &der, cert->len); if (x) { redwax_print_error(r, "pem-out: %s: %s\n", type, redwax_openssl_name(r->pool, X509_get_subject_name(x))); if ((r->text && !X509_print_ex(bio, x, 0, 0)) || !PEM_write_bio_X509(bio, x)) { redwax_openssl_print_errors(r); X509_free(x); return APR_ENOENT; } if (!X509_NAME_cmp(X509_get_issuer_name(x), X509_get_subject_name(x))) { /* self signed - end of chain */ return APR_SUCCESS; } if (r->chain_out) { for (i = 0; i < r->intermediates_out->nelts; i++) { const redwax_certificate_t *intermediate = &APR_ARRAY_IDX(r->intermediates_out, i, const redwax_certificate_t); const unsigned char *der = intermediate->der; xi = d2i_X509(NULL, &der, intermediate->len); if (xi) { if (X509_check_issued(xi, x) == X509_V_OK) { status = process_pem_out_chain(r, bio, intermediate, "intermediate"); if (APR_SUCCESS != status) { return status; } } X509_free(xi); } } } if (r->root_out || r->trust_out) { for (i = 0; i < r->trusted_out->nelts; i++) { const redwax_certificate_t *trusted = &APR_ARRAY_IDX(r->trusted_out, i, const redwax_certificate_t); const unsigned char *der = trusted->der; xi = d2i_X509_AUX(NULL, &der, cert->len); if (xi) { if (X509_check_issued(xi, x) == X509_V_OK) { status = process_pem_out_chain(r, bio, trusted, "trusted"); if (APR_SUCCESS != status) { return status; } } X509_free(xi); } } } // todo loop chain X509_free(x); } return APR_SUCCESS; } static apr_status_t process_pem_out_certificate(redwax_tool_t *r, BIO *bio, const redwax_certificate_t *cert, const char *type, int last) { apr_status_t status; redwax_key_t *key = apr_hash_get(r->keys_index, cert->common.subjectpublickeyinfo_der, cert->common.subjectpublickeyinfo_len); if (!key) { /* ignore all certs without keys */ return APR_SUCCESS; } if (!last && r->key_out) { status = process_pem_out_key(r, bio, key); if (APR_SUCCESS != status) { return status; } } status = process_pem_out_chain(r, bio, cert, type); if (APR_SUCCESS != status) { return status; } if (last && r->key_out) { status = process_pem_out_key(r, bio, key); if (APR_SUCCESS != status) { return status; } } return APR_SUCCESS; } static apr_status_t redwax_openssl_process_pem_out_key(redwax_tool_t *r, const char *file, const char *secret, int last) { BIO *bio; apr_status_t status; int i; if (!strcmp(file, "-")) { if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } } else if ((bio = BIO_new(BIO_s_file())) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } else if (BIO_write_filename(bio, (char *)file) <= 0) { redwax_openssl_print_errors(r); BIO_free(bio); return APR_ENOENT; } apr_pool_cleanup_register(r->pool, bio, cleanup_bio, apr_pool_cleanup_null); if (r->cert_out) { for (i = 0; i < r->certs_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_out, i, const redwax_certificate_t); status = process_pem_out_certificate(r, bio, cert, "certificate", last); if (APR_SUCCESS != status) { return status; } } } if (r->chain_out) { for (i = 0; i < r->intermediates_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->intermediates_out, i, const redwax_certificate_t); status = process_pem_out_certificate(r, bio, cert, "intermediate", last); if (APR_SUCCESS != status) { return status; } } } if (r->root_out || r->trust_out) { for (i = 0; i < r->trusted_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->trusted_out, i, const redwax_certificate_t); status = process_pem_out_certificate(r, bio, cert, "trusted", last); if (APR_SUCCESS != status) { return status; } } } return APR_SUCCESS; } static apr_status_t redwax_openssl_process_pem_out(redwax_tool_t *r, const char *file, const char *secret) { switch (r->order) { case REDWAX_ORDER_ALL: return redwax_openssl_process_pem_out_all(r, file, secret); case REDWAX_ORDER_KEY_FIRST: return redwax_openssl_process_pem_out_key(r, file, secret, 0); case REDWAX_ORDER_KEY_LAST: return redwax_openssl_process_pem_out_key(r, file, secret, 1); default: return DECLINED; } } static const char *read_secret(redwax_tool_t *r, const char *what, const char *file, const char *secret, int min, int max, int verify, apr_pool_t *pool) { /* * Obtain a secret to encrypt a key. * * Secret file specified and secret file exists, use that secret. * * Secret file specified and secret file does not exist, generate * a random secret and write the secret to the file. * * No secret file specified, tell openssl to ask for the secret from * the ui twice. */ if (secret) { apr_file_t *sfile; apr_status_t status; status = apr_file_open(&sfile, secret, APR_FOPEN_READ, APR_FPROT_OS_DEFAULT, pool); if (APR_SUCCESS == status) { char *buf = apr_palloc(pool, max + 2); char *lf; #if HAVE_APR_CRYPTO_CLEAR apr_crypto_clear(pool, buf, max + 2); #endif status = apr_file_gets(buf, max + 2, sfile); if (APR_SUCCESS != status) { redwax_print_error(r, "Could not read '%s': %pm\n", secret, &status); return NULL; } lf = strrchr(buf, '\n'); if (lf) { *lf = 0; } return buf; } else if (APR_ENOENT == status) { } else { redwax_print_error(r, "Could not open '%s': %pm\n", secret, &status); return NULL; } } else { UI *ui; char *prompt = NULL; char *buf = apr_palloc(pool, max + 2); char *buff = apr_palloc(pool, max + 2); #if HAVE_APR_CRYPTO_CLEAR apr_crypto_clear(pool, buf, max + 2); apr_crypto_clear(pool, buff, max + 2); #endif /* skip command completion */ if (r->complete) { return NULL; } ui = UI_new(); if (ui == NULL) { return NULL; } apr_pool_cleanup_register(pool, ui, cleanup_ui, apr_pool_cleanup_null); if ((prompt = UI_construct_prompt(ui, what, file)) == NULL) { return NULL; } apr_pool_cleanup_register(pool, prompt, cleanup_alloc, apr_pool_cleanup_null); if (verify) { if (UI_add_input_string(ui, prompt, 0, buf, min, max) < 0 || (UI_add_verify_string(ui, prompt, 0, buff, min, max, buf) < 0)) { return NULL; } } else { if (UI_add_input_string(ui, prompt, 0, buf, min, max) < 0) { return NULL; } } if (!UI_process(ui)) { return buf; } else { redwax_print_error(r, "Could not read %s. Must be between %d and %d characters.\n", what, min, max); } } return NULL; } static apr_status_t import_bags(redwax_tool_t *r, const char *file, const char *secret, const STACK_OF(PKCS12_SAFEBAG) *bags, const char **pass, apr_size_t *pass_len); static apr_status_t import_bag(redwax_tool_t *r, const char *file, const char *secret, const PKCS12_SAFEBAG *bag, const char **pass, apr_size_t *pass_len) { const PKCS8_PRIV_KEY_INFO *p8inf; switch (PKCS12_SAFEBAG_get_nid(bag)) { case NID_keyBag: { redwax_key_t *key; BIO *kbio; const ASN1_TYPE *label; p8inf = PKCS12_SAFEBAG_get0_p8inf(bag); key = apr_array_push(r->keys_in); apr_pool_create(&key->pool, r->pool); key->per_module = redwax_create_module_config(key->pool); if ((kbio = BIO_new(BIO_s_mem())) == NULL) { return APR_ENOMEM; } apr_pool_cleanup_register(key->pool, kbio, cleanup_bio, apr_pool_cleanup_null); i2d_PKCS8_PRIV_KEY_INFO_bio(kbio, (PKCS8_PRIV_KEY_INFO *)p8inf); key->len = BIO_get_mem_data(kbio, &key->der); key->origin = file; if ((label = PKCS12_SAFEBAG_get0_attr(bag, NID_friendlyName))) { if (label->type == V_ASN1_BMPSTRING) { #if HAVE_OPENSSL_UNI2UTF8 key->label = OPENSSL_uni2utf8(label->value.bmpstring->data, label->value.bmpstring->length); #else key->label = OPENSSL_uni2asc(label->value.bmpstring->data, label->value.bmpstring->length); #endif key->label_len = strlen(key->label); apr_pool_cleanup_register(r->pool, key->label, cleanup_alloc, apr_pool_cleanup_null); break; } } rt_run_normalise_key(r, key, 1); redwax_print_error(r, "pkcs12-in: private key: %s\n", redwax_pencode_base16_binary(r->pool, key->common.id_der, key->common.id_len, REDWAX_ENCODE_LOWER, NULL)); break; } case NID_pkcs8ShroudedKeyBag: { redwax_key_t *key; BIO *kbio; const ASN1_TYPE *label; if (!(*pass)) { *pass = read_secret(r, "PKCS12 import passphrase", file, secret, 0, REDWAX_PKCS12_MAX, 0, r->pool); if (!(*pass)) { return APR_ENOENT; } *pass_len = strlen(*pass); } if ((p8inf = (const PKCS8_PRIV_KEY_INFO*) PKCS12_decrypt_skey( (PKCS12_SAFEBAG *)bag, *pass, *pass_len)) == NULL) { redwax_openssl_print_errors(r); return APR_EINVAL; } apr_pool_cleanup_register(r->pool, p8inf, cleanup_p8inf, apr_pool_cleanup_null); key = apr_array_push(r->keys_in); apr_pool_create(&key->pool, r->pool); key->per_module = redwax_create_module_config(key->pool); if ((kbio = BIO_new(BIO_s_mem())) == NULL) { return APR_ENOMEM; } apr_pool_cleanup_register(key->pool, kbio, cleanup_bio, apr_pool_cleanup_null); i2d_PKCS8_PRIV_KEY_INFO_bio(kbio, (PKCS8_PRIV_KEY_INFO *)p8inf); key->len = BIO_get_mem_data(kbio, &key->der); key->origin = file; if ((label = PKCS12_SAFEBAG_get0_attr(bag, NID_friendlyName))) { if (label->type == V_ASN1_BMPSTRING) { #if HAVE_OPENSSL_UNI2UTF8 key->label = OPENSSL_uni2utf8(label->value.bmpstring->data, label->value.bmpstring->length); #else key->label = OPENSSL_uni2asc(label->value.bmpstring->data, label->value.bmpstring->length); #endif key->label_len = strlen(key->label); apr_pool_cleanup_register(key->pool, key->label, cleanup_alloc, apr_pool_cleanup_null); } } rt_run_normalise_key(r, key, 1); redwax_print_error(r, "pkcs12-in: private key: %s\n", redwax_pencode_base16_binary(r->pool, key->common.id_der, key->common.id_len, REDWAX_ENCODE_LOWER, NULL)); break; } case NID_certBag: { redwax_certificate_t *cert; BIO *bio; const ASN1_TYPE *label; X509 *x; if (PKCS12_SAFEBAG_get_bag_nid(bag) != NID_x509Certificate) { break; } x = PKCS12_SAFEBAG_get1_cert((PKCS12_SAFEBAG *)bag); apr_pool_cleanup_register(r->pool, x, cleanup_x509, apr_pool_cleanup_null); if (!x) { redwax_print_error(r, "Could not read certificate from '%s', skipping.\n", file); redwax_openssl_print_errors(r); break; } if (X509_check_ca(x)) { cert = apr_array_push(r->intermediates_in); apr_pool_create(&cert->pool, r->pool); cert->common.type = REDWAX_CERTIFICATE_X509; cert->common.category = REDWAX_CERTIFICATE_INTERMEDIATE; redwax_print_error(r, "pkcs12-in: intermediate: %s\n", redwax_openssl_name(cert->pool, X509_get_subject_name(x))); } else { cert = apr_array_push(r->certs_in); apr_pool_create(&cert->pool, r->pool); cert->common.type = REDWAX_CERTIFICATE_X509; cert->common.category = REDWAX_CERTIFICATE_END_ENTITY; redwax_print_error(r, "pkcs12-in: certificate: %s\n", redwax_openssl_name(cert->pool, X509_get_subject_name(x))); } if ((bio = BIO_new(BIO_s_mem())) == NULL) { return APR_ENOMEM; } apr_pool_cleanup_register(cert->pool, bio, cleanup_bio, apr_pool_cleanup_null); i2d_X509_bio(bio, x); cert->len = BIO_get_mem_data(bio, &cert->der); cert->origin = file; if ((label = PKCS12_SAFEBAG_get0_attr(bag, NID_friendlyName))) { if (label->type == V_ASN1_BMPSTRING) { #if HAVE_OPENSSL_UNI2UTF8 cert->label = OPENSSL_uni2utf8(label->value.bmpstring->data, label->value.bmpstring->length); #else cert->label = OPENSSL_uni2asc(label->value.bmpstring->data, label->value.bmpstring->length); #endif cert->label_len = strlen(cert->label); apr_pool_cleanup_register(cert->pool, cert->label, cleanup_alloc, apr_pool_cleanup_null); } } rt_run_normalise_certificate(r, cert, 1); break; } case NID_crlBag: { redwax_crl_t *crl; BIO *bio; X509_CRL *c; if (PKCS12_SAFEBAG_get_bag_nid(bag) != NID_x509Crl) { break; } c = PKCS12_SAFEBAG_get1_crl((PKCS12_SAFEBAG *)bag); if (c) { redwax_print_error(r, "pkcs12-in: crl: %s\n", redwax_openssl_name(r->pool, X509_CRL_get_issuer(c))); sk_X509_CRL_push(crl_index, c); crl = apr_array_push(r->crls_in); apr_pool_create(&crl->pool, r->pool); if ((bio = BIO_new(BIO_s_mem())) == NULL) { return APR_ENOMEM; } apr_pool_cleanup_register(crl->pool, bio, cleanup_bio, apr_pool_cleanup_null); i2d_X509_CRL_bio(bio, c); crl->len = BIO_get_mem_data(bio, &crl->der); crl->origin = file; /* no cleanup because of sk_X509_CRL_push() */ #if 0 apr_pool_cleanup_register(r->pool, c, cleanup_x509_crl, apr_pool_cleanup_null); #endif } break; } case NID_safeContentsBag: { return import_bags(r, file, secret, PKCS12_SAFEBAG_get0_safes(bag), pass, pass_len); } default: break; } return APR_SUCCESS; } static apr_status_t import_bags(redwax_tool_t *r, const char *file, const char *secret, const STACK_OF(PKCS12_SAFEBAG) *bags, const char **pass, apr_size_t *pass_len) { int i; apr_status_t status; for (i = 0; i < sk_PKCS12_SAFEBAG_num(bags); i++) { status = import_bag(r, file, secret, sk_PKCS12_SAFEBAG_value(bags, i), pass, pass_len); if (APR_SUCCESS != status) { return status; } } return APR_SUCCESS; } static apr_status_t redwax_openssl_process_pkcs12_in(redwax_tool_t *r, const char *file, const char *secret) { PKCS12 *p12 = NULL; STACK_OF(PKCS7) *asafes = NULL; STACK_OF(PKCS12_SAFEBAG) *bags; BIO *bio; const char *pass = NULL; apr_size_t pass_len = 0; apr_status_t status; int i, bagnid; if (!strcmp(file, "-")) { if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } } else if ((bio = BIO_new(BIO_s_file())) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } else if (BIO_read_filename(bio, (char *)file) <= 0) { redwax_openssl_print_errors(r); BIO_free(bio); return APR_ENOENT; } apr_pool_cleanup_register(r->pool, bio, cleanup_bio, apr_pool_cleanup_null); if (!(p12 = d2i_PKCS12_bio(bio, NULL))) { redwax_openssl_print_errors(r); return APR_ENOENT; } if (!(asafes = PKCS12_unpack_authsafes(p12))) { redwax_openssl_print_errors(r); return APR_ENOENT; } apr_pool_cleanup_register(r->pool, asafes, cleanup_sk_pkcs7, apr_pool_cleanup_null); for (i = 0; i < sk_PKCS7_num(asafes); i++) { PKCS7 *p7; p7 = sk_PKCS7_value(asafes, i); bagnid = OBJ_obj2nid(p7->type); if (bagnid == NID_pkcs7_data) { bags = PKCS12_unpack_p7data(p7); } else if (bagnid == NID_pkcs7_encrypted) { if (!pass) { pass = read_secret(r, "PKCS12 import passphrase", file, secret, 0, REDWAX_PKCS12_MAX, 0, r->pool); if (!pass) { return APR_ENOENT; } pass_len = strlen(pass); } bags = PKCS12_unpack_p7encdata(p7, pass, pass_len); } else { continue; } if (!bags) { redwax_openssl_print_errors(r); return APR_ENOENT; } apr_pool_cleanup_register(r->pool, bags, cleanup_sk_pkcs2_safebag, apr_pool_cleanup_null); status = import_bags(r, file, secret, bags, &pass, &pass_len); if (APR_SUCCESS != status) { return status; } } return APR_SUCCESS; } static apr_status_t redwax_openssl_process_pkcs12_out(redwax_tool_t *r, const char *file, const char *secret) { PKCS12 *p12 = NULL; X509 *x = NULL; STACK_OF(X509) *xis = NULL; const char *pass; const char *name = NULL; EVP_PKEY *pkey = NULL; int iter = PKCS12_DEFAULT_ITER, mac_iter = PKCS12_DEFAULT_ITER; int key_pbe = NID_pbe_WithSHA1And3_Key_TripleDES_CBC; int cert_pbe = NID_pbe_WithSHA1And3_Key_TripleDES_CBC; int keytype = 0; BIO *bio; int i; if (!strcmp(file, "-")) { if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } } else if ((bio = BIO_new(BIO_s_file())) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } else if (BIO_write_filename(bio, (char *)file) <= 0) { redwax_openssl_print_errors(r); BIO_free(bio); return APR_ENOENT; } apr_pool_cleanup_register(r->pool, bio, cleanup_bio, apr_pool_cleanup_null); xis = sk_X509_new_null(); apr_pool_cleanup_register(r->pool, xis, cleanup_sk_x509, apr_pool_cleanup_null); if (r->cert_out) { if (!r->certs_out->nelts) { redwax_print_error(r, "Warning: no certificate present, skipping pkcs12.\n"); return APR_ENOENT; } else if (r->certs_out->nelts == 1) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_out, 0, const redwax_certificate_t); const unsigned char *der = cert->der; x = d2i_X509(NULL, &der, cert->len); if (!x) { redwax_openssl_print_errors(r); return APR_ENOENT; } redwax_print_error(r, "pkcs12-out: certificate: %s\n", redwax_openssl_name(r->pool, X509_get_subject_name(x))); apr_pool_cleanup_register(r->pool, x, cleanup_x509, apr_pool_cleanup_null); } else { redwax_print_error(r, "Warning: more than one certificate present (%d), skipping pkcs12.\n", r->certs_out->nelts); return APR_ENOENT; } } if (r->chain_out) { for (i = 0; i < r->intermediates_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->intermediates_out, i, const redwax_certificate_t); const unsigned char *der = cert->der; X509 *xi = d2i_X509(NULL, &der, cert->len); if (!xi) { redwax_openssl_print_errors(r); X509_free(x); return APR_ENOENT; } redwax_print_error(r, "pkcs12-out: intermediate: %s\n", redwax_openssl_name(r->pool, X509_get_subject_name(xi))); sk_X509_push(xis, xi); } } if (r->trust_out) { for (i = 0; i < r->trusted_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->trusted_out, i, const redwax_certificate_t); const unsigned char *der = cert->der; X509 *xi = d2i_X509_AUX(NULL, &der, cert->len); if (!xi) { redwax_openssl_print_errors(r); X509_free(x); return APR_ENOENT; } redwax_print_error(r, "pkcs12-out: trusted: %s\n", redwax_openssl_name(r->pool, X509_get_subject_name(xi))); sk_X509_push(xis, xi); } } if (r->key_out) { if (!r->keys_out->nelts) { redwax_print_error(r, "Warning: no key present, skipping pkcs12.\n"); return APR_ENOENT; } else if (r->keys_out->nelts == 1) { const redwax_key_t *key = &APR_ARRAY_IDX(r->keys_out, 0, const redwax_key_t); const unsigned char *der = key->der; if (!key->der) { redwax_print_error(r, "pkcs12-out: non-extractable private key, skipping\n"); return APR_ENOENT; } pkey = d2i_AutoPrivateKey(NULL, &der, key->len); if (!pkey) { redwax_openssl_print_errors(r); return APR_ENOENT; } #if HAVE_EVP_PKEY_GET0_DESCRIPTION redwax_print_error(r, "pkcs12-out: private key: %s\n", EVP_PKEY_get0_description(pkey)); #else redwax_print_error(r, "pkcs12-out: private key\n"); #endif apr_pool_cleanup_register(r->pool, pkey, cleanup_evp_pkey, apr_pool_cleanup_null); } else { redwax_print_error(r, "Warning: more than one key present (%d), skipping.\n", r->keys_out->nelts); return APR_ENOENT; } } pass = read_secret(r, "PKCS12 export passphrase", file, secret, REDWAX_PKCS12_MIN, REDWAX_PKCS12_MAX, 1, r->pool); if (!pass) { return APR_ENOENT; } if (r->label_out) { name = r->label_out; } else if (x) { X509_NAME *subject = X509_get_subject_name(x); int lastpos = -1; if (subject) { lastpos = X509_NAME_get_index_by_NID(subject, NID_commonName, -1); } if (lastpos > -1) { X509_NAME_ENTRY *e; ASN1_STRING *cn; BIO *lbio; char *buf = NULL; int len = 0; e = X509_NAME_get_entry(subject, lastpos); cn = X509_NAME_ENTRY_get_data(e); if ((lbio = BIO_new(BIO_s_mem())) == NULL) { return APR_ENOMEM; } ASN1_STRING_print_ex(lbio, cn, ASN1_STRFLGS_ESC_2253 | ASN1_STRFLGS_ESC_CTRL | ASN1_STRFLGS_UTF8_CONVERT); len = BIO_get_mem_data(lbio, &buf); name = apr_psprintf(r->pool, "%.*s", len, buf); BIO_free(lbio); } } p12 = PKCS12_create((char *)pass, (char *)name, pkey, x, xis, key_pbe, cert_pbe, iter, mac_iter, keytype); if (!p12) { redwax_openssl_print_errors(r); return APR_ENOENT; } else { apr_pool_cleanup_register(r->pool, p12, cleanup_pkcs12, apr_pool_cleanup_null); i2d_PKCS12_bio(bio, p12); } return APR_SUCCESS; } static void redwax_openssl_ssh_bignum(BIO *bio, unsigned char *buf, apr_size_t len) { apr_size_t padded_len = len; unsigned char c; if (buf[0] & 0x80) { padded_len++; } c = padded_len >> 24; BIO_write(bio, &c, 1); c = padded_len >> 16; BIO_write(bio, &c, 1); c = padded_len >> 8; BIO_write(bio, &c, 1); c = padded_len; BIO_write(bio, &c, 1); if (buf[0] & 0x80) { c = 0; BIO_write(bio, &c, 1); } BIO_write(bio, buf, len); } static apr_status_t redwax_openssl_process_ssh_public_out(redwax_tool_t *r, const char *file, const char *secret) { BIO *bio; int i; if (!strcmp(file, "-")) { if ((bio = BIO_new_fp(stdout, BIO_NOCLOSE)) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } } else if ((bio = BIO_new(BIO_s_file())) == NULL) { redwax_openssl_print_errors(r); return APR_ENOMEM; } else if (BIO_write_filename(bio, (char *)file) <= 0) { redwax_openssl_print_errors(r); BIO_free(bio); return APR_ENOENT; } apr_pool_cleanup_register(r->pool, bio, cleanup_bio, apr_pool_cleanup_null); if (r->key_out) { for (i = 0; i < r->keys_out->nelts; i++) { BIO *b64; const redwax_key_t *key = &APR_ARRAY_IDX(r->keys_out, i, const redwax_key_t); switch (key->common.type) { case REDWAX_KEY_RSA: { static unsigned char sshHeader[11] = { 0x00, 0x00, 0x00, 0x07, 's', 's', 'h', '-', 'r', 's', 'a'}; if (!key->rsa) { redwax_print_error(r, "ssh-public-out: no rsa components, skipping\n"); break; } b64 = BIO_new(BIO_f_base64()); BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); BIO_printf(bio, "ssh-rsa "); bio = BIO_push(b64, bio); BIO_write(bio, sshHeader, sizeof(sshHeader)); redwax_openssl_ssh_bignum(bio, key->rsa->public_exponent, key->rsa->public_exponent_len); redwax_openssl_ssh_bignum(bio, key->rsa->modulus, key->rsa->modulus_len); BIO_flush(bio); bio = BIO_pop(b64); BIO_printf(bio, " %s\n", "label"); BIO_flush(bio); BIO_free(b64); redwax_print_error(r, "ssh-public-out: private key\n"); break; } default: { redwax_print_error(r, "ssh-public-out: unsupported key, skipping\n"); break; } } } } return APR_SUCCESS; } apr_status_t redwax_openssl_writev(void *ctx, const struct iovec *vec, apr_size_t nvec) { apr_size_t nbytes; return apr_file_writev(ctx, vec, nvec, &nbytes); } static apr_status_t redwax_openssl_spki_metadata(redwax_tool_t *r, redwax_metadata_t *m, const X509_PUBKEY *xpkey) { EVP_PKEY *pkey = NULL; ASN1_OBJECT *xpoid; BIO *bio; char *buf = NULL; int len = 0; X509_PUBKEY_get0_param(&xpoid, NULL, NULL, NULL, (X509_PUBKEY *) xpkey); redwax_metadata_push_object(m, "SubjectPublicKeyInfo", 0); if (!(bio = BIO_new(BIO_s_mem()))) { return APR_ENOMEM; } i2a_ASN1_OBJECT(bio, xpoid); len = BIO_get_mem_data(bio, &buf); redwax_metadata_add_string(m, "PublicKeyAlgorithm", apr_psprintf(m->pool, "%.*s", len, buf)); BIO_free(bio); pkey = X509_PUBKEY_get((X509_PUBKEY *)xpkey); if (pkey) { #if 0 // EVP_PKEY_print_public(bp, pkey, 16, NULL); #endif } redwax_metadata_pop_object(m); return APR_SUCCESS; } static apr_status_t redwax_openssl_general_name_metadata(redwax_tool_t *r, redwax_metadata_t *m, GENERAL_NAME *gen) { BIO *bio; const char *key = NULL; const char *okey = NULL; if (!(bio = BIO_new(BIO_s_mem()))) { return APR_ENOMEM; } switch (gen->type) { case GEN_OTHERNAME: { int nid; nid = OBJ_obj2nid(gen->d.otherName->type_id); #ifdef NID_SRVName if ((nid == NID_SRVName && gen->d.otherName->value->type != V_ASN1_IA5STRING) || (nid != NID_SRVName && gen->d.otherName->value->type != V_ASN1_UTF8STRING)) { redwax_print_error(r, "metadata-out: unsupported othername with nid %d, ignoring\n", nid); break; } #endif switch (nid) { #ifdef NID_id_on_SmtpUTF8Mailbox case NID_id_on_SmtpUTF8Mailbox: key = "OtherName"; okey = "SmtpUTF8Mailbox"; BIO_printf(bio, "%s", gen->d.otherName->value->value.utf8string->data); break; #endif #ifdef NID_XmppAddr case NID_XmppAddr: key = "OtherName"; okey = "XmppAddr"; BIO_printf(bio, "%s", gen->d.otherName->value->value.utf8string->data); break; #endif #ifdef NID_SRVName case NID_SRVName: key = "OtherName"; okey = "SRVName"; BIO_printf(bio, "%s", gen->d.otherName->value->value.ia5string->data); break; #endif case NID_ms_upn: key = "OtherName"; okey = "UPN"; BIO_printf(bio, "%s", gen->d.otherName->value->value.utf8string->data); break; #ifdef NID_NAIRealm case NID_NAIRealm: key = "OtherName"; okey = "NAIRealm"; BIO_printf(bio, "%s", gen->d.otherName->value->value.utf8string->data); break; #endif default: redwax_print_error(r, "metadata-out: unsupported othername with nid %d, ignoring\n", nid); break; } break; } case GEN_X400: { /* X400Name unsupported */ redwax_print_error(r, "metadata-out: unsupported X400Name, ignoring\n"); break; } case GEN_EDIPARTY: { /* EdiPartyName unsupported */ /* Maybe fix this: it is supported now */ redwax_print_error(r, "metadata-out: unsupported EdiPartyName, ignoring\n"); break; } case GEN_EMAIL: { key = "Email"; ASN1_STRING_print(bio, gen->d.ia5); break; } case GEN_DNS: { key = "DNS"; ASN1_STRING_print(bio, gen->d.ia5); break; } case GEN_URI: { key = "URI"; ASN1_STRING_print(bio, gen->d.ia5); break; } case GEN_DIRNAME: { key = "DirName"; X509_NAME_print_ex(bio, gen->d.dirn, 0, XN_FLAG_RFC2253); break; } case GEN_IPADD: { unsigned char *p; int i; p = gen->d.ip->data; if (gen->d.ip->length == 4) { key = "IPAddress"; BIO_printf(bio, "%d.%d.%d.%d", p[0], p[1], p[2], p[3]); } else if (gen->d.ip->length == 16) { key = "IPAddress"; for (i = 0; i < 8; i++) { if (i) { BIO_puts(bio, ":"); } BIO_printf(bio, "%X", p[0] << 8 | p[1]); p += 2; } } break; } case GEN_RID: { key = "RegisteredID"; i2a_ASN1_OBJECT(bio, gen->d.rid); break; } } if (key) { char *buf = NULL; int len = 0; len = BIO_get_mem_data(bio, &buf); if (okey) { redwax_metadata_push_object(m, key, 0); redwax_metadata_add_string(m, okey, apr_psprintf(m->pool, "%.*s", len, buf)); redwax_metadata_pop_object(m); } else { redwax_metadata_add_string(m, key, apr_psprintf(m->pool, "%.*s", len, buf)); } } BIO_free(bio); return APR_SUCCESS; } static apr_status_t redwax_openssl_general_names_metadata(redwax_tool_t *r, redwax_metadata_t *m, STACK_OF(GENERAL_NAME) *gens) { int i; if (sk_GENERAL_NAME_num(gens)) { redwax_metadata_push_array(m, "Names", 0); for (i = 0; i < sk_GENERAL_NAME_num(gens); i++) { GENERAL_NAME *gen; gen = sk_GENERAL_NAME_value(gens, i); redwax_metadata_push_object(m, "Name", 0); redwax_openssl_general_name_metadata(r, m, gen); redwax_metadata_pop_object(m); } redwax_metadata_pop_array(m); } return APR_SUCCESS; } static BIT_STRING_BITNAME ns_cert_type_table[] = { {0, "SSL Client", "client"}, {1, "SSL Server", "server"}, {2, "S/MIME", "email"}, {3, "Object Signing", "objsign"}, {4, "Unused", "reserved"}, {5, "SSL CA", "sslCA"}, {6, "S/MIME CA", "emailCA"}, {7, "Object Signing CA", "objCA"}, {-1, NULL, NULL} }; static BIT_STRING_BITNAME key_usage_type_table[] = { {0, "Digital Signature", "digitalSignature"}, {1, "Non Repudiation", "nonRepudiation"}, {2, "Key Encipherment", "keyEncipherment"}, {3, "Data Encipherment", "dataEncipherment"}, {4, "Key Agreement", "keyAgreement"}, {5, "Certificate Sign", "keyCertSign"}, {6, "CRL Sign", "cRLSign"}, {7, "Encipher Only", "encipherOnly"}, {8, "Decipher Only", "decipherOnly"}, {-1, NULL, NULL} }; static const BIT_STRING_BITNAME reason_flags[] = { {0, "Unused", "unused"}, {1, "Key Compromise", "keyCompromise"}, {2, "CA Compromise", "CACompromise"}, {3, "Affiliation Changed", "affiliationChanged"}, {4, "Superseded", "superseded"}, {5, "Cessation Of Operation", "cessationOfOperation"}, {6, "Certificate Hold", "certificateHold"}, {7, "Privilege Withdrawn", "privilegeWithdrawn"}, {8, "AA Compromise", "AACompromise"}, {-1, NULL, NULL} }; static apr_status_t redwax_openssl_extension_metadata(redwax_tool_t *r, redwax_metadata_t *m, X509_EXTENSION *ex) { ASN1_OBJECT *obj; BIO *bio; char *buf = NULL; int len = 0; int crit, nid; obj = X509_EXTENSION_get_object(ex); if ((bio = BIO_new(BIO_s_mem())) == NULL) { return APR_ENOMEM; } i2a_ASN1_OBJECT(bio, obj); len = BIO_get_mem_data(bio, &buf); redwax_metadata_push_object(m, apr_pstrip_whitespace(m->pool, buf, len), 0); BIO_free(bio); crit = X509_EXTENSION_get_critical(ex); if (crit) { redwax_metadata_add_boolean(m, "Critical", 1); } nid = OBJ_obj2nid(obj); switch (nid) { case NID_basic_constraints: { BASIC_CONSTRAINTS *bs = X509V3_EXT_d2i(ex); if (bs) { redwax_metadata_add_boolean(m, "CA", bs->ca); if (bs->pathlen) { if ((bs->pathlen->type == V_ASN1_NEG_INTEGER) || !bs->ca) { redwax_metadata_add_boolean(m, "InvalidPathLength", 1); } else { redwax_metadata_add_long(m, "PathLength", ASN1_INTEGER_get(bs->pathlen)); } } BASIC_CONSTRAINTS_free(bs); } break; } case NID_key_usage: { ASN1_BIT_STRING *usage = X509V3_EXT_d2i(ex); apr_uint32_t ex_kusage; int i, usages = 0; if (usage->length > 0) { ex_kusage = usage->data[0]; if (usage->length > 1) ex_kusage |= usage->data[1] << 8; } else { ex_kusage = 0; } for (i = 0; key_usage_type_table[i].lname; i++) { if ((ex_kusage >> i) & 1) { usages++; } } if (usages) { redwax_metadata_push_array(m, "KeyUsages", 0); for (i = 0; key_usage_type_table[i].sname; i++) { if ((ex_kusage >> i) & 1) { redwax_metadata_add_string(m, "KeyUsage", key_usage_type_table[i].sname); } } redwax_metadata_pop_array(m); } ASN1_BIT_STRING_free(usage); break; } case NID_ext_key_usage: { EXTENDED_KEY_USAGE *eku = X509V3_EXT_d2i(ex); int i; if (sk_ASN1_OBJECT_num(eku)) { redwax_metadata_push_array(m, "ExtendedKeyUsages", 0); for (i = 0; i < sk_ASN1_OBJECT_num(eku); i++) { char obj_tmp[80]; ASN1_OBJECT *obj; obj = sk_ASN1_OBJECT_value(eku, i); i2t_ASN1_OBJECT(obj_tmp, sizeof(obj_tmp), obj); redwax_metadata_add_string(m, "ExtendedKeyUsage", key_usage_type_table[i].sname); } redwax_metadata_pop_array(m); } sk_ASN1_OBJECT_pop_free(eku, ASN1_OBJECT_free); break; } case NID_netscape_cert_type: { ASN1_BIT_STRING *nscert = X509V3_EXT_d2i(ex); apr_uint32_t ex_nscert; int i, nscerts = 0; if (nscert->length > 0) { ex_nscert = nscert->data[0]; if (nscert->length > 1) ex_nscert |= nscert->data[1] << 8; } else { ex_nscert = 0; } for (i = 0; ns_cert_type_table[i].lname; i++) { if ((ex_nscert >> i) & 1) { nscerts++; } } if (nscerts) { redwax_metadata_push_array(m, "CertificateTypes", 0); for (i = 0; ns_cert_type_table[i].sname; i++) { if ((ex_nscert >> i) & 1) { redwax_metadata_add_string(m, "CertificateType", ns_cert_type_table[i].sname); } } redwax_metadata_pop_array(m); } ASN1_BIT_STRING_free(nscert); break; } case NID_info_access: case NID_sinfo_access: { AUTHORITY_INFO_ACCESS *ainfo = X509V3_EXT_d2i(ex); int i; if (sk_ACCESS_DESCRIPTION_num(ainfo)) { redwax_metadata_push_array(m, "Accesses", 0); for (i = 0; i < sk_ACCESS_DESCRIPTION_num(ainfo); i++) { char objtmp[80]; ACCESS_DESCRIPTION *desc; desc = sk_ACCESS_DESCRIPTION_value(ainfo, i); i2t_ASN1_OBJECT(objtmp, sizeof(objtmp), desc->method); redwax_metadata_push_object(m, "Access", 0); redwax_metadata_push_object(m, apr_pstrip_whitespace(m->pool, objtmp, strlen(objtmp)), 0); redwax_openssl_general_name_metadata(r, m, desc->location); redwax_metadata_pop_object(m); redwax_metadata_pop_object(m); } redwax_metadata_pop_array(m); } AUTHORITY_INFO_ACCESS_free(ainfo); break; } case NID_subject_alt_name: case NID_issuer_alt_name: case NID_certificate_issuer: { GENERAL_NAMES *gens = X509V3_EXT_d2i(ex); redwax_openssl_general_names_metadata(r, m, gens); break; } case NID_subject_key_identifier: { ASN1_OCTET_STRING *oct = X509V3_EXT_d2i(ex); redwax_metadata_add_string(m, "ID", redwax_pencode_base16_binary(m->pool, oct->data, oct->length, REDWAX_ENCODE_LOWER | REDWAX_ENCODE_COLON, NULL)); break; } case NID_authority_key_identifier: { AUTHORITY_KEYID *akeyid = X509V3_EXT_d2i(ex); if (akeyid->keyid) { redwax_metadata_add_string(m, "ID", redwax_pencode_base16_binary(m->pool, akeyid->keyid->data, akeyid->keyid->length, REDWAX_ENCODE_LOWER | REDWAX_ENCODE_COLON, NULL)); } if (akeyid->issuer) { redwax_metadata_push_object(m, "Issuer", 0); redwax_openssl_general_names_metadata(r, m, akeyid->issuer); redwax_metadata_pop_object(m); } if (akeyid->serial) { redwax_metadata_add_string(m, "Serial", redwax_pencode_base16_binary(m->pool, akeyid->serial->data, akeyid->serial->length, REDWAX_ENCODE_LOWER | REDWAX_ENCODE_COLON, NULL)); } break; } case NID_certificate_policies: { STACK_OF(POLICYINFO) *pol = X509V3_EXT_d2i(ex); int i, j, k; if (sk_POLICYINFO_num(pol)) { redwax_metadata_push_array(m, "Policies", 0); for (i = 0; i < sk_POLICYINFO_num(pol); i++) { POLICYINFO *pinfo; BIO *bio; char *buf = NULL; int len = 0; pinfo = sk_POLICYINFO_value(pol, i); if (pinfo->policyid) { redwax_metadata_push_object(m, "Policy", 0); if ((bio = BIO_new(BIO_s_mem())) == NULL) { return APR_ENOMEM; } i2a_ASN1_OBJECT(bio, pinfo->policyid); len = BIO_get_mem_data(bio, &buf); redwax_metadata_add_string(m, "ID", apr_psprintf(m->pool, "%.*s", len, buf)); BIO_free(bio); if (pinfo->qualifiers && sk_POLICYQUALINFO_num(pinfo->qualifiers)) { redwax_metadata_push_array(m, "Qualifiers", 0); for (j = 0; j < sk_POLICYQUALINFO_num(pinfo->qualifiers); j++) { POLICYQUALINFO *qualinfo; redwax_metadata_push_object(m, "Qualifier", 0); qualinfo = sk_POLICYQUALINFO_value( pinfo->qualifiers, j); switch (OBJ_obj2nid(qualinfo->pqualid)) { case NID_id_qt_cps: { redwax_metadata_add_string(m, "CPS", apr_psprintf(m->pool, "%.*s", qualinfo->d.cpsuri->length, qualinfo->d.cpsuri->data)); break; } case NID_id_qt_unotice: { USERNOTICE *notice = qualinfo->d.usernotice; redwax_metadata_push_object(m, "User Notice", 0); if (notice->noticeref) { NOTICEREF *ref; ref = notice->noticeref; if (ref->organization && ref->organization->data) { redwax_metadata_add_string(m, "Organization", apr_psprintf(m->pool, "%.*s", ref->organization->length, ref->organization->data)); } if (sk_ASN1_INTEGER_num(ref->noticenos)) { redwax_metadata_push_array(m, "Numbers", 0); for (k = 0; k < sk_ASN1_INTEGER_num( ref->noticenos); k++) { ASN1_INTEGER *num; num = sk_ASN1_INTEGER_value( ref->noticenos, k); if (num) { char *tmp; tmp = i2s_ASN1_INTEGER(NULL, num); if (tmp) { redwax_metadata_add_number( m, "Number", tmp, strlen(tmp)); OPENSSL_free(tmp); } } else { redwax_metadata_add_null(m, "Number"); } } redwax_metadata_pop_array(m); } } if (notice->exptext) { redwax_metadata_add_string(m, "ExplicitText", apr_psprintf(m->pool, "%.*s", notice->exptext->length, notice->exptext->data)); } redwax_metadata_pop_object(m); break; } default: { redwax_print_error(r, "metadata-out: unsupported Policy qualifier, ignoring\n"); break; } } redwax_metadata_pop_object(m); } redwax_metadata_pop_array(m); } redwax_metadata_pop_object(m); } } redwax_metadata_pop_array(m); } break; } case NID_crl_distribution_points: case NID_freshest_crl: { STACK_OF(DIST_POINT) *crld = X509V3_EXT_d2i(ex); int i, j; if (sk_DIST_POINT_num(crld)) { redwax_metadata_push_array(m, "CRLDistributionPoints", 0); for (i = 0; i < sk_DIST_POINT_num(crld); i++) { DIST_POINT *point; point = sk_DIST_POINT_value(crld, i); redwax_metadata_push_object(m, "CRLDistributionPoint", 0); if (point->distpoint) { DIST_POINT_NAME *dpn; dpn = point->distpoint; if (dpn->type == 0) { redwax_metadata_push_object(m, "FullName", 0); redwax_openssl_general_names_metadata(r, m, dpn->name.fullname); redwax_metadata_pop_object(m); } else { X509_NAME *ntmp; ntmp = X509_NAME_new(); for (j = 0; j < sk_X509_NAME_ENTRY_num( dpn->name.relativename); j++) { X509_NAME_ENTRY *entry; entry = sk_X509_NAME_ENTRY_value( dpn->name.relativename, j); X509_NAME_add_entry(ntmp, entry, -1, j ? 0 : 1); } redwax_metadata_add_string(m, "RelativeName", redwax_openssl_name(m->pool, ntmp)); X509_NAME_free(ntmp); } } if (point->reasons) { const BIT_STRING_BITNAME *pbn; redwax_metadata_push_array(m, "Reasons", 0); for (pbn = reason_flags; pbn->lname; pbn++) { if (ASN1_BIT_STRING_get_bit(point->reasons, pbn->bitnum)) { redwax_metadata_add_string(m, "Reason", pbn->sname); } } redwax_metadata_pop_array(m); } if (point->CRLissuer) { redwax_metadata_push_object(m, "CRLIssuer", 0); redwax_openssl_general_names_metadata(r, m, point->CRLissuer); redwax_metadata_pop_object(m); } redwax_metadata_pop_object(m); } redwax_metadata_pop_array(m); } break; } #if HAVE_OPENSSL_CT_H case NID_ct_precert_scts: case NID_ct_cert_scts: { STACK_OF(SCT) *sct_list = X509V3_EXT_d2i(ex); int i; if (sk_SCT_num(sct_list)) { redwax_metadata_push_array(m, "SignedCertificateTimestamps", 0); for (i = 0; i < sk_SCT_num(sct_list); ++i) { SCT *sct; ASN1_GENERALIZEDTIME *gen; char genstr[20]; unsigned char *ext, *sig; apr_size_t ext_len, sig_len; uint64_t timestamp; int nid; sct = sk_SCT_value(sct_list, i); redwax_metadata_push_object(m, "SignedCertificateTimestamp", 0); if (SCT_get_version(sct) == SCT_VERSION_V1) { redwax_metadata_add_string(m, "Version", "v1 (0x0)"); } timestamp = SCT_get_timestamp(sct); gen = ASN1_GENERALIZEDTIME_new(); if (!gen) { return APR_ENOMEM; } ASN1_GENERALIZEDTIME_adj(gen, (time_t)0, (int)(timestamp / 86400000), (timestamp % 86400000) / 1000); BIO_snprintf(genstr, sizeof(genstr), "%.14s.%03dZ", ASN1_STRING_get0_data(gen), (unsigned int)(timestamp % 1000)); if (ASN1_GENERALIZEDTIME_set_string(gen, genstr)) { redwax_metadata_add_string(m, "Timestamp", redwax_openssl_time(m->pool, gen)); } ASN1_GENERALIZEDTIME_free(gen); ext_len = SCT_get0_extensions(sct, &ext); if (ext_len) { redwax_metadata_add_string(m, "Extensions", redwax_pencode_base16_binary(m->pool, ext, ext_len, REDWAX_ENCODE_LOWER | REDWAX_ENCODE_COLON, NULL)); } nid = SCT_get_signature_nid(sct); if (nid != NID_undef) { redwax_metadata_push_object(m, "Signature", 0); redwax_metadata_add_string(m, "SignatureAlgorithm", OBJ_nid2sn(nid)); sig_len = SCT_get0_signature(sct, &sig); if (sig_len) { redwax_metadata_add_string(m, "SignatureValue", redwax_pencode_base16_binary(m->pool, sig, sig_len, REDWAX_ENCODE_LOWER | REDWAX_ENCODE_COLON, NULL)); } redwax_metadata_pop_object(m); } redwax_metadata_pop_object(m); } redwax_metadata_pop_array(m); } break; } case NID_ct_precert_poison: { /* no metadata needed */ break; } #endif default: /* * Unknown extensions - we print they exist, but print no contents. * * This allows us to fill in contents at a future date without * changing the existing format. */ break; } redwax_metadata_pop_object(m); return APR_SUCCESS; } static apr_status_t redwax_openssl_extensions_metadata(redwax_tool_t *r, redwax_metadata_t *m, const STACK_OF(X509_EXTENSION) *exts) { int i; redwax_metadata_push_object(m, "X509v3Extensions", !sk_X509_EXTENSION_num(exts)); for (i = 0; i < sk_X509_EXTENSION_num(exts); i++) { X509_EXTENSION *ex; ex = sk_X509_EXTENSION_value(exts, i); if (ex) { redwax_openssl_extension_metadata(r, m, ex); } } redwax_metadata_pop_object(m); return APR_SUCCESS; } static apr_status_t redwax_openssl_signature_metadata(redwax_tool_t *r, redwax_metadata_t *m, const X509_ALGOR *sig_alg, const ASN1_BIT_STRING *sig) { BIO *bio; char *buf = NULL; int len = 0; if (sig_alg) { if ((bio = BIO_new(BIO_s_mem())) == NULL) { return APR_ENOMEM; } i2a_ASN1_OBJECT(bio, sig_alg->algorithm); len = BIO_get_mem_data(bio, &buf); redwax_metadata_add_string(m, "SignatureAlgorithm", apr_psprintf(m->pool, "%.*s", len, buf)); BIO_free(bio); } if (sig) { redwax_metadata_add_string(m, "SignatureValue", redwax_pencode_base16_binary(m->pool, sig->data, sig->length, REDWAX_ENCODE_LOWER | REDWAX_ENCODE_COLON, NULL)); } return APR_SUCCESS; } static apr_status_t redwax_openssl_cert_metadata(redwax_tool_t *r, redwax_metadata_t *m, const redwax_certificate_t *cert) { X509 *x = NULL; openssl_certificate_config_t *conf = redwax_get_module_config(cert->per_module, &openssl_module); if (!conf) { conf = apr_pcalloc(cert->pool, sizeof(openssl_certificate_config_t)); redwax_set_module_config(cert->per_module, &openssl_module, conf); } redwax_metadata_push_object(m, "Certificate", 0); redwax_metadata_add_string(m, "Origin", cert->origin); if (cert->common.type == REDWAX_CERTIFICATE_X509 && cert->x509 && cert->id_der && cert->id_len) { redwax_metadata_add_string(m, "Id", redwax_pencode_base16_binary(m->pool, cert->id_der, cert->id_len, REDWAX_ENCODE_LOWER, NULL)); } if (cert->label && cert->label_len) { redwax_metadata_add_string(m, "Label", apr_pstrndup(m->pool, cert->label, cert->label_len)); } if (cert->token && cert->token_len) { redwax_metadata_add_string(m, "Token", apr_pstrndup(m->pool, cert->token, cert->token_len)); } redwax_metadata_push_object(m, "DNS", 0); rt_run_add_dns_metadata(r, m, cert); redwax_metadata_pop_object(m); if (r->text) { const unsigned char *der = cert->der; x = d2i_X509(NULL, &der, cert->len); if (x) { const STACK_OF(X509_EXTENSION) *xe; const ASN1_BIT_STRING *iuid, *suid; X509_PUBKEY *xpkey; #if (OPENSSL_VERSION_NUMBER < 0x10100000L) X509_ALGOR *tsig_alg; #else const X509_ALGOR *tsig_alg; #endif #if (OPENSSL_VERSION_NUMBER < 0x10100000L) ASN1_BIT_STRING *sig; #else const ASN1_BIT_STRING *sig; #endif ASN1_INTEGER *bs; const ASN1_TIME *before, *after; ASN1_TIME *now = NULL; const char *valid_status = NULL, *valid_warning = NULL, *valid_error = NULL; long l; redwax_metadata_push_object(m, "Data", 0); /* version */ l = X509_get_version(x); if (l >= 0 && l <= 2) { redwax_metadata_add_string(m, "Version", apr_psprintf(m->pool, "%ld (0x%lx)", l + 1, (unsigned long)l)); } else { redwax_metadata_add_string(m, "Version", apr_psprintf(m->pool, "Unknown (%ld)", l)); } /* serial number */ bs = X509_get_serialNumber(x); if (bs) { redwax_metadata_add_string(m, "SerialNumber", redwax_pencode_base16_binary(m->pool, bs->data, bs->length, REDWAX_ENCODE_LOWER | REDWAX_ENCODE_COLON, NULL)); } /* signature algorithm */ tsig_alg = (X509_ALGOR *)X509_get0_tbs_sigalg(x); if (tsig_alg) { redwax_openssl_signature_metadata(r, m, tsig_alg, NULL); } /* issuer */ redwax_metadata_add_string(m, "Issuer", redwax_openssl_name(m->pool, X509_get_issuer_name(x))); /* validity */ redwax_metadata_push_object(m, "Validity", 0); before = X509_get0_notBefore(x); if (before) { redwax_metadata_add_string(m, "NotBefore", redwax_openssl_time(m->pool, before)); } after = X509_get0_notAfter(x); if (after) { redwax_metadata_add_string(m, "NotAfter", redwax_openssl_time(m->pool, after)); } if (r->verify_date) { now = ASN1_TIME_new(); ASN1_TIME_set_string(now, r->verify_date); ASN1_TIME_normalize(now); } if (before) { int pday, psec; ASN1_TIME_diff(&pday, &psec, before, now); if (pday < 0 || psec < 0) { valid_error = apr_psprintf(m->pool, "Valid in %d day(s) %d second(s)", -pday, -psec); } else { valid_status = apr_psprintf(m->pool, "Valid for %d day(s) %d second(s)", pday, psec); } } if (after && !valid_error) { int pday, psec; ASN1_TIME_diff(&pday, &psec, now, after); if (pday < 0 || psec < 0) { valid_error = apr_psprintf(m->pool, "Expired %d day(s) %d second(s) ago", -pday, -psec); } else if (pday * 86400 + psec < r->threshold) { valid_warning = apr_psprintf(m->pool, "Expires in %d day(s) %d second(s)", pday, psec); } else { valid_status = apr_psprintf(m->pool, "Expires in %d day(s) %d second(s)", pday, psec); } } if (conf->verification == X509_V_ERR_DANE_NO_MATCH) { valid_error = apr_psprintf(m->pool, "DANE TLSA records do not match: %s", apr_array_pstrcat(r->pool, r->tlsa_qnames, ',')); } if (valid_error) { redwax_metadata_add_string(m, "Error", valid_error); } else if (valid_warning) { redwax_metadata_add_string(m, "Warning", valid_warning); } else if (valid_status) { redwax_metadata_add_string(m, "Status", valid_status); } redwax_metadata_pop_object(m); /* subject */ redwax_metadata_add_string(m, "Subject", redwax_openssl_name(m->pool, X509_get_subject_name(x))); /* public key */ xpkey = X509_get_X509_PUBKEY(x); if (xpkey) { redwax_openssl_spki_metadata(r, m, xpkey); } /* issuer unique id / subject unique id */ X509_get0_uids(x, &iuid, &suid); if (iuid) { redwax_metadata_add_string(m, "IssuerUniqueID", redwax_pencode_base16_binary(m->pool, iuid->data, iuid->length, REDWAX_ENCODE_LOWER, NULL)); } if (suid) { redwax_metadata_add_string(m, "SubjectUniqueID", redwax_pencode_base16_binary(m->pool, suid->data, suid->length, REDWAX_ENCODE_LOWER, NULL)); } /* extensions */ xe = X509_get0_extensions(x); if (xe) { redwax_openssl_extensions_metadata(r, m, xe); } redwax_metadata_pop_object(m); /* signature */ X509_get0_signature(&sig, &tsig_alg, x); if (tsig_alg && sig) { redwax_openssl_signature_metadata(r, m, tsig_alg, sig); } } else { redwax_openssl_print_errors(r); X509_free(x); } } else { redwax_metadata_push_object(m, "Data", 0); redwax_metadata_add_string(m, "Subject", cert->common.subject); redwax_metadata_pop_object(m); } redwax_metadata_pop_object(m); return APR_SUCCESS; } static apr_status_t redwax_openssl_key_metadata(redwax_tool_t *r, redwax_metadata_t *m, const redwax_key_t *key) { redwax_metadata_push_object(m, "Key", 0); redwax_metadata_add_string(m, "Origin", key->origin); if (key->common.id_der && key->common.id_len) { redwax_metadata_add_string(m, "Id", redwax_pencode_base16_binary(m->pool, key->common.id_der, key->common.id_len, REDWAX_ENCODE_LOWER, NULL)); } if (key->label && key->label_len) { redwax_metadata_add_string(m, "Label", apr_pstrndup(m->pool, key->label, key->label_len)); } if (key->token && key->token_len) { redwax_metadata_add_string(m, "Token", apr_pstrndup(m->pool, key->token, key->token_len)); } redwax_metadata_pop_object(m); return APR_SUCCESS; } static apr_status_t redwax_openssl_process_metadata_out(redwax_tool_t *r, const char *file) { redwax_metadata_t *m; apr_file_t *out; int i; apr_status_t status; if (!strcmp(file, "-")) { out = r->out; } else { status = apr_file_open(&out, file, APR_FOPEN_WRITE | APR_FOPEN_CREATE | APR_FOPEN_TRUNCATE, APR_FPROT_OS_DEFAULT, r->pool); if (APR_SUCCESS != status) { return status; } } if (r->cert_out) { for (i = 0; i < r->certs_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_out, i, const redwax_certificate_t); redwax_print_error(r, "metadata-out: certificate: %s\n", cert->common.subject); } } if (r->chain_out) { for (i = 0; i < r->intermediates_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->intermediates_out, i, const redwax_certificate_t); redwax_print_error(r, "metadata-out: intermediate: %s\n", cert->common.subject); } } if (r->trust_out) { for (i = 0; i < r->trusted_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->trusted_out, i, const redwax_certificate_t); redwax_print_error(r, "metadata-out: trusted: %s\n", cert->common.subject); } } if (r->key_out) { for (i = 0; i < r->keys_out->nelts; i++) { #if 0 const redwax_key_t *key = &APR_ARRAY_IDX(r->keys_out, i, const redwax_key_t); #endif redwax_print_error(r, "metadata-out: key\n"); } } redwax_metadata_push_root(r->pool, "Vault", redwax_openssl_writev, out, r->format, &m); if (r->cert_out) { redwax_metadata_push_array(m, "Certificates", !r->certs_out->nelts); for (i = 0; i < r->certs_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->certs_out, i, const redwax_certificate_t); redwax_openssl_cert_metadata(r, m, cert); } redwax_metadata_pop_array(m); } if (r->chain_out) { redwax_metadata_push_array(m, "Chains", !r->intermediates_out->nelts); for (i = 0; i < r->intermediates_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->intermediates_out, i, const redwax_certificate_t); redwax_openssl_cert_metadata(r, m, cert); } redwax_metadata_pop_array(m); } if (r->trust_out) { redwax_metadata_push_array(m, "Trusts", !r->trusted_out->nelts); for (i = 0; i < r->trusted_out->nelts; i++) { const redwax_certificate_t *cert = &APR_ARRAY_IDX(r->trusted_out, i, const redwax_certificate_t); redwax_openssl_cert_metadata(r, m, cert); } redwax_metadata_pop_array(m); } if (r->key_out) { redwax_metadata_push_array(m, "Keys", !r->keys_out->nelts); for (i = 0; i < r->keys_out->nelts; i++) { const redwax_key_t *key = &APR_ARRAY_IDX(r->keys_out, i, const redwax_key_t); redwax_openssl_key_metadata(r, m, key); } redwax_metadata_pop_array(m); } redwax_metadata_pop_root(m); return APR_SUCCESS; } static apr_status_t redwax_openssl_complete_format_out(redwax_tool_t *r, apr_hash_t *params) { apr_hash_set(params, "xml", APR_HASH_KEY_STRING, "xml"); apr_hash_set(params, "json", APR_HASH_KEY_STRING, "json"); apr_hash_set(params, "yaml", APR_HASH_KEY_STRING, "yaml"); return APR_SUCCESS; } static apr_status_t redwax_openssl_set_format_out(redwax_tool_t *r, const char *arg) { if (!strcmp(arg, "xml")) { r->format = REDWAX_FORMAT_XML; return APR_SUCCESS; } if (!strcmp(arg, "json")) { r->format = REDWAX_FORMAT_JSON; return APR_SUCCESS; } if (!strcmp(arg, "yaml")) { r->format = REDWAX_FORMAT_YAML; return APR_SUCCESS; } return DECLINED; } apr_status_t redwax_openssl_complete_order_out(redwax_tool_t *r, apr_hash_t *orders) { apr_hash_set(orders, REDWAX_ORDER_ALL_TEXT, strlen(REDWAX_ORDER_ALL_TEXT), REDWAX_ORDER_ALL_TEXT); apr_hash_set(orders, REDWAX_ORDER_KEY_FIRST_TEXT, strlen(REDWAX_ORDER_KEY_FIRST_TEXT), REDWAX_ORDER_KEY_FIRST_TEXT); apr_hash_set(orders, REDWAX_ORDER_KEY_LAST_TEXT, strlen(REDWAX_ORDER_KEY_LAST_TEXT), REDWAX_ORDER_KEY_LAST_TEXT); return APR_SUCCESS; } static apr_status_t redwax_openssl_set_order_out(redwax_tool_t *r, const char *arg) { if (!strcmp(arg, REDWAX_ORDER_ALL_TEXT)) { r->order = REDWAX_ORDER_ALL; return APR_SUCCESS; } if (!strcmp(arg, REDWAX_ORDER_KEY_FIRST_TEXT)) { r->order = REDWAX_ORDER_KEY_FIRST; return APR_SUCCESS; } if (!strcmp(arg, REDWAX_ORDER_KEY_LAST_TEXT)) { r->order = REDWAX_ORDER_KEY_LAST; return APR_SUCCESS; } return DECLINED; } static apr_status_t redwax_openssl_process_jwks_out(redwax_tool_t *r, const char *file) { /* placeholder for the JWK set implementation */ return APR_ENOTIMPL; } static apr_status_t redwax_openssl_search_chain(redwax_tool_t *r, const redwax_certificate_t *cert, const redwax_certificate_t **current) { redwax_certificate_t *ncert; int j; const unsigned char *der = cert->der; X509 *x = d2i_X509(NULL, &der, cert->len); if (x) { if (!r->quiet && !r->complete) { BIO *bio_err; bio_err = BIO_new_fp(stderr, BIO_NOCLOSE); BIO_printf(bio_err, "search-filter: "); X509_NAME_print_ex(bio_err, X509_get_subject_name(x), 0, XN_FLAG_RFC2253); BIO_printf(bio_err, "\n"); BIO_free(bio_err); } // FIXME: use openssl flag for self signed if (!X509_NAME_cmp(X509_get_issuer_name(x), X509_get_subject_name(x))) { X509_free(x); return APR_ENOENT; } for (j = 0; j < sk_X509_num(trusted_index); j++) { X509 *xi = sk_X509_value(trusted_index, j); if (X509_check_issued(xi, x) == X509_V_OK) { redwax_certificate_t *chain = X509_get_ex_data(xi, redwax_get_x509_index()); /* detect loops */ ncert = apr_array_push(r->trusted_out); memcpy(ncert, chain, sizeof(redwax_certificate_t)); if (X509_NAME_cmp(X509_get_issuer_name(x), X509_get_subject_name(x))) { rt_run_search_chain(r, chain, NULL); } } } for (j = 0; j < sk_X509_num(chain_index); j++) { X509 *xi = sk_X509_value(chain_index, j); if (X509_check_issued(xi, x) == X509_V_OK) { redwax_certificate_t *chain = X509_get_ex_data(xi, redwax_get_x509_index()); /* detect loops / depth */ ncert = apr_array_push(r->intermediates_out); memcpy(ncert, chain, sizeof(redwax_certificate_t)); if (X509_NAME_cmp(X509_get_issuer_name(x), X509_get_subject_name(x))) { rt_run_search_chain(r, chain, NULL); } } } // FIXME: consider root certs too X509_free(x); } else { return APR_EINVAL; } return APR_SUCCESS; } static apr_status_t redwax_openssl_search_key(redwax_tool_t *r, const redwax_certificate_t *cert) { redwax_key_t *key = apr_hash_get(r->keys_index, cert->common.subjectpublickeyinfo_der, cert->common.subjectpublickeyinfo_len); if (key) { redwax_key_t *nkey = apr_array_push(r->keys_out); memcpy(nkey, key, sizeof(redwax_key_t)); return APR_SUCCESS; } else { return DECLINED; } } static int redwax_openssl_compare_certificate(redwax_tool_t *r, const redwax_certificate_t *c1, const redwax_certificate_t *c2) { X509 *x1, *x2; const ASN1_TIME *a1, *a2, *b1, *b2; const unsigned char *der; int bc1, bc2, ac1, ac2; int pday, psec, diff; /* a present certificate always beats an absent certificate */ if ((!c1 || !c1->len) && (!c2 || !c2->len)) { return SAME; } if ((!c1 || !c1->len)) { return RIGHT; } if ((!c2 || !c2->len)) { return LEFT; } if (c1->len == c2->len && !memcmp(c1->der, c2->der, c1->len)) { return SAME; } /* a parseable certificate always beats an unreadable certificate */ der = c1->der; x1 = d2i_X509(NULL, &der, c1->len); der = c2->der; x2 = d2i_X509(NULL, &der, c2->len); if (!x1 && !x2) { return SAME; } if (!x1) { X509_free(x2); return RIGHT; } if (!x2) { X509_free(x1); return LEFT; } /* a valid certificate always beats a not-yet-valid certificate */ b1 = X509_get0_notBefore(x1); b2 = X509_get0_notBefore(x2); bc1 = b1 ? X509_cmp_time(b1, r->now) : -1; bc2 = b2 ? X509_cmp_time(b2, r->now) : -1; if (bc1 == bc2) { /* we have a tie */ } else if (bc1 > -1) { X509_free(x1); X509_free(x2); return RIGHT; } else if (bc2 > -1) { X509_free(x1); X509_free(x2); return LEFT; } /* a valid certificate always beats a expired certificate */ a1 = X509_get0_notAfter(x1); a2 = X509_get0_notAfter(x2); ac1 = a1 ? X509_cmp_time(a1, r->now) : 1; ac2 = a2 ? X509_cmp_time(a2, r->now) : 1; if (ac1 == ac2) { /* we have a tie */ } else if (ac1 < 1) { X509_free(x1); X509_free(x2); return RIGHT; } else if (ac2 < 1) { X509_free(x1); X509_free(x2); return LEFT; } /* the longest validity beats the shortest validity */ if (ASN1_TIME_diff(&pday, &psec, a1, a2)) { if (!pday && !psec) { /* still a tie */ } else if (pday < 0 || psec < 0) { X509_free(x1); X509_free(x2); return LEFT; } else if (pday > 0 || psec > 0) { X509_free(x1); X509_free(x2); return RIGHT; } } /* we still have a tie, choose one cert, but deterministically */ diff = X509_cmp(x1, x2); if (diff < 0) { X509_free(x1); X509_free(x2); return LEFT; } else if (diff > 0) { X509_free(x1); X509_free(x2); return RIGHT; } /* after all this, they're the same */ X509_free(x1); X509_free(x2); return SAME; } static apr_status_t redwax_openssl_normalise_key(redwax_tool_t *r, redwax_key_t *key, int index) { /* * DER encoded key is present, but the components are not. */ if (key->der && key->common.type == REDWAX_KEY_NONE) { PKCS8_PRIV_KEY_INFO *p8inf = NULL; EVP_PKEY *pkey = NULL; X509_PUBKEY *pub = NULL; const unsigned char *der; der = key->der; p8inf = d2i_PKCS8_PRIV_KEY_INFO(NULL, &der, key->len); if (!p8inf) { /* could not unpack, let someone else try */ return DECLINED; } apr_pool_cleanup_register(key->pool, p8inf, cleanup_p8inf, apr_pool_cleanup_null); pkey = EVP_PKCS82PKEY(p8inf); if (!pkey) { /* could not convert, let someone else try */ return DECLINED; } apr_pool_cleanup_register(key->pool, pkey, cleanup_evp_pkey, apr_pool_cleanup_null); if (X509_PUBKEY_set(&pub, pkey)) { unsigned char *der; key->common.subjectpublickeyinfo_len = i2d_X509_PUBKEY(pub, NULL); key->common.subjectpublickeyinfo_der = der = apr_palloc(key->pool, key->common.subjectpublickeyinfo_len); i2d_X509_PUBKEY(pub, &der); /* index the key */ if (index) { if (!apr_hash_get(r->keys_index, key->common.subjectpublickeyinfo_der, key->common.subjectpublickeyinfo_len)) { key->keys_index = r->keys_index; apr_hash_set(key->keys_index, key->common.subjectpublickeyinfo_der, key->common.subjectpublickeyinfo_len, key); apr_pool_cleanup_register(key->pool, key, cleanup_key, apr_pool_cleanup_null); } } } switch(EVP_PKEY_base_id(pkey)) { case EVP_PKEY_RSA: { /* id is the sha1 hash of the modulus */ unsigned char digest[EVP_MAX_MD_SIZE]; unsigned int len; #if HAVE_EVP_PKEY_GET_BN_PARAM BIGNUM *n = NULL; BIGNUM *e = NULL; BIGNUM *d = NULL; BIGNUM *p = NULL; BIGNUM *q = NULL; BIGNUM *dmp1 = NULL; BIGNUM *dmq1 = NULL; BIGNUM *iqmp = NULL; EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &n); EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &e); EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_D, &d); EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_FACTOR1, &p); EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_FACTOR2, &q); EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT1, &dmp1); EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_EXPONENT2, &dmq1); EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, &iqmp); #else RSA *rsa = EVP_PKEY_get1_RSA(pkey); const BIGNUM *n = RSA_get0_n(rsa); const BIGNUM *e = RSA_get0_e(rsa); const BIGNUM *d = RSA_get0_d(rsa); const BIGNUM *p = RSA_get0_p(rsa); const BIGNUM *q = RSA_get0_q(rsa); const BIGNUM *dmp1 = RSA_get0_dmp1(rsa); const BIGNUM *dmq1 = RSA_get0_dmq1(rsa); const BIGNUM *iqmp = RSA_get0_iqmp(rsa); #endif key->rsa = apr_pcalloc(key->pool, sizeof(redwax_key_rsa_t)); /* public */ if (n) { key->rsa->modulus_len = BN_num_bytes(n); key->rsa->modulus = apr_palloc(key->pool, key->rsa->modulus_len); BN_bn2bin(n, key->rsa->modulus); } if (e) { key->rsa->public_exponent_len = BN_num_bytes(e); key->rsa->public_exponent = apr_palloc(key->pool, key->rsa->public_exponent_len); BN_bn2bin(e, key->rsa->public_exponent); } /* private */ if (d) { key->rsa->private_exponent_len = BN_num_bytes(d); key->rsa->private_exponent = apr_palloc(key->pool, key->rsa->private_exponent_len); BN_bn2bin(d, key->rsa->private_exponent); } if (p) { key->rsa->prime_1_len = BN_num_bytes(p); key->rsa->prime_1 = apr_palloc(key->pool, key->rsa->prime_1_len); BN_bn2bin(p, key->rsa->prime_1); } if (q) { key->rsa->prime_2_len = BN_num_bytes(q); key->rsa->prime_2 = apr_palloc(key->pool, key->rsa->prime_2_len); BN_bn2bin(q, key->rsa->prime_2); } if (dmp1) { key->rsa->exponent_1_len = BN_num_bytes(dmp1); key->rsa->exponent_1 = apr_palloc(key->pool, key->rsa->exponent_1_len); BN_bn2bin(dmp1, key->rsa->exponent_1); } if (dmq1) { key->rsa->exponent_2_len = BN_num_bytes(dmq1); key->rsa->exponent_2 = apr_palloc(key->pool, key->rsa->exponent_2_len); BN_bn2bin(dmq1, key->rsa->exponent_2); } if (iqmp) { key->rsa->coefficient_len = BN_num_bytes(iqmp); key->rsa->coefficient = apr_palloc(key->pool, key->rsa->coefficient_len); BN_bn2bin(iqmp, key->rsa->coefficient); } /* key type is RSA */ key->common.type = REDWAX_KEY_RSA; /* ID is a SHA1 hash of the modulus */ EVP_Digest(key->rsa->modulus, key->rsa->modulus_len, digest, &len, EVP_sha1(), NULL); key->common.gid_der = apr_pmemdup(key->pool, digest, len); key->common.gid_len = len; #if HAVE_EVP_PKEY_GET_BN_PARAM BN_clear_free(n); BN_clear_free(e); BN_clear_free(d); BN_clear_free(p); BN_clear_free(q); BN_clear_free(dmp1); BN_clear_free(dmq1); BN_clear_free(iqmp); #else RSA_free(rsa); #endif break; } #if 0 case EVP_PKEY_DSA: break; case EVP_PKEY_DH: break; case EVP_PKEY_EC: break; #endif } } /* * RSA components are present, but the DER encoded key is not. */ else if (key->common.type == REDWAX_KEY_RSA && key->rsa && !key->der) { BIO *kbio; PKCS8_PRIV_KEY_INFO *p8inf; X509_PUBKEY *pub = NULL; #if HAVE_EVP_PKEY_CTX_NEW_FROM_NAME BIGNUM *n = BN_bin2bn(key->rsa->modulus, key->rsa->modulus_len, NULL); BIGNUM *e = BN_bin2bn(key->rsa->public_exponent, key->rsa->public_exponent_len, NULL); BIGNUM *d = BN_bin2bn(key->rsa->private_exponent, key->rsa->private_exponent_len, NULL); BIGNUM *p = BN_bin2bn(key->rsa->prime_1, key->rsa->prime_1_len, NULL); BIGNUM *q = BN_bin2bn(key->rsa->prime_2, key->rsa->prime_2_len, NULL); BIGNUM *dmp1 = BN_bin2bn(key->rsa->exponent_1, key->rsa->exponent_1_len, NULL); BIGNUM *dmq1 = BN_bin2bn(key->rsa->exponent_2, key->rsa->exponent_2_len, NULL); BIGNUM *iqmp = BN_bin2bn(key->rsa->coefficient, key->rsa->coefficient_len, NULL); OSSL_PARAM params[] = { OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_N, n, BN_num_bytes(n)), OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_E, e, BN_num_bytes(e)), OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_D, d, BN_num_bytes(d)), OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_FACTOR1, p, BN_num_bytes(p)), OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_FACTOR2, q, BN_num_bytes(q)), OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_EXPONENT1, dmp1, BN_num_bytes(dmp1)), OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_EXPONENT2, dmq1, BN_num_bytes(dmq1)), OSSL_PARAM_BN(OSSL_PKEY_PARAM_RSA_COEFFICIENT1, iqmp, BN_num_bytes(iqmp)), OSSL_PARAM_END }; EVP_PKEY *pkey = NULL; EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); if (ctx == NULL || EVP_PKEY_fromdata_init(ctx) <= 0 || EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) { return APR_EGENERAL; } #else RSA *rsa = RSA_new(); EVP_PKEY *pkey = EVP_PKEY_new(); #if HAVE_RSA_SET0_KEY RSA_set0_key(rsa, BN_bin2bn(key->rsa->modulus, key->rsa->modulus_len, NULL), BN_bin2bn(key->rsa->public_exponent, key->rsa->public_exponent_len, NULL), BN_bin2bn(key->rsa->private_exponent, key->rsa->private_exponent_len, NULL)); #else rsa->n = BN_bin2bn(key->rsa->modulus, key->rsa->modulus_len, NULL); rsa->e = BN_bin2bn(key->rsa->public_exponent, key->rsa->public_exponent_len, NULL); rsa->d = BN_bin2bn(key->rsa->private_exponent, key->rsa->private_exponent_len, NULL); #endif #if HAVE_RSA_SET0_FACTORS RSA_set0_factors(rsa, BN_bin2bn(key->rsa->prime_1, key->rsa->prime_1_len, NULL), BN_bin2bn(key->rsa->prime_2, key->rsa->prime_2_len, NULL)); #else rsa->p = BN_bin2bn(key->rsa->prime_1, key->rsa->prime_1_len, NULL); rsa->q = BN_bin2bn(key->rsa->prime_2, key->rsa->prime_2_len, NULL); #endif #if HAVE_RSA_SET0_CRT_PARAMS RSA_set0_crt_params(rsa, BN_bin2bn(key->rsa->exponent_1, key->rsa->exponent_1_len, NULL), BN_bin2bn(key->rsa->exponent_2, key->rsa->exponent_2_len, NULL), BN_bin2bn(key->rsa->coefficient, key->rsa->coefficient_len, NULL)); #else rsa->dmp1 = BN_bin2bn(key->rsa->exponent_1, key->rsa->exponent_1_len, NULL); rsa->dmq1 = BN_bin2bn(key->rsa->exponent_2, key->rsa->exponent_2_len, NULL); rsa->iqmp = BN_bin2bn(key->rsa->coefficient, key->rsa->coefficient_len, NULL); #endif EVP_PKEY_set1_RSA(pkey, rsa); #endif p8inf = EVP_PKEY2PKCS8(pkey); /* handle public key */ if (X509_PUBKEY_set(&pub, pkey)) { unsigned char *der; key->common.subjectpublickeyinfo_len = i2d_X509_PUBKEY(pub, NULL); key->common.subjectpublickeyinfo_der = der = apr_palloc(key->pool, key->common.subjectpublickeyinfo_len); i2d_X509_PUBKEY(pub, &der); /* index the key */ if (index) { if (!apr_hash_get(r->keys_index, key->common.subjectpublickeyinfo_der, key->common.subjectpublickeyinfo_len)) { key->keys_index = r->keys_index; apr_hash_set(key->keys_index, key->common.subjectpublickeyinfo_der, key->common.subjectpublickeyinfo_len, key); apr_pool_cleanup_register(key->pool, key, cleanup_key, apr_pool_cleanup_null); } } } /* handle private key */ if (key->rsa->private_exponent_len) { if ((kbio = BIO_new(BIO_s_mem())) == NULL) { return APR_ENOMEM; } apr_pool_cleanup_register(key->pool, kbio, cleanup_bio, apr_pool_cleanup_null); i2d_PKCS8_PRIV_KEY_INFO_bio(kbio, p8inf); key->len = BIO_get_mem_data(kbio, &key->der); PKCS8_PRIV_KEY_INFO_free(p8inf); } EVP_PKEY_free(pkey); #if HAVE_EVP_PKEY_CTX_NEW_FROM_NAME EVP_PKEY_CTX_free(ctx); BN_clear_free(n); BN_clear_free(e); BN_clear_free(d); BN_clear_free(p); BN_clear_free(q); BN_clear_free(dmp1); BN_clear_free(dmq1); BN_clear_free(iqmp); #else RSA_free(rsa); #endif } return APR_SUCCESS; } static apr_status_t redwax_openssl_normalise_certificate(redwax_tool_t *r, redwax_certificate_t *cert, int must_index) { /* * DER encoded certificate is present, but the components are not. */ if (cert->der && !cert->x509) { X509 *x; X509_NAME *name; X509_PUBKEY *pub; ASN1_INTEGER *integer; ASN1_OCTET_STRING *skid; const ASN1_TIME *before, *after; const unsigned char *der; redwax_certificate_x509_t *x509; der = cert->der; x = d2i_X509(NULL, &der, cert->len); if (!x) { /* could not unpack, let someone else try */ return DECLINED; } if (REDWAX_CERTIFICATE_UNSPECIFIED == cert->common.category) { if (X509_check_ca(x)) { if (X509_get_extension_flags(x) & EXFLAG_SI) { cert->common.category = REDWAX_CERTIFICATE_ROOT; } else { cert->common.category = REDWAX_CERTIFICATE_INTERMEDIATE; } } else { cert->common.category = REDWAX_CERTIFICATE_END_ENTITY; } } /* handle indexing */ if (must_index) { switch (cert->common.category) { case REDWAX_CERTIFICATE_END_ENTITY: { apr_hash_t *index; GENERAL_NAMES *gens = NULL; int i; /* index */ X509_set_ex_data(x, redwax_get_x509_index(), (void *)cert); sk_X509_push(cert_index, x); X509_up_ref(x); gens = X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL); if (gens) { for (i = 0; i < sk_GENERAL_NAME_num(gens); i++) { GENERAL_NAME *gen; ASN1_STRING *cstr; int astrlen; unsigned char *astr; gen = sk_GENERAL_NAME_value(gens, i); switch (gen->type) { case GEN_DNS: { cstr = gen->d.dNSName; index = r->hostnames_index; break; } case GEN_EMAIL: { cstr = gen->d.rfc822Name; index = r->emails_index; break; } case GEN_IPADD: { cstr = gen->d.iPAddress; index = r->ips_index; break; } default: continue; } astrlen = ASN1_STRING_to_UTF8(&astr, cstr); if (astr) { apr_pool_cleanup_register(r->pool, astr, cleanup_alloc, apr_pool_cleanup_null); apr_hash_set(index, astr, astrlen, apr_pmemdup(r->pool, astr, astrlen)); } } GENERAL_NAMES_free(gens); } break; } case REDWAX_CERTIFICATE_INTERMEDIATE: X509_set_ex_data(x, redwax_get_x509_index(), (void *)cert); sk_X509_push(chain_index, x); X509_up_ref(x); break; case REDWAX_CERTIFICATE_ROOT: case REDWAX_CERTIFICATE_TRUSTED: X509_set_ex_data(x, redwax_get_x509_index(), (void *)cert); sk_X509_push(trusted_index, x); X509_up_ref(x); break; default: break; } } x509 = cert->x509 = apr_pcalloc(cert->pool, sizeof(redwax_certificate_x509_t)); pub = X509_get_X509_PUBKEY(x); if (pub) { EVP_PKEY *pkey = X509_PUBKEY_get(pub); unsigned char *der; cert->common.subjectpublickeyinfo_len = i2d_X509_PUBKEY(pub, NULL); cert->common.subjectpublickeyinfo_der = der = apr_palloc(r->pool, cert->common.subjectpublickeyinfo_len); i2d_X509_PUBKEY(pub, &der); switch(EVP_PKEY_base_id(pkey)) { case EVP_PKEY_RSA: { #if HAVE_EVP_PKEY_GET_BN_PARAM BIGNUM *n = NULL; EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &n); #else RSA *rsa = EVP_PKEY_get1_RSA(pkey); const BIGNUM *n = RSA_get0_n(rsa); #endif /* public */ if (n) { unsigned char digest[EVP_MAX_MD_SIZE]; unsigned int len = BN_num_bytes(n); unsigned char *modulus = apr_palloc(cert->pool, len); BN_bn2bin(n, modulus); EVP_Digest(modulus, len, digest, &len, EVP_sha1(), NULL); x509->gid_der = apr_pmemdup(cert->pool, digest, len); x509->gid_len = len; } #if HAVE_EVP_PKEY_GET_BN_PARAM BN_clear_free(n); #else RSA_free(rsa); #endif break; } default: break; } } cert->common.subject = redwax_openssl_name(cert->pool, X509_get_subject_name(x)); name = X509_get_subject_name(x); if (name) { unsigned char *der; int index = X509_NAME_get_index_by_NID(name, NID_commonName, -1); X509_NAME_ENTRY *cnEntry = X509_NAME_get_entry(name, index); if (cnEntry) { ASN1_STRING *cnASN1 = X509_NAME_ENTRY_get_data(cnEntry); if (cnASN1) { unsigned char *astr; cert->common.glabel_len = ASN1_STRING_to_UTF8(&astr, cnASN1); if (astr) { cert->common.glabel = apr_pmemdup(cert->pool, astr, cert->common.glabel_len); apr_pool_cleanup_register(cert->pool, astr, cleanup_alloc, apr_pool_cleanup_null); } } } x509->subject_len = i2d_X509_NAME(name, NULL); x509->subject_der = der = apr_palloc(r->pool, x509->subject_len); i2d_X509_NAME(name, &der); } skid = X509_get_ext_d2i(x, NID_subject_key_identifier, NULL, NULL); if (skid) { x509->skid_len = skid->length; x509->skid_der = apr_pmemdup(cert->pool, skid->data, skid->length); } name = X509_get_issuer_name(x); if (name) { unsigned char *der; x509->issuer_len = i2d_X509_NAME(name, NULL); x509->issuer_der = der = apr_palloc(r->pool, x509->issuer_len); i2d_X509_NAME(name, &der); } integer = X509_get_serialNumber(x); if (integer) { unsigned char *der; x509->serial_len = i2d_ASN1_INTEGER(integer, NULL); x509->serial_der = der = apr_palloc(r->pool, x509->serial_len); i2d_ASN1_INTEGER(integer, &der); } before = X509_get0_notBefore(x); if (before) { struct tm stime = { 0 }; ASN1_TIME_to_tm(before, &stime); x509->before = apr_palloc(r->pool, sizeof(apr_time_t)); *x509->before = timegm(&stime); } after = X509_get0_notAfter(x); if (after) { struct tm stime = { 0 }; ASN1_TIME_to_tm(after, &stime); x509->after = apr_palloc(r->pool, sizeof(apr_time_t)); *x509->after = timegm(&stime); } x509->text = redwax_openssl_x509_text(r->pool, x, 0); x509->compact = redwax_openssl_x509_text(r->pool, x, X509_FLAG_NO_VERSION | X509_FLAG_NO_PUBKEY | X509_FLAG_NO_SIGDUMP); x509->pem = redwax_openssl_x509_pem(r->pool, x); X509_free(x); } return APR_SUCCESS; } void redwax_add_default_openssl_hooks() { rt_hook_initialise(redwax_openssl_initialise, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_complete_verify_param(redwax_openssl_complete_verify_param, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_set_verify_param(redwax_openssl_set_verify_param, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_set_verify_date(redwax_openssl_set_verify_date, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_complete_verify_expiry(redwax_openssl_complete_verify_expiry, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_set_verify_expiry(redwax_openssl_set_verify_expiry, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_complete_verify_dane(redwax_openssl_complete_verify_dane, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_set_verify_dane(redwax_openssl_set_verify_dane, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_complete_purpose(redwax_openssl_complete_purpose, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_set_purpose(redwax_openssl_set_purpose, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_pem_in(redwax_openssl_process_pem_in, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_trust_pem_in(redwax_openssl_process_trust_pem_in, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_pkcs12_in(redwax_openssl_process_pkcs12_in, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_set_tlsa(redwax_openssl_set_tlsa, NULL, NULL, APR_HOOK_LAST); rt_hook_process_tlsa(redwax_openssl_process_tlsa, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_complete_filter(redwax_openssl_complete_filter_verify, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_filter(redwax_openssl_process_filter_verify, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_complete_filter(redwax_openssl_complete_filter_search, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_filter(redwax_openssl_process_filter_search, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_der_out(redwax_openssl_process_der_out, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_pem_out(redwax_openssl_process_pem_out, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_pkcs12_out(redwax_openssl_process_pkcs12_out, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_ssh_public_out(redwax_openssl_process_ssh_public_out, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_metadata_out(redwax_openssl_process_metadata_out, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_complete_format_out(redwax_openssl_complete_format_out, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_set_format_out(redwax_openssl_set_format_out, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_complete_order_out(redwax_openssl_complete_order_out, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_set_order_out(redwax_openssl_set_order_out, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_process_jwks_out(redwax_openssl_process_jwks_out, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_search_chain(redwax_openssl_search_chain, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_search_key(redwax_openssl_search_key, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_compare_certificate(redwax_openssl_compare_certificate, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_normalise_key(redwax_openssl_normalise_key, NULL, NULL, APR_HOOK_MIDDLE); rt_hook_normalise_certificate(redwax_openssl_normalise_certificate, NULL, NULL, APR_HOOK_MIDDLE); } #else void redwax_add_default_openssl_hooks() { } #endif REDWAX_DECLARE_MODULE(openssl) = { STANDARD_MODULE_STUFF, redwax_add_default_openssl_hooks /* register hooks */ };