File: [local] / src / usr.sbin / snmpd / usm.c (download)
Revision 1.30, Fri Dec 22 13:03:16 2023 UTC (5 months, 1 week ago) by martijn
Branch: MAIN
CVS Tags: OPENBSD_7_5_BASE, OPENBSD_7_5, HEAD Changes since 1.29: +2 -23 lines
Remove a log_debug from usm_checkuser(). It would only display what is in
the config and was never actually displayed because of insufficient
verbosity level during config-parsing and would display the wrong auth
algorithm for SHA2.
OK tb@
|
/* $OpenBSD: usm.c,v 1.30 2023/12/22 13:03:16 martijn Exp $ */
/*
* Copyright (c) 2012 GeNUA mbH
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <sys/queue.h>
#include <sys/types.h>
#include <openssl/evp.h>
#include <openssl/hmac.h>
#ifdef DEBUG
#include <assert.h>
#endif
#include <ber.h>
#include <endian.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include "application.h"
#include "log.h"
#include "mib.h"
#include "snmp.h"
#include "snmpd.h"
SLIST_HEAD(, usmuser) usmuserlist;
const EVP_MD *usm_get_md(enum usmauth);
size_t usm_get_digestlen(enum usmauth);
const EVP_CIPHER *usm_get_cipher(enum usmpriv);
int usm_valid_digestlen(size_t digestlen);
void usm_cb_digest(void *, size_t);
int usm_valid_digest(struct snmp_message *, off_t, char *,
size_t);
struct ber_element *usm_decrypt(struct snmp_message *,
struct ber_element *);
ssize_t usm_crypt(struct snmp_message *, u_char *, int,
u_char *, int);
char *usm_passwd2key(const EVP_MD *, char *, int *);
void
usm_generate_keys(void)
{
struct usmuser *up;
const EVP_MD *md;
char *key;
int len;
SLIST_FOREACH(up, &usmuserlist, uu_next) {
if ((md = usm_get_md(up->uu_auth)) == NULL)
continue;
/* convert auth password to key */
len = 0;
key = usm_passwd2key(md, up->uu_authkey, &len);
free(up->uu_authkey);
up->uu_authkey = key;
up->uu_authkeylen = len;
/* optionally convert privacy password to key */
if (up->uu_priv != PRIV_NONE) {
arc4random_buf(&up->uu_salt, sizeof(up->uu_salt));
len = SNMP_CIPHER_KEYLEN;
key = usm_passwd2key(md, up->uu_privkey, &len);
free(up->uu_privkey);
up->uu_privkey = key;
}
}
return;
}
const EVP_MD *
usm_get_md(enum usmauth ua)
{
switch (ua) {
case AUTH_MD5:
return EVP_md5();
case AUTH_SHA1:
return EVP_sha1();
case AUTH_SHA224:
return EVP_sha224();
case AUTH_SHA256:
return EVP_sha256();
case AUTH_SHA384:
return EVP_sha384();
case AUTH_SHA512:
return EVP_sha512();
case AUTH_NONE:
default:
return NULL;
}
}
size_t
usm_get_digestlen(enum usmauth ua)
{
switch (ua) {
case AUTH_MD5:
case AUTH_SHA1:
return 12;
case AUTH_SHA224:
return 16;
case AUTH_SHA256:
return 24;
case AUTH_SHA384:
return 32;
case AUTH_SHA512:
return 48;
case AUTH_NONE:
default:
return 0;
}
}
const EVP_CIPHER *
usm_get_cipher(enum usmpriv up)
{
switch (up) {
case PRIV_DES:
return EVP_des_cbc();
case PRIV_AES:
return EVP_aes_128_cfb128();
case PRIV_NONE:
default:
return NULL;
}
}
int
usm_valid_digestlen(size_t digestlen)
{
switch (digestlen) {
case 0:
case 12:
case 16:
case 24:
case 32:
case 48:
return 1;
default:
return 0;
}
}
struct usmuser *
usm_newuser(char *name, const char **errp)
{
struct usmuser *up = usm_finduser(name);
if (up != NULL) {
*errp = "user redefined";
return NULL;
}
if ((up = calloc(1, sizeof(*up))) == NULL)
fatal("usm");
up->uu_name = name;
SLIST_INSERT_HEAD(&usmuserlist, up, uu_next);
return up;
}
const struct usmuser *
usm_check_mincred(int minlevel, const char **errstr)
{
struct usmuser *up;
if (minlevel == 0)
return NULL;
SLIST_FOREACH(up, &usmuserlist, uu_next) {
if (minlevel & SNMP_MSGFLAG_PRIV && up->uu_privkey == NULL) {
*errstr = "missing enckey";
return up;
}
if (minlevel & SNMP_MSGFLAG_AUTH && up->uu_authkey == NULL) {
*errstr = "missing authkey";
return up;
}
}
return NULL;
}
struct usmuser *
usm_finduser(char *name)
{
struct usmuser *up;
SLIST_FOREACH(up, &usmuserlist, uu_next) {
if (!strcmp(up->uu_name, name))
return up;
}
return NULL;
}
int
usm_checkuser(struct usmuser *up, const char **errp)
{
if (up->uu_auth != AUTH_NONE && up->uu_authkey == NULL) {
*errp = "missing auth passphrase";
goto fail;
} else if (up->uu_auth == AUTH_NONE && up->uu_authkey != NULL)
up->uu_auth = AUTH_DEFAULT;
if (up->uu_priv != PRIV_NONE && up->uu_privkey == NULL) {
*errp = "missing priv passphrase";
goto fail;
} else if (up->uu_priv == PRIV_NONE && up->uu_privkey != NULL)
up->uu_priv = PRIV_DEFAULT;
if (up->uu_auth == AUTH_NONE && up->uu_priv != PRIV_NONE) {
/* Standard prohibits noAuthPriv */
*errp = "auth is mandatory with enc";
goto fail;
}
switch (up->uu_auth) {
case AUTH_NONE:
break;
case AUTH_MD5:
case AUTH_SHA1:
case AUTH_SHA224:
case AUTH_SHA256:
case AUTH_SHA384:
case AUTH_SHA512:
up->uu_seclevel |= SNMP_MSGFLAG_AUTH;
break;
}
switch (up->uu_priv) {
case PRIV_NONE:
break;
case PRIV_DES:
case PRIV_AES:
up->uu_seclevel |= SNMP_MSGFLAG_PRIV;
break;
}
return 0;
fail:
free(up->uu_name);
free(up->uu_authkey);
free(up->uu_privkey);
SLIST_REMOVE(&usmuserlist, up, usmuser, uu_next);
free(up);
return -1;
}
struct ber_element *
usm_decode(struct snmp_message *msg, struct ber_element *elm, const char **errp)
{
struct snmp_stats *stats = &snmpd_env->sc_stats;
off_t offs, offs2;
char *usmparams;
size_t len;
size_t enginelen, userlen, digestlen, saltlen;
struct ber ber;
struct ber_element *usm = NULL, *next = NULL, *decr;
char *engineid;
char *user;
char *digest, *salt;
u_long now;
long long engine_boots, engine_time;
bzero(&ber, sizeof(ber));
offs = ober_getpos(elm);
if (ober_get_nstring(elm, (void *)&usmparams, &len) < 0) {
*errp = "cannot decode security params";
msg->sm_flags &= SNMP_MSGFLAG_REPORT;
goto done;
}
ober_set_readbuf(&ber, usmparams, len);
usm = ober_read_elements(&ber, NULL);
if (usm == NULL) {
*errp = "cannot decode security params";
msg->sm_flags &= SNMP_MSGFLAG_REPORT;
goto done;
}
#ifdef DEBUG
fprintf(stderr, "decode USM parameters:\n");
smi_debug_elements(usm);
#endif
if (ober_scanf_elements(usm, "{xiixpxx$", &engineid, &enginelen,
&engine_boots, &engine_time, &user, &userlen, &offs2,
&digest, &digestlen, &salt, &saltlen) != 0) {
*errp = "cannot decode USM params";
msg->sm_flags &= SNMP_MSGFLAG_REPORT;
goto done;
}
log_debug("USM: engineid '%s', engine boots %lld, engine time %lld, "
"user '%s'", tohexstr(engineid, enginelen), engine_boots,
engine_time, user);
if (enginelen > SNMPD_MAXENGINEIDLEN ||
userlen > SNMPD_MAXUSERNAMELEN ||
!usm_valid_digestlen(digestlen) ||
(saltlen != (MSG_HAS_PRIV(msg) ? SNMP_USM_SALTLEN : 0))) {
*errp = "bad field length";
msg->sm_flags &= SNMP_MSGFLAG_REPORT;
goto done;
}
if (enginelen != snmpd_env->sc_engineid_len ||
memcmp(engineid, snmpd_env->sc_engineid, enginelen) != 0) {
*errp = "unknown engine id";
msg->sm_usmerr = OIDVAL_usmErrEngineId;
stats->snmp_usmnosuchengine++;
msg->sm_flags &= SNMP_MSGFLAG_REPORT;
goto done;
}
msg->sm_engine_boots = (u_int32_t)engine_boots;
msg->sm_engine_time = (u_int32_t)engine_time;
memcpy(msg->sm_username, user, userlen);
msg->sm_username[userlen] = '\0';
msg->sm_user = usm_finduser(msg->sm_username);
if (msg->sm_user == NULL) {
*errp = "no such user";
msg->sm_usmerr = OIDVAL_usmErrUserName;
stats->snmp_usmnosuchuser++;
msg->sm_flags &= SNMP_MSGFLAG_REPORT;
goto done;
}
if (MSG_SECLEVEL(msg) > msg->sm_user->uu_seclevel) {
*errp = "unsupported security model";
msg->sm_usmerr = OIDVAL_usmErrSecLevel;
stats->snmp_usmbadseclevel++;
msg->sm_flags &= SNMP_MSGFLAG_REPORT;
goto done;
}
/*
* offs is the offset of the USM string within the serialized msg
* and offs2 the offset of the digest within the USM string.
*/
if (!usm_valid_digest(msg, offs + offs2, digest, digestlen)) {
*errp = "bad msg digest";
msg->sm_usmerr = OIDVAL_usmErrDigest;
stats->snmp_usmwrongdigest++;
msg->sm_flags &= SNMP_MSGFLAG_REPORT;
goto done;
}
if (MSG_HAS_PRIV(msg)) {
memcpy(msg->sm_salt, salt, saltlen);
if ((decr = usm_decrypt(msg, elm->be_next)) == NULL) {
*errp = "cannot decrypt msg";
msg->sm_usmerr = OIDVAL_usmErrDecrypt;
stats->snmp_usmdecrypterr++;
msg->sm_flags &= SNMP_MSGFLAG_REPORT;
goto done;
}
ober_replace_elements(elm, decr);
}
if (MSG_HAS_AUTH(msg)) {
now = snmpd_engine_time();
if (engine_boots != snmpd_env->sc_engine_boots ||
engine_time < (long long)(now - SNMP_MAX_TIMEWINDOW) ||
engine_time > (long long)(now + SNMP_MAX_TIMEWINDOW)) {
*errp = "out of time window";
msg->sm_usmerr = OIDVAL_usmErrTimeWindow;
stats->snmp_usmtimewindow++;
goto done;
}
}
next = elm->be_next;
done:
ober_free(&ber);
if (usm != NULL)
ober_free_elements(usm);
return next;
}
struct ber_element *
usm_encode(struct snmp_message *msg, struct ber_element *e)
{
struct ber ber;
struct ber_element *usm, *a, *res = NULL;
void *ptr;
char digest[SNMP_USM_MAXDIGESTLEN];
size_t digestlen, saltlen;
ssize_t len;
msg->sm_digest_offs = 0;
bzero(&ber, sizeof(ber));
usm = ober_add_sequence(NULL);
if (MSG_HAS_AUTH(msg)) {
/*
* Fill in enough zeroes and remember the position within the
* messages. The digest will be calculated once the message
* is complete.
*/
#ifdef DEBUG
assert(msg->sm_user != NULL);
#endif
bzero(digest, sizeof(digest));
digestlen = usm_get_digestlen(msg->sm_user->uu_auth);
} else
digestlen = 0;
if (MSG_HAS_PRIV(msg)) {
#ifdef DEBUG
assert(msg->sm_user != NULL);
#endif
++(msg->sm_user->uu_salt);
memcpy(msg->sm_salt, &msg->sm_user->uu_salt,
sizeof(msg->sm_salt));
saltlen = sizeof(msg->sm_salt);
} else
saltlen = 0;
msg->sm_engine_boots = (u_int32_t)snmpd_env->sc_engine_boots;
msg->sm_engine_time = (u_int32_t)snmpd_engine_time();
if ((a = ober_printf_elements(usm, "xdds",
snmpd_env->sc_engineid, snmpd_env->sc_engineid_len,
msg->sm_engine_boots, msg->sm_engine_time,
msg->sm_username)) == NULL)
goto done;
if ((a = ober_add_nstring(a, digest, digestlen)) == NULL)
goto done;
if (digestlen > 0)
ober_set_writecallback(a, usm_cb_digest, msg);
if ((a = ober_add_nstring(a, msg->sm_salt, saltlen)) == NULL)
goto done;
#ifdef DEBUG
fprintf(stderr, "encode USM parameters:\n");
smi_debug_elements(usm);
#endif
len = ober_write_elements(&ber, usm);
if (ober_get_writebuf(&ber, &ptr) > 0) {
res = ober_add_nstring(e, (char *)ptr, len);
if (digestlen > 0)
ober_set_writecallback(res, usm_cb_digest, msg);
}
done:
ober_free(&ber);
ober_free_elements(usm);
return res;
}
void
usm_cb_digest(void *arg, size_t offs)
{
struct snmp_message *msg = arg;
msg->sm_digest_offs += offs;
}
struct ber_element *
usm_encrypt(struct snmp_message *msg, struct ber_element *pdu)
{
struct ber ber;
struct ber_element *encrpdu = NULL;
void *ptr;
ssize_t elen, len;
u_char *encbuf;
if (!MSG_HAS_PRIV(msg))
return pdu;
bzero(&ber, sizeof(ber));
#ifdef DEBUG
fprintf(stderr, "encrypted PDU:\n");
smi_debug_elements(pdu);
#endif
len = ober_write_elements(&ber, pdu);
if (ober_get_writebuf(&ber, &ptr) > 0 &&
(encbuf = malloc(len + EVP_MAX_BLOCK_LENGTH)) != NULL) {
elen = usm_crypt(msg, ptr, len, encbuf, 1);
if (elen > 0)
encrpdu = ober_add_nstring(NULL, (char *)encbuf, elen);
free(encbuf);
}
ober_free(&ber);
ober_free_elements(pdu);
return encrpdu;
}
/*
* Calculate message digest and replace within message
*/
void
usm_finalize_digest(struct snmp_message *msg, char *buf, ssize_t len)
{
const EVP_MD *md;
u_char digest[EVP_MAX_MD_SIZE];
size_t digestlen;
unsigned hlen;
if (msg->sm_resp == NULL ||
!MSG_HAS_AUTH(msg) ||
msg->sm_user == NULL ||
msg->sm_digest_offs == 0 ||
len <= 0)
return;
if ((digestlen = usm_get_digestlen(msg->sm_user->uu_auth)) == 0)
return;
bzero(digest, digestlen);
#ifdef DEBUG
assert(msg->sm_digest_offs + digestlen <= (size_t)len);
assert(!memcmp(buf + msg->sm_digest_offs, digest, digestlen));
#endif
if ((md = usm_get_md(msg->sm_user->uu_auth)) == NULL)
return;
HMAC(md, msg->sm_user->uu_authkey, (int)msg->sm_user->uu_authkeylen,
(u_char*)buf, (size_t)len, digest, &hlen);
memcpy(buf + msg->sm_digest_offs, digest, digestlen);
return;
}
void
usm_make_report(struct snmp_message *msg)
{
appl_report(msg, 0, &OID(MIB_usmStats, msg->sm_usmerr, 0));
}
int
usm_valid_digest(struct snmp_message *msg, off_t offs,
char *digest, size_t digestlen)
{
const EVP_MD *md;
u_char exp_digest[EVP_MAX_MD_SIZE];
unsigned hlen;
if (!MSG_HAS_AUTH(msg))
return 1;
if (digestlen != usm_get_digestlen(msg->sm_user->uu_auth))
return 0;
#ifdef DEBUG
assert(offs + digestlen <= msg->sm_datalen);
assert(bcmp(&msg->sm_data[offs], digest, digestlen) == 0);
#endif
if ((md = usm_get_md(msg->sm_user->uu_auth)) == NULL)
return 0;
memset(&msg->sm_data[offs], 0, digestlen);
HMAC(md, msg->sm_user->uu_authkey, (int)msg->sm_user->uu_authkeylen,
msg->sm_data, msg->sm_datalen, exp_digest, &hlen);
/* we don't bother to restore the original message */
if (hlen < digestlen)
return 0;
return memcmp(digest, exp_digest, digestlen) == 0;
}
struct ber_element *
usm_decrypt(struct snmp_message *msg, struct ber_element *encr)
{
u_char *privstr;
size_t privlen;
u_char *buf;
struct ber ber;
struct ber_element *scoped_pdu = NULL;
ssize_t scoped_pdu_len;
if (ober_get_nstring(encr, (void *)&privstr, &privlen) < 0 ||
(buf = malloc(privlen)) == NULL)
return NULL;
scoped_pdu_len = usm_crypt(msg, privstr, (int)privlen, buf, 0);
if (scoped_pdu_len < 0) {
free(buf);
return NULL;
}
bzero(&ber, sizeof(ber));
ober_set_application(&ber, smi_application);
ober_set_readbuf(&ber, buf, scoped_pdu_len);
scoped_pdu = ober_read_elements(&ber, NULL);
#ifdef DEBUG
if (scoped_pdu != NULL) {
fprintf(stderr, "decrypted scoped PDU:\n");
smi_debug_elements(scoped_pdu);
}
#endif
ober_free(&ber);
free(buf);
return scoped_pdu;
}
ssize_t
usm_crypt(struct snmp_message *msg, u_char *inbuf, int inlen, u_char *outbuf,
int do_encrypt)
{
const EVP_CIPHER *cipher;
EVP_CIPHER_CTX *ctx;
u_char *privkey;
int i;
u_char iv[EVP_MAX_IV_LENGTH];
int len, len2;
int rv;
u_int32_t ivv;
if ((cipher = usm_get_cipher(msg->sm_user->uu_priv)) == NULL)
return -1;
privkey = (u_char *)msg->sm_user->uu_privkey;
#ifdef DEBUG
assert(privkey != NULL);
#endif
switch (msg->sm_user->uu_priv) {
case PRIV_DES:
/* RFC3414, chap 8.1.1.1. */
for (i = 0; i < 8; i++)
iv[i] = msg->sm_salt[i] ^ privkey[SNMP_USM_SALTLEN + i];
break;
case PRIV_AES:
/* RFC3826, chap 3.1.2.1. */
ivv = htobe32(msg->sm_engine_boots);
memcpy(iv, &ivv, sizeof(ivv));
ivv = htobe32(msg->sm_engine_time);
memcpy(iv + sizeof(ivv), &ivv, sizeof(ivv));
memcpy(iv + 2 * sizeof(ivv), msg->sm_salt, SNMP_USM_SALTLEN);
break;
default:
return -1;
}
if ((ctx = EVP_CIPHER_CTX_new()) == NULL)
return -1;
if (!EVP_CipherInit(ctx, cipher, privkey, iv, do_encrypt)) {
EVP_CIPHER_CTX_free(ctx);
return -1;
}
if (!do_encrypt)
EVP_CIPHER_CTX_set_padding(ctx, 0);
if (EVP_CipherUpdate(ctx, outbuf, &len, inbuf, inlen) &&
EVP_CipherFinal_ex(ctx, outbuf + len, &len2))
rv = len + len2;
else
rv = -1;
EVP_CIPHER_CTX_free(ctx);
return rv;
}
/*
* RFC3414, Password to Key Algorithm
*/
char *
usm_passwd2key(const EVP_MD *md, char *passwd, int *maxlen)
{
EVP_MD_CTX *ctx;
int i, count;
u_char *pw, *c;
u_char pwbuf[2 * EVP_MAX_MD_SIZE + SNMPD_MAXENGINEIDLEN];
u_char keybuf[EVP_MAX_MD_SIZE];
unsigned dlen;
char *key;
if ((ctx = EVP_MD_CTX_new()) == NULL)
return NULL;
if (!EVP_DigestInit_ex(ctx, md, NULL)) {
EVP_MD_CTX_free(ctx);
return NULL;
}
pw = (u_char *)passwd;
for (count = 0; count < 1048576; count += 64) {
c = pwbuf;
for (i = 0; i < 64; i++) {
if (*pw == '\0')
pw = (u_char *)passwd;
*c++ = *pw++;
}
if (!EVP_DigestUpdate(ctx, pwbuf, 64)) {
EVP_MD_CTX_free(ctx);
return NULL;
}
}
if (!EVP_DigestFinal_ex(ctx, keybuf, &dlen)) {
EVP_MD_CTX_free(ctx);
return NULL;
}
/* Localize the key */
#ifdef DEBUG
assert(snmpd_env->sc_engineid_len <= SNMPD_MAXENGINEIDLEN);
#endif
memcpy(pwbuf, keybuf, dlen);
memcpy(pwbuf + dlen, snmpd_env->sc_engineid,
snmpd_env->sc_engineid_len);
memcpy(pwbuf + dlen + snmpd_env->sc_engineid_len, keybuf, dlen);
if (!EVP_DigestInit_ex(ctx, md, NULL) ||
!EVP_DigestUpdate(ctx, pwbuf,
2 * dlen + snmpd_env->sc_engineid_len) ||
!EVP_DigestFinal_ex(ctx, keybuf, &dlen)) {
EVP_MD_CTX_free(ctx);
return NULL;
}
EVP_MD_CTX_free(ctx);
if (*maxlen > 0 && dlen > (unsigned)*maxlen)
dlen = (unsigned)*maxlen;
if ((key = malloc(dlen)) == NULL)
fatal("key");
memcpy(key, keybuf, dlen);
*maxlen = (int)dlen;
return key;
}