Line | Count | Source |
1 | | /* $OpenBSD: mac.c,v 1.37 2025/09/05 10:01:35 dtucker Exp $ */ |
2 | | /* |
3 | | * Copyright (c) 2001 Markus Friedl. All rights reserved. |
4 | | * |
5 | | * Redistribution and use in source and binary forms, with or without |
6 | | * modification, are permitted provided that the following conditions |
7 | | * are met: |
8 | | * 1. Redistributions of source code must retain the above copyright |
9 | | * notice, this list of conditions and the following disclaimer. |
10 | | * 2. Redistributions in binary form must reproduce the above copyright |
11 | | * notice, this list of conditions and the following disclaimer in the |
12 | | * documentation and/or other materials provided with the distribution. |
13 | | * |
14 | | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
15 | | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
16 | | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
17 | | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
18 | | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
19 | | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
20 | | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
21 | | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
22 | | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
23 | | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
24 | | */ |
25 | | |
26 | | #include "includes.h" |
27 | | |
28 | | #include <sys/types.h> |
29 | | |
30 | | #include <stdio.h> |
31 | | #include <stdlib.h> |
32 | | #include <string.h> |
33 | | |
34 | | #include "digest.h" |
35 | | #include "hmac.h" |
36 | | #include "umac.h" |
37 | | #include "mac.h" |
38 | | #include "misc.h" |
39 | | #include "ssherr.h" |
40 | | #include "sshbuf.h" |
41 | | |
42 | | #include "openbsd-compat/openssl-compat.h" |
43 | | |
44 | 36 | #define SSH_DIGEST 1 /* SSH_DIGEST_XXX */ |
45 | 80 | #define SSH_UMAC 2 /* UMAC (not integrated with OpenSSL) */ |
46 | 77 | #define SSH_UMAC128 3 |
47 | | |
48 | | struct macalg { |
49 | | char *name; |
50 | | int type; |
51 | | int alg; |
52 | | int truncatebits; /* truncate digest if != 0 */ |
53 | | int key_len; /* just for UMAC */ |
54 | | int len; /* just for UMAC */ |
55 | | int etm; /* Encrypt-then-MAC */ |
56 | | }; |
57 | | |
58 | | static const struct macalg macs[] = { |
59 | | /* Encrypt-and-MAC (encrypt-and-authenticate) variants */ |
60 | | { "hmac-sha1", SSH_DIGEST, SSH_DIGEST_SHA1, 0, 0, 0, 0 }, |
61 | | { "hmac-sha1-96", SSH_DIGEST, SSH_DIGEST_SHA1, 96, 0, 0, 0 }, |
62 | | { "hmac-sha2-256", SSH_DIGEST, SSH_DIGEST_SHA256, 0, 0, 0, 0 }, |
63 | | { "hmac-sha2-512", SSH_DIGEST, SSH_DIGEST_SHA512, 0, 0, 0, 0 }, |
64 | | { "hmac-md5", SSH_DIGEST, SSH_DIGEST_MD5, 0, 0, 0, 0 }, |
65 | | { "hmac-md5-96", SSH_DIGEST, SSH_DIGEST_MD5, 96, 0, 0, 0 }, |
66 | | { "none", SSH_DIGEST, SSH_DIGEST_NULL, 0, 0, 0, 0 }, |
67 | | { "umac-64@openssh.com", SSH_UMAC, 0, 0, 128, 64, 0 }, |
68 | | { "umac-128@openssh.com", SSH_UMAC128, 0, 0, 128, 128, 0 }, |
69 | | |
70 | | /* Encrypt-then-MAC variants */ |
71 | | { "hmac-sha1-etm@openssh.com", SSH_DIGEST, SSH_DIGEST_SHA1, 0, 0, 0, 1 }, |
72 | | { "hmac-sha1-96-etm@openssh.com", SSH_DIGEST, SSH_DIGEST_SHA1, 96, 0, 0, 1 }, |
73 | | { "hmac-sha2-256-etm@openssh.com", SSH_DIGEST, SSH_DIGEST_SHA256, 0, 0, 0, 1 }, |
74 | | { "hmac-sha2-512-etm@openssh.com", SSH_DIGEST, SSH_DIGEST_SHA512, 0, 0, 0, 1 }, |
75 | | { "hmac-md5-etm@openssh.com", SSH_DIGEST, SSH_DIGEST_MD5, 0, 0, 0, 1 }, |
76 | | { "hmac-md5-96-etm@openssh.com", SSH_DIGEST, SSH_DIGEST_MD5, 96, 0, 0, 1 }, |
77 | | { "umac-64-etm@openssh.com", SSH_UMAC, 0, 0, 128, 64, 1 }, |
78 | | { "umac-128-etm@openssh.com", SSH_UMAC128, 0, 0, 128, 128, 1 }, |
79 | | |
80 | | { NULL, 0, 0, 0, 0, 0, 0 } |
81 | | }; |
82 | | |
83 | | /* Returns a list of supported MACs separated by the specified char. */ |
84 | | char * |
85 | | mac_alg_list(char sep) |
86 | 0 | { |
87 | 0 | char *ret = NULL; |
88 | 0 | const struct macalg *m; |
89 | 0 | char sep_str[2] = {sep, '\0'}; |
90 | |
|
91 | 0 | for (m = macs; m->name != NULL; m++) |
92 | 0 | xextendf(&ret, sep_str, "%s", m->name); |
93 | |
|
94 | 0 | return ret; |
95 | 0 | } |
96 | | |
97 | | static int |
98 | | mac_setup_by_alg(struct sshmac *mac, const struct macalg *macalg) |
99 | 36 | { |
100 | 36 | mac->type = macalg->type; |
101 | 36 | if (mac->type == SSH_DIGEST) { |
102 | 27 | if ((mac->hmac_ctx = ssh_hmac_start(macalg->alg)) == NULL) |
103 | 0 | return SSH_ERR_ALLOC_FAIL; |
104 | 27 | mac->key_len = mac->mac_len = ssh_hmac_bytes(macalg->alg); |
105 | 27 | } else { |
106 | 9 | mac->mac_len = macalg->len / 8; |
107 | 9 | mac->key_len = macalg->key_len / 8; |
108 | 9 | mac->umac_ctx = NULL; |
109 | 9 | } |
110 | 36 | if (macalg->truncatebits != 0) |
111 | 0 | mac->mac_len = macalg->truncatebits / 8; |
112 | 36 | mac->etm = macalg->etm; |
113 | 36 | return 0; |
114 | 36 | } |
115 | | |
116 | | int |
117 | | mac_setup(struct sshmac *mac, char *name) |
118 | 36 | { |
119 | 36 | const struct macalg *m; |
120 | | |
121 | 208 | for (m = macs; m->name != NULL; m++) { |
122 | 208 | if (strcmp(name, m->name) != 0) |
123 | 172 | continue; |
124 | 36 | if (mac != NULL) |
125 | 36 | return mac_setup_by_alg(mac, m); |
126 | 0 | return 0; |
127 | 36 | } |
128 | 0 | return SSH_ERR_INVALID_ARGUMENT; |
129 | 36 | } |
130 | | |
131 | | int |
132 | | mac_init(struct sshmac *mac) |
133 | 0 | { |
134 | 0 | if (mac->key == NULL) |
135 | 0 | return SSH_ERR_INVALID_ARGUMENT; |
136 | 0 | switch (mac->type) { |
137 | 0 | case SSH_DIGEST: |
138 | 0 | if (mac->hmac_ctx == NULL || |
139 | 0 | ssh_hmac_init(mac->hmac_ctx, mac->key, mac->key_len) < 0) |
140 | 0 | return SSH_ERR_INVALID_ARGUMENT; |
141 | 0 | return 0; |
142 | 0 | case SSH_UMAC: |
143 | 0 | if ((mac->umac_ctx = umac_new(mac->key)) == NULL) |
144 | 0 | return SSH_ERR_ALLOC_FAIL; |
145 | 0 | return 0; |
146 | 0 | case SSH_UMAC128: |
147 | 0 | if ((mac->umac_ctx = umac128_new(mac->key)) == NULL) |
148 | 0 | return SSH_ERR_ALLOC_FAIL; |
149 | 0 | return 0; |
150 | 0 | default: |
151 | 0 | return SSH_ERR_INVALID_ARGUMENT; |
152 | 0 | } |
153 | 0 | } |
154 | | |
155 | | int |
156 | | mac_compute(struct sshmac *mac, u_int32_t seqno, |
157 | | const u_char *data, int datalen, |
158 | | u_char *digest, size_t dlen) |
159 | 0 | { |
160 | 0 | static union { |
161 | 0 | u_char m[SSH_DIGEST_MAX_LENGTH]; |
162 | 0 | u_int64_t for_align; |
163 | 0 | } u; |
164 | 0 | u_char b[4]; |
165 | 0 | u_char nonce[8]; |
166 | |
|
167 | 0 | if (mac->mac_len > sizeof(u)) |
168 | 0 | return SSH_ERR_INTERNAL_ERROR; |
169 | | |
170 | 0 | switch (mac->type) { |
171 | 0 | case SSH_DIGEST: |
172 | 0 | put_u32(b, seqno); |
173 | | /* reset HMAC context */ |
174 | 0 | if (ssh_hmac_init(mac->hmac_ctx, NULL, 0) < 0 || |
175 | 0 | ssh_hmac_update(mac->hmac_ctx, b, sizeof(b)) < 0 || |
176 | 0 | ssh_hmac_update(mac->hmac_ctx, data, datalen) < 0 || |
177 | 0 | ssh_hmac_final(mac->hmac_ctx, u.m, sizeof(u.m)) < 0) |
178 | 0 | return SSH_ERR_LIBCRYPTO_ERROR; |
179 | 0 | break; |
180 | 0 | case SSH_UMAC: |
181 | 0 | POKE_U64(nonce, seqno); |
182 | 0 | umac_update(mac->umac_ctx, data, datalen); |
183 | 0 | umac_final(mac->umac_ctx, u.m, nonce); |
184 | 0 | break; |
185 | 0 | case SSH_UMAC128: |
186 | 0 | put_u64(nonce, seqno); |
187 | 0 | umac128_update(mac->umac_ctx, data, datalen); |
188 | 0 | umac128_final(mac->umac_ctx, u.m, nonce); |
189 | 0 | break; |
190 | 0 | default: |
191 | 0 | return SSH_ERR_INVALID_ARGUMENT; |
192 | 0 | } |
193 | 0 | if (digest != NULL) { |
194 | 0 | if (dlen > mac->mac_len) |
195 | 0 | dlen = mac->mac_len; |
196 | 0 | memcpy(digest, u.m, dlen); |
197 | 0 | } |
198 | 0 | return 0; |
199 | 0 | } |
200 | | |
201 | | int |
202 | | mac_check(struct sshmac *mac, u_int32_t seqno, |
203 | | const u_char *data, size_t dlen, |
204 | | const u_char *theirmac, size_t mlen) |
205 | 0 | { |
206 | 0 | u_char ourmac[SSH_DIGEST_MAX_LENGTH]; |
207 | 0 | int r; |
208 | |
|
209 | 0 | if (mac->mac_len > mlen) |
210 | 0 | return SSH_ERR_INVALID_ARGUMENT; |
211 | 0 | if ((r = mac_compute(mac, seqno, data, dlen, |
212 | 0 | ourmac, sizeof(ourmac))) != 0) |
213 | 0 | return r; |
214 | 0 | if (timingsafe_bcmp(ourmac, theirmac, mac->mac_len) != 0) |
215 | 0 | return SSH_ERR_MAC_INVALID; |
216 | 0 | return 0; |
217 | 0 | } |
218 | | |
219 | | void |
220 | | mac_clear(struct sshmac *mac) |
221 | 80 | { |
222 | 80 | if (mac->type == SSH_UMAC) { |
223 | 3 | if (mac->umac_ctx != NULL) |
224 | 0 | umac_delete(mac->umac_ctx); |
225 | 77 | } else if (mac->type == SSH_UMAC128) { |
226 | 6 | if (mac->umac_ctx != NULL) |
227 | 0 | umac128_delete(mac->umac_ctx); |
228 | 71 | } else if (mac->hmac_ctx != NULL) |
229 | 27 | ssh_hmac_free(mac->hmac_ctx); |
230 | 80 | mac->hmac_ctx = NULL; |
231 | 80 | mac->umac_ctx = NULL; |
232 | 80 | } |
233 | | |
234 | | /* XXX copied from ciphers_valid */ |
235 | 0 | #define MAC_SEP "," |
236 | | int |
237 | | mac_valid(const char *names) |
238 | 0 | { |
239 | 0 | char *maclist, *cp, *p; |
240 | |
|
241 | 0 | if (names == NULL || strcmp(names, "") == 0) |
242 | 0 | return 0; |
243 | 0 | if ((maclist = cp = strdup(names)) == NULL) |
244 | 0 | return 0; |
245 | 0 | for ((p = strsep(&cp, MAC_SEP)); p && *p != '\0'; |
246 | 0 | (p = strsep(&cp, MAC_SEP))) { |
247 | 0 | if (mac_setup(NULL, p) < 0) { |
248 | 0 | free(maclist); |
249 | 0 | return 0; |
250 | 0 | } |
251 | 0 | } |
252 | 0 | free(maclist); |
253 | 0 | return 1; |
254 | 0 | } |