[BACK]Return to usm.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / snmp

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: }