| 1 | /* digesthmac.c --- Compute DIGEST-MD5 response value. |
| 2 | * Copyright (C) 2002-2012 Simon Josefsson |
| 3 | * |
| 4 | * This file is part of GNU SASL Library. |
| 5 | * |
| 6 | * GNU SASL Library is free software; you can redistribute it and/or |
| 7 | * modify it under the terms of the GNU Lesser General Public License |
| 8 | * as published by the Free Software Foundation; either version 2.1 of |
| 9 | * the License, or (at your option) any later version. |
| 10 | * |
| 11 | * GNU SASL Library is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| 14 | * Lesser General Public License for more details. |
| 15 | * |
| 16 | * You should have received a copy of the GNU Lesser General Public |
| 17 | * License along with GNU SASL Library; if not, write to the Free |
| 18 | * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
| 19 | * Boston, MA 02110-1301, USA. |
| 20 | * |
| 21 | */ |
| 22 | |
| 23 | #ifdef HAVE_CONFIG_H |
| 24 | #include "config.h" |
| 25 | #endif |
| 26 | |
| 27 | /* Get specification. */ |
| 28 | #include "digesthmac.h" |
| 29 | |
| 30 | /* Get malloc, free. */ |
| 31 | #include <stdlib.h> |
| 32 | |
| 33 | /* Get memcpy, strlen. */ |
| 34 | #include <string.h> |
| 35 | |
| 36 | /* Get sprintf. */ |
| 37 | #include <stdio.h> |
| 38 | |
| 39 | /* Get gc_md5. */ |
| 40 | #include <gc.h> |
| 41 | |
| 42 | #define HEXCHAR(c) ((c & 0x0F) > 9 ? 'a' + (c & 0x0F) - 10 : '0' + (c & 0x0F)) |
| 43 | |
| 44 | #define QOP_AUTH "auth" |
| 45 | #define QOP_AUTH_INT "auth-int" |
| 46 | #define QOP_AUTH_CONF "auth-conf" |
| 47 | |
| 48 | #define A2_PRE "AUTHENTICATE:" |
| 49 | #define A2_POST ":00000000000000000000000000000000" |
| 50 | #define COLON ":" |
| 51 | #define MD5LEN 16 |
| 52 | #define DERIVE_CLIENT_INTEGRITY_KEY_STRING \ |
| 53 | "Digest session key to client-to-server signing key magic constant" |
| 54 | #define DERIVE_CLIENT_INTEGRITY_KEY_STRING_LEN 65 |
| 55 | #define DERIVE_SERVER_INTEGRITY_KEY_STRING \ |
| 56 | "Digest session key to server-to-client signing key magic constant" |
| 57 | #define DERIVE_SERVER_INTEGRITY_KEY_STRING_LEN 65 |
| 58 | #define DERIVE_CLIENT_CONFIDENTIALITY_KEY_STRING \ |
| 59 | "Digest H(A1) to client-to-server sealing key magic constant" |
| 60 | #define DERIVE_CLIENT_CONFIDENTIALITY_KEY_STRING_LEN 59 |
| 61 | #define DERIVE_SERVER_CONFIDENTIALITY_KEY_STRING \ |
| 62 | "Digest H(A1) to server-to-client sealing key magic constant" |
| 63 | #define DERIVE_SERVER_CONFIDENTIALITY_KEY_STRING_LEN 59 |
| 64 | |
| 65 | /* Compute in 33 bytes large array OUTPUT the DIGEST-MD5 response |
| 66 | value. SECRET holds the 16 bytes MD5 hash SS, i.e., |
| 67 | H(username:realm:passwd). NONCE is a zero terminated string with |
| 68 | the server nonce. NC is the nonce-count, typically 1 for initial |
| 69 | authentication. CNONCE is a zero terminated string with the client |
| 70 | nonce. QOP is the quality of protection to use. AUTHZID is a zero |
| 71 | terminated string with the authorization identity. DIGESTURI is a |
| 72 | zero terminated string with the server principal (e.g., |
| 73 | imap/mail.example.org). RSPAUTH is a boolean which indicate |
| 74 | whether to compute a value for the RSPAUTH response or the "real" |
| 75 | authentication. CIPHER is the cipher to use. KIC, KIS, KCC, KCS |
| 76 | are either NULL, or points to 16 byte arrays that will hold the |
| 77 | computed keys on output. Returns 0 on success. */ |
| 78 | int |
| 79 | digest_md5_hmac (char *output, char secret[MD5LEN], const char *nonce, |
| 80 | unsigned long nc, const char *cnonce, digest_md5_qop qop, |
| 81 | const char *authzid, const char *digesturi, int rspauth, |
| 82 | digest_md5_cipher cipher, |
| 83 | char *kic, char *kis, char *kcc, char *kcs) |
| 84 | { |
| 85 | const char *a2string = rspauth ? COLON : A2_PRE; |
| 86 | char nchex[9]; |
| 87 | char a1hexhash[2 * MD5LEN]; |
| 88 | char a2hexhash[2 * MD5LEN]; |
| 89 | char hash[MD5LEN]; |
| 90 | char *tmp, *p; |
| 91 | size_t tmplen; |
| 92 | int rc; |
| 93 | int i; |
| 94 | |
| 95 | /* A1 */ |
| 96 | |
| 97 | tmplen = MD5LEN + strlen (COLON) + strlen (nonce) + |
| 98 | strlen (COLON) + strlen (cnonce); |
| 99 | if (authzid && strlen (authzid) > 0) |
| 100 | tmplen += strlen (COLON) + strlen (authzid); |
| 101 | |
| 102 | p = tmp = malloc (tmplen); |
| 103 | if (tmp == NULL) |
| 104 | return -1; |
| 105 | |
| 106 | memcpy (p, secret, MD5LEN); |
| 107 | p += MD5LEN; |
| 108 | memcpy (p, COLON, strlen (COLON)); |
| 109 | p += strlen (COLON); |
| 110 | memcpy (p, nonce, strlen (nonce)); |
| 111 | p += strlen (nonce); |
| 112 | memcpy (p, COLON, strlen (COLON)); |
| 113 | p += strlen (COLON); |
| 114 | memcpy (p, cnonce, strlen (cnonce)); |
| 115 | p += strlen (cnonce); |
| 116 | if (authzid && strlen (authzid) > 0) |
| 117 | { |
| 118 | memcpy (p, COLON, strlen (COLON)); |
| 119 | p += strlen (COLON); |
| 120 | memcpy (p, authzid, strlen (authzid)); |
| 121 | p += strlen (authzid); |
| 122 | } |
| 123 | |
| 124 | rc = gc_md5 (tmp, tmplen, hash); |
| 125 | free (tmp); |
| 126 | if (rc) |
| 127 | return rc; |
| 128 | |
| 129 | if (kic) |
| 130 | { |
| 131 | char hash2[MD5LEN]; |
| 132 | char q[MD5LEN + DERIVE_CLIENT_INTEGRITY_KEY_STRING_LEN]; |
| 133 | size_t qlen = MD5LEN + DERIVE_CLIENT_INTEGRITY_KEY_STRING_LEN; |
| 134 | |
| 135 | memcpy (q, hash, MD5LEN); |
| 136 | memcpy (q + MD5LEN, DERIVE_CLIENT_INTEGRITY_KEY_STRING, |
| 137 | DERIVE_CLIENT_INTEGRITY_KEY_STRING_LEN); |
| 138 | |
| 139 | rc = gc_md5 (q, qlen, hash2); |
| 140 | if (rc) |
| 141 | return rc; |
| 142 | |
| 143 | memcpy (kic, hash2, MD5LEN); |
| 144 | } |
| 145 | |
| 146 | if (kis) |
| 147 | { |
| 148 | char hash2[MD5LEN]; |
| 149 | char q[MD5LEN + DERIVE_SERVER_INTEGRITY_KEY_STRING_LEN]; |
| 150 | size_t qlen = MD5LEN + DERIVE_SERVER_INTEGRITY_KEY_STRING_LEN; |
| 151 | |
| 152 | memcpy (q, hash, MD5LEN); |
| 153 | memcpy (q + MD5LEN, DERIVE_SERVER_INTEGRITY_KEY_STRING, |
| 154 | DERIVE_SERVER_INTEGRITY_KEY_STRING_LEN); |
| 155 | |
| 156 | rc = gc_md5 (q, qlen, hash2); |
| 157 | if (rc) |
| 158 | return rc; |
| 159 | |
| 160 | memcpy (kis, hash2, MD5LEN); |
| 161 | } |
| 162 | |
| 163 | if (kcc) |
| 164 | { |
| 165 | char hash2[MD5LEN]; |
| 166 | int n; |
| 167 | char q[MD5LEN + DERIVE_CLIENT_CONFIDENTIALITY_KEY_STRING_LEN]; |
| 168 | |
| 169 | if (cipher == DIGEST_MD5_CIPHER_RC4_40) |
| 170 | n = 5; |
| 171 | else if (cipher == DIGEST_MD5_CIPHER_RC4_56) |
| 172 | n = 7; |
| 173 | else |
| 174 | n = MD5LEN; |
| 175 | |
| 176 | memcpy (q, hash, n); |
| 177 | memcpy (q + n, DERIVE_CLIENT_CONFIDENTIALITY_KEY_STRING, |
| 178 | DERIVE_CLIENT_CONFIDENTIALITY_KEY_STRING_LEN); |
| 179 | |
| 180 | rc = gc_md5 (q, n + DERIVE_CLIENT_CONFIDENTIALITY_KEY_STRING_LEN, |
| 181 | hash2); |
| 182 | if (rc) |
| 183 | return rc; |
| 184 | |
| 185 | memcpy (kcc, hash2, MD5LEN); |
| 186 | } |
| 187 | |
| 188 | if (kcs) |
| 189 | { |
| 190 | char hash2[MD5LEN]; |
| 191 | int n; |
| 192 | char q[MD5LEN + DERIVE_SERVER_CONFIDENTIALITY_KEY_STRING_LEN]; |
| 193 | |
| 194 | if (cipher == DIGEST_MD5_CIPHER_RC4_40) |
| 195 | n = 5; |
| 196 | else if (cipher == DIGEST_MD5_CIPHER_RC4_56) |
| 197 | n = 7; |
| 198 | else |
| 199 | n = MD5LEN; |
| 200 | |
| 201 | memcpy (q, hash, n); |
| 202 | memcpy (q + n, DERIVE_SERVER_CONFIDENTIALITY_KEY_STRING, |
| 203 | DERIVE_SERVER_CONFIDENTIALITY_KEY_STRING_LEN); |
| 204 | |
| 205 | rc = gc_md5 (q, n + DERIVE_SERVER_CONFIDENTIALITY_KEY_STRING_LEN, |
| 206 | hash2); |
| 207 | if (rc) |
| 208 | return rc; |
| 209 | |
| 210 | memcpy (kcs, hash2, MD5LEN); |
| 211 | } |
| 212 | |
| 213 | for (i = 0; i < MD5LEN; i++) |
| 214 | { |
| 215 | a1hexhash[2 * i + 1] = HEXCHAR (hash[i]); |
| 216 | a1hexhash[2 * i + 0] = HEXCHAR (hash[i] >> 4); |
| 217 | } |
| 218 | |
| 219 | /* A2 */ |
| 220 | |
| 221 | tmplen = strlen (a2string) + strlen (digesturi); |
| 222 | if (qop & DIGEST_MD5_QOP_AUTH_INT || qop & DIGEST_MD5_QOP_AUTH_CONF) |
| 223 | tmplen += strlen (A2_POST); |
| 224 | |
| 225 | p = tmp = malloc (tmplen); |
| 226 | if (tmp == NULL) |
| 227 | return -1; |
| 228 | |
| 229 | memcpy (p, a2string, strlen (a2string)); |
| 230 | p += strlen (a2string); |
| 231 | memcpy (p, digesturi, strlen (digesturi)); |
| 232 | p += strlen (digesturi); |
| 233 | if (qop & DIGEST_MD5_QOP_AUTH_INT || qop & DIGEST_MD5_QOP_AUTH_CONF) |
| 234 | memcpy (p, A2_POST, strlen (A2_POST)); |
| 235 | |
| 236 | rc = gc_md5 (tmp, tmplen, hash); |
| 237 | free (tmp); |
| 238 | if (rc) |
| 239 | return rc; |
| 240 | |
| 241 | for (i = 0; i < MD5LEN; i++) |
| 242 | { |
| 243 | a2hexhash[2 * i + 1] = HEXCHAR (hash[i]); |
| 244 | a2hexhash[2 * i + 0] = HEXCHAR (hash[i] >> 4); |
| 245 | } |
| 246 | |
| 247 | /* response_value */ |
| 248 | |
| 249 | sprintf (nchex, "%08lx" , nc); |
| 250 | |
| 251 | tmplen = 2 * MD5LEN + strlen (COLON) + strlen (nonce) + strlen (COLON) + |
| 252 | strlen (nchex) + strlen (COLON) + strlen (cnonce) + strlen (COLON); |
| 253 | if (qop & DIGEST_MD5_QOP_AUTH_CONF) |
| 254 | tmplen += strlen (QOP_AUTH_CONF); |
| 255 | else if (qop & DIGEST_MD5_QOP_AUTH_INT) |
| 256 | tmplen += strlen (QOP_AUTH_INT); |
| 257 | else if (qop & DIGEST_MD5_QOP_AUTH) |
| 258 | tmplen += strlen (QOP_AUTH); |
| 259 | tmplen += strlen (COLON) + 2 * MD5LEN; |
| 260 | |
| 261 | p = tmp = malloc (tmplen); |
| 262 | if (tmp == NULL) |
| 263 | return -1; |
| 264 | |
| 265 | memcpy (p, a1hexhash, 2 * MD5LEN); |
| 266 | p += 2 * MD5LEN; |
| 267 | memcpy (p, COLON, strlen (COLON)); |
| 268 | p += strlen (COLON); |
| 269 | memcpy (p, nonce, strlen (nonce)); |
| 270 | p += strlen (nonce); |
| 271 | memcpy (p, COLON, strlen (COLON)); |
| 272 | p += strlen (COLON); |
| 273 | memcpy (p, nchex, strlen (nchex)); |
| 274 | p += strlen (nchex); |
| 275 | memcpy (p, COLON, strlen (COLON)); |
| 276 | p += strlen (COLON); |
| 277 | memcpy (p, cnonce, strlen (cnonce)); |
| 278 | p += strlen (cnonce); |
| 279 | memcpy (p, COLON, strlen (COLON)); |
| 280 | p += strlen (COLON); |
| 281 | if (qop & DIGEST_MD5_QOP_AUTH_CONF) |
| 282 | { |
| 283 | memcpy (p, QOP_AUTH_CONF, strlen (QOP_AUTH_CONF)); |
| 284 | p += strlen (QOP_AUTH_CONF); |
| 285 | } |
| 286 | else if (qop & DIGEST_MD5_QOP_AUTH_INT) |
| 287 | { |
| 288 | memcpy (p, QOP_AUTH_INT, strlen (QOP_AUTH_INT)); |
| 289 | p += strlen (QOP_AUTH_INT); |
| 290 | } |
| 291 | else if (qop & DIGEST_MD5_QOP_AUTH) |
| 292 | { |
| 293 | memcpy (p, QOP_AUTH, strlen (QOP_AUTH)); |
| 294 | p += strlen (QOP_AUTH); |
| 295 | } |
| 296 | memcpy (p, COLON, strlen (COLON)); |
| 297 | p += strlen (COLON); |
| 298 | memcpy (p, a2hexhash, 2 * MD5LEN); |
| 299 | |
| 300 | rc = gc_md5 (tmp, tmplen, hash); |
| 301 | free (tmp); |
| 302 | if (rc) |
| 303 | return rc; |
| 304 | |
| 305 | for (i = 0; i < MD5LEN; i++) |
| 306 | { |
| 307 | output[2 * i + 1] = HEXCHAR (hash[i]); |
| 308 | output[2 * i + 0] = HEXCHAR (hash[i] >> 4); |
| 309 | } |
| 310 | output[32] = '\0'; |
| 311 | |
| 312 | return 0; |
| 313 | } |
| 314 | |