Repository: randombit/botan
Pull request #1830 (2019-02-11 15:59:39)

Access to System Certificate Store on macOS
This is ongoing work towards an adapter for the macOS system certificate store onto Botan's `Certificate_Store` interface. I will add documentation, description and tests along the way and would be happy about a quick code review. Particularly, whether my build-system integration is reasonable. :-)

Currently implemented are:

* `Certificate_Store::find_cert()`
* `Certificate_Store::find_all_certs()`
* `Certificate_Store::find_cert_by_pubkey_sha1()`
* `Certificate_Store::all_subjects()`

However, I'm not sure whether `::find_crl_for()` and `::find_cert_by_raw_subject_dn_sha256()` are in reach.

Furthermore, I'm a bit worried about testing this. The tests added for now try to fetch the root certificate that signs the TLS certificate of "botan.randombit.net" in different ways. However, more sophisticated tests would require root access to the CI machine, to alter the root certificate chains. I don't think that this is feasible.

randombit: Oh nice, thanks.

I don't have time to review this right now but will do so asap, probably in next few days.

We should (I think) have root on the CI so it would be possible to test it there. However we probably should not ask for or require root access to run the tests normally, eg when installing through Homebrew. Nor should we mess with an end users certificate store ;)

To some extent that could be worked around by adding a flag to `botan-test` that signals "I'm running in continuous integration environment and it's ok to run potentially dangerous or destructive tests." and any test modifying the local certstore could check that flag before running.

codecov-io: # [Codecov](https://codecov.io/gh/randombit/botan/pull/1830?src=pr&el=h1) Report
> Merging [#1830](https://codecov.io/gh/randombit/botan/pull/1830?src=pr&el=desc) into [master](https://codecov.io/gh/randombit/botan/commit/d67783f3f8af0562301f1a5a36010e94f7b8d935?src=pr&el=desc) will **increase** coverage by `0.91%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/randombit/botan/pull/1830/graphs/tree.svg?width=650&token=VR6iYmSPH8&height=150&src=pr)](https://codecov.io/gh/randombit/botan/pull/1830?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1830      +/-   ##
==========================================
+ Coverage   90.76%   91.67%   +0.91%     
==========================================
  Files         523      523              
  Lines       55540    55540              
  Branches     5781     5781              
==========================================
+ Hits        50409    50917     +508     
+ Misses       5131     4623     -508
```


| [Impacted Files](https://codecov.io/gh/randombit/botan/pull/1830?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/lib/hash/sha2\_64/sha2\_64.cpp](https://codecov.io/gh/randombit/botan/pull/1830/diff?src=pr&el=tree#diff-c3JjL2xpYi9oYXNoL3NoYTJfNjQvc2hhMl82NC5jcHA=) | `38.54% <0%> (-60.34%)` | :arrow_down: |
| [src/lib/hash/sha3/sha3.cpp](https://codecov.io/gh/randombit/botan/pull/1830/diff?src=pr&el=tree#diff-c3JjL2xpYi9oYXNoL3NoYTMvc2hhMy5jcHA=) | `53.33% <0%> (-45.19%)` | :arrow_down: |
| [src/lib/stream/chacha/chacha.cpp](https://codecov.io/gh/randombit/botan/pull/1830/diff?src=pr&el=tree#diff-c3JjL2xpYi9zdHJlYW0vY2hhY2hhL2NoYWNoYS5jcHA=) | `100% <0%> (+0.93%)` | :arrow_up: |
| [src/lib/hash/sha2\_32/sha2\_32.cpp](https://codecov.io/gh/randombit/botan/pull/1830/diff?src=pr&el=tree#diff-c3JjL2xpYi9oYXNoL3NoYTJfMzIvc2hhMl8zMi5jcHA=) | `98.63% <0%> (+1.36%)` | :arrow_up: |
| [src/lib/block/threefish\_512/threefish\_512.cpp](https://codecov.io/gh/randombit/botan/pull/1830/diff?src=pr&el=tree#diff-c3JjL2xpYi9ibG9jay90aHJlZWZpc2hfNTEyL3RocmVlZmlzaF81MTIuY3Bw) | `100% <0%> (+3.12%)` | :arrow_up: |
| [src/lib/block/serpent/serpent.cpp](https://codecov.io/gh/randombit/botan/pull/1830/diff?src=pr&el=tree#diff-c3JjL2xpYi9ibG9jay9zZXJwZW50L3NlcnBlbnQuY3Bw) | `100% <0%> (+6.58%)` | :arrow_up: |
| [src/lib/utils/cpuid/cpuid\_x86.cpp](https://codecov.io/gh/randombit/botan/pull/1830/diff?src=pr&el=tree#diff-c3JjL2xpYi91dGlscy9jcHVpZC9jcHVpZF94ODYuY3Bw) | `83.63% <0%> (+7.27%)` | :arrow_up: |
| [src/lib/stream/chacha/chacha\_avx2/chacha\_avx2.cpp](https://codecov.io/gh/randombit/botan/pull/1830/diff?src=pr&el=tree#diff-c3JjL2xpYi9zdHJlYW0vY2hhY2hhL2NoYWNoYV9hdngyL2NoYWNoYV9hdngyLmNwcA==) | `100% <0%> (+100%)` | :arrow_up: |
| [src/lib/hash/sha2\_32/sha2\_32\_bmi2/sha2\_32\_bmi2.cpp](https://codecov.io/gh/randombit/botan/pull/1830/diff?src=pr&el=tree#diff-c3JjL2xpYi9oYXNoL3NoYTJfMzIvc2hhMl8zMl9ibWkyL3NoYTJfMzJfYm1pMi5jcHA=) | `100% <0%> (+100%)` | :arrow_up: |
| [...fish\_512/threefish\_512\_avx2/threefish\_512\_avx2.cpp](https://codecov.io/gh/randombit/botan/pull/1830/diff?src=pr&el=tree#diff-c3JjL2xpYi9ibG9jay90aHJlZWZpc2hfNTEyL3RocmVlZmlzaF81MTJfYXZ4Mi90aHJlZWZpc2hfNTEyX2F2eDIuY3Bw) | `100% <0%> (+100%)` | :arrow_up: |
| ... and [3 more](https://codecov.io/gh/randombit/botan/pull/1830/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/randombit/botan/pull/1830?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/randombit/botan/pull/1830?src=pr&el=footer). Last update [d67783f...43e447c](https://codecov.io/gh/randombit/botan/pull/1830?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

randombit: Have not had time for a full review but a few quick comments:

- Question: Is it possible to replace `scoped_CFType` with `unique_ptr` which is passed a release function? As done for example in https://github.com/randombit/botan/blob/master/src/lib/prov/openssl/openssl_ec.cpp#L141

- Use `BOTAN_ASSERT` or `BOTAN_ASSERT_EQUAL` instead of C stdlib `assert`. I think `assert_notnull` could be replaced by `BOTAN_ASSERT_NONNULL`.

- Question: is it possible this works on iOS as well?

reneme: Regarding the `scoped_CFType`: I failed to fully replace it with `unique_ptr`. Those `CFTypeRef`-like types are eventually typedefs of `void*`, so they already _are_ a pointer themselves. I tried to replace it like so:

```C++
template <typename T>
struct CFType_deleter {
  void operator()(T cftype) {
    if (cftype != nullptr) {
      CFRelease(cftype);
    }
  }
};

template <typename T>
using scoped_CFType = std::unique_ptr<typename std::remove_pointer<T>::type, CFType_deleter<T>>;
```

This sort-of works, but doesn't allow to take the address of the wrapped `CFTypeRef`. That's needed when using it as output parameter of the `SecKeychainOpen()` call. One could of course add a short-lived bare `CFTypeRef` and wrap it after the call, but I didn't like that approach.

reneme: I replaced the `assert()` calls with `BOTAN_ASSERT` and renamed the `assert_nonnull` and `assert_success` to `check_xxx`, as I intend to throw exceptions in those conditions.
reneme: Regarding iOS: I would imagine that this is mostly going to work on iOS as well. I think, accessing the keychain itself is handled a bit differently there, though. In particular there is only _one_ keychain in the whole system, so the calls to `SecKeychainOpen()` can probably be omitted. A quick glance in the documentation of the used API calls suggests that it should work from iOS 10.6 onwards with minor adaptions. That said, I don't have a test setup readily available to try it.
reneme: Finally, I found a somewhat major bummer that sounds pretty odd to me: In order to successfully find certificates using Apple's `SecItemCopyMatching()` call, I needed to "normalize" the DN objects. See [here](https://github.com/randombit/botan/pull/1830/commits/a27f1c2ef6efb7d90b2f2a44c5e08a13903d7407#diff-ce3705d4ff7e58a182e5ff3f3334b904R78) for the actual method.

Apple states in their [keychain implementation](https://opensource.apple.com/source/Security/Security-55471/sec/Security/SecCertificate.c.auto.html) that ASN.1 strings of the type 'PrintableString' are not case sensitive. Hence, they transform those strings into uppercase and furthermore strip leading and trailing whitespace. Also the certificate lookup seems to be rather slow: The current test body, takes nearly 500ms on my laptop, despite fetching just a handful certificates.

As an alternative of using Apple's APIs for certificate lookups, we might be better off to pull the entire system certificate store into the `Certificate_Store_In_Memory` implementation upon initialization. Initially, I rejected that idea as rather dirty. What do you think?
randombit: > Apple states in their [keychain implementation](https://opensource.apple.com/source/Security/Security-55471/sec/Security/SecCertificate.c.auto.html) that ASN.1 strings of the type 'PrintableString' are not case sensitive. Hence, they transform those strings into uppercase and furthermore strip leading and trailing whitespace.

So, this follows from the requirements of RFC 3280 which states that when comparing DNs, "attribute values in PrintableString are not case sensitive" as well as the whitespace normalization. And in fact X.509 proper requires such for all string types, and we implement that when comparing DNs (function `x500_name_cmp` in `parsing.cpp`).

However Apple's behavior here is completely broken: how could "case insensitive" turn out to mean "you must take into account casing"?!? I think they were just too lazy to implement the correct DN comparison algorithm and did this as a hack.

> Also the certificate lookup seems to be rather slow: The current test body, takes nearly 500ms on my laptop, despite fetching just a handful certificates.

Wow that is quite slow. Maybe worth timing each test individually to help diagnose the issue? Call `start_timer`/`end_timer` on your `Test::Result` instances to enable this.

> As an alternative of using Apple's APIs for certificate lookups, we might be better off to pull the entire system certificate store into the `Certificate_Store_In_Memory` implementation upon initialization. Initially, I rejected that idea as rather dirty. What do you think?

Yeah that seems messy, for example consider a long running server process: you would probably expect that adding a cert to the system store would make it available in the server process without a process restart.

I wonder too if perhaps there is some startup overhead to using the system store, say 400ms on first use, but then all subsequent operations are fast. You might be able to detect this using `botan-test --test-runs=10 <test names>` which runs the named tests several times in a row.
reneme: > I wonder too if perhaps there is some startup overhead to using the system store, say 400ms on first use, but then all subsequent operations are fast.

Thanks for the pointer with `start_timer`/`end_timer`. That was helpful. Turns out that the `.all_subjects()` test takes 95% of the full time, i.e. some 450ms. Which is unfortunate, but somewhat expected. It needs to pull in and parse _all_ trusted certificates, after all. Initialization of the `SecKeychain` object is in the ballpark of 1ms. So are individual certificate lookups.

> However Apple's behavior here is completely broken: how could "case insensitive" turn out to mean "you must take into account casing"?!?

Its annoying for sure. I'll have another look whether we can somehow do the "normalization" with some public Apple API, rather than re-implementing it. At least that would be less brittle, I guess.

> you would probably expect that adding a cert to the system store would make it available in the server process without a process restart.

That's exactly what I had in mind as well, when deciding against this option.
randombit: > I'll have another look whether we can somehow do the "normalization" with some public Apple API, rather than re-implementing it. At least that would be less brittle, I guess.

Looks like https://developer.apple.com/documentation/security/1396030-seccertificatecopynormalizedsubj?language=objc is the intended API for this. And yeah, good to use the public API here since they might decide to apply the same normalization to other string types in the future.

reneme: > Looks like https://developer.apple.com/documentation/security/1396030-seccertificatecopynormalizedsubj?language=objc is the intended API for this.

Unfortunately this API is deprecated and also takes a `SecCertificateRef`. In [Security/SecCertificate.c](https://opensource.apple.com/source/Security/Security-55471/sec/Security/SecCertificate.c.auto.html) I found an internal API called `SecDistinguishedNameCopyNormalizedContent` (defined in [Security/SecCertificateInternal.h](https://opensource.apple.com/source/Security/Security-55471/sec/Security/SecCertificateInternal.h.auto.html)) which does pretty much what we need. It is not defined in the public header though, so I needed to define it in the compilation unit like so:

```C++
extern "C" CFDataRef SecDistinguishedNameCopyNormalizedContent(CFDataRef distinguished_name);
```

With a bit of further mangling (the call strips the enclosing ASN.1 `SEQUENCE` from the DN data), it actually does what it should. However, I obviously don't feel comfortable linking against internal Apple's internal API... :-/
randombit: > However, I obviously don't feel comfortable linking against internal Apple's internal API...

Yeah that is no good. OK I guess we use the normalization you wrote and hope Apple doesn't change their minds about what is normalized or how.
reneme: Thanks for the review. I fixed the simple things right away and will do the rest tomorrow.
reneme: I'm done with applying your comments.
randombit: There are a few warnings:

```
src/lib/x509/certstor_system_macos/certstor_macos.cpp:245:49: warning: suggest braces around initialization of subobject [-Wmissing-braces]
         std::array<const void*, 2> keychains({ m_system_roots.get(),
                                                ^~~~~~~~~~~~~~~~~~~~~
                                                {
src/lib/x509/certstor_system_macos/certstor_macos.cpp:463:95: warning: unused parameter 'subject' [-Wunused-parameter]
std::shared_ptr<const X509_CRL> Certificate_Store_MacOS::find_crl_for(const X509_Certificate& subject) const
                                                                                              ^
2 warnings generated.
src/tests/test_certstor_macos.cpp:81:44: warning: empty parentheses interpreted as a function declaration [-Wvexing-parse]
      Botan::Certificate_Store_MacOS unused();
                                           ^~
src/tests/test_certstor_macos.cpp:81:44: note: remove parentheses to declare a variable
      Botan::Certificate_Store_MacOS unused();
                                           ^~
src/tests/test_certstor_macos.cpp:81:38: warning: unused function 'unused' [-Wunused-function]
      Botan::Certificate_Store_MacOS unused();
                                     ^
2 warnings generated.
```

Can you fix these and then squash all the commits?

Also, I realized now why all_subjects is slow. In addition to accessing every cert, it has to actually load/parse the certificates to work around Apple's DN mangling. This triggers a signature check on each CA certificate (this is necessary to test self-signed vs self-issued certificates). Likely if that logic were made to be lazy (computed just when/if X509_Certificate::is_self_signed is called) then the operation would be much faster.

Patch:
diff --git a/configure.py b/configure.py
index a4c2d2372..8c921ba5f 100755
--- a/configure.py
+++ b/configure.py
@@ -1073,7 +1073,7 @@ class CompilerInfo(InfoObject): # pylint: disable=too-many-instance-attributes
             infofile,
             [],
             ['cpu_flags', 'cpu_flags_no_debug', 'so_link_commands', 'binary_link_commands',
-             'mach_abi_linking', 'isa_flags', 'sanitizers', 'lib_flags'],
+             'mach_abi_linking', 'isa_flags', 'sanitizers'],
             {
                 'binary_name': None,
                 'linker_name': None,
@@ -1121,7 +1121,6 @@ class CompilerInfo(InfoObject): # pylint: disable=too-many-instance-attributes
         self.debug_info_flags = lex.debug_info_flags
         self.isa_flags = lex.isa_flags
         self.lang_flags = lex.lang_flags
-        self.lib_flags = lex.lib_flags
         self.linker_name = lex.linker_name
         self.mach_abi_linking = lex.mach_abi_linking
         self.macro_name = lex.macro_name
@@ -1175,10 +1174,9 @@ class CompilerInfo(InfoObject): # pylint: disable=too-many-instance-attributes
 
         return " ".join(sorted(flags))
 
-    def gen_lib_flags(self, options, variables):
+    def gen_shared_flags(self, options):
         """
-        Return any flags specific to building the library
-        (vs the cli or tests)
+        Return the shared library build flags, if any
         """
 
         def flag_builder():
@@ -1186,10 +1184,6 @@ class CompilerInfo(InfoObject): # pylint: disable=too-many-instance-attributes
                 yield self.shared_flags
                 yield self.visibility_build_flags
 
-            if 'debug' in self.lib_flags and options.with_debug_info:
-                yield process_template_string(self.lib_flags['debug'], variables, self.infofile)
-
-
         return ' '.join(list(flag_builder()))
 
     def gen_visibility_attribute(self, options):
@@ -1521,7 +1515,7 @@ def read_textfile(filepath):
         return ''.join(f.readlines())
 
 
-def process_template_string(template_text, variables, template_source):
+def process_template(template_file, variables):
     # pylint: disable=too-many-branches,too-many-statements
 
     """
@@ -1620,14 +1614,11 @@ def process_template_string(template_text, variables, template_source):
             return self.value_pattern.sub(insert_value, output) + '\n'
 
     try:
-        return SimpleTemplate(variables).substitute(template_text)
+        return SimpleTemplate(variables).substitute(read_textfile(template_file))
     except KeyError as e:
-        logging.error('Unbound var %s in template %s' % (e, template_source))
+        logging.error('Unbound var %s in template %s' % (e, template_file))
     except Exception as e: # pylint: disable=broad-except
-        logging.error('Exception %s during template processing file %s' % (e, template_source))
-
-def process_template(template_file, variables):
-    return process_template_string(read_textfile(template_file), variables, template_file)
+        logging.error('Exception %s during template processing file %s' % (e, template_file))
 
 def yield_objectfile_list(sources, obj_dir, obj_suffix):
     obj_suffix = '.' + obj_suffix
@@ -1966,6 +1957,8 @@ def create_template_vars(source_paths, build_paths, options, modules, cc, arch,
         'output_to_exe': cc.output_to_exe,
         'cc_macro': cc.macro_name,
 
+        'shared_flags': cc.gen_shared_flags(options),
+        'cmake_shared_flags': cmake_escape(cc.gen_shared_flags(options)),
         'visibility_attribute': cc.gen_visibility_attribute(options),
 
         'lib_link_cmd': cc.so_link_command_for(osinfo.basename, options) + external_link_cmd(),
@@ -2008,9 +2001,6 @@ def create_template_vars(source_paths, build_paths, options, modules, cc, arch,
         'mod_list': sorted([m.basename for m in modules])
     }
 
-    variables['lib_flags'] = cc.gen_lib_flags(options, variables)
-    variables['cmake_shared_flags'] = cmake_escape(variables['lib_flags'])
-
     if options.with_pkg_config:
         variables['botan_pkgconfig'] = os.path.join(build_paths.build_dir, 'botan-%d.pc' % (Version.major()))
 
@@ -3164,9 +3154,8 @@ def main(argv):
 
     setup_logging(options)
 
-    if not options.list_modules:
-        logging.info('Configuring to build Botan %s (revision %s)' % (
-            Version.as_string(), Version.vc_rev()))
+    logging.info('Configuring to build Botan %s (revision %s)' % (
+        Version.as_string(), Version.vc_rev()))
 
     source_paths = SourcePaths(os.path.dirname(argv[0]))
 
diff --git a/doc/manual/deprecated.rst b/doc/manual/deprecated.rst
index 3cd7c8c70..62c7c1b97 100644
--- a/doc/manual/deprecated.rst
+++ b/doc/manual/deprecated.rst
@@ -14,9 +14,6 @@ in the source.
 
 - The headers ``botan.h``, ``init.h``, ``lookup.h``, ``threefish.h``, ``sm2_enc.h``
 
-- Using a default output length for "SHAKE-128" and "SHAKE-256". Instead,
-  always specify the desired output length.
-
 - All or nothing package transform (``package.h``)
 
 - The TLS constructors taking `std::function` for callbacks. Instead
diff --git a/doc/manual/hash.rst b/doc/manual/hash.rst
index ec83b7f16..5de2d7c5c 100644
--- a/doc/manual/hash.rst
+++ b/doc/manual/hash.rst
@@ -109,7 +109,7 @@ Available Hash Functions
 ------------------------------
 
 The following cryptographic hash functions are implemented. If in doubt,
-any of SHA-384, SHA-3, or BLAKE2b are fine choices.
+any of SHA-384, SHA-3, BLAKE2b, or Skein-512 are fine choices.
 
 BLAKE2b
 ^^^^^^^^^
@@ -204,19 +204,9 @@ SHAKE (SHAKE-128, SHAKE-256)
 
 Available if ``BOTAN_HAS_SHAKE`` is defined.
 
-These are actually XOFs (extensible output functions) based on SHA-3, which can
-output a value of any byte length. For example "SHAKE-128(1024)" will produce
-1024 bits of output. The specified length must be a multiple of 8. Not
-specifying an output length, "SHAKE-128" defaults to a 128-bit output and
-"SHAKE-256" defaults to a 256-bit output.
-
-.. warning::
-    In the case of SHAKE-128, the default output length in insufficient
-    to ensure security. The choice of default lengths was a bug which is
-    currently retained for compatability; they should have been 256 and
-    512 bits resp to match SHAKE's security level. Using the default
-    lengths with SHAKE is deprecated and will be removed in a future major
-    release. Instead, always specify the desired output length.
+These are actually XOFs (extensible output functions) based on SHA-3,
+which can output a value of any byte length. For example "SHAKE-128(1024)"
+will produce 1024 bits of output.
 
 SM3
 ^^^^^^^^^^^^^^^
@@ -224,8 +214,7 @@ SM3
 Available if ``BOTAN_HAS_SM3`` is defined.
 
 Chinese national hash function, 256 bit output. Widely used in industry there.
-Fast and seemingly secure, but no reason to prefer it over SHA-2 or SHA-3 unless
-required.
+Fast and seemingly secure.
 
 Skein-512
 ^^^^^^^^^^^^^^^
@@ -250,18 +239,14 @@ Newly designed Russian national hash function. Due to use of input-dependent
 table lookups, it is vulnerable to side channels. There is no reason to use it
 unless compatibility is needed.
 
-.. warning::
-   The Streebog Sbox has recently been revealed to have a hidden structure which
-   interacts with its linear layer in a way which may provide a backdoor when
-   used in certain ways. Avoid Streebog if at all possible.
-
 Tiger
 ^^^^^^^^^^^^^^^
 
 Available if ``BOTAN_HAS_TIGER`` is defined.
 
 An older 192-bit hash function, optimized for 64-bit systems. Possibly
-vulnerable to side channels due to its use of table lookups.
+vulnerable to side channels due to its use of table lookups. Prefer Skein-512 or
+BLAKE2b in new code.
 
 Tiger supports variable length output (16, 20 or 24 bytes) and
 variable rounds (which must be at least 3). Default is 24 byte output
@@ -273,8 +258,9 @@ Whirlpool
 Available if ``BOTAN_HAS_WHIRLPOOL`` is defined.
 
 A 512-bit hash function standardized by ISO and NESSIE. Relatively slow, and due
-to the table based implementation it is potentially vulnerable to cache based
-side channels.
+to the table based implementation it is (unlike almost all other hashes)
+potentially vulnerable to cache based side channels. Prefer Skein-512 or BLAKE2b
+in new code.
 
 Hash Function Combiners
 ---------------------------
diff --git a/doc/oids.txt b/doc/oids.txt
deleted file mode 100644
index 674d88d84..000000000
--- a/doc/oids.txt
+++ /dev/null
@@ -1,42 +0,0 @@
-Private OID Assignments
-==========================
-
-The library uses some OIDs under a private arc assigned by IANA,
-1.3.6.1.4.1.25258
-
-Values currently assigned are::
-
-  randombit   OBJECT IDENTIFIER ::= { 1 3 6 1 4 1 25258 }
-
-  publicKey   OBJECT IDENTIFIER ::= { randombit 1 }
-
-  mceliece    OBJECT IDENTIFIER ::= { publicKey 3 }
-  -- {publicKey 4 } previously used as private X25519
-  xmss        OBJECT IDENTIFIER ::= { publicKey 5 }
-  gost-3410-with-sha256 OBJECT IDENTIFIER ::= { publicKey 6 1 }
-  kyber       OBJECT IDENTIFIER ::= { publicKey 7 }
-
-  symmetricKey OBJECT IDENTIFIER ::= { randombit 3 }
-
-  ocbModes OBJECT IDENTIFIER ::= { symmetricKey 2 }
-
-  aes-128-ocb      OBJECT IDENTIFIER ::= { ocbModes 1 }
-  aes-192-ocb      OBJECT IDENTIFIER ::= { ocbModes 2 }
-  aes-256-ocb      OBJECT IDENTIFIER ::= { ocbModes 3 }
-  serpent-256-ocb  OBJECT IDENTIFIER ::= { ocbModes 4 }
-  twofish-256-ocb  OBJECT IDENTIFIER ::= { ocbModes 5 }
-  camellia-128-ocb OBJECT IDENTIFIER ::= { ocbModes 6 }
-  camellia-192-ocb OBJECT IDENTIFIER ::= { ocbModes 7 }
-  camellia-256-ocb OBJECT IDENTIFIER ::= { ocbModes 8 }
-
-  sivModes OBJECT IDENTIFIER ::= { symmetricKey 4 }
-
-  aes-128-siv      OBJECT IDENTIFIER ::= { sivModes 1 }
-  aes-192-siv      OBJECT IDENTIFIER ::= { sivModes 2 }
-  aes-256-siv      OBJECT IDENTIFIER ::= { sivModes 3 }
-  serpent-256-siv  OBJECT IDENTIFIER ::= { sivModes 4 }
-  twofish-256-siv  OBJECT IDENTIFIER ::= { sivModes 5 }
-  camellia-128-siv OBJECT IDENTIFIER ::= { sivModes 6 }
-  camellia-192-siv OBJECT IDENTIFIER ::= { sivModes 7 }
-  camellia-256-siv OBJECT IDENTIFIER ::= { sivModes 8 }
-  sm4-128-siv      OBJECT IDENTIFIER ::= { sivModes 9 }
diff --git a/doc/todo.rst b/doc/todo.rst
index c4fbcd52f..31131c9b9 100644
--- a/doc/todo.rst
+++ b/doc/todo.rst
@@ -48,7 +48,6 @@ Public Key Crypto, Math
 * SPAKE2+ (draft-irtf-cfrg-spake2)
 * SPHINCS+
 * Dilithium lattice based signatures
-* Kyber lattice based KEM
 * X448 and Ed448
 * FHMQV
 * Use GLV decomposition to speed up secp256k1 operations
@@ -140,6 +139,7 @@ Compat Headers
 New C APIs
 ----------------------------------------
 
+* Expose NewHope and CECPQ1
 * X.509 CRL handling
 * PKCS10 requests
 * Certificate signing
diff --git a/news.rst b/news.rst
index 75e449b0e..d59307a66 100644
--- a/news.rst
+++ b/news.rst
@@ -4,17 +4,15 @@ Release Notes
 Version 2.10.0, Not Yet Released
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-* Redesigned ``Memory_Pool`` class, which services allocations out of a
-  set of pages locked into memory (using ``mlock``/``VirtualLock``). It is now
+* Redesigned Memory_Pool class, which services allocations out of a
+  set of pages locked into memory (using mlock/VirtualLock). It is now
   faster and with improved exploit mitigations. (GH #1800)
 
 * Add BMI2 implementations of SHA-512 and SHA-3 which improve performance by
   25-35% on common CPUs. (GH #1815)
 
-* Add a ``Thread_Pool`` class. It is now possible to run the tests in multiple
-  threads with `--test-threads=N`` flag to select the number of threads to use.
-  Use ``--test-threads=0`` to run with as many CPU cores as are available on the
-  current system. The default remains single threaded. (GH #1819)
+* Add a Thread_Pool class. It is now possible to run the tests in multiple
+  threads with `--test-threads=N`` flag. (GH #1819)
 
 * Some functions related to encoding and decoding BigInts have been deprecated.
   (GH #1817)
@@ -22,19 +20,15 @@ Version 2.10.0, Not Yet Released
 * Binary encoding and decoding of BigInts has been optimized by performing
   word-size operations when possible. (GH #1817)
 
-* Rename the exception ``Integrity_Failure`` to ``Invalid_Authentication_Tag`` to make
+* Rename the exception Integrity_Failure to Invalid_Authentication_Tag to make
   its meaning and usage more clear. The old name remains as a typedef. (GH #1816)
 
-* Support for using Boost ``filesystem`` and MSVC's ``std::filesystem`` have been
+* Support for using Boost filesystem and MSVC's std::filesystem have been
   removed, since already POSIX and Win32 versions had to be maintained for
   portability. (GH #1814)
 
-* Newly generated McEliece and XMSS keys now default to being encrypted using
-  SIV mode, support for which was added in 2.8.0. Previously GCM was used by
-  default for these algorithms.
-
 * Add a facility for sandboxing the command line util. Currently FreeBSD
-  (Capsicum) and OpenBSD (``pledge``) sandboxes are supported. (GH #1808)
+  (Capsicum) and OpenBSD (pledge) sandboxes are supported. (GH #1808)
 
 * Use ``if constexpr`` when available.
 
diff --git a/src/build-data/cc/msvc.txt b/src/build-data/cc/msvc.txt
index cb2ef304e..445050a3d 100644
--- a/src/build-data/cc/msvc.txt
+++ b/src/build-data/cc/msvc.txt
@@ -53,10 +53,6 @@ rdseed -> ""
 sha    -> ""
 </isa_flags>
 
-<lib_flags>
-debug -> "/Fd%{build_dir}/%{libname}%{lib_suffix}.pdb"
-</lib_flags>
-
 <so_link_commands>
 default       -> "$(LINKER) /DLL"
 default-debug -> "$(LINKER) /DLL /DEBUG"
diff --git a/src/build-data/cmake.in b/src/build-data/cmake.in
index 16c2b1aeb..1595401f6 100644
--- a/src/build-data/cmake.in
+++ b/src/build-data/cmake.in
@@ -33,7 +33,7 @@ option(ENABLED_LTO "If enabled link time optimization will be used" OFF)
 set(COMPILER_FEATURES_RELEASE %{cc_lang_flags} %{cc_compile_opt_flags} %{cxx_abi_opt_flags})
 set(COMPILER_FEATURES_DEBUG %{cc_lang_flags} %{cc_compile_debug_flags} %{cxx_abi_debug_flags})
 set(COMPILER_FEATURES $<$<NOT:$<CONFIG:DEBUG>>:${COMPILER_FEATURES_RELEASE}>  $<$<CONFIG:DEBUG>:${COMPILER_FEATURES_DEBUG}>)
-set(SHARED_FEATURES %{cmake_lib_flags})
+set(SHARED_FEATURES %{cmake_shared_flags})
 set(STATIC_FEATURES -DBOTAN_DLL=)
 set(COMPILER_WARNINGS %{cc_warning_flags})
 set(COMPILER_INCLUDE_DIRS build/include build/include/external)
diff --git a/src/build-data/makefile.in b/src/build-data/makefile.in
index f24b4dbbf..ba06abea6 100644
--- a/src/build-data/makefile.in
+++ b/src/build-data/makefile.in
@@ -12,7 +12,7 @@ ABI_FLAGS      = %{cc_sysroot} %{cxx_abi_flags}
 LANG_FLAGS     = %{cc_lang_flags}
 CXXFLAGS       = %{cc_compile_flags}
 WARN_FLAGS     = %{cc_warning_flags}
-LIB_FLAGS      = %{lib_flags}
+SO_OBJ_FLAGS   = %{shared_flags}
 LDFLAGS        = %{ldflags}
 
 EXE_LINK_CMD   = %{exe_link_cmd}
@@ -111,7 +111,7 @@ fuzzer_corpus_zip: fuzzer_corpus
 
 %{for lib_build_info}
 %{obj}: %{src}
-	$(CXX) $(LIB_FLAGS) $(BUILD_FLAGS) %{isa_flags} %{include_paths} %{dash_c} %{src} %{dash_o}$@
+	$(CXX) $(SO_OBJ_FLAGS) $(BUILD_FLAGS) %{isa_flags} %{include_paths} %{dash_c} %{src} %{dash_o}$@
 %{endfor}
 
 %{for cli_build_info}
diff --git a/src/build-data/os/darwin.txt b/src/build-data/os/darwin.txt
index 3b0147c22..b54dd99f1 100644
--- a/src/build-data/os/darwin.txt
+++ b/src/build-data/os/darwin.txt
@@ -21,6 +21,8 @@ commoncrypto
 sockets
 threads
 filesystem
+
+apple_keychain
 </target_features>
 
 <aliases>
diff --git a/src/cli/pk_crypt.cpp b/src/cli/pk_crypt.cpp
index 9b7f50746..62c1d519e 100644
--- a/src/cli/pk_crypt.cpp
+++ b/src/cli/pk_crypt.cpp
@@ -16,7 +16,6 @@
 #include <botan/oids.h>
 #include <botan/aead.h>
 #include <botan/pem.h>
-#include <botan/rng.h>
 
 namespace Botan_CLI {
 
diff --git a/src/lib/block/serpent/serpent.cpp b/src/lib/block/serpent/serpent.cpp
index 98e701738..35231cab5 100644
--- a/src/lib/block/serpent/serpent.cpp
+++ b/src/lib/block/serpent/serpent.cpp
@@ -10,7 +10,7 @@
 #include <botan/rotate.h>
 #include <botan/internal/serpent_sbox.h>
 
-#if defined(BOTAN_HAS_SERPENT_SIMD) || defined(BOTAN_HAS_SERPENT_AVX2)
+#if defined(BOTAN_HAS_SERPENT_SIMD)
   #include <botan/cpuid.h>
 #endif
 
diff --git a/src/lib/pbkdf/pwdhash.cpp b/src/lib/pbkdf/pwdhash.cpp
index 610ae7ac7..783ac7066 100644
--- a/src/lib/pbkdf/pwdhash.cpp
+++ b/src/lib/pbkdf/pwdhash.cpp
@@ -4,7 +4,7 @@
 * Botan is released under the Simplified BSD License (see license.txt)
 */
 
-#include <botan/pwdhash.h>
+#include <botan/pbkdf.h>
 #include <botan/exceptn.h>
 #include <botan/scan_name.h>
 
diff --git a/src/lib/pk_pad/emsa.cpp b/src/lib/pk_pad/emsa.cpp
index a3e448686..4b02776c2 100644
--- a/src/lib/pk_pad/emsa.cpp
+++ b/src/lib/pk_pad/emsa.cpp
@@ -196,12 +196,7 @@ std::string hash_for_emsa(const std::string& algo_spec)
       return pos_hash;
       }
 
-   // If we don't understand what this is return a safe default
-#if defined(BOTAN_HAS_SHA2_64)
-   return "SHA-512";
-#else
-   return "SHA-256";
-#endif
+   return "SHA-512"; // safe default if nothing we understand
    }
 
 }
diff --git a/src/lib/pubkey/pkcs8.cpp b/src/lib/pubkey/pkcs8.cpp
index d5add2adf..d299a98a4 100644
--- a/src/lib/pubkey/pkcs8.cpp
+++ b/src/lib/pubkey/pkcs8.cpp
@@ -159,24 +159,11 @@ choose_pbe_params(const std::string& pbe_algo, const std::string& key_algo)
    {
    if(pbe_algo.empty())
       {
-      /*
-      * For algorithms where we are using a non-RFC format anyway, default to
-      * SIV or GCM. For others (RSA, ECDSA, ...) default to something widely
-      * compatible.
-      */
-      const bool nonstandard_pk = (key_algo == "McEliece" || key_algo == "XMSS");
-
-      if(nonstandard_pk)
-         {
-#if defined(BOTAN_HAS_AEAD_SIV) && defined(BOTAN_HAS_SHA2_64)
-         return std::make_pair("AES-256/SIV", "SHA-512");
-#elif defined(BOTAN_HAS_AEAD_GCM) && defined(BOTAN_HAS_SHA2_64)
+      // Defaults:
+      if(key_algo == "Curve25519" || key_algo == "McEliece")
          return std::make_pair("AES-256/GCM", "SHA-512");
-#endif
-         }
-
-      // Default is something compatible with everyone else
-      return std::make_pair("AES-256/CBC", "SHA-256");
+      else // for everything else (RSA, DSA, ECDSA, GOST, ...)
+         return std::make_pair("AES-256/CBC", "SHA-256");
       }
 
    SCAN_Name request(pbe_algo);
diff --git a/src/lib/rng/system_rng/system_rng.cpp b/src/lib/rng/system_rng/system_rng.cpp
index 88a8e8f01..8abf8afdc 100644
--- a/src/lib/rng/system_rng/system_rng.cpp
+++ b/src/lib/rng/system_rng/system_rng.cpp
@@ -107,7 +107,7 @@ class System_RNG_Impl final : public RandomNumberGenerator
       void clear() override { /* not possible */ }
       std::string name() const override { return "crypto_ng"; }
    private:
-      BCRYPT_ALG_HANDLE m_prov;
+      BCRYPT_ALG_HANDLE m_handle;
    };
 
 #elif defined(BOTAN_TARGET_OS_HAS_ARC4RANDOM)
diff --git a/src/lib/stream/chacha/chacha_avx2/info.txt b/src/lib/stream/chacha/chacha_avx2/info.txt
index cb34095b1..3ec1e39d5 100644
--- a/src/lib/stream/chacha/chacha_avx2/info.txt
+++ b/src/lib/stream/chacha/chacha_avx2/info.txt
@@ -3,7 +3,3 @@ CHACHA_AVX2 -> 20180418
 </defines>
 
 need_isa avx2
-
-<requires>
-simd_avx2
-</requires>
diff --git a/src/lib/stream/shake_cipher/shake_cipher.cpp b/src/lib/stream/shake_cipher/shake_cipher.cpp
index f1920959e..b0e08a700 100644
--- a/src/lib/stream/shake_cipher/shake_cipher.cpp
+++ b/src/lib/stream/shake_cipher/shake_cipher.cpp
@@ -18,19 +18,17 @@ SHAKE_128_Cipher::SHAKE_128_Cipher() :
 
 void SHAKE_128_Cipher::cipher(const uint8_t in[], uint8_t out[], size_t length)
    {
-   const size_t SHAKE_128_BYTERATE = (1600-256)/8;
-
    verify_key_set(m_state.empty() == false);
 
-   while(length >= SHAKE_128_BYTERATE - m_buf_pos)
+   while(length >= m_buffer.size() - m_buf_pos)
       {
-      xor_buf(out, in, &m_buffer[m_buf_pos], SHAKE_128_BYTERATE - m_buf_pos);
-      length -= (SHAKE_128_BYTERATE - m_buf_pos);
-      in += (SHAKE_128_BYTERATE - m_buf_pos);
-      out += (SHAKE_128_BYTERATE - m_buf_pos);
+      xor_buf(out, in, &m_buffer[m_buf_pos], m_buffer.size() - m_buf_pos);
+      length -= (m_buffer.size() - m_buf_pos);
+      in += (m_buffer.size() - m_buf_pos);
+      out += (m_buffer.size() - m_buf_pos);
 
       SHA_3::permute(m_state.data());
-      copy_out_le(m_buffer.data(), SHAKE_128_BYTERATE, m_state.data());
+      copy_out_le(m_buffer.data(), m_buffer.size(), m_state.data());
 
       m_buf_pos = 0;
       }
@@ -40,13 +38,19 @@ void SHAKE_128_Cipher::cipher(const uint8_t in[], uint8_t out[], size_t length)
 
 void SHAKE_128_Cipher::key_schedule(const uint8_t key[], size_t length)
    {
-   const size_t SHAKE_128_BITRATE = (1600-256);
    m_state.resize(25);
-   m_buffer.resize(SHAKE_128_BITRATE/8);
+   m_buffer.resize((1600 - 256) / 8);
    zeroise(m_state);
 
-   const size_t S_pos = SHA_3::absorb(SHAKE_128_BITRATE, m_state, 0, key, length);
-   SHA_3::finish(SHAKE_128_BITRATE, m_state, S_pos, 0x1F, 0x80);
+   for(size_t i = 0; i < length/8; ++i)
+      {
+      m_state[i] ^= load_le<uint64_t>(key, i);
+      }
+
+   m_state[length/8] ^= 0x000000000000001F;
+   m_state[20]       ^= 0x8000000000000000;
+
+   SHA_3::permute(m_state.data());
    copy_out_le(m_buffer.data(), m_buffer.size(), m_state.data());
    }
 
@@ -74,7 +78,11 @@ void SHAKE_128_Cipher::seek(uint64_t)
 
 Key_Length_Specification SHAKE_128_Cipher::key_spec() const
    {
-   return Key_Length_Specification(1, 160);
+   /*
+   In principle SHAKE can accept arbitrary length inputs, but this
+   does not seem required for a stream cipher.
+   */
+   return Key_Length_Specification(16, 160, 8);
    }
 
 std::string SHAKE_128_Cipher::name() const
diff --git a/src/lib/stream/stream_cipher.cpp b/src/lib/stream/stream_cipher.cpp
index 340682ce2..692464723 100644
--- a/src/lib/stream/stream_cipher.cpp
+++ b/src/lib/stream/stream_cipher.cpp
@@ -82,7 +82,7 @@ std::unique_ptr<StreamCipher> StreamCipher::create(const std::string& algo_spec,
 #endif
 
 #if defined(BOTAN_HAS_SHAKE_CIPHER)
-   if(req.algo_name() == "SHAKE-128" || req.algo_name() == "SHAKE-128-XOF")
+   if(req.algo_name() == "SHAKE-128")
       {
       if(provider.empty() || provider == "base")
          return std::unique_ptr<StreamCipher>(new SHAKE_128_Cipher);
diff --git a/src/lib/utils/compiler.h b/src/lib/utils/compiler.h
index 3e41daed0..9dfbd9fa2 100644
--- a/src/lib/utils/compiler.h
+++ b/src/lib/utils/compiler.h
@@ -84,7 +84,7 @@
 * Define BOTAN_MALLOC_FN
 */
 #if defined(__GNUG__) || defined(__clang__)
-  #define BOTAN_MALLOC_FN __attribute__ ((malloc, returns_nonnull, alloc_size(1,2)))
+  #define BOTAN_MALLOC_FN __attribute__ ((malloc))
 #elif defined(_MSC_VER)
   #define BOTAN_MALLOC_FN __declspec(restrict)
 #else
diff --git a/src/lib/x509/certstor_system_macos/certstor_macos.cpp b/src/lib/x509/certstor_system_macos/certstor_macos.cpp
new file mode 100644
index 000000000..3a3652ae1
--- /dev/null
+++ b/src/lib/x509/certstor_system_macos/certstor_macos.cpp
@@ -0,0 +1,472 @@
+/*
+* Certificate Store
+* (C) 1999-2019 Jack Lloyd
+* (C) 2019      René Meusel
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include <botan/build.h>
+
+#include <algorithm>
+#include <array>
+
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreServices/CoreServices.h>
+
+#include <botan/assert.h>
+#include <botan/ber_dec.h>
+#include <botan/certstor_macos.h>
+#include <botan/data_src.h>
+#include <botan/der_enc.h>
+#include <botan/exceptn.h>
+#include <botan/x509_dn.h>
+
+namespace Botan {
+
+namespace {
+
+/**
+ * Abstract RAII wrapper for CFTypeRef-style object handles
+ * All of those xxxRef types are eventually typedefs to void*
+ */
+template<typename T>
+class scoped_CFType
+   {
+   public:
+      explicit scoped_CFType(T value)
+         : m_value(value)
+         {
+         }
+
+      scoped_CFType(const scoped_CFType<T>& rhs) = delete;
+      scoped_CFType(scoped_CFType<T>&& rhs) :
+         m_value(std::move(rhs.m_value))
+         {
+         rhs.m_value = nullptr;
+         }
+
+      ~scoped_CFType()
+         {
+         if(m_value)
+            {
+            CFRelease(m_value);
+            }
+         }
+
+      operator bool() const { return m_value != nullptr; }
+
+      void assign(T value)
+         {
+         BOTAN_ASSERT(m_value == nullptr, "scoped_CFType was not set yet");
+         m_value = value;
+         }
+
+      T& get() { return m_value; }
+      const T& get() const { return m_value; }
+
+   private:
+      T m_value;
+   };
+
+/**
+ * Apple's DN parser "normalizes" ASN1 'PrintableString' into upper-case values
+ * and strips leading, trailing as well as multiple white spaces.
+ * See: opensource.apple.com/source/Security/Security-55471/sec/Security/SecCertificate.c.auto.html
+ */
+X509_DN normalize(const X509_DN& dn)
+   {
+   X509_DN result;
+
+   for(const auto& rdn : dn.dn_info())
+      {
+      // TODO: C++14 - use std::get<ASN1_String>(), resp. std::get<OID>()
+      const auto oid = rdn.first;
+      auto str = rdn.second;
+
+      if(str.tagging() == ASN1_Tag::PRINTABLE_STRING)
+         {
+         std::string normalized;
+         normalized.reserve(str.value().size());
+         for(const char c : str.value())
+            {
+            if(c != ' ')
+               {
+               // store all 'normal' characters as upper case
+               normalized.push_back(::toupper(c));
+               }
+            else if(!normalized.empty() && normalized.back() != ' ')
+               {
+               // remove leading and squash multiple white spaces
+               normalized.push_back(c);
+               }
+            }
+
+         if(normalized.back() == ' ')
+            {
+            // remove potential remaining single trailing white space char
+            normalized.erase(normalized.end() - 1);
+            }
+
+         str = ASN1_String(normalized, str.tagging());
+         }
+
+      result.add_attribute(oid, str);
+      }
+
+   return result;
+   }
+
+std::string to_string(const CFStringRef cfstring)
+   {
+   const char* ccstr = CFStringGetCStringPtr(cfstring, kCFStringEncodingUTF8);
+
+   if(ccstr != nullptr)
+      {
+      return std::string(ccstr);
+      }
+
+   auto utf16_pairs    = CFStringGetLength(cfstring);
+   auto max_utf8_bytes = CFStringGetMaximumSizeForEncoding(utf16_pairs, kCFStringEncodingUTF8);
+
+   std::vector<char> cstr(max_utf8_bytes, '\0');
+   auto result = CFStringGetCString(cfstring,
+                                    cstr.data(), cstr.size(),
+                                    kCFStringEncodingUTF8);
+
+   return (result) ? std::string(cstr.data()) : std::string();
+   }
+
+std::string to_string(const OSStatus status)
+   {
+   scoped_CFType<CFStringRef> eCFString(
+      SecCopyErrorMessageString(status, nullptr));
+   return to_string(eCFString.get());
+   }
+
+void check_success(const OSStatus status, const std::string context)
+   {
+   if(errSecSuccess == status)
+      {
+      return;
+      }
+
+   throw Internal_Error(
+      std::string("failed to " + context + ": " + to_string(status)));
+   }
+
+template <typename T>
+void check_notnull(const scoped_CFType<T>& value, const std::string context)
+   {
+   if(value)
+      {
+      return;
+      }
+
+   throw Internal_Error(std::string("failed to ") + context);
+   }
+
+SecCertificateRef to_SecCertificateRef(CFTypeRef object)
+   {
+   if(!object || CFGetTypeID(object) != SecCertificateGetTypeID())
+      {
+      throw Internal_Error("cannot convert CFTypeRef to SecCertificateRef");
+      }
+
+   return static_cast<SecCertificateRef>(const_cast<void*>(object));
+   }
+
+/**
+ * Create a CFDataRef view over some provided std::vector<uint8_t. The data is
+ * not copied but the resulting CFDataRef uses the std::vector's buffer as data
+ * store. Note that the CFDataRef still needs to be manually freed, hence the
+ * scoped_CFType wrapper.
+ */
+scoped_CFType<CFDataRef> createCFDataView(const std::vector<uint8_t>& data)
+   {
+   return scoped_CFType<CFDataRef>(
+             CFDataCreateWithBytesNoCopy(kCFAllocatorDefault,
+                                         data.data(),
+                                         data.size(),
+                                         kCFAllocatorNull));
+   }
+
+/**
+ * Convert a SecCertificateRef object into a Botan::X509_Certificate
+ */
+std::shared_ptr<const X509_Certificate> readCertificate(SecCertificateRef cert)
+   {
+   scoped_CFType<CFDataRef> derData(SecCertificateCopyData(cert));
+   check_notnull(derData, "read extracted certificate");
+
+   // TODO: factor this out into a createDataSourceView() as soon as this class
+   //       gets a move-constructor
+   const auto data   = CFDataGetBytePtr(derData.get());
+   const auto length = CFDataGetLength(derData.get());
+
+   DataSource_Memory ds(data, length);
+   return std::make_shared<Botan::X509_Certificate>(ds);
+   }
+
+}
+
+/**
+ * Internal class implementation (i.e. Pimpl) to keep the required platform-
+ * dependent members of Certificate_Store_MacOS contained in this compilation
+ * unit.
+ */
+class Certificate_Store_MacOS_Impl
+   {
+   private:
+      static constexpr const char* system_roots =
+         "/System/Library/Keychains/SystemRootCertificates.keychain";
+      static constexpr const char* system_keychain =
+         "/Library/Keychains/System.keychain";
+
+   public:
+      using Query = std::vector<std::pair<CFStringRef, CFTypeRef>>;
+
+   public:
+      Certificate_Store_MacOS_Impl() :
+         m_policy(SecPolicyCreateBasicX509()),
+         m_system_roots(nullptr),
+         m_system_chain(nullptr),
+         m_keychains(nullptr)
+         {
+         check_success(SecKeychainOpen(system_roots, &m_system_roots.get()),
+                       "open system root certificates");
+         check_success(SecKeychainOpen(system_keychain, &m_system_chain.get()),
+                       "open system keychain");
+         check_notnull(m_system_roots, "open system root certificate chain");
+         check_notnull(m_system_chain, "open system certificate chain");
+
+         // m_keychains is merely a convenience list view into all open keychain
+         // objects. This list is required in prepareQuery().
+         std::array<const void*, 2> keychains{{
+               m_system_roots.get(),
+               m_system_chain.get()
+               }};
+
+         m_keychains.assign(
+            CFArrayCreate(kCFAllocatorDefault,
+                          keychains.data(),
+                          keychains.size(),
+                          &kCFTypeArrayCallBacks));
+         check_notnull(m_keychains, "initialize keychain array");
+         }
+
+      CFArrayRef keychains() const { return m_keychains.get(); }
+      SecPolicyRef policy() const { return m_policy.get(); }
+
+      /**
+       * Searches certificates in all opened system keychains. Takes an optional
+       * \p query that defines filter attributes to be searched for. That query
+       * is amended by generic attributes for "certificate filtering".
+       *
+       * \param query  a list of key-value pairs used for filtering
+       * \returns      an array with the resulting certificates or nullptr if
+       *               no matching certificate was found
+       */
+      scoped_CFType<CFArrayRef> search(Query query = Query()) const
+         {
+         scoped_CFType<CFDictionaryRef> fullQuery(
+            prepareQuery(std::move(query)));
+         check_notnull(fullQuery, "create search query");
+
+         scoped_CFType<CFArrayRef> result(nullptr);
+         auto status = SecItemCopyMatching(fullQuery.get(),
+                                           (CFTypeRef*)&result.get());
+         if(errSecItemNotFound == status)
+            {
+            return scoped_CFType<CFArrayRef>(nullptr);  // no matches
+            }
+
+         check_success(status, "look up certificate");
+         check_notnull(result, "look up certificate (invalid result value)");
+
+         return result;
+         }
+
+   protected:
+      /**
+       * Amends the user-provided search query with generic filter rules for
+       * the associated system keychains.
+       */
+      scoped_CFType<CFDictionaryRef> prepareQuery(Query pairs) const
+         {
+         std::vector<CFStringRef> keys({kSecClass,
+                                        kSecReturnRef,
+                                        kSecMatchLimit,
+                                        kSecMatchTrustedOnly,
+                                        kSecMatchSearchList,
+                                        kSecMatchPolicy});
+         std::vector<CFTypeRef>   values({kSecClassCertificate,
+                                          kCFBooleanTrue,
+                                          kSecMatchLimitAll,
+                                          kCFBooleanTrue,
+                                          keychains(),
+                                          policy()});
+         keys.reserve(pairs.size() + keys.size());
+         values.reserve(pairs.size() + values.size());
+
+         for(const auto& pair : pairs)
+            {
+            keys.push_back(pair.first);
+            values.push_back(pair.second);
+            }
+
+         BOTAN_ASSERT_EQUAL(keys.size(), values.size(), "valid key-value pairs");
+
+         return scoped_CFType<CFDictionaryRef>(CFDictionaryCreate(
+               kCFAllocatorDefault, (const void**)keys.data(),
+               (const void**)values.data(), keys.size(),
+               &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks));
+         }
+
+   private:
+      scoped_CFType<SecPolicyRef>    m_policy;
+      scoped_CFType<SecKeychainRef>  m_system_roots;
+      scoped_CFType<SecKeychainRef>  m_system_chain;
+      scoped_CFType<CFArrayRef>      m_keychains;
+   };
+
+
+Certificate_Store_MacOS::Certificate_Store_MacOS() :
+   m_impl(std::make_shared<Certificate_Store_MacOS_Impl>())
+   {
+   }
+
+std::vector<X509_DN> Certificate_Store_MacOS::all_subjects() const
+   {
+   scoped_CFType<CFArrayRef> result(m_impl->search());
+
+   if(!result)
+      {
+      return {};  // not a single certificate found in the keychain
+      }
+
+   const auto count = CFArrayGetCount(result.get());
+   BOTAN_ASSERT(count > 0, "subject result list contains data");
+
+   std::vector<X509_DN> output;
+   output.reserve(count);
+   for(unsigned int i = 0; i < count; ++i)
+      {
+      // Note: Apple's API provides SecCertificateCopyNormalizedSubjectSequence
+      //       which would have saved us from reading a Botan::X509_Certificate,
+      //       however, this function applies the same DN "normalization" as
+      //       stated above.
+      auto cfCert = to_SecCertificateRef(CFArrayGetValueAtIndex(result.get(), i));
+      auto cert = readCertificate(cfCert);
+      output.emplace_back(cert->subject_dn());
+      }
+
+   return output;
+   }
+
+std::shared_ptr<const X509_Certificate>
+Certificate_Store_MacOS::find_cert(const X509_DN& subject_dn,
+                                   const std::vector<uint8_t>& key_id) const
+   {
+   const auto certs = find_all_certs(subject_dn, key_id);
+
+   if(certs.empty())
+      {
+      return nullptr;  // certificate not found
+      }
+
+   if(certs.size() != 1)
+      {
+      throw Lookup_Error("ambiguous certificate result");
+      }
+
+   return certs.front();
+   }
+
+std::vector<std::shared_ptr<const X509_Certificate>> Certificate_Store_MacOS::find_all_certs(
+         const X509_DN& subject_dn,
+         const std::vector<uint8_t>& key_id) const
+   {
+   std::vector<uint8_t> dn_data;
+   DER_Encoder encoder(dn_data);
+   normalize(subject_dn).encode_into(encoder);
+
+   scoped_CFType<CFDataRef> dn_cfdata(createCFDataView(dn_data));
+   check_notnull(dn_cfdata, "create DN search object");
+
+   Certificate_Store_MacOS_Impl::Query query_params(
+      {
+         {kSecAttrSubject, dn_cfdata.get()}
+      });
+
+   scoped_CFType<CFDataRef> keyid_cfdata(createCFDataView(key_id));
+   check_notnull(keyid_cfdata, "create key ID search object");
+   if(!key_id.empty())
+      {
+      query_params.push_back({kSecAttrSubjectKeyID, keyid_cfdata.get()});
+      }
+
+   scoped_CFType<CFArrayRef> result(m_impl->search(std::move(query_params)));
+
+   if(!result)
+      {
+      return {};  // no certificates found
+      }
+
+   const auto count = CFArrayGetCount(result.get());
+   BOTAN_ASSERT(count > 0, "certificate result list contains data");
+
+   std::vector<std::shared_ptr<const X509_Certificate>> output;
+   output.reserve(count);
+   for(unsigned int i = 0; i < count; ++i)
+      {
+      auto cfCert = to_SecCertificateRef(CFArrayGetValueAtIndex(result.get(), i));
+      output.emplace_back(readCertificate(cfCert));
+      }
+
+   return output;
+   }
+
+std::shared_ptr<const X509_Certificate>
+Certificate_Store_MacOS::find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const
+   {
+   if(key_hash.size() != 20)
+      {
+      throw Invalid_Argument("Certificate_Store_MacOS::find_cert_by_pubkey_sha1 invalid hash");
+      }
+
+   scoped_CFType<CFDataRef> key_hash_cfdata(createCFDataView(key_hash));
+   check_notnull(key_hash_cfdata, "create key hash search object");
+
+   scoped_CFType<CFArrayRef> result(m_impl->search(
+      {
+         {kSecAttrPublicKeyHash, key_hash_cfdata.get()},
+      }));
+
+   if(!result)
+      {
+      return nullptr;  // no certificate found
+      }
+
+   const auto count = CFArrayGetCount(result.get());
+   BOTAN_ASSERT(count > 0, "certificate result list contains an object");
+
+   // `count` might be greater than 1, but we'll just select the first match
+   auto cfCert = to_SecCertificateRef(CFArrayGetValueAtIndex(result.get(), 0));
+   return readCertificate(cfCert);
+   }
+
+std::shared_ptr<const X509_Certificate>
+Certificate_Store_MacOS::find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const
+   {
+   BOTAN_UNUSED(subject_hash);
+   throw Not_Implemented("Certificate_Store_MacOS::find_cert_by_raw_subject_dn_sha256");
+   }
+
+std::shared_ptr<const X509_CRL> Certificate_Store_MacOS::find_crl_for(const X509_Certificate& subject) const
+   {
+   BOTAN_UNUSED(subject);
+   return {};
+   }
+
+}
diff --git a/src/lib/x509/certstor_system_macos/certstor_macos.h b/src/lib/x509/certstor_system_macos/certstor_macos.h
new file mode 100644
index 000000000..e7416e631
--- /dev/null
+++ b/src/lib/x509/certstor_system_macos/certstor_macos.h
@@ -0,0 +1,81 @@
+/*
+* Certificate Store
+* (C) 1999-2019 Jack Lloyd
+* (C) 2019      René Meusel
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#ifndef BOTAN_CERT_STORE_SYSTEM_MACOS_H_
+#define BOTAN_CERT_STORE_SYSTEM_MACOS_H_
+
+#include <memory>
+
+#include <botan/certstor.h>
+
+namespace Botan {
+
+class Certificate_Store_MacOS_Impl;
+
+/**
+* Certificate Store that is backed by the system trust store on macOS. This
+* opens a handle to the macOS keychain and serves certificate queries directly
+* from there.
+*/
+class BOTAN_PUBLIC_API(2, 10) Certificate_Store_MacOS final : public Certificate_Store
+   {
+   public:
+      Certificate_Store_MacOS();
+
+      Certificate_Store_MacOS(const Certificate_Store_MacOS&) = default;
+      Certificate_Store_MacOS(Certificate_Store_MacOS&&) = default;
+      Certificate_Store_MacOS& operator=(const Certificate_Store_MacOS&) = default;
+      Certificate_Store_MacOS& operator=(Certificate_Store_MacOS&&) = default;
+
+      /**
+      * @return DNs for all certificates managed by the store
+      */
+      std::vector<X509_DN> all_subjects() const override;
+
+      /**
+      * Find a certificate by Subject DN and (optionally) key identifier
+      * @return the first certificate that matches
+      */
+      std::shared_ptr<const X509_Certificate> find_cert(
+         const X509_DN& subject_dn,
+         const std::vector<uint8_t>& key_id) const override;
+
+      /**
+      * Find all certificates with a given Subject DN.
+      * Subject DN and even the key identifier might not be unique.
+      */
+      std::vector<std::shared_ptr<const X509_Certificate>> find_all_certs(
+               const X509_DN& subject_dn, const std::vector<uint8_t>& key_id) const override;
+
+      /**
+      * Find a certificate by searching for one with a matching SHA-1 hash of
+      * public key.
+      * @return a matching certificate or nullptr otherwise
+      */
+      std::shared_ptr<const X509_Certificate>
+      find_cert_by_pubkey_sha1(const std::vector<uint8_t>& key_hash) const override;
+
+      /**
+       * @throws Botan::Not_Implemented
+       */
+      std::shared_ptr<const X509_Certificate>
+      find_cert_by_raw_subject_dn_sha256(const std::vector<uint8_t>& subject_hash) const override;
+
+      /**
+       * Fetching CRLs is not supported by the keychain on macOS. This will
+       * always return an empty list.
+       */
+      std::shared_ptr<const X509_CRL> find_crl_for(const X509_Certificate& subject) const override;
+
+   private:
+      std::shared_ptr<Certificate_Store_MacOS_Impl> m_impl;
+   };
+
+}
+
+#endif
\ No newline at end of file
diff --git a/src/lib/x509/certstor_system_macos/info.txt b/src/lib/x509/certstor_system_macos/info.txt
new file mode 100644
index 000000000..46cc432e5
--- /dev/null
+++ b/src/lib/x509/certstor_system_macos/info.txt
@@ -0,0 +1,16 @@
+<defines>
+CERTSTOR_MACOS -> 20190207
+</defines>
+
+<os_features>
+apple_keychain
+</os_features>
+
+<header:public>
+certstor_macos.h
+</header:public>
+
+<frameworks>
+darwin -> CoreFoundation
+darwin -> Security
+</frameworks>
\ No newline at end of file


Test patch:
diff --git a/src/scripts/test_all_configs.py b/src/scripts/test_all_configs.py
index efa07430a..f90d7722e 100755
--- a/src/scripts/test_all_configs.py
+++ b/src/scripts/test_all_configs.py
@@ -49,18 +49,15 @@ def try_to_run(cmdline):
 
     if failed:
         print("FAILURE")
-        print(stdout)
         print(stdout.decode('ascii'))
         print(stderr.decode('ascii'))
         sys.stdout.flush()
-        #sys.exit(1)
 
     return not failed
 
-def run_test_build(configure_py, modules, include, jobs, run_tests):
-    config = [configure_py, '--without-documentation']
+def run_test_build(configure_py, modules, include, run_tests=False):
+    config = [configure_py]
 
-    print(modules)
     if include:
         config.append('--minimized')
         if modules:
@@ -71,13 +68,13 @@ def run_test_build(configure_py, modules, include, jobs, run_tests):
     if try_to_run(config) is False:
         return False
 
-    if try_to_run(['make', '-j', str(jobs)]) is False:
+    if try_to_run(['make', '-j', str(get_concurrency())]) is False:
         return False
 
     if run_tests is False:
         return True
 
-    return try_to_run(['./botan-test', '--test-threads=%d' % (jobs)])
+    return try_to_run(['./botan-test'])
 
 def main(args):
 
@@ -86,10 +83,6 @@ def main(args):
     parser = optparse.OptionParser()
 
     parser.add_option('--run-tests', default=False, action='store_true')
-    parser.add_option('--jobs', default=get_concurrency(),
-                      help="jobs to run (default %default)")
-
-    jobs = get_concurrency()
 
     (options, args) = parser.parse_args(args)
 
@@ -99,7 +92,7 @@ def main(args):
     modules = get_module_list(configure_py)
 
     cant_disable = ['block', 'hash', 'hex', 'mac', 'modes', 'rng', 'stream', 'utils', 'cpuid', 'entropy']
-    always_include = ['thread_utils', 'sha2_64']#, 'sha2_64', 'aes']
+    always_include = ['sha2_32', 'sha2_64', 'aes']
 
     failed = []
 
@@ -110,19 +103,17 @@ def main(args):
         extra = []
         if module == 'auto_rng':
             extra.append('dev_random')
-        if run_test_build(configure_py, [module] + always_include + extra, True, jobs, run_tests) is False:
+        if run_test_build(configure_py, [module] + always_include + extra, True, run_tests) is False:
             failed.append(module)
 
     for module in sorted(modules):
         if module in cant_disable or module in always_include:
             continue
-        if run_test_build(configure_py, [module], False, jobs, run_tests) is False:
+        if run_test_build(configure_py, [module], False, run_tests) is False:
             failed.append(module)
 
-    if len(failed) > 0:
-        print("Failed building with %s" % (' '.join(failed)))
-    else:
-        print("All configurations ok")
+    print("Failed building with %s", ' '.join(failed))
+
 
 if __name__ == '__main__':
     sys.exit(main(sys.argv))
diff --git a/src/tests/data/stream/shake128.vec b/src/tests/data/stream/shake128.vec
index 02ebfbcf2..b2740405c 100644
--- a/src/tests/data/stream/shake128.vec
+++ b/src/tests/data/stream/shake128.vec
@@ -3377,59 +3377,3 @@ Out = 0db7f7196eee8dd6994a16ded19cb09f05f89ccd2464333df2c017c6ca041fa0d54a4832a7
 Key = 0a13ad2c7a239b4ba73ea6592ae84ea9
 Out = 5feaf99c15f48851943ff9baa6e5055d8377f0dd347aa4dbece51ad3a6d9ce0c01aee9fe2260b80a4673a909b532adcdd1e421c32d6460535b5fe392a58d2634979a5a104d6c470aa3306c400b061db91c463b2848297bca2bc26d1864ba49d7ff949ebca50fbf79a5e63716dc82b600bd52ca7437ed774d169f6bf02e46487956fba2230f34cd2a0485484d
 
-Key = 0e
-Out = fa996dafaa208d72287c23bc4ed4bfd5
-
-Key = d9e8
-Out = c7211512340734235bb8d3c4651495aa
-
-Key = 1b3b6e
-Out = d7335497e4cd3666885edbb0824d7a75
-
-Key = 983f235a
-Out = a5597fff9277088ab56d4d5485023fec
-
-Key = 76fc16763f
-Out = a539f540f3e69e2d25a46ef0b1cf16dd
-
-Key = cb1b0103501c
-Out = a07d5bd636c2ecca4b239124ef9fcac0
-
-Key = 7216a825029da1
-Out = 9de6ffacf3e59693a3de81b02f7db77a
-
-Key = 7bf2fef375bcaff3
-Out = 5ef5578b89c50532131b7843de7329a3
-
-Key = fc948f094aa4b4e035
-Out = 9dfe4efaa2c43081702cbfe8636859ce
-
-Key = f83091fff290c4b333fb
-Out = 637e1cb6bdbebb4793447e81e2a2bb60
-
-Key = ae7cafada3e3e9f4314fa2
-Out = ae03aecba2995b344aa8e874808b5b7e
-
-Key = 84f6cb3dc77b9bf856caf54e
-Out = 56538d52b26f967bb9405e0f54fdf6e2
-
-Key = 2af26c79175fcca8e13fb783b1
-Out = 9cca3ec830b2d9ef819b377a96a6c94e
-
-Key = 52977e532bccdb89dfeff7e9e4ad
-Out = fbfba5c1e179df1469fcc8588ae5d2cc
-
-Key = e49706130f266af2946e496e0e722a
-Out = 4d84336572ccaabb827e81a0d887ec9a
-
-Key = d4d67b00ca51397791b81205d5582c0a
-Out = d0acfb2a14928caf8c168ae514925e4e
-
-Key = 6159096096f4cba469db393377c57d9be0
-Out = 281b6b007cb17235b8aab33781c4cf59
-
-Key = 43bdb11eac71031f02a11c15a1885fa42898
-Out = de68027da130663a73980e3525b88c75
-
-Key = 1e5dff69ea217bdd182fc8d6d25b74792db36d
-Out = e1905b84d9c060db55cc119f328695d9
diff --git a/src/tests/test_c25519.cpp b/src/tests/test_c25519.cpp
index 0b81b0aa5..88352f7ce 100644
--- a/src/tests/test_c25519.cpp
+++ b/src/tests/test_c25519.cpp
@@ -83,7 +83,7 @@ class Curve25519_Roundtrip_Test final : public Test
             Botan::Curve25519_PrivateKey a_priv_gen(Test::rng());
             Botan::Curve25519_PrivateKey b_priv_gen(Test::rng());
 
-#if defined(BOTAN_HAS_PKCS5_PBES2) && defined(BOTAN_HAS_AES) && defined(BOTAN_HAS_AEAD_GCM)
+#if defined(BOTAN_HAS_AES) && defined(BOTAN_HAS_AEAD_GCM)
             // Then serialize to encrypted storage
 
             const std::string a_pass = "alice pass";
diff --git a/src/tests/test_certstor_macos.cpp b/src/tests/test_certstor_macos.cpp
new file mode 100644
index 000000000..243a150da
--- /dev/null
+++ b/src/tests/test_certstor_macos.cpp
@@ -0,0 +1,331 @@
+/*
+* (C) 1999-2019 Jack Lloyd
+* (C) 2019      René Meusel
+*
+* Botan is released under the Simplified BSD License (see license.txt)
+*/
+
+#include "tests.h"
+
+#if defined(BOTAN_HAS_CERTSTOR_MACOS)
+
+#include <botan/certstor_macos.h>
+#include <botan/ber_dec.h>
+#include <botan/der_enc.h>
+#include <botan/hex.h>
+
+namespace Botan_Tests {
+
+namespace {
+
+Botan::X509_DN read_dn(const std::string hex)
+   {
+   Botan::X509_DN dn;
+   Botan::BER_Decoder decoder(Botan::hex_decode(hex));
+   dn.decode_from(decoder);
+   return dn;
+   }
+
+Botan::X509_DN get_dn()
+   {
+   // Public key fingerprint of "DST Root CA X3"
+   // This certificate is in the standard "System Roots" of any macOS setup,
+   // serves as the trust root of botan.randombit.net and expires on
+   // Thursday, 30. September 2021 at 16:01:15 Central European Summer Time
+   return read_dn("303f31243022060355040a131b4469676974616c205369676e6174757265"
+                  "20547275737420436f2e311730150603550403130e44535420526f6f7420"
+                  "4341205833");
+   }
+
+std::vector<uint8_t> get_key_id()
+   {
+   // this is the same as the public key SHA1
+   return Botan::hex_decode("c4a7b1a47b2c71fadbe14b9075ffc41560858910");
+   }
+
+Botan::X509_DN get_unknown_dn()
+   {
+   // thats a D-Trust "Test Certificate". It should be fairly likely that
+   // _nobody_ will _ever_ have that in their system keychain
+   // CN: D-TRUST Limited Basic Test PU CA 1-4 2016
+   return read_dn("305b310b300906035504061302444531153013060355040a0c0c442d5472"
+                  "75737420476d62483135303306035504030c2c442d5452555354204c696d"
+                  "6974656420426173696320526f6f74205465737420505520434120312032"
+                  "303135");
+   }
+
+Botan::X509_DN get_skewed_dn()
+   {
+   // This DN contains ASN.1 PrintableString fields that are not 'normalized'
+   // according to Apple's idea of a normalized PrintableString field:
+   //   (1) It has leading and trailing white space
+   //   (2) It contains multiple spaces between 'words'
+   return read_dn("304b312a3028060355040a132120204469676974616c2020205369676e61"
+                  "7475726520547275737420436f2e2020311d301b06035504031314202044"
+                  "5354202020526f6f742043412058332020");
+   }
+
+std::vector<uint8_t> get_unknown_key_id()
+   {
+   // this is the same as the public key SHA1
+   return Botan::hex_decode("785c0b67b536eeacbb2b27cf9123301abe7ab09a");
+   }
+
+Test::Result open_certificate_store()
+   {
+   Test::Result result("macOS Certificate Store - Open Keychain");
+
+   try
+      {
+      result.start_timer();
+      Botan::Certificate_Store_MacOS unused;
+      result.end_timer();
+      }
+   catch(std::exception& e)
+      {
+      result.test_failure(e.what());
+      }
+
+   result.test_success();
+
+   return result;
+   }
+
+Test::Result find_certificate_by_pubkey_sha1()
+   {
+   Test::Result result("macOS Certificate Store - Find Certificate by SHA1(pubkey)");
+
+   try
+      {
+      result.start_timer();
+      Botan::Certificate_Store_MacOS certstore;
+      auto cert = certstore.find_cert_by_pubkey_sha1(get_key_id());
+      result.end_timer();
+
+      if(result.test_not_null("found certificate", cert.get()))
+         {
+         auto cns = cert->subject_dn().get_attribute("CN");
+         result.test_is_eq("exactly one CN", cns.size(), 1ul);
+         result.test_eq("CN", cns.front(), "DST Root CA X3");
+         }
+      }
+   catch(std::exception& e)
+      {
+      result.test_failure(e.what());
+      }
+
+   result.test_throws("on invalid SHA1 hash data", [&]
+      {
+      Botan::Certificate_Store_MacOS certstore;
+      certstore.find_cert_by_pubkey_sha1({});
+      });
+
+   return result;
+   }
+
+Test::Result find_cert_by_subject_dn()
+   {
+   Test::Result result("macOS Certificate Store - Find Certificate by subject DN");
+
+   try
+      {
+      auto dn = get_dn();
+
+      result.start_timer();
+      Botan::Certificate_Store_MacOS certstore;
+      auto cert = certstore.find_cert(dn, std::vector<uint8_t>());
+      result.end_timer();
+
+      if(result.test_not_null("found certificate", cert.get()))
+         {
+         auto cns = cert->subject_dn().get_attribute("CN");
+         result.test_is_eq("exactly one CN", cns.size(), 1ul);
+         result.test_eq("CN", cns.front(), "DST Root CA X3");
+         }
+      }
+   catch(std::exception& e)
+      {
+      result.test_failure(e.what());
+      }
+
+   return result;
+   }
+
+Test::Result find_cert_by_subject_dn_and_key_id()
+   {
+   Test::Result result("macOS Certificate Store - Find Certificate by subject DN and key ID");
+
+   try
+      {
+      auto dn = get_dn();
+
+      result.start_timer();
+      Botan::Certificate_Store_MacOS certstore;
+      auto cert = certstore.find_cert(dn, get_key_id());
+      result.end_timer();
+
+      if(result.test_not_null("found certificate", cert.get()))
+         {
+         auto cns = cert->subject_dn().get_attribute("CN");
+         result.test_is_eq("exactly one CN", cns.size(), 1ul);
+         result.test_eq("CN", cns.front(), "DST Root CA X3");
+         }
+      }
+   catch(std::exception& e)
+      {
+      result.test_failure(e.what());
+      }
+
+   return result;
+   }
+
+Test::Result find_certs_by_subject_dn_and_key_id()
+   {
+   Test::Result result("macOS Certificate Store - Find Certificates by subject DN and key ID");
+
+   try
+      {
+      auto dn = get_dn();
+
+      result.start_timer();
+      Botan::Certificate_Store_MacOS certstore;
+      auto certs = certstore.find_all_certs(dn, get_key_id());
+      result.end_timer();
+
+      if(result.confirm("result not empty", !certs.empty()) &&
+            result.test_eq("exactly one certificate", certs.size(), 1))
+         {
+         auto cns = certs.front()->subject_dn().get_attribute("CN");
+         result.test_is_eq("exactly one CN", cns.size(), 1ul);
+         result.test_eq("CN", cns.front(), "DST Root CA X3");
+         }
+      }
+   catch(std::exception& e)
+      {
+      result.test_failure(e.what());
+      }
+
+   return result;
+   }
+
+Test::Result find_all_subjects()
+   {
+   Test::Result result("macOS Certificate Store - Find all Certificate Subjects");
+
+   try
+      {
+      result.start_timer();
+      Botan::Certificate_Store_MacOS certstore;
+      auto subjects = certstore.all_subjects();
+      result.end_timer();
+
+      if(result.confirm("result not empty", !subjects.empty()))
+         {
+         auto dn = get_dn();
+         auto needle = std::find_if(subjects.cbegin(),
+                                    subjects.cend(),
+                                    [=](const Botan::X509_DN &subject)
+            {
+            return subject == dn;
+            });
+
+         if(result.confirm("found expected certificate", needle != subjects.end()))
+            {
+            result.confirm("expected certificate", *needle == dn);
+            }
+         }
+      }
+   catch(std::exception& e)
+      {
+      result.test_failure(e.what());
+      }
+
+   return result;
+   }
+
+Test::Result no_certificate_matches()
+   {
+   Test::Result result("macOS Certificate Store - can deal with no matches (regression test)");
+
+   try
+      {
+      auto dn  = get_unknown_dn();
+      auto kid = get_unknown_key_id();
+
+      result.start_timer();
+      Botan::Certificate_Store_MacOS certstore;
+
+      auto certs = certstore.find_all_certs(dn, kid);
+      auto cert = certstore.find_cert(dn, kid);
+      auto pubk_cert = certstore.find_cert_by_pubkey_sha1(kid);
+      result.end_timer();
+
+      result.confirm("find_all_certs did not find the dummy", certs.empty());
+      result.confirm("find_cert did not find the dummy", !cert);
+      result.confirm("find_cert_by_pubkey_sha1 did not find the dummy", !pubk_cert);
+      }
+   catch(std::exception& e)
+      {
+      result.test_failure(e.what());
+      }
+
+   return result;
+   }
+
+Test::Result certificate_matching_with_dn_normalization()
+   {
+   Test::Result result("macOS Certificate Store - normalization of X.509 DN (regression test)");
+
+   try
+      {
+      auto dn  = get_skewed_dn();
+
+      result.start_timer();
+      Botan::Certificate_Store_MacOS certstore;
+
+      auto certs = certstore.find_all_certs(dn, std::vector<uint8_t>());
+      auto cert = certstore.find_cert(dn, std::vector<uint8_t>());
+      result.end_timer();
+
+      if(result.confirm("find_all_certs did find the skewed DN", !certs.empty()) &&
+            result.confirm("find_cert did find the skewed DN", cert != nullptr))
+         {
+         result.test_eq("it is the correct cert", certs.front()->subject_dn().get_first_attribute("CN"), "DST Root CA X3");
+         result.test_eq("it is the correct cert", cert->subject_dn().get_first_attribute("CN"), "DST Root CA X3");
+         }
+      }
+   catch(std::exception& e)
+      {
+      result.test_failure(e.what());
+      }
+
+   return result;
+   }
+
+class Certstor_macOS_Tests final : public Test
+   {
+   public:
+      std::vector<Test::Result> run() override
+         {
+         std::vector<Test::Result> results;
+
+         results.push_back(open_certificate_store());
+         results.push_back(find_certificate_by_pubkey_sha1());
+         results.push_back(find_cert_by_subject_dn());
+         results.push_back(find_cert_by_subject_dn_and_key_id());
+         results.push_back(find_certs_by_subject_dn_and_key_id());
+         results.push_back(find_all_subjects());
+         results.push_back(no_certificate_matches());
+         results.push_back(certificate_matching_with_dn_normalization());
+
+         return results;
+         }
+   };
+
+BOTAN_REGISTER_TEST("certstor_macos", Certstor_macOS_Tests);
+
+}
+
+}
+
+#endif
diff --git a/src/tests/test_ecies.cpp b/src/tests/test_ecies.cpp
index 5dd70afd7..5c95e9a0c 100644
--- a/src/tests/test_ecies.cpp
+++ b/src/tests/test_ecies.cpp
@@ -237,7 +237,7 @@ class ECIES_Tests final : public Text_Based_Test
 
 BOTAN_REGISTER_TEST("ecies", ECIES_Tests);
 
-#if defined(BOTAN_HAS_KDF1_18033) && defined(BOTAN_HAS_HMAC) && defined(BOTAN_HAS_AES) && defined(BOTAN_HAS_SHA2_64)
+#if defined(BOTAN_HAS_KDF1_18033) && defined(BOTAN_HAS_HMAC) && defined(BOTAN_HAS_AES)
 
 Test::Result test_other_key_not_set()
    {
diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp
index 2e59faec2..ce9dc94d5 100644
--- a/src/tests/test_ffi.cpp
+++ b/src/tests/test_ffi.cpp
@@ -1511,15 +1511,9 @@ class FFI_Unit_Tests final : public Test
          const std::string pbe_hash = "SHA-512";
 #endif
 
-#if defined(BOTAN_HAS_GCM)
-         const std::string pbe_cipher = "AES-256/GCM";
-#else
-         const std::string pbe_cipher = "AES-256/CBC";
-#endif
-
          TEST_FFI_OK(botan_privkey_export_encrypted_pbkdf_msec,
                      (priv, privkey.data(), &privkey_len, rng, "password",
-                      pbkdf_msec, &pbkdf_iters_out, pbe_cipher.c_str(), pbe_hash.c_str(), 0));
+                      pbkdf_msec, &pbkdf_iters_out, "AES-256/GCM", pbe_hash.c_str(), 0));
 
          if(pbe_hash == "Scrypt")
             {
diff --git a/src/tests/test_kdf.cpp b/src/tests/test_kdf.cpp
index 865a713d0..24211a32d 100644
--- a/src/tests/test_kdf.cpp
+++ b/src/tests/test_kdf.cpp
@@ -12,7 +12,6 @@
 
 #if defined(BOTAN_HAS_HKDF)
    #include <botan/hkdf.h>
-   #include <botan/hash.h>
 #endif
 
 namespace Botan_Tests {
@@ -75,14 +74,6 @@ class HKDF_Expand_Label_Tests final : public Text_Based_Test
          const std::string label = vars.get_req_str("Label");
          const std::vector<uint8_t> expected = vars.get_req_bin("Output");
 
-         auto hash = Botan::HashFunction::create(hash_name);
-
-         if(!hash)
-            {
-            result.test_note("Skipping test due to missing hash");
-            return result;
-            }
-
          Botan::secure_vector<uint8_t> output =
             Botan::hkdf_expand_label(hash_name,
                                      secret.data(), secret.size(),
diff --git a/src/tests/test_mceliece.cpp b/src/tests/test_mceliece.cpp
index e6e72a714..ed4b5a597 100644
--- a/src/tests/test_mceliece.cpp
+++ b/src/tests/test_mceliece.cpp
@@ -62,6 +62,8 @@ class McEliece_Keygen_Encrypt_Test final : public Text_Based_Test
             return result;
             }
 
+         result.start_timer();
+
          Botan::HMAC_DRBG rng("SHA-384");
          rng.initialize_with(keygen_seed.data(), keygen_seed.size());
          Botan::McEliece_PrivateKey mce_priv(rng, keygen_n, keygen_t);
diff --git a/src/tests/test_pk_pad.cpp b/src/tests/test_pk_pad.cpp
index 7c92cf876..5d56acbc8 100644
--- a/src/tests/test_pk_pad.cpp
+++ b/src/tests/test_pk_pad.cpp
@@ -115,17 +115,12 @@ class EMSA_unit_tests final : public Test
             {
             try
                {
-               const std::string hash_to_use = Botan::hash_for_emsa(pad);
                std::unique_ptr<Botan::EMSA> emsa_1(
-                  Botan::get_emsa(pad + "(" + hash_to_use + ")"));
+                  Botan::get_emsa(pad + "(" + Botan::hash_for_emsa(pad) + ")"));
                std::unique_ptr<Botan::EMSA> emsa_2(Botan::get_emsa(emsa_1->name()));
                name_tests.test_eq("EMSA_name_test for " + pad,
                      emsa_1->name(), emsa_2->name());
                }
-            catch(Botan::Lookup_Error&)
-               {
-               name_tests.test_note("Skipping test due to missing hash");
-               }
             catch(const std::exception& e)
                {
                name_tests.test_failure("EMSA_name_test for " + pad + ": " + e.what());
@@ -142,10 +137,6 @@ class EMSA_unit_tests final : public Test
                name_tests.test_failure("EMSA_name_test for " + pad + ": " +
                    "Could create EMSA with fantasy hash YYZ");
                }
-            catch(Botan::Lookup_Error&)
-               {
-               name_tests.test_note("Skipping test due to missing hash");
-               }
             catch(const std::exception& e)
                {
                name_tests.test_eq("EMSA_name_test for " + pad,
@@ -163,10 +154,6 @@ class EMSA_unit_tests final : public Test
                name_tests.test_eq("EMSA_name_test for " + pad,
                      emsa_1->name(), emsa_2->name());
                }
-            catch(Botan::Lookup_Error&)
-               {
-               name_tests.test_note("Skipping test due to missing hash");
-               }
             catch(const std::exception& e)
                {
                name_tests.test_failure("EMSA_name_test for " + pad + ": " + e.what());
diff --git a/src/tests/test_siv.cpp b/src/tests/test_siv.cpp
index 9810a68a5..d7380a9ba 100644
--- a/src/tests/test_siv.cpp
+++ b/src/tests/test_siv.cpp
@@ -34,15 +34,8 @@ class SIV_Tests final : public Text_Based_Test
 
          Test::Result result(algo + "/SIV");
 
-         auto cipher = Botan::BlockCipher::create(algo);
-
-         if(!cipher)
-            {
-            result.test_note("Skipping test due to missing cipher");
-            return result;
-            }
-
-         std::unique_ptr<Botan::SIV_Mode> siv(new Botan::SIV_Encryption(cipher.release()));
+         std::unique_ptr<Botan::SIV_Mode> siv(
+            new Botan::SIV_Encryption(Botan::BlockCipher::create(algo).release()));
 
          siv->set_key(key);
 
diff --git a/src/tests/unit_tls.cpp b/src/tests/unit_tls.cpp
index 28b09324f..e50f21aba 100644
--- a/src/tests/unit_tls.cpp
+++ b/src/tests/unit_tls.cpp
@@ -847,7 +847,7 @@ class TLS_Unit_Tests final : public Test
             test_all_versions("AES-128 RSA", results, *client_ses, *server_ses, *creds, "RSA", "AES-128", "SHA-256 SHA-1", etm_setting);
             test_all_versions("AES-128 ECDH", results, *client_ses, *server_ses, *creds, "ECDH", "AES-128", "SHA-256 SHA-1", etm_setting);
 
-#if defined(BOTAN_HAS_CAMELLIA) && defined(BOTAN_HAS_TLS_CBC)
+#if defined(BOTAN_HAS_CAMELLIA)
             test_all_versions("Camellia-128 RSA", results, *client_ses, *server_ses,
                               *creds, "RSA", "Camellia-128", "SHA-256 SHA-1", etm_setting);
             test_all_versions("Camellia-256 RSA SHA-2", results, *client_ses, *server_ses,
@@ -903,10 +903,8 @@ class TLS_Unit_Tests final : public Test
 
          client_ses->remove_all();
 
-#if defined(BOTAN_HAS_CAMELLIA) && defined(BOTAN_HAS_TLS_CBC)
+#if defined(BOTAN_HAS_CAMELLIA)
          test_modern_versions("Camellia-256 SHA-2", results, *client_ses, *server_ses, *creds, "RSA", "Camellia-256", "SHA-384 SHA-256");
-#endif
-#if defined(BOTAN_HAS_CAMELLIA) && defined(BOTAN_HAS_GCM)
          test_modern_versions("Camellia-128/GCM ECDH", results, *client_ses, *server_ses, *creds, "ECDH", "Camellia-128/GCM", "AEAD");
 #endif