From 709e675cdf0c8f9b07507aad12400398f354cc90 Mon Sep 17 00:00:00 2001 From: Stephen Gallagher Date: Mon, 16 Jan 2017 09:19:43 -0500 Subject: [PATCH 43/54] Add OpenSSL support for digest and signatures Autotools: add --with-crypto=openssl This enables RPM to locate the appropriate flags for compiling against OpenSSL for digest and hash functions. This implementation changes the old behavior of --with[out]-beecrypt toggling between beecrypt and nss. It will now throw an error if attempting to use --with-beecrypt indicating that the user should instead use --with-crypto= See also: https://github.com/rpm-software-management/rpm/issues/119 (cherry picked from commit 64028f9a1c25ada8ffc7a48775f526600edcbf85) Conflicts: Makefile.am configure.ac --- INSTALL | 27 +- Makefile.am | 17 +- configure.ac | 115 ++++++- rpmio/Makefile.am | 6 + rpmio/digest_openssl.c | 838 +++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 982 insertions(+), 21 deletions(-) create mode 100644 rpmio/digest_openssl.c diff --git a/INSTALL b/INSTALL index 60536e316..8eefef799 100644 --- a/INSTALL +++ b/INSTALL @@ -9,17 +9,34 @@ The libmagic (aka file) library for file type detection (used by rpmbuild). The source for the file utility + library is available from ftp://ftp.astron.com/pub/file/ -The NSS >= 3.12 library for encryption, and NSPR library which NSS uses. -Both NSPR and NSS libraries and headers need to be installed during RPM -compilation. As NSPR and NSS typically install their headers outside -the regular include search path, you need to tell configure about this, -eg something like: +You will need a cryptographic library to support digests and signatures. +This library may be Mozilla NSS, OpenSSL or beecrypt. Which library to use +must be specified with the --with-crypto=[beecrypt|nss|openssl] argument +to configure. + +If using the Mozilla NSS library for encyption (and NSPR library which +NSS uses) it must be version 3.12 or later. Both NSPR and NSS libraries and +headers need to be installed during RPM compilation. As NSPR and NSS +typically install their headers outside the regular include search path, +you need to tell configure about this, eg something like: ./configure <......> CPPFLAGS="-I/usr/include/nspr -I/usr/include/nss" The NSPR and NSS libraries are available from http://www.mozilla.org/projects/security/pki/nss/ http://www.mozilla.org/projects/nspr/ +If using the OpenSSL library for encryption, it must be version 1.0.2 or +later. Note: when compiling against OpenSSL, there is a possible license +incompatibility. For more details on this, see +https://people.gnome.org/~markmc/openssl-and-the-gpl.html +Some Linux distributions have different legal interpretations of this +possible incompatibility. It is recommended to consult with a lawyer before +building RPM against OpenSSL. +Fedora: https://fedoraproject.org/wiki/Licensing:FAQ#What.27s_the_deal_with_the_OpenSSL_license.3F +Debian: https://lists.debian.org/debian-legal/2002/10/msg00113.html + +The OpenSSL crypto library is available from https://www.openssl.org/ + The Berkeley DB >= 4.3.x (4.5.x or newer recommended) is required for the default database backend. BDB can be downloaded from http://www.oracle.com/technology/software/products/berkeley-db/index.html diff --git a/Makefile.am b/Makefile.am index 1b77730aa..a5268030b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -41,6 +41,7 @@ AM_CPPFLAGS += -I$(top_srcdir)/build AM_CPPFLAGS += -I$(top_srcdir)/lib AM_CPPFLAGS += -I$(top_srcdir)/rpmio AM_CPPFLAGS += @WITH_NSS_INCLUDE@ +AM_CPPFLAGS += @WITH_OPENSSL_INCLUDE@ AM_CPPFLAGS += @WITH_POPT_INCLUDE@ AM_CPPFLAGS += -I$(top_srcdir)/misc AM_CPPFLAGS += -DLOCALEDIR="\"$(localedir)\"" @@ -113,45 +114,45 @@ rpm_SOURCES = rpmqv.c debug.h system.h rpm_CPPFLAGS = $(AM_CPPFLAGS) -DIAM_RPMEIU -DIAM_RPMQ -DIAM_RPMV rpm_LDADD = libcliutils.la rpm_LDADD += lib/librpm.la rpmio/librpmio.la -rpm_LDADD += @WITH_NSS_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ +rpm_LDADD += @WITH_NSS_LIB@ @WITH_OPENSSL_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ rpmdb_SOURCES = rpmdb.c debug.h system.h rpmdb_CPPFLAGS = $(AM_CPPFLAGS) rpmdb_LDADD = libcliutils.la rpmdb_LDADD += lib/librpm.la rpmio/librpmio.la -rpmdb_LDADD += @WITH_NSS_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ +rpmdb_LDADD += @WITH_NSS_LIB@ @WITH_OPENSSL_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ rpmkeys_SOURCES = rpmkeys.c debug.h system.h rpmkeys_CPPFLAGS = $(AM_CPPFLAGS) rpmkeys_LDADD = libcliutils.la rpmkeys_LDADD += lib/librpm.la rpmio/librpmio.la -rpmkeys_LDADD += @WITH_NSS_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ +rpmkeys_LDADD += @WITH_NSS_LIB@ @WITH_OPENSSL_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ rpmsign_SOURCES = rpmsign.c debug.h system.h rpmsign_CPPFLAGS = $(AM_CPPFLAGS) rpmsign_LDADD = libcliutils.la rpmsign_LDADD += sign/librpmsign.la lib/librpm.la rpmio/librpmio.la -rpmsign_LDADD += @WITH_NSS_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ +rpmsign_LDADD += @WITH_NSS_LIB@ @WITH_OPENSSL_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ rpmbuild_SOURCES = rpmbuild.c debug.h system.h rpmbuild_CPPFLAGS = $(AM_CPPFLAGS) rpmbuild_LDADD = libcliutils.la rpmbuild_LDADD += build/librpmbuild.la lib/librpm.la rpmio/librpmio.la -rpmbuild_LDADD += @WITH_NSS_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ +rpmbuild_LDADD += @WITH_NSS_LIB@ @WITH_OPENSSL_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ rpmspec_SOURCES = rpmspec.c debug.h system.h rpmspec_CPPFLAGS = $(AM_CPPFLAGS) rpmspec_LDADD = libcliutils.la rpmspec_LDADD += build/librpmbuild.la lib/librpm.la rpmio/librpmio.la -rpmspec_LDADD += @WITH_NSS_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ +rpmspec_LDADD += @WITH_NSS_LIB@ @WITH_OPENSSL_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ rpm2cpio_SOURCES = rpm2cpio.c debug.h system.h rpm2cpio_LDADD = lib/librpm.la rpmio/librpmio.la -rpm2cpio_LDADD += @WITH_NSS_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ +rpm2cpio_LDADD += @WITH_NSS_LIB@ @WITH_OPENSSL_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ rpm2archive_SOURCES = rpm2archive.c debug.h system.h rpm2archive_LDADD = lib/librpm.la rpmio/librpmio.la -rpm2archive_LDADD += @WITH_NSS_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ @WITH_ARCHIVE_LIB@ +rpm2archive_LDADD += @WITH_NSS_LIB@ @WITH_OPENSSL_LIB@ @WITH_POPT_LIB@ @WITH_ZLIB_LIB@ @WITH_ARCHIVE_LIB@ if LIBELF diff --git a/configure.ac b/configure.ac index e6362535b..65052870a 100644 --- a/configure.ac +++ b/configure.ac @@ -243,18 +243,30 @@ AC_CHECK_HEADERS([dwarf.h], [ AM_CONDITIONAL(LIBDWARF,[test "$WITH_LIBDWARF" = yes]) #================= +# Select crypto library +AC_ARG_WITH(crypto, + [AC_HELP_STRING([--with-crypto=CRYPTO_LIB], + [The cryptographic library to use (nss|beecrypt|openssl). The default is nss.]) + ],[], + [with_crypto=nss]) + +# Refuse to proceed if someone specified --with-beecrypt (removed) +AC_ARG_WITH(beecrypt, + [AC_HELP_STRING([--with-beecrypt (OBSOLETE)], [Obsolete argument. Use --with-crypto=beecrypt]) + ],[AC_MSG_ERROR([--with-beecrypt no longer supported. Use --with-crypto=beecrypt])], + []) + # Check for beecrypt library if requested. -AC_ARG_WITH(beecrypt, [ --with-beecrypt build with beecrypt support ],,[with_beecrypt=no]) AC_ARG_WITH(internal_beecrypt, [ --with-internal-beecrypt build with internal beecrypt library ],,[with_internal_beecrypt=no]) AM_CONDITIONAL([WITH_INTERNAL_BEECRYPT],[test "$with_internal_beecrypt" = yes]) if test "$with_internal_beecrypt" = yes ; then - with_beecrypt=yes + with_crypto=beecrypt fi -AM_CONDITIONAL([WITH_BEECRYPT],[test "$with_beecrypt" = yes]) +AM_CONDITIONAL([WITH_BEECRYPT],[test "$with_crypto" = beecrypt]) WITH_BEECRYPT_INCLUDE= WITH_BEECRYPT_LIB= -if test "$with_beecrypt" = yes ; then +if test "$with_crypto" = beecrypt ; then AC_DEFINE(WITH_BEECRYPT, 1, [Build with beecrypt instead of nss3 support?]) if test "$with_internal_beecrypt" = yes ; then WITH_BEECRYPT_INCLUDE="-I\$(top_srcdir)/beecrypt" @@ -263,7 +275,7 @@ if test "$with_beecrypt" = yes ; then AC_CHECK_LIB(beecrypt, mpfprintln, [ WITH_BEECRYPT_LIB="-lbeecrypt" ],[ - AC_MSG_ERROR([missing required library 'beecrypt']) + AC_MSG_ERROR([missing required library 'beecrypt']) ]) AC_CHECK_HEADER([beecrypt/api.h], [AC_DEFINE(HAVE_BEECRYPT_API_H, 1, [Define to 1 if you have the header file.]) ]) @@ -273,13 +285,100 @@ AC_SUBST(WITH_BEECRYPT_LIB) AC_SUBST(WITH_BEECRYPT_INCLUDE) #================= +# Check for OpenSSL library. +# We need evp.h from OpenSSL. + +WITH_OPENSSL_INCLUDE= +WITH_OPENSSL_LIB= +if test "$with_crypto" = openssl; then +# If we have pkgconfig make sure CPPFLAGS are setup correctly for the OpenSSL +# -I include path. +AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no], [$PATH:/usr/bin:/usr/local/bin]) +if test "x$PKGCONFIG" != "xno"; then + CPPFLAGS="$CPPFLAGS $($PKGCONFIG --cflags libcrypto)" + WITH_OPENSSL_LIB=$($PKGCONFIG --libs libcrypto) +else + WITH_OPENSSL_LIB=-lcrypto +fi + +AC_CHECK_HEADERS([openssl/evp.h], [], [ + AC_MSG_ERROR([missing required OpenSSL header]) +]) +AC_CHECK_HEADERS([openssl/rsa.h], [], [ + AC_MSG_ERROR([missing required OpenSSL header]) +]) +AC_CHECK_HEADERS([openssl/dsa.h], [], [ + AC_MSG_ERROR([missing required OpenSSL header]) +]) + +AC_CHECK_LIB(crypto, EVP_DigestInit_ex, [], [ + AC_MSG_ERROR([required OpenSSL library 'libcrypto' missing or too old]) +]) + +AC_CHECK_LIB(crypto, EVP_MD_CTX_new, [ + AC_DEFINE(HAVE_EVP_MD_CTX_NEW, 1, [Define to 1 if OpenSSL has EVP_MD_CTX_new]) + AC_SUBST(HAVE_EVP_MD_CTX_NEW, [1]) + ], [ + AC_CHECK_LIB(crypt, EVP_MD_CTX_create, [], [ + AC_MSG_ERROR([required OpenSSL library 'libcrypto' missing or too old]) + ]) +]) + +AC_CHECK_LIB(crypto, EVP_PKEY_CTX_new, [], [ + AC_MSG_ERROR([required OpenSSL library 'libcrypto' missing or too old]) +]) + +AC_CHECK_LIB(crypto, DSA_set0_key, [ + AC_DEFINE(HAVE_DSA_SET0_KEY, 1, [Define to 1 if OpenSSL has DSA_set0_key]) + AC_SUBST(HAVE_DSA_SET0_KEY, [1]) + ], [] +) + +AC_CHECK_LIB(crypto, DSA_set0_pqg, [ + AC_DEFINE(HAVE_DSA_SET0_PQG, 1, [Define to 1 if OpenSSL has DSA_set0_pqg]) + AC_SUBST(HAVE_DSA_SET0_PQG, [1]) + ], [] +) + +AC_CHECK_LIB(crypto, DSA_SIG_set0, [ + AC_DEFINE(HAVE_DSA_SIG_SET0, 1, [Define to 1 if OpenSSL has DSA_SIG_set0]) + AC_SUBST(HAVE_DSA_SIG_SET0, [1]) + ], [] +) + +AC_CHECK_LIB(crypto, RSA_set0_key, [ + AC_DEFINE(HAVE_RSA_SET0_KEY, 1, [Define to 1 if OpenSSL has RSA_set0_key]) + AC_SUBST(HAVE_RSA_SET0_KEY, [1]) + ], [] +) + +AC_CHECK_LIB(crypto, BN_bn2binpad, [ + AC_DEFINE(HAVE_BN2BINPAD, 1, [Define to 1 if OpenSSL has BN_bn2binpad]) + AC_SUBST(HAVE_BN2BINPAD, [1]) + ], [] +) + +fi + +AM_CONDITIONAL([WITH_OPENSSL],[test "$with_crypto" = openssl]) +AC_SUBST(WITH_OPENSSL_INCLUDE) +AC_SUBST(WITH_OPENSSL_LIB) + +#================= # Check for NSS library. -# We need nss.h from NSS which needs nspr.h. Unfortunately both glibc and NSS -# have a header named nss.h... so make extra check for NSS's sechash.h +# We need nss.h from NSS which needs nspr.h. Unfortunately both glibc and NSS +# have a header named nss.h... so make extra check for NSS's sechash.h # which we use too and hopefully is slightly more unique to NSS. WITH_NSS_INCLUDE= WITH_NSS_LIB= -if test "$with_beecrypt" != yes ; then +if test "$with_crypto" = nss; then +# If we have pkgconfig make sure CPPFLAGS are setup correctly for the nss +# -I include path. Otherwise the below checks will fail because nspr.h +# cannot be found. +AC_PATH_TOOL([PKGCONFIG], [pkg-config], [no], [$PATH:/usr/bin:/usr/local/bin]) +if test "x$PKGCONFIG" != "xno"; then + CPPFLAGS="$CPPFLAGS $($PKGCONFIG --cflags nss)" +fi AC_CHECK_HEADERS([nspr.h nss.h sechash.h], [], [ AC_MSG_ERROR([missing required NSPR / NSS header]) ]) diff --git a/rpmio/Makefile.am b/rpmio/Makefile.am index 68a821934..9062f58a5 100644 --- a/rpmio/Makefile.am +++ b/rpmio/Makefile.am @@ -5,6 +5,7 @@ include $(top_srcdir)/rpm.am AM_CPPFLAGS = -I$(top_builddir) -I$(top_srcdir) -I$(top_builddir)/include/ AM_CPPFLAGS += @WITH_NSS_INCLUDE@ AM_CPPFLAGS += @WITH_BEECRYPT_INCLUDE@ +AM_CPPFLAGS += @WITH_OPENSSL_INCLUDE@ AM_CPPFLAGS += @WITH_POPT_INCLUDE@ AM_CPPFLAGS += -I$(top_srcdir)/misc AM_CPPFLAGS += -DRPMCONFIGDIR="\"@RPMCONFIGDIR@\"" @@ -23,8 +24,12 @@ librpmio_la_SOURCES = \ if WITH_BEECRYPT librpmio_la_SOURCES += digest_beecrypt.c else +if WITH_OPENSSL +librpmio_la_SOURCES += digest_openssl.c +else librpmio_la_SOURCES += digest_nss.c endif +endif librpmio_la_LDFLAGS = -version-info $(rpm_version_info) @@ -32,6 +37,7 @@ librpmio_la_LIBADD = \ ../misc/libmisc.la \ @WITH_NSS_LIB@ \ @WITH_BEECRYPT_LIB@ \ + @WITH_OPENSSL_LIB@ \ @WITH_BZ2_LIB@ \ @WITH_ZLIB_LIB@ \ @WITH_LIBELF_LIB@ \ diff --git a/rpmio/digest_openssl.c b/rpmio/digest_openssl.c new file mode 100644 index 000000000..aea460e39 --- /dev/null +++ b/rpmio/digest_openssl.c @@ -0,0 +1,838 @@ +#include "system.h" + +#include +#include +#include +#include + +#include "rpmio/digest.h" + + +/* Compatibility functions for OpenSSL 1.0.2 */ + +#ifndef HAVE_EVP_MD_CTX_NEW +# define EVP_MD_CTX_new EVP_MD_CTX_create +# define EVP_MD_CTX_free EVP_MD_CTX_destroy +#endif + +#ifndef HAVE_RSA_SET0_KEY +int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d); +int RSA_set0_key(RSA *r, BIGNUM *n, BIGNUM *e, BIGNUM *d) +{ + if (!r) return 0; + + if (n) { + r->n = n; + } + + if (e) { + r->e = e; + } + + if (d) { + r->d = d; + } + + return 1; +} +#endif /* HAVE_RSA_SET0_KEY */ + +#ifndef HAVE_DSA_SET0_KEY +int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key); + +int DSA_set0_key(DSA *d, BIGNUM *pub_key, BIGNUM *priv_key) +{ + if (!d) return 0; + + if (pub_key) { + d->pub_key = pub_key; + } + + if (priv_key) { + d->priv_key = priv_key; + } + + return 1; +} +#endif /* HAVE_DSA_SET0_KEY */ + +#ifndef HAVE_DSA_SET0_PQG +int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g); + +int DSA_set0_pqg(DSA *d, BIGNUM *p, BIGNUM *q, BIGNUM *g) +{ + if (!d) return 0; + + if (p) { + d->p = p; + } + + if (q) { + d->q = q; + } + + if (g) { + d->g = g; + } + + return 1; +} +#endif /* HAVE_DSA_SET0_PQG */ + +#ifndef HAVE_DSA_SIG_SET0 +int DSA_SIG_set0(DSA_SIG *sig, BIGNUM *r, BIGNUM *s); + +int DSA_SIG_set0(DSA_SIG *sig, BIGNUM *r, BIGNUM *s) +{ + if (!sig) return 0; + + if (r) { + sig->r = r; + } + + if (s) { + sig->s = s; + } + + return 1; +} +#endif /* HAVE_DSA_SIG_SET0 */ + +#ifndef HAVE_BN2BINPAD +static int BN_bn2binpad(const BIGNUM *a, unsigned char *to, int tolen) +{ + int i; + + i = BN_num_bytes(a); + if (tolen < i) + return -1; + + /* Add leading zeroes if necessary */ + if (tolen > i) { + memset(to, 0, tolen - i); + to += tolen - i; + } + + BN_bn2bin(a, to); + + return tolen; +} +#endif /* HAVE_BN2BINPAD */ + +struct DIGEST_CTX_s { + rpmDigestFlags flags; /*!< Bit(s) to control digest operation. */ + int algo; /*!< Used hash algorithm */ + + EVP_MD_CTX *md_ctx; /* Digest context (opaque) */ + +}; + +/**************************** init ************************************/ + +int rpmInitCrypto(void) { + return 0; +} + +int rpmFreeCrypto(void) { + return 0; +} + +/**************************** digest ************************************/ + +DIGEST_CTX rpmDigestDup(DIGEST_CTX octx) +{ + if (!octx) return NULL; + + DIGEST_CTX nctx = NULL; + nctx = xcalloc(1, sizeof(*nctx)); + + nctx->flags = octx->flags; + nctx->algo = octx->algo; + nctx->md_ctx = EVP_MD_CTX_new(); + if (!nctx->md_ctx) { + free(nctx); + return NULL; + } + + if (!EVP_MD_CTX_copy(nctx->md_ctx, octx->md_ctx)) { + free(nctx); + return NULL; + } + + return nctx; +} + +static const EVP_MD *getEVPMD(int hashalgo) +{ + switch (hashalgo) { + + case PGPHASHALGO_MD5: + return EVP_md5(); + + case PGPHASHALGO_SHA1: + return EVP_sha1(); + + case PGPHASHALGO_RIPEMD160: + return EVP_ripemd160(); + + case PGPHASHALGO_MD2: + return EVP_md2(); + + case PGPHASHALGO_SHA256: + return EVP_sha256(); + + case PGPHASHALGO_SHA384: + return EVP_sha384(); + + case PGPHASHALGO_SHA512: + return EVP_sha512(); + + case PGPHASHALGO_SHA224: + return EVP_sha224(); + + default: + return EVP_md_null(); + } +} + +size_t rpmDigestLength(int hashalgo) +{ + return EVP_MD_size(getEVPMD(hashalgo)); +} + +DIGEST_CTX rpmDigestInit(int hashalgo, rpmDigestFlags flags) +{ + DIGEST_CTX ctx = xcalloc(1, sizeof(*ctx)); + + ctx->md_ctx = EVP_MD_CTX_new(); + if (!ctx->md_ctx) { + free(ctx); + return NULL; + } + + const EVP_MD *md = getEVPMD(hashalgo); + if (md == EVP_md_null()) { + free(ctx->md_ctx); + free(ctx); + return NULL; + } + + ctx->algo = hashalgo; + ctx->flags = flags; + if (!EVP_DigestInit_ex(ctx->md_ctx, md, NULL)) { + free(ctx->md_ctx); + free(ctx); + return NULL; + } + + return ctx; +} + +int rpmDigestUpdate(DIGEST_CTX ctx, const void *data, size_t len) +{ + if (ctx == NULL) return -1; + + EVP_DigestUpdate(ctx->md_ctx, data, len); + + return 0; +} + +int rpmDigestFinal(DIGEST_CTX ctx, void ** datap, size_t *lenp, int asAscii) +{ + int ret; + unsigned char *digest = NULL; + unsigned int digestlen; + + if (ctx == NULL) return -1; + + digestlen = EVP_MD_CTX_size(ctx->md_ctx); + digest = xcalloc(digestlen, sizeof(*digest)); + + ret = EVP_DigestFinal_ex(ctx->md_ctx, digest, &digestlen); + if (ret != 1) goto done; + + if (!asAscii) { + /* Raw data requested */ + if (lenp) *lenp = digestlen; + if (datap) { + *datap = digest; + digest = NULL; + } + } + + else { + /* ASCII requested */ + if (lenp) *lenp = (2*digestlen) + 1; + if (datap) { + const uint8_t * s = (const uint8_t *) digest; + *datap = pgpHexStr(s, digestlen); + } + } + + ret = 1; + +done: + if (digest) { + /* Zero the digest, just in case it's sensitive */ + memset(digest, 0, digestlen); + free(digest); + } + + EVP_MD_CTX_free(ctx->md_ctx); + free(ctx); + + if (ret != 1) { + return -1; + } + + return 0; +} + + +/****************************** RSA **************************************/ + +/* Key */ + +struct pgpDigKeyRSA_s { + size_t nbytes; /* Size of modulus */ + + BIGNUM *n; /* Common Modulus */ + BIGNUM *e; /* Public Exponent */ + + EVP_PKEY *evp_pkey; /* Fully constructed key */ +}; + +static int constructRSASigningKey(struct pgpDigKeyRSA_s *key) +{ + if (key->evp_pkey) { + /* We've already constructed it, so just reuse it */ + return 1; + } + + /* Create the RSA key */ + RSA *rsa = RSA_new(); + if (!rsa) return 0; + + if (!RSA_set0_key(rsa, key->n, key->e, NULL)) { + RSA_free(rsa); + return 0; + } + + /* Create an EVP_PKEY container to abstract the key-type. */ + key->evp_pkey = EVP_PKEY_new(); + if (!key->evp_pkey) { + RSA_free(rsa); + return 0; + } + + /* Assign the RSA key to the EVP_PKEY structure. + This will take over memory management of the RSA key */ + if (!EVP_PKEY_assign_RSA(key->evp_pkey, rsa)) { + EVP_PKEY_free(key->evp_pkey); + key->evp_pkey = NULL; + RSA_free(rsa); + } + + return 1; +} + +static int pgpSetKeyMpiRSA(pgpDigAlg pgpkey, int num, const uint8_t *p) +{ + size_t mlen = pgpMpiLen(p) - 2; + struct pgpDigKeyRSA_s *key = pgpkey->data; + + if(!key) { + key = pgpkey->data = xcalloc(1, sizeof(*key)); + } + + switch(num) { + case 0: + /* Modulus */ + if (key->n) { + /* This should only ever happen once per key */ + return 1; + } + + key->nbytes = mlen; + /* Create a BIGNUM from the pointer. + Note: this assumes big-endian data as required by PGP */ + key->n = BN_bin2bn(p+2, mlen, NULL); + if (!key->n) return 1; + break; + + case 1: + /* Exponent */ + if (key->e) { + /* This should only ever happen once per key */ + return 1; + } + + /* Create a BIGNUM from the pointer. + Note: this assumes big-endian data as required by PGP */ + key->e = BN_bin2bn(p+2, mlen, NULL); + if (!key->e) return 1; + break; + } + + return 0; +} + +static void pgpFreeKeyRSA(pgpDigAlg pgpkey) +{ + struct pgpDigKeyRSA_s *key = pgpkey->data; + if (key) { + if (key->evp_pkey) { + EVP_PKEY_free(key->evp_pkey); + } else { + /* If key->evp_pkey was constructed, + * the memory management of these BNs + * are freed with it. */ + BN_clear_free(key->n); + BN_clear_free(key->e); + } + + free(key); + } +} + +/* Signature */ + +struct pgpDigSigRSA_s { + BIGNUM *bn; + size_t len; +}; + +static int pgpSetSigMpiRSA(pgpDigAlg pgpsig, int num, const uint8_t *p) +{ + BIGNUM *bn = NULL; + + int mlen = pgpMpiLen(p) - 2; + int rc = 1; + + struct pgpDigSigRSA_s *sig = pgpsig->data; + if (!sig) { + sig = xcalloc(1, sizeof(*sig)); + } + + switch (num) { + case 0: + if (sig->bn) { + /* This should only ever happen once per signature */ + return 1; + } + + bn = sig->bn = BN_new(); + if (!bn) return 1; + + /* Create a BIGNUM from the signature pointer. + Note: this assumes big-endian data as required + by the PGP multiprecision integer format + (RFC4880, Section 3.2) + This will be useful later, as we can + retrieve this value with appropriate + padding. */ + bn = BN_bin2bn(p+2, mlen, bn); + if (!bn) return 1; + + sig->bn = bn; + sig->len = mlen; + + pgpsig->data = sig; + rc = 0; + break; + } + return rc; +} + +static void pgpFreeSigRSA(pgpDigAlg pgpsig) +{ + struct pgpDigSigRSA_s *sig = pgpsig->data; + if (sig) { + BN_clear_free(sig->bn); + free(pgpsig->data); + } +} + +static int pgpVerifySigRSA(pgpDigAlg pgpkey, pgpDigAlg pgpsig, + uint8_t *hash, size_t hashlen, int hash_algo) +{ + int rc, ret; + EVP_PKEY_CTX *pkey_ctx = NULL; + struct pgpDigSigRSA_s *sig = pgpsig->data; + + void *padded_sig = NULL; + + struct pgpDigKeyRSA_s *key = pgpkey->data; + + if(!constructRSASigningKey(key)) { + rc = 1; + goto done; + } + + pkey_ctx = EVP_PKEY_CTX_new(key->evp_pkey, NULL); + if (!pkey_ctx) { + rc = 1; + goto done; + } + + ret = EVP_PKEY_verify_init(pkey_ctx); + if (ret < 0) { + rc = 1; + goto done; + } + + ret = EVP_PKEY_CTX_set_rsa_padding(pkey_ctx, RSA_PKCS1_PADDING); + if (ret < 0) { + rc = 1; + goto done; + } + + ret = EVP_PKEY_CTX_set_signature_md(pkey_ctx, getEVPMD(hash_algo)); + if (ret < 0) { + rc = 1; + goto done; + } + + int pkey_len = EVP_PKEY_size(key->evp_pkey); + padded_sig = xcalloc(1, pkey_len); + if (!BN_bn2binpad(sig->bn, padded_sig, pkey_len)) { + rc = 1; + goto done; + } + + ret = EVP_PKEY_verify(pkey_ctx, padded_sig, pkey_len, hash, hashlen); + if (ret == 1) + { + /* Success */ + rc = 0; + } + else + { + /* Failure */ + rc = 1; + } + +done: + EVP_PKEY_CTX_free(pkey_ctx); + free(padded_sig); + return rc; +} + +/****************************** DSA ***************************************/ +/* Key */ + +struct pgpDigKeyDSA_s { + BIGNUM *p; /* Prime */ + BIGNUM *q; /* Subprime */ + BIGNUM *g; /* Base */ + BIGNUM *y; /* Public Key */ + + DSA *dsa_key; /* Fully constructed key */ +}; + +static int constructDSASigningKey(struct pgpDigKeyDSA_s *key) +{ + int rc; + + if (key->dsa_key) { + /* We've already constructed it, so just reuse it */ + return 1; + } + + /* Create the DSA key */ + DSA *dsa = DSA_new(); + if (!dsa) return 0; + + if (!DSA_set0_pqg(dsa, key->p, key->q, key->g)) { + rc = 0; + goto done; + } + + if (!DSA_set0_key(dsa, key->y, NULL)) { + rc = 0; + } + + key->dsa_key = dsa; + + rc = 1; +done: + if (rc == 0) { + DSA_free(dsa); + } + return rc; +} + + +static int pgpSetKeyMpiDSA(pgpDigAlg pgpkey, int num, const uint8_t *p) +{ + BIGNUM *bn; + size_t mlen = pgpMpiLen(p) - 2; + struct pgpDigKeyDSA_s *key = pgpkey->data; + + if(!key) { + key = pgpkey->data = xcalloc(1, sizeof(*key)); + } + + /* Create a BIGNUM from the key pointer. + Note: this assumes big-endian data as required + by the PGP multiprecision integer format + (RFC4880, Section 3.2) */ + bn = BN_bin2bn(p+2, mlen, NULL); + if (!bn) return 1; + + switch(num) { + case 0: + /* Prime */ + if (key->p) { + /* This should only ever happen once per key */ + return 1; + } + key->p = bn; + break; + + case 1: + /* Subprime */ + if (key->q) { + /* This should only ever happen once per key */ + return 1; + } + key->q = bn; + break; + case 2: + /* Base */ + if (key->g) { + /* This should only ever happen once per key */ + return 1; + } + key->g = bn; + break; + case 3: + /* Public */ + if (key->y) { + /* This should only ever happen once per key */ + return 1; + } + key->y = bn; + break; + } + + return 0; +} + +static void pgpFreeKeyDSA(pgpDigAlg pgpkey) +{ + struct pgpDigKeyDSA_s *key = pgpkey->data; + if (key) { + if (key->dsa_key) { + DSA_free(key->dsa_key); + } else { + /* If sig->dsa_key was constructed, + * the memory management of these BNs + * are freed with it. */ + BN_clear_free(key->p); + BN_clear_free(key->q); + BN_clear_free(key->g); + BN_clear_free(key->y); + } + free(key); + } +} + +/* Signature */ + +struct pgpDigSigDSA_s { + BIGNUM *r; + BIGNUM *s; + + DSA_SIG *dsa_sig; +}; + +static int constructDSASignature(struct pgpDigSigDSA_s *sig) +{ + int rc; + + if (sig->dsa_sig) { + /* We've already constructed it, so just reuse it */ + return 1; + } + + /* Create the DSA signature */ + DSA_SIG *dsa_sig = DSA_SIG_new(); + if (!dsa_sig) return 0; + + if (!DSA_SIG_set0(dsa_sig, sig->r, sig->s)) { + rc = 0; + goto done; + } + + sig->dsa_sig = dsa_sig; + + rc = 1; +done: + if (rc == 0) { + DSA_SIG_free(sig->dsa_sig); + } + return rc; +} + +static int pgpSetSigMpiDSA(pgpDigAlg pgpsig, int num, const uint8_t *p) +{ + BIGNUM *bn = NULL; + + int mlen = pgpMpiLen(p) - 2; + int rc = 1; + + struct pgpDigSigDSA_s *sig = pgpsig->data; + if (!sig) { + sig = xcalloc(1, sizeof(*sig)); + } + + /* Create a BIGNUM from the signature pointer. + Note: this assumes big-endian data as required + by the PGP multiprecision integer format + (RFC4880, Section 3.2) */ + bn = BN_bin2bn(p+2, mlen, NULL); + if (!bn) return 1; + + switch (num) { + case 0: + if (sig->r) { + /* This should only ever happen once per signature */ + BN_free(bn); + return 1; + } + sig->r = bn; + rc = 0; + break; + case 1: + if (sig->s) { + /* This should only ever happen once per signature */ + BN_free(bn); + return 1; + } + sig->s = bn; + rc = 0; + break; + } + + pgpsig->data = sig; + + return rc; +} + +static void pgpFreeSigDSA(pgpDigAlg pgpsig) +{ + struct pgpDigSigDSA_s *sig = pgpsig->data; + if (sig) { + if (sig->dsa_sig) { + DSA_SIG_free(sig->dsa_sig); + } else { + /* If sig->dsa_sig was constructed, + * the memory management of these BNs + * are freed with it. */ + BN_clear_free(sig->r); + BN_clear_free(sig->s); + } + free(pgpsig->data); + } +} + +static int pgpVerifySigDSA(pgpDigAlg pgpkey, pgpDigAlg pgpsig, + uint8_t *hash, size_t hashlen, int hash_algo) +{ + int rc, ret; + struct pgpDigSigDSA_s *sig = pgpsig->data; + + struct pgpDigKeyDSA_s *key = pgpkey->data; + + if(!constructDSASigningKey(key)) { + rc = 1; + goto done; + } + + if (!constructDSASignature(sig)) { + rc = 1; + goto done; + } + + ret = DSA_do_verify(hash, hashlen, sig->dsa_sig, key->dsa_key); + if (ret == 1) + { + /* Success */ + rc = 0; + } + else + { + /* Failure */ + rc = 1; + } + +done: + return rc; +} + +/****************************** NULL **************************************/ + +static int pgpSetMpiNULL(pgpDigAlg pgpkey, int num, const uint8_t *p) +{ + return 1; +} + +static int pgpVerifyNULL(pgpDigAlg pgpkey, pgpDigAlg pgpsig, + uint8_t *hash, size_t hashlen, int hash_algo) +{ + return 1; +} + +/****************************** PGP **************************************/ +pgpDigAlg pgpPubkeyNew(int algo) +{ + pgpDigAlg ka = xcalloc(1, sizeof(*ka));; + + switch (algo) { + case PGPPUBKEYALGO_RSA: + ka->setmpi = pgpSetKeyMpiRSA; + ka->free = pgpFreeKeyRSA; + ka->mpis = 2; + break; + case PGPPUBKEYALGO_DSA: + ka->setmpi = pgpSetKeyMpiDSA; + ka->free = pgpFreeKeyDSA; + ka->mpis = 4; + break; + default: + ka->setmpi = pgpSetMpiNULL; + ka->mpis = -1; + break; + } + + ka->verify = pgpVerifyNULL; /* keys can't be verified */ + + return ka; +} + +pgpDigAlg pgpSignatureNew(int algo) +{ + pgpDigAlg sa = xcalloc(1, sizeof(*sa)); + + switch (algo) { + case PGPPUBKEYALGO_RSA: + sa->setmpi = pgpSetSigMpiRSA; + sa->free = pgpFreeSigRSA; + sa->verify = pgpVerifySigRSA; + sa->mpis = 1; + break; + case PGPPUBKEYALGO_DSA: + sa->setmpi = pgpSetSigMpiDSA; + sa->free = pgpFreeSigDSA; + sa->verify = pgpVerifySigDSA; + sa->mpis = 2; + break; + default: + sa->setmpi = pgpSetMpiNULL; + sa->verify = pgpVerifyNULL; + sa->mpis = -1; + break; + } + return sa; +} -- 2.13.2