Repository: randombit/botan
Pull request #2213 (2019-12-03 15:39:24)

Add basic support for CRL in FFI API 
Hi, I tried to add some basic support of CRLs in the FFI API.
I plan to add more CRL related functions and move the support to the Python binding as well.

I've got a few questions:

- Is it okay if it stays in the ffi_cert.cpp file?
- Should my `botan_x509_cert_verify_with_crl` stay separate from `botan_x509_cert_verify` to be backwards compatible?
- I couldn't figure out what is the meaning of the `magic` number in `BOTAN_FFI_DECLARE_STRUCT`. Is it just some random number that each struct has assigned to prevent some errors? What should I set it to?

Thanks beforehand for all suggestions.


Patch:
diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst
index 4c9472dd2..4bf03a5b1 100644
--- a/doc/api_ref/ffi.rst
+++ b/doc/api_ref/ffi.rst
@@ -1152,7 +1152,51 @@ X.509 Certificates
     Set ``reference_time`` to be the time which the certificate chain is
     validated against. Use zero to use the current system clock.
 
+.. cpp:function:: int botan_x509_cert_verify_with_crl(int* validation_result, \
+                  botan_x509_cert_t cert, \
+                  const botan_x509_cert_t* intermediates, \
+                  size_t intermediates_len, \
+                  const botan_x509_cert_t* trusted, \
+                  size_t trusted_len, \
+                  const botan_x509_crl_t* crls, \
+                  size_t crls_len, \
+                  const char* trusted_path, \
+                  size_t required_strength, \
+                  const char* hostname, \
+                  uint64_t reference_time)
+
+   Certificate path validation supporting Certificate Revocation Lists.
+
+   Works the same as ``botan_x509_cert_cerify``.
+
+   ``crls`` is an array of ``botan_x509_crl_t`` objects, ``crls_len`` is its length.
+
 .. cpp:function:: const char* botan_x509_cert_validation_status(int code)
 
    Return a (statically allocated) string associated with the verification
    result.
+
+X.509 Certificate Revocation Lists
+----------------------------------------
+
+.. cpp:type:: opaque* botan_x509_crl_t
+
+   An opaque data type for an X.509 CRL.
+
+.. cpp:function:: int botan_x509_crl_load(botan_x509_crl_t* crl_obj, \
+                                        const uint8_t crl[], size_t crl_len)
+
+   Load a CRL from the DER or PEM representation.
+
+.. cpp:function:: int botan_x509_crl_load_file(botan_x509_crl_t* crl_obj, const char* filename)
+
+   Load a CRL from a file.
+
+.. cpp:function:: int botan_x509_crl_destroy(botan_x509_crl_t crl)
+
+   Destroy the CRL object.
+
+.. cpp:function:: int botan_x509_is_revoked(botan_x509_crl_t crl, botan_x509_cert_t cert)
+
+   Check whether a given ``crl`` contains a given ``cert``.
+   Return ``0`` when the certificate is revoked, ``-1`` otherwise.
\ No newline at end of file
diff --git a/doc/api_ref/python.rst b/doc/api_ref/python.rst
index 1fda54193..a57f29e6d 100644
--- a/doc/api_ref/python.rst
+++ b/doc/api_ref/python.rst
@@ -547,7 +547,7 @@ HOTP
 X509Cert
 -----------------------------------------
 
-.. py:class:: X509Cert(filename=None, buf=None)
+.. py:class:: X509Cert(filename=None, buf=None) 
 
    .. py:method:: time_starts()
 
@@ -626,7 +626,8 @@ X509Cert
                   trusted_path=None, \
                   required_strength=0, \
                   hostname=None, \
-                  reference_time=0)
+                  reference_time=0 \
+                  crls=None)
 
       Verify a certificate. Returns 0 if validation was successful, returns a positive error code 
       if the validation was unsuccesful.
@@ -648,16 +649,25 @@ X509Cert
       Set ``reference_time`` to be the time which the certificate chain is
       validated against. Use zero (default) to use the current system clock.
 
+      ``crls`` is a list of CRLs issued by either trusted or untrusted authorities.
+
    .. py:classmethod:: validation_status(error_code)
 
       Return an informative string associated with the verification return code.
 
-     
+   .. py:method:: is_revoked(self, crl)
 
+      Check if the certificate (``self``) is revoked on the given ``crl``.
 
+X509CRL
+-----------------------------------------
 
+.. py:class:: X509CRL(filename=None, buf=None)
 
+   Class representing an X.509 Certificate Revocation List.
 
+   A CRL in PEM or DER format can be loaded from a file, with the ``filename`` argument,
+   or from a bytestring, with the ``buf`` argument.
 
 
 
diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h
index aac196494..a7cd11d25 100644
--- a/src/lib/ffi/ffi.h
+++ b/src/lib/ffi/ffi.h
@@ -1590,6 +1590,41 @@ BOTAN_PUBLIC_API(2,8) int botan_x509_cert_verify(
 */
 BOTAN_PUBLIC_API(2,8) const char* botan_x509_cert_validation_status(int code);
 
+/*
+* X.509 CRL
+**************************/
+
+typedef struct botan_x509_crl_struct* botan_x509_crl_t;
+
+BOTAN_PUBLIC_API(2,13) int botan_x509_crl_load_file(botan_x509_crl_t* crl_obj, const char* crl_path);
+BOTAN_PUBLIC_API(2,13) int botan_x509_crl_load(botan_x509_crl_t* crl_obj, const uint8_t crl_bits[], size_t crl_bits_len);
+
+BOTAN_PUBLIC_API(2,13) int botan_x509_crl_destroy(botan_x509_crl_t crl);
+
+/**
+ * Given a CRL and a certificate, 
+ * check if the certificate is revoked on that particular CRL
+ */
+BOTAN_PUBLIC_API(2,13) int botan_x509_is_revoked(botan_x509_crl_t crl, botan_x509_cert_t cert);
+
+/**
+ * Different flavor of `botan_x509_cert_verify`, supports revocation lists.
+ * CRLs are passed as an array, same as intermediates and trusted CAs 
+ */
+BOTAN_PUBLIC_API(2,13) int botan_x509_cert_verify_with_crl(
+   int* validation_result,
+   botan_x509_cert_t cert,
+   const botan_x509_cert_t* intermediates,
+   size_t intermediates_len,
+   const botan_x509_cert_t* trusted,
+   size_t trusted_len,
+   const botan_x509_crl_t* crls,
+   size_t crls_len,
+   const char* trusted_path,
+   size_t required_strength,
+   const char* hostname,
+   uint64_t reference_time);
+
 /**
  * Key wrapping as per RFC 3394
  */
diff --git a/src/lib/ffi/ffi_cert.cpp b/src/lib/ffi/ffi_cert.cpp
index dd7f37ecb..2aa6f5f3c 100644
--- a/src/lib/ffi/ffi_cert.cpp
+++ b/src/lib/ffi/ffi_cert.cpp
@@ -11,6 +11,7 @@
 #if defined(BOTAN_HAS_X509_CERTIFICATES)
    #include <botan/x509cert.h>
    #include <botan/x509path.h>
+   #include <botan/x509_crl.h>
    #include <botan/data_src.h>
 #endif
 
@@ -349,4 +350,156 @@ const char* botan_x509_cert_validation_status(int code)
 #endif
    }
 
+#if defined(BOTAN_HAS_X509_CERTIFICATES)
+
+BOTAN_FFI_DECLARE_STRUCT(botan_x509_crl_struct, Botan::X509_CRL, 0x2C628910);
+
+#endif
+
+int botan_x509_crl_load_file(botan_x509_crl_t* crl_obj, const char* crl_path)
+   {
+   if(!crl_obj || !crl_path)
+      return BOTAN_FFI_ERROR_NULL_POINTER;
+
+#if defined(BOTAN_HAS_X509_CERTIFICATES) && defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
+
+   return ffi_guard_thunk(__func__, [=]() -> int {
+      std::unique_ptr<Botan::X509_CRL> c(new Botan::X509_CRL(crl_path));
+      *crl_obj = new botan_x509_crl_struct(c.release());
+      return BOTAN_FFI_SUCCESS;
+      });
+
+#else
+   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
+#endif
+   }
+
+int botan_x509_crl_load(botan_x509_crl_t* crl_obj, const uint8_t crl_bits[], size_t crl_bits_len)
+   {
+   if(!crl_obj || !crl_bits)
+      return BOTAN_FFI_ERROR_NULL_POINTER;
+
+#if defined(BOTAN_HAS_X509_CERTIFICATES)
+   return ffi_guard_thunk(__func__, [=]() -> int {
+      Botan::DataSource_Memory bits(crl_bits, crl_bits_len);
+      std::unique_ptr<Botan::X509_CRL> c(new Botan::X509_CRL(bits));
+      *crl_obj = new botan_x509_crl_struct(c.release());
+      return BOTAN_FFI_SUCCESS;
+      });
+#else
+   BOTAN_UNUSED(crl_bits_len);
+   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
+#endif
+   }
+
+int botan_x509_crl_destroy(botan_x509_crl_t crl)
+   {
+#if defined(BOTAN_HAS_X509_CERTIFICATES)
+   return BOTAN_FFI_CHECKED_DELETE(crl);
+#else
+   BOTAN_UNUSED(crl);
+   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
+#endif
+   }
+
+int botan_x509_is_revoked(botan_x509_crl_t crl, botan_x509_cert_t cert)
+   {
+#if defined(BOTAN_HAS_X509_CERTIFICATES)
+   return ffi_guard_thunk(__func__, [=]() -> int {
+      int result = BOTAN_FFI_DO(Botan::X509_CRL, crl, c,
+                       { return c.is_revoked(safe_get(cert)) ? 0 : -1; });
+      return result;
+      });
+#else
+   BOTAN_UNUSED(cert);
+   BOTAN_UNUSED(crl);
+   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
+#endif
+   }
+
+BOTAN_PUBLIC_API(2,13) int botan_x509_cert_verify_with_crl(
+   int* result_code,
+   botan_x509_cert_t cert,
+   const botan_x509_cert_t* intermediates,
+   size_t intermediates_len,
+   const botan_x509_cert_t* trusted,
+   size_t trusted_len,
+   const botan_x509_crl_t* crls,
+   size_t crls_len,
+   const char* trusted_path,
+   size_t required_strength,
+   const char* hostname_cstr,
+   uint64_t reference_time)
+   {
+   if(required_strength == 0)
+      required_strength = 110;
+
+#if defined(BOTAN_HAS_X509_CERTIFICATES)
+   return ffi_guard_thunk(__func__, [=]() -> int {
+      const std::string hostname((hostname_cstr == nullptr) ? "" : hostname_cstr);
+      const Botan::Usage_Type usage = Botan::Usage_Type::UNSPECIFIED;
+      const auto validation_time = reference_time == 0 ?
+         std::chrono::system_clock::now() :
+         std::chrono::system_clock::from_time_t(static_cast<time_t>(reference_time));
+
+      std::vector<Botan::X509_Certificate> end_certs;
+      end_certs.push_back(safe_get(cert));
+      for(size_t i = 0; i != intermediates_len; ++i)
+         end_certs.push_back(safe_get(intermediates[i]));
+
+      std::unique_ptr<Botan::Certificate_Store> trusted_from_path;
+      std::unique_ptr<Botan::Certificate_Store_In_Memory> trusted_extra;
+      std::unique_ptr<Botan::Certificate_Store_In_Memory> trusted_crls;
+      std::vector<Botan::Certificate_Store*> trusted_roots;
+
+      if(trusted_path && *trusted_path)
+         {
+         trusted_from_path.reset(new Botan::Certificate_Store_In_Memory(trusted_path));
+         trusted_roots.push_back(trusted_from_path.get());
+         }
+
+      if(trusted_len > 0)
+         {
+         trusted_extra.reset(new Botan::Certificate_Store_In_Memory);
+         for(size_t i = 0; i != trusted_len; ++i)
+            {
+            trusted_extra->add_certificate(safe_get(trusted[i]));
+            }
+         trusted_roots.push_back(trusted_extra.get());
+         }
+
+      if(crls_len > 0)
+         {
+         trusted_crls.reset(new Botan::Certificate_Store_In_Memory);
+         for(size_t i = 0; i != crls_len; ++i)
+            {
+            trusted_crls->add_crl(safe_get(crls[i]));
+            }
+         trusted_roots.push_back(trusted_crls.get());
+         }
+
+      Botan::Path_Validation_Restrictions restrictions(false, required_strength);
+
+      auto validation_result = Botan::x509_path_validate(end_certs,
+                                                         restrictions,
+                                                         trusted_roots,
+                                                         hostname,
+                                                         usage,
+                                                         validation_time);
+
+      if(result_code)
+         *result_code = static_cast<int>(validation_result.result());
+
+      if(validation_result.successful_validation())
+         return 0;
+      else
+         return 1;
+      });
+#else
+   BOTAN_UNUSED(result_code, cert, intermediates, intermediates_len, trusted);
+   BOTAN_UNUSED(trusted_len, trusted_path, hostname_cstr, reference_time, crls, crls_len);
+   return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
+#endif
+   }
+
 }
diff --git a/src/python/botan2.py b/src/python/botan2.py
index dcff583a9..01fd4daf8 100755
--- a/src/python/botan2.py
+++ b/src/python/botan2.py
@@ -383,6 +383,14 @@ def _set_prototypes(dll):
     dll.botan_x509_cert_validation_status.argtypes = [c_int]
     dll.botan_x509_cert_validation_status.restype = c_char_p
 
+    # X509 CRL
+    ffi_api(dll.botan_x509_crl_load, [c_void_p, c_char_p, c_size_t])
+    ffi_api(dll.botan_x509_crl_load_file, [c_void_p, c_char_p])
+    ffi_api(dll.botan_x509_crl_destroy, [c_void_p])
+    ffi_api(dll.botan_x509_is_revoked, [c_void_p, c_void_p], [-1])
+    ffi_api(dll.botan_x509_cert_verify_with_crl,
+            [POINTER(c_int), c_void_p, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_char_p, c_size_t, c_char_p, c_uint64])
+
     ffi_api(dll.botan_key_wrap3394,
             [c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, POINTER(c_size_t)])
     ffi_api(dll.botan_key_unwrap3394,
@@ -1376,7 +1384,7 @@ class X509Cert(object): # pylint: disable=invalid-name
         rc = _DLL.botan_x509_cert_allowed_usage(self.__obj, c_uint(usage))
         return rc == 0
 
-    def get_obj(self):
+    def handle_(self):
         return self.__obj
 
     def verify(self,
@@ -1385,36 +1393,90 @@ class X509Cert(object): # pylint: disable=invalid-name
                trusted_path=None,
                required_strength=0,
                hostname=None,
-               reference_time=0):
-
-        c_intermediates = len(intermediates) * c_void_p
-        arr_intermediates = c_intermediates()
-        for i, ca in enumerate(intermediates):
-            arr_intermediates[i] = ca.get_obj()
-
-        c_trusted = len(trusted) * c_void_p
-        arr_trusted = c_trusted()
-        for i, ca in enumerate(trusted):
-            arr_trusted[i] = ca.get_obj()
+               reference_time=0,
+               crls=None):
+
+        if intermediates is not None:
+            c_intermediates = len(intermediates) * c_void_p
+            arr_intermediates = c_intermediates()
+            for i, ca in enumerate(intermediates):
+                arr_intermediates[i] = ca.handle_()
+            len_intermediates = c_size_t(len(intermediates))
+        else:
+            arr_intermediates = c_void_p(0)
+            len_intermediates = c_size_t(0)
+
+        if trusted is not None:
+            c_trusted = len(trusted) * c_void_p
+            arr_trusted = c_trusted()
+            for i, ca in enumerate(trusted):
+                arr_trusted[i] = ca.handle_()
+            len_trusted = c_size_t(len(trusted))
+        else:
+            arr_trusted = c_void_p(0)
+            len_trusted = c_size_t(0)
+
+        if crls is not None:
+            c_crls = len(crls) * c_void_p
+            arr_crls = c_crls()
+            for i, crl in enumerate(crls):
+                arr_crls[i] = crl.handle_()
+            len_crls = c_size_t(len(crls))
+        else:
+            arr_crls = c_void_p(0)
+            len_crls = c_size_t(0)
 
         error_code = c_int(0)
 
-        _DLL.botan_x509_cert_verify(byref(error_code),
-                                    self.__obj,
-                                    byref(arr_intermediates),
-                                    c_size_t(len(intermediates)),
-                                    byref(arr_trusted),
-                                    c_size_t(len(trusted)),
-                                    _ctype_str(trusted_path),
-                                    c_size_t(required_strength),
-                                    _ctype_str(hostname),
-                                    c_uint64(reference_time))
+        _DLL.botan_x509_cert_verify_with_crl(byref(error_code),
+                                             self.__obj,
+                                             byref(arr_intermediates),
+                                             len_intermediates,
+                                             byref(arr_trusted),
+                                             len_trusted,
+                                             byref(arr_crls),
+                                             len_crls,
+                                             _ctype_str(trusted_path),
+                                             c_size_t(required_strength),
+                                             _ctype_str(hostname),
+                                             c_uint64(reference_time))
+
         return error_code.value
 
     @classmethod
     def validation_status(cls, error_code):
         return _ctype_to_str(_DLL.botan_x509_cert_validation_status(c_int(error_code)))
 
+    def is_revoked(self, crl):
+        rc = _DLL.botan_x509_is_revoked(crl.handle_(), self.__obj)
+        return rc == 0
+
+
+#
+# X.509 Certificate revocation lists
+#
+class X509CRL(object):
+
+    def __init__(self, filename=None, buf=None):
+        if filename is None and buf is None:
+            raise BotanException("No filename or buf given")
+        if filename is not None and buf is not None:
+            raise BotanException("Both filename and buf given")
+
+        if filename is not None:
+            self.__obj = c_void_p(0)
+            _DLL.botan_x509_crl_load_file(byref(self.__obj), _ctype_str(filename))
+        elif buf is not None:
+            self.__obj = c_void_p(0)
+            _DLL.botan_x509_crl_load(byref(self.__obj), _ctype_bits(buf), len(buf))
+
+    def __del__(self):
+        _DLL.botan_x509_crl_destroy(self.__obj)
+
+    def handle_(self):
+        return self.__obj
+
+
 class MPI(object): # pylint: disable=too-many-public-methods
 
     def __init__(self, initial_value=None, radix=None):


Test patch:
diff --git a/src/scripts/test_python.py b/src/scripts/test_python.py
index ad1be89ce..40a5b6573 100644
--- a/src/scripts/test_python.py
+++ b/src/scripts/test_python.py
@@ -442,6 +442,7 @@ ofvkP1EDmpx50fHLawIDAQAB
             self.assertEqual(a_pem, new_a.to_pem())
 
     def test_certs(self):
+        # pylint: disable=too-many-statements
         cert = botan2.X509Cert(filename="src/tests/data/x509/ecc/CSCA.CSCA.csca-germany.1.crt")
         pubkey = cert.subject_public_key()
 
@@ -498,6 +499,28 @@ ofvkP1EDmpx50fHLawIDAQAB
         self.assertEqual(botan2.X509Cert.validation_status(3000), 'Certificate issuer not found')
         self.assertEqual(botan2.X509Cert.validation_status(4008), 'Certificate does not match provided name')
 
+        rootcrl = botan2.X509CRL("src/tests/data/x509/nist/root.crl")
+
+        end01 = botan2.X509Cert("src/tests/data/x509/nist/test01/end.crt")
+        self.assertEqual(end01.verify([], [root], required_strength=80, crls=[rootcrl]), 0)
+
+        int20 = botan2.X509Cert("src/tests/data/x509/nist/test20/int.crt")
+        end20 = botan2.X509Cert("src/tests/data/x509/nist/test20/end.crt")
+        int20crl = botan2.X509CRL("src/tests/data/x509/nist/test20/int.crl")
+
+        self.assertEqual(end20.verify([int20], [root], required_strength=80, crls=[int20crl, rootcrl]), 5000)
+        self.assertEqual(botan2.X509Cert.validation_status(5000), 'Certificate is revoked')
+
+        int21 = botan2.X509Cert("src/tests/data/x509/nist/test21/int.crt")
+        end21 = botan2.X509Cert("src/tests/data/x509/nist/test21/end.crt")
+        int21crl = botan2.X509CRL("src/tests/data/x509/nist/test21/int.crl")
+        self.assertEqual(end21.verify([int21], [root], required_strength=80, crls=[int21crl, rootcrl]), 5000)
+
+        self.assertTrue(int20.is_revoked(rootcrl))
+        self.assertFalse(int04_1.is_revoked(rootcrl))
+        self.assertTrue(end21.is_revoked(int21crl))
+
+
     def test_mpi(self):
         # pylint: disable=too-many-statements
         z = botan2.MPI()
diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp
index 03974589d..4baf3983c 100644
--- a/src/tests/test_ffi.cpp
+++ b/src/tests/test_ffi.cpp
@@ -61,6 +61,7 @@ class FFI_Unit_Tests final : public Test
          results.push_back(ffi_test_mp(rng));
          results.push_back(ffi_test_pkcs_hash_id());
          results.push_back(ffi_test_cert_validation());
+         results.push_back(ffi_test_crl());
 
 #if defined(BOTAN_HAS_AES)
          results.push_back(ffi_test_block_ciphers());
@@ -280,6 +281,55 @@ class FFI_Unit_Tests final : public Test
          return result;
          }
 
+
+      Test::Result ffi_test_crl()
+         {
+         Test::Result result("FFI CRL");
+
+#if defined(BOTAN_HAS_X509_CERTIFICATES)
+         
+         const char *crl_string = "-----BEGIN X509 CRL-----\n"
+                                 "MIICoTCCAQkCAQEwDQYJKoZIhvcNAQELBQAwgZQxLTArBgNVBAMTJFVzYWJsZSBj\n"
+                                 "ZXJ0IHZhbGlkYXRpb246IFRlbXBvcmFyeSBDQTE5MDcGA1UECxMwQ2VudHJlIGZv\n"
+                                 "ciBSZXNlYXJjaCBvbiBDcnlwdG9ncmFwaHkgYW5kIFNlY3VyaXR5MRswGQYDVQQK\n"
+                                 "ExJNYXNhcnlrIFVuaXZlcnNpdHkxCzAJBgNVBAYTAkNaGA8yMDUwMDIyNTE1MjE0\n"
+                                 "MloYDzIwNTAwMjI1MTUyNDQxWjAAoDowODAfBgNVHSMEGDAWgBRKzxAvI4+rVVo/\n"
+                                 "JzLigRznREyB+TAVBgNVHRQEDgIMXcr16yNys/gjeuCFMA0GCSqGSIb3DQEBCwUA\n"
+                                 "A4IBgQCfxv/5REM/KUnzeVycph3dJr1Yrtxhc6pZmQ9pMzSW/nawLN3rUHm5oG44\n"
+                                 "ZuQgjvzE4PnbU0/DNRu/4w3H58kgrctJHHXbbvkU3lf2ZZLh2wBl+EUh92+/COow\n"
+                                 "ZyGB+jqj/XwB99hYUhrY6NLEWRz08kpgG6dnNMEU0uFqdQKWk0CQPnmgPRgDb8BW\n"
+                                 "IuMBcjY7aF9XoCZFOqPYdEvUKzAo4QGCf7uJ7fNGS3LqvjaLjAHJseSr5/yR7Q9r\n"
+                                 "nEdI38yKPbRj0tNHe7j+BbYg31C+X+AZZKJtlTg8GxYR3qfQio1kDgpZ3rQLzHY3\n"
+                                 "ea2MLX/Kdx9cPSwh4KwlcDxQmQKoELb4EnZW1CScSBHi9HQyCBNyCkgkOBMGcJqz\n"
+                                 "Ihq1dGeSf8eca9+Avk5kAQ3yjXK1TI2CDEi0msrXLr9XbgowXiOLLzR+rYkhQz+V\n"
+                                 "RnIoBwjnrGoJoz636KS170SZCB9ARNs17WE4IvbJdZrTXNOGaVZCQUUpiLRj4ZSO\n"
+                                 "Na/nobI=\n"
+                                 "-----END X509 CRL-----";
+         
+         botan_x509_crl_t bytecrl;
+         REQUIRE_FFI_OK(botan_x509_crl_load, (&bytecrl, (const uint8_t*)crl_string, 966));
+
+         botan_x509_crl_t crl;
+         REQUIRE_FFI_OK(botan_x509_crl_load_file, (&crl, Test::data_file("x509/nist/root.crl").c_str()));
+         
+         botan_x509_cert_t cert1;
+         REQUIRE_FFI_OK(botan_x509_cert_load_file, (&cert1, Test::data_file("x509/nist/test01/end.crt").c_str()));
+         TEST_FFI_RC(-1, botan_x509_is_revoked, (crl, cert1));
+         TEST_FFI_OK(botan_x509_cert_destroy, (cert1));
+            
+         botan_x509_cert_t cert2;
+         REQUIRE_FFI_OK(botan_x509_cert_load_file, (&cert2, Test::data_file("x509/nist/test20/int.crt").c_str()));
+         TEST_FFI_RC(0, botan_x509_is_revoked, (crl, cert2));
+         TEST_FFI_RC(-1, botan_x509_is_revoked, (bytecrl, cert2));
+         TEST_FFI_OK(botan_x509_cert_destroy, (cert2));
+
+         TEST_FFI_OK(botan_x509_crl_destroy, (crl));
+         TEST_FFI_OK(botan_x509_crl_destroy, (bytecrl));
+#endif
+         return result;
+         }
+
+
       Test::Result ffi_test_cert_validation()
          {
          Test::Result result("FFI Cert validation");
@@ -318,11 +368,38 @@ class FFI_Unit_Tests final : public Test
          result.confirm("Validation passed", rc == 0);
          result.test_eq("Validation status string", botan_x509_cert_validation_status(rc), "Verified");
 
+         TEST_FFI_RC(1, botan_x509_cert_verify_with_crl, (&rc, end7, subs, 2, nullptr, 0, nullptr, 0, "x509/farce", 0, nullptr, 0));
+         result.confirm("Validation failed", rc == 3000);
+         result.test_eq("Validation status string", botan_x509_cert_validation_status(rc), "Certificate issuer not found");
+
+         botan_x509_crl_t rootcrl;
+
+         REQUIRE_FFI_OK(botan_x509_crl_load_file, (&rootcrl, Test::data_file("x509/nist/root.crl").c_str()));
+         TEST_FFI_RC(0, botan_x509_cert_verify_with_crl, (&rc, end7, subs, 2, &root, 1, &rootcrl, 1, nullptr, 80, nullptr, 0));
+         result.confirm("Validation passed", rc == 0);
+         result.test_eq("Validation status string", botan_x509_cert_validation_status(rc), "Verified");
+
+         botan_x509_cert_t end20;
+         botan_x509_cert_t sub20;
+         botan_x509_crl_t sub20crl;
+         REQUIRE_FFI_OK(botan_x509_cert_load_file, (&end20, Test::data_file("x509/nist/test20/end.crt").c_str()));
+         REQUIRE_FFI_OK(botan_x509_cert_load_file, (&sub20, Test::data_file("x509/nist/test20/int.crt").c_str()));
+         REQUIRE_FFI_OK(botan_x509_crl_load_file, (&sub20crl, Test::data_file("x509/nist/test20/int.crl").c_str()));
+         botan_x509_crl_t crls[2] = {sub20crl, rootcrl};
+         TEST_FFI_RC(1, botan_x509_cert_verify_with_crl, (&rc, end20, &sub20, 1, &root, 1, crls, 2, nullptr, 80, nullptr, 0));
+         result.confirm("Validation failed", rc == 5000);
+         result.test_eq("Validation status string", botan_x509_cert_validation_status(rc),
+                        "Certificate is revoked");
+         
          TEST_FFI_OK(botan_x509_cert_destroy, (end2));
          TEST_FFI_OK(botan_x509_cert_destroy, (sub2));
          TEST_FFI_OK(botan_x509_cert_destroy, (end7));
          TEST_FFI_OK(botan_x509_cert_destroy, (sub7));
+         TEST_FFI_OK(botan_x509_cert_destroy, (end20));
+         TEST_FFI_OK(botan_x509_cert_destroy, (sub20));
+         TEST_FFI_OK(botan_x509_crl_destroy, (sub20crl));
          TEST_FFI_OK(botan_x509_cert_destroy, (root));
+         TEST_FFI_OK(botan_x509_crl_destroy, (rootcrl));
 
 #endif
          return result;