1/*-
2 * Copyright (c) 2010 The FreeBSD Foundation
3 *
4 * This software was developed by Shteryana Sotirova Shopova under
5 * sponsorship from the FreeBSD Foundation.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 *
28 * $FreeBSD$
29 */
30#include <sys/types.h>
31#include <sys/socket.h>
32#include <stdio.h>
33#include <stdlib.h>
34#include <stddef.h>
35#include <stdarg.h>
36#ifdef HAVE_STDINT_H
37#include <stdint.h>
38#elif defined(HAVE_INTTYPES_H)
39#include <inttypes.h>
40#endif
41#include <string.h>
42#include <ctype.h>
43#include <errno.h>
44#include <netinet/in.h>
45
46#ifdef HAVE_LIBCRYPTO
47#include <openssl/evp.h>
48#endif
49
50#include "asn1.h"
51#include "snmp.h"
52#include "snmppriv.h"
53
54#define	SNMP_PRIV_AES_IV_SIZ		16
55#define	SNMP_EXTENDED_KEY_SIZ		64
56#define	SNMP_AUTH_KEY_LOOPCNT		1048576
57#define	SNMP_AUTH_BUF_SIZE		72
58
59#ifdef HAVE_LIBCRYPTO
60
61static const uint8_t ipad = 0x36;
62static const uint8_t opad = 0x5c;
63
64static int32_t
65snmp_digest_init(const struct snmp_user *user, EVP_MD_CTX *ctx,
66    const EVP_MD **dtype, uint32_t *keylen)
67{
68	if (user->auth_proto == SNMP_AUTH_HMAC_MD5) {
69		*dtype = EVP_md5();
70		*keylen = SNMP_AUTH_HMACMD5_KEY_SIZ;
71	} else if (user->auth_proto == SNMP_AUTH_HMAC_SHA) {
72		*dtype = EVP_sha1();
73		*keylen = SNMP_AUTH_HMACSHA_KEY_SIZ;
74	} else if (user->auth_proto == SNMP_AUTH_NOAUTH)
75		return (0);
76	else {
77		snmp_error("unknown authentication option - %d",
78		    user->auth_proto);
79		return (-1);
80	}
81
82	if (EVP_DigestInit(ctx, *dtype) != 1)
83		return (-1);
84
85	return (1);
86}
87
88enum snmp_code
89snmp_pdu_calc_digest(const struct snmp_pdu *pdu, uint8_t *digest)
90{
91	uint8_t md[EVP_MAX_MD_SIZE], extkey[SNMP_EXTENDED_KEY_SIZ];
92	uint8_t key1[SNMP_EXTENDED_KEY_SIZ], key2[SNMP_EXTENDED_KEY_SIZ];
93	uint32_t i, keylen, olen;
94	int32_t err;
95	const EVP_MD *dtype;
96	EVP_MD_CTX *ctx;
97
98	ctx = EVP_MD_CTX_new();
99	if (ctx == NULL)
100		return (SNMP_CODE_FAILED);
101	err = snmp_digest_init(&pdu->user, ctx, &dtype, &keylen);
102	if (err <= 0)
103		EVP_MD_CTX_free(ctx);
104	if (err < 0)
105		return (SNMP_CODE_BADDIGEST);
106	else if (err == 0)
107		return (SNMP_CODE_OK);
108
109	memset(pdu->digest_ptr, 0, sizeof(pdu->msg_digest));
110	memcpy(extkey, pdu->user.auth_key, keylen);
111	memset(extkey + keylen, 0, sizeof(extkey) - keylen);
112
113	for (i = 0; i < SNMP_EXTENDED_KEY_SIZ; i++) {
114		key1[i] = extkey[i] ^ ipad;
115		key2[i] = extkey[i] ^ opad;
116	}
117
118	if (EVP_DigestUpdate(ctx, key1, SNMP_EXTENDED_KEY_SIZ) != 1 ||
119	    EVP_DigestUpdate(ctx, pdu->outer_ptr, pdu->outer_len) != 1 ||
120	    EVP_DigestFinal(ctx, md, &olen) != 1)
121		goto failed;
122
123	if (EVP_DigestInit(ctx, dtype) != 1 ||
124	    EVP_DigestUpdate(ctx, key2, SNMP_EXTENDED_KEY_SIZ) != 1 ||
125	    EVP_DigestUpdate(ctx, md, olen) != 1 ||
126	    EVP_DigestFinal(ctx, md, &olen) != 1)
127		goto failed;
128
129	if (olen < SNMP_USM_AUTH_SIZE) {
130		snmp_error("bad digest size - %d", olen);
131		EVP_MD_CTX_free(ctx);
132		return (SNMP_CODE_BADDIGEST);
133	}
134
135	memcpy(digest, md, SNMP_USM_AUTH_SIZE);
136	EVP_MD_CTX_free(ctx);
137	return (SNMP_CODE_OK);
138
139failed:
140	EVP_MD_CTX_free(ctx);
141	return (SNMP_CODE_BADDIGEST);
142}
143
144static int32_t
145snmp_pdu_cipher_init(const struct snmp_pdu *pdu, int32_t len,
146    const EVP_CIPHER **ctype, uint8_t *piv)
147{
148	int i;
149	uint32_t netint;
150
151	if (pdu->user.priv_proto == SNMP_PRIV_DES) {
152		if (len  % 8 != 0)
153			return (-1);
154		*ctype = EVP_des_cbc();
155		memcpy(piv, pdu->msg_salt, sizeof(pdu->msg_salt));
156		for (i = 0; i < 8; i++)
157			piv[i] = piv[i] ^ pdu->user.priv_key[8 + i];
158	} else if (pdu->user.priv_proto == SNMP_PRIV_AES) {
159		*ctype = EVP_aes_128_cfb128();
160		netint = htonl(pdu->engine.engine_boots);
161		memcpy(piv, &netint, sizeof(netint));
162		piv += sizeof(netint);
163		netint = htonl(pdu->engine.engine_time);
164		memcpy(piv, &netint, sizeof(netint));
165		piv += sizeof(netint);
166		memcpy(piv, pdu->msg_salt, sizeof(pdu->msg_salt));
167	} else if (pdu->user.priv_proto == SNMP_PRIV_NOPRIV)
168		return (0);
169	else {
170		snmp_error("unknown privacy option - %d", pdu->user.priv_proto);
171		return (-1);
172	}
173
174	return (1);
175}
176
177enum snmp_code
178snmp_pdu_encrypt(const struct snmp_pdu *pdu)
179{
180	int32_t err, olen;
181	uint8_t iv[SNMP_PRIV_AES_IV_SIZ];
182	const EVP_CIPHER *ctype;
183	EVP_CIPHER_CTX *ctx;
184
185	err = snmp_pdu_cipher_init(pdu, pdu->scoped_len, &ctype, iv);
186	if (err < 0)
187		return (SNMP_CODE_EDECRYPT);
188	else if (err == 0)
189		return (SNMP_CODE_OK);
190
191	ctx = EVP_CIPHER_CTX_new();
192	if (ctx == NULL)
193		return (SNMP_CODE_FAILED);
194	if (EVP_EncryptInit(ctx, ctype, pdu->user.priv_key, iv) != 1)
195		goto failed;
196
197	if (EVP_EncryptUpdate(ctx, pdu->scoped_ptr, &olen, pdu->scoped_ptr,
198	    pdu->scoped_len) != 1 ||
199	    EVP_EncryptFinal(ctx, pdu->scoped_ptr + olen, &olen) != 1)
200		goto failed;
201
202	EVP_CIPHER_CTX_free(ctx);
203	return (SNMP_CODE_OK);
204
205failed:
206	EVP_CIPHER_CTX_free(ctx);
207	return (SNMP_CODE_FAILED);
208}
209
210enum snmp_code
211snmp_pdu_decrypt(const struct snmp_pdu *pdu)
212{
213	int32_t err, olen;
214	uint8_t iv[SNMP_PRIV_AES_IV_SIZ];
215	const EVP_CIPHER *ctype;
216	EVP_CIPHER_CTX *ctx;
217
218	err = snmp_pdu_cipher_init(pdu, pdu->scoped_len, &ctype, iv);
219	if (err < 0)
220		return (SNMP_CODE_EDECRYPT);
221	else if (err == 0)
222		return (SNMP_CODE_OK);
223
224	ctx = EVP_CIPHER_CTX_new();
225	if (ctx == NULL)
226		return (SNMP_CODE_FAILED);
227	if (EVP_DecryptInit(ctx, ctype, pdu->user.priv_key, iv) != 1 ||
228	    EVP_CIPHER_CTX_set_padding(ctx, 0) != 1)
229		goto failed;
230
231	if (EVP_DecryptUpdate(ctx, pdu->scoped_ptr, &olen, pdu->scoped_ptr,
232	    pdu->scoped_len) != 1 ||
233	    EVP_DecryptFinal(ctx, pdu->scoped_ptr + olen, &olen) != 1)
234		goto failed;
235
236	EVP_CIPHER_CTX_free(ctx);
237	return (SNMP_CODE_OK);
238
239failed:
240	EVP_CIPHER_CTX_free(ctx);
241	return (SNMP_CODE_EDECRYPT);
242}
243
244/* [RFC 3414] - A.2. Password to Key Algorithm */
245enum snmp_code
246snmp_passwd_to_keys(struct snmp_user *user, char *passwd)
247{
248	int err, loop, i, pwdlen;
249	uint32_t  keylen, olen;
250	const EVP_MD *dtype;
251	EVP_MD_CTX *ctx;
252	uint8_t authbuf[SNMP_AUTH_BUF_SIZE];
253
254	if (passwd == NULL || user == NULL)
255		return (SNMP_CODE_FAILED);
256
257	ctx = EVP_MD_CTX_new();
258	if (ctx == NULL)
259		return (SNMP_CODE_FAILED);
260
261	err = snmp_digest_init(user, ctx, &dtype, &keylen);
262	if (err <= 0)
263		EVP_MD_CTX_free(ctx);
264	if (err < 0)
265		return (SNMP_CODE_BADDIGEST);
266	else if (err == 0)
267		return (SNMP_CODE_OK);
268
269	memset(user->auth_key, 0, sizeof(user->auth_key));
270	pwdlen = strlen(passwd);
271
272	for (loop = 0; loop < SNMP_AUTH_KEY_LOOPCNT; loop += i) {
273		for (i = 0; i < SNMP_EXTENDED_KEY_SIZ; i++)
274			authbuf[i] = passwd[(loop + i) % pwdlen];
275		if (EVP_DigestUpdate(ctx, authbuf, SNMP_EXTENDED_KEY_SIZ) != 1)
276			goto failed;
277	}
278
279	if (EVP_DigestFinal(ctx, user->auth_key, &olen) != 1)
280		goto failed;
281
282	EVP_MD_CTX_free(ctx);
283	return (SNMP_CODE_OK);
284
285failed:
286	EVP_MD_CTX_free(ctx);
287	return (SNMP_CODE_BADDIGEST);
288}
289
290/* [RFC 3414] - 2.6. Key Localization Algorithm */
291enum snmp_code
292snmp_get_local_keys(struct snmp_user *user, uint8_t *eid, uint32_t elen)
293{
294	int err;
295	uint32_t  keylen, olen;
296	const EVP_MD *dtype;
297	EVP_MD_CTX *ctx;
298	uint8_t authbuf[SNMP_AUTH_BUF_SIZE];
299
300	if (user == NULL || eid == NULL || elen > SNMP_ENGINE_ID_SIZ)
301		return (SNMP_CODE_FAILED);
302
303	ctx = EVP_MD_CTX_new();
304	if (ctx == NULL)
305		return (SNMP_CODE_FAILED);
306
307	memset(user->priv_key, 0, sizeof(user->priv_key));
308	memset(authbuf, 0, sizeof(authbuf));
309
310	err = snmp_digest_init(user, ctx, &dtype, &keylen);
311	if (err <= 0)
312		EVP_MD_CTX_free(ctx);
313	if (err < 0)
314		return (SNMP_CODE_BADDIGEST);
315	else if (err == 0)
316		return (SNMP_CODE_OK);
317
318	memcpy(authbuf, user->auth_key, keylen);
319	memcpy(authbuf + keylen, eid, elen);
320	memcpy(authbuf + keylen + elen, user->auth_key, keylen);
321
322	if (EVP_DigestUpdate(ctx, authbuf, 2 * keylen + elen) != 1 ||
323	    EVP_DigestFinal(ctx, user->auth_key, &olen) != 1) {
324		EVP_MD_CTX_free(ctx);
325		return (SNMP_CODE_BADDIGEST);
326	}
327	EVP_MD_CTX_free(ctx);
328
329	if (user->priv_proto != SNMP_PRIV_NOPRIV)
330		memcpy(user->priv_key, user->auth_key, sizeof(user->priv_key));
331
332	return (SNMP_CODE_OK);
333}
334
335enum snmp_code
336snmp_calc_keychange(struct snmp_user *user, uint8_t *keychange)
337{
338	int32_t err, rvalue[SNMP_AUTH_HMACSHA_KEY_SIZ / 4];
339	uint32_t i, keylen, olen;
340	const EVP_MD *dtype;
341	EVP_MD_CTX *ctx;
342
343	ctx = EVP_MD_CTX_new();
344	if (ctx == NULL)
345		return (SNMP_CODE_FAILED);
346
347	err = snmp_digest_init(user, ctx, &dtype, &keylen);
348	if (err <= 0)
349		EVP_MD_CTX_free(ctx);
350	if (err < 0)
351		return (SNMP_CODE_BADDIGEST);
352	else if (err == 0)
353		return (SNMP_CODE_OK);
354
355	for (i = 0; i < keylen / 4; i++)
356		rvalue[i] = random();
357
358	memcpy(keychange, user->auth_key, keylen);
359	memcpy(keychange + keylen, rvalue, keylen);
360
361	if (EVP_DigestUpdate(ctx, keychange, 2 * keylen) != 1 ||
362	    EVP_DigestFinal(ctx, keychange, &olen) != 1) {
363		EVP_MD_CTX_free(ctx);
364		return (SNMP_CODE_BADDIGEST);
365	}
366
367	EVP_MD_CTX_free(ctx);
368	return (SNMP_CODE_OK);
369}
370
371#else /* !HAVE_LIBCRYPTO */
372
373enum snmp_code
374snmp_pdu_calc_digest(const struct snmp_pdu *pdu, uint8_t *digest __unused)
375{
376	if  (pdu->user.auth_proto != SNMP_AUTH_NOAUTH)
377		return (SNMP_CODE_BADSECLEVEL);
378
379
380	return (SNMP_CODE_OK);
381}
382
383enum snmp_code
384snmp_pdu_encrypt(const struct snmp_pdu *pdu)
385{
386	if (pdu->user.priv_proto != SNMP_PRIV_NOPRIV)
387		return (SNMP_CODE_BADSECLEVEL);
388
389	return (SNMP_CODE_OK);
390}
391
392enum snmp_code
393snmp_pdu_decrypt(const struct snmp_pdu *pdu)
394{
395	if (pdu->user.priv_proto != SNMP_PRIV_NOPRIV)
396		return (SNMP_CODE_BADSECLEVEL);
397
398	return (SNMP_CODE_OK);
399}
400
401enum snmp_code
402snmp_passwd_to_keys(struct snmp_user *user, char *passwd __unused)
403{
404	if (user->auth_proto == SNMP_AUTH_NOAUTH &&
405	    user->priv_proto == SNMP_PRIV_NOPRIV)
406		return (SNMP_CODE_OK);
407
408	errno = EPROTONOSUPPORT;
409
410	return (SNMP_CODE_FAILED);
411}
412
413enum snmp_code
414snmp_get_local_keys(struct snmp_user *user, uint8_t *eid __unused,
415    uint32_t elen __unused)
416{
417	if (user->auth_proto == SNMP_AUTH_NOAUTH &&
418	    user->priv_proto == SNMP_PRIV_NOPRIV)
419		return (SNMP_CODE_OK);
420
421	errno = EPROTONOSUPPORT;
422
423	return (SNMP_CODE_FAILED);
424}
425
426enum snmp_code
427snmp_calc_keychange(struct snmp_user *user __unused,
428    uint8_t *keychange __unused)
429{
430	errno = EPROTONOSUPPORT;
431	return (SNMP_CODE_FAILED);
432}
433
434#endif /* HAVE_LIBCRYPTO */
435