From: Martin Willi Date: Wed, 28 Jan 2015 12:35:28 +0000 (+0100) Subject: cga: Add a cga plugin providing RFC 3972 CGA generation and parsing X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=13cdfdc459e3738b4f719860f80d997726ecadd5;p=thirdparty%2Fstrongswan.git cga: Add a cga plugin providing RFC 3972 CGA generation and parsing --- diff --git a/configure.ac b/configure.ac index f1e5046510..2d77652d11 100644 --- a/configure.ac +++ b/configure.ac @@ -147,6 +147,7 @@ ARG_DISBL_SET([sha1], [disable SHA1 software implementation plugin.]) ARG_DISBL_SET([sha2], [disable SHA256/SHA384/SHA512 software implementation plugin.]) ARG_DISBL_SET([xcbc], [disable xcbc crypto implementation plugin.]) # encoding/decoding plugins +ARG_ENABL_SET([cga], [enable IPv6 Cryptographically Generated Address plugin.]) ARG_DISBL_SET([dnskey], [disable DNS RR key decoding plugin.]) ARG_DISBL_SET([pem], [disable PEM decoding plugin.]) ARG_DISBL_SET([pgp], [disable PGP key decoding plugin.]) @@ -1237,6 +1238,7 @@ ADD_PLUGIN([pkcs7], [s charon scepclient pki scripts nm cmd]) ADD_PLUGIN([pkcs8], [s charon scepclient pki scripts manager medsrv attest nm cmd]) ADD_PLUGIN([pkcs12], [s charon scepclient pki scripts cmd]) ADD_PLUGIN([pgp], [s charon]) +ADD_PLUGIN([cga], [s charon pki]) ADD_PLUGIN([dnskey], [s charon pki]) ADD_PLUGIN([sshkey], [s charon pki nm cmd]) ADD_PLUGIN([dnscert], [c charon]) @@ -1389,6 +1391,7 @@ AM_CONDITIONAL(USE_PKCS7, test x$pkcs7 = xtrue) AM_CONDITIONAL(USE_PKCS8, test x$pkcs8 = xtrue) AM_CONDITIONAL(USE_PKCS12, test x$pkcs12 = xtrue) AM_CONDITIONAL(USE_PGP, test x$pgp = xtrue) +AM_CONDITIONAL(USE_CGA, test x$cga = xtrue) AM_CONDITIONAL(USE_DNSKEY, test x$dnskey = xtrue) AM_CONDITIONAL(USE_SSHKEY, test x$sshkey = xtrue) AM_CONDITIONAL(USE_PEM, test x$pem = xtrue) @@ -1632,6 +1635,7 @@ AC_CONFIG_FILES([ src/libstrongswan/plugins/pkcs8/Makefile src/libstrongswan/plugins/pkcs12/Makefile src/libstrongswan/plugins/pgp/Makefile + src/libstrongswan/plugins/cga/Makefile src/libstrongswan/plugins/dnskey/Makefile src/libstrongswan/plugins/sshkey/Makefile src/libstrongswan/plugins/pem/Makefile diff --git a/src/libstrongswan/Makefile.am b/src/libstrongswan/Makefile.am index 91d92c3f3e..876d6a8dbd 100644 --- a/src/libstrongswan/Makefile.am +++ b/src/libstrongswan/Makefile.am @@ -399,6 +399,13 @@ if MONOLITHIC endif endif +if USE_CGA + SUBDIRS += plugins/cga +if MONOLITHIC + libstrongswan_la_LIBADD += plugins/cga/libstrongswan-cga.la +endif +endif + if USE_DNSKEY SUBDIRS += plugins/dnskey if MONOLITHIC diff --git a/src/libstrongswan/plugins/cga/Makefile.am b/src/libstrongswan/plugins/cga/Makefile.am new file mode 100644 index 0000000000..25f0e1b82d --- /dev/null +++ b/src/libstrongswan/plugins/cga/Makefile.am @@ -0,0 +1,17 @@ +AM_CPPFLAGS = \ + -I$(top_srcdir)/src/libstrongswan + +AM_CFLAGS = \ + $(PLUGIN_CFLAGS) + +if MONOLITHIC +noinst_LTLIBRARIES = libstrongswan-cga.la +else +plugin_LTLIBRARIES = libstrongswan-cga.la +endif + +libstrongswan_cga_la_SOURCES = \ + cga_cert.h cga_cert.c \ + cga_plugin.h cga_plugin.c + +libstrongswan_cga_la_LDFLAGS = -module -avoid-version diff --git a/src/libstrongswan/plugins/cga/cga_cert.c b/src/libstrongswan/plugins/cga/cga_cert.c new file mode 100644 index 0000000000..3c8ca327ee --- /dev/null +++ b/src/libstrongswan/plugins/cga/cga_cert.c @@ -0,0 +1,519 @@ +/* + * Copyright (C) 2015 Martin Willi + * Copyright (C) 2015 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "cga_cert.h" + +#include + +#include +#include +#include +#include +#include +#include + + +typedef struct private_cga_cert_t private_cga_cert_t; + +/** + * Private data of a cga_cert_t object. + */ +struct private_cga_cert_t { + + /** + * Public interface for this certificate. + */ + cga_cert_t public; + + /** + * CGA parameters encoding + */ + chunk_t encoding; + + /** + * Wrapped public key + */ + public_key_t *public_key; + + /** + * CGA as ID_IPV6_ADDR identity, the certificate subject + */ + identification_t *cga; + + /** + * Certificate issuer, which is "CGA trust anchor" + */ + identification_t *anchor; + + /** + * Reference count + */ + refcount_t ref; +}; + +METHOD(certificate_t, get_type, certificate_type_t, + private_cga_cert_t *this) +{ + return CERT_CGA_PARAMS; +} + +METHOD(certificate_t, get_subject, identification_t*, + private_cga_cert_t *this) +{ + return this->cga; +} + +METHOD(certificate_t, get_issuer, identification_t*, + private_cga_cert_t *this) +{ + return this->anchor; +} + +METHOD(certificate_t, has_subject, id_match_t, + private_cga_cert_t *this, identification_t *subject) +{ + return this->cga->matches(this->cga, subject); +} + +METHOD(certificate_t, has_issuer, id_match_t, + private_cga_cert_t *this, identification_t *issuer) +{ + return this->anchor->matches(this->anchor, issuer); +} + +METHOD(certificate_t, issued_by, bool, + private_cga_cert_t *this, certificate_t *issuer, + signature_scheme_t *schemep) +{ + if (issuer->get_type(issuer) != CERT_CGA_PARAMS) + { + return FALSE; + } + if (!this->anchor->equals(this->anchor, issuer->get_subject(issuer))) + { + return FALSE; + } + /* any parsed CGA is valid */ + if (schemep) + { + *schemep = SIGN_CGA_SHA1; + } + return TRUE; +} + +METHOD(certificate_t, get_public_key, public_key_t*, + private_cga_cert_t *this) +{ + return this->public_key->get_ref(this->public_key); +} + +METHOD(certificate_t, get_ref, certificate_t*, + private_cga_cert_t *this) +{ + ref_get(&this->ref); + return &this->public.interface; +} + +METHOD(certificate_t, get_validity, bool, + private_cga_cert_t *this, time_t *when, time_t *not_before, + time_t *not_after) +{ + if (not_before) + { + *not_before = UNDEFINED_TIME; + } + if (not_after) + { + *not_after = UNDEFINED_TIME; + } + return TRUE; +} + +METHOD(certificate_t, get_encoding, bool, + private_cga_cert_t *this, cred_encoding_type_t type, chunk_t *encoding) +{ + if (type == CERT_CGA_ENCODING) + { + *encoding = chunk_clone(this->encoding); + return TRUE; + } + return FALSE; +} + +METHOD(certificate_t, equals, bool, + private_cga_cert_t *this, certificate_t *other) +{ + chunk_t encoding; + bool equal; + + if (this == (private_cga_cert_t*)other) + { + return TRUE; + } + if (other->get_type(other) != CERT_CGA_PARAMS) + { + return FALSE; + } + if (other->equals == (void*)equals) + { /* same implementation */ + return chunk_equals(this->encoding, + ((private_cga_cert_t*)other)->encoding); + } + if (!other->get_encoding(other, CERT_CGA_ENCODING, &encoding)) + { + return FALSE; + } + equal = chunk_equals(this->encoding, encoding); + free(encoding.ptr); + return equal; +} + +METHOD(certificate_t, destroy, void, + private_cga_cert_t *this) +{ + if (ref_put(&this->ref)) + { + free(this->encoding.ptr); + DESTROY_IF(this->public_key); + DESTROY_IF(this->cga); + this->anchor->destroy(this->anchor); + free(this); + } +} + +/** + * Generic constructor + */ +static private_cga_cert_t* create() +{ + private_cga_cert_t *this; + + INIT(this, + .public = { + .interface = { + .get_type = _get_type, + .get_subject = _get_subject, + .get_issuer = _get_issuer, + .has_subject = _has_subject, + .has_issuer = _has_issuer, + .issued_by = _issued_by, + .get_public_key = _get_public_key, + .get_validity = _get_validity, + .get_encoding = _get_encoding, + .equals = _equals, + .get_ref = _get_ref, + .destroy = _destroy, + }, + }, + .anchor = identification_create_from_string("CGA Trust Anchor"), + .ref = 1, + ); + return this; +} + +/** + * CGA parameter encoding: + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * + + + * | | + * + Modifier (16 octets) + + * | | + * + + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * + Subnet Prefix (8 octets) + + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * |Collision Count| | + * +-+-+-+-+-+-+-+-+ | + * | | + * ~ Public Key (variable length) ~ + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + * | | + * ~ Extension Fields (optional, variable length) ~ + * | | + * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +*/ +typedef struct __attribute__((packed)) { + char modifier[16]; + char prefix[8]; + u_int8_t collision; + char public_key[]; +} cga_t; + +/** + * Parse CGA parameters and guess the CGA address + */ +static bool parse(private_cga_cert_t *this) +{ + char hash1[HASH_SIZE_SHA1], hash2[HASH_SIZE_SHA1], cga[16], zero[14] = {}; + hasher_t *hasher; + chunk_t pubkey, modifier; + size_t len; + u_int sec; + + if (this->encoding.len <= offsetof(cga_t, public_key)) + { + return FALSE; + } + if (this->encoding.ptr[offsetof(cga_t, collision)] > 2) + { + return FALSE; + } + pubkey = chunk_skip(this->encoding, offsetof(cga_t, public_key)); + len = asn1_length(&pubkey); + if (len == ASN1_INVALID_LENGTH) + { + return FALSE; + } + /* re-add the tag length removed by asn1_length() */ + len += pubkey.ptr - (this->encoding.ptr + offsetof(cga_t, public_key)); + pubkey = chunk_create(this->encoding.ptr + offsetof(cga_t, public_key), len); + this->public_key = lib->creds->create(lib->creds, CRED_PUBLIC_KEY, + KEY_ANY, BUILD_BLOB, + pubkey, BUILD_END); + if (!this->public_key) + { + return FALSE; + } + modifier = chunk_create(this->encoding.ptr, 16); + + hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); + if (!hasher) + { + return FALSE; + } + if (!hasher->get_hash(hasher, this->encoding, hash1)) + { + hasher->destroy(hasher); + return FALSE; + } + + /* set u/g bits to zero */ + hash1[0] &= ~0x03; + /* Reconstruct a CGA from the parameters for the highest matching Sec + * parameter. We generate CGA parameters that have a unique CGA when + * reconstructed this way, but a ~1:2^16 probability exists that we pick a + * CGA with a higher Sec level for externally generated parameters. */ + for (sec = 7; sec <= 7; sec--) + { + hash1[0] &= ~(0xE0); + hash1[0] |= sec << 5; + + if (!hasher->get_hash(hasher, modifier, NULL) || + !hasher->get_hash(hasher, chunk_create(zero, 9), NULL) || + !hasher->get_hash(hasher, pubkey, hash2)) + { + hasher->destroy(hasher); + return FALSE; + } + if (memeq(zero, hash2, sec * 2)) + { + memcpy(cga, &this->encoding.ptr[offsetof(cga_t, prefix)], 8); + memcpy(cga + 8, hash1, 8); + this->cga = identification_create_from_encoding(ID_IPV6_ADDR, + chunk_from_thing(cga)); + hasher->destroy(hasher); + return TRUE; + } + } + hasher->destroy(hasher); + return FALSE; +} + +/** + * See header. + */ +cga_cert_t *cga_cert_load(certificate_type_t type, va_list args) +{ + chunk_t blob = chunk_empty, *map = NULL; + private_cga_cert_t *cert; + char *file = NULL; + + while (TRUE) + { + switch (va_arg(args, builder_part_t)) + { + case BUILD_BLOB: + blob = va_arg(args, chunk_t); + continue; + case BUILD_FROM_FILE: + file = va_arg(args, char*); + continue; + case BUILD_END: + break; + default: + return NULL; + } + break; + } + + if (file) + { + map = chunk_map(file, FALSE); + if (!map) + { + DBG1(DBG_LIB, "reading CGA file '%s' failed: %s", + file, strerror(errno)); + return NULL; + } + } + cert = create(); + if (map) + { + cert->encoding = chunk_clone(*map); + chunk_unmap(map); + } + else + { + cert->encoding = chunk_clone(blob); + } + if (!parse(cert)) + { + destroy(cert); + return NULL; + } + return &cert->public; +} + +/** + * Generate a a new CGA for the supplied parameters + */ +static bool generate(private_cga_cert_t *this, char *prefix, u_int sec) +{ + char modifier[16], zero[16] = {}, hash[HASH_SIZE_SHA1], cga[16]; + u_int8_t collision = 0; + chunk_t pubkey; + hasher_t *hasher; + rng_t *rng; + + rng = lib->crypto->create_rng(lib->crypto, RNG_WEAK); + if (!rng) + { + return FALSE; + } + if (!rng->get_bytes(rng, sizeof(modifier), modifier)) + { + rng->destroy(rng); + return FALSE; + } + rng->destroy(rng); + if (!this->public_key->get_encoding(this->public_key, + PUBKEY_SPKI_ASN1_DER, &pubkey)) + { + return FALSE; + } + + hasher = lib->crypto->create_hasher(lib->crypto, HASH_SHA1); + if (!hasher) + { + free(pubkey.ptr); + return FALSE; + } + do + { + chunk_increment(chunk_from_thing(modifier)); + if (!hasher->get_hash(hasher, chunk_from_thing(modifier), NULL) || + !hasher->get_hash(hasher, chunk_create(zero, 9), NULL) || + !hasher->get_hash(hasher, pubkey, hash)) + { + hasher->destroy(hasher); + free(pubkey.ptr); + return FALSE; + } + } + /* brute force until sec words are zero. We skip hashes that would comply + * to a higher Sec level: This makes CGAs unique when re-constructed from + * the CGA parameters if the highest matching Sec value is used during + * reconstruction. */ + while (!memeq(zero, hash, sec * 2) || memeq(zero, hash, (sec + 1) * 2)); + + this->encoding = chunk_cat("cccm", + chunk_from_thing(modifier), chunk_create(prefix, 8), + chunk_from_thing(collision), pubkey); + + if (!hasher->get_hash(hasher, this->encoding, hash)) + { + hasher->destroy(hasher); + return FALSE; + } + hasher->destroy(hasher); + + /* write Sec parameter */ + hash[0] &= ~0xE0; + hash[0] |= sec << 5; + /* set u/g bits to zero */ + hash[0] &= ~0x03; + + memcpy(cga, prefix, 8); + memcpy(cga + 8, hash, 8); + + this->cga = identification_create_from_encoding(ID_IPV6_ADDR, + chunk_from_thing(cga)); + + return TRUE; +} + +/** + * See header. + */ +cga_cert_t *cga_cert_gen(certificate_type_t type, va_list args) +{ + private_cga_cert_t *cert; + public_key_t *public_key = NULL; + chunk_t prefix = chunk_empty; + int sec = 0; + + while (TRUE) + { + switch (va_arg(args, builder_part_t)) + { + case BUILD_PUBLIC_KEY: + public_key = va_arg(args, public_key_t*); + continue; + case BUILD_CGA_PREFIX: + prefix = va_arg(args, chunk_t); + continue; + case BUILD_CGA_SEC: + sec = va_arg(args, int); + continue; + case BUILD_END: + break; + default: + return NULL; + } + break; + } + + if (sec < 0 || sec > 7 || prefix.len != 8 || !public_key) + { + DBG1(DBG_LIB, "invalid CGA parameters"); + return NULL; + } + cert = create(); + cert->public_key = public_key->get_ref(public_key); + if (generate(cert, prefix.ptr, sec)) + { + return &cert->public; + } + destroy(cert); + return NULL; +} diff --git a/src/libstrongswan/plugins/cga/cga_cert.h b/src/libstrongswan/plugins/cga/cga_cert.h new file mode 100644 index 0000000000..ae374b4029 --- /dev/null +++ b/src/libstrongswan/plugins/cga/cga_cert.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2015 Martin Willi + * Copyright (C) 2015 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup cga_cert cga_cert + * @{ @ingroup cga_p + */ + +#ifndef CGA_CERT_H_ +#define CGA_CERT_H_ + +typedef struct cga_cert_t cga_cert_t; + +#include +#include + +/** + * IPv6 CGA parameters implemented as certificate_t + */ +struct cga_cert_t { + + /** + * Implements the certificate_t interface + */ + certificate_t interface; +}; + +/** + * Load IPv6 CGA parameters as a certificate. + * + * This function takes a BUILD_BLOB builder part. + * + * @param type certificate type, CERT_CGA_PARAMS only + * @param args builder_part_t argument list + * @return CGA parameters as certificate, NULL on failure + */ +cga_cert_t *cga_cert_load(certificate_type_t type, va_list args); + +/** + * Generate new IPv6 CGA parameters from a public key. + * + * This function takes a BUILD_PUBLIC_KEY with the public key, a + * BUILD_CGA_PREFIX defining the subnet prefix, and optionally a BUILD_CGA_SEC, + * the security parameter Sec. + * + * @param type certificate type, CERT_CGA_PARAMS only + * @param args builder_part_t argument list + * @return CGA parameters as certificate, NULL on failure + */ +cga_cert_t *cga_cert_gen(certificate_type_t type, va_list args); + +#endif /** CGA_CERT_H_ @}*/ diff --git a/src/libstrongswan/plugins/cga/cga_plugin.c b/src/libstrongswan/plugins/cga/cga_plugin.c new file mode 100644 index 0000000000..598d87ec1d --- /dev/null +++ b/src/libstrongswan/plugins/cga/cga_plugin.c @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015 Martin Willi + * Copyright (C) 2015 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +#include "cga_plugin.h" +#include "cga_cert.h" + +#include + +typedef struct private_cga_plugin_t private_cga_plugin_t; + +/** + * Private data of cga_plugin_t + */ +struct private_cga_plugin_t { + + /** + * Public functions + */ + cga_plugin_t public; +}; + +METHOD(plugin_t, get_name, char*, + private_cga_plugin_t *this) +{ + return "cga"; +} + +METHOD(plugin_t, get_features, int, + private_cga_plugin_t *this, plugin_feature_t *features[]) +{ + static plugin_feature_t f[] = { + PLUGIN_REGISTER(CERT_ENCODE, cga_cert_gen, FALSE), + PLUGIN_PROVIDE(CERT_ENCODE, CERT_CGA_PARAMS), + PLUGIN_DEPENDS(HASHER, HASH_SHA1), + PLUGIN_DEPENDS(RNG, RNG_WEAK), + PLUGIN_REGISTER(CERT_DECODE, cga_cert_load, TRUE), + PLUGIN_PROVIDE(CERT_DECODE, CERT_CGA_PARAMS), + PLUGIN_DEPENDS(HASHER, HASH_SHA1), + PLUGIN_DEPENDS(PUBKEY, KEY_ANY), + }; + *features = f; + return countof(f); +} + +METHOD(plugin_t, destroy, void, + private_cga_plugin_t *this) +{ + free(this); +} + +/* + * see header file + */ +plugin_t *cga_plugin_create() +{ + private_cga_plugin_t *this; + + INIT(this, + .public = { + .plugin = { + .get_name = _get_name, + .get_features = _get_features, + .destroy = _destroy, + }, + }, + ); + + return &this->public.plugin; +} diff --git a/src/libstrongswan/plugins/cga/cga_plugin.h b/src/libstrongswan/plugins/cga/cga_plugin.h new file mode 100644 index 0000000000..0ea4a02619 --- /dev/null +++ b/src/libstrongswan/plugins/cga/cga_plugin.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2015 Martin Willi + * Copyright (C) 2015 revosec AG + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. See . + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + */ + +/** + * @defgroup cga_p cga + * @ingroup plugins + * + * @defgroup cga_plugin cga_plugin + * @{ @ingroup cga_p + */ + +#ifndef CGA_PLUGIN_H_ +#define CGA_PLUGIN_H_ + +#include + +typedef struct cga_plugin_t cga_plugin_t; + +/** + * Plugin implementing IPv6 Cryptographically Generated Address support + */ +struct cga_plugin_t { + + /** + * Implements plugin interface + */ + plugin_t plugin; +}; + +#endif /** CGA_PLUGIN_H_ @}*/