=================================================================== RCS file: /cvsrepo/anoncvs/cvs/src/usr.bin/snmp/usm.c,v retrieving revision 1.2 retrieving revision 1.3 diff -c -r1.2 -r1.3 *** src/usr.bin/snmp/usm.c 2019/09/18 09:52:47 1.2 --- src/usr.bin/snmp/usm.c 2019/09/18 09:54:36 1.3 *************** *** 1,4 **** ! /* $OpenBSD: usm.c,v 1.2 2019/09/18 09:52:47 martijn Exp $ */ /* * Copyright (c) 2019 Martijn van Duren --- 1,4 ---- ! /* $OpenBSD: usm.c,v 1.3 2019/09/18 09:54:36 martijn Exp $ */ /* * Copyright (c) 2019 Martijn van Duren *************** *** 44,49 **** --- 44,52 ---- enum usm_key_level authlevel; const EVP_MD *digest; char *authkey; + enum usm_key_level privlevel; + const EVP_CIPHER *cipher; + char *privkey; int bootsset; uint32_t boots; int timeset; *************** *** 53,65 **** struct usm_cookie { size_t digestoffset; }; static int usm_doinit(struct snmp_agent *); static char *usm_genparams(struct snmp_agent *, size_t *, void **); static int usm_finalparams(struct snmp_agent *, char *, size_t, size_t, void *); static int usm_parseparams(struct snmp_agent *, char *, size_t, off_t, char *, ! size_t, uint8_t); static void usm_digest_pos(void *, size_t); static void usm_free(void *); static char *usm_passwd2mkey(const EVP_MD *, const char *); --- 56,76 ---- struct usm_cookie { size_t digestoffset; + long long salt; + uint32_t boots; + uint32_t time; }; static int usm_doinit(struct snmp_agent *); static char *usm_genparams(struct snmp_agent *, size_t *, void **); static int usm_finalparams(struct snmp_agent *, char *, size_t, size_t, void *); + static struct ber_element *usm_encpdu(struct snmp_agent *agent, + struct ber_element *pdu, void *cookie); + static char *usm_crypt(const EVP_CIPHER *, int, char *, struct usm_cookie *, + char *, size_t, size_t *); static int usm_parseparams(struct snmp_agent *, char *, size_t, off_t, char *, ! size_t, uint8_t, void **); ! struct ber_element *usm_decpdu(struct snmp_agent *, char *, size_t, void *); static void usm_digest_pos(void *, size_t); static void usm_free(void *); static char *usm_passwd2mkey(const EVP_MD *, const char *); *************** *** 95,101 **** --- 106,114 ---- sec->model = SNMP_SEC_USM; sec->init = usm_doinit; sec->genparams = usm_genparams; + sec->encpdu = usm_encpdu; sec->parseparams = usm_parseparams; + sec->decpdu = usm_decpdu; sec->finalparams = usm_finalparams; sec->free = usm_free; sec->freecookie = free; *************** *** 139,150 **** struct ber_element *params, *digestelm; struct usm_sec *usm = agent->v3->sec->data; char digest[USM_MAX_DIGESTLEN]; ! size_t digestlen = 0; char *secparams = NULL; ssize_t berlen = 0; struct usm_cookie *usmcookie; struct timespec now, timediff; - uint32_t boots, time; bzero(digest, sizeof(digest)); --- 152,162 ---- struct ber_element *params, *digestelm; struct usm_sec *usm = agent->v3->sec->data; char digest[USM_MAX_DIGESTLEN]; ! size_t digestlen = 0, saltlen = 0; char *secparams = NULL; ssize_t berlen = 0; struct usm_cookie *usmcookie; struct timespec now, timediff; bzero(digest, sizeof(digest)); *************** *** 152,172 **** return NULL; *cookie = usmcookie; if (usm->timeset) { if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) { free(usmcookie); return NULL; } timespecsub(&now, &(usm->timecheck), &timediff); ! time = usm->time + timediff.tv_sec; } else ! time = 0; ! boots = usm->boots; if (agent->v3->level & SNMP_MSGFLAG_AUTH) digestlen = usm_digestlen(usm->digest); if ((params = ber_printf_elements(NULL, "{xddxxx}", usm->engineid, ! usm->engineidlen, boots, time, usm->user, usm->userlen, digest, ! digestlen, NULL, (size_t) 0)) == NULL) { free(usmcookie); return NULL; } --- 164,190 ---- return NULL; *cookie = usmcookie; + arc4random_buf(&(usmcookie->salt), sizeof(usmcookie->salt)); if (usm->timeset) { if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) { free(usmcookie); return NULL; } timespecsub(&now, &(usm->timecheck), &timediff); ! usmcookie->time = usm->time + timediff.tv_sec; } else ! usmcookie->time = 0; ! usmcookie->boots = usm->boots; ! if (agent->v3->level & SNMP_MSGFLAG_AUTH) digestlen = usm_digestlen(usm->digest); + if (agent->v3->level & SNMP_MSGFLAG_PRIV) + saltlen = sizeof(usmcookie->salt); + if ((params = ber_printf_elements(NULL, "{xddxxx}", usm->engineid, ! usm->engineidlen, usmcookie->boots, usmcookie->time, usm->user, ! usm->userlen, digest, digestlen, &(usmcookie->salt), ! saltlen)) == NULL) { free(usmcookie); return NULL; } *************** *** 190,195 **** --- 208,300 ---- return secparams; } + static struct ber_element * + usm_encpdu(struct snmp_agent *agent, struct ber_element *pdu, void *cookie) + { + struct usm_sec *usm = agent->v3->sec->data; + struct usm_cookie *usmcookie = cookie; + struct ber ber; + struct ber_element *retpdu; + char *serialpdu, *encpdu; + ssize_t pdulen; + size_t encpdulen; + + bzero(&ber, sizeof(ber)); + ber_set_application(&ber, smi_application); + pdulen = ber_write_elements(&ber, pdu); + if (pdulen == -1) + return NULL; + + ber_get_writebuf(&ber, (void **)&serialpdu); + + encpdu = usm_crypt(usm->cipher, 1, usm->privkey, usmcookie, serialpdu, + pdulen, &encpdulen); + ber_free(&ber); + if (encpdu == NULL) + return NULL; + + retpdu = ber_add_nstring(NULL, encpdu, encpdulen); + free(encpdu); + return retpdu; + } + + static char * + usm_crypt(const EVP_CIPHER *cipher, int do_enc, char *key, + struct usm_cookie *cookie, char *serialpdu, size_t pdulen, size_t *outlen) + { + EVP_CIPHER_CTX ctx; + size_t i; + char iv[EVP_MAX_IV_LENGTH]; + char *salt = (char *)&(cookie->salt); + char *outtext; + int len, len2, bs; + uint32_t ivv; + + switch (EVP_CIPHER_type(cipher)) { + case NID_des_cbc: + /* RFC3414, chap 8.1.1.1. */ + for (i = 0; i < 8; i++) + iv[i] = salt[i] ^ key[USM_SALTOFFSET + i]; + break; + case NID_aes_128_cfb128: + /* RFC3826, chap 3.1.2.1. */ + ivv = htobe32(cookie->boots); + memcpy(iv, &ivv, sizeof(ivv)); + ivv = htobe32(cookie->time); + memcpy(iv + sizeof(ivv), &ivv, sizeof(ivv)); + memcpy(iv + 2 * sizeof(ivv), &(cookie->salt), + sizeof(cookie->salt)); + break; + default: + return NULL; + } + + bzero(&ctx, sizeof(ctx)); + if (!EVP_CipherInit(&ctx, cipher, key, iv, do_enc)) + return NULL; + + EVP_CIPHER_CTX_set_padding(&ctx, do_enc); + + bs = EVP_CIPHER_block_size(cipher); + /* Maximum output size */ + *outlen = pdulen + (bs - (pdulen % bs)); + + if ((outtext = malloc(*outlen)) == NULL) + return NULL; + + if (EVP_CipherUpdate(&ctx, outtext, &len, serialpdu, pdulen) && + EVP_CipherFinal_ex(&ctx, outtext + len, &len2)) + *outlen = len + len2; + else { + free(outtext); + outtext = NULL; + } + + EVP_CIPHER_CTX_cleanup(&ctx); + + return outtext; + } + static int usm_finalparams(struct snmp_agent *agent, char *buf, size_t buflen, size_t secparamsoffset, void *cookie) *************** *** 215,231 **** static int usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen, ! off_t secparamsoffset, char *buf, size_t buflen, uint8_t level) { struct usm_sec *usm = agent->v3->sec->data; struct ber ber; struct ber_element *secparams; ! char *engineid, *user, *digest; ! size_t engineidlen, userlen, digestlen; struct timespec now, timediff; off_t digestoffset; char exp_digest[EVP_MAX_MD_SIZE]; ! uint32_t boots, time; bzero(&ber, sizeof(ber)); bzero(exp_digest, sizeof(exp_digest)); --- 320,337 ---- static int usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen, ! off_t secparamsoffset, char *buf, size_t buflen, uint8_t level, ! void **cookie) { struct usm_sec *usm = agent->v3->sec->data; struct ber ber; struct ber_element *secparams; ! char *engineid, *user, *digest, *salt; ! size_t engineidlen, userlen, digestlen, saltlen; struct timespec now, timediff; off_t digestoffset; char exp_digest[EVP_MAX_MD_SIZE]; ! struct usm_cookie *usmcookie; bzero(&ber, sizeof(ber)); bzero(exp_digest, sizeof(exp_digest)); *************** *** 236,246 **** return -1; ber_free(&ber); ! if (ber_scanf_elements(secparams, "{xddxpxS}", &engineid, &engineidlen, ! &boots, &time, &user, &userlen, &digestoffset, &digest, ! &digestlen) == -1) goto fail; if (!usm->engineidset) { if (usm_setengineid(agent->v3->sec, engineid, engineidlen) == -1) --- 342,359 ---- return -1; ber_free(&ber); ! if ((usmcookie = malloc(sizeof(*usmcookie))) == NULL) goto fail; + *cookie = usmcookie; + if (ber_scanf_elements(secparams, "{xddxpxx}", &engineid, &engineidlen, + &(usmcookie->boots), &(usmcookie->time), &user, &userlen, + &digestoffset, &digest, &digestlen, &salt, &saltlen) == -1) + goto fail; + if (saltlen != sizeof(usmcookie->salt) && saltlen != 0) + goto fail; + memcpy(&(usmcookie->salt), salt, saltlen); + if (!usm->engineidset) { if (usm_setengineid(agent->v3->sec, engineid, engineidlen) == -1) *************** *** 253,264 **** } if (!usm->bootsset) { ! usm->boots = boots; usm->bootsset = 1; } else { ! if (boots < usm->boots) goto fail; ! if (boots > usm->boots) { usm->bootsset = 0; usm->timeset = 0; usm_doinit(agent); --- 366,377 ---- } if (!usm->bootsset) { ! usm->boots = usmcookie->boots; usm->bootsset = 1; } else { ! if (usmcookie->boots < usm->boots) goto fail; ! if (usmcookie->boots > usm->boots) { usm->bootsset = 0; usm->timeset = 0; usm_doinit(agent); *************** *** 267,273 **** } if (!usm->timeset) { ! usm->time = time; if (clock_gettime(CLOCK_MONOTONIC, &usm->timecheck) == -1) goto fail; usm->timeset = 1; --- 380,386 ---- } if (!usm->timeset) { ! usm->time = usmcookie->time; if (clock_gettime(CLOCK_MONOTONIC, &usm->timecheck) == -1) goto fail; usm->timeset = 1; *************** *** 275,282 **** if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) goto fail; timespecsub(&now, &(usm->timecheck), &timediff); ! if (time < usm->time + timediff.tv_sec - USM_MAX_TIMEWINDOW || ! time > usm->time + timediff.tv_sec + USM_MAX_TIMEWINDOW) { usm->bootsset = 0; usm->timeset = 0; usm_doinit(agent); --- 388,397 ---- if (clock_gettime(CLOCK_MONOTONIC, &now) == -1) goto fail; timespecsub(&now, &(usm->timecheck), &timediff); ! if (usmcookie->time < ! usm->time + timediff.tv_sec - USM_MAX_TIMEWINDOW || ! usmcookie->time > ! usm->time + timediff.tv_sec + USM_MAX_TIMEWINDOW) { usm->bootsset = 0; usm->timeset = 0; usm_doinit(agent); *************** *** 308,317 **** --- 423,457 ---- return 0; fail: + free(usmcookie); ber_free_element(secparams); return -1; } + struct ber_element * + usm_decpdu(struct snmp_agent *agent, char *encpdu, size_t encpdulen, void *cookie) + { + struct usm_sec *usm = agent->v3->sec->data; + struct usm_cookie *usmcookie = cookie; + struct ber ber; + struct ber_element *scopedpdu; + char *rawpdu; + size_t rawpdulen; + + if ((rawpdu = usm_crypt(usm->cipher, 0, usm->privkey, usmcookie, + encpdu, encpdulen, &rawpdulen)) == NULL) + return NULL; + + bzero(&ber, sizeof(ber)); + ber_set_application(&ber, smi_application); + ber_set_readbuf(&ber, rawpdu, rawpdulen); + scopedpdu = ber_read_elements(&ber, NULL); + ber_free(&ber); + free(rawpdu); + + return scopedpdu; + } + static void usm_digest_pos(void *data, size_t offset) { *************** *** 327,332 **** --- 467,473 ---- free(usm->user); free(usm->authkey); + free(usm->privkey); free(usm->engineid); free(usm); } *************** *** 365,370 **** --- 506,548 ---- } int + usm_setpriv(struct snmp_sec *sec, const EVP_CIPHER *cipher, const char *key, + size_t keylen, enum usm_key_level level) + { + struct usm_sec *usm = sec->data; + char *lkey; + + if (usm->digest == NULL) { + errno = EINVAL; + return -1; + } + + /* + * We could transform a master key to a local key here if we already + * have usm_setengineid called. Sine snmpc.c is the only caller at + * the moment there's no need, since it always calls us first. + */ + if (level == USM_KEY_PASSWORD) { + if ((usm->privkey = usm_passwd2mkey(usm->digest, key)) == NULL) + return -1; + level = USM_KEY_MASTER; + keylen = EVP_MD_size(usm->digest); + } else { + if (keylen != (size_t)EVP_MD_size(usm->digest)) { + errno = EINVAL; + return -1; + } + if ((lkey = malloc(keylen)) == NULL) + return -1; + memcpy(lkey, key, keylen); + usm->privkey = lkey; + } + usm->cipher = cipher; + usm->privlevel = level; + return 0; + } + + int usm_setengineid(struct snmp_sec *sec, char *engineid, size_t engineidlen) { struct usm_sec *usm = sec->data; *************** *** 387,392 **** --- 565,580 ---- } free(mkey); usm->authlevel = USM_KEY_LOCALIZED; + } + if (usm->privlevel == USM_KEY_MASTER) { + mkey = usm->privkey; + if ((usm->privkey = usm_mkey2lkey(usm, usm->digest, + mkey)) == NULL) { + usm->privkey = mkey; + return -1; + } + free(mkey); + usm->privlevel = USM_KEY_LOCALIZED; } return 0;