#include <sys/param.h>
#include <sys/systm.h>
#include <sys/socket.h>
#include <sys/queue.h>
#include <sys/syslog.h>
#include <sys/errno.h>
#include <sys/mbuf.h>
#include <sys/mcache.h>
#include <mach/vm_param.h>
#include <kern/locks.h>
#include <string.h>
#include <net/if.h>
#include <net/route.h>
#include <net/net_osdep.h>
#include <netinet6/ipsec.h>
#include <netinet6/esp.h>
#include <netinet6/esp_chachapoly.h>
#include <netkey/key.h>
#include <netkey/keydb.h>
#include <corecrypto/cc.h>
#include <libkern/crypto/chacha20poly1305.h>
#define ESP_CHACHAPOLY_SALT_LEN 4
#define ESP_CHACHAPOLY_KEY_LEN 32
#define ESP_CHACHAPOLY_NONCE_LEN 12
_Static_assert(_Alignof(chacha20poly1305_ctx) <= 8,
"Alignment guarantee is broken");
#if ((( 8 * (ESP_CHACHAPOLY_KEY_LEN + ESP_CHACHAPOLY_SALT_LEN)) != ESP_CHACHAPOLY_KEYBITS_WITH_SALT) || \
(ESP_CHACHAPOLY_KEY_LEN != CCCHACHA20_KEY_NBYTES) || \
(ESP_CHACHAPOLY_NONCE_LEN != CCCHACHA20POLY1305_NONCE_NBYTES))
#error "Invalid sizes"
#endif
extern lck_mtx_t *sadb_mutex;
typedef struct _esp_chachapoly_ctx {
chacha20poly1305_ctx ccp_ctx;
uint8_t ccp_salt[ESP_CHACHAPOLY_SALT_LEN];
bool ccp_implicit_iv;
} esp_chachapoly_ctx_s, *esp_chachapoly_ctx_t;
#define ESP_ASSERT(_cond, _format, ...) \
do { \
if (!(_cond)) { \
panic("%s:%d " _format, __FUNCTION__, __LINE__, ##__VA_ARGS__); \
} \
} while (0)
#define ESP_CHECK_ARG(_arg) ESP_ASSERT(_arg != NULL, #_arg "is NULL")
#define _esp_log(_level, _format, ...) \
log(_level, "%s:%d " _format, __FUNCTION__, __LINE__, ##__VA_ARGS__)
#define esp_log_err(_format, ...) _esp_log(LOG_ERR, _format, ##__VA_ARGS__)
#define _esp_packet_log(_level, _format, ...) \
ipseclog((_level, "%s:%d " _format, __FUNCTION__, __LINE__, ##__VA_ARGS__))
#define esp_packet_log_err(_format, ...) _esp_packet_log(LOG_ERR, _format, ##__VA_ARGS__)
int
esp_chachapoly_mature(struct secasvar *sav)
{
const struct esp_algorithm *algo;
ESP_CHECK_ARG(sav);
if ((sav->flags & SADB_X_EXT_OLD) != 0) {
esp_log_err("ChaChaPoly is incompatible with SADB_X_EXT_OLD");
return 1;
}
if ((sav->flags & SADB_X_EXT_DERIV) != 0) {
esp_log_err("ChaChaPoly is incompatible with SADB_X_EXT_DERIV");
return 1;
}
if (sav->alg_enc != SADB_X_EALG_CHACHA20POLY1305) {
esp_log_err("ChaChaPoly unsupported algorithm %d",
sav->alg_enc);
return 1;
}
if (sav->key_enc == NULL) {
esp_log_err("ChaChaPoly key is missing");
return 1;
}
algo = esp_algorithm_lookup(sav->alg_enc);
if (algo == NULL) {
esp_log_err("ChaChaPoly lookup failed for algorithm %d",
sav->alg_enc);
return 1;
}
if (sav->key_enc->sadb_key_bits != ESP_CHACHAPOLY_KEYBITS_WITH_SALT) {
esp_log_err("ChaChaPoly invalid key length %d bits",
sav->key_enc->sadb_key_bits);
return 1;
}
return 0;
}
int
esp_chachapoly_schedlen(__unused const struct esp_algorithm *algo)
{
return sizeof(esp_chachapoly_ctx_s);
}
int
esp_chachapoly_schedule(__unused const struct esp_algorithm *algo,
struct secasvar *sav)
{
esp_chachapoly_ctx_t esp_ccp_ctx;
int rc = 0;
ESP_CHECK_ARG(sav);
if (sav->ivlen != ESP_CHACHAPOLY_IV_LEN) {
esp_log_err("Invalid ivlen %u", sav->ivlen);
return EINVAL;
}
if (_KEYLEN(sav->key_enc) != ESP_CHACHAPOLY_KEY_LEN + ESP_CHACHAPOLY_SALT_LEN) {
esp_log_err("Invalid key len %u", _KEYLEN(sav->key_enc));
return EINVAL;
}
LCK_MTX_ASSERT(sadb_mutex, LCK_MTX_ASSERT_OWNED);
esp_ccp_ctx = (esp_chachapoly_ctx_t)sav->sched;
rc = chacha20poly1305_init(&esp_ccp_ctx->ccp_ctx,
(const uint8_t *)_KEYBUF(sav->key_enc));
if (rc != 0) {
esp_log_err("chacha20poly1305_init returned %d", rc);
return rc;
}
memcpy(esp_ccp_ctx->ccp_salt,
(const uint8_t *)_KEYBUF(sav->key_enc) + ESP_CHACHAPOLY_KEY_LEN,
sizeof(esp_ccp_ctx->ccp_salt));
esp_ccp_ctx->ccp_implicit_iv = ((sav->flags & SADB_X_EXT_IIV) != 0);
return 0;
}
int
esp_chachapoly_encrypt_finalize(struct secasvar *sav,
unsigned char *tag,
unsigned int tag_bytes)
{
esp_chachapoly_ctx_t esp_ccp_ctx;
int rc = 0;
ESP_CHECK_ARG(sav);
ESP_CHECK_ARG(tag);
if (tag_bytes != ESP_CHACHAPOLY_ICV_LEN) {
esp_log_err("Invalid tag_bytes %u", tag_bytes);
return EINVAL;
}
esp_ccp_ctx = (esp_chachapoly_ctx_t)sav->sched;
rc = chacha20poly1305_finalize(&esp_ccp_ctx->ccp_ctx, tag);
if (rc != 0) {
esp_log_err("chacha20poly1305_finalize returned %d", rc);
return rc;
}
return 0;
}
int
esp_chachapoly_decrypt_finalize(struct secasvar *sav,
unsigned char *tag,
unsigned int tag_bytes)
{
esp_chachapoly_ctx_t esp_ccp_ctx;
int rc = 0;
ESP_CHECK_ARG(sav);
ESP_CHECK_ARG(tag);
if (tag_bytes != ESP_CHACHAPOLY_ICV_LEN) {
esp_log_err("Invalid tag_bytes %u", tag_bytes);
return EINVAL;
}
esp_ccp_ctx = (esp_chachapoly_ctx_t)sav->sched;
rc = chacha20poly1305_verify(&esp_ccp_ctx->ccp_ctx, tag);
if (rc != 0) {
esp_log_err("chacha20poly1305_finalize returned %d", rc);
return rc;
}
return 0;
}
int
esp_chachapoly_encrypt(struct mbuf *m, size_t off, __unused size_t plen,
struct secasvar *sav,
__unused const struct esp_algorithm *algo,
int ivlen)
{
struct mbuf *s = m; int32_t soff = 0; int32_t sn = 0; uint8_t *sp; size_t len; const int32_t ivoff = (int32_t)off + (int32_t)sizeof(struct newesp); int32_t bodyoff; int rc = 0; struct newesp esp_hdr; _Static_assert(sizeof(esp_hdr) == 8, "Bad size");
uint8_t nonce[ESP_CHACHAPOLY_NONCE_LEN];
esp_chachapoly_ctx_t esp_ccp_ctx;
ESP_CHECK_ARG(m);
ESP_CHECK_ARG(sav);
if (ivlen != ESP_CHACHAPOLY_IV_LEN) {
m_freem(m);
esp_log_err("Invalid ivlen %u", ivlen);
return EINVAL;
}
if (sav->ivlen != ESP_CHACHAPOLY_IV_LEN) {
m_freem(m);
esp_log_err("Invalid sav->ivlen %u", sav->ivlen);
return EINVAL;
}
esp_ccp_ctx = (esp_chachapoly_ctx_t)sav->sched;
if (esp_ccp_ctx->ccp_implicit_iv) {
bodyoff = ivoff;
} else {
bodyoff = ivoff + ivlen;
}
if (m->m_pkthdr.len < bodyoff) {
esp_log_err("Packet too short %d < %zu", m->m_pkthdr.len, bodyoff);
m_freem(m);
return EINVAL;
}
rc = chacha20poly1305_reset(&esp_ccp_ctx->ccp_ctx);
if (rc != 0) {
m_freem(m);
esp_log_err("chacha20poly1305_reset failed %d", rc);
return rc;
}
memset(sav->iv, 0, 4);
memcpy(sav->iv + 4, &sav->seq, sizeof(sav->seq));
_Static_assert(4 + sizeof(sav->seq) == ESP_CHACHAPOLY_IV_LEN,
"Bad IV length");
memcpy(nonce, esp_ccp_ctx->ccp_salt, ESP_CHACHAPOLY_SALT_LEN);
memcpy(nonce + ESP_CHACHAPOLY_SALT_LEN, sav->iv, ESP_CHACHAPOLY_IV_LEN);
_Static_assert(ESP_CHACHAPOLY_SALT_LEN + ESP_CHACHAPOLY_IV_LEN == sizeof(nonce),
"Bad nonce length");
rc = chacha20poly1305_setnonce(&esp_ccp_ctx->ccp_ctx, nonce);
if (rc != 0) {
m_freem(m);
esp_log_err("chacha20poly1305_setnonce failed %d", rc);
return rc;
}
if (!esp_ccp_ctx->ccp_implicit_iv) {
m_copyback(m, ivoff, ivlen, sav->iv);
}
cc_clear(sizeof(nonce), nonce);
m_copydata(m, (int)off, sizeof(esp_hdr), (void *)&esp_hdr);
rc = chacha20poly1305_aad(&esp_ccp_ctx->ccp_ctx,
sizeof(esp_hdr),
(void *)&esp_hdr);
if (rc != 0) {
m_freem(m);
esp_log_err("chacha20poly1305_aad failed %d", rc);
return rc;
}
while (s != NULL && soff < bodyoff) {
if (soff + s->m_len > bodyoff) {
sn = bodyoff - soff;
break;
}
soff += s->m_len;
s = s->m_next;
}
while (s != NULL && soff < m->m_pkthdr.len) {
len = (size_t)(s->m_len - sn);
if (len == 0) {
continue;
}
sp = mtod(s, uint8_t *) + sn;
rc = chacha20poly1305_encrypt(&esp_ccp_ctx->ccp_ctx,
len, sp, sp);
if (rc != 0) {
m_freem(m);
esp_log_err("chacha20poly1305_encrypt failed %d", rc);
return rc;
}
sn = 0;
soff += s->m_len;
s = s->m_next;
}
if (s == NULL && soff != m->m_pkthdr.len) {
m_freem(m);
esp_log_err("not enough mbufs %d %d", soff, m->m_pkthdr.len);
return EFBIG;
}
return 0;
}
int
esp_chachapoly_decrypt(struct mbuf *m, size_t off, struct secasvar *sav,
__unused const struct esp_algorithm *algo,
int ivlen)
{
struct mbuf *s = m; int32_t soff = 0; int32_t sn = 0; uint8_t *sp; size_t len; const int32_t ivoff = (int32_t)off + (int32_t)sizeof(struct newesp); int32_t bodyoff; int rc = 0; struct newesp esp_hdr; _Static_assert(sizeof(esp_hdr) == 8, "Bad size");
uint8_t nonce[ESP_CHACHAPOLY_NONCE_LEN];
esp_chachapoly_ctx_t esp_ccp_ctx;
ESP_CHECK_ARG(m);
ESP_CHECK_ARG(sav);
if (ivlen != ESP_CHACHAPOLY_IV_LEN) {
m_freem(m);
esp_log_err("Invalid ivlen %u", ivlen);
return EINVAL;
}
if (sav->ivlen != ESP_CHACHAPOLY_IV_LEN) {
m_freem(m);
esp_log_err("Invalid sav->ivlen %u", sav->ivlen);
return EINVAL;
}
esp_ccp_ctx = (esp_chachapoly_ctx_t)sav->sched;
if (esp_ccp_ctx->ccp_implicit_iv) {
bodyoff = ivoff;
} else {
bodyoff = ivoff + ivlen;
}
if (m->m_pkthdr.len < bodyoff) {
esp_packet_log_err("Packet too short %d < %zu", m->m_pkthdr.len, bodyoff);
m_freem(m);
return EINVAL;
}
rc = chacha20poly1305_reset(&esp_ccp_ctx->ccp_ctx);
if (rc != 0) {
m_freem(m);
esp_log_err("chacha20poly1305_reset failed %d", rc);
return rc;
}
m_copydata(m, (int)off, sizeof(esp_hdr), (void *)&esp_hdr);
memcpy(nonce, esp_ccp_ctx->ccp_salt, ESP_CHACHAPOLY_SALT_LEN);
if (esp_ccp_ctx->ccp_implicit_iv) {
memset(nonce + ESP_CHACHAPOLY_SALT_LEN, 0, 4);
memcpy(nonce + ESP_CHACHAPOLY_SALT_LEN + 4, &esp_hdr.esp_seq, sizeof(esp_hdr.esp_seq));
_Static_assert(4 + sizeof(esp_hdr.esp_seq) == ESP_CHACHAPOLY_IV_LEN, "Bad IV length");
} else {
m_copydata(m, ivoff, ESP_CHACHAPOLY_IV_LEN, nonce + ESP_CHACHAPOLY_SALT_LEN);
}
_Static_assert(ESP_CHACHAPOLY_SALT_LEN + ESP_CHACHAPOLY_IV_LEN == sizeof(nonce),
"Bad nonce length");
rc = chacha20poly1305_setnonce(&esp_ccp_ctx->ccp_ctx, nonce);
if (rc != 0) {
m_freem(m);
esp_log_err("chacha20poly1305_setnonce failed %d", rc);
return rc;
}
cc_clear(sizeof(nonce), nonce);
rc = chacha20poly1305_aad(&esp_ccp_ctx->ccp_ctx,
sizeof(esp_hdr),
(void *)&esp_hdr);
if (rc != 0) {
m_freem(m);
esp_log_err("chacha20poly1305_aad failed %d", rc);
return rc;
}
while (s != NULL && soff < bodyoff) {
if (soff + s->m_len > bodyoff) {
sn = bodyoff - soff;
break;
}
soff += s->m_len;
s = s->m_next;
}
while (s != NULL && soff < m->m_pkthdr.len) {
len = (size_t)(s->m_len - sn);
if (len == 0) {
continue;
}
sp = mtod(s, uint8_t *) + sn;
rc = chacha20poly1305_decrypt(&esp_ccp_ctx->ccp_ctx,
len, sp, sp);
if (rc != 0) {
m_freem(m);
esp_packet_log_err("chacha20poly1305_decrypt failed %d", rc);
return rc;
}
sn = 0;
soff += s->m_len;
s = s->m_next;
}
if (s == NULL && soff != m->m_pkthdr.len) {
m_freem(m);
esp_packet_log_err("not enough mbufs %d %d", soff, m->m_pkthdr.len);
return EFBIG;
}
return 0;
}