strongswan/src/pluto/x509.c

464 lines
11 KiB
C

/* Support of X.509 certificates
* Copyright (C) 2000 Andreas Hess, Patric Lichtsteiner, Roger Wegmann
* Copyright (C) 2001 Marco Bertossa, Andreas Schleiss
* Copyright (C) 2002 Mario Strasser
* Copyright (C) 2000-2009 Andreas Steffen - 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.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <dirent.h>
#include <time.h>
#include <sys/types.h>
#include <freeswan.h>
#include <asn1/asn1.h>
#include <crypto/hashers/hasher.h>
#include <utils/enumerator.h>
#include <utils/identification.h>
#include "constants.h"
#include "defs.h"
#include "log.h"
#include "x509.h"
#include "crl.h"
#include "ca.h"
#include "certs.h"
#include "keys.h"
#include "whack.h"
#include "fetch.h"
#include "ocsp.h"
/**
* Check for equality between two key identifiers
*/
bool same_keyid(chunk_t a, chunk_t b)
{
if (a.ptr == NULL || b.ptr == NULL)
{
return FALSE;
}
return chunk_equals(a, b);
}
/**
* Stores a chained list of end certs and CA certs
*/
void store_x509certs(linked_list_t *certs, bool strict)
{
cert_t *x509cert, *cacerts = NULL;
certificate_t *cert;
enumerator_t *enumerator;
/* first extract CA certs, ignoring self-signed root CA certs */
enumerator = certs->create_enumerator(certs);
while (enumerator->enumerate(enumerator, &cert))
{
x509_t *x509 = (x509_t*)cert;
x509_flag_t flags;
flags = x509->get_flags(x509);
if (flags & X509_CA)
{
/* we don't accept self-signed CA certs */
if (flags & X509_SELF_SIGNED)
{
plog("self-signed cacert rejected");
}
else
{
/* insertion into temporary chain of candidate CA certs */
x509cert = malloc_thing(cert_t);
*x509cert = cert_empty;
x509cert->cert = cert->get_ref(cert);
x509cert->next = cacerts;
cacerts = x509cert;
}
}
}
enumerator->destroy(enumerator);
/* now verify the candidate CA certs */
while (cacerts)
{
cert_t *cert = cacerts;
cacerts = cacerts->next;
if (trust_authcert_candidate(cert, cacerts))
{
add_authcert(cert, X509_CA);
}
else
{
plog("intermediate cacert rejected");
cert_free(cert);
}
}
/* now verify the end certificates */
enumerator = certs->create_enumerator(certs);
while (enumerator->enumerate(enumerator, &cert))
{
time_t valid_until;
x509_t *x509 = (x509_t*)cert;
if (!(x509->get_flags(x509) & X509_CA))
{
x509cert = malloc_thing(cert_t);
*x509cert = cert_empty;
x509cert->cert = cert->get_ref(cert);
if (verify_x509cert(x509cert, strict, &valid_until))
{
DBG(DBG_CONTROL | DBG_PARSING,
DBG_log("public key validated")
)
add_public_key_from_cert(x509cert, valid_until, DAL_SIGNED);
}
else
{
plog("X.509 certificate rejected");
cert_free(x509cert);
}
}
}
enumerator->destroy(enumerator);
}
/**
* Check if a signature over binary blob is genuine
*/
bool x509_check_signature(chunk_t tbs, chunk_t sig, int algorithm,
certificate_t *issuer_cert)
{
bool success;
public_key_t *key;
signature_scheme_t scheme;
scheme = signature_scheme_from_oid(algorithm);
if (scheme == SIGN_UNKNOWN)
{
return FALSE;
}
key = issuer_cert->get_public_key(issuer_cert);
if (key == NULL)
{
return FALSE;
}
success = key->verify(key, scheme, tbs, sig);
key->destroy(key);
return success;
}
/**
* Build an ASN.1 encoded PKCS#1 signature over a binary blob
*/
chunk_t x509_build_signature(chunk_t tbs, int algorithm, private_key_t *key,
bool bit_string)
{
chunk_t signature;
signature_scheme_t scheme = signature_scheme_from_oid(algorithm);
if (scheme == SIGN_UNKNOWN || !key->sign(key, scheme, tbs, &signature))
{
return chunk_empty;
}
return (bit_string) ? asn1_bitstring("m", signature)
: asn1_wrap(ASN1_OCTET_STRING, "m", signature);
}
/**
* Verifies a X.509 certificate
*/
bool verify_x509cert(cert_t *cert, bool strict, time_t *until)
{
int pathlen, pathlen_constraint;
*until = 0;
for (pathlen = -1; pathlen <= X509_MAX_PATH_LEN; pathlen++)
{
certificate_t *certificate = cert->cert;
identification_t *subject = certificate->get_subject(certificate);
identification_t *issuer = certificate->get_issuer(certificate);
x509_t *x509 = (x509_t*)certificate;
chunk_t authKeyID = x509->get_authKeyIdentifier(x509);
cert_t *issuer_cert;
time_t notBefore, notAfter;
bool valid;
DBG(DBG_CONTROL,
DBG_log("subject: '%Y'", subject);
DBG_log("issuer: '%Y'", issuer);
if (authKeyID.ptr)
{
DBG_log("authkey: %#B", &authKeyID);
}
)
valid = certificate->get_validity(certificate, NULL,
&notBefore, &notAfter);
if (*until == UNDEFINED_TIME || notAfter < *until)
{
*until = notAfter;
}
if (!valid)
{
plog("certificate is invalid (valid from %T to %T)",
&notBefore, FALSE, &notAfter, FALSE);
return FALSE;
}
DBG(DBG_CONTROL,
DBG_log("certificate is valid")
)
lock_authcert_list("verify_x509cert");
issuer_cert = get_authcert(issuer, authKeyID, X509_CA);
if (issuer_cert == NULL)
{
plog("issuer cacert not found");
unlock_authcert_list("verify_x509cert");
return FALSE;
}
DBG(DBG_CONTROL,
DBG_log("issuer cacert found")
)
if (!certificate->issued_by(certificate, issuer_cert->cert))
{
plog("certificate signature is invalid");
unlock_authcert_list("verify_x509cert");
return FALSE;
}
DBG(DBG_CONTROL,
DBG_log("certificate signature is valid")
)
unlock_authcert_list("verify_x509cert");
/* check path length constraint */
pathlen_constraint = x509->get_pathLenConstraint(x509);
if (pathlen_constraint != X509_NO_PATH_LEN_CONSTRAINT &&
pathlen > pathlen_constraint)
{
plog("path length of %d violates constraint of %d",
pathlen, pathlen_constraint);
return FALSE;
}
/* check if cert is a self-signed root ca */
if (pathlen >= 0 && (x509->get_flags(x509) & X509_SELF_SIGNED))
{
DBG(DBG_CONTROL,
DBG_log("reached self-signed root ca with a path length of %d",
pathlen)
)
return TRUE;
}
else
{
time_t nextUpdate = *until;
time_t revocationDate = UNDEFINED_TIME;
crl_reason_t revocationReason = CRL_REASON_UNSPECIFIED;
/* first check certificate revocation using ocsp */
cert_status_t status = verify_by_ocsp(cert, &nextUpdate
, &revocationDate, &revocationReason);
/* if ocsp service is not available then fall back to crl */
if ((status == CERT_UNDEFINED)
|| (status == CERT_UNKNOWN && strict))
{
status = verify_by_crl(cert, &nextUpdate, &revocationDate
, &revocationReason);
}
switch (status)
{
case CERT_GOOD:
/* if status information is stale */
if (strict && nextUpdate < time(NULL))
{
DBG(DBG_CONTROL,
DBG_log("certificate is good but status is stale")
)
remove_x509_public_key(cert);
return FALSE;
}
DBG(DBG_CONTROL,
DBG_log("certificate is good")
)
/* with strict crl policy the public key must have the same
* lifetime as the validity of the ocsp status or crl lifetime
*/
if (strict && nextUpdate < *until)
{
*until = nextUpdate;
}
break;
case CERT_REVOKED:
plog("certificate was revoked on %T, reason: %N"
, &revocationDate, TRUE
, crl_reason_names, revocationReason);
remove_x509_public_key(cert);
return FALSE;
case CERT_UNKNOWN:
case CERT_UNDEFINED:
default:
plog("certificate status unknown");
if (strict)
{
remove_x509_public_key(cert);
return FALSE;
}
break;
}
}
/* go up one step in the trust chain */
cert = issuer_cert;
}
plog("maximum path length of %d exceeded", X509_MAX_PATH_LEN);
return FALSE;
}
/**
* List all X.509 certs in a chained list
*/
void list_x509cert_chain(const char *caption, cert_t* cert,
x509_flag_t flags, bool utc)
{
bool first = TRUE;
time_t now;
/* determine the current time */
time(&now);
while (cert)
{
certificate_t *certificate = cert->cert;
x509_t *x509 = (x509_t*)certificate;
if (certificate->get_type(certificate) == CERT_X509 &&
(flags == X509_NONE || (flags & x509->get_flags(x509))))
{
enumerator_t *enumerator;
char buf[BUF_LEN];
char *pos = buf;
int len = BUF_LEN, pathlen;
bool first_altName = TRUE;
identification_t *id;
time_t notBefore, notAfter;
public_key_t *key;
chunk_t serial, keyid, subjkey, authkey;
if (first)
{
whack_log(RC_COMMENT, " ");
whack_log(RC_COMMENT, "List of X.509 %s Certificates:", caption);
first = FALSE;
}
whack_log(RC_COMMENT, " ");
enumerator = x509->create_subjectAltName_enumerator(x509);
while (enumerator->enumerate(enumerator, &id))
{
int written;
if (first_altName)
{
written = snprintf(pos, len, "%Y", id);
first_altName = FALSE;
}
else
{
written = snprintf(pos, len, ", %Y", id);
}
if (written < 0 || written >= len)
{
break;
}
pos += written;
len -= written;
}
enumerator->destroy(enumerator);
if (!first_altName)
{
whack_log(RC_COMMENT, " altNames: %s", buf);
}
whack_log(RC_COMMENT, " subject: \"%Y\"",
certificate->get_subject(certificate));
whack_log(RC_COMMENT, " issuer: \"%Y\"",
certificate->get_issuer(certificate));
serial = x509->get_serial(x509);
whack_log(RC_COMMENT, " serial: %#B", &serial);
/* list validity */
certificate->get_validity(certificate, &now, &notBefore, &notAfter);
whack_log(RC_COMMENT, " validity: not before %T %s",
&notBefore, utc,
(notBefore < now)?"ok":"fatal (not valid yet)");
whack_log(RC_COMMENT, " not after %T %s",
&notAfter, utc,
check_expiry(notAfter, CA_CERT_WARNING_INTERVAL, TRUE));
key = certificate->get_public_key(certificate);
if (key)
{
whack_log(RC_COMMENT, " pubkey: %N %4d bits%s",
key_type_names, key->get_type(key),
key->get_keysize(key),
cert->smartcard ? ", on smartcard" :
(has_private_key(cert)? ", has private key" : ""));
if (key->get_fingerprint(key, KEYID_PUBKEY_INFO_SHA1, &keyid))
{
whack_log(RC_COMMENT, " keyid: %#B", &keyid);
}
if (key->get_fingerprint(key, KEYID_PUBKEY_SHA1, &subjkey))
{
whack_log(RC_COMMENT, " subjkey: %#B", &subjkey);
}
key->destroy(key);
}
/* list optional authorityKeyIdentifier */
authkey = x509->get_authKeyIdentifier(x509);
if (authkey.ptr)
{
whack_log(RC_COMMENT, " authkey: %#B", &authkey);
}
/* list optional pathLenConstraint */
pathlen = x509->get_pathLenConstraint(x509);
if (pathlen != X509_NO_PATH_LEN_CONSTRAINT)
{
whack_log(RC_COMMENT, " pathlen: %d", pathlen);
}
}
cert = cert->next;
}
}