From 353c7b57c8e09fc0e8d08c4f4ff53557b1758c4a Mon Sep 17 00:00:00 2001 From: Andreas Steffen Date: Mon, 29 May 2006 07:06:02 +0000 Subject: [PATCH] reworked parsing and matching of subjectAltNames --- src/libstrongswan/crypto/x509.c | 343 +++++++++++++++++------ src/libstrongswan/crypto/x509.h | 9 + src/libstrongswan/utils/identification.c | 299 ++++++-------------- src/libstrongswan/utils/identification.h | 30 +- 4 files changed, 355 insertions(+), 326 deletions(-) diff --git a/src/libstrongswan/crypto/x509.c b/src/libstrongswan/crypto/x509.c index 9123b6368..3e0f039e1 100755 --- a/src/libstrongswan/crypto/x509.c +++ b/src/libstrongswan/crypto/x509.c @@ -27,6 +27,8 @@ #include "x509.h" +#include +#include #include #include #include @@ -34,24 +36,31 @@ #include #include -#define BUF_LEN 512 #define BITS_PER_BYTE 8 #define RSA_MIN_OCTETS (1024 / BITS_PER_BYTE) #define RSA_MIN_OCTETS_UGH "RSA modulus too small for security: less than 1024 bits" #define RSA_MAX_OCTETS (8192 / BITS_PER_BYTE) #define RSA_MAX_OCTETS_UGH "RSA modulus too large: more than 8192 bits" +#define CERT_WARNING_INTERVAL 30 /* days */ + logger_t *logger; -typedef struct generalName_t generalName_t; - /** - * A generalName, chainable in a list + * Different kinds of generalNames */ -struct generalName_t { - generalName_t *next; - generalNames_t kind; - chunk_t name; +typedef enum generalNames_t generalNames_t; + +enum generalNames_t { + GN_OTHER_NAME = 0, + GN_RFC822_NAME = 1, + GN_DNS_NAME = 2, + GN_X400_ADDRESS = 3, + GN_DIRECTORY_NAME = 4, + GN_EDI_PARTY_NAME = 5, + GN_URI = 6, + GN_IP_ADDRESS = 7, + GN_REGISTERED_ID = 8, }; typedef struct private_x509_t private_x509_t; @@ -110,11 +119,6 @@ struct private_x509_t { */ linked_list_t *subjectAltNames; - /** - * List of identification_t's representing issuerAltNames - */ - linked_list_t *issuerAltNames; - /** * List of identification_t's representing crlDistributionPoints */ @@ -153,6 +157,50 @@ struct private_x509_t { chunk_t signature; }; +/** + * ASN.1 definition of generalName + */ +static const asn1Object_t generalNameObjects[] = { + { 0, "otherName", ASN1_CONTEXT_C_0, ASN1_OPT|ASN1_BODY }, /* 0 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 1 */ + { 0, "rfc822Name", ASN1_CONTEXT_S_1, ASN1_OPT|ASN1_BODY }, /* 2 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 3 */ + { 0, "dnsName", ASN1_CONTEXT_S_2, ASN1_OPT|ASN1_BODY }, /* 4 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 5 */ + { 0, "x400Address", ASN1_CONTEXT_S_3, ASN1_OPT|ASN1_BODY }, /* 6 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 7 */ + { 0, "directoryName", ASN1_CONTEXT_C_4, ASN1_OPT|ASN1_BODY }, /* 8 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 9 */ + { 0, "ediPartyName", ASN1_CONTEXT_C_5, ASN1_OPT|ASN1_BODY }, /* 10 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 11 */ + { 0, "URI", ASN1_CONTEXT_S_6, ASN1_OPT|ASN1_BODY }, /* 12 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 13 */ + { 0, "ipAddress", ASN1_CONTEXT_S_7, ASN1_OPT|ASN1_BODY }, /* 14 */ + { 0, "end choice", ASN1_EOC, ASN1_END }, /* 15 */ + { 0, "registeredID", ASN1_CONTEXT_S_8, ASN1_OPT|ASN1_BODY }, /* 16 */ + { 0, "end choice", ASN1_EOC, ASN1_END } /* 17 */ +}; +#define GN_OBJ_OTHER_NAME 0 +#define GN_OBJ_RFC822_NAME 2 +#define GN_OBJ_DNS_NAME 4 +#define GN_OBJ_X400_ADDRESS 6 +#define GN_OBJ_DIRECTORY_NAME 8 +#define GN_OBJ_EDI_PARTY_NAME 10 +#define GN_OBJ_URI 12 +#define GN_OBJ_IP_ADDRESS 14 +#define GN_OBJ_REGISTERED_ID 16 +#define GN_OBJ_ROOF 18 + +/** + * ASN.1 definition of otherName + */ +static const asn1Object_t otherNameObjects[] = { + {0, "type-id", ASN1_OID, ASN1_BODY }, /* 0 */ + {0, "value", ASN1_CONTEXT_C_0, ASN1_BODY } /* 1 */ +}; +#define ON_OBJ_ID_TYPE 0 +#define ON_OBJ_VALUE 1 +#define ON_OBJ_ROOF 2 /** * ASN.1 definition of a basicConstraints extension */ @@ -330,56 +378,6 @@ static bool equals(private_x509_t *this, private_x509_t *other) return chunk_equals(this->signature, other->signature); } -/** - * encode a linked list of subjectAltNames - */ -chunk_t build_subjectAltNames(generalName_t *subjectAltNames) -{ - u_char *pos; - chunk_t names; - size_t len = 0; - generalName_t *gn = subjectAltNames; - - /* compute the total size of the ASN.1 attributes object */ - while (gn != NULL) - { - len += gn->name.len; - gn = gn->next; - } - - pos = build_asn1_object(&names, ASN1_SEQUENCE, len); - - gn = subjectAltNames; - while (gn != NULL) - { - memcpy(pos, gn->name.ptr, gn->name.len); - pos += gn->name.len; - gn = gn->next; - } - - return asn1_wrap(ASN1_SEQUENCE, "cm", - ASN1_subjectAltName_oid, - asn1_wrap(ASN1_OCTET_STRING, "m", names) - ); -} - -/** - * free the dynamic memory used to store generalNames - */ -void free_generalNames(generalName_t* gn, bool free_name) -{ - while (gn != NULL) - { - generalName_t *gn_top = gn; - if (free_name) - { - free(gn->name.ptr); - } - gn = gn->next; - free(gn_top); - } -} - /** * extracts the basicConstraints extension */ @@ -402,13 +400,113 @@ static bool parse_basicConstraints(chunk_t blob, int level0) if (objectID == BASIC_CONSTRAINTS_CA) { isCA = object.len && *object.ptr; - logger->log(logger, RAW|LEVEL1, " %s", isCA ? "TRUE" : "FALSE"); + logger->log(logger, CONTROL|LEVEL1, " %s", isCA ? "TRUE" : "FALSE"); } objectID++; } return isCA; } +/* + * extracts an otherName + */ +static bool +parse_otherName(chunk_t blob, int level0) +{ + asn1_ctx_t ctx; + chunk_t object; + int objectID = 0; + u_int level; + int oid = OID_UNKNOWN; + + asn1_init(&ctx, blob, level0, FALSE); + + while (objectID < ON_OBJ_ROOF) + { + if (!extract_object(otherNameObjects, &objectID, &object, &level, &ctx)) + return FALSE; + + switch (objectID) + { + case ON_OBJ_ID_TYPE: + oid = known_oid(object); + break; + case ON_OBJ_VALUE: + if (oid == OID_XMPP_ADDR) + { + if (!parse_asn1_simple_object(&object, ASN1_UTF8STRING, level + 1, "xmppAddr")) + return FALSE; + } + break; + default: + break; + } + objectID++; + } + return TRUE; +} + +/* + * extracts a generalName + */ +static identification_t *parse_generalName(chunk_t blob, int level0) +{ + u_char buf[BUF_LEN]; + asn1_ctx_t ctx; + chunk_t object; + int objectID = 0; + u_int level; + + asn1_init(&ctx, blob, level0, FALSE); + + while (objectID < GN_OBJ_ROOF) + { + id_type_t id_type = ID_ANY; + + if (!extract_object(generalNameObjects, &objectID, &object, &level, &ctx)) + return NULL; + + switch (objectID) + { + case GN_OBJ_RFC822_NAME: + id_type = ID_RFC822_ADDR; + break; + case GN_OBJ_DNS_NAME: + id_type = ID_FQDN; + break; + case GN_OBJ_URI: + id_type = ID_DER_ASN1_GN_URI; + break; + case GN_OBJ_DIRECTORY_NAME: + id_type = ID_DER_ASN1_DN; + break; + case GN_OBJ_IP_ADDRESS: + id_type = ID_IPV4_ADDR; + break; + case GN_OBJ_OTHER_NAME: + if (!parse_otherName(object, level + 1)) + return NULL; + break; + case GN_OBJ_X400_ADDRESS: + case GN_OBJ_EDI_PARTY_NAME: + case GN_OBJ_REGISTERED_ID: + break; + default: + break; + } + + if (id_type != ID_ANY) + { + identification_t *gn = identification_create_from_encoding(id_type, object); + logger->log(logger, CONTROL|LEVEL1, " '%s'", gn->get_string(gn)); + return gn; + } + objectID++; + } + return NULL; +} + + /** * extracts one or several GNs and puts them into a chained list */ @@ -428,7 +526,10 @@ static void parse_generalNames(chunk_t blob, int level0, bool implicit, linked_l if (objectID == GENERAL_NAMES_GN) { - list->insert_last(list, identification_create_from_encoding(ID_DER_ASN1_GN, object)); + identification_t *gn = parse_generalName(object, level+1); + + if (gn != NULL) + list->insert_last(list, gn); } objectID++; } @@ -547,10 +648,8 @@ static void parse_authorityInfoAccess(chunk_t blob, int level0, chunk_t *accessL if (*object.ptr == ASN1_CONTEXT_S_6) { if (asn1_length(&object) == ASN1_INVALID_LENGTH) - { return; - } - logger->log(logger, RAW|LEVEL1, " '%.*s'",(int)object.len, object.ptr); + logger->log(logger, CONTROL|LEVEL1, " '%.*s'",(int)object.len, object.ptr); /* only HTTP(S) URIs accepted */ if (strncasecmp(object.ptr, "http", 4) == 0) { @@ -659,7 +758,7 @@ bool parse_x509cert(chunk_t blob, u_int level0, private_x509_t *cert) break; case X509_OBJ_VERSION: cert->version = (object.len) ? (1+(u_int)*object.ptr) : 1; - logger->log(logger, RAW|LEVEL1, " v%d", cert->version); + logger->log(logger, CONTROL|LEVEL1, " v%d", cert->version); break; case X509_OBJ_SERIAL_NUMBER: cert->serialNumber = object; @@ -669,6 +768,7 @@ bool parse_x509cert(chunk_t blob, u_int level0, private_x509_t *cert) break; case X509_OBJ_ISSUER: cert->issuer = identification_create_from_encoding(ID_DER_ASN1_DN, object); + logger->log(logger, CONTROL|LEVEL1, " '%s'", cert->issuer->get_string(cert->issuer)); break; case X509_OBJ_NOT_BEFORE: cert->notBefore = parse_time(object, level); @@ -678,6 +778,7 @@ bool parse_x509cert(chunk_t blob, u_int level0, private_x509_t *cert) break; case X509_OBJ_SUBJECT: cert->subject = identification_create_from_encoding(ID_DER_ASN1_DN, object); + logger->log(logger, CONTROL|LEVEL1, " '%s'", cert->subject->get_string(cert->subject)); break; case X509_OBJ_SUBJECT_PUBLIC_KEY_ALGORITHM: if (parse_algorithmIdentifier(object, level, NULL) != OID_RSA_ENCRYPTION) @@ -737,9 +838,7 @@ bool parse_x509cert(chunk_t blob, u_int level0, private_x509_t *cert) case OID_NS_CA_POLICY_URL: case OID_NS_COMMENT: if (!parse_asn1_simple_object(&object, ASN1_IA5STRING , level, oid_names[extn_oid].name)) - { return FALSE; - } break; default: break; @@ -789,12 +888,35 @@ err_t check_validity(const private_x509_t *cert, time_t *until) } } +/** + * Implements x509_t.equals_subjectAltName + */ +static bool equals_subjectAltName(private_x509_t *this, identification_t *id) +{ + bool found = FALSE; + iterator_t *iterator = this->subjectAltNames->create_iterator(this->subjectAltNames, TRUE); + + while (iterator->has_next(iterator)) + { + identification_t *subjectAltName; + + iterator->current(iterator, (void**)&subjectAltName); + if (id->equals(id, subjectAltName)) + { + found = TRUE; + break; + } + } + iterator->destroy(iterator); + return found; +} + /** * Implements x509_t.get_public_key */ static rsa_public_key_t *get_public_key(private_x509_t *this) { - return this->public_key->clone(this->public_key);; + return this->public_key->clone(this->public_key); } /** @@ -824,32 +946,69 @@ static void destroy(private_x509_t *this) id->destroy(id); } this->subjectAltNames->destroy(this->subjectAltNames); - while (this->issuerAltNames->remove_last(this->issuerAltNames, (void**)&id) == SUCCESS) - { - id->destroy(id); - } - this->issuerAltNames->destroy(this->issuerAltNames); + while (this->crlDistributionPoints->remove_last(this->crlDistributionPoints, (void**)&id) == SUCCESS) { id->destroy(id); } this->crlDistributionPoints->destroy(this->crlDistributionPoints); + if (this->issuer) - { this->issuer->destroy(this->issuer); - } + if (this->subject) - { this->subject->destroy(this->subject); - } + if (this->public_key) - { this->public_key->destroy(this->public_key); - } + free(this->certificate.ptr); free(this); } +/** checks if the expiration date has been reached and + * warns during the warning_interval of the imminent + * expiry. strict=TRUE declares a fatal error, + * strict=FALSE issues a warning upon expiry. + */ +char* check_expiry(time_t expiration_date, int warning_interval, bool strict) +{ + time_t now; + int time_left; + + if (expiration_date == UNDEFINED_TIME) + return "ok (expires never)"; + + time_left = (expiration_date - time(NULL)); + if (time_left < 0) + return strict? "fatal (expired)" : "warning (expired)"; + + { + static char buf[35]; /* temporary storage */ + const char* unit = "second"; + + if (time_left > 86400*warning_interval) + return "ok"; + + if (time_left > 172800) + { + time_left /= 86400; + unit = "day"; + } + else if (time_left > 7200) + { + time_left /= 3600; + unit = "hour"; + } + else if (time_left > 120) + { + time_left /= 60; + unit = "minute"; + } + snprintf(buf, sizeof(buf), "warning (expires in %d %s%s)", time_left, unit, (time_left == 1)?"":"s"); + } +} + /** * log certificate */ @@ -861,6 +1020,10 @@ static void log_certificate(private_x509_t *this, logger_t *logger, bool utc) rsa_public_key_t *rsa_key = this->public_key; char buf[BUF_LEN]; + time_t now; + + /* determine the current time */ + time(&now); timetoa(buf, BUF_LEN, &this->installed, utc); logger->log(logger, CONTROL, "%s", buf); @@ -869,9 +1032,11 @@ static void log_certificate(private_x509_t *this, logger_t *logger, bool utc) chunk_to_hex(buf, BUF_LEN, this->serialNumber); logger->log(logger, CONTROL, " serial: %s", buf); timetoa(buf, BUF_LEN, &this->notBefore, utc); - logger->log(logger, CONTROL, " validity: not before %s", buf); + logger->log(logger, CONTROL, " validity: not before %s %s", buf, + (this->notBefore < now)? "ok":"fatal (not valid yet)"); timetoa(buf, BUF_LEN, &this->notAfter, utc); - logger->log(logger, CONTROL, " not after %s", buf); + logger->log(logger, CONTROL, " not after %s %s", buf, + check_expiry(this->notAfter, CERT_WARNING_INTERVAL, TRUE)); logger->log(logger, CONTROL, " pubkey: RSA %d bits", BITS_PER_BYTE * rsa_key->get_keysize(rsa_key)); if (this->subjectKeyID.ptr != NULL) { @@ -899,6 +1064,7 @@ x509_t *x509_create_from_chunk(chunk_t chunk) /* public functions */ this->public.equals = (bool (*) (x509_t*,x509_t*))equals; + this->public.equals_subjectAltName = (bool (*) (x509_t*,identification_t*))equals_subjectAltName; this->public.destroy = (void (*) (x509_t*))destroy; this->public.get_public_key = (rsa_public_key_t* (*) (x509_t*))get_public_key; this->public.get_subject = (identification_t* (*) (x509_t*))get_subject; @@ -911,7 +1077,6 @@ x509_t *x509_create_from_chunk(chunk_t chunk) this->subject = NULL; this->issuer = NULL; this->subjectAltNames = linked_list_create(); - this->issuerAltNames = linked_list_create(); this->crlDistributionPoints = linked_list_create(); this->subjectKeyID = CHUNK_INITIALIZER; this->authKeyID = CHUNK_INITIALIZER; @@ -920,8 +1085,7 @@ x509_t *x509_create_from_chunk(chunk_t chunk) /* we do not use a per-instance logger right now, since its not always accessible */ logger = logger_manager->get_logger(logger_manager, ASN1); - if (!is_asn1(chunk) || - !parse_x509cert(chunk, 0, this)) + if (!is_asn1(chunk) || !parse_x509cert(chunk, 0, this)) { destroy(this); return NULL; @@ -950,9 +1114,8 @@ x509_t *x509_create_from_file(const char *filename) return NULL; cert = x509_create_from_chunk(chunk); + if (cert == NULL) - { free(chunk.ptr); - } return cert; } diff --git a/src/libstrongswan/crypto/x509.h b/src/libstrongswan/crypto/x509.h index 9caf0ab13..8e13dfc2a 100755 --- a/src/libstrongswan/crypto/x509.h +++ b/src/libstrongswan/crypto/x509.h @@ -106,6 +106,15 @@ struct x509_t { */ bool (*equals) (x509_t *this, x509_t *that); + /** + * @brief Checks if the certificate contains a subjectAltName equal to id. + * + * @param this certificate being examined + * @param id id which is being compared to the subjectAltNames + * @return TRUE if a match is found + */ + bool (*equals_subjectAltName) (x509_t *this, identification_t *id); + /** * @brief Destroys the certificate. * diff --git a/src/libstrongswan/utils/identification.c b/src/libstrongswan/utils/identification.c index 8132d6ed5..369d481fb 100644 --- a/src/libstrongswan/utils/identification.c +++ b/src/libstrongswan/utils/identification.c @@ -28,6 +28,7 @@ #include #include +#include "definitions.h" #include "identification.h" #include @@ -47,21 +48,17 @@ mapping_t id_type_m[] = { {MAPPING_END, NULL} }; - /** * X.501 acronyms for well known object identifiers (OIDs) */ static u_char oid_ND[] = { - 0x02, 0x82, 0x06, 0x01, - 0x0A, 0x07, 0x14 + 0x02, 0x82, 0x06, 0x01, 0x0A, 0x07, 0x14 }; static u_char oid_UID[] = { - 0x09, 0x92, 0x26, 0x89, 0x93, - 0xF2, 0x2C, 0x64, 0x01, 0x01 + 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x01 }; static u_char oid_DC[] = { - 0x09, 0x92, 0x26, 0x89, 0x93, - 0xF2, 0x2C, 0x64, 0x01, 0x19 + 0x09, 0x92, 0x26, 0x89, 0x93, 0xF2, 0x2C, 0x64, 0x01, 0x19 }; static u_char oid_CN[] = { 0x55, 0x04, 0x03 @@ -106,20 +103,16 @@ static u_char oid_ID[] = { 0x55, 0x04, 0x2D }; static u_char oid_EN[] = { - 0x60, 0x86, 0x48, 0x01, 0x86, - 0xF8, 0x42, 0x03, 0x01, 0x03 + 0x60, 0x86, 0x48, 0x01, 0x86, 0xF8, 0x42, 0x03, 0x01, 0x03 }; static u_char oid_E[] = { - 0x2A, 0x86, 0x48, 0x86, 0xF7, - 0x0D, 0x01, 0x09, 0x01 + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01 }; static u_char oid_UN[] = { - 0x2A, 0x86, 0x48, 0x86, 0xF7, - 0x0D, 0x01, 0x09, 0x02 + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x02 }; static u_char oid_TCGID[] = { - 0x2B, 0x06, 0x01, 0x04, 0x01, 0x89, - 0x31, 0x01, 0x01, 0x02, 0x02, 0x4B + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x89, 0x31, 0x01, 0x01, 0x02, 0x02, 0x4B }; /** @@ -162,49 +155,10 @@ static const x501rdn_t x501rdns[] = { #define X501_RDN_ROOF 26 /** - * ASN.1 definition of generalName + * maximum number of RDNs in atodn() */ -static const asn1Object_t generalNameObjects[] = { - { 0, "otherName", ASN1_CONTEXT_C_0, ASN1_OPT|ASN1_BODY }, /* 0 */ - { 0, "end choice", ASN1_EOC, ASN1_END }, /* 1 */ - { 0, "rfc822Name", ASN1_CONTEXT_S_1, ASN1_OPT|ASN1_BODY }, /* 2 */ - { 0, "end choice", ASN1_EOC, ASN1_END }, /* 3 */ - { 0, "dnsName", ASN1_CONTEXT_S_2, ASN1_OPT|ASN1_BODY }, /* 4 */ - { 0, "end choice", ASN1_EOC, ASN1_END }, /* 5 */ - { 0, "x400Address", ASN1_CONTEXT_S_3, ASN1_OPT|ASN1_BODY }, /* 6 */ - { 0, "end choice", ASN1_EOC, ASN1_END }, /* 7 */ - { 0, "directoryName", ASN1_CONTEXT_C_4, ASN1_OPT|ASN1_BODY }, /* 8 */ - { 0, "end choice", ASN1_EOC, ASN1_END }, /* 9 */ - { 0, "ediPartyName", ASN1_CONTEXT_C_5, ASN1_OPT|ASN1_BODY }, /* 10 */ - { 0, "end choice", ASN1_EOC, ASN1_END }, /* 11 */ - { 0, "URI", ASN1_CONTEXT_S_6, ASN1_OPT|ASN1_BODY }, /* 12 */ - { 0, "end choice", ASN1_EOC, ASN1_END }, /* 13 */ - { 0, "ipAddress", ASN1_CONTEXT_S_7, ASN1_OPT|ASN1_BODY }, /* 14 */ - { 0, "end choice", ASN1_EOC, ASN1_END }, /* 15 */ - { 0, "registeredID", ASN1_CONTEXT_S_8, ASN1_OPT|ASN1_BODY }, /* 16 */ - { 0, "end choice", ASN1_EOC, ASN1_END } /* 17 */ -}; -#define GN_OBJ_OTHER_NAME 0 -#define GN_OBJ_RFC822_NAME 2 -#define GN_OBJ_DNS_NAME 4 -#define GN_OBJ_X400_ADDRESS 6 -#define GN_OBJ_DIRECTORY_NAME 8 -#define GN_OBJ_EDI_PARTY_NAME 10 -#define GN_OBJ_URI 12 -#define GN_OBJ_IP_ADDRESS 14 -#define GN_OBJ_REGISTERED_ID 16 -#define GN_OBJ_ROOF 18 +#define RDN_MAX 20 -/** - * ASN.1 definition of otherName - */ -static const asn1Object_t otherNameObjects[] = { - {0, "type-id", ASN1_OID, ASN1_BODY }, /* 0 */ - {0, "value", ASN1_CONTEXT_C_0, ASN1_BODY } /* 1 */ -}; -#define ON_OBJ_ID_TYPE 0 -#define ON_OBJ_VALUE 1 -#define ON_OBJ_ROOF 2 typedef struct private_identification_t private_identification_t; @@ -235,7 +189,6 @@ struct private_identification_t { static private_identification_t *identification_create(void); - /** * updates a chunk (!????) * TODO: We should reconsider this stuff, its not really clear @@ -253,7 +206,7 @@ void hex_str(chunk_t bin, chunk_t *str) { u_int i; update_chunk(str, snprintf(str->ptr,str->len,"0x")); - for (i=0; i < bin.len; i++) + for (i = 0; i < bin.len; i++) { update_chunk(str, snprintf(str->ptr,str->len,"%02X",*bin.ptr++)); } @@ -396,18 +349,14 @@ static status_t dntoa(chunk_t dn, chunk_t *str) status_t status = init_rdn(dn, &rdn, &attribute, &next); if (status != SUCCESS) - {/* a parsing error has occured */ return status; - } while (next) { status = get_next_rdn(&rdn, &attribute, &oid, &value, &type, &next); if (status != SUCCESS) - {/* a parsing error has occured */ return status; - } if (first) { /* first OID/value pair */ @@ -447,18 +396,15 @@ static bool same_dn(chunk_t a, chunk_t b) /* same lengths for the DNs */ if (a.len != b.len) - { return FALSE; - } + /* try a binary comparison first */ - if (memcmp(a.ptr, b.ptr, b.len) == 0) - { + if (memeq(a.ptr, b.ptr, b.len)) return TRUE; - } /* initialize DN parsing */ - if (init_rdn(a, &rdn_a, &attribute_a, &next_a) != SUCCESS || - init_rdn(b, &rdn_b, &attribute_b, &next_b) != SUCCESS) + if (init_rdn(a, &rdn_a, &attribute_a, &next_a) != SUCCESS + || init_rdn(b, &rdn_b, &attribute_b, &next_b) != SUCCESS) { return FALSE; } @@ -467,36 +413,31 @@ static bool same_dn(chunk_t a, chunk_t b) while (next_a && next_b) { /* parse next RDNs and check for errors */ - if (get_next_rdn(&rdn_a, &attribute_a, &oid_a, &value_a, &type_a, &next_a) != SUCCESS || - get_next_rdn(&rdn_b, &attribute_b, &oid_b, &value_b, &type_b, &next_b) != SUCCESS) + if (get_next_rdn(&rdn_a, &attribute_a, &oid_a, &value_a, &type_a, &next_a) != SUCCESS + || get_next_rdn(&rdn_b, &attribute_b, &oid_b, &value_b, &type_b, &next_b) != SUCCESS) { return FALSE; } + /* OIDs must agree */ if (oid_a.len != oid_b.len || memcmp(oid_a.ptr, oid_b.ptr, oid_b.len) != 0) - { return FALSE; - } + /* same lengths for values */ if (value_a.len != value_b.len) - { return FALSE; - } + /* printableStrings and email RDNs require uppercase comparison */ - if (type_a == type_b && (type_a == ASN1_PRINTABLESTRING || - (type_a == ASN1_IA5STRING && known_oid(oid_a) == OID_PKCS9_EMAIL))) + if (type_a == type_b && (type_a == ASN1_PRINTABLESTRING + || (type_a == ASN1_IA5STRING && known_oid(oid_a) == OID_PKCS9_EMAIL))) { if (strncasecmp(value_a.ptr, value_b.ptr, value_b.len) != 0) - { return FALSE; - } } else { if (strncmp(value_a.ptr, value_b.ptr, value_b.len) != 0) - { return FALSE; - } } } /* both DNs must have same number of RDNs */ @@ -524,25 +465,25 @@ bool match_dn(chunk_t a, chunk_t b, int *wildcards) *wildcards = 0; /* initialize DN parsing */ - if (init_rdn(a, &rdn_a, &attribute_a, &next_a) != SUCCESS || - init_rdn(b, &rdn_b, &attribute_b, &next_b) != SUCCESS) + if (init_rdn(a, &rdn_a, &attribute_a, &next_a) != SUCCESS + || init_rdn(b, &rdn_b, &attribute_b, &next_b) != SUCCESS) { return FALSE; } + /* fetch next RDN pair */ while (next_a && next_b) { /* parse next RDNs and check for errors */ - if (get_next_rdn(&rdn_a, &attribute_a, &oid_a, &value_a, &type_a, &next_a) != SUCCESS || - get_next_rdn(&rdn_b, &attribute_b, &oid_b, &value_b, &type_b, &next_b) != SUCCESS) + if (get_next_rdn(&rdn_a, &attribute_a, &oid_a, &value_a, &type_a, &next_a) != SUCCESS + || get_next_rdn(&rdn_b, &attribute_b, &oid_b, &value_b, &type_b, &next_b) != SUCCESS) { return FALSE; } /* OIDs must agree */ if (oid_a.len != oid_b.len || memcmp(oid_a.ptr, oid_b.ptr, oid_b.len) != 0) - { return FALSE; - } + /* does rdn_b contain a wildcard? */ if (value_b.len == 1 && *value_b.ptr == '*') { @@ -551,24 +492,19 @@ bool match_dn(chunk_t a, chunk_t b, int *wildcards) } /* same lengths for values */ if (value_a.len != value_b.len) - { return FALSE; - } + /* printableStrings and email RDNs require uppercase comparison */ - if (type_a == type_b && (type_a == ASN1_PRINTABLESTRING || - (type_a == ASN1_IA5STRING && known_oid(oid_a) == OID_PKCS9_EMAIL))) + if (type_a == type_b && (type_a == ASN1_PRINTABLESTRING + || (type_a == ASN1_IA5STRING && known_oid(oid_a) == OID_PKCS9_EMAIL))) { if (strncasecmp(value_a.ptr, value_b.ptr, value_b.len) != 0) - { return FALSE; - } } else { if (strncmp(value_a.ptr, value_b.ptr, value_b.len) != 0) - { return FALSE; - } } } /* both DNs must have same number of RDNs */ @@ -580,59 +516,6 @@ bool match_dn(chunk_t a, chunk_t b, int *wildcards) return TRUE; } -/** - * get string representation of a general name - * TODO: Add support for gn types - */ -static char *gntoa(chunk_t blob) -{ - asn1_ctx_t ctx; - chunk_t object; - int objectID = 0; - u_int level; - char buf[128]; - - asn1_init(&ctx, blob, 0, FALSE); - - while (objectID < GN_OBJ_ROOF) - { - if (!extract_object(generalNameObjects, &objectID, &object, &level, &ctx)) - { - return NULL; - } - switch (objectID) - { - case GN_OBJ_RFC822_NAME: - case GN_OBJ_DNS_NAME: - case GN_OBJ_URI: - snprintf(buf, sizeof(buf), "%.*s", object.len, object.ptr); - return strdup(buf); - case GN_OBJ_IP_ADDRESS: - if (object.len == 4 && - inet_ntop(AF_INET, object.ptr, buf, sizeof(buf))) - { - return strdup(buf); - } - return NULL; - break; - case GN_OBJ_OTHER_NAME: - return strdup("(other name)"); - case GN_OBJ_X400_ADDRESS: - return strdup("(X400 Address)"); - case GN_OBJ_EDI_PARTY_NAME: - return strdup("(EDI party name)"); - case GN_OBJ_REGISTERED_ID: - return strdup("(registered ID)"); - case GN_OBJ_DIRECTORY_NAME: - return strdup("(directory name)"); - default: - break; - } - objectID++; - } - return NULL; -} - /** * Converts an LDAP-style human-readable ASCII-encoded * ASN.1 distinguished name into binary DER-encoded format @@ -648,13 +531,13 @@ static status_t atodn(char *src, chunk_t *dn) UNKNOWN_OID = 4 } state_t; - char *wrap_mode; chunk_t oid = CHUNK_INITIALIZER; chunk_t name = CHUNK_INITIALIZER; - chunk_t names[25]; /* max to 25 rdns */ - int name_count = 0; + chunk_t rdns[RDN_MAX]; + int rdn_count = 0; + int dn_len = 0; int whitespace = 0; - int pos = 0; + int i; asn1_t rdn_type; state_t state = SEARCH_OID; status_t status = SUCCESS; @@ -678,15 +561,15 @@ static status_t atodn(char *src, chunk_t *dn) } else { - for (pos = 0; pos < X501_RDN_ROOF; pos++) + for (i = 0; i < X501_RDN_ROOF; i++) { - if (strlen(x501rdns[pos].name) == oid.len && - strncasecmp(x501rdns[pos].name, oid.ptr, oid.len) == 0) + if (strlen(x501rdns[i].name) == oid.len + && strncasecmp(x501rdns[i].name, oid.ptr, oid.len) == 0) { break; /* found a valid OID */ } } - if (pos == X501_RDN_ROOF) + if (i == X501_RDN_ROOF) { status = NOT_SUPPORTED; state = UNKNOWN_OID; @@ -711,29 +594,26 @@ static status_t atodn(char *src, chunk_t *dn) { name.len++; if (*src == ' ') - { whitespace++; - } else - { whitespace = 0; - } } else { name.len -= whitespace; - rdn_type = (x501rdns[pos].type == ASN1_PRINTABLESTRING - && !is_printablestring(name))? ASN1_T61STRING : x501rdns[pos].type; + rdn_type = (x501rdns[i].type == ASN1_PRINTABLESTRING + && !is_printablestring(name))? ASN1_T61STRING : x501rdns[i].type; - if (name_count < 25) + if (rdn_count < RDN_MAX) { - names[name_count++] = + rdns[rdn_count] = asn1_wrap(ASN1_SET, "m", - asn1_wrap(ASN1_SEQUENCE, "mm", - asn1_wrap(ASN1_OID, "c", x501rdns[pos].oid), - asn1_wrap(rdn_type, "c", name) - ) - ); + asn1_wrap(ASN1_SEQUENCE, "mm", + asn1_wrap(ASN1_OID, "c", x501rdns[i].oid), + asn1_wrap(rdn_type, "c", name) + ) + ); + dn_len += rdns[rdn_count++].len; } else { @@ -749,17 +629,19 @@ static status_t atodn(char *src, chunk_t *dn) } } while (*src++ != '\0'); - /* build the distinguished name sequence */ - wrap_mode = alloca(26); - memset(wrap_mode, 0, 26); - memset(wrap_mode, 'm', name_count); - *dn = asn1_wrap(ASN1_SEQUENCE, wrap_mode, - names[0], names[1], names[2], names[3], names[4], - names[5], names[6], names[7], names[8], names[9], - names[10], names[11], names[12], names[13], names[14], - names[15], names[16], names[17], names[18], names[19], - names[20], names[21], names[22], names[23], names[24]); + { + int i; + u_char *pos = build_asn1_object(dn, ASN1_SEQUENCE, dn_len); + + for (i = 0; i < rdn_count; i++) + { + memcpy(pos, rdns[i].ptr, rdns[i].len); + pos += rdns[i].len; + free(rdns[i].ptr); + } + } + if (status != SUCCESS) { free(dn->ptr); @@ -797,29 +679,16 @@ static char *get_string(private_identification_t *this) */ static bool contains_wildcards(private_identification_t *this) { - if (this->type == ID_ANY || - memchr(this->encoded.ptr, '*', this->encoded.len) != NULL) - { - return TRUE; - } - return FALSE; + return this->type == ID_ANY || memchr(this->encoded.ptr, '*', this->encoded.len) != NULL; } /** * Default implementation of identification_t.equals and identification_t.belongs_to. * compares encoded chunk for equality. */ -static bool equals_binary(private_identification_t *this,private_identification_t *other) +static bool equals_binary(private_identification_t *this, private_identification_t *other) { - if (this->type == other->type) - { - if (this->encoded.len == other->encoded.len && - memcmp(this->encoded.ptr, other->encoded.ptr, this->encoded.len) == 0) - { - return TRUE; - } - } - return FALSE; + return this->type == other->type && chunk_equals(this->encoded, other->encoded); } /** @@ -882,11 +751,7 @@ static bool belongs_to_wc_string(private_identification_t *this, private_identif */ static bool belongs_to_any(private_identification_t *this, private_identification_t *other) { - if (other->type == ID_ANY) - { - return TRUE; - } - return FALSE; + return other->type == ID_ANY; } /** @@ -968,9 +833,8 @@ identification_t *identification_create_from_string(char *string) { /* we interpret this as an ASCII X.501 ID_DER_ASN1_DN. * convert from LDAP style or openssl x509 -subject style to ASN.1 DN - * discard optional @ character in front of DN */ - if (atodn((*string == '@') ? string + 1 : string, &this->encoded) != SUCCESS) + if (atodn(string, &this->encoded) != SUCCESS) { free(this); return NULL; @@ -983,11 +847,11 @@ identification_t *identification_create_from_string(char *string) } else if (strchr(string, '@') == NULL) { - if (strcmp(string, "%any") == 0 || - strcmp(string, "0.0.0.0") == 0 || - strcmp(string, "*") == 0 || - strcmp(string, "::") == 0|| - strcmp(string, "0::0") == 0) + if (streq(string, "%any") + || streq(string, "0.0.0.0") + || streq(string, "*") + || streq(string, "::") + || streq(string, "0::0")) { /* any ID will be accepted */ this->type = ID_ANY; @@ -997,8 +861,6 @@ identification_t *identification_create_from_string(char *string) } else { - /* TODO: Pluto resolve domainnames without '@' to IPv4/6 address. Is this really needed? */ - if (strchr(string, ':') == NULL) { /* try IPv4 */ @@ -1039,14 +901,15 @@ identification_t *identification_create_from_string(char *string) { if (*(string + 1) == '#') { - /* TODO: Pluto handles '#' as hex encoded ASN1/KEY ID. Do we need this, too? */ + /* TODO: Pluto handles '#' as hex encoded ASN1/KEY ID. Do we need this, too? + Yes, key IDs are needed */ free(this); return NULL; } else { this->type = ID_FQDN; - this->string = strdup(string + 1); /* discard @ */ + this->string = strdup(string); this->encoded.ptr = strdup(string + 1); this->encoded.len = strlen(string + 1); this->public.belongs_to = (bool (*) (identification_t*,identification_t*))belongs_to_wc_string; @@ -1070,10 +933,10 @@ identification_t *identification_create_from_string(char *string) */ identification_t *identification_create_from_encoding(id_type_t type, chunk_t encoded) { - private_identification_t *this = identification_create(); - char buf[256]; - chunk_t buf_chunk = chunk_from_buf(buf); char *pos; + char buf[BUF_LEN]; + chunk_t buf_chunk = chunk_from_buf(buf); + private_identification_t *this = identification_create(); this->type = type; switch (type) @@ -1123,10 +986,14 @@ identification_t *identification_create_from_encoding(id_type_t type, chunk_t en this->public.belongs_to = (bool (*) (identification_t*,identification_t*))belongs_to_dn; break; case ID_DER_ASN1_GN: - this->string = gntoa(encoded); + this->string = strdup("ASN.1 coded generalName"); break; case ID_KEY_ID: - this->string = strdup("(unparsed KEY_ID)"); + this->string = strdup("(KEY_ID)"); + break; + case ID_DER_ASN1_GN_URI: + snprintf(buf, sizeof(buf), "%.*s", encoded.len, encoded.ptr); + this->string = strdup(buf); break; default: snprintf(buf, sizeof(buf), "(invalid ID type: %d)", type); diff --git a/src/libstrongswan/utils/identification.h b/src/libstrongswan/utils/identification.h index e1e147fab..ca1b53454 100644 --- a/src/libstrongswan/utils/identification.h +++ b/src/libstrongswan/utils/identification.h @@ -36,6 +36,11 @@ typedef enum id_type_t id_type_t; */ enum id_type_t { + /** + * private type which matches any other id. + */ + ID_ANY = 0, + /** * ID data is a single four (4) octet IPv4 address. */ @@ -78,11 +83,12 @@ enum id_type_t { * types of identification. */ ID_KEY_ID = 11, - + /** - * Special type of PRIVATE USE which matches to any other id. + * private type which represents a GeneralName of type URI */ - ID_ANY = 201, + ID_DER_ASN1_GN_URI = 201, + }; /** @@ -90,23 +96,6 @@ enum id_type_t { */ extern mapping_t id_type_m[]; -/** - * Different kinds of generalNames - */ -typedef enum generalNames_t generalNames_t; - -enum generalNames_t { - GN_OTHER_NAME = 0, - GN_RFC822_NAME = 1, - GN_DNS_NAME = 2, - GN_X400_ADDRESS = 3, - GN_DIRECTORY_NAME = 4, - GN_EDI_PARTY_NAME = 5, - GN_URI = 6, - GN_IP_ADDRESS = 7, - GN_REGISTERED_ID = 8, -}; - typedef struct identification_t identification_t; /** @@ -120,6 +109,7 @@ typedef struct identification_t identification_t; * - ID_DER_ASN1_DN * - ID_DER_ASN1_GN * - ID_KEY_ID + * - ID_DER_ASN1_GN_URI * * @b Constructors: * - identification_create_from_string()