From 90fd660803282713c3e2d29207dc26255f2e3899 Mon Sep 17 00:00:00 2001 From: Alexander Zakharov Date: Thu, 31 Aug 2017 23:17:30 +0300 Subject: [PATCH] Parse X.509 certificate, get RSA public key, RSA encrypt Also add support older (< 3.5.0) GnuTLS versions --- asn.c | 22 +++- asn.h | 4 +- configure.ac | 84 +++------------ ssl.c | 299 +++++++++++++++++++++++++++++---------------------- ssl.h | 17 +-- 5 files changed, 213 insertions(+), 213 deletions(-) diff --git a/asn.c b/asn.c index 8b7926f..a8e8987 100644 --- a/asn.c +++ b/asn.c @@ -207,7 +207,7 @@ int write_pkcs1_der_pubkey(const gnutls_datum_t *m, const gnutls_datum_t *e, uin return 0; } -int libtasn_read_cert_pk_parameters(uint8_t *data, size_t len, gnutls_datum_t *m, gnutls_datum_t *e) +int libtasn_read_cert_pk_parameters(uint8_t *data, size_t len, gnutls_datum_t *m, gnutls_datum_t *e, int check_pk_algo) { int asn1_rv; asn1_node asn_cert; @@ -234,6 +234,26 @@ int libtasn_read_cert_pk_parameters(uint8_t *data, size_t len, gnutls_datum_t *m return 1; } + if (check_pk_algo) { + /* Get and check cert's public key algorithm */ + buflen = sizeof(buf) - 1; + + if (ASN1_SUCCESS != (asn1_rv = asn1_read_value(asn_cert, "tbsCertificate.subjectPublicKeyInfo.algorithm.algorithm", buf, (int *)&buflen))) { + logger(Core, Error, "%s:%s:%d Failed to get cert's public key algorithm. Error = 0x%x (%s)\n", + __FILE__, __func__, __LINE__, asn1_rv, asn1_strerror(asn1_rv)); + return 1; + } + + if ((strncmp((char *)buf, OID_SHA_WITH_RSA_SIGNATURE, strlen(OID_SHA_WITH_RSA_SIGNATURE)) != 0) + && (strncmp((char *)buf, OID_MD5_WITH_RSA_SIGNATURE, strlen(OID_MD5_WITH_RSA_SIGNATURE)) != 0)) { + + logger(Core, Error, "%s:%s:%d Wrong public key algorithm: %s\n", + __FILE__, __func__, __LINE__, buf); + + return 1; + } + } + buflen = sizeof(buf) - 1; if (ASN1_SUCCESS != (asn1_rv = asn1_read_value(asn_cert, "tbsCertificate.subjectPublicKeyInfo.subjectPublicKey", buf, &buflen))) { logger(Core, Error, "%s:%s:%d Failed to get cert's public key. Error = 0x%x (%s)\n", diff --git a/asn.h b/asn.h index 95e6092..8cd47c3 100644 --- a/asn.h +++ b/asn.h @@ -29,10 +29,12 @@ extern "C" { #endif +#define OID_SHA_WITH_RSA_SIGNATURE "1.3.14.3.2.15" +#define OID_MD5_WITH_RSA_SIGNATURE "1.3.14.3.2.25" int init_asn1_lib(void); int write_pkcs1_der_pubkey(const gnutls_datum_t *m, const gnutls_datum_t *e, uint8_t *out, int *out_len); -int libtasn_read_cert_pk_parameters(uint8_t *data, size_t len, gnutls_datum_t *m, gnutls_datum_t *e); +int libtasn_read_cert_pk_parameters(uint8_t *data, size_t len, gnutls_datum_t *m, gnutls_datum_t *e, int check_pk_algo); #ifdef __cplusplus } diff --git a/configure.ac b/configure.ac index d2c61ee..36c1f59 100644 --- a/configure.ac +++ b/configure.ac @@ -29,6 +29,9 @@ fi AC_PATH_TOOL(PKG_CONFIG, pkg-config) +# no .pc for GMP +AC_SEARCH_LIBS([__gmpz_init], [gmp]) + AC_SEARCH_LIBS(socket, socket) AC_SEARCH_LIBS(inet_aton, resolv) @@ -61,74 +64,6 @@ AC_ARG_ENABLE([address-sanitizer], AS_HELP_STRING([--enable-address-sanitizer], [AC_MSG_ERROR([Address Sanitizer not available])]) ]) -# -# OpenSSL detection borrowed from stunnel -# -checkssldir() { : - if test -f "$1/include/openssl/ssl.h"; then - ssldir="$1" - return 0 - fi - return 1 -} -AC_MSG_CHECKING([for OpenSSL directory]) -AC_ARG_WITH(openssl, - [ --with-openssl=DIR look for OpenSSL at DIR/include, DIR/lib], - [ - dnl Check the specified location only - checkssldir "$withval" - ], - [ - dnl Search default locations of OpenSSL library - for maindir in /usr/local /usr/lib /usr/pkg /usr /var/ssl /opt; do - for dir in $maindir $maindir/openssl $maindir/ssl; do - checkssldir $dir && break 2 - done - done - ] -) -if test -z "$ssldir"; then - AC_MSG_RESULT([Not found]) - echo - echo "ERROR: Could not find OpenSSL headers/libraries." - if test -f /etc/debian_version; then - echo "Probably you need to install the libssl-dev package." - elif test -f /etc/redhat-release; then - echo "Probably you need to install the openssl-devel package." - fi - echo "To specify a path manually, use the --with-openssl option." - echo - exit 1 -fi -AC_MSG_RESULT([$ssldir]) -AC_SUBST(ssldir) -AC_DEFINE_UNQUOTED(ssldir, "$ssldir") - -dnl Add OpenSSL includes and libraries -CFLAGS="$CFLAGS -I$ssldir/include" -AC_ARG_ENABLE(static-openssl, - [ --enable-static-openssl link OpenSSL statically], - [static_openssl=yes], - [static_openssl=no]) -if test x"$static_openssl" = "xyes"; then - # OpenSSL generally relies on libz - AC_SEARCH_LIBS(deflate, z) - LIBS="-L$ssldir/lib -L$ssldir/lib64 -Wl,-Bstatic -lssl -lcrypto -Wl,-Bdynamic -ldl $LIBS" -else - LIBS="-L$ssldir/lib -L$ssldir/lib64 -lssl -lcrypto -ldl $LIBS" - - # - # target-specific stuff - # - case "$host" in - *-*-solaris*) - LDFLAGS="$LDFLAGS -R$ssldir/lib" - ;; - *-dec-osf*) - LDFLAGS="$LDFLAGS -Wl,-rpath,$ssldir/lib" - ;; - esac -fi dnl CredSSP feature AC_ARG_ENABLE([credssp], AS_HELP_STRING([--disable-credssp], [disable support for CredSSP])) @@ -229,6 +164,19 @@ else exit 1 fi +# hogweed +if test -n "$PKG_CONFIG"; then + PKG_CHECK_MODULES(HOGWEED, hogweed, [HAVE_HOGWEED=1], [HAVE_HOGWEED=0]) +fi +if test x"$HAVE_HOGWEED" = "x1"; then + CFLAGS="$CFLAGS $HOGWEED_CFLAGS" + LIBS="$LIBS $HOGWEED_LIBS" +else + echo + echo "rdesktop requires hogweed. Please install the dependency" + echo + exit 1 +fi # GnuTLS if test -n "$PKG_CONFIG"; then diff --git a/ssl.c b/ssl.c index b178d80..0907a56 100644 --- a/ssl.c +++ b/ssl.c @@ -24,23 +24,7 @@ #include "ssl.h" #include "asn.h" -/* Helper function to log internal SSL errors using logger */ -void -rdssl_log_ssl_errors(const char *prefix) -{ - unsigned long err; - while (1) - { - err = ERR_get_error(); - if (err == 0) - break; - - logger(Protocol, Error, - "%s, 0x%.8x:%s:%s: %s", - prefix, err, ERR_lib_error_string(err), - ERR_func_error_string(err), ERR_reason_error_string(err)); - } -} +#include void rdssl_sha1_init(RDSSL_SHA1 * sha1) @@ -108,145 +92,186 @@ void rdssl_rsa_encrypt(uint8 * out, uint8 * in, int len, uint32 modulus_size, uint8 * modulus, uint8 * exponent) { - BN_CTX *ctx; - BIGNUM *mod, *exp, *x, *y; - uint8 inr[SEC_MAX_MODULUS_SIZE]; - int outlen; + mpz_t exp, mod; - reverse(modulus, modulus_size); - reverse(exponent, SEC_EXPONENT_SIZE); - memcpy(inr, in, len); - reverse(inr, len); + mpz_t y; + mpz_t x; - ctx = BN_CTX_new(); - mod = BN_new(); - exp = BN_new(); - x = BN_new(); - y = BN_new(); + size_t outlen; + + mpz_init(y); + mpz_init(x); + mpz_init(exp); + mpz_init(mod); + + mpz_import(mod, modulus_size, 1, sizeof(modulus[0]), 0, 0, modulus); + // TODO: Need exponent size + mpz_import(exp, 3, 1, sizeof(exponent[0]), 0, 0, exponent); + + mpz_import(x, len, -1, sizeof(in[0]), 0, 0, in); + + mpz_powm(y, x, exp, mod); + + mpz_export(out, &outlen, -1, sizeof(out[0]), 0, 0, y); - BN_bin2bn(modulus, modulus_size, mod); - BN_bin2bn(exponent, SEC_EXPONENT_SIZE, exp); - BN_bin2bn(inr, len, x); - BN_mod_exp(y, x, exp, mod, ctx); - outlen = BN_bn2bin(y, out); - reverse(out, outlen); if (outlen < (int) modulus_size) memset(out + outlen, 0, modulus_size - outlen); - - BN_free(y); - BN_clear_free(x); - BN_free(exp); - BN_free(mod); - BN_CTX_free(ctx); } /* returns newly allocated RDSSL_CERT or NULL */ RDSSL_CERT * rdssl_cert_read(uint8 * data, uint32 len) { - /* this will move the data pointer but we don't care, we don't use it again */ - return d2i_X509(NULL, (D2I_X509_CONST unsigned char **) &data, len); + int ret; + gnutls_datum_t cert_data; + gnutls_x509_crt_t *cert; + + cert = malloc(sizeof(*cert)); + + if (!cert) { + logger(Protocol, Error, "%s:%s:%d: Failed to allocate memory for certificate structure.\n", + __FILE__, __func__, __LINE__); + return NULL; + } + + if ((ret = gnutls_x509_crt_init(cert)) != GNUTLS_E_SUCCESS) { + logger(Protocol, Error, "%s:%s:%d: Failed to init certificate structure. GnuTLS error = 0x%02x (%s)\n", + __FILE__, __func__, __LINE__, ret, gnutls_strerror(ret)); + + return NULL; + } + + cert_data.size = len; + cert_data.data = data; + + if ((ret = gnutls_x509_crt_import(*cert, &cert_data, GNUTLS_X509_FMT_DER)) != GNUTLS_E_SUCCESS) { + logger(Protocol, Error, "%s:%s:%d: Failed to import DER encoded certificate. GnuTLS error = 0x%02x (%s)\n", + __FILE__, __func__, __LINE__, ret, gnutls_strerror(ret)); + return NULL; + } + + return cert; } void rdssl_cert_free(RDSSL_CERT * cert) { - X509_free(cert); + gnutls_free(cert); } + +/* + * AFAIK, there's no way to alter the decoded certificate using GnuTLS. + * + * Upon detecting "problem" (wrong public RSA key OID) certificate + * we basically have two options: + * + * 1)) encode certificate back to DER, then parse it using libtasn1, + * fix public key OID (set it to 1.2.840.113549.1.1.1), encode to DER again + * and finally reparse using GnuTLS + * + * 2) encode cert back to DER, get RSA public key parameters using libtasn1 + * + * Or can rewrite the whole certificate related stuff later. + */ + /* returns newly allocated RDSSL_RKEY or NULL */ RDSSL_RKEY * rdssl_cert_to_rkey(RDSSL_CERT * cert, uint32 * key_len) { - EVP_PKEY *epk = NULL; - RDSSL_RKEY *lkey; - int nid; int ret; + int check_pk_algo; -#if OPENSSL_VERSION_NUMBER >= 0x10100000L - const unsigned char *p; - RSA *rsa = NULL; - int pklen; -#endif + RDSSL_RKEY *pkey; + gnutls_datum_t m, e; + + unsigned int algo, bits; + char oid[64]; + size_t oid_size = sizeof(oid); + + uint8_t data[2048]; + size_t len; + + check_pk_algo = 1; + + algo = gnutls_x509_crt_get_pk_algorithm(*cert, &bits); /* By some reason, Microsoft sets the OID of the Public RSA key to the oid for "MD5 with RSA Encryption" instead of "RSA Encryption" - Kudos to Richard Levitte for the following (. intuitive .) - lines of code that resets the OID and let's us extract the key. */ + Kudos to Richard Levitte for the finding this and proposed the fix + using OpenSSL. */ - X509_PUBKEY *key = NULL; - X509_ALGOR *algor = NULL; - - key = X509_get_X509_PUBKEY(cert); - if (key == NULL) - { - logger(Protocol, Error, - "rdssl_cert_to_key(), failed to get public key from certificate"); - rdssl_log_ssl_errors("rdssl_cert_to_key()"); - - return NULL; - } - - ret = X509_PUBKEY_get0_param(NULL, NULL, 0, &algor, key); - if (ret != 1) - { - logger(Protocol, Error, - "rdssl_cert_to_key(), failed to get algorithm used for public key"); - rdssl_log_ssl_errors("rdssl_cert_to_key()"); - - return NULL; - } - - nid = OBJ_obj2nid(algor->algorithm); - - if ((nid == NID_md5WithRSAEncryption) || (nid == NID_shaWithRSAEncryption)) - { -#if OPENSSL_VERSION_NUMBER < 0x10100000L - logger(Protocol, Debug, - "rdssl_cert_to_key(), re-setting algorithm type to RSA in server certificate"); - X509_PUBKEY_set0_param(key, OBJ_nid2obj(NID_rsaEncryption), 0, NULL, NULL, 0); -#else - - if (!X509_PUBKEY_get0_param(NULL, &p, &pklen, NULL, key)) - { - logger(Protocol, Error, - "rdssl_cert_to_key(), failed to get algorithm used for public key"); - rdssl_log_ssl_errors("rdssl_cert_to_key()"); + if (algo == GNUTLS_PK_RSA) { + if ((ret = gnutls_x509_crt_get_pk_rsa_raw(*cert, &m, &e)) != GNUTLS_E_SUCCESS) { + logger(Protocol, Error, "%s:%s:%d: Failed to get RSA public key parameters from certificate. GnuTLS error = 0x%02x (%s)\n", + __FILE__, __func__, __LINE__, ret, gnutls_strerror(ret)); return NULL; } - if (!(rsa = d2i_RSAPublicKey(NULL, &p, pklen))) - { - logger(Protocol, Error, - "rdssl_cert_to_key(), failed to extract public key from certificate"); - rdssl_log_ssl_errors("rdssl_cert_to_key()"); + } else if (algo == GNUTLS_E_UNIMPLEMENTED_FEATURE) { + /* Maybe we should get rid of gnutls_x509_crt_get_pk_oid() and + check public key algo in libtasn_read_cert_pk_params() */ + +#if GNUTLS_VERSION_NUMBER >= 0x030500 + // manpage says that this function is useful when gnutls_x509_crt_get_pk_algorithm() returns GNUTLS_PK_UNKNOWN + if ((ret = gnutls_x509_crt_get_pk_oid(*cert, oid, &oid_size)) != GNUTLS_E_SUCCESS) { + logger(Protocol, Error, "%s:%s:%d: Failed to get OID of public key algorithm. GnuTLS error = 0x%02x (%s)\n", + __FILE__, __func__, __LINE__, ret, gnutls_strerror(ret)); return NULL; } - lkey = RSAPublicKey_dup(rsa); - *key_len = RSA_size(lkey); - return lkey; + if (!strncmp(oid, OID_SHA_WITH_RSA_SIGNATURE, strlen(OID_SHA_WITH_RSA_SIGNATURE)) + || !strncmp(oid, OID_MD5_WITH_RSA_SIGNATURE, strlen(OID_MD5_WITH_RSA_SIGNATURE))) { + check_pk_algo = 0; + } else { + logger(Protocol, Error, "%s:%s:%d: Wrong public key algorithm algo = 0x%02x (%s)\n", + __FILE__, __func__, __LINE__, algo, oid); + return NULL; + } #endif - } + len = sizeof(data); - epk = X509_get_pubkey(cert); - if (NULL == epk) - { - logger(Protocol, Error, - "rdssl_cert_to_key(), failed to extract public key from certificate"); - rdssl_log_ssl_errors("rdssl_cert_to_key()"); + if ((ret = gnutls_x509_crt_export(*cert, GNUTLS_X509_FMT_DER, data, &len)) != GNUTLS_E_SUCCESS) { + logger(Protocol, Error, "%s:%s:%d: Failed to encode X.509 certificate to DER. GnuTLS error = 0x%02x (%s)\n", + __FILE__, __func__, __LINE__, ret, gnutls_strerror(ret)); + return NULL; + } + if ((ret = libtasn_read_cert_pk_parameters(data, len, &m, &e, check_pk_algo)) != 0) { + logger(Protocol, Error, "%s:%s:%d: Failed to read RSA public key parameters\n", + __FILE__, __func__, __LINE__); + + return NULL; + } + + } else { + logger(Protocol, Error, "%s:%s:%d: Failed to get public key algorithm from certificate. algo = 0x%02x (%d)\n", + __FILE__, __func__, __LINE__, algo, algo); return NULL; } - lkey = RSAPublicKey_dup(EVP_PKEY_get1_RSA(epk)); - EVP_PKEY_free(epk); - *key_len = RSA_size(lkey); - return lkey; + pkey = malloc(sizeof(*pkey)); + + if (!pkey) { + logger(Protocol, Error, "%s:%s:%d: Failed to allocate memory for RSA public key\n", + __FILE__, __func__, __LINE__); + return NULL; + } + + rsa_public_key_init(pkey); + + mpz_import(pkey->n, m.size, 1, sizeof(m.data[0]), 0, 0, m.data); + mpz_import(pkey->e, e.size, 1, sizeof(e.data[0]), 0, 0, e.data); + + rsa_public_key_prepare(pkey); + + *key_len = pkey->size; + + return pkey; } /* returns boolean */ @@ -270,40 +295,52 @@ rdssl_certs_ok(RDSSL_CERT * server_cert, RDSSL_CERT * cacert) int rdssl_cert_print_fp(FILE * fp, RDSSL_CERT * cert) { - return X509_print_fp(fp, cert); + int ret; + gnutls_datum_t cinfo; + + ret = gnutls_x509_crt_print(*cert, GNUTLS_CRT_PRINT_ONELINE, &cinfo); + + if (ret == 0) { + fprintf (fp, "\t%s\n", cinfo.data); + gnutls_free(cinfo.data); + } + + return 0; } void rdssl_rkey_free(RDSSL_RKEY * rkey) { - RSA_free(rkey); + rsa_public_key_clear(rkey); + free(rkey); } +/* Actually we can get rid of this function and use rsa_public)_key in rdssl_rsa_encrypt */ /* returns error */ int rdssl_rkey_get_exp_mod(RDSSL_RKEY * rkey, uint8 * exponent, uint32 max_exp_len, uint8 * modulus, uint32 max_mod_len) { - int len; + size_t outlen; - BIGNUM *e = NULL; - BIGNUM *n = NULL; + // TODO: Check size before exporing + mpz_export(modulus, &outlen, 1, sizeof(uint8), 0, 0, rkey->n); + mpz_export(exponent, &outlen, 1, sizeof(uint8), 0, 0, rkey->e); -#if OPENSSL_VERSION_NUMBER < 0x10100000L - e = rkey->e; - n = rkey->n; -#else - RSA_get0_key(rkey, &n, &e, NULL); -#endif + /* + * Note that gnutls_x509_crt_get_pk_rsa_raw() exports modulus with additional + * zero byte as signed bignum. We can easily import this value using mpz_import() + * After we use mpz_export() on pkey.n (modulus) it will (according to GMP docs) + * export data without sign byte. + * + * This is only important if you get modulus from certificate using GnuTLS, + * save it somewhere, import it into mpz and then export it from the said mpz to some + * buffer. If you then compare initiail (saved) modulus with newly exported one they + * will be different. + * + * On the other hand if we use mpz_t all the way, there will be no such situation. + */ - if ((BN_num_bytes(e) > (int) max_exp_len) || (BN_num_bytes(n) > (int) max_mod_len)) - { - return 1; - } - len = BN_bn2bin(e, exponent); - reverse(exponent, len); - len = BN_bn2bin(n, modulus); - reverse(modulus, len); return 0; } diff --git a/ssl.h b/ssl.h index c457647..c10f09b 100644 --- a/ssl.h +++ b/ssl.h @@ -23,26 +23,19 @@ #ifndef _RDSSL_H #define _RDSSL_H -#include -#include -#include - -#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x0090800f) -#define D2I_X509_CONST const -#else -#define D2I_X509_CONST -#endif - #include #include #include #include +#include + +#include #define RDSSL_RC4 struct arcfour_ctx #define RDSSL_SHA1 struct sha1_ctx #define RDSSL_MD5 struct md5_ctx -#define RDSSL_CERT X509 -#define RDSSL_RKEY RSA +#define RDSSL_CERT gnutls_x509_crt_t +#define RDSSL_RKEY struct rsa_public_key void rdssl_sha1_init(RDSSL_SHA1 * sha1); void rdssl_sha1_update(RDSSL_SHA1 * sha1, uint8 * data, uint32 len);