Moved CRL/OCSP checking to a dedicated plugin called revocation
authorMartin Willi <martin@revosec.ch>
Mon, 5 Jul 2010 13:26:35 +0000 (15:26 +0200)
committerMartin Willi <martin@revosec.ch>
Tue, 13 Jul 2010 08:26:07 +0000 (10:26 +0200)
configure.in
src/libstrongswan/Makefile.am
src/libstrongswan/credentials/credential_manager.c
src/libstrongswan/plugins/revocation/Makefile.am [new file with mode: 0644]
src/libstrongswan/plugins/revocation/revocation_plugin.c [new file with mode: 0644]
src/libstrongswan/plugins/revocation/revocation_plugin.h [new file with mode: 0644]
src/libstrongswan/plugins/revocation/revocation_validator.c [new file with mode: 0644]
src/libstrongswan/plugins/revocation/revocation_validator.h [new file with mode: 0644]

index 5ddd062..a7a3171 100644 (file)
@@ -78,6 +78,7 @@ ARG_DISBL_SET([fips-prf],       [disable FIPS PRF software implementation plugin
 ARG_DISBL_SET([gmp],            [disable GNU MP (libgmp) based crypto implementation plugin.])
 ARG_DISBL_SET([random],         [disable RNG implementation on top of /dev/(u)random.])
 ARG_DISBL_SET([x509],           [disable X509 certificate implementation plugin.])
+ARG_DISBL_SET([revocation],     [disable X509 CRL/OCSP revocation check plugin.])
 ARG_DISBL_SET([pubkey],         [disable RAW public key support plugin.])
 ARG_DISBL_SET([pkcs1],          [disable PKCS1 key decoding plugin.])
 ARG_DISBL_SET([pgp],            [disable PGP key decoding plugin.])
@@ -707,6 +708,9 @@ if test x$x509 = xtrue; then
        libstrongswan_plugins=${libstrongswan_plugins}" x509"
        pluto_plugins=${pluto_plugins}" x509"
 fi
+if test x$revocation = xtrue; then
+       libstrongswan_plugins=${libstrongswan_plugins}" revocation"
+fi
 if test x$pubkey = xtrue; then
        libstrongswan_plugins=${libstrongswan_plugins}" pubkey"
        pluto_plugins=${pluto_plugins}" pubkey"
@@ -803,6 +807,7 @@ AM_CONDITIONAL(USE_FIPS_PRF, test x$fips_prf = xtrue)
 AM_CONDITIONAL(USE_GMP, test x$gmp = xtrue)
 AM_CONDITIONAL(USE_RANDOM, test x$random = xtrue)
 AM_CONDITIONAL(USE_X509, test x$x509 = xtrue)
+AM_CONDITIONAL(USE_REVOCATION, test x$revocation = xtrue)
 AM_CONDITIONAL(USE_PUBKEY, test x$pubkey = xtrue)
 AM_CONDITIONAL(USE_PKCS1, test x$pkcs1 = xtrue)
 AM_CONDITIONAL(USE_PGP, test x$pgp = xtrue)
@@ -928,6 +933,7 @@ AC_OUTPUT(
        src/libstrongswan/plugins/hmac/Makefile
        src/libstrongswan/plugins/xcbc/Makefile
        src/libstrongswan/plugins/x509/Makefile
+       src/libstrongswan/plugins/revocation/Makefile
        src/libstrongswan/plugins/pubkey/Makefile
        src/libstrongswan/plugins/pkcs1/Makefile
        src/libstrongswan/plugins/pgp/Makefile
index d90773d..0ef26c8 100644 (file)
@@ -209,6 +209,13 @@ if MONOLITHIC
 endif
 endif
 
+if USE_REVOCATION
+  SUBDIRS += plugins/revocation
+if MONOLITHIC
+  libstrongswan_la_LIBADD += plugins/revocation/libstrongswan-revocation.la
+endif
+endif
+
 if USE_PUBKEY
   SUBDIRS += plugins/pubkey
 if MONOLITHIC
index 8df232b..3b671c7 100644 (file)
 #include <threading/thread_value.h>
 #include <threading/mutex.h>
 #include <threading/rwlock.h>
-#include <selectors/traffic_selector.h>
 #include <utils/linked_list.h>
 #include <credentials/sets/cert_cache.h>
 #include <credentials/sets/auth_cfg_wrapper.h>
-#include <credentials/sets/ocsp_response_wrapper.h>
 #include <credentials/certificates/x509.h>
-#include <credentials/certificates/crl.h>
-#include <credentials/certificates/ocsp_request.h>
-#include <credentials/certificates/ocsp_response.h>
 
 /**
  * Maximum length of a certificate trust chain
@@ -452,492 +447,6 @@ static void cache_queue(private_credential_manager_t *this)
 }
 
 /**
- * forward declaration
- */
-static enumerator_t *create_trusted_enumerator(private_credential_manager_t *this,
-                                       key_type_t type, identification_t *id, bool online);
-
-/**
- * Do an OCSP request
- */
-static certificate_t *fetch_ocsp(private_credential_manager_t *this, char *url,
-                                                                certificate_t *subject, certificate_t *issuer)
-{
-       certificate_t *request, *response;
-       chunk_t send, receive;
-
-       /* TODO: requestor name, signature */
-       request = lib->creds->create(lib->creds,
-                                               CRED_CERTIFICATE, CERT_X509_OCSP_REQUEST,
-                                               BUILD_CA_CERT, issuer,
-                                               BUILD_CERT, subject, BUILD_END);
-       if (!request)
-       {
-               DBG1(DBG_CFG, "generating ocsp request failed");
-               return NULL;
-       }
-
-       send = request->get_encoding(request);
-       request->destroy(request);
-
-       DBG1(DBG_CFG, "  requesting ocsp status from '%s' ...", url);
-       if (lib->fetcher->fetch(lib->fetcher, url, &receive,
-                                                       FETCH_REQUEST_DATA, send,
-                                                       FETCH_REQUEST_TYPE, "application/ocsp-request",
-                                                       FETCH_END) != SUCCESS)
-       {
-               DBG1(DBG_CFG, "ocsp request to %s failed", url);
-               chunk_free(&send);
-               return NULL;
-       }
-       chunk_free(&send);
-
-       response = lib->creds->create(lib->creds,
-                                                                 CRED_CERTIFICATE, CERT_X509_OCSP_RESPONSE,
-                                                                 BUILD_BLOB_ASN1_DER, receive, BUILD_END);
-       chunk_free(&receive);
-       if (!response)
-       {
-               DBG1(DBG_CFG, "parsing ocsp response failed");
-               return NULL;
-       }
-       return response;
-}
-
-/**
- * check the signature of an OCSP response
- */
-static bool verify_ocsp(private_credential_manager_t *this,
-                                               ocsp_response_t *response)
-{
-       certificate_t *issuer, *subject;
-       identification_t *responder;
-       ocsp_response_wrapper_t *wrapper;
-       enumerator_t *enumerator;
-       bool verified = FALSE;
-
-       wrapper = ocsp_response_wrapper_create((ocsp_response_t*)response);
-       add_local_set(this, &wrapper->set);
-
-       subject = &response->certificate;
-       responder = subject->get_issuer(subject);
-       enumerator = create_trusted_enumerator(this, KEY_ANY, responder, FALSE);
-       while (enumerator->enumerate(enumerator, &issuer, NULL))
-       {
-               if (this->cache->issued_by(this->cache, subject, issuer))
-               {
-                       DBG1(DBG_CFG, "  ocsp response correctly signed by \"%Y\"",
-                                                        issuer->get_subject(issuer));
-                       verified = TRUE;
-                       break;
-               }
-       }
-       enumerator->destroy(enumerator);
-
-       remove_local_set(this, &wrapper->set);
-       wrapper->destroy(wrapper);
-       return verified;
-}
-
-/**
- * Get the better of two OCSP responses, and check for usable OCSP info
- */
-static certificate_t *get_better_ocsp(private_credential_manager_t *this,
-                                                                         certificate_t *cand, certificate_t *best,
-                                                                         x509_t *subject, x509_t *issuer,
-                                                                         cert_validation_t *valid, bool cache)
-{
-       ocsp_response_t *response;
-       time_t revocation, this_update, next_update, valid_until;
-       crl_reason_t reason;
-       bool revoked = FALSE;
-
-       response = (ocsp_response_t*)cand;
-
-       /* check ocsp signature */
-       if (!verify_ocsp(this, response))
-       {
-               DBG1(DBG_CFG, "ocsp response verification failed");
-               cand->destroy(cand);
-               return best;
-       }
-       /* check if response contains our certificate */
-       switch (response->get_status(response, subject, issuer, &revocation, &reason,
-                                                                &this_update, &next_update))
-       {
-               case VALIDATION_REVOKED:
-                       /* subject has been revoked by a valid OCSP response */
-                       DBG1(DBG_CFG, "certificate was revoked on %T, reason: %N",
-                                                 &revocation, TRUE, crl_reason_names, reason);
-                       revoked = TRUE;
-                       break;
-               case VALIDATION_GOOD:
-                       /* results in either good or stale */
-                       break;
-               default:
-               case VALIDATION_FAILED:
-                       /* candidate unusable, does not contain our cert */
-                       DBG1(DBG_CFG, "  ocsp response contains no status on our certificate");
-                       cand->destroy(cand);
-                       return best;
-       }
-
-       /* select the better of the two responses */
-       if (best == NULL || certificate_is_newer(cand, best))
-       {
-               DESTROY_IF(best);
-               best = cand;
-               if (best->get_validity(best, NULL, NULL, &valid_until))
-               {
-                       DBG1(DBG_CFG, "  ocsp response is valid: until %T",
-                                                        &valid_until, FALSE);
-                       *valid = VALIDATION_GOOD;
-                       if (cache)
-                       {       /* cache non-stale only, stale certs get refetched */
-                               cache_cert(this, best);
-                       }
-               }
-               else
-               {
-                       DBG1(DBG_CFG, "  ocsp response is stale: since %T",
-                                                        &valid_until, FALSE);
-                       *valid = VALIDATION_STALE;
-               }
-       }
-       else
-       {
-               *valid = VALIDATION_STALE;
-               cand->destroy(cand);
-       }
-       if (revoked)
-       {       /* revoked always counts, even if stale */
-               *valid = VALIDATION_REVOKED;
-       }
-       return best;
-}
-
-/**
- * validate a x509 certificate using OCSP
- */
-static cert_validation_t check_ocsp(private_credential_manager_t *this,
-                                                                       x509_t *subject, x509_t *issuer,
-                                                                       auth_cfg_t *auth)
-{
-       enumerator_t *enumerator;
-       cert_validation_t valid = VALIDATION_SKIPPED;
-       certificate_t *best = NULL, *current;
-       identification_t *keyid = NULL;
-       public_key_t *public;
-       chunk_t chunk;
-       char *uri = NULL;
-
-       /** lookup cache for valid OCSP responses */
-       enumerator = create_cert_enumerator(this, CERT_X509_OCSP_RESPONSE,
-                                                                               KEY_ANY, NULL, FALSE);
-       while (enumerator->enumerate(enumerator, &current))
-       {
-               current->get_ref(current);
-               best = get_better_ocsp(this, current, best, subject, issuer,
-                                                          &valid, FALSE);
-               if (best && valid != VALIDATION_STALE)
-               {
-                       DBG1(DBG_CFG, "  using cached ocsp response");
-                       break;
-               }
-       }
-       enumerator->destroy(enumerator);
-
-       /* derive the authorityKeyIdentifier from the issuer's public key */
-       current = &issuer->interface;
-       public = current->get_public_key(current);
-       if (public && public->get_fingerprint(public, KEY_ID_PUBKEY_SHA1, &chunk))
-       {
-               keyid = identification_create_from_encoding(ID_KEY_ID, chunk);
-       }
-       /** fetch from configured OCSP responder URLs */
-       if (keyid && valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED)
-       {
-               enumerator = create_cdp_enumerator(this, CERT_X509_OCSP_RESPONSE, keyid);
-               while (enumerator->enumerate(enumerator, &uri))
-               {
-                       current = fetch_ocsp(this, uri, &subject->interface,
-                                                                &issuer->interface);
-                       if (current)
-                       {
-                               best = get_better_ocsp(this, current, best, subject, issuer,
-                                                                          &valid, TRUE);
-                               if (best && valid != VALIDATION_STALE)
-                               {
-                                       break;
-                               }
-                       }
-               }
-               enumerator->destroy(enumerator);
-       }
-       DESTROY_IF(public);
-       DESTROY_IF(keyid);
-
-       /* fallback to URL fetching from subject certificate's URIs */
-       if (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED)
-       {
-               enumerator = subject->create_ocsp_uri_enumerator(subject);
-               while (enumerator->enumerate(enumerator, &uri))
-               {
-                       current = fetch_ocsp(this, uri, &subject->interface,
-                                                                &issuer->interface);
-                       if (current)
-                       {
-                               best = get_better_ocsp(this, current, best, subject, issuer,
-                                                                          &valid, TRUE);
-                               if (best && valid != VALIDATION_STALE)
-                               {
-                                       break;
-                               }
-                       }
-               }
-               enumerator->destroy(enumerator);
-       }
-       /* an uri was found, but no result. switch validation state to failed */
-       if (valid == VALIDATION_SKIPPED && uri)
-       {
-               valid = VALIDATION_FAILED;
-       }
-       if (auth)
-       {
-               auth->add(auth, AUTH_RULE_OCSP_VALIDATION, valid);
-               if (valid == VALIDATION_GOOD)
-               {       /* successful OCSP check fulfills also CRL constraint */
-                       auth->add(auth, AUTH_RULE_CRL_VALIDATION, VALIDATION_GOOD);
-               }
-       }
-       DESTROY_IF(best);
-       return valid;
-}
-
-/**
- * fetch a CRL from an URL
- */
-static certificate_t* fetch_crl(private_credential_manager_t *this, char *url)
-{
-       certificate_t *crl;
-       chunk_t chunk;
-
-       DBG1(DBG_CFG, "  fetching crl from '%s' ...", url);
-       if (lib->fetcher->fetch(lib->fetcher, url, &chunk, FETCH_END) != SUCCESS)
-       {
-               DBG1(DBG_CFG, "crl fetching failed");
-               return NULL;
-       }
-       crl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
-                                                        BUILD_BLOB_ASN1_DER, chunk, BUILD_END);
-       chunk_free(&chunk);
-       if (!crl)
-       {
-               DBG1(DBG_CFG, "crl fetched successfully but parsing failed");
-               return NULL;
-       }
-       return crl;
-}
-
-/**
- * check the signature of an CRL
- */
-static bool verify_crl(private_credential_manager_t *this, certificate_t *crl)
-{
-       certificate_t *issuer;
-       enumerator_t *enumerator;
-       bool verified = FALSE;
-
-       enumerator = create_trusted_enumerator(this, KEY_ANY, crl->get_issuer(crl),
-                                                                                  FALSE);
-       while (enumerator->enumerate(enumerator, &issuer, NULL))
-       {
-               if (this->cache->issued_by(this->cache, crl, issuer))
-               {
-                       DBG1(DBG_CFG, "  crl correctly signed by \"%Y\"",
-                                                  issuer->get_subject(issuer));
-                       verified = TRUE;
-                       break;
-               }
-       }
-       enumerator->destroy(enumerator);
-
-       return verified;
-}
-
-/**
- * Get the better of two CRLs, and check for usable CRL info
- */
-static certificate_t *get_better_crl(private_credential_manager_t *this,
-                                                                        certificate_t *cand, certificate_t *best,
-                                                                        x509_t *subject, x509_t *issuer,
-                                                                        cert_validation_t *valid, bool cache)
-{
-       enumerator_t *enumerator;
-       time_t revocation, valid_until;
-       crl_reason_t reason;
-       chunk_t serial;
-       crl_t *crl;
-
-       /* check CRL signature */
-       if (!verify_crl(this, cand))
-       {
-               DBG1(DBG_CFG, "crl response verification failed");
-               cand->destroy(cand);
-               return best;
-       }
-
-       crl = (crl_t*)cand;
-       enumerator = crl->create_enumerator(crl);
-       while (enumerator->enumerate(enumerator, &serial, &revocation, &reason))
-       {
-               if (chunk_equals(serial, subject->get_serial(subject)))
-               {
-                       DBG1(DBG_CFG, "certificate was revoked on %T, reason: %N",
-                                &revocation, TRUE, crl_reason_names, reason);
-                       *valid = VALIDATION_REVOKED;
-                       enumerator->destroy(enumerator);
-                       DESTROY_IF(best);
-                       return cand;
-               }
-       }
-       enumerator->destroy(enumerator);
-
-       /* select the better of the two CRLs */
-       if (best == NULL || crl_is_newer(crl, (crl_t*)best))
-       {
-               DESTROY_IF(best);
-               best = cand;
-               if (best->get_validity(best, NULL, NULL, &valid_until))
-               {
-                       DBG1(DBG_CFG, "  crl is valid: until %T", &valid_until, FALSE);
-                       *valid = VALIDATION_GOOD;
-                       if (cache)
-                       {       /* we cache non-stale crls only, as a stale crls are refetched */
-                               cache_cert(this, best);
-                       }
-               }
-               else
-               {
-                       DBG1(DBG_CFG, "  crl is stale: since %T", &valid_until, FALSE);
-                       *valid = VALIDATION_STALE;
-               }
-       }
-       else
-       {
-               *valid = VALIDATION_STALE;
-               cand->destroy(cand);
-       }
-       return best;
-}
-
-/**
- * validate a x509 certificate using CRL
- */
-static cert_validation_t check_crl(private_credential_manager_t *this,
-                                                                  x509_t *subject, x509_t *issuer,
-                                                                  auth_cfg_t *auth)
-{
-       cert_validation_t valid = VALIDATION_SKIPPED;
-       identification_t *keyid = NULL;
-       certificate_t *best = NULL;
-       certificate_t *current;
-       public_key_t *public;
-       enumerator_t *enumerator;
-       chunk_t chunk;
-       char *uri = NULL;
-
-       /* derive the authorityKeyIdentifier from the issuer's public key */
-       current = &issuer->interface;
-       public = current->get_public_key(current);
-       if (public && public->get_fingerprint(public, KEY_ID_PUBKEY_SHA1, &chunk))
-       {
-               keyid = identification_create_from_encoding(ID_KEY_ID, chunk);
-
-               /* find a cached crl by authorityKeyIdentifier */
-               enumerator = create_cert_enumerator(this, CERT_X509_CRL, KEY_ANY,
-                                                                                       keyid, FALSE);
-               while (enumerator->enumerate(enumerator, &current))
-               {
-                       current->get_ref(current);
-                       best = get_better_crl(this, current, best, subject, issuer,
-                                                                 &valid, FALSE);
-                       if (best && valid != VALIDATION_STALE)
-                       {
-                               DBG1(DBG_CFG, "  using cached crl");
-                               break;
-                       }
-               }
-               enumerator->destroy(enumerator);
-
-               /* fallback to fetching crls from credential sets cdps */
-               if (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED)
-               {
-                       enumerator = create_cdp_enumerator(this, CERT_X509_CRL, keyid);
-
-                       while (enumerator->enumerate(enumerator, &uri))
-                       {
-                               current = fetch_crl(this, uri);
-                               if (current)
-                               {
-                                       best = get_better_crl(this, current, best, subject, issuer,
-                                                                                 &valid, TRUE);
-                                       if (best && valid != VALIDATION_STALE)
-                                       {
-                                               break;
-                                       }
-                               }
-                       }
-                       enumerator->destroy(enumerator);
-               }
-               keyid->destroy(keyid);
-       }
-       DESTROY_IF(public);
-
-       /* fallback to fetching crls from cdps from subject's certificate */
-       if (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED)
-       {
-               enumerator = subject->create_crl_uri_enumerator(subject);
-
-               while (enumerator->enumerate(enumerator, &uri))
-               {
-                       current = fetch_crl(this, uri);
-                       if (current)
-                       {
-                               best = get_better_crl(this, current, best, subject, issuer,
-                                                                         &valid, TRUE);
-                               if (best && valid != VALIDATION_STALE)
-                               {
-                                       break;
-                               }
-                       }
-               }
-               enumerator->destroy(enumerator);
-       }
-
-       /* an uri was found, but no result. switch validation state to failed */
-       if (valid == VALIDATION_SKIPPED && uri)
-       {
-               valid = VALIDATION_FAILED;
-       }
-       if (auth)
-       {
-               if (valid == VALIDATION_SKIPPED)
-               {       /* if we skipped CRL validation, we use the result of OCSP for
-                        * constraint checking */
-                       auth->add(auth, AUTH_RULE_CRL_VALIDATION,
-                                         auth->get(auth, AUTH_RULE_OCSP_VALIDATION));
-               }
-               else
-               {
-                       auth->add(auth, AUTH_RULE_CRL_VALIDATION, valid);
-               }
-       }
-       DESTROY_IF(best);
-       return valid;
-}
-
-/**
  * check a certificate for its lifetime
  */
 static bool check_certificate(private_credential_manager_t *this,
@@ -976,46 +485,6 @@ static bool check_certificate(private_credential_manager_t *this,
                                 pathlen, pathlen_constraint);
                        return FALSE;
                }
-
-               if (online)
-               {
-                       DBG1(DBG_CFG, "checking certificate status of \"%Y\"",
-                                                  subject->get_subject(subject));
-                       switch (check_ocsp(this, (x509_t*)subject, (x509_t*)issuer, auth))
-                       {
-                               case VALIDATION_GOOD:
-                                       DBG1(DBG_CFG, "certificate status is good");
-                                       return TRUE;
-                               case VALIDATION_REVOKED:
-                                       /* has already been logged */
-                                       return FALSE;
-                               case VALIDATION_SKIPPED:
-                                       DBG2(DBG_CFG, "ocsp check skipped, no ocsp found");
-                                       break;
-                               case VALIDATION_STALE:
-                                       DBG1(DBG_CFG, "ocsp information stale, fallback to crl");
-                                       break;
-                               case VALIDATION_FAILED:
-                                       DBG1(DBG_CFG, "ocsp check failed, fallback to crl");
-                                       break;
-                       }
-                       switch (check_crl(this, (x509_t*)subject, (x509_t*)issuer, auth))
-                       {
-                               case VALIDATION_GOOD:
-                                       DBG1(DBG_CFG, "certificate status is good");
-                                       return TRUE;
-                               case VALIDATION_REVOKED:
-                                       /* has already been logged */
-                                       return FALSE;
-                               case VALIDATION_FAILED:
-                               case VALIDATION_SKIPPED:
-                                       DBG1(DBG_CFG, "certificate status is not available");
-                                       break;
-                               case VALIDATION_STALE:
-                                       DBG1(DBG_CFG, "certificate status is unknown, crl is stale");
-                                       break;
-                       }
-               }
        }
 
        enumerator = this->validators->create_enumerator(this->validators);
diff --git a/src/libstrongswan/plugins/revocation/Makefile.am b/src/libstrongswan/plugins/revocation/Makefile.am
new file mode 100644 (file)
index 0000000..fb6d019
--- /dev/null
@@ -0,0 +1,16 @@
+
+INCLUDES = -I$(top_srcdir)/src/libstrongswan
+
+AM_CFLAGS = -rdynamic
+
+if MONOLITHIC
+noinst_LTLIBRARIES = libstrongswan-revocation.la
+else
+plugin_LTLIBRARIES = libstrongswan-revocation.la
+endif
+
+libstrongswan_revocation_la_SOURCES = \
+       revocation_plugin.h revocation_plugin.c \
+       revocation_validator.h revocation_validator.c
+
+libstrongswan_revocation_la_LDFLAGS = -module -avoid-version
diff --git a/src/libstrongswan/plugins/revocation/revocation_plugin.c b/src/libstrongswan/plugins/revocation/revocation_plugin.c
new file mode 100644 (file)
index 0000000..d352a95
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2010 Martin Willi
+ * Copyright (C) 2010 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 <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 "revocation_plugin.h"
+
+#include <library.h>
+#include "revocation_validator.h"
+
+typedef struct private_revocation_plugin_t private_revocation_plugin_t;
+
+/**
+ * private data of revocation_plugin
+ */
+struct private_revocation_plugin_t {
+
+       /**
+        * public functions
+        */
+       revocation_plugin_t public;
+
+       /**
+        * Validator implementation instance.
+        */
+       revocation_validator_t *validator;
+};
+
+METHOD(plugin_t, destroy, void,
+       private_revocation_plugin_t *this)
+{
+       lib->credmgr->remove_validator(lib->credmgr, &this->validator->validator);
+       this->validator->destroy(this->validator);
+       free(this);
+}
+
+/*
+ * see header file
+ */
+plugin_t *revocation_plugin_create()
+{
+       private_revocation_plugin_t *this;
+
+       INIT(this,
+               .public.plugin.destroy = _destroy,
+               .validator = revocation_validator_create(),
+       );
+       lib->credmgr->add_validator(lib->credmgr, &this->validator->validator);
+
+       return &this->public.plugin;
+}
diff --git a/src/libstrongswan/plugins/revocation/revocation_plugin.h b/src/libstrongswan/plugins/revocation/revocation_plugin.h
new file mode 100644 (file)
index 0000000..fb886d5
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2010 Martin Willi
+ * Copyright (C) 2010 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 <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.
+ */
+
+/**
+ * @defgroup revocation revocation
+ * @ingroup plugins
+ *
+ * @defgroup revocation_plugin revocation_plugin
+ * @{ @ingroup revocation
+ */
+
+#ifndef REVOCATION_PLUGIN_H_
+#define REVOCATION_PLUGIN_H_
+
+#include <plugins/plugin.h>
+
+typedef struct revocation_plugin_t revocation_plugin_t;
+
+/**
+ * X509 certificate revocation support using CRL and OCSP.
+ */
+struct revocation_plugin_t {
+
+       /**
+        * Implements plugin_t. interface.
+        */
+       plugin_t plugin;
+};
+
+#endif /** REVOCATION_PLUGIN_H_ @}*/
diff --git a/src/libstrongswan/plugins/revocation/revocation_validator.c b/src/libstrongswan/plugins/revocation/revocation_validator.c
new file mode 100644 (file)
index 0000000..e7802d4
--- /dev/null
@@ -0,0 +1,582 @@
+/*
+ * Copyright (C) 2010 Martin Willi
+ * Copyright (C) 2010 revosec AG
+ * Copyright (C) 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 "revocation_validator.h"
+
+#include <debug.h>
+#include <credentials/certificates/x509.h>
+#include <credentials/certificates/crl.h>
+#include <credentials/certificates/ocsp_request.h>
+#include <credentials/certificates/ocsp_response.h>
+#include <credentials/sets/ocsp_response_wrapper.h>
+#include <selectors/traffic_selector.h>
+
+typedef struct private_revocation_validator_t private_revocation_validator_t;
+
+/**
+ * Private data of an revocation_validator_t object.
+ */
+struct private_revocation_validator_t {
+
+       /**
+        * Public revocation_validator_t interface.
+        */
+       revocation_validator_t public;
+};
+
+/**
+ * Do an OCSP request
+ */
+static certificate_t *fetch_ocsp(char *url, certificate_t *subject,
+                                                                certificate_t *issuer)
+{
+       certificate_t *request, *response;
+       chunk_t send, receive;
+
+       /* TODO: requestor name, signature */
+       request = lib->creds->create(lib->creds,
+                                               CRED_CERTIFICATE, CERT_X509_OCSP_REQUEST,
+                                               BUILD_CA_CERT, issuer,
+                                               BUILD_CERT, subject, BUILD_END);
+       if (!request)
+       {
+               DBG1(DBG_CFG, "generating ocsp request failed");
+               return NULL;
+       }
+
+       send = request->get_encoding(request);
+       request->destroy(request);
+
+       DBG1(DBG_CFG, "  requesting ocsp status from '%s' ...", url);
+       if (lib->fetcher->fetch(lib->fetcher, url, &receive,
+                                                       FETCH_REQUEST_DATA, send,
+                                                       FETCH_REQUEST_TYPE, "application/ocsp-request",
+                                                       FETCH_END) != SUCCESS)
+       {
+               DBG1(DBG_CFG, "ocsp request to %s failed", url);
+               chunk_free(&send);
+               return NULL;
+       }
+       chunk_free(&send);
+
+       response = lib->creds->create(lib->creds,
+                                                                 CRED_CERTIFICATE, CERT_X509_OCSP_RESPONSE,
+                                                                 BUILD_BLOB_ASN1_DER, receive, BUILD_END);
+       chunk_free(&receive);
+       if (!response)
+       {
+               DBG1(DBG_CFG, "parsing ocsp response failed");
+               return NULL;
+       }
+       return response;
+}
+
+/**
+ * check the signature of an OCSP response
+ */
+static bool verify_ocsp(ocsp_response_t *response)
+{
+       certificate_t *issuer, *subject;
+       identification_t *responder;
+       ocsp_response_wrapper_t *wrapper;
+       enumerator_t *enumerator;
+       bool verified = FALSE;
+
+       wrapper = ocsp_response_wrapper_create((ocsp_response_t*)response);
+       lib->credmgr->add_local_set(lib->credmgr, &wrapper->set);
+
+       subject = &response->certificate;
+       responder = subject->get_issuer(subject);
+       enumerator = lib->credmgr->create_trusted_enumerator(lib->credmgr,
+                                                                                                       KEY_ANY, responder, FALSE);
+       while (enumerator->enumerate(enumerator, &issuer, NULL))
+       {
+               if (lib->credmgr->issued_by(lib->credmgr, subject, issuer))
+               {
+                       DBG1(DBG_CFG, "  ocsp response correctly signed by \"%Y\"",
+                                                        issuer->get_subject(issuer));
+                       verified = TRUE;
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+
+       lib->credmgr->remove_local_set(lib->credmgr, &wrapper->set);
+       wrapper->destroy(wrapper);
+       return verified;
+}
+
+/**
+ * Get the better of two OCSP responses, and check for usable OCSP info
+ */
+static certificate_t *get_better_ocsp(certificate_t *cand, certificate_t *best,
+               x509_t *subject, x509_t *issuer, cert_validation_t *valid, bool cache)
+{
+       ocsp_response_t *response;
+       time_t revocation, this_update, next_update, valid_until;
+       crl_reason_t reason;
+       bool revoked = FALSE;
+
+       response = (ocsp_response_t*)cand;
+
+       /* check ocsp signature */
+       if (!verify_ocsp(response))
+       {
+               DBG1(DBG_CFG, "ocsp response verification failed");
+               cand->destroy(cand);
+               return best;
+       }
+       /* check if response contains our certificate */
+       switch (response->get_status(response, subject, issuer, &revocation, &reason,
+                                                                &this_update, &next_update))
+       {
+               case VALIDATION_REVOKED:
+                       /* subject has been revoked by a valid OCSP response */
+                       DBG1(DBG_CFG, "certificate was revoked on %T, reason: %N",
+                                                 &revocation, TRUE, crl_reason_names, reason);
+                       revoked = TRUE;
+                       break;
+               case VALIDATION_GOOD:
+                       /* results in either good or stale */
+                       break;
+               default:
+               case VALIDATION_FAILED:
+                       /* candidate unusable, does not contain our cert */
+                       DBG1(DBG_CFG, "  ocsp response contains no status on our certificate");
+                       cand->destroy(cand);
+                       return best;
+       }
+
+       /* select the better of the two responses */
+       if (best == NULL || certificate_is_newer(cand, best))
+       {
+               DESTROY_IF(best);
+               best = cand;
+               if (best->get_validity(best, NULL, NULL, &valid_until))
+               {
+                       DBG1(DBG_CFG, "  ocsp response is valid: until %T",
+                                                        &valid_until, FALSE);
+                       *valid = VALIDATION_GOOD;
+                       if (cache)
+                       {       /* cache non-stale only, stale certs get refetched */
+                               lib->credmgr->cache_cert(lib->credmgr, best);
+                       }
+               }
+               else
+               {
+                       DBG1(DBG_CFG, "  ocsp response is stale: since %T",
+                                                        &valid_until, FALSE);
+                       *valid = VALIDATION_STALE;
+               }
+       }
+       else
+       {
+               *valid = VALIDATION_STALE;
+               cand->destroy(cand);
+       }
+       if (revoked)
+       {       /* revoked always counts, even if stale */
+               *valid = VALIDATION_REVOKED;
+       }
+       return best;
+}
+
+/**
+ * validate a x509 certificate using OCSP
+ */
+static cert_validation_t check_ocsp(x509_t *subject, x509_t *issuer,
+                                                                       auth_cfg_t *auth)
+{
+       enumerator_t *enumerator;
+       cert_validation_t valid = VALIDATION_SKIPPED;
+       certificate_t *best = NULL, *current;
+       identification_t *keyid = NULL;
+       public_key_t *public;
+       chunk_t chunk;
+       char *uri = NULL;
+
+       /** lookup cache for valid OCSP responses */
+       enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr,
+                                                               CERT_X509_OCSP_RESPONSE, KEY_ANY, NULL, FALSE);
+       while (enumerator->enumerate(enumerator, &current))
+       {
+               current->get_ref(current);
+               best = get_better_ocsp(current, best, subject, issuer, &valid, FALSE);
+               if (best && valid != VALIDATION_STALE)
+               {
+                       DBG1(DBG_CFG, "  using cached ocsp response");
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+
+       /* derive the authorityKeyIdentifier from the issuer's public key */
+       current = &issuer->interface;
+       public = current->get_public_key(current);
+       if (public && public->get_fingerprint(public, KEY_ID_PUBKEY_SHA1, &chunk))
+       {
+               keyid = identification_create_from_encoding(ID_KEY_ID, chunk);
+       }
+       /** fetch from configured OCSP responder URLs */
+       if (keyid && valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED)
+       {
+               enumerator = lib->credmgr->create_cdp_enumerator(lib->credmgr,
+                                                                                       CERT_X509_OCSP_RESPONSE, keyid);
+               while (enumerator->enumerate(enumerator, &uri))
+               {
+                       current = fetch_ocsp(uri, &subject->interface, &issuer->interface);
+                       if (current)
+                       {
+                               best = get_better_ocsp(current, best, subject, issuer,
+                                                                          &valid, TRUE);
+                               if (best && valid != VALIDATION_STALE)
+                               {
+                                       break;
+                               }
+                       }
+               }
+               enumerator->destroy(enumerator);
+       }
+       DESTROY_IF(public);
+       DESTROY_IF(keyid);
+
+       /* fallback to URL fetching from subject certificate's URIs */
+       if (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED)
+       {
+               enumerator = subject->create_ocsp_uri_enumerator(subject);
+               while (enumerator->enumerate(enumerator, &uri))
+               {
+                       current = fetch_ocsp(uri, &subject->interface, &issuer->interface);
+                       if (current)
+                       {
+                               best = get_better_ocsp(current, best, subject, issuer,
+                                                                          &valid, TRUE);
+                               if (best && valid != VALIDATION_STALE)
+                               {
+                                       break;
+                               }
+                       }
+               }
+               enumerator->destroy(enumerator);
+       }
+       /* an uri was found, but no result. switch validation state to failed */
+       if (valid == VALIDATION_SKIPPED && uri)
+       {
+               valid = VALIDATION_FAILED;
+       }
+       if (auth)
+       {
+               auth->add(auth, AUTH_RULE_OCSP_VALIDATION, valid);
+               if (valid == VALIDATION_GOOD)
+               {       /* successful OCSP check fulfills also CRL constraint */
+                       auth->add(auth, AUTH_RULE_CRL_VALIDATION, VALIDATION_GOOD);
+               }
+       }
+       DESTROY_IF(best);
+       return valid;
+}
+
+/**
+ * fetch a CRL from an URL
+ */
+static certificate_t* fetch_crl(char *url)
+{
+       certificate_t *crl;
+       chunk_t chunk;
+
+       DBG1(DBG_CFG, "  fetching crl from '%s' ...", url);
+       if (lib->fetcher->fetch(lib->fetcher, url, &chunk, FETCH_END) != SUCCESS)
+       {
+               DBG1(DBG_CFG, "crl fetching failed");
+               return NULL;
+       }
+       crl = lib->creds->create(lib->creds, CRED_CERTIFICATE, CERT_X509_CRL,
+                                                        BUILD_BLOB_ASN1_DER, chunk, BUILD_END);
+       chunk_free(&chunk);
+       if (!crl)
+       {
+               DBG1(DBG_CFG, "crl fetched successfully but parsing failed");
+               return NULL;
+       }
+       return crl;
+}
+
+/**
+ * check the signature of an CRL
+ */
+static bool verify_crl(certificate_t *crl)
+{
+       certificate_t *issuer;
+       enumerator_t *enumerator;
+       bool verified = FALSE;
+
+       enumerator = lib->credmgr->create_trusted_enumerator(lib->credmgr,
+                                                                               KEY_ANY, crl->get_issuer(crl), FALSE);
+       while (enumerator->enumerate(enumerator, &issuer, NULL))
+       {
+               if (lib->credmgr->issued_by(lib->credmgr, crl, issuer))
+               {
+                       DBG1(DBG_CFG, "  crl correctly signed by \"%Y\"",
+                                                  issuer->get_subject(issuer));
+                       verified = TRUE;
+                       break;
+               }
+       }
+       enumerator->destroy(enumerator);
+
+       return verified;
+}
+
+/**
+ * Get the better of two CRLs, and check for usable CRL info
+ */
+static certificate_t *get_better_crl(certificate_t *cand, certificate_t *best,
+               x509_t *subject, x509_t *issuer, cert_validation_t *valid, bool cache)
+{
+       enumerator_t *enumerator;
+       time_t revocation, valid_until;
+       crl_reason_t reason;
+       chunk_t serial;
+       crl_t *crl;
+
+       /* check CRL signature */
+       if (!verify_crl(cand))
+       {
+               DBG1(DBG_CFG, "crl response verification failed");
+               cand->destroy(cand);
+               return best;
+       }
+
+       crl = (crl_t*)cand;
+       enumerator = crl->create_enumerator(crl);
+       while (enumerator->enumerate(enumerator, &serial, &revocation, &reason))
+       {
+               if (chunk_equals(serial, subject->get_serial(subject)))
+               {
+                       DBG1(DBG_CFG, "certificate was revoked on %T, reason: %N",
+                                &revocation, TRUE, crl_reason_names, reason);
+                       *valid = VALIDATION_REVOKED;
+                       enumerator->destroy(enumerator);
+                       DESTROY_IF(best);
+                       return cand;
+               }
+       }
+       enumerator->destroy(enumerator);
+
+       /* select the better of the two CRLs */
+       if (best == NULL || crl_is_newer(crl, (crl_t*)best))
+       {
+               DESTROY_IF(best);
+               best = cand;
+               if (best->get_validity(best, NULL, NULL, &valid_until))
+               {
+                       DBG1(DBG_CFG, "  crl is valid: until %T", &valid_until, FALSE);
+                       *valid = VALIDATION_GOOD;
+                       if (cache)
+                       {       /* we cache non-stale crls only, as a stale crls are refetched */
+                               lib->credmgr->cache_cert(lib->credmgr, best);
+                       }
+               }
+               else
+               {
+                       DBG1(DBG_CFG, "  crl is stale: since %T", &valid_until, FALSE);
+                       *valid = VALIDATION_STALE;
+               }
+       }
+       else
+       {
+               *valid = VALIDATION_STALE;
+               cand->destroy(cand);
+       }
+       return best;
+}
+
+/**
+ * validate a x509 certificate using CRL
+ */
+static cert_validation_t check_crl(x509_t *subject, x509_t *issuer,
+                                                                  auth_cfg_t *auth)
+{
+       cert_validation_t valid = VALIDATION_SKIPPED;
+       identification_t *keyid = NULL;
+       certificate_t *best = NULL;
+       certificate_t *current;
+       public_key_t *public;
+       enumerator_t *enumerator;
+       chunk_t chunk;
+       char *uri = NULL;
+
+       /* derive the authorityKeyIdentifier from the issuer's public key */
+       current = &issuer->interface;
+       public = current->get_public_key(current);
+       if (public && public->get_fingerprint(public, KEY_ID_PUBKEY_SHA1, &chunk))
+       {
+               keyid = identification_create_from_encoding(ID_KEY_ID, chunk);
+
+               /* find a cached crl by authorityKeyIdentifier */
+               enumerator = lib->credmgr->create_cert_enumerator(lib->credmgr,
+                                                                               CERT_X509_CRL, KEY_ANY, keyid, FALSE);
+               while (enumerator->enumerate(enumerator, &current))
+               {
+                       current->get_ref(current);
+                       best = get_better_crl(current, best, subject, issuer,
+                                                                 &valid, FALSE);
+                       if (best && valid != VALIDATION_STALE)
+                       {
+                               DBG1(DBG_CFG, "  using cached crl");
+                               break;
+                       }
+               }
+               enumerator->destroy(enumerator);
+
+               /* fallback to fetching crls from credential sets cdps */
+               if (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED)
+               {
+                       enumerator = lib->credmgr->create_cdp_enumerator(lib->credmgr,
+                                                                                                               CERT_X509_CRL, keyid);
+                       while (enumerator->enumerate(enumerator, &uri))
+                       {
+                               current = fetch_crl(uri);
+                               if (current)
+                               {
+                                       best = get_better_crl(current, best, subject, issuer,
+                                                                                 &valid, TRUE);
+                                       if (best && valid != VALIDATION_STALE)
+                                       {
+                                               break;
+                                       }
+                               }
+                       }
+                       enumerator->destroy(enumerator);
+               }
+               keyid->destroy(keyid);
+       }
+       DESTROY_IF(public);
+
+       /* fallback to fetching crls from cdps from subject's certificate */
+       if (valid != VALIDATION_GOOD && valid != VALIDATION_REVOKED)
+       {
+               enumerator = subject->create_crl_uri_enumerator(subject);
+
+               while (enumerator->enumerate(enumerator, &uri))
+               {
+                       current = fetch_crl(uri);
+                       if (current)
+                       {
+                               best = get_better_crl(current, best, subject, issuer,
+                                                                         &valid, TRUE);
+                               if (best && valid != VALIDATION_STALE)
+                               {
+                                       break;
+                               }
+                       }
+               }
+               enumerator->destroy(enumerator);
+       }
+
+       /* an uri was found, but no result. switch validation state to failed */
+       if (valid == VALIDATION_SKIPPED && uri)
+       {
+               valid = VALIDATION_FAILED;
+       }
+       if (auth)
+       {
+               if (valid == VALIDATION_SKIPPED)
+               {       /* if we skipped CRL validation, we use the result of OCSP for
+                        * constraint checking */
+                       auth->add(auth, AUTH_RULE_CRL_VALIDATION,
+                                         auth->get(auth, AUTH_RULE_OCSP_VALIDATION));
+               }
+               else
+               {
+                       auth->add(auth, AUTH_RULE_CRL_VALIDATION, valid);
+               }
+       }
+       DESTROY_IF(best);
+       return valid;
+}
+
+METHOD(cert_validator_t, validate, bool,
+       private_revocation_validator_t *this, certificate_t *subject,
+       certificate_t *issuer, bool online, int pathlen, auth_cfg_t *auth)
+{
+       if (subject->get_type(subject) == CERT_X509 &&
+               issuer->get_type(issuer) == CERT_X509 &&
+               online)
+       {
+               DBG1(DBG_CFG, "checking certificate status of \"%Y\"",
+                                          subject->get_subject(subject));
+               switch (check_ocsp((x509_t*)subject, (x509_t*)issuer, auth))
+               {
+                       case VALIDATION_GOOD:
+                               DBG1(DBG_CFG, "certificate status is good");
+                               return TRUE;
+                       case VALIDATION_REVOKED:
+                               /* has already been logged */
+                               return FALSE;
+                       case VALIDATION_SKIPPED:
+                               DBG2(DBG_CFG, "ocsp check skipped, no ocsp found");
+                               break;
+                       case VALIDATION_STALE:
+                               DBG1(DBG_CFG, "ocsp information stale, fallback to crl");
+                               break;
+                       case VALIDATION_FAILED:
+                               DBG1(DBG_CFG, "ocsp check failed, fallback to crl");
+                               break;
+               }
+               switch (check_crl((x509_t*)subject, (x509_t*)issuer, auth))
+               {
+                       case VALIDATION_GOOD:
+                               DBG1(DBG_CFG, "certificate status is good");
+                               return TRUE;
+                       case VALIDATION_REVOKED:
+                               /* has already been logged */
+                               return FALSE;
+                       case VALIDATION_FAILED:
+                       case VALIDATION_SKIPPED:
+                               DBG1(DBG_CFG, "certificate status is not available");
+                               break;
+                       case VALIDATION_STALE:
+                               DBG1(DBG_CFG, "certificate status is unknown, crl is stale");
+                               break;
+               }
+       }
+       return TRUE;
+}
+
+METHOD(revocation_validator_t, destroy, void,
+       private_revocation_validator_t *this)
+{
+       free(this);
+}
+
+/**
+ * See header
+ */
+revocation_validator_t *revocation_validator_create()
+{
+       private_revocation_validator_t *this;
+
+       INIT(this,
+               .public = {
+                       .validator.validate = _validate,
+                       .destroy = _destroy,
+               },
+       );
+
+       return &this->public;
+}
diff --git a/src/libstrongswan/plugins/revocation/revocation_validator.h b/src/libstrongswan/plugins/revocation/revocation_validator.h
new file mode 100644 (file)
index 0000000..82cbde2
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2010 Martin Willi
+ * Copyright (C) 2010 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 <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.
+ */
+
+/**
+ * @defgroup revocation_validator revocation_validator
+ * @{ @ingroup revocation
+ */
+
+#ifndef REVOCATION_VALIDATOR_H_
+#define REVOCATION_VALIDATOR_H_
+
+#include <credentials/cert_validator.h>
+
+typedef struct revocation_validator_t revocation_validator_t;
+
+/**
+ * Certificate validator doing CRL/OCSP checking of X509 certificates.
+ */
+struct revocation_validator_t {
+
+       /**
+        * Implements cert_validator_t interface.
+        */
+       cert_validator_t validator;
+
+       /**
+        * Destroy a revocation_validator_t.
+        */
+       void (*destroy)(revocation_validator_t *this);
+};
+
+/**
+ * Create a revocation_validator instance.
+ */
+revocation_validator_t *revocation_validator_create();
+
+#endif /** REVOCATION_VALIDATOR_H_ @}*/