diff options
author | Martijn van Duren <martijn@cvs.openbsd.org> | 2019-09-18 09:54:37 +0000 |
---|---|---|
committer | Martijn van Duren <martijn@cvs.openbsd.org> | 2019-09-18 09:54:37 +0000 |
commit | 6124587d3ce2e2fe998f4c7c05ad3b1bd0d89474 (patch) | |
tree | fe9b898310c809866d16a36a3e3731d9a4336dd0 /usr.bin/snmp/usm.c | |
parent | 4ef48e0cd179f9842390d743e3bea521f30642a8 (diff) |
Add support for SNMPv3/USM privacy.
net-snmp's -3K is supported via the -K parameter, -3K is not supported.
Feedback and OK jmatthew@
Diffstat (limited to 'usr.bin/snmp/usm.c')
-rw-r--r-- | usr.bin/snmp/usm.c | 232 |
1 files changed, 210 insertions, 22 deletions
diff --git a/usr.bin/snmp/usm.c b/usr.bin/snmp/usm.c index 1ca43316b43..7da977c9b25 100644 --- a/usr.bin/snmp/usm.c +++ b/usr.bin/snmp/usm.c @@ -1,4 +1,4 @@ -/* $OpenBSD: usm.c,v 1.2 2019/09/18 09:52:47 martijn Exp $ */ +/* $OpenBSD: usm.c,v 1.3 2019/09/18 09:54:36 martijn Exp $ */ /* * Copyright (c) 2019 Martijn van Duren <martijn@openbsd.org> @@ -44,6 +44,9 @@ struct usm_sec { 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,13 +56,21 @@ struct usm_sec { 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); + 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,7 +106,9 @@ usm_init(const char *user, size_t userlen) 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,12 +152,11 @@ usm_genparams(struct snmp_agent *agent, size_t *len, void **cookie) struct ber_element *params, *digestelm; struct usm_sec *usm = agent->v3->sec->data; char digest[USM_MAX_DIGESTLEN]; - size_t digestlen = 0; + size_t digestlen = 0, saltlen = 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,21 +164,27 @@ usm_genparams(struct snmp_agent *agent, size_t *len, void **cookie) 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); - time = usm->time + timediff.tv_sec; + usmcookie->time = usm->time + timediff.tv_sec; } else - time = 0; - boots = usm->boots; + 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, boots, time, usm->user, usm->userlen, digest, - digestlen, NULL, (size_t) 0)) == NULL) { + usm->engineidlen, usmcookie->boots, usmcookie->time, usm->user, + usm->userlen, digest, digestlen, &(usmcookie->salt), + saltlen)) == NULL) { free(usmcookie); return NULL; } @@ -190,6 +208,93 @@ usm_genparams(struct snmp_agent *agent, size_t *len, void **cookie) 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,17 +320,18 @@ usm_finalparams(struct snmp_agent *agent, char *buf, size_t buflen, static int usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen, - off_t secparamsoffset, char *buf, size_t buflen, uint8_t level) + 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; - size_t engineidlen, userlen, digestlen; + 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]; - uint32_t boots, time; + struct usm_cookie *usmcookie; bzero(&ber, sizeof(ber)); bzero(exp_digest, sizeof(exp_digest)); @@ -236,10 +342,17 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen, return -1; ber_free(&ber); - if (ber_scanf_elements(secparams, "{xddxpxS}", &engineid, &engineidlen, - &boots, &time, &user, &userlen, &digestoffset, &digest, - &digestlen) == -1) + 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, @@ -253,12 +366,12 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen, } if (!usm->bootsset) { - usm->boots = boots; + usm->boots = usmcookie->boots; usm->bootsset = 1; } else { - if (boots < usm->boots) + if (usmcookie->boots < usm->boots) goto fail; - if (boots > usm->boots) { + if (usmcookie->boots > usm->boots) { usm->bootsset = 0; usm->timeset = 0; usm_doinit(agent); @@ -267,7 +380,7 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen, } if (!usm->timeset) { - usm->time = time; + usm->time = usmcookie->time; if (clock_gettime(CLOCK_MONOTONIC, &usm->timecheck) == -1) goto fail; usm->timeset = 1; @@ -275,8 +388,10 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen, 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) { + 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,10 +423,35 @@ usm_parseparams(struct snmp_agent *agent, char *packet, size_t packetlen, 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,6 +467,7 @@ usm_free(void *data) free(usm->user); free(usm->authkey); + free(usm->privkey); free(usm->engineid); free(usm); } @@ -365,6 +506,43 @@ usm_setauth(struct snmp_sec *sec, const EVP_MD *digest, const char *key, } 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; @@ -388,6 +566,16 @@ usm_setengineid(struct snmp_sec *sec, char *engineid, size_t engineidlen) 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; } |