]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Add rlm_ciper for encrypting/decrypting arbitrary data
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Tue, 27 Feb 2018 15:31:19 +0000 (21:31 +0600)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 28 Feb 2018 11:06:42 +0000 (17:06 +0600)
Not yet functional because OpenSSL is stupid...

raddb/mods-available/cipher [new file with mode: 0644]
src/modules/rlm_cipher/README.md [new file with mode: 0644]
src/modules/rlm_cipher/all.mk [new file with mode: 0644]
src/modules/rlm_cipher/rlm_cipher.c [new file with mode: 0644]
src/tests/modules/cipher/module.conf [new file with mode: 0644]
src/tests/modules/cipher/rsa_encrypt_decrypt.unlang [new file with mode: 0644]
src/tests/modules/cipher/server.crt [new file with mode: 0644]
src/tests/modules/cipher/server.key [new file with mode: 0644]
src/tests/modules/cipher/server.pem [new file with mode: 0644]

diff --git a/raddb/mods-available/cipher b/raddb/mods-available/cipher
new file mode 100644 (file)
index 0000000..c7b5440
--- /dev/null
@@ -0,0 +1,88 @@
+
+cipher {
+       #
+       #  RSA asymmetrically keyed ciphering
+       #
+       #  Registers the following expansions:
+       #  - %{<inst>_encrypt:<plaintext>...}                   - Encrypts plaintext using `certificate_file`
+       #  - %{<inst>_decrypt:<ciphertext>...}                  - Decrypts ciphertext using `private_key_file`
+       #  - %{<inst>_sign:<plaintext>...}                      - Signs plaintext using `private_key_file`
+       #  - %{<inst>_verify:<signature> <plaintext>...}        - Validates a signature using `certificate_file`
+       #
+       #  Note: <ciphertext> and <signature> are ingested and excreted to in their raw form.
+       #  You should use armouring expansions i.e. %{base64_encode:}, %{base64_decode:} if the values
+       #  are to be passed outside of FreeRADIUS.
+       #
+       #  Example:
+       #
+       #    %{base64_encode:%{cipher_encrypt:<plaintext>}}
+       #    %{cipher_decrypt:%{base64_decode:<ciphertext>}}
+       #
+       #  Supported digest types vary depending on the version OpenSSL was built against.
+       #
+       #  Reasonably modern >= 1.0.0 versions of OpenSSL should support at least:
+       #
+       #  - md2        (not recommended)
+       #  - md4        (not recommended)
+       #  - md5        (not recommended)
+       #  - sha1       (widely used but deprecated)
+       #  - sha224
+       #  - sha256     (the default - strongly recommended)
+       #  - sha384
+       #  - sha512
+       #
+       #  Bleeding edge versions of OpenSSL may also support the sha3 family of digest functions.
+       #
+       #  Again, the supported versions are determined *entirely* by the version of OpenSSL used, we
+       #  pass the name of the digest off to OpenSSL and it tells *us* whether it's valid/supported
+       #  or not.
+       #
+       rsa {
+               #
+               #  Private key used for decrypting and signing data
+               #
+               private_key_password = whatever
+               private_key_file = ${certdir}/server.pem
+
+               #
+               #  The PEM encoded certificate used for encrypting data
+               #  and verifying signatures.
+               #
+               certificate_file = ${certdir}/server.pem
+
+               #
+               #  Parameters for the OAEP RSA padding scheme
+               #
+               oaep {
+#                      oaep_digest = "sha256"
+#                      mgf1_digest = "sha256"
+#                      label = ""
+               }
+
+               #
+               #  Digest used to ingest the plaintext before signing
+               #  or verification.
+               #
+#              signature_digest = "sha256"
+
+               #
+               #  The type of padding applied to the plaintext
+               #
+               #  One of:
+               #  - none (no padding)
+               #  - pkcs
+               #  - oaep
+               #  - x931 (signing only)
+               #  - ssl (v1/v2)
+               #
+               #  Defaults to pkcs
+               #
+#              padding_type = pkcs
+       }
+
+       #
+       #  Available schemes are:
+       #  - rsa
+       #
+       type = rsa
+}
diff --git a/src/modules/rlm_cipher/README.md b/src/modules/rlm_cipher/README.md
new file mode 100644 (file)
index 0000000..be319d1
--- /dev/null
@@ -0,0 +1,8 @@
+# rlm_example
+## Metadata
+<dl>
+  <dt>category</dt><dd>policy</dd>
+</dl>
+
+## Summary
+An example module to use as a template when writing new modules.
diff --git a/src/modules/rlm_cipher/all.mk b/src/modules/rlm_cipher/all.mk
new file mode 100644 (file)
index 0000000..108009a
--- /dev/null
@@ -0,0 +1,22 @@
+#######################################################################
+#
+# TARGET should be set by autoconf only.  Don't touch it.
+#
+# The SOURCES definition should list ALL source files.
+#
+# SRC_CFLAGS defines addition C compiler flags.  You usually don't
+# want to modify this, though.  Get it from autoconf.
+#
+# The TGT_LDLIBS definition should list ALL required libraries.
+#
+#######################################################################
+
+TARGETNAME     := rlm_cipher
+
+ifneq "$(OPENSSL_LIBS)" ""
+TARGET         := $(TARGETNAME).a
+endif
+
+SOURCES                := $(TARGETNAME).c
+
+TGT_PREREQS    := $(LIBFREERADIUS_SERVER)
diff --git a/src/modules/rlm_cipher/rlm_cipher.c b/src/modules/rlm_cipher/rlm_cipher.c
new file mode 100644 (file)
index 0000000..ebf3c6c
--- /dev/null
@@ -0,0 +1,1027 @@
+/*
+ *   This program is is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or (at
+ *   your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ * @file rlm_cipher.c
+ * @brief Creates dynamic expansions for encrypting/decrypting data.
+ *
+ * @author Arran Cudbard-Bell \<a.cudbardb@freeradius.org\>
+ *
+ * @copyright 2018 The FreeRADIUS server project
+ * @copyright 2018 Network RADIUS \<info@networkradius.com\>
+ *
+ */
+RCSID("$Id$")
+
+#include <freeradius-devel/radiusd.h>
+#include <freeradius-devel/modules.h>
+#include <freeradius-devel/rad_assert.h>
+
+#include <openssl/pem.h>
+#include <openssl/evp.h>
+#include <openssl/rsa.h>
+
+static int digest_type_parse(UNUSED TALLOC_CTX *ctx, void *out, CONF_ITEM *ci, UNUSED CONF_PARSER const *rule);
+static int cipher_rsa_padding_type_parse(UNUSED TALLOC_CTX *ctx, void *out, CONF_ITEM *ci, UNUSED CONF_PARSER const *rule);
+static int cipher_type_parse(UNUSED TALLOC_CTX *ctx, void *out, CONF_ITEM *ci, UNUSED CONF_PARSER const *rule);
+
+static int cipher_rsa_private_key_file_load(UNUSED TALLOC_CTX *ctx, void *out, CONF_ITEM *ci, UNUSED CONF_PARSER const *rule);
+static int cipher_rsa_certificate_file_load(TALLOC_CTX *ctx, void *out, CONF_ITEM *ci, UNUSED CONF_PARSER const *rule);
+
+typedef enum {
+       RLM_CIPHER_TYPE_INVALID = 0,
+       RLM_CIPHER_TYPE_RSA = 1,
+} cipher_type_t;
+
+/** The type of padding used
+ *
+ */
+const FR_NAME_NUMBER cipher_rsa_padding[] = {
+       { "none",       RSA_NO_PADDING          },
+       { "pkcs",       RSA_PKCS1_PADDING       },              /* PKCS 1.5 */
+       { "oaep",       RSA_PKCS1_OAEP_PADDING  },              /* PKCS OAEP padding */
+       { "x931",       RSA_X931_PADDING        },
+       { "ssl",        RSA_SSLV23_PADDING      },
+
+       { NULL, 0                               },
+};
+
+const FR_NAME_NUMBER cipher_type[] = {
+       { "rsa",        RLM_CIPHER_TYPE_RSA     },
+
+       { NULL, 0                               }
+};
+
+typedef struct {
+       EVP_PKEY_CTX            *evp_pkey_ctx;                  //!< Pre-allocated evp_pkey_ctx.
+       EVP_MD_CTX              *evp_md_ctx;                    //!< Pre-allocated evp_md_ctx for sign and verify.
+       uint8_t                 *digest_buff;                   //!< Pre-allocated digest buffer.
+} rlm_cipher_rsa_thread_inst_t;
+
+/** Configuration for the OAEP padding method
+ *
+ */
+typedef struct {
+       EVP_MD                  *oaep_digest;                   //!< Padding digest type.
+       EVP_MD                  *mgf1_digest;                   //!< Masking function digest.
+
+       char const              *label;                         //!< Additional input to the hashing function.
+} cipher_rsa_oaep_t;
+
+/** Configuration for RSA encryption/decryption/signing
+ *
+ */
+typedef struct {
+       char const              *private_key_password;          //!< Password to decrypt the private key.
+       char const              *random_file;                   //!< If set, we read 10K of data (or the complete file)
+                                                               //!< and use it to seed OpenSSL's PRNG.
+
+       EVP_PKEY                *private_key_file;              //!< Private key file.
+       EVP_PKEY                *certificate_file;              //!< Public (certificate) file.
+
+       int                     padding;                        //!< Type of padding to apply to the plaintext
+                                                               ///< or ciphertext before feeding it to RSA crypto
+                                                               ///< functions.
+
+       EVP_MD                  *sig_digest;                    //!< Signature digest type.
+
+       cipher_rsa_oaep_t       *oaep;                          //!< OAEP can use a configurable message digest type
+                                                               ///< and additional keying labeleter.
+} cipher_rsa_t;
+
+/** Instance configuration
+ *
+ */
+typedef struct {
+       char const              *xlat_name;                     //!< Name of xlat we registered.
+       cipher_type_t           type;                           //!< Type of encryption to use.
+
+       /** Supported cipher types
+        *
+        */
+       union {
+               cipher_rsa_t    *rsa;                           //!< Use RSA encryption (with optional padding).
+       };
+} rlm_cipher_t;
+
+/** Configuration for the RSA-PCKS1-OAEP padding scheme
+ *
+ */
+static const CONF_PARSER rsa_oaep_config[] = {
+       { FR_CONF_OFFSET("oaep_digest", FR_TYPE_VOID | FR_TYPE_NOT_EMPTY, cipher_rsa_oaep_t, oaep_digest), .func = digest_type_parse, .dflt = "sha256" },
+       { FR_CONF_OFFSET("mgf1_digest", FR_TYPE_VOID | FR_TYPE_NOT_EMPTY, cipher_rsa_oaep_t, mgf1_digest), .func = digest_type_parse, .dflt = "sha256" },
+       { FR_CONF_OFFSET("label", FR_TYPE_STRING, cipher_rsa_oaep_t, label) },
+
+       CONF_PARSER_TERMINATOR
+};
+
+/** Configuration for the RSA cipher type
+ *
+ */
+static const CONF_PARSER rsa_config[] = {
+       { FR_CONF_OFFSET("private_key_password", FR_TYPE_STRING | FR_TYPE_SECRET, cipher_rsa_t, private_key_password) },        /* Must come before private_key */
+       { FR_CONF_OFFSET("private_key_file", FR_TYPE_VOID | FR_TYPE_NOT_EMPTY, cipher_rsa_t, private_key_file), .func = cipher_rsa_private_key_file_load },
+       { FR_CONF_OFFSET("certificate_file", FR_TYPE_VOID | FR_TYPE_NOT_EMPTY, cipher_rsa_t, certificate_file), .func = cipher_rsa_certificate_file_load },
+
+       { FR_CONF_OFFSET("random_file", FR_TYPE_STRING, cipher_rsa_t, random_file) },
+
+       { FR_CONF_OFFSET("signature_digest", FR_TYPE_VOID | FR_TYPE_NOT_EMPTY, cipher_rsa_t, sig_digest), .func = digest_type_parse, .dflt = "sha256" },
+
+       { FR_CONF_OFFSET("padding_type", FR_TYPE_VOID | FR_TYPE_NOT_EMPTY, cipher_rsa_t, padding), .func = cipher_rsa_padding_type_parse, .dflt = "pkcs" },
+       { FR_CONF_OFFSET("oaep", FR_TYPE_SUBSECTION, cipher_rsa_t, oaep),
+                        .subcs_size = sizeof(cipher_rsa_oaep_t), .subcs_type = "cipher_rsa_oaep_t", .subcs = (void const *) rsa_oaep_config },
+
+       CONF_PARSER_TERMINATOR
+};
+
+/*
+ *     A mapping of configuration file names to internal variables.
+ */
+static const CONF_PARSER module_config[] = {
+       { FR_CONF_OFFSET("type", FR_TYPE_VOID | FR_TYPE_NOT_EMPTY, rlm_cipher_t, type), .func = cipher_type_parse, .dflt = "rsa" },
+       { FR_CONF_OFFSET("rsa", FR_TYPE_SUBSECTION, rlm_cipher_t, rsa),
+                        .subcs_size = sizeof(cipher_rsa_t), .subcs_type = "cipher_rsa_t", .subcs = (void const *) rsa_config },
+
+       CONF_PARSER_TERMINATOR
+};
+
+/** Calls EVP_get_digestbyname() to covert the digest type
+ *
+ * @label[in] ctx      to allocate data in (instance of proto_radius).
+ * @label[out] out     EVP_MD representing the OpenSSL digest type.
+ * @label[in] ci       #CONF_PAIR specifying the name of the digest.
+ * @label[in] rule     unused.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static int digest_type_parse(UNUSED TALLOC_CTX *ctx, void *out, CONF_ITEM *ci, UNUSED CONF_PARSER const *rule)
+{
+       EVP_MD const    *md;
+       char const      *type_str;
+
+       type_str = cf_pair_value(cf_item_to_pair(ci));
+       md = EVP_get_digestbyname(type_str);
+       if (!md) {
+               cf_log_err(ci, "Invalid digest type \"%s\"", type_str);
+               return -1;
+       }
+
+       *((EVP_MD const **)out) = md;
+
+       return 0;
+}
+
+/** Checks if the specified padding type is valid
+ *
+ * @label[in] ctx      to allocate data in (instance of proto_radius).
+ * @label[out] out     Padding type.
+ * @label[in] ci       #CONF_PAIR specifying the padding type..
+ * @label[in] rule     unused.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static int cipher_rsa_padding_type_parse(UNUSED TALLOC_CTX *ctx, void *out, CONF_ITEM *ci,
+                                        UNUSED CONF_PARSER const *rule)
+{
+       int             type;
+       char const      *type_str;
+
+       type_str = cf_pair_value(cf_item_to_pair(ci));
+       type = fr_str2int(cipher_rsa_padding, type_str, -1);
+       if (type == -1) {
+               cf_log_err(ci, "Invalid padding type \"%s\"", type_str);
+               return -1;
+       }
+
+       *((int *)out) = type;
+
+       return 0;
+}
+
+/** Checks if the specified cipher type is valid
+ *
+ * @label[in] ctx      to allocate data in (instance of proto_radius).
+ * @label[out] out     Cipher enumeration type.
+ * @label[in] ci       #CONF_PAIR specifying the name of the type module.
+ * @label[in] rule     unused.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static int cipher_type_parse(UNUSED TALLOC_CTX *ctx, void *out, CONF_ITEM *ci, UNUSED CONF_PARSER const *rule)
+{
+       cipher_type_t   type;
+       char const      *type_str;
+
+       type_str = cf_pair_value(cf_item_to_pair(ci));
+       type = fr_str2int(cipher_type, type_str, RLM_CIPHER_TYPE_INVALID);
+       switch (type) {
+       case RLM_CIPHER_TYPE_RSA:
+               break;
+
+       case RLM_CIPHER_TYPE_INVALID:
+               cf_log_err(ci, "Invalid cipher type \"%s\"", type_str);
+               return -1;
+       }
+
+       *((cipher_type_t *)out) = type;
+
+       return 0;
+}
+
+/** Return the static private key password we have configured
+ *
+ * @label[out] buf     Where to write the password to.
+ * @label[in] num      The length of buf.
+ * @label[in] rwflag
+ *                     - 0 if password used for decryption.
+ *                     - 1 if password used for encryption.
+ * @label[in] userdata The static password.
+ * @return
+ *     - 0 on error.
+ *     - >0 on success (the length of the password).
+ */
+static int _get_private_key_password(char *buf, int size, UNUSED int rwflag, void *u)
+{
+       char            *pass;
+       size_t          len;
+
+       if (!u) {
+               ERROR("Certificate encrypted but no private_key_password configured");
+               return 0;
+       }
+
+       pass = talloc_get_type_abort(u, char);
+       len = talloc_array_length(pass);        /* Len includes \0 */
+       if (len > (size_t)size) {
+               ERROR("Password too long.  Maximum length is %i bytes", size - 1);
+               return -1;
+       }
+       memcpy(buf, pass, len);                 /* Copy complete password including \0 byte */
+
+       return len - 1;
+}
+
+/** Talloc destructor for freeing an EVP_PKEY (representing a certificate)
+ *
+ * @label[in] pkey     to free.
+ * @return 0
+ */
+static int _evp_pkey_free(EVP_PKEY *pkey)
+{
+       EVP_PKEY_free(pkey);
+
+       return 0;
+}
+
+/** Load and (optionally decrypt) an RSA private key using OpenSSL functions
+ *
+ * @label[in] ctx      UNUSED. Although the EVP_PKEY struct will be allocated
+ *                     with talloc, we need to call the specialised free
+ *                     function anyway.
+ * @label[out] out     Where to write the EVP_PKEY * representing the
+ *                     certificate we just loaded.
+ * @label[in] ci       Config item containing the certificate path.
+ * @label[in] rule     this callback was attached to.
+ * @return
+ *     - -1 on failure.
+ *     - 0 on success.
+ */
+static int cipher_rsa_private_key_file_load(TALLOC_CTX *ctx, void *out, CONF_ITEM *ci,
+                                           UNUSED CONF_PARSER const *rule)
+{
+       FILE            *fp;
+       char const      *filename;
+       cipher_rsa_t    *rsa_inst = talloc_get_type_abort(ctx, cipher_rsa_t);   /* Yeah this is a bit hacky */
+       EVP_PKEY        *pkey;
+       void            *pass;
+
+       filename = cf_pair_value(cf_item_to_pair(ci));
+
+       fp = fopen(filename, "r");
+       if (!fp) {
+               cf_log_err(ci, "Failed opening file: %s", fr_syserror(errno));
+               return -1;
+       }
+
+       memcpy(&pass, &rsa_inst->private_key_password, sizeof(pass));
+
+       pkey = PEM_read_PrivateKey(fp, (EVP_PKEY **)out, _get_private_key_password, pass);
+       fclose(fp);
+
+       if (!pkey) {
+               tls_strerror_printf(true, NULL);
+               cf_log_perr(ci, "Error loading private certificate file \"%s\"", filename);
+
+               return -1;
+       }
+
+       (void)talloc_steal(ctx, pkey);                  /* Bind lifetime to config */
+       talloc_set_destructor(pkey, _evp_pkey_free);    /* Free pkey correctly on chunk free */
+
+       return 0;
+}
+
+/** Load an RSA public key using OpenSSL functions
+ *
+ * @label[in] ctx      UNUSED. Although the EVP_PKEY struct will be allocated
+ *                     with talloc, we need to call the specialised free
+ *                     function anyway.
+ * @label[out] out     Where to write the EVP_PKEY * representing the
+ *                     certificate we just loaded.
+ * @label[in] ci       Config item containing the certificate path.
+ * @label[in] rule     this callback was attached to.
+ * @return
+ *     - -1 on failure.
+ *     - 0 on success.
+ */
+static int cipher_rsa_certificate_file_load(UNUSED TALLOC_CTX *ctx, void *out, CONF_ITEM *ci,
+                                           UNUSED CONF_PARSER const *rule)
+{
+       FILE            *fp;
+       char const      *filename;
+       EVP_PKEY        *pkey;
+
+       filename = cf_pair_value(cf_item_to_pair(ci));
+
+       fp = fopen(filename, "r");
+       if (!fp) {
+               cf_log_err(ci, "Failed opening file: %s", fr_syserror(errno));
+               return -1;
+       }
+
+       pkey = PEM_read_PUBKEY(fp, (EVP_PKEY **)out, NULL, NULL);
+       fclose(fp);
+
+       if (!pkey) {
+               tls_strerror_printf(true, NULL);
+               cf_log_perr(ci, "Error loading certificate file \"%s\"", filename);
+
+               return -1;
+       }
+
+       (void)talloc_steal(ctx, pkey);                  /* Bind lifetime to config */
+       talloc_set_destructor(pkey, _evp_pkey_free);    /* Free pkey correctly on chunk free */
+
+       return 0;
+}
+
+static int cipher_rsa_padding_params_set(REQUEST *request, EVP_PKEY_CTX *evp_pkey_ctx, cipher_rsa_t const *rsa_inst)
+{
+
+       if (unlikely(EVP_PKEY_CTX_set_rsa_padding(evp_pkey_ctx, rsa_inst->padding)) <= 0) {
+               tls_log_error(request, "Failed setting RSA padding type");
+               return -1;
+       }
+
+       switch (rsa_inst->padding) {
+       case RSA_NO_PADDING:
+       case RSA_X931_PADDING:
+       case RSA_SSLV23_PADDING:
+       case RSA_PKCS1_PADDING:
+               return 0;
+
+       /*
+        *      Configure OAEP advanced padding options
+        */
+       case RSA_PKCS1_OAEP_PADDING:
+               if (unlikely(EVP_PKEY_CTX_set_rsa_oaep_md(evp_pkey_ctx, rsa_inst->oaep->oaep_digest) <= 0)) {
+                       tls_log_error(request, "Failed setting OAEP digest");
+                       return -1;
+               }
+
+               if (unlikely(EVP_PKEY_CTX_set_rsa_mgf1_md(evp_pkey_ctx, rsa_inst->oaep->mgf1_digest) <= 0)) {
+                       tls_log_error(request, "Failed setting MGF1 digest");
+                       return -1;
+               }
+
+               if (rsa_inst->oaep->label) {
+                       char *label;
+
+                       memcpy(&label, &rsa_inst->oaep->label, sizeof(label));
+
+                       if (unlikely(EVP_PKEY_CTX_set0_rsa_oaep_label(evp_pkey_ctx, label,
+                                                                     talloc_array_length(label) - 1) <= 0)) {
+                               tls_log_error(request, "Failed setting OAEP padding label");
+                               return -1;
+                       }
+               }
+               return 0;
+
+       default:
+               rad_assert(0);
+               return -1;
+       }
+}
+
+/** Encrypt input data
+ *
+ * Arguments are (<plaintext>...).
+ *
+ * If multiple arguments are provided they will be concatenated.
+ */
+static xlat_action_t cipher_rsa_encrypt_xlat(TALLOC_CTX *ctx, fr_cursor_t *out,
+                                            REQUEST *request, void const *xlat_inst, void *xlat_thread_inst,
+                                            fr_value_box_t **in)
+{
+       rlm_cipher_t const              *inst = talloc_get_type_abort(*((void const * const *)xlat_inst), rlm_cipher_t);
+       rlm_cipher_rsa_thread_inst_t    *xt = talloc_get_type_abort(*((void const * const *)xlat_thread_inst),
+                                                                   rlm_cipher_rsa_thread_inst_t);
+
+       char const                      *plaintext;
+       size_t                          plaintext_len;
+
+       uint8_t                         *ciphertext;
+       size_t                          ciphertext_len;
+
+       fr_value_box_t                  *vb;
+
+       if (!*in) {
+               REDEBUG("encrypt requires one or arguments (<plaintext>...)");
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (fr_value_box_list_concat(ctx, *in, in, FR_TYPE_STRING, true) < 0) {
+               tls_log_error(request, "Failed concatenating arguments to form plaintext");
+               return XLAT_ACTION_FAIL;
+       }
+       plaintext = (*in)->vb_strvalue;
+       plaintext_len = (*in)->vb_length;
+
+       if (unlikely(EVP_PKEY_encrypt_init(xt->evp_pkey_ctx) <= 0)) {
+               tls_log_error(request, "Failed initialising EVP_PKEY_CTX");
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (unlikely(cipher_rsa_padding_params_set(request, xt->evp_pkey_ctx, inst->rsa) < 0)) return XLAT_ACTION_FAIL;
+
+       /*
+        *      Figure out the buffer we need
+        */
+       if (EVP_PKEY_encrypt(xt->evp_pkey_ctx, NULL, &ciphertext_len,
+                            (unsigned char const *)plaintext, plaintext_len) <= 0) {
+               tls_log_error(request, "Failed encrypting plaintext");
+               return XLAT_ACTION_FAIL;
+       }
+
+       MEM(ciphertext = talloc_array(ctx, uint8_t, ciphertext_len));
+       if (EVP_PKEY_encrypt(xt->evp_pkey_ctx, ciphertext, &ciphertext_len,
+                            (unsigned char const *)plaintext, plaintext_len) <= 0) {
+               tls_log_error(request, "Failed encrypting plaintext");
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (ciphertext_len != talloc_array_length(ciphertext)) {
+               uint8_t *n;
+
+               n = talloc_realloc_size(ctx, ciphertext, ciphertext_len);
+               if (unlikely(!n)) {
+                       REDEBUG("Failed shrinking ciphertext buffer");
+                       talloc_free(ciphertext);
+                       return XLAT_ACTION_FAIL;
+               }
+               talloc_set_type(n, uint8_t);
+
+               ciphertext = n;
+       }
+
+       MEM(vb = fr_value_box_alloc_null(ctx));
+       fr_value_box_memsteal(vb, vb, NULL, ciphertext, false);
+       fr_cursor_append(out, vb);
+
+       return XLAT_ACTION_DONE;
+}
+
+/** Sign input data
+ *
+ * Arguments are (<plaintext>...).
+ *
+ * If multiple arguments are provided they will be concatenated.
+ */
+static xlat_action_t cipher_rsa_sign_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_cursor_t *out,
+                                         REQUEST *request, void const *xlat_inst, void *xlat_thread_inst,
+                                         fr_value_box_t **in)
+{
+       rlm_cipher_t const              *inst = talloc_get_type_abort(*((void const * const *)xlat_inst), rlm_cipher_t);
+       rlm_cipher_rsa_thread_inst_t    *xt = talloc_get_type_abort(*((void const * const *)xlat_thread_inst),
+                                                                   rlm_cipher_rsa_thread_inst_t);
+
+       char const                      *msg;
+       size_t                          msg_len;
+
+       uint8_t                         *sig;
+       size_t                          sig_len;
+
+       size_t                          digest_len;
+
+       fr_value_box_t                  *vb;
+
+       if (!*in) {
+               REDEBUG("sign requires one or arguments (<plaintext>...)");
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (fr_value_box_list_concat(ctx, *in, in, FR_TYPE_STRING, true) < 0) {
+               REDEBUG("Failed concatenating arguments to form plaintext");
+               return XLAT_ACTION_FAIL;
+       }
+       msg = (*in)->next->vb_strvalue;
+       msg_len = (*in)->vb_length;
+
+       if (unlikely(EVP_PKEY_sign_init(xt->evp_pkey_ctx) <= 0)) {
+               tls_log_error(request, "Failed initialising EVP_PKEY_CTX");
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (unlikely(cipher_rsa_padding_params_set(request, xt->evp_pkey_ctx, inst->rsa) < 0)) return XLAT_ACTION_FAIL;
+       if (unlikely(EVP_PKEY_CTX_set_signature_md(xt->evp_pkey_ctx, inst->rsa->sig_digest)) <= 0) {
+               tls_log_error(request, "Failed setting signature digest type");
+               return XLAT_ACTION_FAIL;
+       }
+
+       /*
+        *      First produce a digest of the message
+        */
+       if (unlikely(EVP_DigestInit(xt->evp_md_ctx, inst->rsa->sig_digest) <= 0)) {
+               tls_log_error(request, "Failed initialising message digest");
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (EVP_DigestUpdate(xt->evp_md_ctx, msg, msg_len) <= 0) {
+               tls_log_error(request, "Failed ingesting message");
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (EVP_DigestFinal(xt->evp_md_ctx, xt->digest_buff, (unsigned int *)&digest_len) <= 0) {
+               tls_log_error(request, "Failed finalising message digest");
+               return XLAT_ACTION_FAIL;
+       }
+
+       /*
+        *      Then sign the digest
+        */
+       if (EVP_PKEY_sign(ctx, NULL, &sig_len, xt->digest_buff, digest_len) <= 0) {
+               tls_log_error(request, "Failed signing message digest");
+               return XLAT_ACTION_FAIL;
+       }
+
+       MEM(sig = talloc_array(ctx, uint8_t, sig_len));
+       if (EVP_PKEY_sign(ctx, sig, &sig_len, xt->digest_buff, digest_len) <= 0) {
+               tls_log_error(request, "Failed signing message digest");
+               return XLAT_ACTION_FAIL;
+       }
+
+       /*
+        *      Fixup the output buffer
+        */
+       if (sig_len != talloc_array_length(sig)) {
+               uint8_t *n;
+
+               n = talloc_realloc_size(ctx, sig, sig_len);
+               if (unlikely(!n)) {
+                       REDEBUG("Failed shrinking signature buffer");
+                       talloc_free(sig);
+                       return XLAT_ACTION_FAIL;
+               }
+               talloc_set_type(n, uint8_t);
+
+               sig = n;
+       }
+
+       MEM(vb = fr_value_box_alloc_null(ctx));
+       fr_value_box_memsteal(vb, vb, NULL, sig, false);
+       fr_cursor_append(out, vb);
+
+       return XLAT_ACTION_DONE;
+}
+
+/** Decrypt input data
+ *
+ * Arguments are (<ciphertext>...).
+ *
+ * If multiple arguments are provided they will be concatenated.
+ */
+static xlat_action_t cipher_rsa_decrypt_xlat(TALLOC_CTX *ctx, UNUSED fr_cursor_t *out,
+                                            REQUEST *request, void const *xlat_inst, void *xlat_thread_inst,
+                                            fr_value_box_t **in)
+{
+       rlm_cipher_t const              *inst = talloc_get_type_abort(*((void const * const *)xlat_inst), rlm_cipher_t);
+       rlm_cipher_rsa_thread_inst_t    *xt = talloc_get_type_abort(*((void const * const *)xlat_thread_inst),
+                                                                   rlm_cipher_rsa_thread_inst_t);
+
+       uint8_t const                   *ciphertext;
+       size_t                          ciphertext_len;
+
+       char                            *plaintext;
+       size_t                          plaintext_len;
+
+       fr_value_box_t                  *vb;
+
+       if (!*in) {
+               REDEBUG("decrypt requires one or more arguments (<ciphertext>...)");
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (fr_value_box_list_concat(ctx, *in, in, FR_TYPE_OCTETS, true) < 0) {
+               REDEBUG("Failed concatenating arguments to form plaintext");
+               return XLAT_ACTION_FAIL;
+       }
+       ciphertext = (*in)->vb_octets;
+       ciphertext_len = (*in)->vb_length;
+
+       if (unlikely(EVP_PKEY_decrypt_init(xt->evp_pkey_ctx) <= 0)) {
+               tls_log_error(request, "Failed initialising EVP_PKEY_CTX");
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (unlikely(cipher_rsa_padding_params_set(request, xt->evp_pkey_ctx, inst->rsa) < 0)) return XLAT_ACTION_FAIL;
+
+       /*
+        *      Decrypt the plaintext
+        */
+       if (EVP_PKEY_decrypt(ctx, NULL, &plaintext_len, ciphertext, ciphertext_len) <= 0) {
+               tls_log_error(request, "Failed decrypting ciphertext");
+               return XLAT_ACTION_FAIL;
+       }
+
+       MEM(plaintext = talloc_array(ctx, char, plaintext_len + 1));
+       if (EVP_PKEY_decrypt(ctx, (unsigned char *)plaintext, &plaintext_len, ciphertext, ciphertext_len) <= 0) {
+               tls_log_error(request, "Failed decrypting ciphertext");
+               return XLAT_ACTION_FAIL;
+       }
+
+       /*
+        *      Fixup the output buffer (and ensure it's \0 terminated)
+        */
+       {
+               char *n;
+
+               n = talloc_realloc_bstr(plaintext, plaintext_len);
+               if (unlikely(!n)) {
+                       REDEBUG("Failed shrinking plaintext buffer");
+                       talloc_free(plaintext);
+                       return XLAT_ACTION_FAIL;
+               }
+
+               plaintext = n;
+       }
+
+       MEM(vb = fr_value_box_alloc_null(ctx));
+       fr_value_box_strsteal(vb, vb, NULL, plaintext, false);
+       fr_cursor_append(out, vb);
+
+       return XLAT_ACTION_DONE;
+}
+
+/** Verify input data
+ *
+ * Arguments are (<signature>, <plaintext>...).
+ *
+ * If multiple arguments are provided (after <signature>) they will be concatenated.
+ */
+static xlat_action_t cipher_rsa_verify_xlat(UNUSED TALLOC_CTX *ctx, UNUSED fr_cursor_t *out,
+                                           REQUEST *request, void const *xlat_inst, void *xlat_thread_inst,
+                                           fr_value_box_t **in)
+{
+       rlm_cipher_t const              *inst = talloc_get_type_abort(*((void const * const *)xlat_inst), rlm_cipher_t);
+       rlm_cipher_rsa_thread_inst_t    *xt = talloc_get_type_abort(*((void const * const *)xlat_thread_inst),
+                                                                   rlm_cipher_rsa_thread_inst_t);
+
+       uint8_t const                   *sig;
+       size_t                          sig_len;
+
+       char const                      *msg;
+       size_t                          msg_len;
+
+       size_t                          digest_len;
+
+       fr_value_box_t                  *vb;
+
+       if (!*in) {
+               REDEBUG("verification requires two or more arguments (<signature>, <message>...)");
+               return XLAT_ACTION_FAIL;
+       }
+
+       /*
+        *      Check we have at least two boxed values
+        */
+       if (!(*in)->next) {
+               REDEBUG("Missing message data arg or message data was (null)");
+               return XLAT_ACTION_FAIL;
+       }
+
+       /*
+        *      Don't auto-cast to octets if the signature
+        *      isn't already in that form.
+        *      It could be hexits or base64 or some other encoding.
+        */
+       if ((*in)->type != FR_TYPE_OCTETS) {
+               REDEBUG("Signature argument wrong type, expected %s, got %s.  "
+                       "Use %%{base64_decode:<text>} or %%{hex_decode:<text>} if signature is armoured",
+                       fr_int2str(dict_attr_types, FR_TYPE_OCTETS, "?Unknown?"),
+                       fr_int2str(dict_attr_types, (*in)->type, "?Unknown?"));
+               return XLAT_ACTION_FAIL;
+       }
+       sig = (*in)->vb_octets;
+       sig_len = (*in)->vb_length;
+
+       /*
+        *      Concat (...) args to get message data
+        */
+       if (fr_value_box_list_concat(ctx, (*in)->next, &((*in)->next), FR_TYPE_STRING, true) < 0) {
+               REDEBUG("Failed concatenating arguments to form plaintext");
+               return XLAT_ACTION_FAIL;
+       }
+       msg = (*in)->next->vb_strvalue;
+       msg_len = (*in)->next->vb_length;
+
+       /*
+        *      The argument separator also gets rolled into
+        *      the concatenate buffer... We should probably
+        *      figure out a cleaner way of doing this.
+        */
+       if (*msg != ' ') {
+               REDEBUG("Expected whitespace argument separator");
+               return XLAT_ACTION_FAIL;
+       }
+       msg++;
+       msg_len--;
+
+       if (msg_len == 0) {
+               REDEBUG("Zero length message data");
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (unlikely(EVP_PKEY_verify_init(xt->evp_pkey_ctx) <= 0)) {
+               tls_log_error(request, "Failed initialising EVP_PKEY_CTX");
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (unlikely(cipher_rsa_padding_params_set(request, xt->evp_pkey_ctx, inst->rsa) < 0)) return XLAT_ACTION_FAIL;
+       if (unlikely(EVP_PKEY_CTX_set_signature_md(xt->evp_pkey_ctx, inst->rsa->sig_digest)) <= 0) {
+               tls_log_error(request, "Failed setting signature digest type");
+               return XLAT_ACTION_FAIL;
+       }
+
+       /*
+        *      First produce a digest of the message
+        */
+       if (unlikely(EVP_DigestInit(xt->evp_md_ctx, inst->rsa->sig_digest) <= 0)) {
+               tls_log_error(request, "Failed initialising message digest");
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (EVP_DigestUpdate(xt->evp_md_ctx, msg, msg_len) <= 0) {
+               tls_log_error(request, "Failed ingesting message");
+               return XLAT_ACTION_FAIL;
+       }
+
+       if (EVP_DigestFinal(xt->evp_md_ctx, xt->digest_buff, (unsigned int *)&digest_len) <= 0) {
+               tls_log_error(request, "Failed finalising message digest");
+               return XLAT_ACTION_FAIL;
+       }
+
+       /*
+        *      Now check the signature matches what we expected
+        */
+       switch (EVP_PKEY_verify(ctx, sig, sig_len, xt->digest_buff, digest_len)) {
+       case 1:         /* success (signature valid) */
+               MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_BOOL, NULL, false));
+               vb->vb_bool = true;
+               fr_cursor_append(out, vb);
+               break;
+
+       case 0:         /* failure (signature not valid) */
+               MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_BOOL, NULL, false));
+               vb->vb_bool = false;
+               fr_cursor_append(out, vb);
+               break;
+
+       default:
+               tls_log_error(request, "Failed validating signature");
+               return XLAT_ACTION_FAIL;
+       }
+
+       return XLAT_ACTION_DONE;
+}
+
+/** Talloc destructor for freeing an EVP_PKEY_CTX
+ *
+ * @label[in] pkey     to free.
+ * @return 0
+ */
+static int _evp_pkey_ctx_free(EVP_PKEY_CTX *evp_pkey_ctx)
+{
+       EVP_PKEY_CTX_free(evp_pkey_ctx);
+
+       return 0;
+}
+
+/** Talloc destructor for freeing an EVP_MD_CTX
+ *
+ * @label[in] pkey     to free.
+ * @return 0
+ */
+static int _evp_md_ctx_free(EVP_MD_CTX *evp_md_ctx)
+{
+       EVP_MD_CTX_destroy(evp_md_ctx);
+
+       return 0;
+}
+
+/** Boilerplate to copy the pointer to the main module thread instance into xlat thread instance data
+ *
+ */
+static int cipher_xlat_thread_instantiate(UNUSED void *xlat_inst, void *xlat_thread_inst,
+                                         UNUSED xlat_exp_t const *exp, void *uctx)
+{
+       rlm_cipher_t                    *inst = talloc_get_type_abort(uctx, rlm_cipher_t);
+
+       *((rlm_cipher_rsa_thread_inst_t **)xlat_thread_inst) =
+               talloc_get_type_abort(module_thread_instance_by_data(inst), rlm_cipher_rsa_thread_inst_t);
+
+       return 0;
+}
+
+/** Boilerplate to copy the pointer to the main module config into the xlat instance data
+ *
+ */
+static int cipher_xlat_instantiate(void *xlat_inst, UNUSED xlat_exp_t const *exp, void *uctx)
+{
+       *((rlm_cipher_t **)xlat_inst) = talloc_get_type_abort(uctx, rlm_cipher_t);
+
+       return 0;
+}
+
+
+/** Pre-initialises the EVP_PKEY_CTX necessary for performing RSA encryption/decryption/sign/verify
+ *
+ * If reference counting is used for EVP_PKEY structs, should also prevent any mutex contention
+ * associated with incrementing/decrementing those references.
+ *
+ * xlat functions MUST NOT interleave PKEY operations with yields
+ *
+ * @return 0.
+ */
+static int cipher_rsa_thread_instantiate(UNUSED CONF_SECTION const *conf, void *instance,
+                                        UNUSED fr_event_list_t *el, void *thread)
+{
+       rlm_cipher_t const              *inst = talloc_get_type_abort(instance, rlm_cipher_t);
+       rlm_cipher_rsa_thread_inst_t    *ti = talloc_get_type_abort(thread, rlm_cipher_rsa_thread_inst_t);
+
+       ti->evp_pkey_ctx = EVP_PKEY_CTX_new(inst->rsa->certificate_file, NULL);
+       if (!ti->evp_pkey_ctx) {
+               tls_strerror_printf(true, NULL);
+               PERROR("%s: Failed allocating EVP_PKEY_CTX", __FUNCTION__);
+               return -1;
+       }
+
+       ti->evp_pkey_ctx = talloc_steal(ti, ti->evp_pkey_ctx);  /* Bind lifetime to instance */
+       talloc_set_destructor(ti->evp_pkey_ctx, _evp_pkey_ctx_free);    /* Free ctx correctly on chunk free */
+
+       ti->evp_md_ctx = EVP_MD_CTX_create();
+       if (!ti->evp_md_ctx) {
+               tls_strerror_printf(true, NULL);
+               PERROR("%s: Failed allocating EVP_MD_CTX", __FUNCTION__);
+               return -1;
+       }
+
+       ti->evp_md_ctx = talloc_steal(ti, ti->evp_md_ctx);              /* Bind lifetime to instance */
+       talloc_set_destructor(ti->evp_md_ctx, _evp_md_ctx_free);                /* Free ctx correctly on chunk free */
+
+       MEM(ti->digest_buff = talloc_array(ti, uint8_t, EVP_MD_size(inst->rsa->sig_digest)));
+
+       return 0;
+}
+
+static int mod_thread_instantiate(CONF_SECTION const *conf, void *instance,
+                                 fr_event_list_t *el, void *thread)
+{
+       rlm_cipher_t    *inst = talloc_get_type_abort(instance, rlm_cipher_t);
+
+       switch (inst->type) {
+       case RLM_CIPHER_TYPE_RSA:
+               return cipher_rsa_thread_instantiate(conf, instance, el, thread);
+
+       case RLM_CIPHER_TYPE_INVALID:
+               rad_assert(0);
+       }
+
+       return 0;
+}
+
+/*
+ *     Do any per-module initialization that is separate to each
+ *     configured instance of the module.  e.g. set up connections
+ *     to external databases, read configuration files, set up
+ *     dictionary entries, etc.
+ */
+static int mod_bootstrap(void *instance, CONF_SECTION *conf)
+{
+       rlm_cipher_t    *inst = talloc_get_type_abort(instance, rlm_cipher_t);
+
+       inst->xlat_name = cf_section_name2(conf);
+       if (!inst->xlat_name) inst->xlat_name = cf_section_name1(conf);
+
+       switch (inst->type) {
+       case RLM_CIPHER_TYPE_RSA:
+               if (!inst->rsa) {
+                       cf_log_err(conf, "type = rsa, but no 'rsa { ... }' configuration section provided");
+                       return -1;
+               }
+
+               if (!inst->rsa->private_key_file && !inst->rsa->certificate_file) {
+                       cf_log_err(conf, "type = rsa, but neither "
+                                        "'private_key_file' nor 'certificate_file' configured");
+                       return -1;
+               }
+
+               if (inst->rsa->private_key_file) {
+                       char *decrypt_name;
+                       char *verify_name;
+
+                       decrypt_name = talloc_asprintf(inst, "%s_decrypt", inst->xlat_name);
+                       verify_name = talloc_asprintf(inst, "%s_verify", inst->xlat_name);
+
+                       xlat_async_register(inst, decrypt_name, cipher_rsa_decrypt_xlat,
+                                           cipher_xlat_instantiate, rlm_cipher_t *, NULL,
+                                           cipher_xlat_thread_instantiate, rlm_cipher_rsa_thread_inst_t *,
+                                           NULL, inst);
+
+                       xlat_async_register(inst, verify_name, cipher_rsa_verify_xlat,
+                                           cipher_xlat_instantiate, rlm_cipher_t *, NULL,
+                                           cipher_xlat_thread_instantiate, rlm_cipher_rsa_thread_inst_t *,
+                                           NULL, inst);
+
+                       talloc_free(decrypt_name);
+                       talloc_free(verify_name);
+               }
+
+               if (inst->rsa->certificate_file) {
+                       char *encrypt_name;
+                       char *sign_name;
+
+                       encrypt_name = talloc_asprintf(inst, "%s_encrypt", inst->xlat_name);
+                       sign_name = talloc_asprintf(inst, "%s_sign", inst->xlat_name);
+
+                       xlat_async_register(inst, encrypt_name, cipher_rsa_encrypt_xlat,
+                                           cipher_xlat_instantiate, rlm_cipher_t *, NULL,
+                                           cipher_xlat_thread_instantiate, rlm_cipher_rsa_thread_inst_t *,
+                                           NULL, inst);
+                       xlat_async_register(inst, sign_name, cipher_rsa_sign_xlat,
+                                           cipher_xlat_instantiate, rlm_cipher_t *, NULL,
+                                           cipher_xlat_thread_instantiate, rlm_cipher_rsa_thread_inst_t *,
+                                           NULL, inst);
+
+                       talloc_free(encrypt_name);
+                       talloc_free(sign_name);
+               }
+               break;
+
+       /*
+        *      Populated by cipher_type_parse() so if
+        *      the value is unrecognised we've got an issue.
+        */
+       default:
+               rad_assert(0);
+               return -1;
+       };
+
+       return 0;
+}
+
+/*
+ *     The module name should be the only globally exported symbol.
+ *     That is, everything else should be 'static'.
+ *
+ *     If the module needs to temporarily modify it's instantiation
+ *     data, the type should be changed to RLM_TYPE_THREAD_UNSAFE.
+ *     The server will then take care of ensuring that the module
+ *     is single-threaded.
+ */
+extern rad_module_t rlm_cipher;
+rad_module_t rlm_cipher = {
+       .magic                  = RLM_MODULE_INIT,
+       .name                   = "cipher",
+       .type                   = RLM_TYPE_THREAD_SAFE,
+       .inst_size              = sizeof(rlm_cipher_t),
+       .config                 = module_config,
+       .bootstrap              = mod_bootstrap,
+       .thread_instantiate     = mod_thread_instantiate,
+};
diff --git a/src/tests/modules/cipher/module.conf b/src/tests/modules/cipher/module.conf
new file mode 100644 (file)
index 0000000..46c9048
--- /dev/null
@@ -0,0 +1,18 @@
+cipher rsa {
+       rsa {
+               private_key_password = whatever
+               private_key_file = $ENV{MODULE_TEST_DIR}/server.key
+               certificate_file = $ENV{MODULE_TEST_DIR}/server.crt
+
+               oaep {
+                       oaep_digest = "sha256"
+                       mgf1_digest = "sha256"
+                       label = "freeradius"
+               }
+
+               signature_digest = "sha256"
+               padding_type = oaep
+       }
+
+       type = rsa
+}
diff --git a/src/tests/modules/cipher/rsa_encrypt_decrypt.unlang b/src/tests/modules/cipher/rsa_encrypt_decrypt.unlang
new file mode 100644 (file)
index 0000000..5d1b57d
--- /dev/null
@@ -0,0 +1,46 @@
+update request {
+       &Tmp-String-0 := "Hello world!"
+}
+
+update request {
+       &Tmp-Octets-0 := "%{cipher_rsa_encrypt:%{Tmp-String-0}}"
+}
+
+if (!&Tmp-Octets-0) {
+       test_fail
+}
+else {
+       test_pass
+}
+
+if (<octets>&Tmp-String-0[0] == &Tmp-Octets-0[0]) {
+       test_fail
+}
+else {
+       test_pass
+}
+
+update request {
+       &Tmp-String-1 := "%{cipher_rsa_decrypt:%{Tmp-Octets-0}}"
+}
+
+if (&Tmp-String-0 != &Tmp-String-1) {
+       test_fail
+}
+else {
+       test_pass
+}
+
+#
+#  Padding scheme should ensure ciphertext is not consistent
+#
+update request {
+       &Tmp-Octets-1 := "%{cipher_rsa_encrypt:%{Tmp-String-0}}"
+}
+
+if (&Tmp-Octets-0 == &Tmp-Octets-1) {
+       test_fail
+}
+else {
+       test_pass
+}
diff --git a/src/tests/modules/cipher/server.crt b/src/tests/modules/cipher/server.crt
new file mode 100644 (file)
index 0000000..ff5f87a
--- /dev/null
@@ -0,0 +1,89 @@
+Certificate:
+    Data:
+        Version: 3 (0x2)
+        Serial Number: 1 (0x1)
+    Signature Algorithm: sha256WithRSAEncryption
+        Issuer: C=FR, ST=Radius, L=Somewhere, O=Example Inc/emailAddress=admin@example.org, CN=Example Certificate Authority
+        Validity
+            Not Before: Feb 21 10:35:43 2018 GMT
+            Not After : Apr 22 10:35:43 2018 GMT
+        Subject: C=FR, ST=Radius, O=Example Inc, CN=Example Server Certificate/emailAddress=admin@example.org
+        Subject Public Key Info:
+            Public Key Algorithm: rsaEncryption
+                Public-Key: (2048 bit)
+                Modulus:
+                    00:c2:fe:ba:f8:2e:3c:0b:a8:a7:fe:1d:14:c2:99:
+                    e8:5b:9e:a2:ec:e1:41:8f:5f:c9:1f:39:5e:ef:29:
+                    d2:66:3c:bf:ab:19:fd:5d:0d:46:8d:d9:77:23:26:
+                    60:cf:25:30:63:f9:01:01:1e:96:74:8e:e9:31:97:
+                    52:44:21:ea:7f:e3:bd:8e:b2:cd:da:55:0a:f7:4f:
+                    7a:82:52:58:be:ed:95:04:a7:ea:ad:81:1b:b5:86:
+                    30:fe:c7:7f:41:ab:db:61:a9:03:19:79:0a:e9:cc:
+                    6d:68:02:56:71:50:f2:25:1d:73:8a:9f:ef:9d:2c:
+                    a7:d3:20:95:b3:0a:41:c4:12:0e:df:60:ac:e9:d8:
+                    64:08:02:95:f8:54:91:18:7e:e2:36:13:84:f6:aa:
+                    cf:0c:c8:64:1c:d8:b8:e4:4e:ee:55:fa:eb:21:80:
+                    40:f0:28:60:52:ab:8a:6d:e4:23:61:bd:ff:cb:24:
+                    da:c5:ff:0e:92:5c:23:fa:c2:f0:84:2f:7b:a4:d8:
+                    cb:a5:33:a6:b0:45:63:c0:d5:ba:d6:8f:40:a2:3b:
+                    31:fd:82:12:59:81:7e:66:8d:19:de:0d:f3:16:07:
+                    86:a6:b2:51:06:b8:84:ca:49:75:fb:99:73:27:77:
+                    c1:53:a6:f6:d2:9c:16:57:4f:e6:1b:a8:27:23:79:
+                    9f:39
+                Exponent: 65537 (0x10001)
+        X509v3 extensions:
+            X509v3 Basic Constraints: 
+                CA:FALSE
+            X509v3 Key Usage: 
+                Digital Signature, Non Repudiation, Key Encipherment
+            X509v3 Extended Key Usage: 
+                TLS Web Server Authentication
+            X509v3 CRL Distribution Points: 
+
+                Full Name:
+                  URI:http://www.example.com/example_ca.crl
+
+            Authority Information Access: 
+                OCSP - URI:http://www.example.org/ocsp
+
+    Signature Algorithm: sha256WithRSAEncryption
+         16:38:3c:13:4d:0a:d9:d2:29:f5:e7:6b:97:7a:ff:61:fb:6a:
+         4f:c6:ad:9c:93:67:16:f9:e4:49:00:92:36:06:80:bb:e4:19:
+         29:82:28:8a:ca:fa:11:d4:d6:14:78:45:50:a5:e7:5f:6d:1c:
+         42:e4:c4:26:92:27:ea:01:a1:34:b7:43:84:5c:52:78:89:1b:
+         6d:0c:f2:ae:92:83:d5:54:82:da:ef:a3:d7:93:f8:58:98:35:
+         6d:24:ce:b9:52:bf:16:52:76:6d:f6:66:a5:4c:76:a5:73:d2:
+         81:fb:0f:3a:45:5b:9e:5e:24:4e:63:cf:15:38:8d:ad:79:98:
+         71:c7:48:e0:c3:fe:a2:86:ed:c1:ac:3e:67:fe:44:45:21:06:
+         f0:a0:33:3e:94:7c:ca:dd:e1:20:f8:b5:18:0b:53:f9:ae:4b:
+         c5:0d:63:73:d5:2a:35:bb:3d:3a:03:28:ea:7e:26:35:98:81:
+         f3:93:9b:81:92:b6:a6:6b:c5:f6:0d:a2:52:54:e5:51:a8:c3:
+         18:ed:45:c9:bc:af:21:76:66:21:fb:2d:e4:7b:a0:96:d3:6f:
+         62:d3:ff:e3:14:35:85:f9:4b:c2:d3:ea:7a:49:00:3d:f7:bd:
+         1c:2f:1a:ba:0c:31:26:65:d7:5a:a7:d7:ce:be:d4:3d:c1:07:
+         aa:58:c6:1e
+-----BEGIN CERTIFICATE-----
+MIIEKzCCAxOgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBkjELMAkGA1UEBhMCRlIx
+DzANBgNVBAgMBlJhZGl1czESMBAGA1UEBwwJU29tZXdoZXJlMRQwEgYDVQQKDAtF
+eGFtcGxlIEluYzEgMB4GCSqGSIb3DQEJARYRYWRtaW5AZXhhbXBsZS5vcmcxJjAk
+BgNVBAMMHUV4YW1wbGUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTE4MDIyMTEw
+MzU0M1oXDTE4MDQyMjEwMzU0M1owezELMAkGA1UEBhMCRlIxDzANBgNVBAgMBlJh
+ZGl1czEUMBIGA1UECgwLRXhhbXBsZSBJbmMxIzAhBgNVBAMMGkV4YW1wbGUgU2Vy
+dmVyIENlcnRpZmljYXRlMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBleGFtcGxlLm9y
+ZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAML+uvguPAuop/4dFMKZ
+6FueouzhQY9fyR85Xu8p0mY8v6sZ/V0NRo3ZdyMmYM8lMGP5AQEelnSO6TGXUkQh
+6n/jvY6yzdpVCvdPeoJSWL7tlQSn6q2BG7WGMP7Hf0Gr22GpAxl5CunMbWgCVnFQ
+8iUdc4qf750sp9MglbMKQcQSDt9grOnYZAgClfhUkRh+4jYThPaqzwzIZBzYuORO
+7lX66yGAQPAoYFKrim3kI2G9/8sk2sX/DpJcI/rC8IQve6TYy6UzprBFY8DVutaP
+QKI7Mf2CElmBfmaNGd4N8xYHhqayUQa4hMpJdfuZcyd3wVOm9tKcFldP5huoJyN5
+nzkCAwEAAaOBoTCBnjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAK
+BggrBgEFBQcDATA2BgNVHR8ELzAtMCugKaAnhiVodHRwOi8vd3d3LmV4YW1wbGUu
+Y29tL2V4YW1wbGVfY2EuY3JsMDcGCCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAYYb
+aHR0cDovL3d3dy5leGFtcGxlLm9yZy9vY3NwMA0GCSqGSIb3DQEBCwUAA4IBAQAW
+ODwTTQrZ0in152uXev9h+2pPxq2ck2cW+eRJAJI2BoC75BkpgiiKyvoR1NYUeEVQ
+pedfbRxC5MQmkifqAaE0t0OEXFJ4iRttDPKukoPVVILa76PXk/hYmDVtJM65Ur8W
+UnZt9malTHalc9KB+w86RVueXiROY88VOI2teZhxx0jgw/6ihu3BrD5n/kRFIQbw
+oDM+lHzK3eEg+LUYC1P5rkvFDWNz1So1uz06AyjqfiY1mIHzk5uBkrama8X2DaJS
+VOVRqMMY7UXJvK8hdmYh+y3ke6CW029i0//jFDWF+UvC0+p6SQA9970cLxq6DDEm
+Zddap9fOvtQ9wQeqWMYe
+-----END CERTIFICATE-----
diff --git a/src/tests/modules/cipher/server.key b/src/tests/modules/cipher/server.key
new file mode 100644 (file)
index 0000000..deb31ec
--- /dev/null
@@ -0,0 +1,30 @@
+-----BEGIN ENCRYPTED PRIVATE KEY-----
+MIIFHzBJBgkqhkiG9w0BBQ0wPDAbBgkqhkiG9w0BBQwwDgQIX+r9DJvjng0CAggA
+MB0GCWCGSAFlAwQBKgQQvW/Ms0F6ZLE6m8rlqPUxMQSCBNA3QYVOcz570/x1PQOz
+WYg8WEFaxp/eC2+hZndjdLFWWSgOr/I17+RW13zD5rWodlUH+zAt4gHpjmGDh6om
+COUDCT6BbZDlV/Fdh9hQ5LP9YXC4nRofxXxr9281rvMaYd1ZBzNRoR2+z0MXW21u
+Y+wN9aVPHm0ghlCK28uItCHU0yPuacG9wvVJesATD8j9qy6vaiH0Y/7a8dZWA9IW
+BWQeGRXzyUnWXdLZFSs/6JBZm2o+RMja7YCHpGcWORDNvN8i4vYgG8/ZbRJcqCUs
+2yegib2qdKAMCuEh8wHGssgoh4WRkeGVInNKRCbHlnBgdCa3dx78ZPnbepgQclhC
+opaU7EshE7BUsy22rnjtWypRxpCSoncUCS0bsubqyyRb1A0TpcVQTuQ9XARhydJd
+zz9YuvaibytWr5mPXd/nvtv5HFIiT4gmNCV9o+xp44MEly6IYUKOMxzva1ATrNoU
+ojUPRPhiicLe+cUqE0Ap1kHM2ddJyWCqvQ1WPvvVMsdBYJBzo+jSXzhFgLCwZ9RU
+KvG31E8VDoU7nbGF+5UUBExvEHM0wuGb71U7aDbBXj0rnR1ReUxcdF2DBbYSMubJ
+Fq98+6OojMSvP1VzbNQfTvhU2qIXAIbk8vFs+66CMq42I5x+Zva6xZGJsB9PlzXb
+fnYqk1YWtipozoDbvcArF0BnVsHj4klmybBmNh+nj4cZAXKAowdVrliE96smCMWl
+6sejZNPyWyz2Nr1tiXNzVASXrpldj41Rm9iTuMZ1x3UihkJ9HnzsQ7NPXXK9q2KV
+icHnHdTo88XR4fudxHBmxSjIExrDx3+PTWwWbkPawxs1ekLp5ECuARLPwwD20/RQ
+tGIGWX2Ez0+nROIlYIZradYXx7BmxIuQftZ+uRQXSsK1p1VROmg1v7nEpI1BXrJW
+6Zw1bJmtos2yo3guDtmtyvP3wjZDJkrFSZwVvYrZ0EuDtMuuWxk/3L3jLoUm7omt
+tHcF7wBQrsEPD8eER9gpU7vKZkenjWateoUKJMSkqp1IKqVpP7BuaZuhZKE3P31y
+22P5LDsubZNsud4iUZDVFr7zl3ERHhflPJdjT1rXRkAjM0937SVaxmW36wXacaUP
+QHkdei9zvPbsLdwAJqwbEJWRs+2aQ3qxYtjtlSPQqbEVgMA84++gqU/XvtJv+ao0
+AFoKq3AE/LB5Hvsswh7ZpQBORoKoZwPY1i/vfmxDR6hXlHYhFZoz2Gra8PCiBqud
+zrrp8gbKH0S0aTOceo/2NmJhhaBHFqgV14IpeJnVsWTfwtUmkItkHM2/s87+fF9Q
+XfNRK78MoAXQVsgeU40WcgbPhDSg7/H87Ms52TQTfNvDi+H3WPRja9V0GhV7KyYG
+Kszg6b8a+DWYy/UzAyKM7O1kqw15wUbops9rWdQ1Clqpccaa8rFu7plsywHUcb0x
+msU3EspnkemLN6VKf1S3EXM/AEmb2rGsgWo4x7Qyadsri9FWBjMhHj5zDf/y4RdK
+givdegKG1i+MCiWwCwDViwAEFeMBKC6shZF5yygZdjVFKahoVVat6V9ZYU/p5Xmi
+xq4R6a5iWfeTk0K9wKtZcjwB/GY5p2q7O4tmHY5EomALO1rN8lzlY0ZfKef3oMak
+TazRKf9MQYyZhX7dMQHF/P1zsA==
+-----END ENCRYPTED PRIVATE KEY-----
diff --git a/src/tests/modules/cipher/server.pem b/src/tests/modules/cipher/server.pem
new file mode 100644 (file)
index 0000000..9ee0eb8
--- /dev/null
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIEKzCCAxOgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBkjELMAkGA1UEBhMCRlIx
+DzANBgNVBAgMBlJhZGl1czESMBAGA1UEBwwJU29tZXdoZXJlMRQwEgYDVQQKDAtF
+eGFtcGxlIEluYzEgMB4GCSqGSIb3DQEJARYRYWRtaW5AZXhhbXBsZS5vcmcxJjAk
+BgNVBAMMHUV4YW1wbGUgQ2VydGlmaWNhdGUgQXV0aG9yaXR5MB4XDTE4MDIyMTEw
+MzU0M1oXDTE4MDQyMjEwMzU0M1owezELMAkGA1UEBhMCRlIxDzANBgNVBAgMBlJh
+ZGl1czEUMBIGA1UECgwLRXhhbXBsZSBJbmMxIzAhBgNVBAMMGkV4YW1wbGUgU2Vy
+dmVyIENlcnRpZmljYXRlMSAwHgYJKoZIhvcNAQkBFhFhZG1pbkBleGFtcGxlLm9y
+ZzCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAML+uvguPAuop/4dFMKZ
+6FueouzhQY9fyR85Xu8p0mY8v6sZ/V0NRo3ZdyMmYM8lMGP5AQEelnSO6TGXUkQh
+6n/jvY6yzdpVCvdPeoJSWL7tlQSn6q2BG7WGMP7Hf0Gr22GpAxl5CunMbWgCVnFQ
+8iUdc4qf750sp9MglbMKQcQSDt9grOnYZAgClfhUkRh+4jYThPaqzwzIZBzYuORO
+7lX66yGAQPAoYFKrim3kI2G9/8sk2sX/DpJcI/rC8IQve6TYy6UzprBFY8DVutaP
+QKI7Mf2CElmBfmaNGd4N8xYHhqayUQa4hMpJdfuZcyd3wVOm9tKcFldP5huoJyN5
+nzkCAwEAAaOBoTCBnjAJBgNVHRMEAjAAMAsGA1UdDwQEAwIF4DATBgNVHSUEDDAK
+BggrBgEFBQcDATA2BgNVHR8ELzAtMCugKaAnhiVodHRwOi8vd3d3LmV4YW1wbGUu
+Y29tL2V4YW1wbGVfY2EuY3JsMDcGCCsGAQUFBwEBBCswKTAnBggrBgEFBQcwAYYb
+aHR0cDovL3d3dy5leGFtcGxlLm9yZy9vY3NwMA0GCSqGSIb3DQEBCwUAA4IBAQAW
+ODwTTQrZ0in152uXev9h+2pPxq2ck2cW+eRJAJI2BoC75BkpgiiKyvoR1NYUeEVQ
+pedfbRxC5MQmkifqAaE0t0OEXFJ4iRttDPKukoPVVILa76PXk/hYmDVtJM65Ur8W
+UnZt9malTHalc9KB+w86RVueXiROY88VOI2teZhxx0jgw/6ihu3BrD5n/kRFIQbw
+oDM+lHzK3eEg+LUYC1P5rkvFDWNz1So1uz06AyjqfiY1mIHzk5uBkrama8X2DaJS
+VOVRqMMY7UXJvK8hdmYh+y3ke6CW029i0//jFDWF+UvC0+p6SQA9970cLxq6DDEm
+Zddap9fOvtQ9wQeqWMYe
+-----END CERTIFICATE-----