Annotation of src/usr.bin/snmp/usm.c, Revision 1.2
1.2 ! martijn 1: /* $OpenBSD: usm.c,v 1.1 2019/09/18 09:48:14 martijn Exp $ */
1.1 martijn 2:
3: /*
4: * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org>
5: *
6: * Permission to use, copy, modify, and distribute this software for any
7: * purpose with or without fee is hereby granted, provided that the above
8: * copyright notice and this permission notice appear in all copies.
9: *
10: * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11: * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12: * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13: * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14: * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15: * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16: * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17: */
18:
19: #include <sys/time.h>
20:
21: #include <openssl/evp.h>
22: #include <openssl/hmac.h>
23:
24: #include <ber.h>
25: #include <errno.h>
26: #include <string.h>
27: #include <time.h>
28:
29: #include "smi.h"
30: #include "snmp.h"
31: #include "usm.h"
32:
33: #define USM_MAX_DIGESTLEN 48
34: #define USM_MAX_TIMEWINDOW 150
35: #define USM_SALTOFFSET 8
36:
37: struct usm_sec {
38: struct snmp_sec snmp;
39: char *user;
40: size_t userlen;
41: int engineidset;
42: char *engineid;
43: size_t engineidlen;
1.2 ! martijn 44: enum usm_key_level authlevel;
! 45: const EVP_MD *digest;
! 46: char *authkey;
1.1 martijn 47: int bootsset;
48: uint32_t boots;
49: int timeset;
50: uint32_t time;
51: struct timespec timecheck;
52: };
53:
1.2 ! martijn 54: struct usm_cookie {
! 55: size_t digestoffset;
! 56: };
! 57:
1.1 martijn 58: static int usm_doinit(struct snmp_agent *);
1.2 ! martijn 59: static char *usm_genparams(struct snmp_agent *, size_t *, void **);
! 60: static int usm_finalparams(struct snmp_agent *, char *, size_t, size_t, void *);
1.1 martijn 61: static int usm_parseparams(struct snmp_agent *, char *, size_t, off_t, char *,
62: size_t, uint8_t);
1.2 ! martijn 63: static void usm_digest_pos(void *, size_t);
1.1 martijn 64: static void usm_free(void *);
1.2 ! martijn 65: static char *usm_passwd2mkey(const EVP_MD *, const char *);
! 66: static char *usm_mkey2lkey(struct usm_sec *, const EVP_MD *, const char *);
! 67: static size_t usm_digestlen(const EVP_MD *);
1.1 martijn 68:
69: struct snmp_sec *
70: usm_init(const char *user, size_t userlen)
71: {
72: struct snmp_sec *sec;
73: struct usm_sec *usm;
74:
75: if (user == NULL || user[0] == '\0') {
76: errno = EINVAL;
77: return NULL;
78: }
79:
80: if ((sec = malloc(sizeof(*sec))) == NULL)
81: return NULL;
82:
83: if ((usm = calloc(1, sizeof(struct usm_sec))) == NULL) {
84: free(sec);
85: return NULL;
86: }
87: if ((usm->user = malloc(userlen)) == NULL) {
88: free(sec);
89: free(usm);
90: return NULL;
91: }
92: memcpy(usm->user, user, userlen);
93: usm->userlen = userlen;
94:
95: sec->model = SNMP_SEC_USM;
96: sec->init = usm_doinit;
97: sec->genparams = usm_genparams;
98: sec->parseparams = usm_parseparams;
1.2 ! martijn 99: sec->finalparams = usm_finalparams;
1.1 martijn 100: sec->free = usm_free;
1.2 ! martijn 101: sec->freecookie = free;
1.1 martijn 102: sec->data = usm;
103: return sec;
104: }
105:
106: static int
107: usm_doinit(struct snmp_agent *agent)
108: {
109: struct ber_element *ber;
110: struct usm_sec *usm = agent->v3->sec->data;
111: int level;
112: size_t userlen;
113:
114: if (usm->engineidset && usm->bootsset && usm->timeset)
115: return 0;
116:
117: level = agent->v3->level;
118: agent->v3->level = SNMP_MSGFLAG_REPORT;
119: userlen = usm->userlen;
120: usm->userlen = 0;
121:
122: if ((ber = snmp_get(agent, NULL, 0)) == NULL) {
123: agent->v3->level = level;
124: usm->userlen = userlen;
125: return -1;
126: }
127: ber_free_element(ber);
128:
129: agent->v3->level = level;
130: usm->userlen = userlen;
131:
132: return 0;
133: }
134:
135: static char *
1.2 ! martijn 136: usm_genparams(struct snmp_agent *agent, size_t *len, void **cookie)
1.1 martijn 137: {
138: struct ber ber;
1.2 ! martijn 139: struct ber_element *params, *digestelm;
1.1 martijn 140: struct usm_sec *usm = agent->v3->sec->data;
1.2 ! martijn 141: char digest[USM_MAX_DIGESTLEN];
! 142: size_t digestlen = 0;
1.1 martijn 143: char *secparams = NULL;
144: ssize_t berlen = 0;
1.2 ! martijn 145: struct usm_cookie *usmcookie;
1.1 martijn 146: struct timespec now, timediff;
147: uint32_t boots, time;
148:
1.2 ! martijn 149: bzero(digest, sizeof(digest));
! 150:
! 151: if ((usmcookie = calloc(1, sizeof(*usmcookie))) == NULL)
! 152: return NULL;
! 153: *cookie = usmcookie;
! 154:
1.1 martijn 155: if (usm->timeset) {
1.2 ! martijn 156: if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) {
! 157: free(usmcookie);
1.1 martijn 158: return NULL;
1.2 ! martijn 159: }
1.1 martijn 160: timespecsub(&now, &(usm->timecheck), &timediff);
161: time = usm->time + timediff.tv_sec;
162: } else
163: time = 0;
164: boots = usm->boots;
1.2 ! martijn 165: if (agent->v3->level & SNMP_MSGFLAG_AUTH)
! 166: digestlen = usm_digestlen(usm->digest);
! 167: if ((params = ber_printf_elements(NULL, "{xddxxx}", usm->engineid,
! 168: usm->engineidlen, boots, time, usm->user, usm->userlen, digest,
! 169: digestlen, NULL, (size_t) 0)) == NULL) {
! 170: free(usmcookie);
! 171: return NULL;
! 172: }
1.1 martijn 173:
1.2 ! martijn 174: if (ber_scanf_elements(params, "{SSSSe", &digestelm) == -1) {
! 175: ber_free_element(params);
! 176: free(usmcookie);
1.1 martijn 177: return NULL;
1.2 ! martijn 178: }
! 179:
! 180: ber_set_writecallback(digestelm, usm_digest_pos, usmcookie);
1.1 martijn 181:
182: bzero(&ber, sizeof(ber));
183: ber_set_application(&ber, smi_application);
184: if (ber_write_elements(&ber, params) != -1)
185: berlen = ber_copy_writebuf(&ber, (void **)&secparams);
186:
187: *len = berlen;
188: ber_free_element(params);
189: ber_free(&ber);
190: return secparams;
191: }
192:
193: static int
1.2 ! martijn 194: usm_finalparams(struct snmp_agent *agent, char *buf, size_t buflen,
! 195: size_t secparamsoffset, void *cookie)
! 196: {
! 197: struct usm_sec *usm = agent->v3->sec->data;
! 198: struct usm_cookie *usmcookie = cookie;
! 199: u_char digest[EVP_MAX_MD_SIZE];
! 200:
! 201: if ((agent->v3->level & SNMP_MSGFLAG_AUTH) == 0)
! 202: return 0;
! 203:
! 204: if (usm->authlevel != USM_KEY_LOCALIZED)
! 205: return -1;
! 206:
! 207: if (HMAC(usm->digest, usm->authkey, EVP_MD_size(usm->digest), buf,
! 208: buflen, digest, NULL) == NULL)
! 209: return -1;
! 210:
! 211: memcpy(buf + secparamsoffset + usmcookie->digestoffset, digest,
! 212: usm_digestlen(usm->digest));
! 213: return 0;
! 214: }
! 215:
! 216: static int
1.1 martijn 217: usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen,
218: off_t secparamsoffset, char *buf, size_t buflen, uint8_t level)
219: {
220: struct usm_sec *usm = agent->v3->sec->data;
221: struct ber ber;
222: struct ber_element *secparams;
1.2 ! martijn 223: char *engineid, *user, *digest;
! 224: size_t engineidlen, userlen, digestlen;
1.1 martijn 225: struct timespec now, timediff;
1.2 ! martijn 226: off_t digestoffset;
! 227: char exp_digest[EVP_MAX_MD_SIZE];
1.1 martijn 228: uint32_t boots, time;
229:
230: bzero(&ber, sizeof(ber));
1.2 ! martijn 231: bzero(exp_digest, sizeof(exp_digest));
1.1 martijn 232:
233: ber_set_application(&ber, smi_application);
234: ber_set_readbuf(&ber, buf, buflen);
235: if ((secparams = ber_read_elements(&ber, NULL)) == NULL)
236: return -1;
237: ber_free(&ber);
238:
1.2 ! martijn 239: if (ber_scanf_elements(secparams, "{xddxpxS}", &engineid, &engineidlen,
! 240: &boots, &time, &user, &userlen, &digestoffset, &digest,
! 241: &digestlen) == -1)
1.1 martijn 242: goto fail;
243:
244: if (!usm->engineidset) {
245: if (usm_setengineid(agent->v3->sec, engineid,
246: engineidlen) == -1)
247: goto fail;
248: } else {
249: if (usm->engineidlen != engineidlen)
250: goto fail;
251: if (memcmp(usm->engineid, engineid, engineidlen) != 0)
252: goto fail;
253: }
254:
255: if (!usm->bootsset) {
256: usm->boots = boots;
257: usm->bootsset = 1;
258: } else {
259: if (boots < usm->boots)
260: goto fail;
261: if (boots > usm->boots) {
262: usm->bootsset = 0;
263: usm->timeset = 0;
264: usm_doinit(agent);
265: goto fail;
266: }
267: }
268:
269: if (!usm->timeset) {
270: usm->time = time;
271: if (clock_gettime(CLOCK_MONOTONIC, &usm->timecheck) == -1)
272: goto fail;
273: usm->timeset = 1;
274: } else {
275: if (clock_gettime(CLOCK_MONOTONIC, &now) == -1)
276: goto fail;
277: timespecsub(&now, &(usm->timecheck), &timediff);
278: if (time < usm->time + timediff.tv_sec - USM_MAX_TIMEWINDOW ||
279: time > usm->time + timediff.tv_sec + USM_MAX_TIMEWINDOW) {
280: usm->bootsset = 0;
281: usm->timeset = 0;
282: usm_doinit(agent);
283: goto fail;
284: }
285: }
286:
287: if (userlen != usm->userlen ||
288: memcmp(user, usm->user, userlen) != 0)
289: goto fail;
290:
1.2 ! martijn 291: if (level & SNMP_MSGFLAG_AUTH) {
! 292: if (digestlen != usm_digestlen(usm->digest))
! 293: goto fail;
! 294: }
! 295: if ((agent->v3->level & SNMP_MSGFLAG_AUTH)) {
! 296: bzero(packet + secparamsoffset + digestoffset, digestlen);
! 297: if (HMAC(usm->digest, usm->authkey, EVP_MD_size(usm->digest), packet,
! 298: packetlen, exp_digest, NULL) == NULL)
! 299: goto fail;
! 300:
! 301: if (memcmp(exp_digest, digest, digestlen) != 0)
! 302: goto fail;
! 303: } else
! 304: if (digestlen != 0)
! 305: goto fail;
! 306:
1.1 martijn 307: ber_free_element(secparams);
308: return 0;
309:
310: fail:
311: ber_free_element(secparams);
312: return -1;
313: }
314:
315: static void
1.2 ! martijn 316: usm_digest_pos(void *data, size_t offset)
! 317: {
! 318: struct usm_cookie *usmcookie = data;
! 319:
! 320: usmcookie->digestoffset = offset;
! 321: }
! 322:
! 323: static void
1.1 martijn 324: usm_free(void *data)
325: {
326: struct usm_sec *usm = data;
327:
328: free(usm->user);
1.2 ! martijn 329: free(usm->authkey);
1.1 martijn 330: free(usm->engineid);
331: free(usm);
332: }
333:
334: int
1.2 ! martijn 335: usm_setauth(struct snmp_sec *sec, const EVP_MD *digest, const char *key,
! 336: size_t keylen, enum usm_key_level level)
! 337: {
! 338: struct usm_sec *usm = sec->data;
! 339: char *lkey;
! 340:
! 341: /*
! 342: * We could transform a master key to a local key here if we already
! 343: * have usm_setengineid called. Sine snmpc.c is the only caller at
! 344: * the moment there's no need, since it always calls this function
! 345: * first.
! 346: */
! 347: if (level == USM_KEY_PASSWORD) {
! 348: if ((usm->authkey = usm_passwd2mkey(digest, key)) == NULL)
! 349: return -1;
! 350: level = USM_KEY_MASTER;
! 351: keylen = EVP_MD_size(digest);
! 352: } else {
! 353: if (keylen != (size_t)EVP_MD_size(digest)) {
! 354: errno = EINVAL;
! 355: return -1;
! 356: }
! 357: if ((lkey = malloc(keylen)) == NULL)
! 358: return -1;
! 359: memcpy(lkey, key, keylen);
! 360: usm->authkey = lkey;
! 361: }
! 362: usm->digest = digest;
! 363: usm->authlevel = level;
! 364: return 0;
! 365: }
! 366:
! 367: int
1.1 martijn 368: usm_setengineid(struct snmp_sec *sec, char *engineid, size_t engineidlen)
369: {
370: struct usm_sec *usm = sec->data;
1.2 ! martijn 371: char *mkey;
1.1 martijn 372:
373: if (usm->engineid != NULL)
374: free(usm->engineid);
375: if ((usm->engineid = malloc(engineidlen)) == NULL)
376: return -1;
377: memcpy(usm->engineid, engineid, engineidlen);
378: usm->engineidlen = engineidlen;
379: usm->engineidset = 1;
380:
1.2 ! martijn 381: if (usm->authlevel == USM_KEY_MASTER) {
! 382: mkey = usm->authkey;
! 383: if ((usm->authkey = usm_mkey2lkey(usm, usm->digest,
! 384: mkey)) == NULL) {
! 385: usm->authkey = mkey;
! 386: return -1;
! 387: }
! 388: free(mkey);
! 389: usm->authlevel = USM_KEY_LOCALIZED;
! 390: }
! 391:
1.1 martijn 392: return 0;
393: }
394:
395: int
396: usm_setbootstime(struct snmp_sec *sec, uint32_t boots, uint32_t time)
397: {
398: struct usm_sec *usm = sec->data;
399:
400: if (clock_gettime(CLOCK_MONOTONIC, &(usm->timecheck)) == -1)
401: return -1;
402:
403: usm->boots = boots;
404: usm->bootsset = 1;
405: usm->time = time;
406: usm->timeset = 1;
407: return 0;
1.2 ! martijn 408: }
! 409:
! 410: static char *
! 411: usm_passwd2mkey(const EVP_MD *md, const char *passwd)
! 412: {
! 413: EVP_MD_CTX ctx;
! 414: int i, count;
! 415: const u_char *pw;
! 416: u_char *c;
! 417: u_char keybuf[EVP_MAX_MD_SIZE];
! 418: unsigned dlen;
! 419: char *key;
! 420:
! 421: bzero(&ctx, sizeof(ctx));
! 422: EVP_DigestInit_ex(&ctx, md, NULL);
! 423: pw = (const u_char *)passwd;
! 424: for (count = 0; count < 1048576; count += 64) {
! 425: c = keybuf;
! 426: for (i = 0; i < 64; i++) {
! 427: if (*pw == '\0')
! 428: pw = (const u_char *)passwd;
! 429: *c++ = *pw++;
! 430: }
! 431: EVP_DigestUpdate(&ctx, keybuf, 64);
! 432: }
! 433: EVP_DigestFinal_ex(&ctx, keybuf, &dlen);
! 434: EVP_MD_CTX_cleanup(&ctx);
! 435:
! 436: if ((key = malloc(dlen)) == NULL)
! 437: return NULL;
! 438: memcpy(key, keybuf, dlen);
! 439: return key;
! 440: }
! 441:
! 442: static char *
! 443: usm_mkey2lkey(struct usm_sec *usm, const EVP_MD *md, const char *mkey)
! 444: {
! 445: EVP_MD_CTX ctx;
! 446: u_char buf[EVP_MAX_MD_SIZE];
! 447: u_char *lkey;
! 448: unsigned lklen;
! 449:
! 450: bzero(&ctx, sizeof(ctx));
! 451: EVP_DigestInit_ex(&ctx, md, NULL);
! 452:
! 453: EVP_DigestUpdate(&ctx, mkey, EVP_MD_size(md));
! 454: EVP_DigestUpdate(&ctx, usm->engineid, usm->engineidlen);
! 455: EVP_DigestUpdate(&ctx, mkey, EVP_MD_size(md));
! 456:
! 457: EVP_DigestFinal_ex(&ctx, buf, &lklen);
! 458: EVP_MD_CTX_cleanup(&ctx);
! 459:
! 460: if ((lkey = malloc(lklen)) == NULL)
! 461: return NULL;
! 462: memcpy(lkey, buf, lklen);
! 463: return lkey;
! 464: }
! 465:
! 466: static size_t
! 467: usm_digestlen(const EVP_MD *md)
! 468: {
! 469: switch (EVP_MD_type(md)) {
! 470: case NID_md5:
! 471: case NID_sha1:
! 472: return 12;
! 473: case NID_sha224:
! 474: return 16;
! 475: case NID_sha256:
! 476: return 24;
! 477: case NID_sha384:
! 478: return 32;
! 479: case NID_sha512:
! 480: return 48;
! 481: default:
! 482: return 0;
! 483: }
1.1 martijn 484: }