Coverage Report

Created: 2025-12-14 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/freeradius-server/src/lib/util/hmac_md5.c
Line
Count
Source
1
/*
2
 *   This library is free software; you can redistribute it and/or
3
 *   modify it under the terms of the GNU Lesser General Public
4
 *   License as published by the Free Software Foundation; either
5
 *   version 2.1 of the License, or (at your option) any later version.
6
 *
7
 *   This library is distributed in the hope that it will be useful,
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
10
 *   Lesser General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU Lesser General Public
13
 *   License along with this library; if not, write to the Free Software
14
 *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15
 */
16
17
/** MD5 HMAC not dependent on OpenSSL
18
 *
19
 * @file src/lib/util/hmac_md5.c
20
 *
21
 * @note New code that needs fast or incremental HMACs should use the OpenSSL EVP_* HMAC
22
 *  interface instead, as that can take advantage of acceleration instructions provided
23
 *  by various CPUs (and provides an incremental hashing interface).
24
 *
25
 * For the sake of illustration we provide the following sample code for the implementation
26
 * of HMAC-MD5 as well as some corresponding test vectors (the code is based on MD5 code as
27
 * described in [MD5]).
28
 *
29
 * @copyright 2000,2006 The FreeRADIUS server project
30
 */
31
RCSID("$Id: 3432d82e70ccc491bbd10bf6cd63363adb638d4b $")
32
33
#include <freeradius-devel/util/atexit.h>
34
#include <freeradius-devel/util/md5.h>
35
#include <freeradius-devel/util/strerror.h>
36
37
#ifdef HAVE_OPENSSL_EVP_H
38
#  include <freeradius-devel/tls/openssl_user_macros.h>
39
#  include <openssl/hmac.h>
40
41
static _Thread_local EVP_MD_CTX *md5_hmac_ctx;
42
43
static int _hmac_md5_ctx_free_on_exit(void *arg)
44
1
{
45
1
  EVP_MD_CTX_free(arg);
46
1
  return 0;
47
1
}
48
49
/** Calculate HMAC using OpenSSL's MD5 implementation
50
 *
51
 * @param digest Caller digest to be filled in.
52
 * @param in Pointer to data stream.
53
 * @param inlen length of data stream.
54
 * @param key Pointer to authentication key.
55
 * @param key_len Length of authentication key.
56
 * @return
57
 *  - 0 on success.
58
 *      - -1 on error.
59
 */
60
int fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *in, size_t inlen,
61
    uint8_t const *key, size_t key_len)
62
9
{
63
9
  EVP_MD_CTX *ctx;
64
9
  EVP_PKEY *pkey;
65
66
9
  if (unlikely(!md5_hmac_ctx)) {
67
1
    ctx = EVP_MD_CTX_new();
68
1
    if (unlikely(!ctx)) {
69
0
      fr_strerror_const("Failed allocating EVP_MD_CTX for HMAC-MD5");
70
0
      return -1;
71
0
    }
72
1
    EVP_MD_CTX_set_flags(ctx, EVP_MD_CTX_FLAG_ONESHOT);
73
1
    fr_atexit_thread_local(md5_hmac_ctx, _hmac_md5_ctx_free_on_exit, ctx);
74
8
  } else {
75
8
    ctx = md5_hmac_ctx;
76
8
  }
77
78
9
  pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, key, key_len);
79
9
  if (unlikely(pkey == NULL)) {
80
0
    fr_strerror_const("Failed allocating pkey for HMAC-MD5");
81
0
    return -1;
82
0
  }
83
84
9
  if (unlikely(EVP_DigestSignInit(ctx, NULL, EVP_md5(), NULL, pkey) != 1)) {
85
0
    fr_strerror_const("Failed initialising EVP_MD_CTX for HMAC-MD5");
86
0
  error:
87
0
    EVP_PKEY_free(pkey);
88
0
    return -1;
89
0
  }
90
9
  if (unlikely(EVP_DigestSignUpdate(ctx, in, inlen) != 1)) {
91
0
    fr_strerror_const("Failed ingesting data for HMAC-MD5");
92
0
    goto error;
93
0
  }
94
  /*
95
   *  OpenSSL <= 1.1.1 requires a non-null pointer for len
96
   */
97
9
  if (unlikely(EVP_DigestSignFinal(ctx, digest, &(size_t){ MD5_DIGEST_LENGTH }) != 1)) {
98
0
    fr_strerror_const("Failed finalising HMAC-MD5");
99
0
    goto error;
100
0
  }
101
102
9
  EVP_PKEY_free(pkey);
103
9
  EVP_MD_CTX_reset(ctx);
104
105
9
  return 0;
106
9
}
107
#else
108
/** Calculate HMAC using internal MD5 implementation
109
 *
110
 * @param digest Caller digest to be filled in.
111
 * @param in Pointer to data stream.
112
 * @param inlen length of data stream.
113
 * @param key Pointer to authentication key.
114
 * @param key_len Length of authentication key.
115
 * @return
116
 *  - 0 on success.
117
 *      - -1 on error.
118
 */
119
int fr_hmac_md5(uint8_t digest[MD5_DIGEST_LENGTH], uint8_t const *in, size_t inlen,
120
    uint8_t const *key, size_t key_len)
121
{
122
  fr_md5_ctx_t  *ctx;
123
  uint8_t   k_ipad[65];    /* inner padding - key XORd with ipad */
124
  uint8_t   k_opad[65];    /* outer padding - key XORd with opad */
125
  uint8_t   tk[16];
126
  int i;
127
128
  ctx = fr_md5_ctx_alloc_from_list();
129
130
  /* if key is longer than 64 bytes reset it to key=MD5(key) */
131
  if (key_len > 64) {
132
    fr_md5_update(ctx, key, key_len);
133
    fr_md5_final(tk, ctx);
134
    fr_md5_ctx_reset(ctx);
135
136
    key = tk;
137
    key_len = 16;
138
  }
139
140
  /*
141
   * the HMAC_MD5 transform looks like:
142
   *
143
   * MD5(K XOR opad, MD5(K XOR ipad, in))
144
   *
145
   * where K is an n byte key
146
   * ipad is the byte 0x36 repeated 64 times
147
148
   * opad is the byte 0x5c repeated 64 times
149
   * and in is the data being protected
150
   */
151
152
  /* start out by storing key in pads */
153
  memset(k_ipad, 0, sizeof(k_ipad));
154
  memset(k_opad, 0, sizeof(k_opad));
155
  memcpy(k_ipad, key, key_len);
156
  memcpy(k_opad, key, key_len);
157
158
  /* XOR key with ipad and opad values */
159
  for (i = 0; i < 64; i++) {
160
    k_ipad[i] ^= 0x36;
161
    k_opad[i] ^= 0x5c;
162
  }
163
  /*
164
   * perform inner MD5
165
   */
166
  fr_md5_update(ctx, k_ipad, 64);   /* start with inner pad */
167
  fr_md5_update(ctx, in, inlen);    /* then in of datagram */
168
  fr_md5_final(digest, ctx);    /* finish up 1st pass */
169
170
171
  /*
172
   * perform outer MD5
173
   */
174
  fr_md5_ctx_reset(ctx);
175
  fr_md5_update(ctx, k_opad, 64);   /* start with outer pad */
176
  fr_md5_update(ctx, digest, 16);   /* then results of 1st hash */
177
  fr_md5_final(digest, ctx);    /* finish up 2nd pass */
178
179
  fr_md5_ctx_free_from_list(&ctx);
180
181
  return 0;
182
}
183
#endif /* HAVE_OPENSSL_EVP_H */