diff options
author | minfrin <minfrin@13f79535-47bb-0310-9956-ffa450edef68> | 2008-09-07 20:42:47 +0000 |
---|---|---|
committer | minfrin <minfrin@13f79535-47bb-0310-9956-ffa450edef68> | 2008-09-07 20:42:47 +0000 |
commit | 73d8835d9c69243fdf27d7231420ad6f154c74df (patch) | |
tree | 6f6afad335f6b7f31eca03108b38ab085d2e5f8e | |
parent | 69fb31e9a20ae26dbb54bdd6818b7f6b9df29283 (diff) | |
download | libapr-util-73d8835d9c69243fdf27d7231420ad6f154c74df.tar.gz |
Add apr_crypto implementations for OpenSSL and Mozilla NSS. Add a unit
test to verify the interoperability of the two modules. Builds default
to disabled unless explicitly enabled.
git-svn-id: http://svn.apache.org/repos/asf/apr/apr-util/trunk@692949 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r-- | CHANGES | 5 | ||||
-rw-r--r-- | Makefile.in | 2 | ||||
-rw-r--r-- | build.conf | 17 | ||||
-rw-r--r-- | build/crypto.m4 | 205 | ||||
-rw-r--r-- | build/dso.m4 | 8 | ||||
-rw-r--r-- | configure.in | 4 | ||||
-rw-r--r-- | crypto/apr_crypto_nss.c | 766 | ||||
-rw-r--r-- | crypto/apr_crypto_openssl.c | 649 | ||||
-rw-r--r-- | test/Makefile.in | 6 | ||||
-rw-r--r-- | test/abts_tests.h | 1 | ||||
-rw-r--r-- | test/testcrypto.c | 698 | ||||
-rw-r--r-- | test/testutil.h | 1 |
12 files changed, 2356 insertions, 6 deletions
@@ -1,6 +1,11 @@ -*- coding: utf-8 -*- Changes with APR-util 1.4.0 + *) Add apr_crypto implementations for OpenSSL and Mozilla NSS. Add a unit + test to verify the interoperability of the two modules. Builds default + to disabled unless explicitly enabled. + [Graham Leggett] + *) Add the apr_crypto interface, a rewrite of the earlier apr_ssl code, based on the modular dso interface used for dbd and ldap. Initially, the interface supports symmetrical encryption and decryption. The diff --git a/Makefile.in b/Makefile.in index 9f78a92b..305d3302 100644 --- a/Makefile.in +++ b/Makefile.in @@ -38,6 +38,8 @@ LDADD_dbd_mysql = @LDADD_dbd_mysql@ LDADD_dbd_freetds = @LDADD_dbd_freetds@ LDADD_dbd_odbc = @LDADD_dbd_odbc@ LDADD_ldap = @LDADD_ldap@ +LDADD_crypto_openssl = @LDADD_crypto_openssl@ +LDADD_crypto_nss = @LDADD_crypto_nss@ TARGETS = $(TARGET_LIB) aprutil.exp apu-config.out $(APU_MODULES) @@ -7,7 +7,12 @@ # the platform-independent .c files paths = buckets/*.c - crypto/*.c + crypto/apr_crypto.c + crypto/apr_md4.c + crypto/apr_md5.c + crypto/apr_sha1.c + crypto/getuuid.c + crypto/uuid.c dbm/*.c dbm/sdbm/*.c encoding/*.c @@ -28,13 +33,21 @@ platform_dirs = # the public headers headers = include/*.h include/private/*.h -modules = ldap dbd_pgsql dbd_sqlite2 dbd_sqlite3 dbd_oracle dbd_mysql dbd_freetds dbd_odbc +modules = crypto_openssl crypto_nss ldap dbd_pgsql dbd_sqlite2 dbd_sqlite3 dbd_oracle dbd_mysql dbd_freetds dbd_odbc # gen_uri_delim.c # we have a recursive makefile for the test files (for now) # test/*.c +[crypto_openssl] +paths = crypto/apr_crypto_openssl.c +target = crypto/apr_crypto_openssl.la + +[crypto_nss] +paths = crypto/apr_crypto_nss.c +target = crypto/apr_crypto_nss.la + [dbd_pgsql] paths = dbd/apr_dbd_pgsql.c target = dbd/apr_dbd_pgsql.la diff --git a/build/crypto.m4 b/build/crypto.m4 new file mode 100644 index 00000000..18725185 --- /dev/null +++ b/build/crypto.m4 @@ -0,0 +1,205 @@ +dnl -------------------------------------------------------- -*- autoconf -*- +dnl Copyright 2006 The Apache Software Foundation or its licensors, as +dnl applicable. +dnl +dnl Licensed under the Apache License, Version 2.0 (the "License"); +dnl you may not use this file except in compliance with the License. +dnl You may obtain a copy of the License at +dnl +dnl http://www.apache.org/licenses/LICENSE-2.0 +dnl +dnl Unless required by applicable law or agreed to in writing, software +dnl distributed under the License is distributed on an "AS IS" BASIS, +dnl WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +dnl See the License for the specific language governing permissions and +dnl limitations under the License. + +dnl +dnl Crypto module +dnl + +dnl +dnl APU_CHECK_CRYPTO: look for crypto libraries and headers +dnl +AC_DEFUN([APU_CHECK_CRYPTO], [ + apu_have_crypto=0 + + old_libs="$LIBS" + old_cppflags="$CPPFLAGS" + old_ldflags="$LDFLAGS" + + AC_ARG_WITH([crypto], [APR_HELP_STRING([--with-crypto], [enable crypto support])], + [ + if test "$withval" = "yes"; then + APU_CHECK_CRYPTO_OPENSSL + APU_CHECK_CRYPTO_NSS + dnl add checks for other varieties of ssl here + fi + ], [ + apu_have_crypto=0 + ]) + + if test "$apu_have_crypto" = "1"; then + AC_DEFINE([APU_HAVE_CRYPTO], 1, [Define that we have crypto capability]) + fi + +]) +dnl + +AC_DEFUN([APU_CHECK_CRYPTO_OPENSSL], [ + apu_have_openssl=0 + openssl_have_headers=0 + openssl_have_libs=0 + + AC_ARG_WITH([openssl], + [APR_HELP_STRING([--with-openssl=DIR], [specify location of OpenSSL])], + [ + if test "$withval" = "yes"; then + AC_CHECK_HEADERS(openssl/x509.h, [openssl_have_headers=1]) + AC_CHECK_LIB(crypto, BN_init, AC_CHECK_LIB(ssl, SSL_accept, [openssl_have_libs=1],,-lcrypto)) + if test "$openssl_have_headers" != "0" && test "$openssl_have_libs" != "0"; then + apu_have_openssl=1 + fi + elif test "$withval" = "no"; then + apu_have_openssl=0 + else + old_cppflags="$CPPFLAGS" + old_ldflags="$LDFLAGS" + + openssl_CPPFLAGS="-I$withval/include" + openssl_LDFLAGS="-L$withval/lib " + + APR_ADDTO(CPPFLAGS, [$openssl_CPPFLAGS]) + APR_ADDTO(LDFLAGS, [$openssl_LDFLAGS]) + + AC_MSG_NOTICE(checking for openssl in $withval) + AC_CHECK_HEADERS(openssl/x509.h, [openssl_have_headers=1]) + AC_CHECK_LIB(crypto, BN_init, AC_CHECK_LIB(ssl, SSL_accept, [openssl_have_libs=1],,-lcrypto)) + if test "$openssl_have_headers" != "0" && test "$openssl_have_libs" != "0"; then + apu_have_openssl=1 + APR_ADDTO(APRUTIL_LDFLAGS, [-L$withval/lib]) + APR_ADDTO(APRUTIL_INCLUDES, [-I$withval/include]) + fi + + if test "$apu_have_openssl" != "1"; then + AC_CHECK_HEADERS(openssl/x509.h, [openssl_have_headers=1]) + AC_CHECK_LIB(crypto, BN_init, AC_CHECK_LIB(ssl, SSL_accept, [openssl_have_libs=1],,-lcrypto)) + if test "$openssl_have_headers" != "0" && test "$openssl_have_libs" != "0"; then + apu_have_openssl=1 + APR_ADDTO(APRUTIL_LDFLAGS, [-L$withval/lib]) + APR_ADDTO(APRUTIL_INCLUDES, [-I$withval/include]) + fi + fi + + AC_CHECK_DECLS([EVP_PKEY_CTX_new], [], [], + [#include <openssl/evp.h>]) + + CPPFLAGS="$old_cppflags" + LDFLAGS="$old_ldflags" + fi + ], [ + apu_have_openssl=0 + ]) + + AC_SUBST(apu_have_openssl) + + dnl Since we have already done the AC_CHECK_LIB tests, if we have it, + dnl we know the library is there. + if test "$apu_have_openssl" = "1"; then + LDADD_crypto_openssl="$openssl_LDFLAGS -lssl -lcrypto" + apu_have_crypto=1 + AC_DEFINE([APU_HAVE_CRYPTO], 1, [Define that we have crypto capability]) + + AC_MSG_CHECKING([for const input buffers in OpenSSL]) + AC_TRY_COMPILE([#include <openssl/rsa.h>], + [ const unsigned char * buf; + unsigned char * outbuf; + RSA rsa; + + RSA_private_decrypt(1, + buf, + outbuf, + &rsa, + RSA_PKCS1_PADDING); + + ], + [AC_MSG_RESULT([yes])] + [AC_DEFINE([CRYPTO_OPENSSL_CONST_BUFFERS], 1, [Define that OpenSSL uses const buffers])], + [AC_MSG_RESULT([no])]) + + fi + AC_SUBST(LDADD_crypto_openssl) + + LIBS="$old_libs" + CPPFLAGS="$old_cppflags" + LDFLAGS="$old_ldflags" +]) + +AC_DEFUN([APU_CHECK_CRYPTO_NSS], [ + apu_have_nss=0 + nss_have_headers=0 + nss_have_libs=0 + + AC_ARG_WITH([nss], + [APR_HELP_STRING([--with-nss=DIR], [specify location of OpenSSL])], + [ + if test "$withval" = "yes"; then + AC_CHECK_HEADERS(nspr/nspr.h nss/nss.h, [nss_have_headers=1]) + AC_CHECK_LIB(nspr4, PR_Initialize, AC_CHECK_LIB(nss3, PK11_CreatePBEV2AlgorithmID, [nss_have_libs=1],,-lnspr4)) + if test "$nss_have_headers" != "0" && test "$nss_have_libs" != "0"; then + apu_have_nss=1 + fi + elif test "$withval" = "no"; then + apu_have_nss=0 + elif test "x$withval" != "x"; then + old_cppflags="$CPPFLAGS" + old_ldflags="$LDFLAGS" + + nss_CPPFLAGS="-I$withval/include -I$withval/../public" + nss_LDFLAGS="-L$withval/lib " + + APR_ADDTO(CPPFLAGS, [$nss_CPPFLAGS]) + APR_ADDTO(LDFLAGS, [$nss_LDFLAGS]) + + AC_MSG_NOTICE(checking for nss in $withval) + AC_CHECK_HEADERS(nspr/nspr.h nss/nss.h, [nss_have_headers=1]) + AC_CHECK_LIB(nspr4, PR_Initialize, AC_CHECK_LIB(nss3, PK11_CreatePBEV2AlgorithmID, [nss_have_libs=1],,-lnspr4)) + if test "$nss_have_headers" != "0" && test "$nss_have_libs" != "0"; then + apu_have_nss=1 + APR_ADDTO(APRUTIL_LDFLAGS, [-L$withval/lib]) + APR_ADDTO(APRUTIL_INCLUDES, [-I$withval/include -I$withval/../public]) + fi + + if test "$apu_have_nss" != "1"; then + AC_CHECK_HEADERS(nspr/nspr.h nss/nss.h, [nss_have_headers=1]) + AC_CHECK_LIB(nspr4, PR_Initialize, AC_CHECK_LIB(nss3, PK11_CreatePBEV2AlgorithmID, [nss_have_libs=1],,-lnspr4)) + if test "$nss_have_headers" != "0" && test "$nss_have_libs" != "0"; then + apu_have_nss=1 + APR_ADDTO(APRUTIL_LDFLAGS, [-L$withval/lib]) + APR_ADDTO(APRUTIL_INCLUDES, [-I$withval/include -I$withval/../public]) + fi + fi + + CPPFLAGS="$old_cppflags" + LDFLAGS="$old_ldflags" + fi + ], [ + apu_have_nss=0 + ]) + + AC_SUBST(apu_have_nss) + + dnl Since we have already done the AC_CHECK_LIB tests, if we have it, + dnl we know the library is there. + if test "$apu_have_nss" = "1"; then + LDADD_crypto_nss="$nss_LDFLAGS -lnspr4 -lnss3" + apu_have_crypto=1 + AC_DEFINE([APU_HAVE_CRYPTO], 1, [Define that we have crypto capability]) + fi + AC_SUBST(LDADD_crypto_nss) + + LIBS="$old_libs" + CPPFLAGS="$old_cppflags" + LDFLAGS="$old_ldflags" +]) +dnl diff --git a/build/dso.m4 b/build/dso.m4 index 8f389150..0b77b7a9 100644 --- a/build/dso.m4 +++ b/build/dso.m4 @@ -22,12 +22,14 @@ AC_DEFUN([APU_CHECK_UTIL_DSO], [ AC_ARG_ENABLE([util-dso], APR_HELP_STRING([--disable-util-dso], - [disable DSO build of modular components (dbd, ldap)])) + [disable DSO build of modular components (crypto, dbd, ldap)])) if test "$enable_util_dso" = "no"; then # Statically link the DBD drivers: objs= + test $apu_have_openssl = 1 && objs="$objs crypto/apr_crypto_openssl.lo" + test $apu_have_nss = 1 && objs="$objs crypto/apr_crypto_nss.lo" test $apu_have_oracle = 1 && objs="$objs dbd/apr_dbd_oracle.lo" test $apu_have_pgsql = 1 && objs="$objs dbd/apr_dbd_pgsql.lo" test $apu_have_mysql = 1 && objs="$objs dbd/apr_dbd_mysql.lo" @@ -54,14 +56,18 @@ AC_DEFUN([APU_CHECK_UTIL_DSO], [ done fi + APRUTIL_LIBS="$APRUTIL_LIBS $LDADD_crypto_openssl $LDADD_crypto_nss" APRUTIL_LIBS="$APRUTIL_LIBS $LDADD_dbd_pgsql $LDADD_dbd_sqlite2 $LDADD_dbd_sqlite3 $LDADD_dbd_oracle $LDADD_dbd_mysql $LDADD_dbd_freetds $LDADD_dbd_odbc" APRUTIL_LIBS="$APRUTIL_LIBS $LDADD_ldap" + APRUTIL_EXPORT_LIBS="$APRUTIL_EXPORT_LIBS $LDADD_crypto_openssl $LDADD_crypto_nss" APRUTIL_EXPORT_LIBS="$APRUTIL_EXPORT_LIBS $LDADD_dbd_pgsql $LDADD_dbd_sqlite2 $LDADD_dbd_sqlite3 $LDADD_dbd_oracle $LDADD_dbd_mysql $LDADD_dbd_freetds $LDADD_dbd_odbc" APRUTIL_EXPORT_LIBS="$APRUTIL_EXPORT_LIBS $LDADD_ldap" else AC_DEFINE([APU_DSO_BUILD], 1, [Define if modular components are built as DSOs]) dsos= + test $apu_have_openssl = 1 && dsos="$dsos crypto/apr_crypto_openssl.la" + test $apu_have_nss = 1 && dsos="$dsos crypto/apr_crypto_nss.la" test $apu_have_oracle = 1 && dsos="$dsos dbd/apr_dbd_oracle.la" test $apu_have_pgsql = 1 && dsos="$dsos dbd/apr_dbd_pgsql.la" test $apu_have_mysql = 1 && dsos="$dsos dbd/apr_dbd_mysql.la" diff --git a/configure.in b/configure.in index d23db5ee..48899b74 100644 --- a/configure.in +++ b/configure.in @@ -13,6 +13,7 @@ sinclude(build/apu-iconv.m4) sinclude(build/apu-hints.m4) sinclude(build/apr_common.m4) sinclude(build/find_apr.m4) +sinclude(build/crypto.m4) sinclude(build/dbm.m4) sinclude(build/dbd.m4) sinclude(build/dso.m4) @@ -149,6 +150,9 @@ dnl Find LDAP library dnl Determine what DBM backend type to use. dnl Find Expat dnl Find an iconv library +APU_CHECK_CRYPTO +APU_CHECK_CRYPTO_OPENSSL +APU_CHECK_CRYPTO_NSS APU_FIND_LDAP APU_CHECK_DBM APU_CHECK_DBD diff --git a/crypto/apr_crypto_nss.c b/crypto/apr_crypto_nss.c new file mode 100644 index 00000000..bab01880 --- /dev/null +++ b/crypto/apr_crypto_nss.c @@ -0,0 +1,766 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +#include "apu.h" + +#include "apu_config.h" +#include "apu_errno.h" + +#include <ctype.h> +#include <stdlib.h> + +#include "apr_strings.h" +#include "apr_time.h" +#include "apr_buckets.h" + +#include "apr_crypto_internal.h" + +#if APU_HAVE_CRYPTO + +#include <prerror.h> +#include <nss/nss.h> +#include <nss/pk11pub.h> + +struct apr_crypto_config_t { +}; + +struct apr_crypto_key_t { + CK_MECHANISM_TYPE cipherMech; + SECOidTag cipherOid; + PK11SymKey *symKey; + int ivSize; +}; + +struct apr_crypto_block_t { + const apr_crypto_t *factory; + apr_pool_t *pool; + PK11Context *ctx; + apr_crypto_key_t *key; + int blockSize; +}; + + +/** + * Shutdown the crypto library and release resources. + * + * It is safe to shut down twice. + */ +static apr_status_t crypto_shutdown(apr_pool_t *pool) +{ + if (NSS_IsInitialized()) { + SECStatus s = NSS_Shutdown(); + if (s != SECSuccess) { + return APR_EINIT; + } + } + return APR_SUCCESS; +} + +static apr_status_t crypto_shutdown_helper(void *data) +{ + apr_pool_t *pool = (apr_pool_t *) data; + return crypto_shutdown(pool); +} + +/** + * Initialise the crypto library and perform one time initialisation. + */ +static apr_status_t crypto_init(apr_pool_t *pool, const apr_array_header_t *params) +{ + SECStatus s; + const char *dir = NULL; + const char *keyPrefix = NULL; + const char *certPrefix = NULL; + const char *secmod = NULL; + PRUint32 flags = 0; + struct apr_crypto_param_t *ents = params ? (struct apr_crypto_param_t *)params->elts : NULL; + int i = 0; + + /* sanity check - we can only initialise NSS once */ + if (NSS_IsInitialized()) { + return APR_EREINIT; + } + + apr_pool_cleanup_register(pool, pool, + crypto_shutdown_helper, + apr_pool_cleanup_null); + + for (i = 0; params && i < params->nelts; i++) { + switch (ents[i].type) { + case APR_CRYPTO_CA_TYPE_DIR: + dir = ents[i].path; + break; + case APR_CRYPTO_CERT_TYPE_KEY3_DB: + keyPrefix = ents[i].path; + break; + case APR_CRYPTO_CA_TYPE_CERT7_DB: + certPrefix = ents[i].path; + break; + case APR_CRYPTO_CA_TYPE_SECMOD: + secmod = ents[i].path; + break; + default: + return APR_EINIT; + } + } + + if (keyPrefix || certPrefix || secmod) { + s = NSS_Initialize(dir, certPrefix, keyPrefix, secmod, flags); + } + else if (dir) { + s = NSS_InitReadWrite(dir); + } + else { + s = NSS_NoDB_Init(NULL); + } + if (s != SECSuccess) { + return APR_ECRYPT; + } + + return APR_SUCCESS; + +} + +/** + * @brief Clean encryption / decryption context. + * @note After cleanup, a context is free to be reused if necessary. + * @param driver - driver to use + * @param ctx The block context to use. + * @return Returns APR_ENOTIMPL if not supported. + */ +static apr_status_t crypto_block_cleanup(apr_crypto_block_t *block) +{ + + if (block->ctx) { + PK11_DestroyContext(block->ctx, PR_TRUE); + block->ctx = NULL; + } + + return APR_SUCCESS; + +} + +static apr_status_t crypto_block_cleanup_helper(void *data) +{ + apr_crypto_block_t *block = (apr_crypto_block_t *) data; + return crypto_block_cleanup(block); +} + +/** + * @brief Clean encryption / decryption factory. + * @note After cleanup, a factory is free to be reused if necessary. + * @param driver - driver to use + * @param f The factory to use. + * @return Returns APR_ENOTIMPL if not supported. + */ +static apr_status_t crypto_cleanup(apr_crypto_t *f) +{ + apr_crypto_key_t *key; + if (f->keys) { + while ((key = apr_array_pop(f->keys))) { + if (key->symKey) { + PK11_FreeSymKey(key->symKey); + key->symKey = NULL; + } + } + } + return APR_SUCCESS; +} + +static apr_status_t crypto_cleanup_helper(void *data) +{ + apr_crypto_t *f = (apr_crypto_t *) data; + return crypto_cleanup(f); +} + +/** + * @brief Create a context for supporting encryption. Keys, certificates, + * algorithms and other parameters will be set per context. More than + * one context can be created at one time. A cleanup will be automatically + * registered with the given pool to guarantee a graceful shutdown. + * @param driver - driver to use + * @param pool - process pool + * @param params - array of key parameters + * @param factory - factory pointer will be written here + * @return APR_ENOENGINE when the engine specified does not exist. APR_EINITENGINE + * if the engine cannot be initialised. + */ +static apr_status_t crypto_factory(apr_pool_t *pool, + const apr_array_header_t *params, + apr_crypto_t **factory) +{ + apr_crypto_config_t *config = NULL; + /* struct apr_crypto_param_t *ents = params ? (struct apr_crypto_param_t *)params->elts : NULL; */ + /* int i = 0; */ + apr_crypto_t *f; + + f = apr_pcalloc(pool, sizeof(apr_crypto_t)); + if (!f) { + return APR_ENOMEM; + } + *factory = f; + f->pool = pool; + config = f->config = apr_pcalloc(pool, sizeof(apr_crypto_config_t)); + if (!config) { + return APR_ENOMEM; + } + f->result = apr_pcalloc(pool, sizeof(apu_err_t)); + if (!f->result) { + return APR_ENOMEM; + } + f->keys = apr_array_make(pool, + 10, sizeof(apr_crypto_key_t)); + + apr_pool_cleanup_register(pool, f, + crypto_cleanup_helper, + apr_pool_cleanup_null); + + /* + for (i = 0; params && i < params->nelts; i++) { + switch (ents[i].type) { + default: + f->result->rc = -1; + f->result->reason = "The NSS module currently supports " + "no per factory initialisation parameters at this time, but " + "may do in future."; + return APR_EINIT; + } + } + */ + + return APR_SUCCESS; + +} + +/** + * @brief Create a key from the given passphrase. By default, the PBKDF2 + * algorithm is used to generate the key from the passphrase. It is expected + * that the same pass phrase will generate the same key, regardless of the + * backend crypto platform used. The key is cleaned up when the context + * is cleaned, and may be reused with multiple encryption or decryption + * operations. + * @note If *key is NULL, a apr_crypto_key_t will be created from a pool. If + * *key is not NULL, *key must point at a previously created structure. + * @param driver - driver to use + * @param p The pool to use. + * @param f The context to use. + * @param pass The passphrase to use. + * @param passLen The passphrase length in bytes + * @param salt The salt to use. + * @param saltLen The salt length in bytes + * @param type 3DES_192, AES_128, AES_192, AES_256. + * @param mode Electronic Code Book / Cipher Block Chaining. + * @param doPad Pad if necessary. + * @param key The key returned, see note. + * @param ivSize The size of the initialisation vector will be returned, based + * on whether an IV is relevant for this type of crypto. + * @return Returns APR_ENOKEY if the pass phrase is missing or empty, or if a backend + * error occurred while generating the key. APR_ENOCIPHER if the type or mode + * is not supported by the particular backend. APR_EKEYTYPE if the key type is + * not known. APR_EPADDING if padding was requested but is not supported. + * APR_ENOTIMPL if not implemented. + */ +static apr_status_t crypto_passphrase(apr_pool_t *p, + const apr_crypto_t *f, + const char *pass, + apr_size_t passLen, + const unsigned char * salt, + apr_size_t saltLen, + const apr_crypto_block_key_type_e type, + const apr_crypto_block_key_mode_e mode, + const int doPad, + const int iterations, + apr_crypto_key_t **k, + apr_size_t *ivSize) +{ + apr_status_t rv = APR_SUCCESS; + PK11SlotInfo * slot; + SECItem passItem; + SECItem saltItem; + SECAlgorithmID *algid; + void *wincx = NULL; /* what is wincx? */ + apr_crypto_key_t *key = *k; + + if (!key) { + *k = key = apr_array_push(f->keys); + } + if (!key) { + return APR_ENOMEM; + } + + /* decide on what cipher mechanism we will be using */ + switch (type) { + + case (KEY_3DES_192) : + if (MODE_CBC == mode) { + key->cipherOid = SEC_OID_DES_EDE3_CBC; + } + else if (MODE_ECB == mode) { + return APR_ENOCIPHER; + /* No OID for CKM_DES3_ECB; */ + } + break; + case (KEY_AES_128) : + if (MODE_CBC == mode) { + key->cipherOid = SEC_OID_AES_128_CBC; + } + else { + key->cipherOid = SEC_OID_AES_128_ECB; + } + break; + case (KEY_AES_192) : + if (MODE_CBC == mode) { + key->cipherOid = SEC_OID_AES_192_CBC; + } + else { + key->cipherOid = SEC_OID_AES_192_ECB; + } + break; + case (KEY_AES_256) : + if (MODE_CBC == mode) { + key->cipherOid = SEC_OID_AES_256_CBC; + } + else { + key->cipherOid = SEC_OID_AES_256_ECB; + } + break; + default: + /* unknown key type, give up */ + return APR_EKEYTYPE; + } + + /* AES_128_CBC --> CKM_AES_CBC --> CKM_AES_CBC_PAD */ + key->cipherMech = PK11_AlgtagToMechanism(key->cipherOid); + if (key->cipherMech == CKM_INVALID_MECHANISM) { + return APR_ENOCIPHER; + } + if (doPad) { + CK_MECHANISM_TYPE paddedMech; + paddedMech = PK11_GetPadMechanism(key->cipherMech); + if (CKM_INVALID_MECHANISM == paddedMech || key->cipherMech == paddedMech) { + return APR_EPADDING; + } + key->cipherMech = paddedMech; + } + + /* Turn the raw passphrase and salt into SECItems */ + passItem.data = (unsigned char*)pass; + passItem.len = passLen; + saltItem.data = (unsigned char*)salt; + saltItem.len = saltLen; + + /* generate the key */ + /* pbeAlg and cipherAlg are the same. NSS decides the keylength. */ + algid = PK11_CreatePBEV2AlgorithmID(key->cipherOid, key->cipherOid, SEC_OID_HMAC_SHA1, 0, iterations, &saltItem); + if (algid) { + slot = PK11_GetBestSlot(key->cipherMech, wincx); + if (slot) { + key->symKey = PK11_PBEKeyGen(slot, algid, &passItem, PR_FALSE, wincx); + PK11_FreeSlot(slot); + } + SECOID_DestroyAlgorithmID(algid, PR_TRUE); + } + + /* sanity check? */ + if (!key->symKey) { + PRErrorCode perr = PORT_GetError(); + if (perr) { + f->result->rc = perr; + f->result->msg = PR_ErrorToName(perr); + rv = APR_ENOKEY; + } + } + + key->ivSize = PK11_GetIVLength(key->cipherMech); + if (ivSize) { + *ivSize = key->ivSize; + } + + return rv; +} + +/** + * @brief Initialise a context for encrypting arbitrary data using the given key. + * @note If *ctx is NULL, a apr_crypto_block_t will be created from a pool. If + * *ctx is not NULL, *ctx must point at a previously created structure. + * @param p The pool to use. + * @param f The block factory to use. + * @param key The key structure. + * @param iv Optional initialisation vector. If the buffer pointed to is NULL, + * an IV will be created at random, in space allocated from the pool. + * If the buffer pointed to is not NULL, the IV in the buffer will be + * used. + * @param ctx The block context returned, see note. + * @param blockSize The block size of the cipher. + * @return Returns APR_ENOIV if an initialisation vector is required but not specified. + * Returns APR_EINIT if the backend failed to initialise the context. Returns + * APR_ENOTIMPL if not implemented. + */ +static apr_status_t crypto_block_encrypt_init(apr_pool_t *p, + const apr_crypto_t *f, + const apr_crypto_key_t *key, + const unsigned char **iv, + apr_crypto_block_t **ctx, + apr_size_t *blockSize) +{ + PRErrorCode perr; + SECItem * secParam; + int usedIvSize; + SECItem ivItem; + unsigned char * usedIv; + apr_crypto_block_t *block = *ctx; + if (!block) { + *ctx = block = apr_pcalloc(p, sizeof(apr_crypto_block_t)); + } + if (!block) { + return APR_ENOMEM; + } + block->factory = f; + block->pool = p; + + apr_pool_cleanup_register(p, block, + crypto_block_cleanup_helper, + apr_pool_cleanup_null); + + if (key->ivSize) { + if (iv == NULL) { + return APR_ENOIV; + } + if (*iv == NULL) { + usedIv = apr_pcalloc(p, key->ivSize); + if (!usedIv) { + return APR_ENOMEM; + } + SECStatus s = PK11_GenerateRandom(usedIv, key->ivSize); + if (s != SECSuccess) { + return APR_ENOIV; + } + *iv = usedIv; + } + else { + usedIv = (unsigned char *)*iv; + } + ivItem.data = usedIv; + ivItem.len = usedIvSize; + secParam = PK11_ParamFromIV(key->cipherMech, &ivItem); + } + else { + secParam = PK11_GenerateNewParam(key->cipherMech, key->symKey); + } + block->blockSize = PK11_GetBlockSize(key->cipherMech, secParam); + block->ctx = PK11_CreateContextBySymKey(key->cipherMech, CKA_ENCRYPT, key->symKey, secParam); + + /* did an error occur? */ + perr = PORT_GetError(); + if (perr || !block->ctx) { + f->result->rc = perr; + f->result->msg = PR_ErrorToName(perr); + return APR_EINIT; + } + + if (blockSize) { + *blockSize = PK11_GetBlockSize(key->cipherMech, secParam); + } + + return APR_SUCCESS; + +} + +/** + * @brief Encrypt data provided by in, write it to out. + * @note The number of bytes written will be written to outlen. If + * out is NULL, outlen will contain the maximum size of the + * buffer needed to hold the data, including any data + * generated by apr_crypto_block_encrypt_finish below. If *out points + * to NULL, a buffer sufficiently large will be created from + * the pool provided. If *out points to a not-NULL value, this + * value will be used as a buffer instead. + * @param ctx The block context to use. + * @param out Address of a buffer to which data will be written, + * see note. + * @param outlen Length of the output will be written here. + * @param in Address of the buffer to read. + * @param inlen Length of the buffer to read. + * @return APR_ECRYPT if an error occurred. Returns APR_ENOTIMPL if + * not implemented. + */ +static apr_status_t crypto_block_encrypt(apr_crypto_block_t *block, + unsigned char **out, + apr_size_t *outlen, + const unsigned char *in, + apr_size_t inlen) +{ + + unsigned char *buffer; + int outl = (int) *outlen; + if (!out) { + *outlen = inlen + block->blockSize; + return APR_SUCCESS; + } + if (!*out) { + buffer = apr_palloc(block->pool, inlen + block->blockSize); + if (!buffer) { + return APR_ENOMEM; + } + *out = buffer; + } + + SECStatus s = PK11_CipherOp(block->ctx, *out, &outl, inlen, (unsigned char*)in, inlen); + if (s != SECSuccess) { + PRErrorCode perr = PORT_GetError(); + if (perr) { + block->factory->result->rc = perr; + block->factory->result->msg = PR_ErrorToName(perr); + } + return APR_ECRYPT; + } + *outlen = outl; + + return APR_SUCCESS; + +} + +/** + * @brief Encrypt final data block, write it to out. + * @note If necessary the final block will be written out after being + * padded. Typically the final block will be written to the + * same buffer used by apr_crypto_block_encrypt, offset by the + * number of bytes returned as actually written by the + * apr_crypto_block_encrypt() call. After this call, the context + * is cleaned and can be reused by apr_crypto_block_encrypt_init(). + * @param ctx The block context to use. + * @param out Address of a buffer to which data will be written. This + * buffer must already exist, and is usually the same + * buffer used by apr_evp_crypt(). See note. + * @param outlen Length of the output will be written here. + * @return APR_ECRYPT if an error occurred. + * @return APR_EPADDING if padding was enabled and the block was incorrectly + * formatted. + * @return APR_ENOTIMPL if not implemented. + */ +static apr_status_t crypto_block_encrypt_finish(apr_crypto_block_t *block, + unsigned char *out, + apr_size_t *outlen) +{ + + apr_status_t rv = APR_SUCCESS; + unsigned int outl = *outlen; + + SECStatus s = PK11_DigestFinal(block->ctx, out, &outl, block->blockSize); + *outlen = outl; + + if (s != SECSuccess) { + PRErrorCode perr = PORT_GetError(); + if (perr) { + block->factory->result->rc = perr; + block->factory->result->msg = PR_ErrorToName(perr); + } + rv = APR_ECRYPT; + } + crypto_block_cleanup(block); + + return rv; + +} + +/** + * @brief Initialise a context for decrypting arbitrary data using the given key. + * @note If *ctx is NULL, a apr_crypto_block_t will be created from a pool. If + * *ctx is not NULL, *ctx must point at a previously created structure. + * @param p The pool to use. + * @param f The block factory to use. + * @param key The key structure. + * @param iv Optional initialisation vector. If the buffer pointed to is NULL, + * an IV will be created at random, in space allocated from the pool. + * If the buffer pointed to is not NULL, the IV in the buffer will be + * used. + * @param ctx The block context returned, see note. + * @param blockSize The block size of the cipher. + * @return Returns APR_ENOIV if an initialisation vector is required but not specified. + * Returns APR_EINIT if the backend failed to initialise the context. Returns + * APR_ENOTIMPL if not implemented. + */ +static apr_status_t crypto_block_decrypt_init(apr_pool_t *p, + const apr_crypto_t *f, + const apr_crypto_key_t *key, + const unsigned char *iv, + apr_crypto_block_t **ctx, + apr_size_t *blockSize) +{ + PRErrorCode perr; + SECItem * secParam; + apr_crypto_block_t *block = *ctx; + if (!block) { + *ctx = block = apr_pcalloc(p, sizeof(apr_crypto_block_t)); + } + if (!block) { + return APR_ENOMEM; + } + block->factory = f; + block->pool = p; + + apr_pool_cleanup_register(p, block, + crypto_block_cleanup_helper, + apr_pool_cleanup_null); + + if (key->ivSize) { + SECItem ivItem; + if (iv == NULL) { + return APR_ENOIV; /* Cannot initialise without an IV */ + } + ivItem.data = (unsigned char*)iv; + ivItem.len = key->ivSize; + secParam = PK11_ParamFromIV(key->cipherMech, &ivItem); + } + else { + secParam = PK11_GenerateNewParam(key->cipherMech, key->symKey); + } + block->blockSize = PK11_GetBlockSize(key->cipherMech, secParam); + block->ctx = PK11_CreateContextBySymKey(key->cipherMech, CKA_DECRYPT, key->symKey, secParam); + + /* did an error occur? */ + perr = PORT_GetError(); + if (perr || !block->ctx) { + f->result->rc = perr; + f->result->msg = PR_ErrorToName(perr); + return APR_EINIT; + } + + if (blockSize) { + *blockSize = PK11_GetBlockSize(key->cipherMech, secParam); + } + + return APR_SUCCESS; + +} + +/** + * @brief Decrypt data provided by in, write it to out. + * @note The number of bytes written will be written to outlen. If + * out is NULL, outlen will contain the maximum size of the + * buffer needed to hold the data, including any data + * generated by apr_crypto_block_final below. If *out points + * to NULL, a buffer sufficiently large will be created from + * the pool provided. If *out points to a not-NULL value, this + * value will be used as a buffer instead. + * @param ctx The block context to use. + * @param out Address of a buffer to which data will be written, + * see note. + * @param outlen Length of the output will be written here. + * @param in Address of the buffer to read. + * @param inlen Length of the buffer to read. + * @return APR_ECRYPT if an error occurred. Returns APR_ENOTIMPL if + * not implemented. + */ +static apr_status_t crypto_block_decrypt(apr_crypto_block_t *block, + unsigned char **out, + apr_size_t *outlen, + const unsigned char *in, + apr_size_t inlen) +{ + + unsigned char *buffer; + int outl = (int) *outlen; + if (!out) { + *outlen = inlen + block->blockSize; + return APR_SUCCESS; + } + if (!*out) { + buffer = apr_palloc(block->pool, inlen + block->blockSize); + if (!buffer) { + return APR_ENOMEM; + } + *out = buffer; + } + + SECStatus s = PK11_CipherOp(block->ctx, *out, &outl, inlen, (unsigned char*)in, inlen); + if (s != SECSuccess) { + PRErrorCode perr = PORT_GetError(); + if (perr) { + block->factory->result->rc = perr; + block->factory->result->msg = PR_ErrorToName(perr); + } + return APR_ECRYPT; + } + *outlen = outl; + + return APR_SUCCESS; + +} + +/** + * @brief Encrypt final data block, write it to out. + * @note If necessary the final block will be written out after being + * padded. Typically the final block will be written to the + * same buffer used by apr_evp_crypt, offset by the number of + * bytes returned as actually written by the apr_evp_crypt() + * call. After this call, the context is cleaned and can be + * reused by apr_env_encrypt_init() or apr_env_decrypt_init(). + * @param ctx The block context to use. + * @param out Address of a buffer to which data will be written. This + * buffer must already exist, and is usually the same + * buffer used by apr_evp_crypt(). See note. + * @param outlen Length of the output will be written here. + * @return APR_ECRYPT if an error occurred. + * @return APR_EPADDING if padding was enabled and the block was incorrectly + * formatted. + * @return APR_ENOTIMPL if not implemented. + */ +static apr_status_t crypto_block_decrypt_finish(apr_crypto_block_t *block, + unsigned char *out, + apr_size_t *outlen) +{ + + apr_status_t rv = APR_SUCCESS; + unsigned int outl = *outlen; + + SECStatus s = PK11_DigestFinal(block->ctx, out, &outl, block->blockSize); + *outlen = outl; + + if (s != SECSuccess) { + PRErrorCode perr = PORT_GetError(); + if (perr) { + block->factory->result->rc = perr; + block->factory->result->msg = PR_ErrorToName(perr); + } + rv = APR_ECRYPT; + } + crypto_block_cleanup(block); + + return rv; + +} + +/** + * OpenSSL module. + */ +APU_MODULE_DECLARE_DATA const apr_crypto_driver_t apr_crypto_nss_driver = { + "nss", + crypto_init, + crypto_factory, + crypto_passphrase, + crypto_block_encrypt_init, + crypto_block_encrypt, + crypto_block_encrypt_finish, + crypto_block_decrypt_init, + crypto_block_decrypt, + crypto_block_decrypt_finish, + crypto_block_cleanup, + crypto_cleanup, + crypto_shutdown +}; + +#endif diff --git a/crypto/apr_crypto_openssl.c b/crypto/apr_crypto_openssl.c new file mode 100644 index 00000000..cc37e470 --- /dev/null +++ b/crypto/apr_crypto_openssl.c @@ -0,0 +1,649 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +#include "apu.h" + +#include "apu_config.h" +#include "apu_errno.h" + +#include <ctype.h> +#include <assert.h> +#include <stdlib.h> + +#include "apr_strings.h" +#include "apr_time.h" +#include "apr_buckets.h" + +#include "apr_crypto_internal.h" + +#if APU_HAVE_CRYPTO + +#include <openssl/evp.h> +#include <openssl/engine.h> + +#define LOG_PREFIX "apr_crypto_openssl: " + +struct apr_crypto_config_t { + ENGINE *engine; +}; + +struct apr_crypto_key_t { + const EVP_CIPHER * cipher; + unsigned char *key; + int keyLen; + int doPad; + int ivSize; +}; + +struct apr_crypto_block_t { + const apr_crypto_t *factory; + apr_pool_t *pool; + EVP_CIPHER_CTX cipherCtx; + int initialised; + int ivSize; + int blockSize; + int doPad; +}; + +/** + * Shutdown the crypto library and release resources. + */ +static apr_status_t crypto_shutdown(apr_pool_t *pool) { + ERR_free_strings(); + EVP_cleanup(); + ENGINE_cleanup(); + return APR_SUCCESS; +} + +static apr_status_t crypto_shutdown_helper(void *data) { + apr_pool_t *pool = (apr_pool_t *) data; + return crypto_shutdown(pool); +} + +/** + * Initialise the crypto library and perform one time initialisation. + */ +static apr_status_t crypto_init(apr_pool_t *pool, + const apr_array_header_t *params) { + CRYPTO_malloc_init(); + ERR_load_crypto_strings(); + /* SSL_load_error_strings(); */ + OpenSSL_add_all_algorithms(); + ENGINE_load_builtin_engines(); + ENGINE_register_all_complete(); + + apr_pool_cleanup_register(pool, pool, crypto_shutdown_helper, + apr_pool_cleanup_null); + + return APR_SUCCESS; +} + +/** + * @brief Clean encryption / decryption context. + * @note After cleanup, a context is free to be reused if necessary. + * @param driver - driver to use + * @param ctx The block context to use. + * @return Returns APR_ENOTIMPL if not supported. + */ +static apr_status_t crypto_block_cleanup(apr_crypto_block_t *ctx) { + + if (ctx->initialised) { + EVP_CIPHER_CTX_cleanup(&ctx->cipherCtx); + ctx->initialised = 0; + } + + return APR_SUCCESS; + +} + +static apr_status_t crypto_block_cleanup_helper(void *data) { + apr_crypto_block_t *block = (apr_crypto_block_t *) data; + return crypto_block_cleanup(block); +} + +/** + * @brief Clean encryption / decryption factory. + * @note After cleanup, a factory is free to be reused if necessary. + * @param driver - driver to use + * @param f The factory to use. + * @return Returns APR_ENOTIMPL if not supported. + */ +static apr_status_t crypto_cleanup(apr_crypto_t *f) { + + if (f->config->engine) { + ENGINE_finish(f->config->engine); + ENGINE_free(f->config->engine); + f->config->engine = NULL; + } + return APR_SUCCESS; + +} + +static apr_status_t crypto_cleanup_helper(void *data) { + apr_crypto_t *f = (apr_crypto_t *) data; + return crypto_cleanup(f); +} + +/** + * @brief Create a context for supporting encryption. Keys, certificates, + * algorithms and other parameters will be set per context. More than + * one context can be created at one time. A cleanup will be automatically + * registered with the given pool to guarantee a graceful shutdown. + * @param driver - driver to use + * @param pool - process pool + * @param params - array of key parameters + * @param factory - factory pointer will be written here + * @return APR_ENOENGINE when the engine specified does not exist. APR_EINITENGINE + * if the engine cannot be initialised. + */ +static apr_status_t crypto_factory(apr_pool_t *pool, + const apr_array_header_t *params, apr_crypto_t **factory) { + apr_crypto_config_t *config = NULL; + struct apr_crypto_param_t *ents = + params ? (struct apr_crypto_param_t *) params->elts : NULL; + int i = 0; + apr_crypto_t *f = apr_pcalloc(pool, sizeof(apr_crypto_t)); + if (!f) { + return APR_ENOMEM; + } + *factory = f; + f->pool = pool; + config = f->config = apr_pcalloc(pool, sizeof(apr_crypto_config_t)); + if (!config) { + return APR_ENOMEM; + } + f->result = apr_pcalloc(pool, sizeof(apu_err_t)); + if (!f->result) { + return APR_ENOMEM; + } + f->keys = apr_array_make(pool, 10, sizeof(apr_crypto_key_t)); + + apr_pool_cleanup_register(pool, f, crypto_cleanup_helper, + apr_pool_cleanup_null); + + for (i = 0; params && i < params->nelts; i++) { + switch (ents[i].type) { + case APR_CRYPTO_ENGINE: + config->engine = ENGINE_by_id(ents[i].path); + if (!config->engine) { + return APR_ENOENGINE; + } + if (!ENGINE_init(config->engine)) { + ENGINE_free(config->engine); + config->engine = NULL; + return APR_EINITENGINE; + } + break; + } + } + + return APR_SUCCESS; + +} + +/** + * @brief Create a key from the given passphrase. By default, the PBKDF2 + * algorithm is used to generate the key from the passphrase. It is expected + * that the same pass phrase will generate the same key, regardless of the + * backend crypto platform used. The key is cleaned up when the context + * is cleaned, and may be reused with multiple encryption or decryption + * operations. + * @note If *key is NULL, a apr_crypto_key_t will be created from a pool. If + * *key is not NULL, *key must point at a previously created structure. + * @param driver - driver to use + * @param p The pool to use. + * @param f The context to use. + * @param pass The passphrase to use. + * @param passLen The passphrase length in bytes + * @param salt The salt to use. + * @param saltLen The salt length in bytes + * @param type 3DES_192, AES_128, AES_192, AES_256. + * @param mode Electronic Code Book / Cipher Block Chaining. + * @param doPad Pad if necessary. + * @param key The key returned, see note. + * @param ivSize The size of the initialisation vector will be returned, based + * on whether an IV is relevant for this type of crypto. + * @return Returns APR_ENOKEY if the pass phrase is missing or empty, or if a backend + * error occurred while generating the key. APR_ENOCIPHER if the type or mode + * is not supported by the particular backend. APR_EKEYTYPE if the key type is + * not known. APR_EPADDING if padding was requested but is not supported. + * APR_ENOTIMPL if not implemented. + */ +static apr_status_t crypto_passphrase(apr_pool_t *p, const apr_crypto_t *f, + const char *pass, apr_size_t passLen, const unsigned char * salt, + apr_size_t saltLen, const apr_crypto_block_key_type_e type, + const apr_crypto_block_key_mode_e mode, const int doPad, + const int iterations, apr_crypto_key_t **k, apr_size_t *ivSize) { + apr_crypto_key_t *key = *k; + + if (!key) { + *k = key = apr_array_push(f->keys); + } + if (!key) { + return APR_ENOMEM; + } + + /* determine the cipher to be used */ + switch (type) { + + case (KEY_3DES_192): + + /* A 3DES key */ + if (mode == MODE_CBC) { + key->cipher = EVP_des_ede3_cbc(); + } else { + key->cipher = EVP_des_ede3_ecb(); + } + break; + + case (KEY_AES_128): + + if (mode == MODE_CBC) { + key->cipher = EVP_aes_128_cbc(); + } else { + key->cipher = EVP_aes_128_ecb(); + } + break; + + case (KEY_AES_192): + + if (mode == MODE_CBC) { + key->cipher = EVP_aes_192_cbc(); + } else { + key->cipher = EVP_aes_192_ecb(); + } + break; + + case (KEY_AES_256): + + if (mode == MODE_CBC) { + key->cipher = EVP_aes_256_cbc(); + } else { + key->cipher = EVP_aes_256_ecb(); + } + break; + + default: + + /* unknown key type, give up */ + return APR_EKEYTYPE; + + } + + /* find the length of the key we need */ + key->keyLen = EVP_CIPHER_key_length(key->cipher); + + /* make space for the key */ + key->key = apr_pcalloc(p, key->keyLen); + if (!key->key) { + return APR_ENOMEM; + } + + /* generate the key */ + if (PKCS5_PBKDF2_HMAC_SHA1(pass, passLen, (unsigned char *) salt, saltLen, + iterations, key->keyLen, key->key) == 0) { + return APR_ENOKEY; + } + + key->doPad = doPad; + + /* note: openssl incorrectly returns non zero IV size values for ECB + * algorithms, so work around this by ignoring the IV size. + */ + if (MODE_ECB != mode) { + key->ivSize = EVP_CIPHER_iv_length(key->cipher); + } + if (ivSize) { + *ivSize = key->ivSize; + } + + return APR_SUCCESS; +} + +/** + * @brief Initialise a context for encrypting arbitrary data using the given key. + * @note If *ctx is NULL, a apr_crypto_block_t will be created from a pool. If + * *ctx is not NULL, *ctx must point at a previously created structure. + * @param p The pool to use. + * @param f The block factory to use. + * @param type 3DES_192, AES_128, AES_192, AES_256. + * @param mode Electronic Code Book / Cipher Block Chaining. + * @param key The key + * @param keyLen The key length in bytes + * @param iv Optional initialisation vector. + * @param doPad Pad if necessary. + * @param ctx The block context returned, see note. + * @param blockSize The block size of the cipher. + * @return Returns APR_ENOIV if an initialisation vector is required but not specified. + * Returns APR_EINIT if the backend failed to initialise the context. Returns + * APR_ENOTIMPL if not implemented. + */ +static apr_status_t crypto_block_encrypt_init(apr_pool_t *p, + const apr_crypto_t *f, const apr_crypto_key_t *key, + const unsigned char **iv, apr_crypto_block_t **ctx, + apr_size_t *blockSize) { + unsigned char *usedIv; + apr_crypto_config_t *config = f->config; + apr_crypto_block_t *block = *ctx; + if (!block) { + *ctx = block = apr_pcalloc(p, sizeof(apr_crypto_block_t)); + } + if (!block) { + return APR_ENOMEM; + } + block->factory = f; + block->pool = p; + + apr_pool_cleanup_register(p, block, crypto_block_cleanup_helper, + apr_pool_cleanup_null); + + /* create a new context for encryption */ + EVP_CIPHER_CTX_init(&block->cipherCtx); + block->initialised = 1; + + /* generate an IV, if necessary */ + usedIv = NULL; + if (key->ivSize) { + if (iv == NULL) { + return APR_ENOIV; + } + if (*iv == NULL) { + usedIv = apr_pcalloc(p, key->ivSize); + if (!usedIv) { + return APR_ENOMEM; + } + if (!((RAND_status() == 1) + && (RAND_bytes(usedIv, key->ivSize) == 1))) { + return APR_ENOIV; + } + *iv = usedIv; + } else { + usedIv = (unsigned char *) *iv; + } + } + + /* set up our encryption context */ +#if CRYPTO_OPENSSL_CONST_BUFFERS + if (!EVP_EncryptInit_ex(&block->cipherCtx, key->cipher, config->engine, + key->key, usedIv)) { +#else + if (!EVP_EncryptInit_ex(&block->cipherCtx, key->cipher, config->engine, (unsigned char *) key->key, (unsigned char *) usedIv)) { +#endif + return APR_EINIT; + } + + /* Clear up any read padding */ + if (!EVP_CIPHER_CTX_set_padding(&block->cipherCtx, key->doPad)) { + return APR_EPADDING; + } + + if (blockSize) { + *blockSize = EVP_CIPHER_block_size(key->cipher); + } + + return APR_SUCCESS; + +} + +/** + * @brief Encrypt data provided by in, write it to out. + * @note The number of bytes written will be written to outlen. If + * out is NULL, outlen will contain the maximum size of the + * buffer needed to hold the data, including any data + * generated by apr_crypto_block_encrypt_finish below. If *out points + * to NULL, a buffer sufficiently large will be created from + * the pool provided. If *out points to a not-NULL value, this + * value will be used as a buffer instead. + * @param ctx The block context to use. + * @param out Address of a buffer to which data will be written, + * see note. + * @param outlen Length of the output will be written here. + * @param in Address of the buffer to read. + * @param inlen Length of the buffer to read. + * @return APR_ECRYPT if an error occurred. Returns APR_ENOTIMPL if + * not implemented. + */ +static apr_status_t crypto_block_encrypt(apr_crypto_block_t *ctx, + unsigned char **out, apr_size_t *outlen, const unsigned char *in, + apr_size_t inlen) { + int outl = *outlen; + unsigned char *buffer; + + /* are we after the maximum size of the out buffer? */ + if (!out) { + *outlen = inlen + EVP_MAX_BLOCK_LENGTH; + return APR_SUCCESS; + } + + /* must we allocate the output buffer from a pool? */ + if (!*out) { + buffer = apr_palloc(ctx->pool, inlen + EVP_MAX_BLOCK_LENGTH); + if (!buffer) { + return APR_ENOMEM; + } + *out = buffer; + } + +#if CRYPT_OPENSSL_CONST_BUFFERS + if (!EVP_EncryptUpdate(&ctx->cipherCtx, (*out), &outl, in, inlen)) { +#else + if (!EVP_EncryptUpdate(&ctx->cipherCtx, (*out), &outl, + (unsigned char *) in, inlen)) { +#endif + return APR_ECRYPT; + } + *outlen = outl; + + return APR_SUCCESS; + +} + +/** + * @brief Encrypt final data block, write it to out. + * @note If necessary the final block will be written out after being + * padded. Typically the final block will be written to the + * same buffer used by apr_crypto_block_encrypt, offset by the + * number of bytes returned as actually written by the + * apr_crypto_block_encrypt() call. After this call, the context + * is cleaned and can be reused by apr_crypto_block_encrypt_init(). + * @param ctx The block context to use. + * @param out Address of a buffer to which data will be written. This + * buffer must already exist, and is usually the same + * buffer used by apr_evp_crypt(). See note. + * @param outlen Length of the output will be written here. + * @return APR_ECRYPT if an error occurred. + * @return APR_EPADDING if padding was enabled and the block was incorrectly + * formatted. + * @return APR_ENOTIMPL if not implemented. + */ +static apr_status_t crypto_block_encrypt_finish(apr_crypto_block_t *ctx, + unsigned char *out, apr_size_t *outlen) { + int len = *outlen; + + if (EVP_EncryptFinal_ex(&ctx->cipherCtx, out, &len) == 0) { + return APR_EPADDING; + } + *outlen = len; + + return APR_SUCCESS; + +} + +/** + * @brief Initialise a context for decrypting arbitrary data using the given key. + * @note If *ctx is NULL, a apr_crypto_block_t will be created from a pool. If + * *ctx is not NULL, *ctx must point at a previously created structure. + * @param p The pool to use. + * @param f The block factory to use. + * @param key The key structure. + * @param iv Optional initialisation vector. If the buffer pointed to is NULL, + * an IV will be created at random, in space allocated from the pool. + * If the buffer is not NULL, the IV in the buffer will be used. + * @param ctx The block context returned, see note. + * @param blockSize The block size of the cipher. + * @return Returns APR_ENOIV if an initialisation vector is required but not specified. + * Returns APR_EINIT if the backend failed to initialise the context. Returns + * APR_ENOTIMPL if not implemented. + */ +static apr_status_t crypto_block_decrypt_init(apr_pool_t *p, + const apr_crypto_t *f, const apr_crypto_key_t *key, + const unsigned char *iv, apr_crypto_block_t **ctx, + apr_size_t *blockSize) { + apr_crypto_config_t *config = f->config; + apr_crypto_block_t *block = *ctx; + if (!block) { + *ctx = block = apr_pcalloc(p, sizeof(apr_crypto_block_t)); + } + if (!block) { + return APR_ENOMEM; + } + block->factory = f; + block->pool = p; + + apr_pool_cleanup_register(p, block, crypto_block_cleanup_helper, + apr_pool_cleanup_null); + + /* create a new context for encryption */ + EVP_CIPHER_CTX_init(&block->cipherCtx); + block->initialised = 1; + + /* generate an IV, if necessary */ + if (key->ivSize) { + if (iv == NULL) { + return APR_ENOIV; + } + } + + /* set up our encryption context */ +#if CRYPTO_OPENSSL_CONST_BUFFERS + if (!EVP_DecryptInit_ex(&block->cipherCtx, key->cipher, config->engine, + key->key, iv)) { +#else + if (!EVP_DecryptInit_ex(&block->cipherCtx, key->cipher, config->engine, (unsigned char *) key->key, (unsigned char *) iv)) { +#endif + return APR_EINIT; + } + + /* Clear up any read padding */ + if (!EVP_CIPHER_CTX_set_padding(&block->cipherCtx, key->doPad)) { + return APR_EPADDING; + } + + if (blockSize) { + *blockSize = EVP_CIPHER_block_size(key->cipher); + } + + return APR_SUCCESS; + +} + +/** + * @brief Decrypt data provided by in, write it to out. + * @note The number of bytes written will be written to outlen. If + * out is NULL, outlen will contain the maximum size of the + * buffer needed to hold the data, including any data + * generated by apr_crypto_block_final below. If *out points + * to NULL, a buffer sufficiently large will be created from + * the pool provided. If *out points to a not-NULL value, this + * value will be used as a buffer instead. + * @param ctx The block context to use. + * @param out Address of a buffer to which data will be written, + * see note. + * @param outlen Length of the output will be written here. + * @param in Address of the buffer to read. + * @param inlen Length of the buffer to read. + * @return APR_ECRYPT if an error occurred. Returns APR_ENOTIMPL if + * not implemented. + */ +static apr_status_t crypto_block_decrypt(apr_crypto_block_t *ctx, + unsigned char **out, apr_size_t *outlen, const unsigned char *in, + apr_size_t inlen) { + + int outl = *outlen; + unsigned char *buffer; + + /* are we after the maximum size of the out buffer? */ + if (!out) { + *outlen = inlen + EVP_MAX_BLOCK_LENGTH; + return APR_SUCCESS; + } + + /* must we allocate the output buffer from a pool? */ + if (!(*out)) { + buffer = apr_palloc(ctx->pool, inlen + EVP_MAX_BLOCK_LENGTH); + if (!buffer) { + return APR_ENOMEM; + } + *out = buffer; + } + +#if CRYPT_OPENSSL_CONST_BUFFERS + if (!EVP_DecryptUpdate(&ctx->cipherCtx, *out, &outl, in, inlen)) { +#else + if (!EVP_DecryptUpdate(&ctx->cipherCtx, *out, &outl, (unsigned char *) in, + inlen)) { +#endif + return APR_ECRYPT; + } + *outlen = outl; + + return APR_SUCCESS; + +} + +/** + * @brief Decrypt final data block, write it to out. + * @note If necessary the final block will be written out after being + * padded. Typically the final block will be written to the + * same buffer used by apr_evp_crypt, offset by the number of + * bytes returned as actually written by the apr_evp_crypt() + * call. After this call, the context is cleaned and can be + * reused by apr_env_encrypt_init() or apr_env_decrypt_init(). + * @param ctx The block context to use. + * @param out Address of a buffer to which data will be written. This + * buffer must already exist, and is usually the same + * buffer used by apr_evp_crypt(). See note. + * @param outlen Length of the output will be written here. + * @return APR_ECRYPT if an error occurred. + * @return APR_EPADDING if padding was enabled and the block was incorrectly + * formatted. + * @return APR_ENOTIMPL if not implemented. + */ +static apr_status_t crypto_block_decrypt_finish(apr_crypto_block_t *ctx, + unsigned char *out, apr_size_t *outlen) { + + int len = *outlen; + + if (EVP_DecryptFinal_ex(&ctx->cipherCtx, out, &len) == 0) { + return APR_EPADDING; + } + *outlen = len; + + return APR_SUCCESS; + +} + +/** + * OpenSSL module. + */ +APU_MODULE_DECLARE_DATA const apr_crypto_driver_t apr_crypto_openssl_driver = { + "openssl", crypto_init, crypto_factory, crypto_passphrase, + crypto_block_encrypt_init, crypto_block_encrypt, + crypto_block_encrypt_finish, crypto_block_decrypt_init, + crypto_block_decrypt, crypto_block_decrypt_finish, + crypto_block_cleanup, crypto_cleanup, crypto_shutdown }; + +#endif diff --git a/test/Makefile.in b/test/Makefile.in index 9b6890a8..beb06d44 100644 --- a/test/Makefile.in +++ b/test/Makefile.in @@ -17,7 +17,7 @@ STDTEST_PORTABLE = testall dbd TESTS = teststrmatch.lo testuri.lo testuuid.lo testbuckets.lo testpass.lo \ testmd4.lo testmd5.lo testldap.lo testdate.lo testdbm.lo testdbd.lo \ testxml.lo testrmm.lo testreslist.lo testqueue.lo testxlate.lo \ - testmemcache.lo + testmemcache.lo testcrypto.lo PROGRAMS = $(STDTEST_PORTABLE) @@ -62,7 +62,7 @@ check: $(TESTALL_COMPONENTS) $(STDTEST_PORTABLE) $(STDTEST_NONPORTABLE) for prog in $(STDTEST_PORTABLE) $(STDTEST_NONPORTABLE); do \ if test "$$prog" = 'dbd'; then \ for driver in sqlite2 sqlite3; do \ - @apr_shlibpath_var@="`echo "../dbd/.libs:../ldap/.libs:$$@apr_shlibpath_var@" | sed -e 's/::*$$//'`" \ + @apr_shlibpath_var@="`echo "../crypto/.libs:../dbd/.libs:../ldap/.libs:$$@apr_shlibpath_var@" | sed -e 's/::*$$//'`" \ ./$$prog $$driver; \ status=$$?; \ if test $$status != 0; then \ @@ -71,7 +71,7 @@ check: $(TESTALL_COMPONENTS) $(STDTEST_PORTABLE) $(STDTEST_NONPORTABLE) fi; \ done; \ else \ - @apr_shlibpath_var@="`echo "../dbd/.libs:../ldap/.libs:$$@apr_shlibpath_var@" | sed -e 's/::*$$//'`" \ + @apr_shlibpath_var@="`echo "../crypto/.libs:../dbd/.libs:../ldap/.libs:$$@apr_shlibpath_var@" | sed -e 's/::*$$//'`" \ ./$$prog; \ status=$$?; \ if test $$status != 0; then \ diff --git a/test/abts_tests.h b/test/abts_tests.h index 040d02db..b612d31b 100644 --- a/test/abts_tests.h +++ b/test/abts_tests.h @@ -30,6 +30,7 @@ const struct testlist { {testpass}, {testmd4}, {testmd5}, + {testcrypto}, {testldap}, {testdbd}, {testdate}, diff --git a/test/testcrypto.c b/test/testcrypto.c new file mode 100644 index 00000000..8528dae2 --- /dev/null +++ b/test/testcrypto.c @@ -0,0 +1,698 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +#include "testutil.h" +#include "apr.h" +#include "apu.h" +#include "apu_errno.h" +#include "apr_pools.h" +#include "apr_dso.h" +#include "apr_crypto.h" +#include "apr_strings.h" + +#if APU_HAVE_CRYPTO + +#define TEST_STRING "12345" +#define ALIGNED_STRING "123456789012345" + +static const apr_crypto_driver_t *get_driver(abts_case *tc, apr_pool_t *pool, + const char *name, const apr_array_header_t *params) { + + const apr_crypto_driver_t *driver = NULL; + const apu_err_t *err = NULL; + apr_status_t rv; + + rv = apr_crypto_init(pool, params); + ABTS_ASSERT(tc, "failed to init apr_crypto", rv == APR_SUCCESS); + + rv = apr_crypto_get_driver(pool, name, &driver, params, &err); + if (APR_SUCCESS != rv && err) { + ABTS_NOT_IMPL(tc, err->msg); + return NULL; + } + if (APR_ENOTIMPL == rv) { + ABTS_NOT_IMPL(tc, (char *)driver); + return NULL; + } + ABTS_ASSERT(tc, "failed to apr_crypto_get_driver", rv == APR_SUCCESS); + ABTS_ASSERT(tc, "apr_crypto_get_driver returned NULL", driver != NULL); + if (!driver || rv) { + return NULL; + } + + return driver; + +} + +static const apr_crypto_driver_t *get_nss_driver(abts_case *tc, + apr_pool_t *pool) { + + apr_array_header_t *params; + apr_crypto_param_t *param; + + /* initialise NSS */ + params = apr_array_make(pool, 10, sizeof(apr_crypto_param_t)); + param = apr_array_push(params); + param->type = APR_CRYPTO_CA_TYPE_DIR; + param->path = "data"; + return get_driver(tc, pool, "nss", params); + +} + +static const apr_crypto_driver_t *get_openssl_driver(abts_case *tc, + apr_pool_t *pool) { + + return get_driver(tc, pool, "openssl", NULL); + +} + +static apr_crypto_t *factory(abts_case *tc, apr_pool_t *pool, + const apr_crypto_driver_t *driver) { + + apr_crypto_t *f = NULL; + + if (!driver) { + return NULL; + } + + /* get the factory */ + apr_crypto_factory(driver, pool, NULL, &f); + ABTS_ASSERT(tc, "apr_crypto_factory returned NULL", f != NULL); + + return f; + +} + +static const apr_crypto_key_t *passphrase(abts_case *tc, apr_pool_t *pool, + const apr_crypto_driver_t *driver, const apr_crypto_t *f, + apr_crypto_block_key_type_e type, apr_crypto_block_key_mode_e mode, + int doPad, const char *description) { + + apr_crypto_key_t *key = NULL; + const char *pass = "secret"; + const char *salt = "salt"; + apr_status_t rv; + + if (!driver || !f) { + return NULL; + } + + /* init the passphrase */ + rv = apr_crypto_passphrase(driver, pool, f, pass, strlen(pass), + (unsigned char *) salt, strlen(salt), type, mode, doPad, 4096, + &key, NULL); + if (APR_ENOCIPHER == rv) { + ABTS_NOT_IMPL(tc, apr_psprintf(pool, "skipped: %s %s passphrase return APR_ENOCIPHER: error %d: %s (%s)\n", description, apr_crypto_driver_name(driver), f->result->rc, f->result->reason ? f->result->reason : "", f->result->msg ? f->result->msg : "")); + return NULL; + } else { + if (APR_SUCCESS != rv) { + fprintf(stderr, "passphrase: %s %s native error %d: %s (%s)\n", + description, apr_crypto_driver_name(driver), f->result->rc, + f->result->reason ? f->result->reason : "", + f->result->msg ? f->result->msg : ""); + } + ABTS_ASSERT(tc, "apr_crypto_passphrase returned APR_ENOKEY", rv != APR_ENOKEY); + ABTS_ASSERT(tc, "apr_crypto_passphrase returned APR_EPADDING", rv != APR_EPADDING); + ABTS_ASSERT(tc, "apr_crypto_passphrase returned APR_EKEYTYPE", rv != APR_EKEYTYPE); + ABTS_ASSERT(tc, "failed to apr_crypto_passphrase", rv == APR_SUCCESS); + ABTS_ASSERT(tc, "apr_crypto_passphrase returned NULL context", key != NULL); + } + if (rv) { + return NULL; + } + return key; + +} + +static unsigned char *encrypt_block(abts_case *tc, apr_pool_t *pool, + const apr_crypto_driver_t *driver, const apr_crypto_t *f, + const apr_crypto_key_t *key, const unsigned char *in, + const apr_size_t inlen, unsigned char **cipherText, + apr_size_t *cipherTextLen, const unsigned char **iv, + apr_size_t *blockSize, const char *description) { + + apr_crypto_block_t *block = NULL; + apr_size_t len = 0; + apr_status_t rv; + + if (!driver || !f || !key || !in) { + return NULL; + } + + /* init the encryption */ + rv = apr_crypto_block_encrypt_init(driver, pool, f, key, iv, &block, + blockSize); + if (APR_ENOTIMPL == rv) { + ABTS_NOT_IMPL(tc, "apr_crypto_block_encrypt_init returned APR_ENOTIMPL"); + } else { + if (APR_SUCCESS != rv) { + fprintf(stderr, "encrypt_init: %s %s native error %d: %s (%s)\n", + description, apr_crypto_driver_name(driver), f->result->rc, + f->result->reason ? f->result->reason : "", + f->result->msg ? f->result->msg : ""); + } + ABTS_ASSERT(tc, "apr_crypto_block_encrypt_init returned APR_ENOKEY", rv != APR_ENOKEY); + ABTS_ASSERT(tc, "apr_crypto_block_encrypt_init returned APR_ENOIV", rv != APR_ENOIV); + ABTS_ASSERT(tc, "apr_crypto_block_encrypt_init returned APR_EKEYTYPE", rv != APR_EKEYTYPE); + ABTS_ASSERT(tc, "apr_crypto_block_encrypt_init returned APR_EKEYLENGTH", rv != APR_EKEYLENGTH); + ABTS_ASSERT(tc, "failed to apr_crypto_block_encrypt_init", rv == APR_SUCCESS); + ABTS_ASSERT(tc, "apr_crypto_block_encrypt_init returned NULL context", block != NULL); + } + if (!block || rv) { + return NULL; + } + + /* encrypt the block */ + rv = apr_crypto_block_encrypt(driver, block, cipherText + len, + cipherTextLen, in, inlen); + if (APR_SUCCESS != rv) { + fprintf(stderr, "encrypt: %s %s native error %d: %s (%s)\n", + description, apr_crypto_driver_name(driver), f->result->rc, + f->result->reason ? f->result->reason : "", + f->result->msg ? f->result->msg : ""); + } + ABTS_ASSERT(tc, "apr_crypto_block_encrypt returned APR_ECRYPT", rv != APR_ECRYPT); + ABTS_ASSERT(tc, "failed to apr_crypto_block_encrypt", rv == APR_SUCCESS); + ABTS_ASSERT(tc, "apr_crypto_block_encrypt failed to allocate buffer", *cipherText != NULL); + if (rv) { + return NULL; + } + + /* finalise the encryption */ + rv = apr_crypto_block_encrypt_finish(driver, block, *cipherText + + *cipherTextLen, &len); + if (APR_SUCCESS != rv) { + fprintf(stderr, "encrypt_finish: %s %s native error %d: %s (%s)\n", + description, apr_crypto_driver_name(driver), f->result->rc, + f->result->reason ? f->result->reason : "", + f->result->msg ? f->result->msg : ""); + } + ABTS_ASSERT(tc, "apr_crypto_block_encrypt_finish returned APR_ECRYPT", rv != APR_ECRYPT); + ABTS_ASSERT(tc, "apr_crypto_block_encrypt_finish returned APR_EPADDING", rv != APR_EPADDING); + ABTS_ASSERT(tc, "failed to apr_crypto_block_encrypt_finish", rv == APR_SUCCESS); + *cipherTextLen += len; + apr_crypto_block_cleanup(driver, block); + if (rv) { + return NULL; + } + + return *cipherText; + +} + +static unsigned char *decrypt_block(abts_case *tc, apr_pool_t *pool, + const apr_crypto_driver_t *driver, const apr_crypto_t *f, + const apr_crypto_key_t *key, unsigned char *cipherText, + apr_size_t cipherTextLen, unsigned char **plainText, + apr_size_t *plainTextLen, const unsigned char *iv, + apr_size_t *blockSize, const char *description) { + + apr_crypto_block_t *block = NULL; + apr_size_t len = 0; + apr_status_t rv; + + if (!driver || !f || !key || !cipherText) { + return NULL; + } + + /* init the decryption */ + rv = apr_crypto_block_decrypt_init(driver, pool, f, key, iv, &block, + blockSize); + if (APR_ENOTIMPL == rv) { + ABTS_NOT_IMPL(tc, "apr_crypto_block_decrypt_init returned APR_ENOTIMPL"); + } else { + if (APR_SUCCESS != rv) { + fprintf(stderr, "decrypt_init: %s %s native error %d: %s (%s)\n", + description, apr_crypto_driver_name(driver), f->result->rc, + f->result->reason ? f->result->reason : "", + f->result->msg ? f->result->msg : ""); + } + ABTS_ASSERT(tc, "apr_crypto_block_decrypt_init returned APR_ENOKEY", rv != APR_ENOKEY); + ABTS_ASSERT(tc, "apr_crypto_block_decrypt_init returned APR_ENOIV", rv != APR_ENOIV); + ABTS_ASSERT(tc, "apr_crypto_block_decrypt_init returned APR_EKEYTYPE", rv != APR_EKEYTYPE); + ABTS_ASSERT(tc, "apr_crypto_block_decrypt_init returned APR_EKEYLENGTH", rv != APR_EKEYLENGTH); + ABTS_ASSERT(tc, "failed to apr_crypto_block_decrypt_init", rv == APR_SUCCESS); + ABTS_ASSERT(tc, "apr_crypto_block_decrypt_init returned NULL context", block != NULL); + } + if (!block || rv) { + return NULL; + } + + /* decrypt the block */ + rv = apr_crypto_block_decrypt(driver, block, plainText, plainTextLen, + cipherText, cipherTextLen); + if (APR_SUCCESS != rv) { + fprintf(stderr, "decrypt: %s %s native error %d: %s (%s)\n", + description, apr_crypto_driver_name(driver), f->result->rc, + f->result->reason ? f->result->reason : "", + f->result->msg ? f->result->msg : ""); + } + ABTS_ASSERT(tc, "apr_crypto_block_decrypt returned APR_ECRYPT", rv != APR_ECRYPT); + ABTS_ASSERT(tc, "failed to apr_crypto_block_decrypt", rv == APR_SUCCESS); + ABTS_ASSERT(tc, "apr_crypto_block_decrypt failed to allocate buffer", *plainText != NULL); + if (rv) { + return NULL; + } + + /* finalise the decryption */ + rv = apr_crypto_block_decrypt_finish(driver, block, *plainText + + *plainTextLen, &len); + if (APR_SUCCESS != rv) { + fprintf(stderr, "decrypt_finish: %s %s native error %d: %s (%s)\n", + description, apr_crypto_driver_name(driver), f->result->rc, + f->result->reason ? f->result->reason : "", + f->result->msg ? f->result->msg : ""); + } + ABTS_ASSERT(tc, "apr_crypto_block_decrypt_finish returned APR_ECRYPT", rv != APR_ECRYPT); + ABTS_ASSERT(tc, "apr_crypto_block_decrypt_finish returned APR_EPADDING", rv != APR_EPADDING); + ABTS_ASSERT(tc, "failed to apr_crypto_block_decrypt_finish", rv == APR_SUCCESS); + if (rv) { + return NULL; + } + + *plainTextLen += len; + apr_crypto_block_cleanup(driver, block); + + return *plainText; + +} + +/** + * Interoperability test. + * + * data must point at an array of two driver structures. Data will be encrypted + * with the first driver, and decrypted with the second. + * + * If the two drivers interoperate, the test passes. + */ +static void crypto_block_cross(abts_case *tc, apr_pool_t *pool, + const apr_crypto_driver_t **drivers, + const apr_crypto_block_key_type_e type, + const apr_crypto_block_key_mode_e mode, int doPad, + const unsigned char *in, apr_size_t inlen, const char *description) { + const apr_crypto_driver_t *driver1 = drivers[0]; + const apr_crypto_driver_t *driver2 = drivers[1]; + apr_crypto_t *f1 = NULL; + apr_crypto_t *f2 = NULL; + const apr_crypto_key_t *key1 = NULL; + const apr_crypto_key_t *key2 = NULL; + + unsigned char *cipherText = NULL; + apr_size_t cipherTextLen = 0; + unsigned char *plainText = NULL; + apr_size_t plainTextLen = 0; + const unsigned char *iv = NULL; + apr_size_t blockSize = 0; + + f1 = factory(tc, pool, driver1); + f2 = factory(tc, pool, driver2); + key1 = passphrase(tc, pool, driver1, f1, type, mode, doPad, description); + key2 = passphrase(tc, pool, driver2, f2, type, mode, doPad, description); + + cipherText = encrypt_block(tc, pool, driver1, f1, key1, in, inlen, + &cipherText, &cipherTextLen, &iv, &blockSize, description); + plainText = decrypt_block(tc, pool, driver2, f2, key2, cipherText, + cipherTextLen, &plainText, &plainTextLen, iv, &blockSize, + description); + + if (cipherText && plainText) { + if (memcmp(in, plainText, inlen)) { + fprintf(stderr, "cross mismatch: %s %s/%s\n", description, + apr_crypto_driver_name(driver1), apr_crypto_driver_name( + driver2)); + } + ABTS_STR_EQUAL(tc, (char *)in, (char *)plainText); + } + +} + +/** + * Test initialisation. + */ +static void test_crypto_init(abts_case *tc, void *data) { + apr_pool_t *pool = NULL; + apr_status_t rv; + + apr_pool_create(&pool, NULL); + + rv = apr_crypto_init(pool, NULL); + ABTS_ASSERT(tc, "failed to init apr_crypto", rv == APR_SUCCESS); + + apr_pool_destroy(pool); + +} + +/** + * Simple test of OpenSSL block crypt. + */ +static void test_crypto_block_openssl(abts_case *tc, void *data) { + apr_pool_t *pool = NULL; + const apr_crypto_driver_t *drivers[] = { NULL, NULL }; + + const unsigned char *in = (const unsigned char *) ALIGNED_STRING; + apr_size_t inlen = sizeof(ALIGNED_STRING); + + apr_pool_create(&pool, NULL); + drivers[0] = get_openssl_driver(tc, pool); + drivers[1] = get_openssl_driver(tc, pool); + crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_CBC, 0, in, inlen, + "KEY_3DES_192/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_ECB, 0, in, inlen, + "KEY_3DES_192/MODE_ECB"); + crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_CBC, 0, in, inlen, + "KEY_AES_256/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_ECB, 0, in, inlen, + "KEY_AES_256/MODE_ECB"); + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_CBC, 0, in, inlen, + "KEY_AES_192/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_ECB, 0, in, inlen, + "KEY_AES_192/MODE_ECB"); + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_CBC, 0, in, inlen, + "KEY_AES_128/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_ECB, 0, in, inlen, + "KEY_AES_128/MODE_ECB"); + apr_pool_destroy(pool); + +} + +/** + * Simple test of NSS block crypt. + */ +static void test_crypto_block_nss(abts_case *tc, void *data) { + apr_pool_t *pool = NULL; + const apr_crypto_driver_t *drivers[] = { NULL, NULL }; + + const unsigned char *in = (const unsigned char *) ALIGNED_STRING; + apr_size_t inlen = sizeof(ALIGNED_STRING); + + apr_pool_create(&pool, NULL); + drivers[0] = get_nss_driver(tc, pool); + drivers[1] = get_nss_driver(tc, pool); + crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_CBC, 0, in, inlen, + "KEY_3DES_192/MODE_CBC"); + /* KEY_3DES_192 / MODE_ECB doesn't work on NSS */ + /* crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_ECB, 0, in, inlen, "KEY_3DES_192/MODE_ECB"); */ + crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_CBC, 0, in, inlen, + "KEY_AES_256/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_ECB, 0, in, inlen, + "KEY_AES_256/MODE_ECB"); + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_CBC, 0, in, inlen, + "KEY_AES_192/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_ECB, 0, in, inlen, + "KEY_AES_192/MODE_ECB"); + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_CBC, 0, in, inlen, + "KEY_AES_128/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_ECB, 0, in, inlen, + "KEY_AES_128/MODE_ECB"); + apr_pool_destroy(pool); + +} + +/** + * Encrypt NSS, decrypt OpenSSL. + */ +static void test_crypto_block_nss_openssl(abts_case *tc, void *data) { + apr_pool_t *pool = NULL; + const apr_crypto_driver_t *drivers[] = { NULL, NULL }; + + const unsigned char *in = (const unsigned char *) ALIGNED_STRING; + apr_size_t inlen = sizeof(ALIGNED_STRING); + + apr_pool_create(&pool, NULL); + drivers[0] = get_nss_driver(tc, pool); + drivers[1] = get_openssl_driver(tc, pool); + + crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_CBC, 0, in, inlen, + "KEY_3DES_192/MODE_CBC"); + + /* KEY_3DES_192 / MODE_ECB doesn't work on NSS */ + /* crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_ECB, 0, in, inlen, "KEY_3DES_192/MODE_ECB"); */ + crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_CBC, 0, in, inlen, + "KEY_AES_256/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_ECB, 0, in, inlen, + "KEY_AES_256/MODE_ECB"); + + /* all 4 of these tests fail to interoperate - a clue from the xml-security code is that + * NSS cannot distinguish between the 128 and 192 bit versions of AES. Will need to be + * investigated. + */ + /* + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_CBC, 0, in, inlen, "KEY_AES_192/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_ECB, 0, in, inlen, "KEY_AES_192/MODE_ECB"); + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_CBC, 0, in, inlen, "KEY_AES_128/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_ECB, 0, in, inlen, "KEY_AES_128/MODE_ECB"); + */ + apr_pool_destroy(pool); + +} + +/** + * Encrypt OpenSSL, decrypt NSS. + */ +static void test_crypto_block_openssl_nss(abts_case *tc, void *data) { + apr_pool_t *pool = NULL; + const apr_crypto_driver_t *drivers[] = { NULL, NULL }; + + const unsigned char *in = (const unsigned char *) ALIGNED_STRING; + apr_size_t inlen = sizeof(ALIGNED_STRING); + + apr_pool_create(&pool, NULL); + drivers[0] = get_openssl_driver(tc, pool); + drivers[1] = get_nss_driver(tc, pool); + crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_CBC, 0, in, inlen, + "KEY_3DES_192/MODE_CBC"); + + /* KEY_3DES_192 / MODE_ECB doesn't work on NSS */ + /* crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_ECB, 0, in, inlen, "KEY_3DES_192/MODE_ECB"); */ + + crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_CBC, 0, in, inlen, + "KEY_AES_256/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_ECB, 0, in, inlen, + "KEY_AES_256/MODE_ECB"); + + /* all 4 of these tests fail to interoperate - a clue from the xml-security code is that + * NSS cannot distinguish between the 128 and 192 bit versions of AES. Will need to be + * investigated. + */ + /* + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_CBC, 0, in, inlen, "KEY_AES_192/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_ECB, 0, in, inlen, "KEY_AES_192/MODE_ECB"); + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_CBC, 0, in, inlen, "KEY_AES_128/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_ECB, 0, in, inlen, "KEY_AES_128/MODE_ECB"); + */ + apr_pool_destroy(pool); + +} + +/** + * Simple test of OpenSSL block crypt. + */ +static void test_crypto_block_openssl_pad(abts_case *tc, void *data) { + apr_pool_t *pool = NULL; + const apr_crypto_driver_t *drivers[] = { NULL, NULL }; + + const unsigned char *in = (const unsigned char *) TEST_STRING; + apr_size_t inlen = sizeof(TEST_STRING); + + apr_pool_create(&pool, NULL); + drivers[0] = get_openssl_driver(tc, pool); + drivers[1] = get_openssl_driver(tc, pool); + + crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_CBC, 1, in, inlen, + "KEY_3DES_192/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_ECB, 1, in, inlen, + "KEY_3DES_192/MODE_ECB"); + crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_CBC, 1, in, inlen, + "KEY_AES_256/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_ECB, 1, in, inlen, + "KEY_AES_256/MODE_ECB"); + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_CBC, 1, in, inlen, + "KEY_AES_192/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_ECB, 1, in, inlen, + "KEY_AES_192/MODE_ECB"); + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_CBC, 1, in, inlen, + "KEY_AES_128/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_ECB, 1, in, inlen, + "KEY_AES_128/MODE_ECB"); + + apr_pool_destroy(pool); + +} + +/** + * Simple test of NSS block crypt. + */ +static void test_crypto_block_nss_pad(abts_case *tc, void *data) { + apr_pool_t *pool = NULL; + const apr_crypto_driver_t *drivers[] = { NULL, NULL }; + + const unsigned char *in = (const unsigned char *) TEST_STRING; + apr_size_t inlen = sizeof(TEST_STRING); + + apr_pool_create(&pool, NULL); + drivers[0] = get_nss_driver(tc, pool); + drivers[1] = get_nss_driver(tc, pool); + + crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_CBC, 1, in, inlen, + "KEY_3DES_192/MODE_CBC"); + /* KEY_3DES_192 / MODE_ECB doesn't work on NSS */ + /* crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_ECB, 1, in, inlen, "KEY_3DES_192/MODE_ECB"); */ + + crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_CBC, 1, in, inlen, + "KEY_AES_256/MODE_CBC"); + + /* KEY_AES_256 / MODE_ECB doesn't support padding on NSS */ + /*crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_ECB, 1, in, inlen, "KEY_AES_256/MODE_ECB");*/ + + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_CBC, 1, in, inlen, + "KEY_AES_192/MODE_CBC"); + + /* KEY_AES_256 / MODE_ECB doesn't support padding on NSS */ + /*crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_ECB, 1, in, inlen, "KEY_AES_192/MODE_ECB");*/ + + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_CBC, 1, in, inlen, + "KEY_AES_128/MODE_CBC"); + + /* KEY_AES_256 / MODE_ECB doesn't support padding on NSS */ + /*crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_ECB, 1, in, inlen, "KEY_AES_128/MODE_ECB");*/ + + apr_pool_destroy(pool); + +} + +/** + * Encrypt NSS, decrypt OpenSSL. + */ +static void test_crypto_block_nss_openssl_pad(abts_case *tc, void *data) { + apr_pool_t *pool = NULL; + const apr_crypto_driver_t *drivers[] = { NULL, NULL }; + + const unsigned char *in = (const unsigned char *) TEST_STRING; + apr_size_t inlen = sizeof(TEST_STRING); + + apr_pool_create(&pool, NULL); + drivers[0] = get_nss_driver(tc, pool); + drivers[1] = get_openssl_driver(tc, pool); + + crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_CBC, 1, in, inlen, + "KEY_3DES_192/MODE_CBC"); + + /* KEY_3DES_192 / MODE_ECB doesn't work on NSS */ + /* crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_ECB, 1, in, inlen, "KEY_3DES_192/MODE_ECB"); */ + + crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_CBC, 1, in, inlen, + "KEY_AES_256/MODE_CBC"); + + /* KEY_AES_256 / MODE_ECB doesn't support padding on NSS */ + /*crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_ECB, 1, in, inlen, "KEY_AES_256/MODE_ECB");*/ + + /* all 4 of these tests fail to interoperate - a clue from the xml-security code is that + * NSS cannot distinguish between the 128 and 192 bit versions of AES. Will need to be + * investigated. + */ + /* + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_CBC, 1, in, inlen, "KEY_AES_192/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_ECB, 1, in, inlen, "KEY_AES_192/MODE_ECB"); + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_CBC, 1, in, inlen, "KEY_AES_128/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_ECB, 1, in, inlen, "KEY_AES_128/MODE_ECB"); + */ + apr_pool_destroy(pool); + +} + +/** + * Encrypt OpenSSL, decrypt NSS. + */ +static void test_crypto_block_openssl_nss_pad(abts_case *tc, void *data) { + apr_pool_t *pool = NULL; + const apr_crypto_driver_t *drivers[] = { NULL, NULL }; + + const unsigned char *in = (const unsigned char *) TEST_STRING; + apr_size_t inlen = sizeof(TEST_STRING); + + apr_pool_create(&pool, NULL); + drivers[0] = get_openssl_driver(tc, pool); + drivers[1] = get_nss_driver(tc, pool); + crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_CBC, 1, in, inlen, + "KEY_3DES_192/MODE_CBC"); + + /* KEY_3DES_192 / MODE_ECB doesn't work on NSS */ + /* crypto_block_cross(tc, pool, drivers, KEY_3DES_192, MODE_ECB, 1, in, inlen, "KEY_3DES_192/MODE_ECB"); */ + + crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_CBC, 1, in, inlen, + "KEY_AES_256/MODE_CBC"); + + /* KEY_AES_256 / MODE_ECB doesn't support padding on NSS */ + /*crypto_block_cross(tc, pool, drivers, KEY_AES_256, MODE_ECB, 1, in, inlen, "KEY_AES_256/MODE_ECB");*/ + + /* all 4 of these tests fail to interoperate - a clue from the xml-security code is that + * NSS cannot distinguish between the 128 and 192 bit versions of AES. Will need to be + * investigated. + */ + /* + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_CBC, 1, in, inlen, "KEY_AES_192/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_192, MODE_ECB, 1, in, inlen, "KEY_AES_192/MODE_ECB"); + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_CBC, 1, in, inlen, "KEY_AES_128/MODE_CBC"); + crypto_block_cross(tc, pool, drivers, KEY_AES_128, MODE_ECB, 1, in, inlen, "KEY_AES_128/MODE_ECB"); + */ + apr_pool_destroy(pool); + +} + +abts_suite *testcrypto(abts_suite *suite) { + suite = ADD_SUITE(suite); + + /* test simple init and shutdown */ + abts_run_test(suite, test_crypto_init, NULL); + + /* test a simple encrypt / decrypt operation - openssl */ + abts_run_test(suite, test_crypto_block_openssl, NULL); + + /* test a padded encrypt / decrypt operation - openssl */ + abts_run_test(suite, test_crypto_block_openssl_pad, NULL); + + /* test a simple encrypt / decrypt operation - nss */ + abts_run_test(suite, test_crypto_block_nss, NULL); + + /* test a padded encrypt / decrypt operation - nss */ + abts_run_test(suite, test_crypto_block_nss_pad, NULL); + + /* test encrypt nss / decrypt openssl */ + abts_run_test(suite, test_crypto_block_nss_openssl, NULL); + + /* test padded encrypt nss / decrypt openssl */ + abts_run_test(suite, test_crypto_block_nss_openssl_pad, NULL); + + /* test encrypt openssl / decrypt nss */ + abts_run_test(suite, test_crypto_block_openssl_nss, NULL); + + /* test padded encrypt openssl / decrypt nss */ + abts_run_test(suite, test_crypto_block_openssl_nss_pad, NULL); + + return suite; +} + +#else + +/** + * Dummy test suite when crypto is turned off. + */ +abts_suite *testcrypto(abts_suite *suite) +{ + return ADD_SUITE(suite); +} + +#endif /* APU_HAVE_CRYPTO */ diff --git a/test/testutil.h b/test/testutil.h index 6b8e5f84..07b34a84 100644 --- a/test/testutil.h +++ b/test/testutil.h @@ -50,6 +50,7 @@ abts_suite *testbuckets(abts_suite *suite); abts_suite *testpass(abts_suite *suite); abts_suite *testmd4(abts_suite *suite); abts_suite *testmd5(abts_suite *suite); +abts_suite *testcrypto(abts_suite *suite); abts_suite *testldap(abts_suite *suite); abts_suite *testdbd(abts_suite *suite); abts_suite *testdate(abts_suite *suite); |