Parse X.509 certificate, get RSA public key, RSA encrypt

Also add support older (< 3.5.0) GnuTLS versions
This commit is contained in:
Alexander Zakharov 2017-08-31 23:17:30 +03:00 committed by Henrik Andersson
parent 166d1bc14d
commit 90fd660803
5 changed files with 213 additions and 213 deletions

22
asn.c
View File

@ -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",

4
asn.h
View File

@ -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
}

View File

@ -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

293
ssl.c
View File

@ -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 <gnutls/x509.h>
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()");
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;
}
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()");
} 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;
}
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 (!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;
}
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()");
return NULL;
}
lkey = RSAPublicKey_dup(rsa);
*key_len = RSA_size(lkey);
return lkey;
#endif
len = sizeof(data);
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;
}
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 = 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;
}
lkey = RSAPublicKey_dup(EVP_PKEY_get1_RSA(epk));
EVP_PKEY_free(epk);
*key_len = RSA_size(lkey);
return lkey;
} 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;
}
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;
}

17
ssl.h
View File

@ -23,26 +23,19 @@
#ifndef _RDSSL_H
#define _RDSSL_H
#include <openssl/err.h>
#include <openssl/bn.h>
#include <openssl/x509v3.h>
#if defined(OPENSSL_VERSION_NUMBER) && (OPENSSL_VERSION_NUMBER >= 0x0090800f)
#define D2I_X509_CONST const
#else
#define D2I_X509_CONST
#endif
#include <nettle/md5.h>
#include <nettle/sha1.h>
#include <nettle/arcfour.h>
#include <nettle/hmac.h>
#include <nettle/rsa.h>
#include <gnutls/x509.h>
#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);