372 lines
9.2 KiB
C
372 lines
9.2 KiB
C
/*
|
|
* Copyright (c) 2013-2021 Apple Inc. All rights reserved.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_START@
|
|
*
|
|
* This file contains Original Code and/or Modifications of Original Code
|
|
* as defined in and that are subject to the Apple Public Source License
|
|
* Version 2.0 (the 'License'). You may not use this file except in
|
|
* compliance with the License. The rights granted to you under the License
|
|
* may not be used to create, or enable the creation or redistribution of,
|
|
* unlawful or unlicensed copies of an Apple operating system, or to
|
|
* circumvent, violate, or enable the circumvention or violation of, any
|
|
* terms of an Apple operating system software license agreement.
|
|
*
|
|
* Please obtain a copy of the License at
|
|
* http://www.opensource.apple.com/apsl/ and read it before using this file.
|
|
*
|
|
* The Original Code and all software distributed under the License are
|
|
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
|
|
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
|
|
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
|
|
* Please see the License for the specific language governing rights and
|
|
* limitations under the License.
|
|
*
|
|
* @APPLE_OSREFERENCE_LICENSE_HEADER_END@
|
|
*/
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/malloc.h>
|
|
|
|
#include <kern/locks.h>
|
|
|
|
#include <libkern/crypto/sha1.h>
|
|
|
|
#include <net/if.h>
|
|
|
|
#include <netinet/in.h>
|
|
#include <netinet6/in6_var.h>
|
|
#include <netinet/ip6.h>
|
|
#include <netinet6/ip6_var.h>
|
|
#include <netinet6/nd6.h>
|
|
|
|
#define IN6_CGA_HASH1_LENGTH 8
|
|
#define IN6_CGA_HASH2_LENGTH 14
|
|
#define IN6_CGA_PREPARE_ZEROES 9
|
|
|
|
struct in6_cga_hash1 {
|
|
u_int8_t octets[IN6_CGA_HASH1_LENGTH];
|
|
};
|
|
|
|
struct in6_cga_hash2 {
|
|
u_int8_t octets[IN6_CGA_HASH2_LENGTH];
|
|
};
|
|
|
|
struct in6_cga_singleton {
|
|
boolean_t cga_initialized;
|
|
decl_lck_mtx_data(, cga_mutex);
|
|
struct in6_cga_prepare cga_prepare;
|
|
struct iovec cga_pubkey;
|
|
struct iovec cga_privkey;
|
|
};
|
|
|
|
static struct in6_cga_singleton in6_cga = {
|
|
.cga_initialized = FALSE,
|
|
.cga_mutex = {},
|
|
.cga_prepare = {
|
|
.cga_modifier = {},
|
|
.cga_security_level = 0,
|
|
},
|
|
.cga_pubkey = {
|
|
.iov_base = NULL,
|
|
.iov_len = 0,
|
|
},
|
|
.cga_privkey = {
|
|
.iov_base = NULL,
|
|
.iov_len = 0,
|
|
},
|
|
};
|
|
|
|
static void
|
|
in6_cga_node_lock_assert(int owned)
|
|
{
|
|
#if !MACH_ASSERT
|
|
#pragma unused(owned)
|
|
#endif
|
|
VERIFY(in6_cga.cga_initialized);
|
|
LCK_MTX_ASSERT(&in6_cga.cga_mutex, owned);
|
|
}
|
|
|
|
static boolean_t
|
|
in6_cga_is_prepare_valid(const struct in6_cga_prepare *prepare,
|
|
const struct iovec *pubkey)
|
|
{
|
|
static const u_int8_t zeroes[IN6_CGA_PREPARE_ZEROES] = { };
|
|
SHA1_CTX ctx;
|
|
u_int8_t sha1[SHA1_RESULTLEN];
|
|
u_int i, n;
|
|
|
|
VERIFY(prepare != NULL);
|
|
VERIFY(pubkey != NULL && pubkey->iov_base != NULL);
|
|
|
|
if (prepare->cga_security_level == 0) {
|
|
return TRUE;
|
|
}
|
|
|
|
if (prepare->cga_security_level > 7) {
|
|
return FALSE;
|
|
}
|
|
|
|
SHA1Init(&ctx);
|
|
SHA1Update(&ctx, &prepare->cga_modifier.octets,
|
|
IN6_CGA_MODIFIER_LENGTH);
|
|
SHA1Update(&ctx, &zeroes, IN6_CGA_PREPARE_ZEROES);
|
|
SHA1Update(&ctx, pubkey->iov_base, pubkey->iov_len);
|
|
/* FUTURE: extension fields */
|
|
SHA1Final(sha1, &ctx);
|
|
|
|
n = 2 * (u_int) prepare->cga_security_level;
|
|
VERIFY(n < SHA1_RESULTLEN);
|
|
for (i = 0; i < n; ++i) {
|
|
if (sha1[i] != 0) {
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/*
|
|
* @brief Generate interface identifier for CGA
|
|
* XXX You may notice that following does not really
|
|
* mirror what is decribed in:
|
|
* https://tools.ietf.org/html/rfc3972#section-4
|
|
* By design kernel here will assume that that
|
|
* modifier has been converged on by userspace
|
|
* for first part of the algorithm for the given
|
|
* security level.
|
|
* We are not doing that yet but that's how the code
|
|
* below is written. So really we are starting
|
|
* from bullet 4 of the algorithm.
|
|
*
|
|
* @param prepare Pointer to object containing modifier,
|
|
* security level & extension to be used.
|
|
* @param pubkey Public key used for IID generation
|
|
* @param collisions Collission count on DAD failure
|
|
* XXX We are not really re-generating IID on DAD
|
|
* failures for now.
|
|
* @param in6 Pointer to the address containing
|
|
* the prefix.
|
|
*
|
|
* @return void
|
|
*/
|
|
static void
|
|
in6_cga_generate_iid(const struct in6_cga_prepare *prepare,
|
|
const struct iovec *pubkey, u_int8_t collisions,
|
|
struct in6_addr *in6, struct ifnet *ifp)
|
|
{
|
|
SHA1_CTX ctx;
|
|
u_int8_t sha1[SHA1_RESULTLEN];
|
|
|
|
VERIFY(prepare != NULL);
|
|
VERIFY(prepare->cga_security_level < 8);
|
|
VERIFY(pubkey != NULL && pubkey->iov_base != NULL);
|
|
VERIFY(in6 != NULL);
|
|
|
|
SHA1Init(&ctx);
|
|
SHA1Update(&ctx, &prepare->cga_modifier.octets, 16);
|
|
SHA1Update(&ctx, in6->s6_addr, 8);
|
|
SHA1Update(&ctx, &collisions, 1);
|
|
SHA1Update(&ctx, pubkey->iov_base, pubkey->iov_len);
|
|
if (ifp->network_id_len) {
|
|
SHA1Update(&ctx, &ifp->network_id, ifp->network_id_len);
|
|
}
|
|
/* FUTURE: extension fields */
|
|
SHA1Final(sha1, &ctx);
|
|
|
|
in6->s6_addr8[8] =
|
|
(u_int8_t)((prepare->cga_security_level << 5) | (sha1[0] & 0x1c));
|
|
in6->s6_addr8[9] = sha1[1];
|
|
in6->s6_addr8[10] = sha1[2];
|
|
in6->s6_addr8[11] = sha1[3];
|
|
in6->s6_addr8[12] = sha1[4];
|
|
in6->s6_addr8[13] = sha1[5];
|
|
in6->s6_addr8[14] = sha1[6];
|
|
in6->s6_addr8[15] = sha1[7];
|
|
}
|
|
|
|
void
|
|
in6_cga_init(void)
|
|
{
|
|
lck_mtx_init(&in6_cga.cga_mutex, &ifa_mtx_grp, &ifa_mtx_attr);
|
|
in6_cga.cga_initialized = TRUE;
|
|
}
|
|
|
|
void
|
|
in6_cga_node_lock(void)
|
|
{
|
|
VERIFY(in6_cga.cga_initialized);
|
|
lck_mtx_lock(&in6_cga.cga_mutex);
|
|
}
|
|
|
|
void
|
|
in6_cga_node_unlock(void)
|
|
{
|
|
VERIFY(in6_cga.cga_initialized);
|
|
lck_mtx_unlock(&in6_cga.cga_mutex);
|
|
}
|
|
|
|
void
|
|
in6_cga_query(struct in6_cga_nodecfg *cfg)
|
|
{
|
|
VERIFY(cfg != NULL);
|
|
in6_cga_node_lock_assert(LCK_MTX_ASSERT_OWNED);
|
|
|
|
cfg->cga_pubkey = in6_cga.cga_pubkey;
|
|
cfg->cga_prepare = in6_cga.cga_prepare;
|
|
}
|
|
|
|
int
|
|
in6_cga_start(const struct in6_cga_nodecfg *cfg)
|
|
{
|
|
struct iovec privkey, pubkey;
|
|
const struct in6_cga_prepare *prepare;
|
|
caddr_t pubkeycopy, privkeycopy;
|
|
|
|
VERIFY(cfg != NULL);
|
|
in6_cga_node_lock_assert(LCK_MTX_ASSERT_OWNED);
|
|
|
|
privkey = cfg->cga_privkey;
|
|
if (privkey.iov_base == NULL || privkey.iov_len == 0 ||
|
|
privkey.iov_len >= IN6_CGA_KEY_MAXSIZE) {
|
|
return EINVAL;
|
|
}
|
|
pubkey = cfg->cga_pubkey;
|
|
if (pubkey.iov_base == NULL || pubkey.iov_len == 0 ||
|
|
pubkey.iov_len >= IN6_CGA_KEY_MAXSIZE) {
|
|
return EINVAL;
|
|
}
|
|
prepare = &cfg->cga_prepare;
|
|
|
|
if (!in6_cga_is_prepare_valid(prepare, &pubkey)) {
|
|
return EINVAL;
|
|
}
|
|
|
|
in6_cga.cga_prepare = *prepare;
|
|
|
|
privkeycopy = (caddr_t)kalloc_data(privkey.iov_len, Z_WAITOK);
|
|
if (privkeycopy == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
pubkeycopy = (caddr_t)kalloc_data(pubkey.iov_len, Z_WAITOK);
|
|
if (pubkeycopy == NULL) {
|
|
if (privkeycopy != NULL) {
|
|
kfree_data(privkeycopy, privkey.iov_len);
|
|
}
|
|
return ENOMEM;
|
|
}
|
|
|
|
bcopy(privkey.iov_base, privkeycopy, privkey.iov_len);
|
|
privkey.iov_base = privkeycopy;
|
|
if (in6_cga.cga_privkey.iov_base != NULL) {
|
|
kfree_data(in6_cga.cga_privkey.iov_base, in6_cga.cga_privkey.iov_len);
|
|
}
|
|
in6_cga.cga_privkey = privkey;
|
|
|
|
bcopy(pubkey.iov_base, pubkeycopy, pubkey.iov_len);
|
|
pubkey.iov_base = pubkeycopy;
|
|
if (in6_cga.cga_pubkey.iov_base != NULL) {
|
|
kfree_data(in6_cga.cga_pubkey.iov_base, in6_cga.cga_pubkey.iov_len);
|
|
}
|
|
in6_cga.cga_pubkey = pubkey;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
in6_cga_stop(void)
|
|
{
|
|
in6_cga_node_lock_assert(LCK_MTX_ASSERT_OWNED);
|
|
|
|
if (in6_cga.cga_privkey.iov_base != NULL) {
|
|
kfree_data(in6_cga.cga_privkey.iov_base, in6_cga.cga_privkey.iov_len);
|
|
in6_cga.cga_privkey.iov_base = NULL;
|
|
in6_cga.cga_privkey.iov_len = 0;
|
|
}
|
|
|
|
if (in6_cga.cga_pubkey.iov_base != NULL) {
|
|
kfree_data(in6_cga.cga_pubkey.iov_base, in6_cga.cga_pubkey.iov_len);
|
|
in6_cga.cga_pubkey.iov_base = NULL;
|
|
in6_cga.cga_pubkey.iov_len = 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
ssize_t
|
|
in6_cga_parameters_prepare(void *output, size_t max,
|
|
const struct in6_addr *prefix, u_int8_t collisions,
|
|
const struct in6_cga_modifier *modifier)
|
|
{
|
|
caddr_t cursor;
|
|
|
|
in6_cga_node_lock_assert(LCK_MTX_ASSERT_OWNED);
|
|
|
|
if (in6_cga.cga_pubkey.iov_len == 0) {
|
|
/* No public key */
|
|
return EINVAL;
|
|
}
|
|
|
|
if (output == NULL ||
|
|
max < in6_cga.cga_pubkey.iov_len + sizeof(modifier->octets) + 9) {
|
|
/* Output buffer error */
|
|
return EINVAL;
|
|
}
|
|
|
|
cursor = output;
|
|
if (modifier == NULL) {
|
|
modifier = &in6_cga.cga_prepare.cga_modifier;
|
|
}
|
|
if (prefix == NULL) {
|
|
static const struct in6_addr llprefix = {{{ 0xfe, 0x80 }}};
|
|
prefix = &llprefix;
|
|
}
|
|
|
|
bcopy(&modifier->octets, cursor, sizeof(modifier->octets));
|
|
cursor += sizeof(modifier->octets);
|
|
|
|
*cursor++ = (char) collisions;
|
|
|
|
bcopy(&prefix->s6_addr[0], cursor, 8);
|
|
cursor += 8;
|
|
|
|
bcopy(in6_cga.cga_pubkey.iov_base, cursor, in6_cga.cga_pubkey.iov_len);
|
|
cursor += in6_cga.cga_pubkey.iov_len;
|
|
|
|
/* FUTURE: Extension fields */
|
|
|
|
return (ssize_t)(cursor - (caddr_t)output);
|
|
}
|
|
|
|
int
|
|
in6_cga_generate(struct in6_cga_prepare *prepare, u_int8_t collisions,
|
|
struct in6_addr *in6, struct ifnet *ifp)
|
|
{
|
|
int error;
|
|
const struct iovec *pubkey;
|
|
|
|
in6_cga_node_lock_assert(LCK_MTX_ASSERT_OWNED);
|
|
VERIFY(in6 != NULL);
|
|
|
|
if (prepare == NULL) {
|
|
prepare = &in6_cga.cga_prepare;
|
|
} else {
|
|
prepare->cga_security_level =
|
|
in6_cga.cga_prepare.cga_security_level;
|
|
}
|
|
|
|
pubkey = &in6_cga.cga_pubkey;
|
|
|
|
if (pubkey->iov_base != NULL) {
|
|
in6_cga_generate_iid(prepare, pubkey, collisions, in6, ifp);
|
|
error = 0;
|
|
} else {
|
|
error = EADDRNOTAVAIL;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
/* End of file */
|