Add support for SHA-2 digests
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 6 Feb 2014 15:50:28 +0000 (15:50 +0000)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 6 Feb 2014 16:01:29 +0000 (16:01 +0000)
share/dictionary.freeradius.internal
src/include/radius.h
src/main/threads.c
src/main/tls.c
src/modules/rlm_expr/rlm_expr.c
src/modules/rlm_pap/rlm_pap.c
src/tests/keywords/sha2 [new file with mode: 0644]

index 3ee56bf..abaf153 100644 (file)
@@ -120,6 +120,7 @@ ATTRIBUTE   Packet-Dst-Port                         1087    integer
 ATTRIBUTE      Packet-Authentication-Vector            1088    octets
 ATTRIBUTE      Time-Of-Day                             1089    string
 ATTRIBUTE      Request-Processing-Stage                1090    string
+ATTRIBUTE      SHA2-Password                           1092    octets
 ATTRIBUTE      SHA-Password                            1093    octets
 ATTRIBUTE      SSHA-Password                           1094    octets
 ATTRIBUTE      SHA1-Password                           1093    octets
index bfef4bb..6247262 100644 (file)
@@ -224,6 +224,7 @@ typedef enum {
 #define PW_TIME_OF_DAY                 1089
 #define PW_REQUEST_PROCESSING_STAGE    1090
 
+#define PW_SHA2_PASSWORD               1092
 #define PW_SHA_PASSWORD                        1093
 #define PW_SSHA_PASSWORD               1094
 #define PW_MD5_PASSWORD                        1095
index e130901..e2d0cf1 100644 (file)
@@ -243,13 +243,6 @@ static int setup_ssl_mutexes(void)
 {
        int i;
 
-#ifdef HAVE_OPENSSL_EVP_H
-       /*
-        *      Enable all ciphers and digests.
-        */
-       OpenSSL_add_all_algorithms();
-#endif
-
        ssl_mutexes = rad_malloc(CRYPTO_num_locks() * sizeof(pthread_mutex_t));
        if (!ssl_mutexes) {
                ERROR("Error allocating memory for SSL mutexes!");
index 03352e5..30e9fa7 100644 (file)
@@ -1909,8 +1909,11 @@ static void sess_free_vps(UNUSED void *parent, void *data_ptr,
  */
 void tls_global_init(void)
 {
-       SSL_library_init();
-       SSL_load_error_strings();
+       SSL_load_error_strings();       /* readable error messages (examples show call before library_init) */
+       SSL_library_init();             /* initialize library */
+#ifdef HAVE_OPENSSL_EVP_H
+       OpenSSL_add_all_algorithms();   /* required for SHA2 in OpenSSL < 0.9.8o and 1.0.0.a */
+#endif
 }
 
 /*
index 59cabac..d36e9a9 100644 (file)
@@ -22,6 +22,7 @@
  * @copyright 2002  Alan DeKok <aland@ox.org>
  */
 RCSID("$Id$")
+USES_APPLE_DEPRECATED_API
 
 #include <freeradius-devel/radiusd.h>
 #include <freeradius-devel/md5.h>
@@ -29,6 +30,10 @@ RCSID("$Id$")
 #include <freeradius-devel/base64.h>
 #include <freeradius-devel/modules.h>
 
+#ifdef HAVE_OPENSSL_EVP_H
+#  include <openssl/evp.h>
+#endif
+
 #include <ctype.h>
 
 #include "rlm_expr.h"
@@ -586,6 +591,63 @@ static ssize_t md5_xlat(UNUSED void *instance, UNUSED REQUEST *request,
        return strlen(out);
 }
 
+/** Calculate any digest supported by OpenSSL EVP_MD
+ *
+ * Example: "%{sha256:foo}" == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
+ */
+#ifdef HAVE_OPENSSL_EVP_H
+static ssize_t evp_md_xlat(UNUSED void *instance, UNUSED REQUEST *request,
+                           char const *fmt, char *out, size_t outlen, EVP_MD const *md)
+{
+       uint8_t digest[EVP_MAX_MD_SIZE];
+       unsigned int digestlen;
+       ssize_t i, len, inlen;
+       uint8_t const *p;
+
+       EVP_MD_CTX *ctx;
+
+        /*
+         *      We need room for at least one octet of output.
+         */
+        if (outlen < 3) {
+                *out = '\0';
+                return 0;
+        }
+
+       inlen = xlat_fmt_to_ref(&p, request, fmt);
+       if (inlen < 0) {
+               return -1;
+       }
+
+       ctx = EVP_MD_CTX_create();
+       EVP_DigestInit_ex(ctx, md, NULL);
+       EVP_DigestUpdate(ctx, p, inlen);
+       EVP_DigestFinal_ex(ctx, digest, &digestlen);
+       EVP_MD_CTX_destroy(ctx);
+
+        /*
+         *      Each digest octet takes two hex digits, plus one for
+         *      the terminating NUL.
+         */
+        len = (outlen / 2) - 1;
+        if (len > digestlen) len = digestlen;
+
+       for (i = 0; i < len; i++) {
+               snprintf(out + i * 2, 3, "%02x", digest[i]);
+       }
+       return strlen(out);
+}
+
+#  define EVP_MD_XLAT(_md) \
+static ssize_t _md##_xlat(UNUSED void *instance, UNUSED REQUEST *request, char const *fmt, char *out, size_t outlen)\
+{\
+       return evp_md_xlat(instance, request, fmt, out, outlen, EVP_##_md());\
+}
+
+EVP_MD_XLAT(sha256);
+EVP_MD_XLAT(sha512);
+#endif
+
 /** Calculate the SHA1 hash of a string or attribute.
  *
  * Example: "%{sha1:foo}" == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33"
@@ -719,6 +781,10 @@ static int mod_instantiate(CONF_SECTION *conf, void *instance)
        xlat_register("tolower", lc_xlat, NULL, inst);
        xlat_register("toupper", uc_xlat, NULL, inst);
        xlat_register("md5", md5_xlat, NULL, inst);
+#ifdef HAVE_OPENSSL_EVP_H
+       xlat_register("sha256", sha256_xlat, NULL, inst);
+       xlat_register("sha512", sha512_xlat, NULL, inst);
+#endif
        xlat_register("sha1", sha1_xlat, NULL, inst);
        xlat_register("base64", base64_xlat, NULL, inst);
        xlat_register("base64tohex", base64_to_hex_xlat, NULL, inst);
index d1724d6..760b45d 100644 (file)
@@ -23,6 +23,7 @@
  * @copyright 2001       Kostas Kalevras <kkalev@noc.ntua.gr>
  */
 RCSID("$Id$")
+USES_APPLE_DEPRECATED_API
 
 #include <freeradius-devel/radiusd.h>
 #include <freeradius-devel/modules.h>
@@ -33,6 +34,10 @@ RCSID("$Id$")
 #include "../../include/md5.h"
 #include "../../include/sha1.h"
 
+#ifdef HAVE_OPENSSL_EVP_H
+#  include <openssl/evp.h>
+#endif
+
 /*
  *      Define a structure for our module configuration.
  *
@@ -75,6 +80,11 @@ static const FR_NAME_NUMBER header_names[] = {
        { "{base64_md5}",       PW_MD5_PASSWORD },
        { "{smd5}",             PW_SMD5_PASSWORD },
        { "{crypt}",            PW_CRYPT_PASSWORD },
+#ifdef HAVE_OPENSSL_EVP_H
+       { "{sha2}",             PW_SHA2_PASSWORD },
+       { "{sha256}",           PW_SHA2_PASSWORD },
+       { "{sha512}",           PW_SHA2_PASSWORD },
+#endif
        { "{sha}",              PW_SHA_PASSWORD },
        { "{ssha}",             PW_SSHA_PASSWORD },
        { "{nt}",               PW_NT_PASSWORD },
@@ -267,8 +277,14 @@ static rlm_rcode_t mod_authorize(void *instance, REQUEST *request)
                        found_pw = true;
                        break;
 
+#ifdef HAVE_OPENSSL_EVP_H
+               case PW_SHA2_PASSWORD:
+                       if (inst->normify) {
+                               normify(request, vp, 28); /* ensure it's in the right format */
+                       }
                        found_pw = true;
                        break;
+#endif
 
                case PW_SHA_PASSWORD:
                case PW_SSHA_PASSWORD:
@@ -520,11 +536,71 @@ static int pap_auth_ssha(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
 
        return RLM_MODULE_OK;
 }
+
+#ifdef HAVE_OPENSSL_EVP_H
+static int pap_auth_sha2(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
+{
+       EVP_MD_CTX *ctx;
+       EVP_MD const *md;
+       char const *name;
+       uint8_t digest[EVP_MAX_MD_SIZE];
+       unsigned int digestlen;
+
+       if (inst->normify) {
+               normify(request, vp, 28);
+       }
+
+       /*
+        *      All the SHA-2 algorithms produce digests of different lengths,
+        *      so it's trivial to determine which EVP_MD to use.
+        */
+       switch (vp->length) {
+       /* SHA-224 */
+       case 28:
+               name = "SHA-224";
+               md = EVP_sha224();
+               break;
+
+       /* SHA-256 */
+       case 32:
+               name = "SHA-256";
+               md = EVP_sha256();
+               break;
+
+       /* SHA-384 */
+       case 48:
+               name = "SHA-384";
+               md = EVP_sha384();
+               break;
+
+       /* SHA-512 */
+       case 64:
+               name = "SHA-512";
+               md = EVP_sha512();
+               break;
+
+       default:
+               REDEBUG("\"known good\" digest length (%zu) does not match output length of any SHA-2 digests",
+                       vp->length);
+               return RLM_MODULE_INVALID;
+       }
+
+       ctx = EVP_MD_CTX_create();
+       EVP_DigestInit_ex(ctx, md, NULL);
+       EVP_DigestUpdate(ctx, request->password->vp_octets, request->password->length);
+       EVP_DigestFinal_ex(ctx, digest, &digestlen);
+       EVP_MD_CTX_destroy(ctx);
+
+       fr_assert(digestlen == (size_t) vp->length);    /* This would be an OpenSSL bug... */
+
+       if (rad_digest_cmp(digest, vp->vp_octets, vp->length) != 0) {
+               REDEBUG("%s digest does not match \"known good\" digest", name);
                return RLM_MODULE_REJECT;
        }
 
        return RLM_MODULE_OK;
 }
+#endif
 
 static int pap_auth_nt(rlm_pap_t *inst, REQUEST *request, VALUE_PAIR *vp)
 {
@@ -697,6 +773,12 @@ static rlm_rcode_t mod_authenticate(void *instance, REQUEST *request)
                        auth_func = &pap_auth_smd5;
                        break;
 
+#ifdef HAVE_OPENSSL_EVP_H
+               case PW_SHA2_PASSWORD:
+                       auth_func = &pap_auth_sha2;
+                       break;
+#endif
+
                case PW_SHA_PASSWORD:
                        auth_func = &pap_auth_sha;
                        break;
diff --git a/src/tests/keywords/sha2 b/src/tests/keywords/sha2
new file mode 100644 (file)
index 0000000..d3bd1a9
--- /dev/null
@@ -0,0 +1,71 @@
+#
+# PRE: update if
+#
+update {
+       control:Cleartext-Password := 'hello'
+       request:Tmp-String-0 := "This is a string\n"
+       request:Tmp-Octets-0 := 0x000504030201
+}
+
+update reply {
+       Filter-Id := 'filter'
+}
+
+#
+#  Put "This is a string" into a file and call "sha1sum" on it.
+#  You should get this string.
+#
+if ("%{sha256:This is a string\n}" != 'b3716a1ab53042bb392034f29071e13b0c38aa19b4edd75d9a76022f91189124') {
+       update reply {
+               Filter-Id += 'fail'
+       }
+}
+
+if ("%{sha256:&Tmp-String-0}" != 'b3716a1ab53042bb392034f29071e13b0c38aa19b4edd75d9a76022f91189124') {
+       update reply {
+               Filter-Id += 'fail'
+       }
+}
+
+if ("%{sha256:&request:Tmp-String-0}" != 'b3716a1ab53042bb392034f29071e13b0c38aa19b4edd75d9a76022f91189124') {
+       update reply {
+               Filter-Id += 'fail'
+       }
+}
+
+if ("%{sha256:%{Tmp-String-0}}" != 'b3716a1ab53042bb392034f29071e13b0c38aa19b4edd75d9a76022f91189124') {
+       update reply {
+               Filter-Id += 'fail'
+       }
+}
+
+#
+#  SHA256 should also be able to cope with references to octet attributes
+#
+if ("%{sha256:&request:Tmp-Octets-0}" != 'f307e202b881fded70e58017aa0c4d7b29c76ab25d02bf078301a5f6635187eb') {
+       update reply {
+               Filter-Id += 'fail'
+       }
+}
+
+#
+#  SHA512 and SHA256 share common code paths, so the tests don't need to be
+#  as exhaustive.
+#
+if ("%{sha512:This is a string\n}" != '56b57df5cce42d4e35c644649798ea23ec16f4f4626e78faf4d2d8f430ea349bcc28cd5532457c82f0aa66bf68988346039fe75b900a92ff94fd53993d45990f') {
+       update reply {
+               Filter-Id += 'fail'
+       }
+}
+
+if ("%{sha512:&Tmp-String-0}" != '56b57df5cce42d4e35c644649798ea23ec16f4f4626e78faf4d2d8f430ea349bcc28cd5532457c82f0aa66bf68988346039fe75b900a92ff94fd53993d45990f') {
+       update reply {
+               Filter-Id += 'fail'
+       }
+}
+
+if ("%{sha512:&request:Tmp-Octets-0}" != 'de80271eb5e03a1c24dd0cd823a22305a743ee3a54f1de5bf97adbf56984561154bfb6928b1da4ccc3f5dde9f4032ad461937b60b9ace4ad3898cf45c90596d7') {
+       update reply {
+               Filter-Id += 'fail'
+       }
+}