1196 lines
27 KiB
C
1196 lines
27 KiB
C
/*
|
|
* Copyright (C) 2009 Tobias Brunner
|
|
* Copyright (C) 2005-2008 Martin Willi
|
|
* Copyright (C) 2005 Jan Hutter
|
|
* Hochschule fuer Technik Rapperswil
|
|
*
|
|
* 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 <http://www.fsf.org/copyleft/gpl.txt>.
|
|
*
|
|
* 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.
|
|
*
|
|
* $Id$
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
|
|
#include "identification.h"
|
|
|
|
#include <asn1/oid.h>
|
|
#include <asn1/asn1.h>
|
|
|
|
ENUM_BEGIN(id_match_names, ID_MATCH_NONE, ID_MATCH_MAX_WILDCARDS,
|
|
"MATCH_NONE",
|
|
"MATCH_ANY",
|
|
"MATCH_MAX_WILDCARDS");
|
|
ENUM_NEXT(id_match_names, ID_MATCH_PERFECT, ID_MATCH_PERFECT, ID_MATCH_MAX_WILDCARDS,
|
|
"MATCH_PERFECT");
|
|
ENUM_END(id_match_names, ID_MATCH_PERFECT);
|
|
|
|
ENUM_BEGIN(id_type_names, ID_ANY, ID_KEY_ID,
|
|
"ID_ANY",
|
|
"ID_IPV4_ADDR",
|
|
"ID_FQDN",
|
|
"ID_RFC822_ADDR",
|
|
"ID_IPV4_ADDR_SUBNET",
|
|
"ID_IPV6_ADDR",
|
|
"ID_IPV6_ADDR_SUBNET",
|
|
"ID_IPV4_ADDR_RANGE",
|
|
"ID_IPV6_ADDR_RANGE",
|
|
"ID_DER_ASN1_DN",
|
|
"ID_DER_ASN1_GN",
|
|
"ID_KEY_ID");
|
|
ENUM_NEXT(id_type_names, ID_DER_ASN1_GN_URI, ID_CERT_DER_SHA1, ID_KEY_ID,
|
|
"ID_DER_ASN1_GN_URI",
|
|
"ID_PUBKEY_INFO_SHA1",
|
|
"ID_PUBKEY_SHA1",
|
|
"ID_CERT_DER_SHA1");
|
|
ENUM_END(id_type_names, ID_CERT_DER_SHA1);
|
|
|
|
/**
|
|
* X.501 acronyms for well known object identifiers (OIDs)
|
|
*/
|
|
static u_char oid_ND[] = {
|
|
0x02, 0x82, 0x06, 0x01, 0x0A, 0x07, 0x14
|
|
};
|
|
static u_char oid_UID[] = {
|
|
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
|
|
};
|
|
static u_char oid_CN[] = {
|
|
0x55, 0x04, 0x03
|
|
};
|
|
static u_char oid_S[] = {
|
|
0x55, 0x04, 0x04
|
|
};
|
|
static u_char oid_SN[] = {
|
|
0x55, 0x04, 0x05
|
|
};
|
|
static u_char oid_C[] = {
|
|
0x55, 0x04, 0x06
|
|
};
|
|
static u_char oid_L[] = {
|
|
0x55, 0x04, 0x07
|
|
};
|
|
static u_char oid_ST[] = {
|
|
0x55, 0x04, 0x08
|
|
};
|
|
static u_char oid_O[] = {
|
|
0x55, 0x04, 0x0A
|
|
};
|
|
static u_char oid_OU[] = {
|
|
0x55, 0x04, 0x0B
|
|
};
|
|
static u_char oid_T[] = {
|
|
0x55, 0x04, 0x0C
|
|
};
|
|
static u_char oid_D[] = {
|
|
0x55, 0x04, 0x0D
|
|
};
|
|
static u_char oid_N[] = {
|
|
0x55, 0x04, 0x29
|
|
};
|
|
static u_char oid_G[] = {
|
|
0x55, 0x04, 0x2A
|
|
};
|
|
static u_char oid_I[] = {
|
|
0x55, 0x04, 0x2B
|
|
};
|
|
static u_char oid_ID[] = {
|
|
0x55, 0x04, 0x2D
|
|
};
|
|
static u_char oid_EN[] = {
|
|
0x60, 0x86, 0x48, 0x01, 0x86, 0xF8, 0x42, 0x03, 0x01, 0x03
|
|
};
|
|
static u_char oid_E[] = {
|
|
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01
|
|
};
|
|
static u_char oid_UN[] = {
|
|
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
|
|
};
|
|
|
|
/**
|
|
* coding of X.501 distinguished name
|
|
*/
|
|
typedef struct {
|
|
const u_char *name;
|
|
chunk_t oid;
|
|
u_char type;
|
|
} x501rdn_t;
|
|
|
|
static const x501rdn_t x501rdns[] = {
|
|
{"ND", {oid_ND, 7}, ASN1_PRINTABLESTRING},
|
|
{"UID", {oid_UID, 10}, ASN1_PRINTABLESTRING},
|
|
{"DC", {oid_DC, 10}, ASN1_PRINTABLESTRING},
|
|
{"CN", {oid_CN, 3}, ASN1_PRINTABLESTRING},
|
|
{"S", {oid_S, 3}, ASN1_PRINTABLESTRING},
|
|
{"SN", {oid_SN, 3}, ASN1_PRINTABLESTRING},
|
|
{"serialNumber", {oid_SN, 3}, ASN1_PRINTABLESTRING},
|
|
{"C", {oid_C, 3}, ASN1_PRINTABLESTRING},
|
|
{"L", {oid_L, 3}, ASN1_PRINTABLESTRING},
|
|
{"ST", {oid_ST, 3}, ASN1_PRINTABLESTRING},
|
|
{"O", {oid_O, 3}, ASN1_PRINTABLESTRING},
|
|
{"OU", {oid_OU, 3}, ASN1_PRINTABLESTRING},
|
|
{"T", {oid_T, 3}, ASN1_PRINTABLESTRING},
|
|
{"D", {oid_D, 3}, ASN1_PRINTABLESTRING},
|
|
{"N", {oid_N, 3}, ASN1_PRINTABLESTRING},
|
|
{"G", {oid_G, 3}, ASN1_PRINTABLESTRING},
|
|
{"I", {oid_I, 3}, ASN1_PRINTABLESTRING},
|
|
{"ID", {oid_ID, 3}, ASN1_PRINTABLESTRING},
|
|
{"EN", {oid_EN, 10}, ASN1_PRINTABLESTRING},
|
|
{"employeeNumber", {oid_EN, 10}, ASN1_PRINTABLESTRING},
|
|
{"E", {oid_E, 9}, ASN1_IA5STRING},
|
|
{"Email", {oid_E, 9}, ASN1_IA5STRING},
|
|
{"emailAddress", {oid_E, 9}, ASN1_IA5STRING},
|
|
{"UN", {oid_UN, 9}, ASN1_IA5STRING},
|
|
{"unstructuredName",{oid_UN, 9}, ASN1_IA5STRING},
|
|
{"TCGID", {oid_TCGID, 12}, ASN1_PRINTABLESTRING}
|
|
};
|
|
#define X501_RDN_ROOF 26
|
|
|
|
/**
|
|
* maximum number of RDNs in atodn()
|
|
*/
|
|
#define RDN_MAX 20
|
|
|
|
|
|
typedef struct private_identification_t private_identification_t;
|
|
|
|
/**
|
|
* Private data of an identification_t object.
|
|
*/
|
|
struct private_identification_t {
|
|
/**
|
|
* Public interface.
|
|
*/
|
|
identification_t public;
|
|
|
|
/**
|
|
* Encoded representation of this ID.
|
|
*/
|
|
chunk_t encoded;
|
|
|
|
/**
|
|
* Type of this ID.
|
|
*/
|
|
id_type_t type;
|
|
};
|
|
|
|
static private_identification_t *identification_create(void);
|
|
|
|
/**
|
|
* updates a chunk (!????)
|
|
* TODO: We should reconsider this stuff, its not really clear
|
|
*/
|
|
static void update_chunk(chunk_t *ch, int n)
|
|
{
|
|
n = (n > -1 && n < (int)ch->len)? n : (int)ch->len-1;
|
|
ch->ptr += n; ch->len -= n;
|
|
}
|
|
|
|
/**
|
|
* Remove any malicious characters from a chunk. We are very restrictive, but
|
|
* whe use these strings only to present it to the user.
|
|
*/
|
|
static bool sanitize_chunk(chunk_t chunk, chunk_t *clone)
|
|
{
|
|
char *pos;
|
|
bool all_printable = TRUE;
|
|
|
|
*clone = chunk_clone(chunk);
|
|
|
|
for (pos = clone->ptr; pos < (char*)(clone->ptr + clone->len); pos++)
|
|
{
|
|
if (!isprint(*pos))
|
|
{
|
|
*pos = '?';
|
|
all_printable = FALSE;
|
|
}
|
|
}
|
|
return all_printable;
|
|
}
|
|
|
|
/**
|
|
* Pointer is set to the first RDN in a DN
|
|
*/
|
|
static bool init_rdn(chunk_t dn, chunk_t *rdn, chunk_t *attribute, bool *next)
|
|
{
|
|
*rdn = chunk_empty;
|
|
*attribute = chunk_empty;
|
|
|
|
/* a DN is a SEQUENCE OF RDNs */
|
|
if (*dn.ptr != ASN1_SEQUENCE)
|
|
{
|
|
/* DN is not a SEQUENCE */
|
|
return FALSE;
|
|
}
|
|
|
|
rdn->len = asn1_length(&dn);
|
|
|
|
if (rdn->len == ASN1_INVALID_LENGTH)
|
|
{
|
|
/* Invalid RDN length */
|
|
return FALSE;
|
|
}
|
|
|
|
rdn->ptr = dn.ptr;
|
|
|
|
/* are there any RDNs ? */
|
|
*next = rdn->len > 0;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Fetches the next RDN in a DN
|
|
*/
|
|
static bool get_next_rdn(chunk_t *rdn, chunk_t * attribute, chunk_t *oid, chunk_t *value, asn1_t *type, bool *next)
|
|
{
|
|
chunk_t body;
|
|
|
|
/* initialize return values */
|
|
*oid = chunk_empty;
|
|
*value = chunk_empty;
|
|
|
|
/* if all attributes have been parsed, get next rdn */
|
|
if (attribute->len <= 0)
|
|
{
|
|
/* an RDN is a SET OF attributeTypeAndValue */
|
|
if (*rdn->ptr != ASN1_SET)
|
|
{
|
|
/* RDN is not a SET */
|
|
return FALSE;
|
|
}
|
|
attribute->len = asn1_length(rdn);
|
|
if (attribute->len == ASN1_INVALID_LENGTH)
|
|
{
|
|
/* Invalid attribute length */
|
|
return FALSE;
|
|
}
|
|
attribute->ptr = rdn->ptr;
|
|
/* advance to start of next RDN */
|
|
rdn->ptr += attribute->len;
|
|
rdn->len -= attribute->len;
|
|
}
|
|
|
|
/* an attributeTypeAndValue is a SEQUENCE */
|
|
if (*attribute->ptr != ASN1_SEQUENCE)
|
|
{
|
|
/* attributeTypeAndValue is not a SEQUENCE */
|
|
return FALSE;
|
|
}
|
|
|
|
/* extract the attribute body */
|
|
body.len = asn1_length(attribute);
|
|
|
|
if (body.len == ASN1_INVALID_LENGTH)
|
|
{
|
|
/* Invalid attribute body length */
|
|
return FALSE;
|
|
}
|
|
|
|
body.ptr = attribute->ptr;
|
|
|
|
/* advance to start of next attribute */
|
|
attribute->ptr += body.len;
|
|
attribute->len -= body.len;
|
|
|
|
/* attribute type is an OID */
|
|
if (*body.ptr != ASN1_OID)
|
|
{
|
|
/* attributeType is not an OID */
|
|
return FALSE;
|
|
}
|
|
/* extract OID */
|
|
oid->len = asn1_length(&body);
|
|
|
|
if (oid->len == ASN1_INVALID_LENGTH)
|
|
{
|
|
/* Invalid attribute OID length */
|
|
return FALSE;
|
|
}
|
|
oid->ptr = body.ptr;
|
|
|
|
/* advance to the attribute value */
|
|
body.ptr += oid->len;
|
|
body.len -= oid->len;
|
|
|
|
/* extract string type */
|
|
*type = *body.ptr;
|
|
|
|
/* extract string value */
|
|
value->len = asn1_length(&body);
|
|
|
|
if (value->len == ASN1_INVALID_LENGTH)
|
|
{
|
|
/* Invalid attribute string length */
|
|
return FALSE;
|
|
}
|
|
value->ptr = body.ptr;
|
|
|
|
/* are there any RDNs left? */
|
|
*next = rdn->len > 0 || attribute->len > 0;
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Parses an ASN.1 distinguished name int its OID/value pairs
|
|
*/
|
|
static bool dntoa(chunk_t dn, chunk_t *str)
|
|
{
|
|
chunk_t rdn, oid, attribute, value, proper;
|
|
asn1_t type;
|
|
int oid_code;
|
|
bool next;
|
|
bool first = TRUE;
|
|
|
|
if (!init_rdn(dn, &rdn, &attribute, &next))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
while (next)
|
|
{
|
|
if (!get_next_rdn(&rdn, &attribute, &oid, &value, &type, &next))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
if (first)
|
|
{ /* first OID/value pair */
|
|
first = FALSE;
|
|
}
|
|
else
|
|
{ /* separate OID/value pair by a comma */
|
|
update_chunk(str, snprintf(str->ptr,str->len,", "));
|
|
}
|
|
|
|
/* print OID */
|
|
oid_code = asn1_known_oid(oid);
|
|
if (oid_code == OID_UNKNOWN)
|
|
{
|
|
update_chunk(str, snprintf(str->ptr,str->len,"0x#B", &oid));
|
|
}
|
|
else
|
|
{
|
|
update_chunk(str, snprintf(str->ptr,str->len,"%s", oid_names[oid_code].name));
|
|
}
|
|
/* print value */
|
|
sanitize_chunk(value, &proper);
|
|
update_chunk(str, snprintf(str->ptr,str->len,"=%.*s", (int)proper.len, proper.ptr));
|
|
chunk_free(&proper);
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* compare two distinguished names by
|
|
* comparing the individual RDNs
|
|
*/
|
|
static bool same_dn(chunk_t a, chunk_t b)
|
|
{
|
|
chunk_t rdn_a, rdn_b, attribute_a, attribute_b;
|
|
chunk_t oid_a, oid_b, value_a, value_b;
|
|
asn1_t type_a, type_b;
|
|
bool next_a, next_b;
|
|
|
|
/* same lengths for the DNs */
|
|
if (a.len != b.len)
|
|
{
|
|
return FALSE;
|
|
}
|
|
/* try a binary comparison first */
|
|
if (memeq(a.ptr, b.ptr, b.len))
|
|
{
|
|
return TRUE;
|
|
}
|
|
/* initialize DN parsing */
|
|
if (!init_rdn(a, &rdn_a, &attribute_a, &next_a) ||
|
|
!init_rdn(b, &rdn_b, &attribute_b, &next_b))
|
|
{
|
|
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) ||
|
|
!get_next_rdn(&rdn_b, &attribute_b, &oid_b, &value_b, &type_b, &next_b))
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
/* OIDs must agree */
|
|
if (oid_a.len != oid_b.len || !memeq(oid_a.ptr, oid_b.ptr, oid_b.len))
|
|
{
|
|
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 && asn1_known_oid(oid_a) == OID_PKCS9_EMAIL)))
|
|
{
|
|
if (strncasecmp(value_a.ptr, value_b.ptr, value_b.len) != 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!strneq(value_a.ptr, value_b.ptr, value_b.len))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
/* both DNs must have same number of RDNs */
|
|
if (next_a || next_b)
|
|
{
|
|
return FALSE;
|
|
}
|
|
/* the two DNs are equal! */
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/**
|
|
* compare two distinguished names by comparing the individual RDNs.
|
|
* A single'*' character designates a wildcard RDN in DN b.
|
|
* TODO: Add support for different RDN order in DN !!
|
|
*/
|
|
bool match_dn(chunk_t a, chunk_t b, int *wildcards)
|
|
{
|
|
chunk_t rdn_a, rdn_b, attribute_a, attribute_b;
|
|
chunk_t oid_a, oid_b, value_a, value_b;
|
|
asn1_t type_a, type_b;
|
|
bool next_a, next_b;
|
|
|
|
/* initialize wildcard counter */
|
|
*wildcards = 0;
|
|
|
|
/* initialize DN parsing */
|
|
if (!init_rdn(a, &rdn_a, &attribute_a, &next_a) ||
|
|
!init_rdn(b, &rdn_b, &attribute_b, &next_b))
|
|
{
|
|
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) ||
|
|
!get_next_rdn(&rdn_b, &attribute_b, &oid_b, &value_b, &type_b, &next_b))
|
|
{
|
|
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 == '*')
|
|
{
|
|
(*wildcards)++;
|
|
continue;
|
|
}
|
|
/* 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 && asn1_known_oid(oid_a) == OID_PKCS9_EMAIL)))
|
|
{
|
|
if (strncasecmp(value_a.ptr, value_b.ptr, value_b.len) != 0)
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!strneq(value_a.ptr, value_b.ptr, value_b.len))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
}
|
|
/* both DNs must have same number of RDNs */
|
|
if (next_a || next_b)
|
|
{
|
|
return FALSE;
|
|
}
|
|
/* the two DNs match! */
|
|
*wildcards = min(*wildcards, ID_MATCH_ONE_WILDCARD - ID_MATCH_MAX_WILDCARDS);
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* Converts an LDAP-style human-readable ASCII-encoded
|
|
* ASN.1 distinguished name into binary DER-encoded format
|
|
*/
|
|
static status_t atodn(char *src, chunk_t *dn)
|
|
{
|
|
/* finite state machine for atodn */
|
|
typedef enum {
|
|
SEARCH_OID = 0,
|
|
READ_OID = 1,
|
|
SEARCH_NAME = 2,
|
|
READ_NAME = 3,
|
|
UNKNOWN_OID = 4
|
|
} state_t;
|
|
|
|
chunk_t oid = chunk_empty;
|
|
chunk_t name = chunk_empty;
|
|
chunk_t rdns[RDN_MAX];
|
|
int rdn_count = 0;
|
|
int dn_len = 0;
|
|
int whitespace = 0;
|
|
int i = 0;
|
|
asn1_t rdn_type;
|
|
state_t state = SEARCH_OID;
|
|
status_t status = SUCCESS;
|
|
|
|
do
|
|
{
|
|
switch (state)
|
|
{
|
|
case SEARCH_OID:
|
|
if (*src != ' ' && *src != '/' && *src != ',')
|
|
{
|
|
oid.ptr = src;
|
|
oid.len = 1;
|
|
state = READ_OID;
|
|
}
|
|
break;
|
|
case READ_OID:
|
|
if (*src != ' ' && *src != '=')
|
|
{
|
|
oid.len++;
|
|
}
|
|
else
|
|
{
|
|
for (i = 0; i < X501_RDN_ROOF; i++)
|
|
{
|
|
if (strlen(x501rdns[i].name) == oid.len
|
|
&& strncasecmp(x501rdns[i].name, oid.ptr, oid.len) == 0)
|
|
{
|
|
break; /* found a valid OID */
|
|
}
|
|
}
|
|
if (i == X501_RDN_ROOF)
|
|
{
|
|
status = NOT_SUPPORTED;
|
|
state = UNKNOWN_OID;
|
|
break;
|
|
}
|
|
/* reset oid and change state */
|
|
oid = chunk_empty;
|
|
state = SEARCH_NAME;
|
|
}
|
|
break;
|
|
case SEARCH_NAME:
|
|
if (*src != ' ' && *src != '=')
|
|
{
|
|
name.ptr = src;
|
|
name.len = 1;
|
|
whitespace = 0;
|
|
state = READ_NAME;
|
|
}
|
|
break;
|
|
case READ_NAME:
|
|
if (*src != ',' && *src != '/' && *src != '\0')
|
|
{
|
|
name.len++;
|
|
if (*src == ' ')
|
|
whitespace++;
|
|
else
|
|
whitespace = 0;
|
|
}
|
|
else
|
|
{
|
|
name.len -= whitespace;
|
|
rdn_type = (x501rdns[i].type == ASN1_PRINTABLESTRING
|
|
&& !asn1_is_printablestring(name))
|
|
? ASN1_T61STRING : x501rdns[i].type;
|
|
|
|
if (rdn_count < RDN_MAX)
|
|
{
|
|
rdns[rdn_count] =
|
|
asn1_wrap(ASN1_SET, "m",
|
|
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
|
|
{
|
|
status = OUT_OF_RES;
|
|
}
|
|
/* reset name and change state */
|
|
name = chunk_empty;
|
|
state = SEARCH_OID;
|
|
}
|
|
break;
|
|
case UNKNOWN_OID:
|
|
break;
|
|
}
|
|
} while (*src++ != '\0');
|
|
|
|
/* build the distinguished name sequence */
|
|
{
|
|
int i;
|
|
u_char *pos = asn1_build_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);
|
|
*dn = chunk_empty;
|
|
}
|
|
return status;
|
|
}
|
|
|
|
/**
|
|
* Implementation of identification_t.get_encoding.
|
|
*/
|
|
static chunk_t get_encoding(private_identification_t *this)
|
|
{
|
|
return this->encoded;
|
|
}
|
|
|
|
/**
|
|
* Implementation of identification_t.get_type.
|
|
*/
|
|
static id_type_t get_type(private_identification_t *this)
|
|
{
|
|
return this->type;
|
|
}
|
|
|
|
/**
|
|
* Implementation of identification_t.contains_wildcards fro ID_DER_ASN1_DN.
|
|
*/
|
|
static bool contains_wildcards_dn(private_identification_t *this)
|
|
{
|
|
chunk_t rdn, attribute;
|
|
chunk_t oid, value;
|
|
asn1_t type;
|
|
bool next;
|
|
|
|
if (!init_rdn(this->encoded, &rdn, &attribute, &next))
|
|
{
|
|
return FALSE;
|
|
}
|
|
/* fetch next RDN */
|
|
while (next)
|
|
{
|
|
/* parse next RDN and check for errors */
|
|
if (!get_next_rdn(&rdn, &attribute, &oid, &value, &type, &next))
|
|
{
|
|
return FALSE;
|
|
}
|
|
/* check if RDN is a wildcard */
|
|
if (value.len == 1 && *value.ptr == '*')
|
|
{
|
|
return TRUE;
|
|
}
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Implementation of identification_t.contains_wildcards.
|
|
*/
|
|
static bool contains_wildcards(private_identification_t *this)
|
|
{
|
|
switch (this->type)
|
|
{
|
|
case ID_ANY:
|
|
return TRUE;
|
|
case ID_FQDN:
|
|
case ID_RFC822_ADDR:
|
|
return memchr(this->encoded.ptr, '*', this->encoded.len) != NULL;
|
|
case ID_DER_ASN1_DN:
|
|
return contains_wildcards_dn(this);
|
|
default:
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Default implementation of identification_t.equals.
|
|
* compares encoded chunk for equality.
|
|
*/
|
|
static bool equals_binary(private_identification_t *this, private_identification_t *other)
|
|
{
|
|
if (this->type == other->type)
|
|
{
|
|
if (this->type == ID_ANY)
|
|
{
|
|
return TRUE;
|
|
}
|
|
return chunk_equals(this->encoded, other->encoded);
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Special implementation of identification_t.equals for ID_DER_ASN1_DN.
|
|
*/
|
|
static bool equals_dn(private_identification_t *this,
|
|
private_identification_t *other)
|
|
{
|
|
return same_dn(this->encoded, other->encoded);
|
|
}
|
|
|
|
/**
|
|
* Special implementation of identification_t.equals for RFC822 and FQDN.
|
|
*/
|
|
static bool equals_strcasecmp(private_identification_t *this,
|
|
private_identification_t *other)
|
|
{
|
|
/* we do some extra sanity checks to check for invalid IDs with a
|
|
* terminating null in it. */
|
|
if (this->encoded.len == other->encoded.len &&
|
|
memchr(this->encoded.ptr, 0, this->encoded.len) == NULL &&
|
|
memchr(other->encoded.ptr, 0, other->encoded.len) == NULL &&
|
|
strncasecmp(this->encoded.ptr, other->encoded.ptr, this->encoded.len) == 0)
|
|
{
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* Default implementation of identification_t.matches.
|
|
*/
|
|
static id_match_t matches_binary(private_identification_t *this,
|
|
private_identification_t *other)
|
|
{
|
|
if (other->type == ID_ANY)
|
|
{
|
|
return ID_MATCH_ANY;
|
|
}
|
|
if (this->type == other->type &&
|
|
chunk_equals(this->encoded, other->encoded))
|
|
{
|
|
return ID_MATCH_PERFECT;
|
|
}
|
|
return ID_MATCH_NONE;
|
|
}
|
|
|
|
/**
|
|
* Special implementation of identification_t.matches for ID_RFC822_ADDR/ID_FQDN.
|
|
* Checks for a wildcard in other-string, and compares it against this-string.
|
|
*/
|
|
static id_match_t matches_string(private_identification_t *this,
|
|
private_identification_t *other)
|
|
{
|
|
u_int len = other->encoded.len;
|
|
|
|
if (other->type == ID_ANY)
|
|
{
|
|
return ID_MATCH_ANY;
|
|
}
|
|
if (this->type != other->type)
|
|
{
|
|
return ID_MATCH_NONE;
|
|
}
|
|
/* try a equals check first */
|
|
if (equals_strcasecmp(this, other))
|
|
{
|
|
return ID_MATCH_PERFECT;
|
|
}
|
|
if (len == 0 || this->encoded.len < len)
|
|
{
|
|
return ID_MATCH_NONE;
|
|
}
|
|
|
|
/* check for single wildcard at the head of the string */
|
|
if (*other->encoded.ptr == '*')
|
|
{
|
|
/* single asterisk matches any string */
|
|
if (len-- == 1)
|
|
{ /* not better than ID_ANY */
|
|
return ID_MATCH_ANY;
|
|
}
|
|
if (strncasecmp(this->encoded.ptr + this->encoded.len - len,
|
|
other->encoded.ptr + 1, len) == 0)
|
|
{
|
|
return ID_MATCH_ONE_WILDCARD;
|
|
}
|
|
}
|
|
return ID_MATCH_NONE;
|
|
}
|
|
|
|
/**
|
|
* Special implementation of identification_t.matches for ID_ANY.
|
|
* ANY matches only another ANY, but nothing other
|
|
*/
|
|
static id_match_t matches_any(private_identification_t *this,
|
|
private_identification_t *other)
|
|
{
|
|
if (other->type == ID_ANY)
|
|
{
|
|
return ID_MATCH_ANY;
|
|
}
|
|
return ID_MATCH_NONE;
|
|
}
|
|
|
|
/**
|
|
* Special implementation of identification_t.matches for ID_DER_ASN1_DN
|
|
*/
|
|
static id_match_t matches_dn(private_identification_t *this,
|
|
private_identification_t *other)
|
|
{
|
|
int wc;
|
|
|
|
if (other->type == ID_ANY)
|
|
{
|
|
return ID_MATCH_ANY;
|
|
}
|
|
|
|
if (this->type == other->type)
|
|
{
|
|
if (match_dn(this->encoded, other->encoded, &wc))
|
|
{
|
|
return ID_MATCH_PERFECT - wc;
|
|
}
|
|
}
|
|
return ID_MATCH_NONE;
|
|
}
|
|
|
|
/**
|
|
* Described in header.
|
|
*/
|
|
int identification_printf_hook(char *dst, size_t len, printf_hook_spec_t *spec,
|
|
const void *const *args)
|
|
{
|
|
private_identification_t *this = *((private_identification_t**)(args[0]));
|
|
char buf[BUF_LEN];
|
|
chunk_t proper, buf_chunk = chunk_from_buf(buf);
|
|
|
|
if (this == NULL)
|
|
{
|
|
return print_in_hook(dst, len, "%*s", spec->width, "(null)");
|
|
}
|
|
|
|
switch (this->type)
|
|
{
|
|
case ID_ANY:
|
|
snprintf(buf, sizeof(buf), "%%any");
|
|
break;
|
|
case ID_IPV4_ADDR:
|
|
if (this->encoded.len < sizeof(struct in_addr) ||
|
|
inet_ntop(AF_INET, this->encoded.ptr, buf, sizeof(buf)) == NULL)
|
|
{
|
|
snprintf(buf, sizeof(buf), "(invalid ID_IPV4_ADDR)");
|
|
}
|
|
break;
|
|
case ID_IPV6_ADDR:
|
|
if (this->encoded.len < sizeof(struct in6_addr) ||
|
|
inet_ntop(AF_INET6, this->encoded.ptr, buf, INET6_ADDRSTRLEN) == NULL)
|
|
{
|
|
snprintf(buf, sizeof(buf), "(invalid ID_IPV6_ADDR)");
|
|
}
|
|
break;
|
|
case ID_FQDN:
|
|
case ID_RFC822_ADDR:
|
|
case ID_DER_ASN1_GN_URI:
|
|
case ID_IETF_ATTR_STRING:
|
|
sanitize_chunk(this->encoded, &proper);
|
|
snprintf(buf, sizeof(buf), "%.*s", proper.len, proper.ptr);
|
|
chunk_free(&proper);
|
|
break;
|
|
case ID_DER_ASN1_DN:
|
|
if (!dntoa(this->encoded, &buf_chunk))
|
|
{
|
|
snprintf(buf, sizeof(buf), "(invalid ID_DER_ASN1_DN)");
|
|
}
|
|
break;
|
|
case ID_DER_ASN1_GN:
|
|
snprintf(buf, sizeof(buf), "(ASN.1 general Name");
|
|
break;
|
|
case ID_KEY_ID:
|
|
if (sanitize_chunk(this->encoded, &proper))
|
|
{ /* fully printable, use ascii version */
|
|
snprintf(buf, sizeof(buf), "%.*s", proper.len, proper.ptr);
|
|
}
|
|
else
|
|
{ /* not printable, hex dump */
|
|
snprintf(buf, sizeof(buf), "%#B", &this->encoded);
|
|
}
|
|
chunk_free(&proper);
|
|
break;
|
|
case ID_PUBKEY_INFO_SHA1:
|
|
case ID_PUBKEY_SHA1:
|
|
case ID_CERT_DER_SHA1:
|
|
snprintf(buf, sizeof(buf), "%#B", &this->encoded);
|
|
break;
|
|
default:
|
|
snprintf(buf, sizeof(buf), "(unknown ID type: %d)", this->type);
|
|
break;
|
|
}
|
|
if (spec->minus)
|
|
{
|
|
return print_in_hook(dst, len, "%-*s", spec->width, buf);
|
|
}
|
|
return print_in_hook(dst, len, "%*s", spec->width, buf);
|
|
}
|
|
|
|
/**
|
|
* Implementation of identification_t.clone.
|
|
*/
|
|
static identification_t *clone_(private_identification_t *this)
|
|
{
|
|
private_identification_t *clone = identification_create();
|
|
|
|
clone->type = this->type;
|
|
if (this->encoded.len)
|
|
{
|
|
clone->encoded = chunk_clone(this->encoded);
|
|
}
|
|
clone->public.equals = this->public.equals;
|
|
clone->public.matches = this->public.matches;
|
|
|
|
return &clone->public;
|
|
}
|
|
|
|
/**
|
|
* Implementation of identification_t.destroy.
|
|
*/
|
|
static void destroy(private_identification_t *this)
|
|
{
|
|
chunk_free(&this->encoded);
|
|
free(this);
|
|
}
|
|
|
|
/**
|
|
* Generic constructor used for the other constructors.
|
|
*/
|
|
static private_identification_t *identification_create(void)
|
|
{
|
|
private_identification_t *this = malloc_thing(private_identification_t);
|
|
|
|
this->public.get_encoding = (chunk_t (*) (identification_t*))get_encoding;
|
|
this->public.get_type = (id_type_t (*) (identification_t*))get_type;
|
|
this->public.contains_wildcards = (bool (*) (identification_t *this))contains_wildcards;
|
|
this->public.clone = (identification_t* (*) (identification_t*))clone_;
|
|
this->public.destroy = (void (*) (identification_t*))destroy;
|
|
/* we use these as defaults, the may be overloaded for special ID types */
|
|
this->public.equals = (bool (*) (identification_t*,identification_t*))equals_binary;
|
|
this->public.matches = (id_match_t (*) (identification_t*,identification_t*))matches_binary;
|
|
|
|
this->encoded = chunk_empty;
|
|
|
|
return this;
|
|
}
|
|
|
|
/*
|
|
* Described in header.
|
|
*/
|
|
identification_t *identification_create_from_string(char *string)
|
|
{
|
|
private_identification_t *this = identification_create();
|
|
|
|
if (string == NULL)
|
|
{
|
|
string = "%any";
|
|
}
|
|
if (strchr(string, '=') != NULL)
|
|
{
|
|
/* 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
|
|
*/
|
|
if (atodn(string, &this->encoded) != SUCCESS)
|
|
{
|
|
this->type = ID_KEY_ID;
|
|
this->encoded = chunk_clone(chunk_create(string, strlen(string)));
|
|
return &this->public;
|
|
}
|
|
this->type = ID_DER_ASN1_DN;
|
|
this->public.equals = (bool (*) (identification_t*,identification_t*))equals_dn;
|
|
this->public.matches = (id_match_t (*) (identification_t*,identification_t*))matches_dn;
|
|
return &this->public;
|
|
}
|
|
else if (strchr(string, '@') == NULL)
|
|
{
|
|
if (streq(string, "%any")
|
|
|| streq(string, "%any6")
|
|
|| streq(string, "0.0.0.0")
|
|
|| streq(string, "*")
|
|
|| streq(string, "::")
|
|
|| streq(string, "0::0"))
|
|
{
|
|
/* any ID will be accepted */
|
|
this->type = ID_ANY;
|
|
this->public.matches = (id_match_t (*)
|
|
(identification_t*,identification_t*))matches_any;
|
|
return &this->public;
|
|
}
|
|
else
|
|
{
|
|
if (strchr(string, ':') == NULL)
|
|
{
|
|
/* try IPv4 */
|
|
struct in_addr address;
|
|
chunk_t chunk = {(void*)&address, sizeof(address)};
|
|
|
|
if (inet_pton(AF_INET, string, &address) <= 0)
|
|
{
|
|
/* not IPv4, mostly FQDN */
|
|
this->type = ID_FQDN;
|
|
this->encoded.ptr = strdup(string);
|
|
this->encoded.len = strlen(string);
|
|
this->public.matches = (id_match_t (*)
|
|
(identification_t*,identification_t*))matches_string;
|
|
this->public.equals = (bool (*)
|
|
(identification_t*,identification_t*))equals_strcasecmp;
|
|
return &this->public;
|
|
}
|
|
this->encoded = chunk_clone(chunk);
|
|
this->type = ID_IPV4_ADDR;
|
|
return &this->public;
|
|
}
|
|
else
|
|
{
|
|
/* try IPv6 */
|
|
struct in6_addr address;
|
|
chunk_t chunk = {(void*)&address, sizeof(address)};
|
|
|
|
if (inet_pton(AF_INET6, string, &address) <= 0)
|
|
{
|
|
this->type = ID_KEY_ID;
|
|
this->encoded = chunk_clone(chunk_create(string,
|
|
strlen(string)));
|
|
return &this->public;
|
|
}
|
|
this->encoded = chunk_clone(chunk);
|
|
this->type = ID_IPV6_ADDR;
|
|
return &this->public;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (*string == '@')
|
|
{
|
|
if (*(string + 1) == '#')
|
|
{
|
|
string += 2;
|
|
this->type = ID_KEY_ID;
|
|
this->encoded = chunk_from_hex(
|
|
chunk_create(string, strlen(string)), NULL);
|
|
return &this->public;
|
|
}
|
|
else
|
|
{
|
|
this->type = ID_FQDN;
|
|
this->encoded.ptr = strdup(string + 1);
|
|
this->encoded.len = strlen(string + 1);
|
|
this->public.matches = (id_match_t (*)
|
|
(identification_t*,identification_t*))matches_string;
|
|
this->public.equals = (bool (*)
|
|
(identification_t*,identification_t*))equals_strcasecmp;
|
|
return &this->public;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
this->type = ID_RFC822_ADDR;
|
|
this->encoded.ptr = strdup(string);
|
|
this->encoded.len = strlen(string);
|
|
this->public.matches = (id_match_t (*)
|
|
(identification_t*,identification_t*))matches_string;
|
|
this->public.equals = (bool (*)
|
|
(identification_t*,identification_t*))equals_strcasecmp;
|
|
return &this->public;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Described in header.
|
|
*/
|
|
identification_t *identification_create_from_encoding(id_type_t type, chunk_t encoded)
|
|
{
|
|
private_identification_t *this = identification_create();
|
|
|
|
this->type = type;
|
|
switch (type)
|
|
{
|
|
case ID_ANY:
|
|
this->public.matches = (id_match_t (*)
|
|
(identification_t*,identification_t*))matches_any;
|
|
break;
|
|
case ID_FQDN:
|
|
case ID_RFC822_ADDR:
|
|
this->public.matches = (id_match_t (*)
|
|
(identification_t*,identification_t*))matches_string;
|
|
this->public.equals = (bool (*)
|
|
(identification_t*,identification_t*))equals_strcasecmp;
|
|
break;
|
|
case ID_DER_ASN1_DN:
|
|
this->public.equals = (bool (*)
|
|
(identification_t*,identification_t*))equals_dn;
|
|
this->public.matches = (id_match_t (*)
|
|
(identification_t*,identification_t*))matches_dn;
|
|
break;
|
|
case ID_IPV4_ADDR:
|
|
case ID_IPV6_ADDR:
|
|
case ID_DER_ASN1_GN:
|
|
case ID_KEY_ID:
|
|
case ID_DER_ASN1_GN_URI:
|
|
case ID_PUBKEY_INFO_SHA1:
|
|
case ID_PUBKEY_SHA1:
|
|
case ID_CERT_DER_SHA1:
|
|
case ID_IETF_ATTR_STRING:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* apply encoded chunk */
|
|
if (type != ID_ANY)
|
|
{
|
|
this->encoded = chunk_clone(encoded);
|
|
}
|
|
return &(this->public);
|
|
}
|
|
|