Initial drop

Change-Id: I987ee6af6960bf2552628ee094cd656bf9e5f2fa
Reviewed-on: https://pigweed-review.googlesource.com/c/open-dice/+/31966
Reviewed-by: Ali Zhang <alizhang@google.com>
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..cde6910
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,2 @@
+# https://clang.llvm.org/docs/ClangFormatStyleOptions.html
+BasedOnStyle: Google
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7aeb491
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.clangd
+compile_commands.json
+out
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..5d10cdd
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,15 @@
+[submodule "boringssl"]
+	path = third_party/boringssl/src
+	url = https://boringssl.googlesource.com/boringssl
+[submodule "mbedtls"]
+	path = third_party/mbedtls/src
+	url = https://github.com/ARMmbed/mbedtls.git
+[submodule "cn-cbor"]
+	path = third_party/cn-cbor/src
+	url = https://github.com/jimsch/cn-cbor.git
+[submodule "cose-c"]
+	path = third_party/cose-c/src
+	url = https://github.com/cose-wg/COSE-C.git
+[submodule "pigweed"]
+	path = third_party/pigweed/src
+	url = https://pigweed.googlesource.com/pigweed/pigweed
diff --git a/.gn b/.gn
new file mode 100644
index 0000000..52124fd
--- /dev/null
+++ b/.gn
@@ -0,0 +1,15 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+buildconfig = "//BUILDCONFIG.gn"
diff --git a/BUILD.gn b/BUILD.gn
new file mode 100644
index 0000000..98c5f8a
--- /dev/null
+++ b/BUILD.gn
@@ -0,0 +1,386 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("//build_overrides/pigweed.gni")
+import("$dir_pw_bloat/bloat.gni")
+import("$dir_pw_build/target_types.gni")
+import("$dir_pw_unit_test/test.gni")
+
+group("default") {
+  deps = [
+    ":fuzzers(//toolchains:host_fuzz)",
+    ":optimized_libs(//toolchains:host_optimized)",
+    ":tests_run(//toolchains:host_debug)",
+  ]
+}
+
+pw_source_set("utils") {
+  public = [
+    "include/dice/dice.h",
+    "include/dice/utils.h",
+  ]
+  sources = [ "src/utils.c" ]
+}
+
+pw_static_library("dice_standalone") {
+  public = [ "include/dice/dice.h" ]
+  sources = [ "src/dice.c" ]
+}
+
+pw_static_library("dice_with_boringssl_ops") {
+  public = [ "include/dice/dice.h" ]
+  sources = [
+    "src/boringssl_cert_op.c",
+    "src/boringssl_hash_kdf_ops.c",
+    "src/dice.c",
+  ]
+  deps = [
+    ":utils",
+    "//third_party/boringssl:crypto",
+  ]
+}
+
+pw_static_library("dice_with_mbedtls_ops") {
+  public = [ "include/dice/dice.h" ]
+  sources = [
+    "src/dice.c",
+    "src/mbedtls_ops.c",
+  ]
+  deps = [
+    ":utils",
+    "//third_party/mbedtls:mbedcrypto",
+  ]
+}
+
+pw_static_library("dice_with_cbor_cert") {
+  public = [ "include/dice/dice.h" ]
+  sources = [
+    "src/boringssl_hash_kdf_ops.c",
+    "src/cbor_cert_op.c",
+    "src/dice.c",
+  ]
+  deps = [
+    ":utils",
+    "//third_party/boringssl:crypto",
+    "//third_party/cn-cbor:cn-cbor",
+  ]
+}
+
+pw_static_library("dice_with_cbor_template_cert") {
+  public = [ "include/dice/dice.h" ]
+  sources = [
+    "src/boringssl_hash_kdf_ops.c",
+    "src/dice.c",
+    "src/template_cbor_cert_op.c",
+  ]
+  deps = [
+    ":utils",
+    "//third_party/boringssl:crypto",
+  ]
+}
+
+pw_static_library("dice_with_x509_template_cert") {
+  public = [ "include/dice/dice.h" ]
+  sources = [
+    "src/boringssl_hash_kdf_ops.c",
+    "src/dice.c",
+    "src/template_cert_op.c",
+  ]
+  deps = [
+    ":utils",
+    "//third_party/boringssl:crypto",
+  ]
+}
+
+pw_source_set("test_utils") {
+  public = [ "include/dice/test_utils.h" ]
+  sources = [ "src/test_utils.cc" ]
+  deps = [
+    ":utils",
+    "$dir_pw_string:pw_string",
+  ]
+  public_deps = [
+    "//third_party/boringssl:crypto",
+    "//third_party/cose-c:cose-c",
+  ]
+  cflags = [ "-Wno-ignored-qualifiers" ]
+}
+
+pw_source_set("fuzz_utils") {
+  public = [
+    "include/dice/dice.h",
+    "include/dice/fuzz_utils.h",
+  ]
+  sources = [ "src/fuzz_utils.cc" ]
+}
+
+pw_test("dice_test") {
+  sources = [ "src/dice_test.cc" ]
+  deps = [
+    ":dice_standalone",
+    ":utils",
+    "//third_party/boringssl:crypto",
+  ]
+}
+
+pw_test("boringssl_ops_test") {
+  sources = [ "src/boringssl_ops_test.cc" ]
+  deps = [
+    ":dice_with_boringssl_ops",
+    ":test_utils",
+    ":utils",
+  ]
+}
+
+pw_executable("boringssl_ops_fuzzer") {
+  sources = [ "src/boringssl_ops_fuzzer.cc" ]
+  deps = [
+    ":dice_with_boringssl_ops",
+    ":fuzz_utils",
+    ":utils",
+  ]
+}
+
+pw_test("template_cert_op_test") {
+  sources = [ "src/template_cert_op_test.cc" ]
+  deps = [
+    ":dice_with_x509_template_cert",
+    ":test_utils",
+    ":utils",
+  ]
+}
+
+pw_executable("template_cert_op_fuzzer") {
+  sources = [ "src/template_cert_op_fuzzer.cc" ]
+  deps = [
+    ":dice_with_x509_template_cert",
+    ":fuzz_utils",
+    ":utils",
+  ]
+}
+
+pw_test("cbor_cert_op_test") {
+  sources = [ "src/cbor_cert_op_test.cc" ]
+  deps = [
+    ":dice_with_cbor_cert",
+    ":test_utils",
+    ":utils",
+  ]
+}
+
+pw_executable("cbor_cert_op_fuzzer") {
+  sources = [ "src/cbor_cert_op_fuzzer.cc" ]
+  deps = [
+    ":dice_with_cbor_cert",
+    ":fuzz_utils",
+    ":utils",
+  ]
+}
+
+pw_test("template_cbor_cert_op_test") {
+  sources = [ "src/template_cbor_cert_op_test.cc" ]
+  deps = [
+    ":dice_with_cbor_template_cert",
+    ":test_utils",
+    ":utils",
+  ]
+}
+
+pw_executable("template_cbor_cert_op_fuzzer") {
+  sources = [ "src/template_cbor_cert_op_fuzzer.cc" ]
+  deps = [
+    ":dice_with_cbor_template_cert",
+    ":fuzz_utils",
+    ":utils",
+  ]
+}
+
+pw_test("mbedtls_ops_test") {
+  sources = [ "src/mbedtls_ops_test.cc" ]
+  deps = [
+    ":dice_with_mbedtls_ops",
+    ":test_utils",
+    ":utils",
+  ]
+}
+
+pw_executable("mbedtls_ops_fuzzer") {
+  sources = [ "src/mbedtls_ops_fuzzer.cc" ]
+  deps = [
+    ":dice_with_mbedtls_ops",
+    ":fuzz_utils",
+    ":utils",
+  ]
+}
+
+pw_test_group("tests") {
+  tests = [
+    ":boringssl_ops_test",
+    ":cbor_cert_op_test",
+    ":dice_test",
+    ":mbedtls_ops_test",
+    ":template_cbor_cert_op_test",
+    ":template_cert_op_test",
+  ]
+}
+
+group("fuzzers") {
+  deps = [
+    ":boringssl_ops_fuzzer",
+    ":cbor_cert_op_fuzzer",
+    ":mbedtls_ops_fuzzer",
+    ":template_cbor_cert_op_fuzzer",
+    ":template_cert_op_fuzzer",
+  ]
+}
+
+pw_static_library("empty_lib") {
+}
+
+pw_executable("empty_main") {
+  sources = [ "src/empty_main.c" ]
+}
+
+pw_executable("dice_standalone_main") {
+  sources = [ "src/dice_standalone_main.c" ]
+  deps = [
+    ":dice_standalone",
+    ":utils",
+  ]
+}
+
+pw_executable("dice_with_boringssl_ops_main") {
+  sources = [ "src/dice_with_boringssl_ops_main.c" ]
+  deps = [
+    ":dice_with_boringssl_ops",
+    ":utils",
+  ]
+}
+
+pw_executable("dice_with_mbedtls_ops_main") {
+  sources = [ "src/dice_with_mbedtls_ops_main.c" ]
+  deps = [
+    ":dice_with_mbedtls_ops",
+    ":utils",
+  ]
+}
+
+pw_executable("dice_with_cbor_cert_main") {
+  sources = [ "src/dice_with_cbor_cert_main.c" ]
+  deps = [
+    ":dice_with_cbor_cert",
+    ":utils",
+  ]
+}
+
+pw_executable("dice_with_cbor_template_cert_main") {
+  sources = [ "src/dice_with_cbor_template_cert_main.c" ]
+  deps = [
+    ":dice_with_cbor_template_cert",
+    ":utils",
+  ]
+}
+
+pw_executable("dice_with_x509_template_cert_main") {
+  sources = [ "src/dice_with_x509_template_cert_main.c" ]
+  deps = [
+    ":dice_with_x509_template_cert",
+    ":utils",
+  ]
+}
+
+pw_size_report("executable_size_report") {
+  title = "Executable sizes (includes thirdparty deps)"
+  base = ":empty_main"
+  binaries = [
+    {
+      target = ":dice_standalone_main"
+      label = "DiceMainFlow only (No Ops)"
+    },
+    {
+      target = ":dice_with_boringssl_ops_main"
+      label = "Boringssl Ops"
+      base = ":dice_standalone_main"
+    },
+    {
+      target = ":dice_with_mbedtls_ops_main"
+      label = "MbedTLS Ops"
+      base = ":dice_standalone_main"
+    },
+    {
+      target = ":dice_with_cbor_cert_main"
+      label = "Boringssl with CBOR Cert"
+      base = ":dice_with_boringssl_ops_main"
+    },
+    {
+      target = ":dice_with_cbor_template_cert_main"
+      label = "Boringssl with CBOR Template Cert"
+      base = ":dice_with_boringssl_ops_main"
+    },
+    {
+      target = ":dice_with_x509_template_cert_main"
+      label = "Boringssl with X.509 Template Cert"
+      base = ":dice_with_boringssl_ops_main"
+    },
+  ]
+}
+
+pw_size_report("library_size_report") {
+  title = "Library sizes (excludes thirdparty deps)"
+  base = ":empty_lib"
+  binaries = [
+    {
+      target = ":dice_standalone"
+      label = "DICE Standalone (No Ops)"
+    },
+    {
+      target = ":dice_with_boringssl_ops"
+      label = "Boringssl Ops"
+      base = ":dice_standalone"
+    },
+    {
+      target = ":dice_with_mbedtls_ops"
+      label = "MbedTLS Ops"
+      base = ":dice_standalone"
+    },
+    {
+      target = ":dice_with_cbor_cert"
+      label = "CBOR Cert"
+      base = ":dice_standalone"
+    },
+    {
+      target = ":dice_with_cbor_template_cert"
+      label = "CBOR Template Cert"
+      base = ":dice_standalone"
+    },
+    {
+      target = ":dice_with_x509_template_cert"
+      label = "X.509 Template Cert"
+      base = ":dice_standalone"
+    },
+  ]
+}
+
+group("optimized_libs") {
+  deps = [
+    ":dice_standalone",
+    ":dice_with_boringssl_ops",
+    ":dice_with_cbor_cert",
+    ":dice_with_cbor_template_cert",
+    ":dice_with_mbedtls_ops",
+    ":dice_with_x509_template_cert",
+    ":executable_size_report",
+    ":library_size_report",
+  ]
+}
diff --git a/BUILDCONFIG.gn b/BUILDCONFIG.gn
new file mode 100644
index 0000000..5a2ac67
--- /dev/null
+++ b/BUILDCONFIG.gn
@@ -0,0 +1,20 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+_pigweed_directory = {
+  import("//build_overrides/pigweed.gni")
+}
+
+# The default toolchain is not used in Pigweed builds.
+set_default_toolchain("${_pigweed_directory.dir_pw_toolchain}/dummy")
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d645695
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,202 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..17fc4cc
--- /dev/null
+++ b/README.md
@@ -0,0 +1,164 @@
+# Open Profile for DICE
+
+This repository contains the specification for the
+[Open Profile for DICE](docs/specification.md) along with production-quality
+code. This profile is a specialization of the
+[Hardware Requirements for a Device Identifier Composition Engine](https://trustedcomputinggroup.org/resource/hardware-requirements-for-a-device-identifier-composition-engine/)
+and
+[DICE Layering Architecture](https://trustedcomputinggroup.org/resource/dice-layering-architecture/)
+specifications published by the Trusted Computing Group (TCG). For readers
+already familiar with those specs, notable distinctives of this profile include:
+
+*   Separate CDIs for attestation and sealing use cases
+*   Categorized inputs, including values related to verified boot
+*   Certified UDS values
+*   X.509 or CBOR certificates
+
+## Mailing List
+
+You can find us (and join us!) at
+https://groups.google.com/g/open-profile-for-dice. We're happy to answer
+questions and discuss proposed changes or features.
+
+## Specification
+
+The specification can be found [here](docs/specification.md). It is versioned
+using a major.minor scheme. Compatibility is maintained across minor versions
+but not necessarily across major versions.
+
+## Code
+
+Production quality, portable C code is included. The main code is in
+[dice.h](include/dice/dice.h) and [dice.c](src/dice.c). Cryptographic and
+certificate generation operations are injected via a set of callbacks. Multiple
+implementations of these operations are provided, all equally acceptable.
+Integrators should choose just one of these, or write their own.
+
+Tests are included for all code and the build files in this repository can be
+used to build and run these tests.
+
+Disclaimer: This is not an officially supported Google product.
+
+### Thirdparty Dependencies
+
+Different implementations use different third party libraries. The third\_party
+directory contains build files and git submodules for each of these. The
+[bootstrap](bootstrap.sh) script will automatically initialize all submodules.
+
+### Building and Running Tests
+
+```bash
+$ source bootstrap.sh
+$ ninja -C out
+```
+
+The easiest way, and currently the only supported way, to build and run tests is
+from a [Pigweed](https://pigweed.googlesource.com/pigweed/pigweed/) environment
+on Linux. Pigweed does support other host platforms so it shouldn't be too hard
+to get this running on Windows for example, but we use Linux.
+
+There are two scripts to help set this up:
+
+*   [bootstrap.sh](bootstrap.sh) will initialize submodules, bootstrap a Pigweed
+    environment, and generate build files. This can take some time and may
+    download on the order of 1GB of dependencies so the normal workflow is to
+    just do this once.
+
+*   [activate.sh](activate.sh) quickly reactivates an environment that has been
+    previously bootstrapped.
+
+These scripts must be sourced into the current session: `source activate.sh`.
+
+In the environment, from the base directory of the dice-profile checkout, run
+`ninja -C out` to build everything and run all tests. You can also run `pw
+watch` which will build, run tests, and continue to watch for changes.
+
+This will build and run tests on the host using the clang toolchain. Pigweed
+makes it easy to configure other targets and toolchains. See
+[toolchains/BUILD.gn](toolchains/BUILD.gn) and the Pigweed documentation.
+
+### Porting
+
+The code is designed to be portable and should work with a variety of modern
+toolchains and in a variety of environments. The main code in dice.h and dice.c
+is C99; it uses uint8\_t, size\_t, and memcpy from the C standard library. The
+various ops implementations are as portable as their dependencies (often not C99
+but still very portable). Notably, this code uses designated initializers for
+readability. This is a feature available in C since C99 but missing from C++
+until C++20 where it appears in a stricter form.
+
+### Style
+
+The [Google C++ Style Guide](https://google.github.io/styleguide/cppguide.html)
+is used. A `.clang-format` file is provided for convenience.
+
+### Incorporating
+
+To incorporate the code into another project, there are a few options:
+
+*   Copy only the necessary code. For example:
+
+    1.  Take the main code as is: [include/dice/dice.h](include/dice/dice.h),
+        [src/dice.c](src/dice.c)
+
+    1.  Choose an implementation for crypto and certificate generation or choose
+        to write your own. If you choose the boringssl implementation, for
+        example, take [include/dice/utils.h](include/dice/utils.h),
+        [include/dice/boringssl_ops.h](include/dice/boringssl_ops.h),
+        [src/utils.c](src/utils.c), and
+        [src/boringssl_ops.c](src/boringssl_ops.c). Taking a look at the library
+        targets in BUILD.gn may be helpful.
+
+*   Add this repository as a git submodule and integrate into the project build,
+    optionally using the gn library targets provided.
+
+*   Integrate into a project already using Pigweed using the gn build files
+    provided.
+
+### Size Reports
+
+The build reports code size using
+[Bloaty McBloatface](https://github.com/google/bloaty) via the pw\_bloat Pigweed
+module. There are two reports generated:
+
+*   Library sizes - This report includes just the library code in this
+    repository. It shows the baseline DICE code with no ops selected, and it
+    shows the delta introduced by choosing various ops implementations. This
+    report **does not** include the size of the third party dependencies.
+
+*   Executable sizes - This report includes sizes for the library code in this
+    repository plus all dependencies linked into a simple main function which
+    makes a single DICE call with all-zero input. It shows the baseline DICE
+    code with no ops (and therefore no dependencies other than libc), and it
+    shows the delta introduced by choosing various ops implementations. This
+    report **does** include the size of the third party dependencies. Note that
+    rows specialized from 'Boringssl Ops' use that as a baseline for sizing.
+
+The reports will be in the build output, but you can also find the reports in
+`.txt` files in the build output. For example, `cat out/host_optimized/gen/*.txt
+| less` will display all reports.
+
+### Thread Safety
+
+This code does not itself use mutable global variables, or any other type of
+shared data structure so there is no thread-safety concerns. However, additional
+care is needed to ensure dependencies are configured to be thread-safe. For
+example, the current boringssl configuration defines
+OPENSSL\_NO\_THREADS\_CORRUPT\_MEMORY\_AND\_LEAK\_SECRETS\_IF\_THREADED, and
+that would need to be changed before running in a threaded environment.
+
+### Clearing Sensitive Data
+
+This code makes a reasonable effort to clear memory holding sensitive data. This
+may help with a broader strategy to clear sensitive data but it is not
+sufficient on its own. Here are a few things to consider.
+
+*   The caller of this code is responsible for buffers they own (of course).
+*   The ops implementations need to clear any copies they make of sensitive
+    data. Both boringssl and mbedtls attempt to zeroize but this may need
+    additional care to integrate correctly. For example, boringssl skips
+    optimization prevention when OPENSSL\_NO\_ASM is defined (and it is
+    currently defined).
+*   Sensitive data may remain in cache.
+*   Sensitive data may have been swapped out.
+*   Sensitive data may be included in a crash dump.
diff --git a/activate.sh b/activate.sh
new file mode 100755
index 0000000..2cfc4c2
--- /dev/null
+++ b/activate.sh
@@ -0,0 +1,20 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+# Reactivates an environment previously bootstrapped.
+#
+# Usage: source activate.sh
+
+# Activate the pigweed environment.
+. third_party/pigweed/src/activate.sh
diff --git a/bootstrap.sh b/bootstrap.sh
new file mode 100755
index 0000000..3404d07
--- /dev/null
+++ b/bootstrap.sh
@@ -0,0 +1,35 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+# Sets up an environment ready to build and run tests. For example, 'pw watch'
+# should work in this environment.
+#
+# Usage: source bootstrap.sh
+
+# Update submodules, initializing if necessary. This will pin all submodules to
+# the committed revision so don't run this with uncommitted changes in a
+# submodule. By convention, submodules are pinned to an upstream revision and
+# any changes should be submitted upstream. Local changes to submodules are not
+# carried long term, but patches waiting to land upstream may be applied
+# manually.
+git submodule update --init
+
+# Apply local submodule patches.
+git -C third_party/mbedtls/src am ../0001-Mark-basic-constraints-critical-as-appropriate.patch
+
+# Bootstrap the pigweed environment.
+. third_party/pigweed/src/bootstrap.sh
+
+# Setup the build.
+gn gen --export-compile-commands out
diff --git a/build_overrides/pigweed.gni b/build_overrides/pigweed.gni
new file mode 100644
index 0000000..182f81a
--- /dev/null
+++ b/build_overrides/pigweed.gni
@@ -0,0 +1,20 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+declare_args() {
+  # Location of the Pigweed repository.
+  dir_pigweed = "//third_party/pigweed/src"
+}
+
+import("$dir_pigweed/modules.gni")
diff --git a/docs/contributing.md b/docs/contributing.md
new file mode 100644
index 0000000..b00dbe5
--- /dev/null
+++ b/docs/contributing.md
@@ -0,0 +1,86 @@
+# Contributing
+
+We'd love to accept your patches and contributions to this project. There are
+just a few small guidelines you need to follow.
+
+## Contributor License Agreement
+
+Contributions to this project must be accompanied by a Contributor License
+Agreement. You (or your employer) retain the copyright to your contribution;
+this simply gives us permission to use and redistribute your contributions as
+part of the project. Head over to <https://cla.developers.google.com/> to see
+your current agreements on file or to sign a new one.
+
+You generally only need to submit a CLA once, so if you've already submitted one
+(even if it was for a different project), you probably don't need to do it
+again.
+
+## Code Reviews
+
+All submissions, including submissions by project members, require review. We
+use Gerrit for this purpose, we do not currently support GitHub pull requests.
+
+See the
+[Gerrit walkthrough](https://gerrit-review.googlesource.com/Documentation/intro-gerrit-walkthrough.html),
+[Gerrit user guide](https://gerrit-review.googlesource.com/Documentation/intro-user.html)
+and other
+[Gerrit documentation](https://gerrit-review.googlesource.com/Documentation/index.html)
+for more information. GitHub users may find the
+[walkthrough for GitHub users](https://gerrit-review.googlesource.com/Documentation/intro-gerrit-walkthrough-github.html)
+helpful.
+
+### Gerrit Commit Hook
+
+Set up the
+[Gerrit commit hook](https://gerrit-review.googlesource.com/tools/hooks/commit-msg)
+to automatically add a `Change-Id` tag to each commit. On Linux you can run:
+
+```bash
+$ f=`git rev-parse --git-dir`/hooks/commit-msg ; mkdir -p $(dirname $f) ; curl -Lo $f https://gerrit-review.googlesource.com/tools/hooks/commit-msg ; chmod +x $f
+```
+
+## Community Guidelines
+
+This project follows
+[Google's Open Source Community Guidelines](https://opensource.google/conduct/).
+
+## Source Code Headers
+
+Every file containing source code must include copyright and license
+information.
+
+Apache header for C and C++ files:
+
+```javascript
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+```
+
+Apache header for Python and GN files:
+
+```python
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+```
diff --git a/docs/specification.md b/docs/specification.md
new file mode 100644
index 0000000..477b118
--- /dev/null
+++ b/docs/specification.md
@@ -0,0 +1,923 @@
+# Open Profile for DICE
+
+v2.3
+
+[TOC]
+
+## Background
+
+The Trusted Computing Group (TCG) specifies
+[Hardware Requirements for a Device Identifier Composition Engine](https://trustedcomputinggroup.org/wp-content/uploads/Hardware-Requirements-for-Device-Identifier-Composition-Engine-r78_For-Publication.pdf)
+(DICE) which provides the context for this document. We'll call this TCG
+document the _TCG DICE specification_. Concepts like a Unique Device Secret
+(UDS) and a Compound Device Identifier (CDI) are used as defined in the TCG DICE
+specification.
+
+#### A Note on Nomenclature
+
+This document uses the term _hardware_ to refer to anything that is immutable by
+design after manufacturing. Code in mask ROM, for example, is _hardware_. The
+terms _firmware_, _software_ and _program_ are all interchangeable; they all
+refer to mutable code. Often we say _firmware_ for code that runs early in boot,
+and _program_ for a particular unit of code, but it's really all _software_.
+
+#### DICE Primer
+
+For those not familiar with DICE, here is a quick primer on the concepts:
+
+*   **UDS** - Unique Device Secret. This is a per-device hardware-level secret
+    accessible to the DICE but not accessible after the DICE runs. This is one
+    of the DICE inputs. Note that when [layering](#layering-details) DICE
+    computations the UDS is only used for the first computation when
+    transitioning from hardware to software. Mutable software must never have
+    access to the hardware UDS.
+*   **CDI** - Compound Device Identifier. This value represents the
+    hardware/software combination measured by the DICE. This is the DICE output
+    and is passed to the software which has been measured. This is a secret.
+*   **DICE** - Device Identifier Composition Engine. This is a process which
+    mixes the UDS with software hashes and other inputs to produce a CDI and
+    locks down further access to the UDS. This computation occurs at the point
+    of transition from hardware (e.g. ROM) to software (e.g. first bootloader),
+    but we can perform the same computation at the point of transition from one
+    program to another in general to extend CDIs throughout a system.
+
+DICE can be implemented with a simple HMAC with the UDS as the key, attributes
+of the target code or system as the input, and the output is the CDI. However,
+for a particular implementation there are questions that need to be addressed
+such as "what is in the input, exactly?", and "how should we use the CDI once we
+have it?". That's where this profile comes in, it fills in many of these
+details.
+
+## Overview
+
+This document specifies a DICE profile suitable for use in a variety of products
+and platforms. The [TCG DICE specification](#background) intentionally allows
+for flexibility in implementation; this document specifies many of these
+implementation details. This document also fills in various details the TCG DICE
+specification considers out of scope. In particular, this document specifies:
+
+*   Cryptographic mechanisms
+*   UDS size and provisioning
+*   DICE input details, including how DICE interacts with verified boot
+*   Additional requirements, including asymmetric key derivation and
+    certification
+
+#### Goals
+
+The main goals of this document are:
+
+*   Make it **easier to implement** DICE well, with quality and confidence.
+*   **Consistency for DICE implementers**, whether in hardware, firmware, or
+    software.
+*   **Consistency for attestation verifiers**. There will always be some details
+    in the certificate extensions that are specific to the target system, but
+    those differences can be minimized by adopting some conventions when
+    generating the certificates.
+
+#### Non-Goals
+
+This document is not intended to:
+
+*   Be a formal standard
+*   Limit the use of DICE-style mechanisms not described here
+
+## Architecture Diagram
+
+This architecture diagram shows the first DICE transition from hardware to
+software, and uses the UDS in the derivation of both the Attestation CDI and
+Sealing CDI. Subsequent DICE transitions would use the current CDI values in
+place of the UDS to compute the subsequent CDI values. See
+[Layering Details](#layering-details).
+
+![Architecture Diagram](images/architecture.png)
+
+## Use Cases
+
+This design is motivated by two use cases: **attestation** and **sealing**.
+_Attestation_ allows a computing device or program to provide verifiable
+evidence of its identity and operating state, including hardware identity,
+software image, security-relevant configuration, operating environment, etc.
+_Sealing_ allows a computing device or program to encrypt data in such a way
+that it can only be decrypted by the same device or program operating in the
+same state as at the time of encryption.
+
+With this design, sealing only works well in combination with some kind of
+verified boot system. For a more sophisticated example of sealing key
+generation, see
+[Appendix C: Versioned Sealing Keys](#appendix-c-versioned-sealing-keys).
+
+# Profile Design
+
+## Input Values
+
+For attestation, DICE inputs should represent all security-relevant properties
+of the target program. The target program is the program to which control will
+be passed, along with DICE outputs, after the DICE computations are complete.
+This profile defines the following types of input, each of which is represented
+by a fixed length value:
+
+1.  **Code (64 bytes)** - This input is computed by hashing the target code.
+    This is the traditional input described most clearly in the
+    [TCG DICE specification](#background). If a software image is too large to
+    load and hash entirely, then a descriptor of the code (like the root hash of
+    a hash tree) may be used instead. Note that this approach requires
+    additional ongoing enforcement to verify pages as they are loaded. A
+    canonical example of this is
+    [dm-verity](https://source.android.com/security/verifiedboot/dm-verity).
+2.  **Configuration Data (64 bytes)** - This input is a catch-all for any
+    security-relevant configuration or environment properties that characterize
+    the integrity of the system and can be used by an external party to validate
+    its identity and/or its operating state. This may capture verified boot
+    authority selection, device mode, boot location, chip status information,
+    instance identifiers, etc. This value may or may not be a hash of the actual
+    configuration data. When it is a hash, the original data must also be
+    included in certificates. It's ok for this input to be _not stable_, it may
+    change from one boot to the next.
+3.  **Authority Data (64 bytes)** - This input is computed by hashing a
+    representation of the verified boot trusted authority. For example, this may
+    be a public key, a hash of a public key, or a hash of a descriptor
+    containing a set of public keys. For many SoCs, this representation of the
+    trusted authority is programmed into one-time-programmable (OTP) memory. If
+    a code authorization mechanism is disabled or not supported, this input
+    should be 64 zero bytes. If multiple public keys are supported with runtime
+    selection, this input value must represent all of them. (This is so the
+    value remains stable across a key change, the actual key that was used
+    during boot should be included in the configuration data input value). The
+    authority input value is designed to be stable, it is very unlikely to
+    change during a device lifecycle.
+4.  **Mode Decision (1 byte)** - This input value is a single-byte mode value.
+    Valid mode values are: **0**: Not Configured, **1**: Normal, **2**: Debug,
+    **3**: Recovery. The mode is determined at runtime based on the other
+    inputs, and only the other inputs. This input is designed to capture a
+    configuration signal in a stable way, and to reflect important decisions a
+    device makes at runtime. In the sealing use case, this enables data to be
+    sealed separately under each mode. See
+    [Mode Value Details](#mode-value-details).
+5.  **Hidden Inputs (64 bytes)** - This optional input value is _hidden_ in the
+    sense that it does not appear in any certificate. It is used for both
+    attestation and sealing CDI derivation so it is expected to be stable; it
+    should not change under normal operation except when that change is an
+    intentional part of the device lifecycle. If not used, this value should be
+    all zero bytes. While this value can be anything, intended use cases
+    include:
+
+    *   Mixing in an additional secret which may be changed as part of the
+        device lifecycle, for example ownership transfer
+    *   Mixing in a rotation nonce or counter to control the rotation of
+        attestation keys and sealing keys
+    *   Mixing in stable instance IDs or other internal IDs which may provide
+        differentiation for sealing CDIs
+    *   Mixing in stable configuration inputs which appear in Configuration Data
+        but also should be used in the sealing CDI derivation
+
+## CDI Values
+
+The [TCG DICE specification](#background) refers to a single CDI, but this
+profile defines multiple CDIs with different characteristics which can be used
+for different use cases:
+
+1.  **Attestation CDI** - This CDI is derived from the combination of all input
+    values and will change across software updates or configuration changes.
+    This CDI is appropriate for attestation and is _mandatory_ for
+    implementations of this profile.
+2.  **Sealing CDI** - This CDI is derived from only the authority data, mode
+    decision, and hidden inputs because these are stable. It will reflect this
+    stability and will remain the same across software updates and some
+    configuration changes. This CDI is appropriate for sealing and is _optional_
+    for implementations of this profile.
+
+### CDI Certificates
+
+This profile requires the generation of a CDI certificate as part of the DICE
+flow. The subject key pair is derived from the Attestation CDI value for the
+target code. The authority key pair which signs the certificate is derived from
+the UDS or, after the initial hardware to software transition, from the
+Attestation CDI value for the current code (see
+[Layering Details](#layering-details)). The DICE flow outputs the CDI values and
+the generated certificate; the private key associated with the certificate may
+be optionally passed along with the CDI values to avoid the need for
+re-derivation by the target code. The UDS-derived public key is certified by an
+external authority during manufacturing to complete the certificate chain. See
+[Certificate Details](#certificate-details).
+
+As an example, if the CDI private key were used to sign a leaf certificate for
+an attestation key, the certificate chain may look like this:
+
+![Single Layer Certificate Chain Diagram](images/single-layer-cert-chain.png)
+
+## High-level DICE Flow
+
+The [TCG DICE specification](#background) outlines a four stage flow: measure,
+compute CDI, lock UDS, and transfer control. This profile expands on this to
+include operations for CDI certification. The expanded flow has the following
+steps:
+
+1.  Measure CDI input values and compute CDI values
+2.  Derive an asymmetric key pair from the UDS
+3.  Lock UDS
+4.  Derive an asymmetric key pair from the Attestation CDI
+5.  Generate a CDI certificate
+6.  Destroy the UDS-derived private key from step (2)
+7.  Transfer control to the target code, passing on the certificate and all CDI
+    values
+
+## Cryptography
+
+This profile requires three cryptographic primitives: a hash function, a key
+derivation function, and an asymmetric digital signature. The recommended
+defaults are [SHA-512](https://en.wikipedia.org/wiki/SHA-2),
+[HKDF](https://en.wikipedia.org/wiki/HKDF) (using SHA-512) and
+[Ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519). Since Ed25519 uses
+SHA-512 under the hood, using this combination means implementing only one hash
+function. See below for the full list of
+[acceptable algorithms](#acceptable-cryptographic-algorithms).
+
+The following pseudocode operations are used throughout this document:
+
+```py
+# A hash function. The input can be any length.
+hash = H(input)
+
+# Random salt values used as the 'salt' KDF argument (hex encoded).
+ASYM_SALT = 63B6A04D2C077FC10F639F21DA793844356CC2B0B441B3A77124035C03F8E1BE
+            6035D31F282821A7450A02222AB1B3CFF1679B05AB1CA5D1AFFB789CCD2B0B3B
+ID_SALT = DBDBAEBC8020DA9FF0DD5A24C83AA5A54286DFC263031E329B4DA148430659FE
+          62CDB5B7E1E00FC680306711EB444AF77209359496FCFF1DB9520BA51C7B29EA
+
+# A KDF operation with the given desired output length, input key material,
+# salt, and info.
+output = KDF(length, ikm, salt, info)
+
+# An asymmetric key pair derivation, either Ed25519 or ECDSA.
+# * The private key is derived using KDF(32, input, ASYM_SALT, "Key Pair").
+# * The public key is derived from the private key (per the chosen algorithm).
+private_key, public_key = ASYM_KDF(input)
+```
+
+### Computing CDI Values
+
+Each CDI value is 32 bytes in length and is computed using a KDF operation with
+the UDS or previous CDI value as the input key material argument and the
+relevant input measurement as the salt argument. The KDF info argument differs
+for each type of CDI.
+
+#### Attestation CDI
+
+The Attestation CDI input measurement is derived from the combination of all
+input values. The [input values](#input-values) are hashed in this order: code,
+config, authority, mode, hidden.
+
+```py
+CDI_Attest = KDF(32, UDS, H(code + config + authority + mode + hidden),
+                 "CDI_Attest")
+```
+
+#### Sealing CDI
+
+The Sealing CDI input measurement is similar but is derived from only the stable
+inputs. The [input values](#input-values) are hashed in this order: authority,
+mode, hidden.
+
+```py
+CDI_Seal = KDF(32, UDS, H(authority + mode + hidden), "CDI_Seal")
+```
+
+### Deriving Asymmetric Key Pairs
+
+There are two key pair derivations; one to derive from the UDS, and the other to
+derive from the Attestation CDI. When deriving from the UDS, the KDF input is
+simply the UDS.
+
+```py
+UDS_Private, UDS_Public = ASYM_KDF(UDS)
+```
+
+When deriving from Attestation CDI, the KDF input is simply the
+[CDI\_Attest](#attestation-cdi) value.
+
+```py
+CDI_Private, CDI_Public = ASYM_KDF(CDI_Attest)
+```
+
+Note: It is important that these two derivations remain consistent except for
+the input key material; this is what makes [layering](#layering-details)
+possible.
+
+### Deriving Identifiers
+
+There are a few cases where the DICE needs to generate an identifier for use in
+certificates. To ensure these identifiers are deterministic and require no
+additional DICE inputs, the identifiers are derived from the associated public
+key. The identifiers are 20 octets so they fit in the RFC 5280 serialNumber
+field constraints and the X520SerialNumber type when hex encoded. The big-endian
+high-order bit is cleared so the ASN.1 integer representation is always positive
+without padding.
+
+```py
+UDS_ID = KDF(20, UDS_Public, ID_SALT, "ID")
+CDI_ID = KDF(20, CDI_Public, ID_SALT, "ID")
+```
+
+Note: Like the public key derivations, it is important that the ID derivations
+remain consistent except for the input key material. This is because these are
+used in certificate issuer and subject fields and need to match when
+[layering](#layering-details).
+
+### Acceptable Cryptographic Algorithms
+
+#### Hash Algorithms
+
+Acceptable hash algorithms are:
+
+*   SHA-256, SHA-384, SHA-512
+*   SHA3-256, SHA3-384, SHA3-512
+
+#### Key Derivation Functions
+
+##### HKDF
+
+[HKDF](https://en.wikipedia.org/wiki/HKDF) can be used with any acceptable hash
+algorithm. The KDF inputs map exactly to HKDF parameters, by design. This is the
+recommended default.
+
+##### DRBG
+
+A
+[DRBG](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-90Ar1.pdf)
+can be used to implement the KDF operation. Depending on the DRBG implementation
+this may require UDS and CDI values larger than 256 bits to provide both _nonce_
+and _entropy_ inputs when instantiating the DRBG. The DRBG should be
+instantiated with a security strength of 256 bits. The sequence of DRBG
+functions {instantiate, generate, uninstantiate}, are used as a KDF operation.
+The mapping of inputs is as shown in the following table.
+
+HKDF Input | Corresponding DRBG Input
+---------- | ------------------------------------
+ikm        | Instantiate: Entropy Input and Nonce
+salt       | Generate: Additional Input
+info       | Instantiate: Personalization String
+
+##### OpenTitan Key Manager
+
+The
+[OpenTitan Key Manager](https://docs.opentitan.org/hw/ip/keymgr/doc/index.html)
+can be used as a KDF. See the OpenTitan documentation for details.
+
+#### Digital Signatures
+
+##### Ed25519
+
+[Ed25519](https://en.wikipedia.org/wiki/EdDSA#Ed25519) is the recommended
+default.
+
+##### ECDSA
+
+[ECDSA](https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm)
+can be used instead of Ed25519. When signing the CDI certificate, the random _k_
+required by ECDSA may be generated deterministically per
+[RFC6979](https://tools.ietf.org/html/rfc6979). One weakness of Ed25519 is that
+implementations may be susceptible to error injection
+([example](https://www.romailler.ch/ddl/10.1109_FDTC.2017.12_eddsa.pdf)).
+Another disadvantage of Ed25519 is that it is not [currently] FIPS 140-2
+certifiable. In any case, either algorithm is acceptable for this profile.
+
+The following [NIST](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf)
+curves are acceptable for use with ECDSA:
+
+*   P-256
+*   P-384
+
+## Layering Details
+
+This DICE profile is designed to be layered. That is, software that receives CDI
+values can in turn execute a DICE flow using those CDI values in place of the
+UDS value. The certificate generated by the next DICE layer can chain to the
+certificate generated by the previous DICE layer because the asymmetric key
+derivation is consistent across layers for authority and subject keys.
+
+### Computing Layered CDI Values
+
+When computing CDI values, the previous Attestation CDI or Sealing CDI is used
+as the input key material instead of the hardware UDS:
+
+```py
+CDI_Attest[n+1] = KDF(32, CDI_Attest[n], H(code + config + authority + mode + hidden), "CDI_Attest")
+CDI_Seal[n+1] = KDF(32, CDI_Seal[n], H(authority + mode + hidden), "CDI_Seal")
+```
+
+### Protecting Layered CDI Values
+
+Just like the UDS is locked in the DICE [flow](#high-level-dice-flow), previous
+layer CDIs must be destroyed, locked, or otherwise protected before control is
+passed to the next layer. Layer[n+1] must never obtain access to CDI[n] values
+and must not be able to use CDI[n] in any computation. For example, a layer[n]
+program cannot offer a service that uses CDI[n] to layer[n+1] programs. In some
+cases a layer[n] program will stay active and spawn multiple programs (for
+example, a kernel, TEE, or hypervisor). In these cases the CDI[n] values must be
+protected from all layer[n+1] programs for the duration they are in operation,
+and must be destroyed when no longer needed.
+
+### Generating Layered Certificates
+
+When generating certificates, the authority is the previous CDI key pair and the
+certificates chain together. So the certificate chain may look like this:
+
+![Multi Layer Certificate Chain Diagram](images/multi-layer-cert-chain.png)
+
+## UDS Details
+
+### Requirements
+
+In addition to the requirements described in the
+[TCG DICE specification](#background), this profile requires the following:
+
+*   The UDS is at least 256 bits in size and is full-entropy. This means the UDS
+    value has been conditioned from at least 512 bits of entropy.
+*   If a UDS has not been provisioned, a value of all zero bytes is used. This
+    convention enables provisioning testability since running a DICE on an
+    unprovisioned UDS will yield predictable outputs.
+*   UDS values and certificates must use one of the provisioning schemes
+    described in this section. The provisioning process is expected to occur
+    very seldom, likely once per device during manufacture. Hardware may or may
+    not support re-provisioning of the UDS.
+
+### Provisioning Scheme 1: Pre-generation
+
+In this scheme, the UDS and an associated certificate are pre-generated and
+injected during a manufacturing process in a controlled environment appropriate
+for the implementation or product. The pre-generation infrastructure does not
+retain UDS values after provisioning. This approach is designed to balance the
+risks and costs associated with provisioning between security and scale.
+Rationale is not described here in detail, but the primary benefits are:
+
+*   No in-factory CAs (which make revocation as difficult as pre-generation)
+*   On-device certificates (which enable offline use cases)
+
+Note: If the UDS is integrated with an SoC at the time of SoC manufacture, the
+issuer may be the SoC vendor. If the UDS is integrated at the time of device
+manufacture, the issuer may be the OEM.
+
+#### Provisioning Flow
+
+1.  [Pre-generation] Generate a random UDS
+2.  [Pre-generation] Derive UDS\_Public and generate an associated certificate
+    which has a subject matching the expected issuer field generated for CDI
+    certificates (see [X.509 UDS Certificates](#x_509-uds-certificates)).
+3.  [Manufacturing] Program the UDS to the hardware (and destroy the source
+    copy)
+4.  [Manufacturing] Test the DICE to ensure:
+    1.  The UDS certificate correctly chains to the CDI certificate
+    2.  The CDI values cannot be reproduced using a zero UDS
+5.  [Manufacturing] Write the certificate to device storage
+
+### Provisioning Scheme 2: Factory CA
+
+In some cases, it may be feasible and preferable to install a CA for UDS
+provisioning during an SoC or device manufacturing stage. In this scheme, the
+UDS is derived on-chip from internal and external entropy, at least 256 bits
+each. Internal entropy may be generated using a
+[PUF](https://en.wikipedia.org/wiki/Physical_unclonable_function), or generated
+once using an internal hardware RNG and stored, for example, in OTP memory.
+External entropy is injected once during manufacturing and stored, for example,
+in OTP memory. The UDS is derived at runtime on every boot from the combined
+entropy. The UDS derivation (i.e. conditioning) from internal and external
+entropy uses a KDF:
+
+```py
+UDS = KDF(32, internal_entropy, external_entropy, "UDS")
+```
+
+With this provisioning scheme, the device must output UDS\_Public so
+provisioning software can read the public key and issue a certificate.
+
+#### Provisioning Flow
+
+All steps occur during manufacturing.
+
+1.  Generate and inject external entropy; do not retain or inject to multiple
+    devices
+2.  Run the DICE flow and read the UDS\_Public key
+3.  Issue a certificate for UDS\_Public
+4.  Test the DICE to ensure:
+    1.  The UDS certificate correctly chains to the CDI certificate
+    2.  The CDI values cannot be reproduced using a zero UDS
+5.  Write the certificate to device storage
+
+### Provisioning Scheme 3: On-Demand Certification
+
+In some cases, the certificate may not need to be stored on the device or the
+device may not be capable of storing a certificate. In this scheme the UDS is
+derived in the same way as
+[Provisioning Scheme 2](#provisioning-scheme-2-factory-ca), and the UDS\_Public
+key must similarly be output by the device. A SHA-512 hash of the UDS\_Public
+key is retained in a secure database by the manufacturer.
+
+The manufacturer then operates or coordinates with an online CA to provide
+on-demand certification of UDS public keys. Acceptable approaches include but
+are not limited to:
+
+*   Manufacturer provides the list of UDS public key hashes to the CA. This has
+    the downside of revealing the total number of devices.
+*   Manufacturer operates a simple web service on a dedicated domain over HTTPS
+    which takes as input the hash of a UDS public key and provides as output a
+    boolean indicating whether or not the hash is valid and should be certified.
+
+The CA issues certificates for any valid UDS public key without requiring
+proof-of-possession from the caller, only requiring a signal of approval from
+the manufacturer. This allows a certificate chain to be requested by a CDI
+certificate verifier that received an incomplete chain from a device. The UDS
+certificate may be cached indefinitely by the device or by a verifier.
+
+#### Provisioning Flow
+
+1.  [Manufacturing] Generate and inject external entropy; do not retain or
+    inject to multiple devices
+2.  [Manufacturing] Run the DICE flow and read the UDS\_Public key
+3.  [Manufacturing] Retain H(UDS\_Public) in a secure database
+4.  [On-Demand] Send UDS\_Public to the CA (no proof-of-possession necessary)
+5.  [CA] Check that H(UDS\_Public) is approved by the manufacturer
+6.  [CA] Issue a certificate for UDS\_Public
+
+## Mode Value Details
+
+The following table describes the semantics of each mode.
+
+Mode           | Value | Description
+-------------- | ----- | -----------
+Not Configured | 0     | This mode indicates that at least one security mechanism has not been configured. This mode also acts as a catch-all for configurations which do not fit the other modes. Invalid mode values -- values not defined here -- should be treated like this mode.
+Normal         | 1     | This mode indicates the device is operating normally under secure configuration. This may mean, for example: Verified boot is enabled, verified boot authorities used for development or debug have been disabled, debug ports or other debug facilities have been disabled, and the device booted software from the normal primary source, for example, eMMC, not USB, network, or removable storage.
+Debug          | 2     | This mode indicates at least one criteria for Normal mode is not met and the device is not in a secure state.
+Recovery       | 3     | This mode indicates a recovery or maintenance mode of some kind. This may mean software is being loaded from an alternate source, or the device is configured to trigger recovery logic instead of a normal boot flow.
+
+## Configuration Input Value Details (Optional)
+
+The format and meaning of the 64-byte configuration input value is
+implementation dependent and may be a hash of more configuration data.
+Implementers may choose to use the following convention for the configuration
+input which covers a set of common security-relevant configuration.
+
+Field                           | Byte/Bits (MSB=0) | Description
+------------------------------- | ----------------- | -----------
+Verified Boot Enabled           | 0/0               | This bit indicates whether a verified boot feature is enabled. The bit is set if enabled, clear if disabled or not supported.
+Verified Boot Authority Enabled | 0/1-7             | These bits indicate which of the verified boot authorities available are enabled. The bit is set if the authority is enabled, clear if disabled. If a verified boot system is disabled or not supported, all bits are clear. The mapping of these bits to particular authorities is implementation dependent.
+Debug Ports Enabled             | 1                 | The bits of this byte each indicate that a debug port or feature is enabled. A bit is set if the port or feature is enabled, clear if disabled. The mapping of these bits to particular ports or features is implementation dependent.
+Boot Source                     | 2                 | This value indicates the boot source; that is, where the target software was loaded from. The mapping of this value to particular boot sources is implementation dependent but by convention 0 is used for the default boot source.
+Version                         | 3-4               | This value encodes target software version information. The format and interpretation of this value is implementation dependent.
+Reserved                        | 5-31              | These are reserved for future versions of this profile.
+Implementation Specific         | 32-63             | An implementation can use these bytes to represent any other security-relevant configuration.
+
+## Certificate Details
+
+This profile allows for two certificate options: standard X.509, or CBOR. The
+certificate type does not need to be consistent for all certificates in a
+certificate chain. Any certificate in the chain may be any type. Attestation
+infrastructure may place additional constraints on certificate type, but this
+profile does not.
+
+Regardless of type, UDS and CDI certificates are always semantically _CA
+certificates_ to enable use cases for certifying subsequent DICE
+[layers](#layering-details) or certifying attestation keys of some kind; the
+UDS\_Private and CDI\_Private keys are not intended to be used for any purpose
+other than signing certificates. In particular, this means CDI\_Private should
+not participate directly in attestation protocols, but should rather certify an
+attestation key. If a target software component does not launch additional
+software, the _pathLenConstraint_ field can be set to zero so certification of a
+subsequent CDI\_Public is not possible.
+
+When UDS and CDI certificates are standard X.509 certificates, they follow the
+profile specified in [RFC 5280](https://tools.ietf.org/html/rfc5280). When they
+are CBOR, they follow the IETF
+[CBOR Web Token](https://tools.ietf.org/html/rfc8392) (CWT) specification, and
+the [CBOR Object Signing and Encryption](https://tools.ietf.org/html/rfc8152)
+(COSE) specification.
+
+### X.509 UDS Certificates
+
+The following table describes all standard fields of a UDS certificate's
+tbsCertificate field that this profile requires. Fields omitted are
+implementation dependent, but must not break the ability to chain to a CDI
+Certificate.
+
+Field                | Description
+-------------------- | -----------
+version              | v3
+subject              | "SERIALNUMBER=\<UDS\_ID\>" where UDS\_ID is hex encoded lower case
+subjectPublicKeyInfo | When using Ed25519, the info per [RFC 8410](https://tools.ietf.org/html/rfc8410) and [RFC 8032](https://tools.ietf.org/html/rfc8032)
+extensions           | The standard extensions described below are included.
+
+##### UDS Standard Extensions
+
+Extension            | Critical     | Description
+-------------------- | ------------ | -----------
+subjectKeyIdentifier | non-critical | Set to UDS\_ID
+keyUsage             | critical     | Contains only keyCertSign
+basicConstraints     | critical     | The cA field is set to TRUE. The pathLenConstraint field is normally not included, but may be included and set to zero if it is known that no additional DICE [layers](#layering-details) exist.
+
+### X.509 CDI Certificates
+
+All standard fields of a CDI certificate and the tbsCertificate field are
+described in the following table. Notably, this certificate can be generated
+deterministically given a CDI\_Public key and the DICE input value details.
+
+Field                | Description
+-------------------- | -----------
+signatureAlgorithm   | id-Ed25519 per [RFC 8410](https://tools.ietf.org/html/rfc8410)
+signatureValue       | 64 byte Ed25519 signature per [RFC 8032](https://tools.ietf.org/html/rfc8032), using UDS\_Private or a previous CDI\_Private as the signing key
+version              | v3
+serialNumber         | CDI\_ID in ASN.1 INTEGER form
+signature            | id-Ed25519 per [RFC 8410](https://tools.ietf.org/html/rfc8410)
+issuer               | "SERIALNUMBER=\<UDS\_ID\>" where UDS\_ID is hex encoded lower case
+validity             | The DICE is not expected to have a reliable source of time when generating a certificate. The validity values are populated as follows: _notBefore_ can be any time known to be in the past; in the absence of a better value, "180322235959Z" can be used which is the date of publication of the [TCG DICE specification](#background), and _notAfter_ is set to the standard value used to indicate no well-known expiry date, "99991231235959Z".
+subject              | "SERIALNUMBER=\<CDI\_ID\>" where CDI\_ID is hex encoded lower case
+subjectPublicKeyInfo | When using Ed25519, the info per [RFC 8410](https://tools.ietf.org/html/rfc8410) and [RFC 8032](https://tools.ietf.org/html/rfc8032)
+issuerUniqueID       | Omitted
+subjectUniqueID      | Omitted
+extensions           | Standard extensions are included as well as a custom extension which holds information about the measurements used to derive CDI values. Both are described below.
+
+##### CDI Standard Extensions
+
+Extension              | Critical     | Description
+---------------------- | ------------ | -----------
+authorityKeyIdentifier | non-critical | Contains only keyIdentifier set to UDS\_ID
+subjectKeyIdentifier   | non-critical | Set to CDI\_ID
+keyUsage               | critical     | Contains only keyCertSign
+basicConstraints       | critical     | The cA field is set to TRUE. The pathLenConstraint field is normally not included, but may be included and set to zero if it is known that no additional DICE [layers](#layering-details) exist.
+
+##### CDI Custom Extension Fields
+
+Field     | Value
+--------- | -----
+extnID    | 1.3.6.1.4.1.11129.2.1.24 (The 1.3.6.1.4.1 is the [enterprise number](https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers), the 11129.2.1 is google.googleSecurity.certificateExtensions, and 24 is diceAttestationData assigned for this profile).
+critical  | FALSE
+extnValue | A [OpenDiceInput](#custom-extension-format) sequence
+
+#### Custom Extension Format
+
+The custom extension follows this ASN.1 format:
+
+```
+Mode ::= INTEGER (0..3)
+OpenDiceInput ::= SEQUENCE {
+  codeHash                [0] EXPLICIT OCTET STRING OPTIONAL,
+  codeDescriptor          [1] EXPLICIT OCTET STRING OPTIONAL,
+  configurationHash       [2] EXPLICIT OCTET STRING OPTIONAL,
+  configurationDescriptor [3] EXPLICIT OCTET STRING OPTIONAL,
+  authorityHash           [4] EXPLICIT OCTET STRING OPTIONAL,
+  authorityDescriptor     [5] EXPLICIT OCTET STRING OPTIONAL,
+  mode                    [6] EXPLICIT Mode OPTIONAL,
+}
+```
+
+All fields are explicitly tagged and optional to allow for flexibility and
+extensibility in the format itself. The actual semantics are as follows:
+
+*   **codeHash** - Required. This is the exact 64-byte code input value used to
+    compute CDI values.
+*   **codeDescriptor** - Optional. This field contains additional information
+    about the code input value. The format of this field is
+    implementation-specific. If this field is included then all the information
+    here must have been used to compute codeHash; i.e. a change in this value
+    implies a change in codeHash.
+*   **configurationHash** - Optional. If the configuration input is a hash, this
+    field contains that hash. If the configuration is not a hash, this field is
+    omitted. If present, this value is the exact configuration input used to
+    compute CDI values, and also matches H(configurationDescriptor).
+*   **configurationDescriptor** - Required. If the configuration input is a hash
+    this field contains the original configuration data that was hashed. If it
+    is not a hash, this field contains the exact 64-byte configuration input
+    value used to compute CDI values.
+*   **authorityHash** - Required. This is the exact 64-byte authority input
+    value used to compute CDI values.
+*   **authorityDescriptor** - Optional. This field contains additional
+    information about the authority input value. The format of this field is
+    implementation-specific. If this field is included then all the information
+    here must have been used to compute authorityHash; i.e. a change in this
+    value implies a change in authorityHash.
+*   **mode** - Required. This is the mode input value.
+
+### CBOR UDS Certificates
+
+A CBOR UDS certificate is a standard signed CWT. The following table lists all
+field constraints required by this profile in addition to the standard. The
+certificate is _untagged_, and it must be a _COSE\_Sign1_ message.
+
+Field | Description
+----- | -----------
+iss   | Required: The value is implementation dependent.
+sub   | Required: The value must be "\<UDS\_ID\>" where UDS\_ID is hex encoded lower case.
+aud   | Omitted
+
+#### Additional Fields
+
+The following table lists additional entries in the CWT. Note these have the
+same labels and semantics as the corresponding fields in
+[CBOR CDI certificates](#cbor-cdi-certificates).
+
+Field            | CBOR Label
+---------------- | ----------
+subjectPublicKey | -4670552
+keyUsage         | -4670553
+
+The _subjectPublicKey_ field contains the public key associated with the subject
+in the form of a COSE\_Key structure encoded to a CBOR byte string.
+
+The _keyUsage_ field contains a CBOR byte string the bits of which correspond to
+the [X.509 KeyUsage bits](https://tools.ietf.org/html/rfc5280#section-4.2.1.3)
+in little-endian byte order (i.e. bit 0 is the low-order bit of the first byte).
+For UDS certificates this should have only the keyCertSign bit set.
+
+### CBOR CDI Certificates
+
+A CBOR CDI certificate is a standard signed CWT with additional fields. The
+certificate is _untagged_, and it must be a _COSE\_Sign1_ message. The following
+table lists all constraints on standard fields required by this profile.
+
+Field | Description
+----- | -----------
+iss   | Required: The value must be "\<UDS\_ID\>" where UDS\_ID is hex encoded lower case.
+sub   | Required: The value must be "\<CDI\_ID\>" where CDI\_ID is hex encoded lower case.
+aud   | Omitted
+exp   | Omitted
+nbf   | Omitted when a reliable time source is not available
+iat   | Omitted when a reliable time source is not available
+cti   | Omitted
+
+#### Additional Fields
+
+The following table lists additional entries in the CWT. By convention, the
+private fields in the map are labeled using negative integers starting at
+-4670545.
+
+Field                   | CBOR Label
+----------------------- | ----------
+codeHash                | -4670545
+codeDescriptor          | -4670546
+configurationHash       | -4670547
+configurationDescriptor | -4670548
+authorityHash           | -4670549
+authorityDescriptor     | -4670550
+mode                    | -4670551
+subjectPublicKey        | -4670552
+keyUsage                | -4670553
+
+The _subjectPublicKey_ field contains the public key associated with the subject
+in the form of a COSE\_Key structure encoded to a CBOR byte string.
+
+The _keyUsage_ field contains a CBOR byte string the bits of which correspond to
+the [X.509 KeyUsage bits](https://tools.ietf.org/html/rfc5280#section-4.2.1.3)
+in little-endian byte order (i.e. bit 0 is the low-order bit of the first byte).
+For CDI certificates this should have only the keyCertSign bit set.
+
+All other fields have identical semantics to their counterparts in the
+[X.509 custom extension](#custom-extension-format). The encoding for each is a
+CBOR byte string including _mode_ which is a CBOR byte string holding a single
+byte (the advantage to using a byte string here is a consistent encoding size
+regardless of the value of mode).
+
+# Appendix A: Implementing on Existing Hardware
+
+This profile requires hardware changes to implement fully. However, there is
+still value in implementing it in software on top of existing hardware.
+Depending on the existing hardware capabilities, the security of the DICE root
+may be equivalent to a full hardware implementation.
+
+## Implementing with Standard DICE Support
+
+If hardware supports a standard DICE mechanism but does not support this profile
+directly, this profile can be implemented in firmware and can use the firmware
+CDI from the standard DICE as a UDS. The provisioned certificate would then
+cover both the hardware and the firmware implementing this profile.
+
+However, this only works if the firmware that implements this profile is
+unmodified during normal operation. It becomes a _ROM extension_ in the sense
+that if it is modified, the firmware CDI changes, and the certificate chain
+provisioned for the device is no longer valid. In an ARM Trusted Firmware
+architecture, it would likely be BL2 firmware that implements this profile.
+
+If the firmware implementing this profile is the first firmware to run on the
+system, this approach has equivalent security to a full hardware implementation.
+
+## Implementing with Lockable Persistent Storage
+
+If hardware supports a lockable persistent storage mechanism early in boot, this
+profile can be implemented in firmware and can use a secret stored using this
+mechanism as a UDS. This firmware should run as early in boot as possible. The
+storage could be lockable OTP memory, lockable NVRAM, a one-time derivation, or
+similar. Security chips like a TPM or SE often have an appropriate capability.
+
+However, this only works along with a robust verified boot system to verify the
+firmware that implements this profile and any other firmware that runs before
+it. It also has the downside that changes to the firmware, or any other firmware
+that runs before it, are not reflected in the CDIs.
+
+The security of this approach is not equivalent to a full hardware
+implementation, but may still be acceptable for many applications. If the
+firmware implementing this profile is the first firmware to run on the system,
+this approach has equivalent security to a full hardware implementation which
+employs a hardware modification mechanism like an FPGA or microcode.
+
+This approach can also be used later in boot, for example in a TEE. However, the
+more code that runs without being covered by a DICE flow, the lower the security
+of the implementation.
+
+## Other Hardware Implementations
+
+With a robust verified boot system, there are many other possible
+implementations as long as (1) A UDS can be made available by some means early
+in boot, and (2) that UDS can be made subsequently unavailable until the next
+boot. These implementations meet the requirements of the TCG DICE specification
+as an _updatable DICE_ per section 6.2.
+
+# Appendix B: Hardware Implementation Checklist
+
+The following is a list of capabilities that a full hardware implementation must
+have. This is intended for the convenience of hardware designers, and is not
+intended to add any additional requirements or constraints.
+
+1.  Provide a UDS capability as required by this profile and the TCG DICE
+    specification. Usually this _cannot_ be implemented in mask ROM but requires
+    additional hardware capabilities. See [UDS Details](#uds-details).
+1.  Reserve on the order of 8KB of mask ROM for DICE, not including crypto
+    primitives. The rest of this list can usually be implemented entirely in
+    ROM.
+1.  Choose crypto primitives and provide implementations, ideally with hardware
+    acceleration. See [Cryptography](#cryptography).
+1.  Provide a code input. At this level a simple code hash is recommended,
+    without an additional descriptor. Often the verified boot system already has
+    a code hash it verifies, and using the same hash as input to DICE is
+    recommended. See [Input Values](#input-values).
+1.  Provide a configuration input. At this level, using the 64-bit value
+    described in this profile is recommended. See [Input Values](#input-values)
+    and
+    [Configuration Input Value Details](#configuration-input-value-details-optional).
+1.  Provide a verified boot authority input. This should be very simple and
+    stable, often copied directly out of OTP memory. At this level a simple hash
+    is recommended, without an additional descriptor. See
+    [Input Values](#input-values).
+1.  Provide a mode input. Determining the mode is a runtime decision so a bit of
+    logic will have to be coded. The 64-bit configuration value should have all
+    the information necessary to make this decision. See
+    [Input Values](#input-values) and [Mode Value Details](#mode-value-details).
+1.  Provide a hidden input value if necessary. At this level it is not
+    recommended.
+1.  Implement the [DICE flow](#high-level-dice-flow) and certificate generation;
+    reference code is available. If recommendations in this list are followed
+    for simple inputs, the certificate will be a constant size and layout and a
+    template can be used (avoiding the need for X.509 or CBOR code). See
+    [Certificate Details](#certificate-details).
+1.  Make DICE outputs available to firmware (CDIs and certificate).
+1.  Depending on which provisioning model is used, make the UDS-derived public
+    key available.
+
+# Appendix C: Versioned Sealing Keys
+
+A versioned sealing key is a key that is derived from a secret seed and one or
+more software versions. The versions cannot be higher than the current software
+version. In other words, a versioned sealing key can be derived for the current
+software version and each previous version, but not for future versions. These
+keys can be used to seal data in a rollback-protected way, that is, in a way
+that current and future software can unseal but older software cannot. Each time
+software is upgraded, the data can be re-sealed to be bound to the latest
+version.
+
+The Sealing CDIs derived by using DICE in layers as described in this profile
+are not versioned; rather they are stable across versions. To achieve versioned
+sealing keys, an additional hardware mechanism is required: a versioned KDF
+(V-KDF). There are many possible implementations but in general it must be
+possible to seed the V-KDF with one or more secrets that it will not expose, and
+one or more maximum versions that it will not allow to be subsequently modified.
+After seeding, the V-KDF accepts version info as input (likely along with other
+inputs), and the output is a key that may be used as a versioned sealing key.
+
+Given such a V-KDF, versioned keys can be derived from a Sealing CDI by adding a
+few steps to precede the [DICE flow](#high-level-dice-flow):
+
+*   Derive a V-KDF seed from the current sealing CDI (or UDS if this is the
+    initial DICE instance) and the same inputs used for deriving the next layer
+    sealing CDI. The derivation differs from the sealing CDI derivation only by
+    the info string:
+
+```py
+VKDF_SEED = KDF(32, CDI_Seal_or_UDS, H(authority + mode + hidden), "VKDF_SEED")
+```
+
+*   Seed the V-KDF with the output of (1) and the version of the target code
+    (the code to which control will be transferred at the end of the DICE flow)
+*   Destroy any copy of the V-KDF seed, so it's only available to the V-KDF
+*   Run the DICE flow as usual
+
+Note that the V-KDF seed is derived from the _current_ sealing CDI; this value
+is _not_ passed to target code but is locked / destroyed as part of the DICE
+flow. As a result the target code can only generate versioned keys as seeded by
+the previous layer.
+
+When multiple layers are involved, the V-KDF should use the seed inputs
+cumulatively:
+
+*   The seed value should be mixed into the current state, it should not reset
+    the state.
+*   The max version value should be retained in addition to the existing max
+    version values. The version info supplied as KDF input must then contain one
+    version for each maximum version configured. The number of layers supported
+    may be limited by the V-KDF hardware; support for at least 8 maximum
+    versions is recommended.
diff --git a/generate_test_values.py b/generate_test_values.py
new file mode 100644
index 0000000..953ef82
--- /dev/null
+++ b/generate_test_values.py
@@ -0,0 +1,148 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+#
+# Lint as: python3
+"""Generates known_test_values.h from dumped test values.
+
+This program generates the known_test_values.h file used for unit tests. This is
+useful to correct the baseline test values based on dumps from the tests. Use
+this after fixing a bug in the code, not to 'fix' test breakage not well
+understood.
+
+Usage:
+  $ cd out
+  $ python ../generate_test_values.py > ../include/dice/known_test_values.h
+
+Prerequisites:
+  pip install absl-py
+"""
+
+from __future__ import print_function
+
+import re
+import subprocess
+import textwrap
+
+from absl import app
+from absl import flags
+
+FLAGS = flags.FLAGS
+
+_FILE_HEADER = textwrap.dedent("""\
+    // Copyright 2020 Google LLC
+    //
+    // Licensed under the Apache License, Version 2.0 (the "License"); you may not
+    // use this file except in compliance with the License. You may obtain a copy of
+    // the License at
+    //
+    //     https://www.apache.org/licenses/LICENSE-2.0
+    //
+    // Unless required by applicable law or agreed to in writing, software
+    // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+    // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+    // License for the specific language governing permissions and limitations under
+    // the License.
+
+    // !!! GENERATED - DO NOT MODIFY !!!
+    // To update this file, use generate_test_values.py.
+
+    #ifndef DICE_KNOWN_TEST_VALUES_H_
+    #define DICE_KNOWN_TEST_VALUES_H_
+
+    #include <stdint.h>
+
+    namespace dice {
+    namespace test {
+
+                               """)
+
+_FILE_FOOTER = textwrap.dedent("""\
+    }  // namespace test
+    }  // namespace dice
+
+    #endif  // DICE_KNOWN_TEST_VALUES_H_
+                               """)
+
+
+def _to_camel_case(s):
+    return ''.join(tmp.capitalize() for tmp in s.split('_'))
+
+
+def _read_file(name):
+    try:
+        with open(name, 'rb') as f:
+            return f.read()
+    except OSError:
+        return ''
+
+
+def _generate_array(name, data):
+    return 'constexpr uint8_t %s[%d] = {%s};\n\n' % (
+        name, len(data), ', '.join('0x%02x' % tmp for tmp in data))
+
+
+def _generate_cert_comment(data):
+    return re.sub('^',
+                  '// ',
+                  subprocess.run([
+                      'openssl', 'x509', '-inform', 'DER', '-noout', '-text',
+                      '-certopt', 'ext_parse'
+                  ],
+                                 input=data,
+                                 capture_output=True,
+                                 check=True).stdout.decode(),
+                  flags=re.MULTILINE)[:-3]
+
+
+def _generate_c(name):
+    """Generates C declarations from dumps identified by |name|."""
+    content = ''
+    attest_cdi_data = _read_file('_attest_cdi_%s.bin' % name)
+    content += _generate_array('kExpectedCdiAttest_%s' % _to_camel_case(name),
+                               attest_cdi_data)
+    seal_cdi_data = _read_file('_seal_cdi_%s.bin' % name)
+    content += _generate_array('kExpectedCdiSeal_%s' % _to_camel_case(name),
+                               seal_cdi_data)
+    for cert_type in ('X509', 'CBOR'):
+        for key_type in ('Ed25519', 'P256'):
+            var_name = 'kExpected%s%sCert_%s' % (_to_camel_case(cert_type),
+                                                 _to_camel_case(key_type),
+                                                 _to_camel_case(name))
+            cert_data = _read_file('_%s_%s_cert_%s.cert' %
+                                   (cert_type, key_type, name))
+            if cert_type == 'X509':
+                content += (
+                    '// $ openssl x509 -inform DER -noout -text -certopt '
+                    'ext_parse\n')
+                content += _generate_cert_comment(cert_data)
+            content += _generate_array(var_name, cert_data)
+    return content
+
+
+def main(argv):
+    if len(argv) > 1:
+        raise app.UsageError('Too many command-line arguments.')
+
+    content = _FILE_HEADER
+    content += _generate_c('zero_input')
+    content += _generate_c('hash_only_input')
+    content += _generate_c('descriptor_input')
+    content += _FILE_FOOTER
+    subprocess.run(['clang-format', '--style=file'],
+                   input=content.encode(),
+                   check=True)
+
+
+if __name__ == '__main__':
+    app.run(main)
diff --git a/images/architecture.png b/images/architecture.png
new file mode 100644
index 0000000..b9a66f5
--- /dev/null
+++ b/images/architecture.png
Binary files differ
diff --git a/images/multi-layer-cert-chain.png b/images/multi-layer-cert-chain.png
new file mode 100644
index 0000000..91bd258
--- /dev/null
+++ b/images/multi-layer-cert-chain.png
Binary files differ
diff --git a/images/single-layer-cert-chain.png b/images/single-layer-cert-chain.png
new file mode 100644
index 0000000..67a96f6
--- /dev/null
+++ b/images/single-layer-cert-chain.png
Binary files differ
diff --git a/include/dice/boringssl_ops.h b/include/dice/boringssl_ops.h
new file mode 100644
index 0000000..c188f01
--- /dev/null
+++ b/include/dice/boringssl_ops.h
@@ -0,0 +1,47 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef DICE_BORINGSSL_OPS_H_
+#define DICE_BORINGSSL_OPS_H_
+
+#include "dice/dice.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// This is a DiceOps implementation which uses boringssl for crypto and
+// certificate generation. These functions are documented as part of the DiceOps
+// struct in dice.h. The algorithms used are SHA512, HKDF-SHA512, and
+// Ed25519-SHA512.
+DiceResult DiceBsslHashOp(const DiceOps* ops, const uint8_t* input,
+                          size_t input_size, uint8_t output[DICE_HASH_SIZE]);
+
+DiceResult DiceBsslKdfOp(const DiceOps* ops, size_t length, const uint8_t* ikm,
+                         size_t ikm_size, const uint8_t* salt, size_t salt_size,
+                         const uint8_t* info, size_t info_size,
+                         uint8_t* output);
+
+DiceResult DiceBsslGenerateCertificateOp(
+    const DiceOps* ops,
+    const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+    const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+    const DiceInputValues* input_values, size_t certificate_buffer_size,
+    uint8_t* certificate, size_t* certificate_actual_size);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // DICE_BORINGSSL_OPS_H_
diff --git a/include/dice/cbor_cert_op.h b/include/dice/cbor_cert_op.h
new file mode 100644
index 0000000..c87dac0
--- /dev/null
+++ b/include/dice/cbor_cert_op.h
@@ -0,0 +1,38 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef DICE_CBOR_CERT_OP_H_
+#define DICE_CBOR_CERT_OP_H_
+
+#include "dice/dice.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// This function implements the 'DiceOps::generate_certificate' callback
+// documented in dice.h. It generates a CWT-style CBOR certificate using the
+// ED25519-SHA512 signature scheme.
+DiceResult DiceGenerateCborCertificateOp(
+    const DiceOps* ops,
+    const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+    const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+    const DiceInputValues* input_values, size_t certificate_buffer_size,
+    uint8_t* certificate, size_t* certificate_actual_size);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // DICE_CBOR_CERT_OP_H_
diff --git a/include/dice/dice.h b/include/dice/dice.h
new file mode 100644
index 0000000..c56ce86
--- /dev/null
+++ b/include/dice/dice.h
@@ -0,0 +1,196 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef DICE_DICE_H_
+#define DICE_DICE_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define DICE_CDI_SIZE 32
+#define DICE_HASH_SIZE 64
+#define DICE_HIDDEN_SIZE 64
+#define DICE_INLINE_CONFIG_SIZE 64
+#define DICE_PRIVATE_KEY_SIZE 32
+
+typedef enum {
+  kDiceResultOk,
+  kDiceResultInvalidInput,
+  kDiceResultBufferTooSmall,
+  kDiceResultPlatformError,
+} DiceResult;
+
+typedef enum {
+  kDiceModeNotInitialized,
+  kDiceModeNormal,
+  kDiceModeDebug,
+  kDiceModeMaintenance,
+} DiceMode;
+
+typedef enum {
+  kDiceConfigTypeInline,
+  kDiceConfigTypeDescriptor,
+} DiceConfigType;
+
+// Contains a full set of input values describing the target program or system.
+// See the Open Profile for DICE specification for a detailed explanation of
+// these inputs.
+//
+// Fields:
+//    code_hash: A hash or similar representation of the target code.
+//    code_descriptor: An optional descriptor to be included in the certificate.
+//        This descriptor is opaque to the DICE flow and is included verbatim
+//        in the certificate with no validation. May be null.
+//    code_descriptor_size: The size in bytes of |code_descriptor|.
+//    config_type: Indicates how to interpret the remaining config-related
+//        fields. If the type is 'inline', then the 64 byte configuration input
+//        value must be provided in |config_value| and |config_descriptor| is
+//        ignored. If the type is 'descriptor', then |config_descriptor| is
+//        hashed to get the configuration input value and |config_value| is
+//        ignored.
+//    config_value: A 64-byte configuration input value when |config_type| is
+//        kDiceConfigTypeInline. Otherwise, this field is ignored.
+//    config_descriptor: A descriptor to be hashed for the configuration input
+//        value when |config_type| is kDiceConfigTypeDescriptor. Otherwise,
+//        this field is ignored and may be null.
+//    config_descriptor_size: The size in bytes of |config_descriptor|.
+//    authority_hash: A hash or similar representation of the authority used to
+//        verify the target code. If the code is not verified or the authority
+//        is implicit, for example hard coded as part of the code currently
+//        executing, then this value should be set to all zero bytes.
+//    authority_descriptor: An optional descriptor to be included in the
+//        certificate. This descriptor is opaque to the DICE flow and is
+//        included verbatim in the certificate with no validation. May be null.
+//    authority_descriptor_size: The size in bytes of |authority_descriptor|.
+//    mode: The current operating mode.
+//    hidden: Additional input which will not appear in certificates. If this is
+//        not used it should be set to all zero bytes.
+typedef struct DiceInputValues_ {
+  uint8_t code_hash[DICE_HASH_SIZE];
+  const uint8_t* code_descriptor;
+  size_t code_descriptor_size;
+  DiceConfigType config_type;
+  uint8_t config_value[DICE_INLINE_CONFIG_SIZE];
+  const uint8_t* config_descriptor;
+  size_t config_descriptor_size;
+  uint8_t authority_hash[DICE_HASH_SIZE];
+  const uint8_t* authority_descriptor;
+  size_t authority_descriptor_size;
+  DiceMode mode;
+  uint8_t hidden[DICE_HIDDEN_SIZE];
+} DiceInputValues;
+
+// Contains a set of function pointers which implement various operations that
+// the DICE flow depends on.
+typedef struct DiceOps_ DiceOps;
+struct DiceOps_ {
+  // Available for use by the DiceOps implementation. May be null.
+  void* context;
+
+  // An implementation of SHA-512, or an alternative hash. Hashes |input_size|
+  // bytes of |input| and populates |output| on success.
+  DiceResult (*hash)(const DiceOps* ops, const uint8_t* input,
+                     size_t input_size, uint8_t output[DICE_HASH_SIZE]);
+
+  // An implementation of HKDF-SHA512, or an alternative KDF. Derives |length|
+  // bytes from |ikm|, |salt|, and |info| and populates |output| on success.
+  // |Output| must point to a buffer of at least |length| bytes.
+  DiceResult (*kdf)(const DiceOps* ops, size_t length, const uint8_t* ikm,
+                    size_t ikm_size, const uint8_t* salt, size_t salt_size,
+                    const uint8_t* info, size_t info_size, uint8_t* output);
+
+  // Generates an X.509 certificate, or an alternative certificate format, from
+  // the given |subject_private_key| and |input_values|, and signed by
+  // |authority_private_key|. The subject private key is supplied here so the
+  // implementation can choose between asymmetric mechanisms, for example ECDSA
+  // vs Ed25519.
+  DiceResult (*generate_certificate)(
+      const DiceOps* ops,
+      const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+      const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+      const DiceInputValues* input_values, size_t certificate_buffer_size,
+      uint8_t* certificate, size_t* certificate_actual_size);
+
+  // Securely clears |size| bytes at |address|. The default implementation uses
+  // the volatile data pointer approach which ostensibly works in practice, but
+  // a particular target platform or toolchain may have a better
+  // implementation available and if so, it can be plugged in here.
+  void (*clear_memory)(const DiceOps* ops, size_t size, void* address);
+};
+
+// Derives a |cdi_private_key| from a |cdi_attest| value. On success populates
+// |cdi_private_key| and returns kDiceResultOk. Note: of the provided |ops|,
+// only 'kdf' is called.
+DiceResult DiceDeriveCdiPrivateKey(const DiceOps* ops,
+                                   const uint8_t cdi_attest[DICE_CDI_SIZE],
+                                   uint8_t cdi_private_key[DICE_CDI_SIZE]);
+
+// Derives an |id| from a |cdi_public_key| value. Because public keys can vary
+// in length depending on the algorithm, the |cdi_public_key_size| in bytes must
+// be provided. When interpreted as an integer, |id| is big-endian. On success
+// populates |id| and returns kDiceResultOk. Note: of the provided |ops|, only
+// 'kdf' is called.
+DiceResult DiceDeriveCdiCertificateId(const DiceOps* ops,
+                                      const uint8_t* cdi_public_key,
+                                      size_t cdi_public_key_size,
+                                      uint8_t id[20]);
+
+// Executes the main DICE flow.
+//
+// Given a full set of input values and the current CDI values, computes the
+// next CDI values and a matching certificate. See the Open Profile for DICE
+// specification for a detailed explanation of this flow.
+//
+// Parameters:
+//    ops: Operations provided by the caller.
+//    current_cdi_attest, current_cdi_seal: The current CDI values as produced
+//        by a previous DICE flow. If this is the first DICE flow in a system,
+//        the Unique Device Secret (UDS) should be used for both of these
+//        arguments.
+//    input_values: A set of input values describing the target program or
+//        system.
+//    next_cdi_certificate_buffer_size: The size in bytes of the buffer pointed
+//        to by the |next_cdi_certificate| argument.
+//    next_cdi_certificate: On success, will be populated with the generated
+//        certificate, up to |next_cdi_certificate_buffer_size| in size. If the
+//        certificate cannot fit in the buffer, |next_cdi_certificate_size| is
+//        populated with the required size and kDiceResultBufferTooSmall is
+//        returned.
+//    next_cdi_certificate_actual_size: On success, will be populated with the
+//        size, in bytes, of the certificate data written to
+//        |next_cdi_certificate|. If kDiceResultBufferTooSmall is returned, will
+//        be populated with the required buffer size.
+//    next_cdi_attest: On success, will be populated with the next CDI value for
+//        attestation.
+//    next_cdi_seal: On success, will be populated with the next CDI value for
+//        sealing.
+DiceResult DiceMainFlow(const DiceOps* ops,
+                        const uint8_t current_cdi_attest[DICE_CDI_SIZE],
+                        const uint8_t current_cdi_seal[DICE_CDI_SIZE],
+                        const DiceInputValues* input_values,
+                        size_t next_cdi_certificate_buffer_size,
+                        uint8_t* next_cdi_certificate,
+                        size_t* next_cdi_certificate_actual_size,
+                        uint8_t next_cdi_attest[DICE_CDI_SIZE],
+                        uint8_t next_cdi_seal[DICE_CDI_SIZE]);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // DICE_DICE_H_
diff --git a/include/dice/fuzz_utils.h b/include/dice/fuzz_utils.h
new file mode 100644
index 0000000..89b1e22
--- /dev/null
+++ b/include/dice/fuzz_utils.h
@@ -0,0 +1,26 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "dice/dice.h"
+
+namespace dice {
+namespace fuzz {
+
+int FuzzDiceMainFlow(const DiceOps* ops, const uint8_t* data, size_t size);
+
+}  // namespace fuzz
+}  // namespace dice
diff --git a/include/dice/known_test_values.h b/include/dice/known_test_values.h
new file mode 100644
index 0000000..49ddfb9
--- /dev/null
+++ b/include/dice/known_test_values.h
@@ -0,0 +1,982 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// !!! GENERATED - DO NOT MODIFY !!!
+// To update this file, use generate_test_values.py.
+
+#ifndef DICE_KNOWN_TEST_VALUES_H_
+#define DICE_KNOWN_TEST_VALUES_H_
+
+#include <stdint.h>
+
+namespace dice {
+namespace test {
+
+constexpr uint8_t kExpectedCdiAttest_ZeroInput[32] = {
+    0xfb, 0xfc, 0x67, 0x97, 0x71, 0x34, 0x2e, 0xea, 0xcb, 0x90, 0x86,
+    0x59, 0xce, 0x49, 0xd6, 0xb6, 0x3b, 0x45, 0x35, 0xda, 0x2c, 0x51,
+    0x43, 0x3d, 0x7f, 0x04, 0xef, 0xa6, 0x31, 0x9e, 0x0c, 0x19};
+
+constexpr uint8_t kExpectedCdiSeal_ZeroInput[32] = {
+    0x8f, 0xf8, 0xb2, 0x25, 0x71, 0x32, 0x5e, 0x7d, 0xef, 0xef, 0xbf,
+    0xea, 0x8d, 0xf1, 0xc9, 0xf3, 0x4b, 0xf4, 0xd9, 0xee, 0x03, 0xb7,
+    0x5b, 0x78, 0x82, 0x19, 0xc6, 0xb1, 0xef, 0x49, 0xbd, 0xc5};
+
+// $ openssl x509 -inform DER -noout -text -certopt ext_parse
+// Certificate:
+//     Data:
+//         Version: 3 (0x2)
+//         Serial Number:
+//             67:c2:2a:88:59:06:2b:98:68:18:e8:e7:2b:0b:cd:9f:59:34:9c:89
+//         Signature Algorithm: ED25519
+//         Issuer: serialNumber = 7a06eee41b789f4863d86b8778b1a201a6fedd56
+//         Validity
+//             Not Before: Mar 22 23:59:59 2018 GMT
+//             Not After : Dec 31 23:59:59 9999 GMT
+//         Subject: serialNumber = 67c22a8859062b986818e8e72b0bcd9f59349c89
+//         Subject Public Key Info:
+//             Public Key Algorithm: ED25519
+//                 ED25519 Public-Key:
+//                 pub:
+//                     0d:14:e5:de:29:2e:b1:c8:b3:1b:ea:e4:3a:b5:5d:
+//                     8e:9d:c0:14:b7:3e:aa:83:b9:25:a0:78:8c:c6:2e:
+//                     5c:8d
+//         X509v3 extensions:
+//             X509v3 Authority Key Identifier:
+//                 keyid:7A:06:EE:E4:1B:78:9F:48:63:D8:6B:87:78:B1:A2:01:A6:FE:DD:56
+//
+//             X509v3 Subject Key Identifier:
+//                 67:C2:2A:88:59:06:2B:98:68:18:E8:E7:2B:0B:CD:9F:59:34:9C:89
+//             X509v3 Key Usage: critical
+//                 Certificate Sign
+//             X509v3 Basic Constraints: critical
+//                 CA:TRUE
+//             1.3.6.1.4.1.11129.2.1.24:
+//     0:d=0  hl=3 l= 209 cons: SEQUENCE
+//     3:d=1  hl=2 l=  66 cons:  cont [ 0 ]
+//     5:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0010 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0020 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0030 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//    71:d=1  hl=2 l=  66 cons:  cont [ 3 ]
+//    73:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0010 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0020 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0030 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//   139:d=1  hl=2 l=  66 cons:  cont [ 4 ]
+//   141:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0010 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0020 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0030 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//   207:d=1  hl=2 l=   3 cons:  cont [ 6 ]
+//   209:d=2  hl=2 l=   1 prim:   ENUMERATED        :00
+//
+//     Signature Algorithm: ED25519
+//          cd:8e:a8:d0:f5:12:b5:28:64:11:47:11:db:12:cf:b2:27:64:
+//          22:8d:a3:76:27:98:32:6e:6c:5a:44:10:ef:b3:ae:39:96:04:
+//          53:57:72:a6:11:2b:58:78:59:06:0b:a6:a8:23:0a:aa:20:73:
+//          91:0e:fb:1a:39:c1:d1:86:41:0b
+constexpr uint8_t kExpectedX509Ed25519Cert_ZeroInput[635] = {
+    0x30, 0x82, 0x02, 0x77, 0x30, 0x82, 0x02, 0x29, 0xa0, 0x03, 0x02, 0x01,
+    0x02, 0x02, 0x14, 0x67, 0xc2, 0x2a, 0x88, 0x59, 0x06, 0x2b, 0x98, 0x68,
+    0x18, 0xe8, 0xe7, 0x2b, 0x0b, 0xcd, 0x9f, 0x59, 0x34, 0x9c, 0x89, 0x30,
+    0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x30, 0x33, 0x31, 0x31, 0x30, 0x2f,
+    0x06, 0x03, 0x55, 0x04, 0x05, 0x13, 0x28, 0x37, 0x61, 0x30, 0x36, 0x65,
+    0x65, 0x65, 0x34, 0x31, 0x62, 0x37, 0x38, 0x39, 0x66, 0x34, 0x38, 0x36,
+    0x33, 0x64, 0x38, 0x36, 0x62, 0x38, 0x37, 0x37, 0x38, 0x62, 0x31, 0x61,
+    0x32, 0x30, 0x31, 0x61, 0x36, 0x66, 0x65, 0x64, 0x64, 0x35, 0x36, 0x30,
+    0x20, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x33, 0x32, 0x32, 0x32, 0x33, 0x35,
+    0x39, 0x35, 0x39, 0x5a, 0x18, 0x0f, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32,
+    0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x33, 0x31,
+    0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x05, 0x13, 0x28, 0x36, 0x37,
+    0x63, 0x32, 0x32, 0x61, 0x38, 0x38, 0x35, 0x39, 0x30, 0x36, 0x32, 0x62,
+    0x39, 0x38, 0x36, 0x38, 0x31, 0x38, 0x65, 0x38, 0x65, 0x37, 0x32, 0x62,
+    0x30, 0x62, 0x63, 0x64, 0x39, 0x66, 0x35, 0x39, 0x33, 0x34, 0x39, 0x63,
+    0x38, 0x39, 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03,
+    0x21, 0x00, 0x0d, 0x14, 0xe5, 0xde, 0x29, 0x2e, 0xb1, 0xc8, 0xb3, 0x1b,
+    0xea, 0xe4, 0x3a, 0xb5, 0x5d, 0x8e, 0x9d, 0xc0, 0x14, 0xb7, 0x3e, 0xaa,
+    0x83, 0xb9, 0x25, 0xa0, 0x78, 0x8c, 0xc6, 0x2e, 0x5c, 0x8d, 0xa3, 0x82,
+    0x01, 0x4b, 0x30, 0x82, 0x01, 0x47, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+    0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x7a, 0x06, 0xee, 0xe4, 0x1b,
+    0x78, 0x9f, 0x48, 0x63, 0xd8, 0x6b, 0x87, 0x78, 0xb1, 0xa2, 0x01, 0xa6,
+    0xfe, 0xdd, 0x56, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+    0x04, 0x14, 0x67, 0xc2, 0x2a, 0x88, 0x59, 0x06, 0x2b, 0x98, 0x68, 0x18,
+    0xe8, 0xe7, 0x2b, 0x0b, 0xcd, 0x9f, 0x59, 0x34, 0x9c, 0x89, 0x30, 0x0e,
+    0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02,
+    0x02, 0x04, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+    0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0x01, 0x30, 0x81, 0xe3, 0x06, 0x0a,
+    0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x18, 0x04, 0x81,
+    0xd4, 0x30, 0x81, 0xd1, 0xa0, 0x42, 0x04, 0x40, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0xa3, 0x42, 0x04, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa4, 0x42, 0x04, 0x40,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0xa6, 0x03, 0x0a, 0x01, 0x00, 0x30, 0x05, 0x06,
+    0x03, 0x2b, 0x65, 0x70, 0x03, 0x41, 0x00, 0xcd, 0x8e, 0xa8, 0xd0, 0xf5,
+    0x12, 0xb5, 0x28, 0x64, 0x11, 0x47, 0x11, 0xdb, 0x12, 0xcf, 0xb2, 0x27,
+    0x64, 0x22, 0x8d, 0xa3, 0x76, 0x27, 0x98, 0x32, 0x6e, 0x6c, 0x5a, 0x44,
+    0x10, 0xef, 0xb3, 0xae, 0x39, 0x96, 0x04, 0x53, 0x57, 0x72, 0xa6, 0x11,
+    0x2b, 0x58, 0x78, 0x59, 0x06, 0x0b, 0xa6, 0xa8, 0x23, 0x0a, 0xaa, 0x20,
+    0x73, 0x91, 0x0e, 0xfb, 0x1a, 0x39, 0xc1, 0xd1, 0x86, 0x41, 0x0b};
+
+// $ openssl x509 -inform DER -noout -text -certopt ext_parse
+// Certificate:
+//     Data:
+//         Version: 3 (0x2)
+//         Serial Number:
+//             7c:7d:c0:a3:c1:e7:8d:4e:68:bc:c1:a2:32:9e:f9:1c:a8:12:44:91
+//         Signature Algorithm: ecdsa-with-SHA512
+//         Issuer: serialNumber = 4c514d88db0f81d57beb96177e3d7ea4aa581e66
+//         Validity
+//             Not Before: Mar 22 23:59:59 2018 GMT
+//             Not After : Dec 31 23:59:59 9999 GMT
+//         Subject: serialNumber = 7c7dc0a3c1e78d4e68bcc1a2329ef91ca8124491
+//         Subject Public Key Info:
+//             Public Key Algorithm: id-ecPublicKey
+//                 Public-Key: (256 bit)
+//                 pub:
+//                     04:4f:3b:4e:82:c4:5a:da:08:45:89:c2:19:7b:af:
+//                     1f:37:6e:ac:40:e1:fd:49:b0:24:06:02:ae:c2:69:
+//                     54:1c:6b:e7:eb:40:19:ab:55:c6:6b:c8:8b:b8:b4:
+//                     69:ad:7e:e8:58:9e:07:d2:f8:bc:88:8e:b3:11:c2:
+//                     df:97:3b:1b:4a
+//                 ASN1 OID: prime256v1
+//                 NIST CURVE: P-256
+//         X509v3 extensions:
+//             X509v3 Authority Key Identifier:
+//                 keyid:4C:51:4D:88:DB:0F:81:D5:7B:EB:96:17:7E:3D:7E:A4:AA:58:1E:66
+//
+//             X509v3 Subject Key Identifier:
+//                 7C:7D:C0:A3:C1:E7:8D:4E:68:BC:C1:A2:32:9E:F9:1C:A8:12:44:91
+//             X509v3 Key Usage: critical
+//                 Certificate Sign
+//             X509v3 Basic Constraints: critical
+//                 CA:TRUE
+//             1.3.6.1.4.1.11129.2.1.24:
+//     0:d=0  hl=3 l= 209 cons: SEQUENCE
+//     3:d=1  hl=2 l=  66 cons:  cont [ 0 ]
+//     5:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0010 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0020 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0030 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//    71:d=1  hl=2 l=  66 cons:  cont [ 3 ]
+//    73:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0010 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0020 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0030 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//   139:d=1  hl=2 l=  66 cons:  cont [ 4 ]
+//   141:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0010 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0020 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//       0030 - 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
+//   207:d=1  hl=2 l=   3 cons:  cont [ 6 ]
+//   209:d=2  hl=2 l=   1 prim:   ENUMERATED        :00
+//
+//     Signature Algorithm: ecdsa-with-SHA512
+//          30:46:02:21:00:b9:97:04:aa:8f:7f:65:d6:da:7c:3b:35:d8:
+//          6c:1f:93:c9:e2:ae:48:69:dd:0d:82:b9:0a:e1:f4:42:02:35:
+//          b0:02:21:00:9b:a9:cf:3c:5f:2b:88:fd:4f:e6:97:2b:b4:c8:
+//          6b:28:91:75:c0:49:17:48:df:47:2f:80:fa:8c:5d:3f:a9:82
+constexpr uint8_t kExpectedX509P256Cert_ZeroInput[704] = {
+    0x30, 0x82, 0x02, 0xbc, 0x30, 0x82, 0x02, 0x5f, 0xa0, 0x03, 0x02, 0x01,
+    0x02, 0x02, 0x14, 0x7c, 0x7d, 0xc0, 0xa3, 0xc1, 0xe7, 0x8d, 0x4e, 0x68,
+    0xbc, 0xc1, 0xa2, 0x32, 0x9e, 0xf9, 0x1c, 0xa8, 0x12, 0x44, 0x91, 0x30,
+    0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04, 0x05,
+    0x00, 0x30, 0x33, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x05,
+    0x13, 0x28, 0x34, 0x63, 0x35, 0x31, 0x34, 0x64, 0x38, 0x38, 0x64, 0x62,
+    0x30, 0x66, 0x38, 0x31, 0x64, 0x35, 0x37, 0x62, 0x65, 0x62, 0x39, 0x36,
+    0x31, 0x37, 0x37, 0x65, 0x33, 0x64, 0x37, 0x65, 0x61, 0x34, 0x61, 0x61,
+    0x35, 0x38, 0x31, 0x65, 0x36, 0x36, 0x30, 0x20, 0x17, 0x0d, 0x31, 0x38,
+    0x30, 0x33, 0x32, 0x32, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x18,
+    0x0f, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35,
+    0x39, 0x35, 0x39, 0x5a, 0x30, 0x33, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03,
+    0x55, 0x04, 0x05, 0x13, 0x28, 0x37, 0x63, 0x37, 0x64, 0x63, 0x30, 0x61,
+    0x33, 0x63, 0x31, 0x65, 0x37, 0x38, 0x64, 0x34, 0x65, 0x36, 0x38, 0x62,
+    0x63, 0x63, 0x31, 0x61, 0x32, 0x33, 0x32, 0x39, 0x65, 0x66, 0x39, 0x31,
+    0x63, 0x61, 0x38, 0x31, 0x32, 0x34, 0x34, 0x39, 0x31, 0x30, 0x59, 0x30,
+    0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08,
+    0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04,
+    0x4f, 0x3b, 0x4e, 0x82, 0xc4, 0x5a, 0xda, 0x08, 0x45, 0x89, 0xc2, 0x19,
+    0x7b, 0xaf, 0x1f, 0x37, 0x6e, 0xac, 0x40, 0xe1, 0xfd, 0x49, 0xb0, 0x24,
+    0x06, 0x02, 0xae, 0xc2, 0x69, 0x54, 0x1c, 0x6b, 0xe7, 0xeb, 0x40, 0x19,
+    0xab, 0x55, 0xc6, 0x6b, 0xc8, 0x8b, 0xb8, 0xb4, 0x69, 0xad, 0x7e, 0xe8,
+    0x58, 0x9e, 0x07, 0xd2, 0xf8, 0xbc, 0x88, 0x8e, 0xb3, 0x11, 0xc2, 0xdf,
+    0x97, 0x3b, 0x1b, 0x4a, 0xa3, 0x82, 0x01, 0x4b, 0x30, 0x82, 0x01, 0x47,
+    0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+    0x14, 0x4c, 0x51, 0x4d, 0x88, 0xdb, 0x0f, 0x81, 0xd5, 0x7b, 0xeb, 0x96,
+    0x17, 0x7e, 0x3d, 0x7e, 0xa4, 0xaa, 0x58, 0x1e, 0x66, 0x30, 0x1d, 0x06,
+    0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x7c, 0x7d, 0xc0, 0xa3,
+    0xc1, 0xe7, 0x8d, 0x4e, 0x68, 0xbc, 0xc1, 0xa2, 0x32, 0x9e, 0xf9, 0x1c,
+    0xa8, 0x12, 0x44, 0x91, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+    0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, 0x04, 0x30, 0x0f, 0x06, 0x03,
+    0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01,
+    0xff, 0x30, 0x81, 0xe3, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6,
+    0x79, 0x02, 0x01, 0x18, 0x04, 0x81, 0xd4, 0x30, 0x81, 0xd1, 0xa0, 0x42,
+    0x04, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa3, 0x42, 0x04, 0x40, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0xa4, 0x42, 0x04, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xa6, 0x03,
+    0x0a, 0x01, 0x00, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d,
+    0x04, 0x03, 0x04, 0x05, 0x00, 0x03, 0x49, 0x00, 0x30, 0x46, 0x02, 0x21,
+    0x00, 0xb9, 0x97, 0x04, 0xaa, 0x8f, 0x7f, 0x65, 0xd6, 0xda, 0x7c, 0x3b,
+    0x35, 0xd8, 0x6c, 0x1f, 0x93, 0xc9, 0xe2, 0xae, 0x48, 0x69, 0xdd, 0x0d,
+    0x82, 0xb9, 0x0a, 0xe1, 0xf4, 0x42, 0x02, 0x35, 0xb0, 0x02, 0x21, 0x00,
+    0x9b, 0xa9, 0xcf, 0x3c, 0x5f, 0x2b, 0x88, 0xfd, 0x4f, 0xe6, 0x97, 0x2b,
+    0xb4, 0xc8, 0x6b, 0x28, 0x91, 0x75, 0xc0, 0x49, 0x17, 0x48, 0xdf, 0x47,
+    0x2f, 0x80, 0xfa, 0x8c, 0x5d, 0x3f, 0xa9, 0x82};
+
+constexpr uint8_t kExpectedCborEd25519Cert_ZeroInput[441] = {
+    0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x6e, 0xa8, 0x01, 0x78,
+    0x28, 0x37, 0x61, 0x30, 0x36, 0x65, 0x65, 0x65, 0x34, 0x31, 0x62, 0x37,
+    0x38, 0x39, 0x66, 0x34, 0x38, 0x36, 0x33, 0x64, 0x38, 0x36, 0x62, 0x38,
+    0x37, 0x37, 0x38, 0x62, 0x31, 0x61, 0x32, 0x30, 0x31, 0x61, 0x36, 0x66,
+    0x65, 0x64, 0x64, 0x35, 0x36, 0x02, 0x78, 0x28, 0x36, 0x37, 0x63, 0x32,
+    0x32, 0x61, 0x38, 0x38, 0x35, 0x39, 0x30, 0x36, 0x32, 0x62, 0x39, 0x38,
+    0x36, 0x38, 0x31, 0x38, 0x65, 0x38, 0x65, 0x37, 0x32, 0x62, 0x30, 0x62,
+    0x63, 0x64, 0x39, 0x66, 0x35, 0x39, 0x33, 0x34, 0x39, 0x63, 0x38, 0x39,
+    0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a,
+    0x00, 0x47, 0x44, 0x53, 0x58, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00,
+    0x47, 0x44, 0x54, 0x58, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x47,
+    0x44, 0x56, 0x41, 0x00, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5,
+    0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20,
+    0x0d, 0x14, 0xe5, 0xde, 0x29, 0x2e, 0xb1, 0xc8, 0xb3, 0x1b, 0xea, 0xe4,
+    0x3a, 0xb5, 0x5d, 0x8e, 0x9d, 0xc0, 0x14, 0xb7, 0x3e, 0xaa, 0x83, 0xb9,
+    0x25, 0xa0, 0x78, 0x8c, 0xc6, 0x2e, 0x5c, 0x8d, 0x3a, 0x00, 0x47, 0x44,
+    0x58, 0x41, 0x20, 0x58, 0x40, 0xf9, 0x9b, 0xd6, 0xdb, 0xc1, 0x24, 0x71,
+    0x53, 0xc1, 0x0f, 0x88, 0x1c, 0x0f, 0x5f, 0x33, 0xbf, 0x02, 0x23, 0xd2,
+    0x22, 0x32, 0x71, 0x24, 0x41, 0xb1, 0x28, 0xd3, 0x83, 0xde, 0x32, 0x1b,
+    0x67, 0xc0, 0x9a, 0x1f, 0x45, 0x91, 0xc4, 0x20, 0xdc, 0xc9, 0xd6, 0x21,
+    0x21, 0xec, 0xa3, 0xd3, 0x89, 0x7a, 0x24, 0x4d, 0xcb, 0xe1, 0x1a, 0x0f,
+    0x9a, 0xb7, 0x9f, 0x67, 0x09, 0x3f, 0xee, 0x56, 0x0f};
+
+constexpr uint8_t kExpectedCborP256Cert_ZeroInput[0] = {};
+
+constexpr uint8_t kExpectedCdiAttest_HashOnlyInput[32] = {
+    0x08, 0x4e, 0xf4, 0x06, 0xc6, 0x9b, 0xa7, 0x4b, 0x1e, 0x24, 0xd0,
+    0x62, 0xf9, 0xab, 0x8a, 0x8d, 0x89, 0xda, 0x6e, 0x03, 0xe4, 0xc6,
+    0xb1, 0x22, 0x85, 0x7c, 0xf7, 0x4f, 0xd6, 0xa4, 0xbe, 0xe5};
+
+constexpr uint8_t kExpectedCdiSeal_HashOnlyInput[32] = {
+    0x90, 0xc9, 0xa2, 0x86, 0x5d, 0xf4, 0xfa, 0x58, 0x30, 0x64, 0x3d,
+    0x6c, 0xae, 0xf0, 0x7c, 0x76, 0xae, 0xaa, 0x15, 0x61, 0x98, 0x28,
+    0xf1, 0xbd, 0xa7, 0xf7, 0x44, 0x82, 0xe2, 0xf0, 0xae, 0x1e};
+
+// $ openssl x509 -inform DER -noout -text -certopt ext_parse
+// Certificate:
+//     Data:
+//         Version: 3 (0x2)
+//         Serial Number:
+//             0d:04:0e:2f:46:00:52:a5:31:1c:1b:91:db:f9:b4:40:83:32:ec:29
+//         Signature Algorithm: ED25519
+//         Issuer: serialNumber = 475708eb3b426f386cfce8f3baf5439046278dfa
+//         Validity
+//             Not Before: Mar 22 23:59:59 2018 GMT
+//             Not After : Dec 31 23:59:59 9999 GMT
+//         Subject: serialNumber = 0d040e2f460052a5311c1b91dbf9b4408332ec29
+//         Subject Public Key Info:
+//             Public Key Algorithm: ED25519
+//                 ED25519 Public-Key:
+//                 pub:
+//                     5a:39:49:67:8c:d3:0e:88:ab:1c:dd:f7:15:55:d5:
+//                     bf:d3:f0:b8:47:25:a9:58:e1:b9:da:4e:b5:f1:38:
+//                     9a:5a
+//         X509v3 extensions:
+//             X509v3 Authority Key Identifier:
+//                 keyid:47:57:08:EB:3B:42:6F:38:6C:FC:E8:F3:BA:F5:43:90:46:27:8D:FA
+//
+//             X509v3 Subject Key Identifier:
+//                 0D:04:0E:2F:46:00:52:A5:31:1C:1B:91:DB:F9:B4:40:83:32:EC:29
+//             X509v3 Key Usage: critical
+//                 Certificate Sign
+//             X509v3 Basic Constraints: critical
+//                 CA:TRUE
+//             1.3.6.1.4.1.11129.2.1.24:
+//     0:d=0  hl=3 l= 209 cons: SEQUENCE
+//     3:d=1  hl=2 l=  66 cons:  cont [ 0 ]
+//     5:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - b7 d4 0c cb 22 5b a5 78-8f 98 ff 9e 86 93 75 f6 ...."[.x......u.
+//       0010 - 90 ac 50 cf 9e bd 0a fe-b1 d9 c2 4e 52 19 e4 de ..P........NR...
+//       0020 - 29 e5 61 f3 f9 29 e8 40-87 7a dd 17 48 05 89 7e ).a..).@.z..H..~
+//       0030 - 2b cb 54 79 cc 66 f1 b3-13 29 0c 68 96 b2 bb 8f +.Ty.f...).h....
+//    71:d=1  hl=2 l=  66 cons:  cont [ 3 ]
+//    73:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - cf 99 7b ea 2e 2c 86 a0-7b 52 09 c8 b5 3c 41 12 ..{..,..{R...<A.
+//       0010 - 29 28 1a 82 0d 49 9c 95-cb 0b 1b 31 1a 01 9c f2 )(...I.....1....
+//       0020 - 66 1a d9 b5 ce 52 59 cb-f4 81 9b 21 af 32 5d 07 f....RY....!.2].
+//       0030 - a0 1e 91 59 6f 06 55 10-8e 2e 08 88 52 28 86 7f ...Yo.U.....R(..
+//   139:d=1  hl=2 l=  66 cons:  cont [ 4 ]
+//   141:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - 22 52 60 17 ef 2c a1 f6-cb ed 39 d5 e2 aa 65 20 "R`..,....9...e
+//       0010 - fb ad 82 93 e5 78 23 22-97 c1 6e 6a 4e 36 d7 6a .....x#"..njN6.j
+//       0020 - 61 39 08 21 d4 fe 92 5f-36 2d eb 5d bb 32 8b e3 a9.!..._6-.].2..
+//       0030 - 94 4f be 1b 21 f9 cc 23-73 41 b6 b9 b6 98 d0 bc .O..!..#sA......
+//   207:d=1  hl=2 l=   3 cons:  cont [ 6 ]
+//   209:d=2  hl=2 l=   1 prim:   ENUMERATED        :00
+//
+//     Signature Algorithm: ED25519
+//          48:f5:68:52:d1:f3:1e:06:73:99:a4:77:be:33:61:df:5f:ce:
+//          3f:4f:fb:23:55:f4:94:d9:d1:d0:19:2a:01:b1:21:6b:d2:b8:
+//          c6:93:d8:c1:a8:c2:84:88:7a:3c:38:60:3b:20:26:8c:b5:56:
+//          5f:09:a4:f4:49:9b:bb:c6:3c:09
+constexpr uint8_t kExpectedX509Ed25519Cert_HashOnlyInput[635] = {
+    0x30, 0x82, 0x02, 0x77, 0x30, 0x82, 0x02, 0x29, 0xa0, 0x03, 0x02, 0x01,
+    0x02, 0x02, 0x14, 0x0d, 0x04, 0x0e, 0x2f, 0x46, 0x00, 0x52, 0xa5, 0x31,
+    0x1c, 0x1b, 0x91, 0xdb, 0xf9, 0xb4, 0x40, 0x83, 0x32, 0xec, 0x29, 0x30,
+    0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x30, 0x33, 0x31, 0x31, 0x30, 0x2f,
+    0x06, 0x03, 0x55, 0x04, 0x05, 0x13, 0x28, 0x34, 0x37, 0x35, 0x37, 0x30,
+    0x38, 0x65, 0x62, 0x33, 0x62, 0x34, 0x32, 0x36, 0x66, 0x33, 0x38, 0x36,
+    0x63, 0x66, 0x63, 0x65, 0x38, 0x66, 0x33, 0x62, 0x61, 0x66, 0x35, 0x34,
+    0x33, 0x39, 0x30, 0x34, 0x36, 0x32, 0x37, 0x38, 0x64, 0x66, 0x61, 0x30,
+    0x20, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x33, 0x32, 0x32, 0x32, 0x33, 0x35,
+    0x39, 0x35, 0x39, 0x5a, 0x18, 0x0f, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32,
+    0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x33, 0x31,
+    0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x05, 0x13, 0x28, 0x30, 0x64,
+    0x30, 0x34, 0x30, 0x65, 0x32, 0x66, 0x34, 0x36, 0x30, 0x30, 0x35, 0x32,
+    0x61, 0x35, 0x33, 0x31, 0x31, 0x63, 0x31, 0x62, 0x39, 0x31, 0x64, 0x62,
+    0x66, 0x39, 0x62, 0x34, 0x34, 0x30, 0x38, 0x33, 0x33, 0x32, 0x65, 0x63,
+    0x32, 0x39, 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03,
+    0x21, 0x00, 0x5a, 0x39, 0x49, 0x67, 0x8c, 0xd3, 0x0e, 0x88, 0xab, 0x1c,
+    0xdd, 0xf7, 0x15, 0x55, 0xd5, 0xbf, 0xd3, 0xf0, 0xb8, 0x47, 0x25, 0xa9,
+    0x58, 0xe1, 0xb9, 0xda, 0x4e, 0xb5, 0xf1, 0x38, 0x9a, 0x5a, 0xa3, 0x82,
+    0x01, 0x4b, 0x30, 0x82, 0x01, 0x47, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+    0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x47, 0x57, 0x08, 0xeb, 0x3b,
+    0x42, 0x6f, 0x38, 0x6c, 0xfc, 0xe8, 0xf3, 0xba, 0xf5, 0x43, 0x90, 0x46,
+    0x27, 0x8d, 0xfa, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+    0x04, 0x14, 0x0d, 0x04, 0x0e, 0x2f, 0x46, 0x00, 0x52, 0xa5, 0x31, 0x1c,
+    0x1b, 0x91, 0xdb, 0xf9, 0xb4, 0x40, 0x83, 0x32, 0xec, 0x29, 0x30, 0x0e,
+    0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02,
+    0x02, 0x04, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+    0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0x01, 0x30, 0x81, 0xe3, 0x06, 0x0a,
+    0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x18, 0x04, 0x81,
+    0xd4, 0x30, 0x81, 0xd1, 0xa0, 0x42, 0x04, 0x40, 0xb7, 0xd4, 0x0c, 0xcb,
+    0x22, 0x5b, 0xa5, 0x78, 0x8f, 0x98, 0xff, 0x9e, 0x86, 0x93, 0x75, 0xf6,
+    0x90, 0xac, 0x50, 0xcf, 0x9e, 0xbd, 0x0a, 0xfe, 0xb1, 0xd9, 0xc2, 0x4e,
+    0x52, 0x19, 0xe4, 0xde, 0x29, 0xe5, 0x61, 0xf3, 0xf9, 0x29, 0xe8, 0x40,
+    0x87, 0x7a, 0xdd, 0x17, 0x48, 0x05, 0x89, 0x7e, 0x2b, 0xcb, 0x54, 0x79,
+    0xcc, 0x66, 0xf1, 0xb3, 0x13, 0x29, 0x0c, 0x68, 0x96, 0xb2, 0xbb, 0x8f,
+    0xa3, 0x42, 0x04, 0x40, 0xcf, 0x99, 0x7b, 0xea, 0x2e, 0x2c, 0x86, 0xa0,
+    0x7b, 0x52, 0x09, 0xc8, 0xb5, 0x3c, 0x41, 0x12, 0x29, 0x28, 0x1a, 0x82,
+    0x0d, 0x49, 0x9c, 0x95, 0xcb, 0x0b, 0x1b, 0x31, 0x1a, 0x01, 0x9c, 0xf2,
+    0x66, 0x1a, 0xd9, 0xb5, 0xce, 0x52, 0x59, 0xcb, 0xf4, 0x81, 0x9b, 0x21,
+    0xaf, 0x32, 0x5d, 0x07, 0xa0, 0x1e, 0x91, 0x59, 0x6f, 0x06, 0x55, 0x10,
+    0x8e, 0x2e, 0x08, 0x88, 0x52, 0x28, 0x86, 0x7f, 0xa4, 0x42, 0x04, 0x40,
+    0x22, 0x52, 0x60, 0x17, 0xef, 0x2c, 0xa1, 0xf6, 0xcb, 0xed, 0x39, 0xd5,
+    0xe2, 0xaa, 0x65, 0x20, 0xfb, 0xad, 0x82, 0x93, 0xe5, 0x78, 0x23, 0x22,
+    0x97, 0xc1, 0x6e, 0x6a, 0x4e, 0x36, 0xd7, 0x6a, 0x61, 0x39, 0x08, 0x21,
+    0xd4, 0xfe, 0x92, 0x5f, 0x36, 0x2d, 0xeb, 0x5d, 0xbb, 0x32, 0x8b, 0xe3,
+    0x94, 0x4f, 0xbe, 0x1b, 0x21, 0xf9, 0xcc, 0x23, 0x73, 0x41, 0xb6, 0xb9,
+    0xb6, 0x98, 0xd0, 0xbc, 0xa6, 0x03, 0x0a, 0x01, 0x00, 0x30, 0x05, 0x06,
+    0x03, 0x2b, 0x65, 0x70, 0x03, 0x41, 0x00, 0x48, 0xf5, 0x68, 0x52, 0xd1,
+    0xf3, 0x1e, 0x06, 0x73, 0x99, 0xa4, 0x77, 0xbe, 0x33, 0x61, 0xdf, 0x5f,
+    0xce, 0x3f, 0x4f, 0xfb, 0x23, 0x55, 0xf4, 0x94, 0xd9, 0xd1, 0xd0, 0x19,
+    0x2a, 0x01, 0xb1, 0x21, 0x6b, 0xd2, 0xb8, 0xc6, 0x93, 0xd8, 0xc1, 0xa8,
+    0xc2, 0x84, 0x88, 0x7a, 0x3c, 0x38, 0x60, 0x3b, 0x20, 0x26, 0x8c, 0xb5,
+    0x56, 0x5f, 0x09, 0xa4, 0xf4, 0x49, 0x9b, 0xbb, 0xc6, 0x3c, 0x09};
+
+// $ openssl x509 -inform DER -noout -text -certopt ext_parse
+// Certificate:
+//     Data:
+//         Version: 3 (0x2)
+//         Serial Number:
+//             68:49:58:d9:ae:a7:2e:bf:7c:06:af:20:03:b6:44:47:82:4a:62:71
+//         Signature Algorithm: ecdsa-with-SHA512
+//         Issuer: serialNumber = 1be5687933db3d9cd5fca729e81d6685465a7bf1
+//         Validity
+//             Not Before: Mar 22 23:59:59 2018 GMT
+//             Not After : Dec 31 23:59:59 9999 GMT
+//         Subject: serialNumber = 684958d9aea72ebf7c06af2003b64447824a6271
+//         Subject Public Key Info:
+//             Public Key Algorithm: id-ecPublicKey
+//                 Public-Key: (256 bit)
+//                 pub:
+//                     04:fe:9d:b2:f9:28:09:c3:04:12:85:dc:d3:70:6f:
+//                     22:1c:72:b6:c4:4f:de:93:ee:fd:fb:6d:57:18:fc:
+//                     8f:6f:0b:09:1a:19:ea:10:7e:a9:38:f4:45:33:c1:
+//                     66:5b:bc:fc:0a:6e:98:99:72:88:c1:ad:0e:15:c2:
+//                     85:77:75:00:0b
+//                 ASN1 OID: prime256v1
+//                 NIST CURVE: P-256
+//         X509v3 extensions:
+//             X509v3 Authority Key Identifier:
+//                 keyid:1B:E5:68:79:33:DB:3D:9C:D5:FC:A7:29:E8:1D:66:85:46:5A:7B:F1
+//
+//             X509v3 Subject Key Identifier:
+//                 68:49:58:D9:AE:A7:2E:BF:7C:06:AF:20:03:B6:44:47:82:4A:62:71
+//             X509v3 Key Usage: critical
+//                 Certificate Sign
+//             X509v3 Basic Constraints: critical
+//                 CA:TRUE
+//             1.3.6.1.4.1.11129.2.1.24:
+//     0:d=0  hl=3 l= 209 cons: SEQUENCE
+//     3:d=1  hl=2 l=  66 cons:  cont [ 0 ]
+//     5:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - b7 d4 0c cb 22 5b a5 78-8f 98 ff 9e 86 93 75 f6 ...."[.x......u.
+//       0010 - 90 ac 50 cf 9e bd 0a fe-b1 d9 c2 4e 52 19 e4 de ..P........NR...
+//       0020 - 29 e5 61 f3 f9 29 e8 40-87 7a dd 17 48 05 89 7e ).a..).@.z..H..~
+//       0030 - 2b cb 54 79 cc 66 f1 b3-13 29 0c 68 96 b2 bb 8f +.Ty.f...).h....
+//    71:d=1  hl=2 l=  66 cons:  cont [ 3 ]
+//    73:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - cf 99 7b ea 2e 2c 86 a0-7b 52 09 c8 b5 3c 41 12 ..{..,..{R...<A.
+//       0010 - 29 28 1a 82 0d 49 9c 95-cb 0b 1b 31 1a 01 9c f2 )(...I.....1....
+//       0020 - 66 1a d9 b5 ce 52 59 cb-f4 81 9b 21 af 32 5d 07 f....RY....!.2].
+//       0030 - a0 1e 91 59 6f 06 55 10-8e 2e 08 88 52 28 86 7f ...Yo.U.....R(..
+//   139:d=1  hl=2 l=  66 cons:  cont [ 4 ]
+//   141:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - 22 52 60 17 ef 2c a1 f6-cb ed 39 d5 e2 aa 65 20 "R`..,....9...e
+//       0010 - fb ad 82 93 e5 78 23 22-97 c1 6e 6a 4e 36 d7 6a .....x#"..njN6.j
+//       0020 - 61 39 08 21 d4 fe 92 5f-36 2d eb 5d bb 32 8b e3 a9.!..._6-.].2..
+//       0030 - 94 4f be 1b 21 f9 cc 23-73 41 b6 b9 b6 98 d0 bc .O..!..#sA......
+//   207:d=1  hl=2 l=   3 cons:  cont [ 6 ]
+//   209:d=2  hl=2 l=   1 prim:   ENUMERATED        :00
+//
+//     Signature Algorithm: ecdsa-with-SHA512
+//          30:45:02:21:00:a8:1f:9a:1c:bd:6e:f7:a1:6c:4c:a8:98:b3:
+//          c9:c7:48:8b:2a:aa:29:37:35:83:8a:e3:64:2b:f2:e2:e2:a1:
+//          44:02:20:5e:80:29:7c:a9:7e:8c:44:3d:01:f7:ea:bd:8a:1f:
+//          eb:e8:f6:92:43:03:16:e6:a9:5d:e7:26:42:f4:9b:6e:54
+constexpr uint8_t kExpectedX509P256Cert_HashOnlyInput[703] = {
+    0x30, 0x82, 0x02, 0xbb, 0x30, 0x82, 0x02, 0x5f, 0xa0, 0x03, 0x02, 0x01,
+    0x02, 0x02, 0x14, 0x68, 0x49, 0x58, 0xd9, 0xae, 0xa7, 0x2e, 0xbf, 0x7c,
+    0x06, 0xaf, 0x20, 0x03, 0xb6, 0x44, 0x47, 0x82, 0x4a, 0x62, 0x71, 0x30,
+    0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04, 0x05,
+    0x00, 0x30, 0x33, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x05,
+    0x13, 0x28, 0x31, 0x62, 0x65, 0x35, 0x36, 0x38, 0x37, 0x39, 0x33, 0x33,
+    0x64, 0x62, 0x33, 0x64, 0x39, 0x63, 0x64, 0x35, 0x66, 0x63, 0x61, 0x37,
+    0x32, 0x39, 0x65, 0x38, 0x31, 0x64, 0x36, 0x36, 0x38, 0x35, 0x34, 0x36,
+    0x35, 0x61, 0x37, 0x62, 0x66, 0x31, 0x30, 0x20, 0x17, 0x0d, 0x31, 0x38,
+    0x30, 0x33, 0x32, 0x32, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x18,
+    0x0f, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35,
+    0x39, 0x35, 0x39, 0x5a, 0x30, 0x33, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03,
+    0x55, 0x04, 0x05, 0x13, 0x28, 0x36, 0x38, 0x34, 0x39, 0x35, 0x38, 0x64,
+    0x39, 0x61, 0x65, 0x61, 0x37, 0x32, 0x65, 0x62, 0x66, 0x37, 0x63, 0x30,
+    0x36, 0x61, 0x66, 0x32, 0x30, 0x30, 0x33, 0x62, 0x36, 0x34, 0x34, 0x34,
+    0x37, 0x38, 0x32, 0x34, 0x61, 0x36, 0x32, 0x37, 0x31, 0x30, 0x59, 0x30,
+    0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08,
+    0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04,
+    0xfe, 0x9d, 0xb2, 0xf9, 0x28, 0x09, 0xc3, 0x04, 0x12, 0x85, 0xdc, 0xd3,
+    0x70, 0x6f, 0x22, 0x1c, 0x72, 0xb6, 0xc4, 0x4f, 0xde, 0x93, 0xee, 0xfd,
+    0xfb, 0x6d, 0x57, 0x18, 0xfc, 0x8f, 0x6f, 0x0b, 0x09, 0x1a, 0x19, 0xea,
+    0x10, 0x7e, 0xa9, 0x38, 0xf4, 0x45, 0x33, 0xc1, 0x66, 0x5b, 0xbc, 0xfc,
+    0x0a, 0x6e, 0x98, 0x99, 0x72, 0x88, 0xc1, 0xad, 0x0e, 0x15, 0xc2, 0x85,
+    0x77, 0x75, 0x00, 0x0b, 0xa3, 0x82, 0x01, 0x4b, 0x30, 0x82, 0x01, 0x47,
+    0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+    0x14, 0x1b, 0xe5, 0x68, 0x79, 0x33, 0xdb, 0x3d, 0x9c, 0xd5, 0xfc, 0xa7,
+    0x29, 0xe8, 0x1d, 0x66, 0x85, 0x46, 0x5a, 0x7b, 0xf1, 0x30, 0x1d, 0x06,
+    0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x68, 0x49, 0x58, 0xd9,
+    0xae, 0xa7, 0x2e, 0xbf, 0x7c, 0x06, 0xaf, 0x20, 0x03, 0xb6, 0x44, 0x47,
+    0x82, 0x4a, 0x62, 0x71, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+    0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, 0x04, 0x30, 0x0f, 0x06, 0x03,
+    0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01,
+    0xff, 0x30, 0x81, 0xe3, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6,
+    0x79, 0x02, 0x01, 0x18, 0x04, 0x81, 0xd4, 0x30, 0x81, 0xd1, 0xa0, 0x42,
+    0x04, 0x40, 0xb7, 0xd4, 0x0c, 0xcb, 0x22, 0x5b, 0xa5, 0x78, 0x8f, 0x98,
+    0xff, 0x9e, 0x86, 0x93, 0x75, 0xf6, 0x90, 0xac, 0x50, 0xcf, 0x9e, 0xbd,
+    0x0a, 0xfe, 0xb1, 0xd9, 0xc2, 0x4e, 0x52, 0x19, 0xe4, 0xde, 0x29, 0xe5,
+    0x61, 0xf3, 0xf9, 0x29, 0xe8, 0x40, 0x87, 0x7a, 0xdd, 0x17, 0x48, 0x05,
+    0x89, 0x7e, 0x2b, 0xcb, 0x54, 0x79, 0xcc, 0x66, 0xf1, 0xb3, 0x13, 0x29,
+    0x0c, 0x68, 0x96, 0xb2, 0xbb, 0x8f, 0xa3, 0x42, 0x04, 0x40, 0xcf, 0x99,
+    0x7b, 0xea, 0x2e, 0x2c, 0x86, 0xa0, 0x7b, 0x52, 0x09, 0xc8, 0xb5, 0x3c,
+    0x41, 0x12, 0x29, 0x28, 0x1a, 0x82, 0x0d, 0x49, 0x9c, 0x95, 0xcb, 0x0b,
+    0x1b, 0x31, 0x1a, 0x01, 0x9c, 0xf2, 0x66, 0x1a, 0xd9, 0xb5, 0xce, 0x52,
+    0x59, 0xcb, 0xf4, 0x81, 0x9b, 0x21, 0xaf, 0x32, 0x5d, 0x07, 0xa0, 0x1e,
+    0x91, 0x59, 0x6f, 0x06, 0x55, 0x10, 0x8e, 0x2e, 0x08, 0x88, 0x52, 0x28,
+    0x86, 0x7f, 0xa4, 0x42, 0x04, 0x40, 0x22, 0x52, 0x60, 0x17, 0xef, 0x2c,
+    0xa1, 0xf6, 0xcb, 0xed, 0x39, 0xd5, 0xe2, 0xaa, 0x65, 0x20, 0xfb, 0xad,
+    0x82, 0x93, 0xe5, 0x78, 0x23, 0x22, 0x97, 0xc1, 0x6e, 0x6a, 0x4e, 0x36,
+    0xd7, 0x6a, 0x61, 0x39, 0x08, 0x21, 0xd4, 0xfe, 0x92, 0x5f, 0x36, 0x2d,
+    0xeb, 0x5d, 0xbb, 0x32, 0x8b, 0xe3, 0x94, 0x4f, 0xbe, 0x1b, 0x21, 0xf9,
+    0xcc, 0x23, 0x73, 0x41, 0xb6, 0xb9, 0xb6, 0x98, 0xd0, 0xbc, 0xa6, 0x03,
+    0x0a, 0x01, 0x00, 0x30, 0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d,
+    0x04, 0x03, 0x04, 0x05, 0x00, 0x03, 0x48, 0x00, 0x30, 0x45, 0x02, 0x21,
+    0x00, 0xa8, 0x1f, 0x9a, 0x1c, 0xbd, 0x6e, 0xf7, 0xa1, 0x6c, 0x4c, 0xa8,
+    0x98, 0xb3, 0xc9, 0xc7, 0x48, 0x8b, 0x2a, 0xaa, 0x29, 0x37, 0x35, 0x83,
+    0x8a, 0xe3, 0x64, 0x2b, 0xf2, 0xe2, 0xe2, 0xa1, 0x44, 0x02, 0x20, 0x5e,
+    0x80, 0x29, 0x7c, 0xa9, 0x7e, 0x8c, 0x44, 0x3d, 0x01, 0xf7, 0xea, 0xbd,
+    0x8a, 0x1f, 0xeb, 0xe8, 0xf6, 0x92, 0x43, 0x03, 0x16, 0xe6, 0xa9, 0x5d,
+    0xe7, 0x26, 0x42, 0xf4, 0x9b, 0x6e, 0x54};
+
+constexpr uint8_t kExpectedCborEd25519Cert_HashOnlyInput[441] = {
+    0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x6e, 0xa8, 0x01, 0x78,
+    0x28, 0x34, 0x37, 0x35, 0x37, 0x30, 0x38, 0x65, 0x62, 0x33, 0x62, 0x34,
+    0x32, 0x36, 0x66, 0x33, 0x38, 0x36, 0x63, 0x66, 0x63, 0x65, 0x38, 0x66,
+    0x33, 0x62, 0x61, 0x66, 0x35, 0x34, 0x33, 0x39, 0x30, 0x34, 0x36, 0x32,
+    0x37, 0x38, 0x64, 0x66, 0x61, 0x02, 0x78, 0x28, 0x30, 0x64, 0x30, 0x34,
+    0x30, 0x65, 0x32, 0x66, 0x34, 0x36, 0x30, 0x30, 0x35, 0x32, 0x61, 0x35,
+    0x33, 0x31, 0x31, 0x63, 0x31, 0x62, 0x39, 0x31, 0x64, 0x62, 0x66, 0x39,
+    0x62, 0x34, 0x34, 0x30, 0x38, 0x33, 0x33, 0x32, 0x65, 0x63, 0x32, 0x39,
+    0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0xb7, 0xd4, 0x0c, 0xcb, 0x22,
+    0x5b, 0xa5, 0x78, 0x8f, 0x98, 0xff, 0x9e, 0x86, 0x93, 0x75, 0xf6, 0x90,
+    0xac, 0x50, 0xcf, 0x9e, 0xbd, 0x0a, 0xfe, 0xb1, 0xd9, 0xc2, 0x4e, 0x52,
+    0x19, 0xe4, 0xde, 0x29, 0xe5, 0x61, 0xf3, 0xf9, 0x29, 0xe8, 0x40, 0x87,
+    0x7a, 0xdd, 0x17, 0x48, 0x05, 0x89, 0x7e, 0x2b, 0xcb, 0x54, 0x79, 0xcc,
+    0x66, 0xf1, 0xb3, 0x13, 0x29, 0x0c, 0x68, 0x96, 0xb2, 0xbb, 0x8f, 0x3a,
+    0x00, 0x47, 0x44, 0x53, 0x58, 0x40, 0xcf, 0x99, 0x7b, 0xea, 0x2e, 0x2c,
+    0x86, 0xa0, 0x7b, 0x52, 0x09, 0xc8, 0xb5, 0x3c, 0x41, 0x12, 0x29, 0x28,
+    0x1a, 0x82, 0x0d, 0x49, 0x9c, 0x95, 0xcb, 0x0b, 0x1b, 0x31, 0x1a, 0x01,
+    0x9c, 0xf2, 0x66, 0x1a, 0xd9, 0xb5, 0xce, 0x52, 0x59, 0xcb, 0xf4, 0x81,
+    0x9b, 0x21, 0xaf, 0x32, 0x5d, 0x07, 0xa0, 0x1e, 0x91, 0x59, 0x6f, 0x06,
+    0x55, 0x10, 0x8e, 0x2e, 0x08, 0x88, 0x52, 0x28, 0x86, 0x7f, 0x3a, 0x00,
+    0x47, 0x44, 0x54, 0x58, 0x40, 0x22, 0x52, 0x60, 0x17, 0xef, 0x2c, 0xa1,
+    0xf6, 0xcb, 0xed, 0x39, 0xd5, 0xe2, 0xaa, 0x65, 0x20, 0xfb, 0xad, 0x82,
+    0x93, 0xe5, 0x78, 0x23, 0x22, 0x97, 0xc1, 0x6e, 0x6a, 0x4e, 0x36, 0xd7,
+    0x6a, 0x61, 0x39, 0x08, 0x21, 0xd4, 0xfe, 0x92, 0x5f, 0x36, 0x2d, 0xeb,
+    0x5d, 0xbb, 0x32, 0x8b, 0xe3, 0x94, 0x4f, 0xbe, 0x1b, 0x21, 0xf9, 0xcc,
+    0x23, 0x73, 0x41, 0xb6, 0xb9, 0xb6, 0x98, 0xd0, 0xbc, 0x3a, 0x00, 0x47,
+    0x44, 0x56, 0x41, 0x00, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5,
+    0x01, 0x01, 0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20,
+    0x5a, 0x39, 0x49, 0x67, 0x8c, 0xd3, 0x0e, 0x88, 0xab, 0x1c, 0xdd, 0xf7,
+    0x15, 0x55, 0xd5, 0xbf, 0xd3, 0xf0, 0xb8, 0x47, 0x25, 0xa9, 0x58, 0xe1,
+    0xb9, 0xda, 0x4e, 0xb5, 0xf1, 0x38, 0x9a, 0x5a, 0x3a, 0x00, 0x47, 0x44,
+    0x58, 0x41, 0x20, 0x58, 0x40, 0x82, 0x99, 0xff, 0x84, 0x55, 0xcb, 0xf9,
+    0x99, 0x89, 0x48, 0x99, 0x12, 0x1d, 0x04, 0x40, 0xcf, 0x90, 0xa4, 0xbc,
+    0x61, 0x4f, 0x0d, 0x2e, 0x77, 0x2e, 0x9c, 0x8f, 0xaa, 0xdd, 0xf4, 0x2f,
+    0xe2, 0x14, 0xd2, 0x42, 0x4a, 0x02, 0x9e, 0x1d, 0x24, 0x72, 0x0b, 0x08,
+    0xb6, 0x71, 0xc7, 0x76, 0x64, 0x25, 0xfb, 0x03, 0xcf, 0xd6, 0x6f, 0x2f,
+    0x9a, 0x15, 0xc8, 0xad, 0x47, 0x9a, 0xf3, 0x16, 0x01};
+
+constexpr uint8_t kExpectedCborP256Cert_HashOnlyInput[0] = {};
+
+constexpr uint8_t kExpectedCdiAttest_DescriptorInput[32] = {
+    0x20, 0xd5, 0x0c, 0x68, 0x5a, 0xd9, 0xe2, 0xdf, 0x77, 0x60, 0x78,
+    0x68, 0x19, 0x00, 0x24, 0xc2, 0x04, 0x4f, 0xb8, 0xde, 0x79, 0xaa,
+    0x9f, 0x5f, 0x12, 0xfc, 0xa8, 0xda, 0x37, 0x08, 0xa3, 0x10};
+
+constexpr uint8_t kExpectedCdiSeal_DescriptorInput[32] = {
+    0x90, 0xc9, 0xa2, 0x86, 0x5d, 0xf4, 0xfa, 0x58, 0x30, 0x64, 0x3d,
+    0x6c, 0xae, 0xf0, 0x7c, 0x76, 0xae, 0xaa, 0x15, 0x61, 0x98, 0x28,
+    0xf1, 0xbd, 0xa7, 0xf7, 0x44, 0x82, 0xe2, 0xf0, 0xae, 0x1e};
+
+// $ openssl x509 -inform DER -noout -text -certopt ext_parse
+// Certificate:
+//     Data:
+//         Version: 3 (0x2)
+//         Serial Number:
+//             52:1f:03:5c:21:e3:2f:16:74:1c:1e:ae:6b:de:d9:3c:e3:21:e0:df
+//         Signature Algorithm: ED25519
+//         Issuer: serialNumber = 475708eb3b426f386cfce8f3baf5439046278dfa
+//         Validity
+//             Not Before: Mar 22 23:59:59 2018 GMT
+//             Not After : Dec 31 23:59:59 9999 GMT
+//         Subject: serialNumber = 521f035c21e32f16741c1eae6bded93ce321e0df
+//         Subject Public Key Info:
+//             Public Key Algorithm: ED25519
+//                 ED25519 Public-Key:
+//                 pub:
+//                     93:7f:d9:c0:4d:c6:bb:2e:1d:11:62:cd:5c:76:94:
+//                     c7:db:02:54:0c:85:01:3a:01:ab:37:fa:ce:f9:6e:
+//                     62:20
+//         X509v3 extensions:
+//             X509v3 Authority Key Identifier:
+//                 keyid:47:57:08:EB:3B:42:6F:38:6C:FC:E8:F3:BA:F5:43:90:46:27:8D:FA
+//
+//             X509v3 Subject Key Identifier:
+//                 52:1F:03:5C:21:E3:2F:16:74:1C:1E:AE:6B:DE:D9:3C:E3:21:E0:DF
+//             X509v3 Key Usage: critical
+//                 Certificate Sign
+//             X509v3 Basic Constraints: critical
+//                 CA:TRUE
+//             1.3.6.1.4.1.11129.2.1.24:
+//     0:d=0  hl=4 l= 426 cons: SEQUENCE
+//     4:d=1  hl=2 l=  66 cons:  cont [ 0 ]
+//     6:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - b7 d4 0c cb 22 5b a5 78-8f 98 ff 9e 86 93 75 f6 ...."[.x......u.
+//       0010 - 90 ac 50 cf 9e bd 0a fe-b1 d9 c2 4e 52 19 e4 de ..P........NR...
+//       0020 - 29 e5 61 f3 f9 29 e8 40-87 7a dd 17 48 05 89 7e ).a..).@.z..H..~
+//       0030 - 2b cb 54 79 cc 66 f1 b3-13 29 0c 68 96 b2 bb 8f +.Ty.f...).h....
+//    72:d=1  hl=2 l= 102 cons:  cont [ 1 ]
+//    74:d=2  hl=2 l= 100 prim:   OCTET STRING
+//       0000 - 6c 46 01 33 26 73 4b 22-65 fd fa 58 d7 57 3e 95 lF.3&sK"e..X.W>.
+//       0010 - 59 e0 3a c3 b9 f7 c8 0e-98 80 8c f5 c4 b8 af e3 Y.:.............
+//       0020 - 16 84 25 a5 35 5d 17 72-56 8f 8e ec 2f 5a 74 60 ..%.5].rV.../Zt`
+//       0030 - 77 2a 6e 90 c0 4e 9f 87-6b f4 8d 9c 66 e3 0b d2 w*n..N..k...f...
+//       0040 - 10 35 21 a8 1d a2 31 17-e7 0c df 18 f7 94 e4 d1 .5!...1.........
+//       0050 - ca 32 7d f2 63 23 1d bc-84 74 61 db 87 f2 ab 72 .2}.c#...ta....r
+//       0060 - ad af 08 f8                                       ....
+//   176:d=1  hl=2 l=  66 cons:  cont [ 2 ]
+//   178:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - 45 00 e9 5c bd 00 57 04-55 87 6c bd 2f ea 41 9c E..\..W.U.l./.A.
+//       0010 - 66 42 51 41 bb 44 ed 0e-e9 66 cf d5 10 73 0d 4b fBQA.D...f...s.K
+//       0020 - 48 e4 7a 53 35 01 0e 6d-15 55 c5 b7 d2 d5 36 b6 H.zS5..m.U....6.
+//       0030 - bc 7e b0 f3 3d e6 19 78-62 eb 02 57 39 56 73 4f .~..=..xb..W9VsO
+//   244:d=1  hl=2 l=  42 cons:  cont [ 3 ]
+//   246:d=2  hl=2 l=  40 prim:   OCTET STRING
+//       0000 - 1b 40 c1 a9 77 60 eb c3-67 f0 5f 6a e1 5e 20 c2   .@..w`..g._j.^
+//       . 0010 - 51 68 4d 82 48 8b 03 32-16 79 88 14 37 78 7f 16
+//       QhM.H..2.y..7x.. 0020 - 9a 06 fd c0 8a 15 80 62- .......b
+//   288:d=1  hl=2 l=  66 cons:  cont [ 4 ]
+//   290:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - 22 52 60 17 ef 2c a1 f6-cb ed 39 d5 e2 aa 65 20 "R`..,....9...e
+//       0010 - fb ad 82 93 e5 78 23 22-97 c1 6e 6a 4e 36 d7 6a .....x#"..njN6.j
+//       0020 - 61 39 08 21 d4 fe 92 5f-36 2d eb 5d bb 32 8b e3 a9.!..._6-.].2..
+//       0030 - 94 4f be 1b 21 f9 cc 23-73 41 b6 b9 b6 98 d0 bc .O..!..#sA......
+//   356:d=1  hl=2 l=  67 cons:  cont [ 5 ]
+//   358:d=2  hl=2 l=  65 prim:   OCTET STRING
+//       0000 - 92 d6 97 b3 83 df e7 8c-c7 bc 4a fc ea 76 c0 53 ..........J..v.S
+//       0010 - 66 bd 2c 1e 10 31 90 80-11 2d 08 4d 7c 39 76 dc f.,..1...-.M|9v.
+//       0020 - 73 e7 1c 16 62 d5 59 d7-49 2b 6a a2 36 67 57 d1 s...b.Y.I+j.6gW.
+//       0030 - f2 f9 af 13 d7 a3 e4 d3-39 5b 02 78 b1 e0 09 70 ........9[.x...p
+//       0040 - a2                                                .
+//   425:d=1  hl=2 l=   3 cons:  cont [ 6 ]
+//   427:d=2  hl=2 l=   1 prim:   ENUMERATED        :00
+//
+//     Signature Algorithm: ED25519
+//          ce:e6:a6:f7:5a:09:2e:a9:f1:27:73:46:61:21:5a:f7:15:c4:
+//          a5:31:43:37:b5:2f:3d:c8:61:f1:d2:65:56:84:81:e2:c5:c4:
+//          a0:87:55:d0:55:15:ce:14:d5:8e:94:5a:f9:b7:0e:09:91:3b:
+//          25:75:e2:ea:cb:7d:37:72:54:0f
+constexpr uint8_t kExpectedX509Ed25519Cert_DescriptorInput[855] = {
+    0x30, 0x82, 0x03, 0x53, 0x30, 0x82, 0x03, 0x05, 0xa0, 0x03, 0x02, 0x01,
+    0x02, 0x02, 0x14, 0x52, 0x1f, 0x03, 0x5c, 0x21, 0xe3, 0x2f, 0x16, 0x74,
+    0x1c, 0x1e, 0xae, 0x6b, 0xde, 0xd9, 0x3c, 0xe3, 0x21, 0xe0, 0xdf, 0x30,
+    0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x30, 0x33, 0x31, 0x31, 0x30, 0x2f,
+    0x06, 0x03, 0x55, 0x04, 0x05, 0x13, 0x28, 0x34, 0x37, 0x35, 0x37, 0x30,
+    0x38, 0x65, 0x62, 0x33, 0x62, 0x34, 0x32, 0x36, 0x66, 0x33, 0x38, 0x36,
+    0x63, 0x66, 0x63, 0x65, 0x38, 0x66, 0x33, 0x62, 0x61, 0x66, 0x35, 0x34,
+    0x33, 0x39, 0x30, 0x34, 0x36, 0x32, 0x37, 0x38, 0x64, 0x66, 0x61, 0x30,
+    0x20, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x33, 0x32, 0x32, 0x32, 0x33, 0x35,
+    0x39, 0x35, 0x39, 0x5a, 0x18, 0x0f, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32,
+    0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x33, 0x31,
+    0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x05, 0x13, 0x28, 0x35, 0x32,
+    0x31, 0x66, 0x30, 0x33, 0x35, 0x63, 0x32, 0x31, 0x65, 0x33, 0x32, 0x66,
+    0x31, 0x36, 0x37, 0x34, 0x31, 0x63, 0x31, 0x65, 0x61, 0x65, 0x36, 0x62,
+    0x64, 0x65, 0x64, 0x39, 0x33, 0x63, 0x65, 0x33, 0x32, 0x31, 0x65, 0x30,
+    0x64, 0x66, 0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03,
+    0x21, 0x00, 0x93, 0x7f, 0xd9, 0xc0, 0x4d, 0xc6, 0xbb, 0x2e, 0x1d, 0x11,
+    0x62, 0xcd, 0x5c, 0x76, 0x94, 0xc7, 0xdb, 0x02, 0x54, 0x0c, 0x85, 0x01,
+    0x3a, 0x01, 0xab, 0x37, 0xfa, 0xce, 0xf9, 0x6e, 0x62, 0x20, 0xa3, 0x82,
+    0x02, 0x27, 0x30, 0x82, 0x02, 0x23, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
+    0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x47, 0x57, 0x08, 0xeb, 0x3b,
+    0x42, 0x6f, 0x38, 0x6c, 0xfc, 0xe8, 0xf3, 0xba, 0xf5, 0x43, 0x90, 0x46,
+    0x27, 0x8d, 0xfa, 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16,
+    0x04, 0x14, 0x52, 0x1f, 0x03, 0x5c, 0x21, 0xe3, 0x2f, 0x16, 0x74, 0x1c,
+    0x1e, 0xae, 0x6b, 0xde, 0xd9, 0x3c, 0xe3, 0x21, 0xe0, 0xdf, 0x30, 0x0e,
+    0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04, 0x03, 0x02,
+    0x02, 0x04, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff,
+    0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0x01, 0x30, 0x82, 0x01, 0xbe, 0x06,
+    0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x18, 0x04,
+    0x82, 0x01, 0xae, 0x30, 0x82, 0x01, 0xaa, 0xa0, 0x42, 0x04, 0x40, 0xb7,
+    0xd4, 0x0c, 0xcb, 0x22, 0x5b, 0xa5, 0x78, 0x8f, 0x98, 0xff, 0x9e, 0x86,
+    0x93, 0x75, 0xf6, 0x90, 0xac, 0x50, 0xcf, 0x9e, 0xbd, 0x0a, 0xfe, 0xb1,
+    0xd9, 0xc2, 0x4e, 0x52, 0x19, 0xe4, 0xde, 0x29, 0xe5, 0x61, 0xf3, 0xf9,
+    0x29, 0xe8, 0x40, 0x87, 0x7a, 0xdd, 0x17, 0x48, 0x05, 0x89, 0x7e, 0x2b,
+    0xcb, 0x54, 0x79, 0xcc, 0x66, 0xf1, 0xb3, 0x13, 0x29, 0x0c, 0x68, 0x96,
+    0xb2, 0xbb, 0x8f, 0xa1, 0x66, 0x04, 0x64, 0x6c, 0x46, 0x01, 0x33, 0x26,
+    0x73, 0x4b, 0x22, 0x65, 0xfd, 0xfa, 0x58, 0xd7, 0x57, 0x3e, 0x95, 0x59,
+    0xe0, 0x3a, 0xc3, 0xb9, 0xf7, 0xc8, 0x0e, 0x98, 0x80, 0x8c, 0xf5, 0xc4,
+    0xb8, 0xaf, 0xe3, 0x16, 0x84, 0x25, 0xa5, 0x35, 0x5d, 0x17, 0x72, 0x56,
+    0x8f, 0x8e, 0xec, 0x2f, 0x5a, 0x74, 0x60, 0x77, 0x2a, 0x6e, 0x90, 0xc0,
+    0x4e, 0x9f, 0x87, 0x6b, 0xf4, 0x8d, 0x9c, 0x66, 0xe3, 0x0b, 0xd2, 0x10,
+    0x35, 0x21, 0xa8, 0x1d, 0xa2, 0x31, 0x17, 0xe7, 0x0c, 0xdf, 0x18, 0xf7,
+    0x94, 0xe4, 0xd1, 0xca, 0x32, 0x7d, 0xf2, 0x63, 0x23, 0x1d, 0xbc, 0x84,
+    0x74, 0x61, 0xdb, 0x87, 0xf2, 0xab, 0x72, 0xad, 0xaf, 0x08, 0xf8, 0xa2,
+    0x42, 0x04, 0x40, 0x45, 0x00, 0xe9, 0x5c, 0xbd, 0x00, 0x57, 0x04, 0x55,
+    0x87, 0x6c, 0xbd, 0x2f, 0xea, 0x41, 0x9c, 0x66, 0x42, 0x51, 0x41, 0xbb,
+    0x44, 0xed, 0x0e, 0xe9, 0x66, 0xcf, 0xd5, 0x10, 0x73, 0x0d, 0x4b, 0x48,
+    0xe4, 0x7a, 0x53, 0x35, 0x01, 0x0e, 0x6d, 0x15, 0x55, 0xc5, 0xb7, 0xd2,
+    0xd5, 0x36, 0xb6, 0xbc, 0x7e, 0xb0, 0xf3, 0x3d, 0xe6, 0x19, 0x78, 0x62,
+    0xeb, 0x02, 0x57, 0x39, 0x56, 0x73, 0x4f, 0xa3, 0x2a, 0x04, 0x28, 0x1b,
+    0x40, 0xc1, 0xa9, 0x77, 0x60, 0xeb, 0xc3, 0x67, 0xf0, 0x5f, 0x6a, 0xe1,
+    0x5e, 0x20, 0xc2, 0x51, 0x68, 0x4d, 0x82, 0x48, 0x8b, 0x03, 0x32, 0x16,
+    0x79, 0x88, 0x14, 0x37, 0x78, 0x7f, 0x16, 0x9a, 0x06, 0xfd, 0xc0, 0x8a,
+    0x15, 0x80, 0x62, 0xa4, 0x42, 0x04, 0x40, 0x22, 0x52, 0x60, 0x17, 0xef,
+    0x2c, 0xa1, 0xf6, 0xcb, 0xed, 0x39, 0xd5, 0xe2, 0xaa, 0x65, 0x20, 0xfb,
+    0xad, 0x82, 0x93, 0xe5, 0x78, 0x23, 0x22, 0x97, 0xc1, 0x6e, 0x6a, 0x4e,
+    0x36, 0xd7, 0x6a, 0x61, 0x39, 0x08, 0x21, 0xd4, 0xfe, 0x92, 0x5f, 0x36,
+    0x2d, 0xeb, 0x5d, 0xbb, 0x32, 0x8b, 0xe3, 0x94, 0x4f, 0xbe, 0x1b, 0x21,
+    0xf9, 0xcc, 0x23, 0x73, 0x41, 0xb6, 0xb9, 0xb6, 0x98, 0xd0, 0xbc, 0xa5,
+    0x43, 0x04, 0x41, 0x92, 0xd6, 0x97, 0xb3, 0x83, 0xdf, 0xe7, 0x8c, 0xc7,
+    0xbc, 0x4a, 0xfc, 0xea, 0x76, 0xc0, 0x53, 0x66, 0xbd, 0x2c, 0x1e, 0x10,
+    0x31, 0x90, 0x80, 0x11, 0x2d, 0x08, 0x4d, 0x7c, 0x39, 0x76, 0xdc, 0x73,
+    0xe7, 0x1c, 0x16, 0x62, 0xd5, 0x59, 0xd7, 0x49, 0x2b, 0x6a, 0xa2, 0x36,
+    0x67, 0x57, 0xd1, 0xf2, 0xf9, 0xaf, 0x13, 0xd7, 0xa3, 0xe4, 0xd3, 0x39,
+    0x5b, 0x02, 0x78, 0xb1, 0xe0, 0x09, 0x70, 0xa2, 0xa6, 0x03, 0x0a, 0x01,
+    0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x41, 0x00, 0xce,
+    0xe6, 0xa6, 0xf7, 0x5a, 0x09, 0x2e, 0xa9, 0xf1, 0x27, 0x73, 0x46, 0x61,
+    0x21, 0x5a, 0xf7, 0x15, 0xc4, 0xa5, 0x31, 0x43, 0x37, 0xb5, 0x2f, 0x3d,
+    0xc8, 0x61, 0xf1, 0xd2, 0x65, 0x56, 0x84, 0x81, 0xe2, 0xc5, 0xc4, 0xa0,
+    0x87, 0x55, 0xd0, 0x55, 0x15, 0xce, 0x14, 0xd5, 0x8e, 0x94, 0x5a, 0xf9,
+    0xb7, 0x0e, 0x09, 0x91, 0x3b, 0x25, 0x75, 0xe2, 0xea, 0xcb, 0x7d, 0x37,
+    0x72, 0x54, 0x0f};
+
+// $ openssl x509 -inform DER -noout -text -certopt ext_parse
+// Certificate:
+//     Data:
+//         Version: 3 (0x2)
+//         Serial Number:
+//             2c:0d:e9:55:c4:fa:08:2c:2c:3a:0b:40:66:59:af:a1:c1:c0:84:6c
+//         Signature Algorithm: ecdsa-with-SHA512
+//         Issuer: serialNumber = 1be5687933db3d9cd5fca729e81d6685465a7bf1
+//         Validity
+//             Not Before: Mar 22 23:59:59 2018 GMT
+//             Not After : Dec 31 23:59:59 9999 GMT
+//         Subject: serialNumber = 2c0de955c4fa082c2c3a0b406659afa1c1c0846c
+//         Subject Public Key Info:
+//             Public Key Algorithm: id-ecPublicKey
+//                 Public-Key: (256 bit)
+//                 pub:
+//                     04:6d:1e:dd:35:38:70:c2:8a:01:df:80:b1:a5:ae:
+//                     85:4b:7a:12:dd:11:f6:97:27:44:9b:27:f3:87:97:
+//                     b3:e7:36:e6:42:87:8c:72:de:f7:af:2d:c6:23:00:
+//                     b1:2b:4e:1c:f3:af:67:f0:9b:88:40:79:3b:09:78:
+//                     30:51:65:38:61
+//                 ASN1 OID: prime256v1
+//                 NIST CURVE: P-256
+//         X509v3 extensions:
+//             X509v3 Authority Key Identifier:
+//                 keyid:1B:E5:68:79:33:DB:3D:9C:D5:FC:A7:29:E8:1D:66:85:46:5A:7B:F1
+//
+//             X509v3 Subject Key Identifier:
+//                 2C:0D:E9:55:C4:FA:08:2C:2C:3A:0B:40:66:59:AF:A1:C1:C0:84:6C
+//             X509v3 Key Usage: critical
+//                 Certificate Sign
+//             X509v3 Basic Constraints: critical
+//                 CA:TRUE
+//             1.3.6.1.4.1.11129.2.1.24:
+//     0:d=0  hl=4 l= 426 cons: SEQUENCE
+//     4:d=1  hl=2 l=  66 cons:  cont [ 0 ]
+//     6:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - b7 d4 0c cb 22 5b a5 78-8f 98 ff 9e 86 93 75 f6 ...."[.x......u.
+//       0010 - 90 ac 50 cf 9e bd 0a fe-b1 d9 c2 4e 52 19 e4 de ..P........NR...
+//       0020 - 29 e5 61 f3 f9 29 e8 40-87 7a dd 17 48 05 89 7e ).a..).@.z..H..~
+//       0030 - 2b cb 54 79 cc 66 f1 b3-13 29 0c 68 96 b2 bb 8f +.Ty.f...).h....
+//    72:d=1  hl=2 l= 102 cons:  cont [ 1 ]
+//    74:d=2  hl=2 l= 100 prim:   OCTET STRING
+//       0000 - 6c 46 01 33 26 73 4b 22-65 fd fa 58 d7 57 3e 95 lF.3&sK"e..X.W>.
+//       0010 - 59 e0 3a c3 b9 f7 c8 0e-98 80 8c f5 c4 b8 af e3 Y.:.............
+//       0020 - 16 84 25 a5 35 5d 17 72-56 8f 8e ec 2f 5a 74 60 ..%.5].rV.../Zt`
+//       0030 - 77 2a 6e 90 c0 4e 9f 87-6b f4 8d 9c 66 e3 0b d2 w*n..N..k...f...
+//       0040 - 10 35 21 a8 1d a2 31 17-e7 0c df 18 f7 94 e4 d1 .5!...1.........
+//       0050 - ca 32 7d f2 63 23 1d bc-84 74 61 db 87 f2 ab 72 .2}.c#...ta....r
+//       0060 - ad af 08 f8                                       ....
+//   176:d=1  hl=2 l=  66 cons:  cont [ 2 ]
+//   178:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - 45 00 e9 5c bd 00 57 04-55 87 6c bd 2f ea 41 9c E..\..W.U.l./.A.
+//       0010 - 66 42 51 41 bb 44 ed 0e-e9 66 cf d5 10 73 0d 4b fBQA.D...f...s.K
+//       0020 - 48 e4 7a 53 35 01 0e 6d-15 55 c5 b7 d2 d5 36 b6 H.zS5..m.U....6.
+//       0030 - bc 7e b0 f3 3d e6 19 78-62 eb 02 57 39 56 73 4f .~..=..xb..W9VsO
+//   244:d=1  hl=2 l=  42 cons:  cont [ 3 ]
+//   246:d=2  hl=2 l=  40 prim:   OCTET STRING
+//       0000 - 1b 40 c1 a9 77 60 eb c3-67 f0 5f 6a e1 5e 20 c2   .@..w`..g._j.^
+//       . 0010 - 51 68 4d 82 48 8b 03 32-16 79 88 14 37 78 7f 16
+//       QhM.H..2.y..7x.. 0020 - 9a 06 fd c0 8a 15 80 62- .......b
+//   288:d=1  hl=2 l=  66 cons:  cont [ 4 ]
+//   290:d=2  hl=2 l=  64 prim:   OCTET STRING
+//       0000 - 22 52 60 17 ef 2c a1 f6-cb ed 39 d5 e2 aa 65 20 "R`..,....9...e
+//       0010 - fb ad 82 93 e5 78 23 22-97 c1 6e 6a 4e 36 d7 6a .....x#"..njN6.j
+//       0020 - 61 39 08 21 d4 fe 92 5f-36 2d eb 5d bb 32 8b e3 a9.!..._6-.].2..
+//       0030 - 94 4f be 1b 21 f9 cc 23-73 41 b6 b9 b6 98 d0 bc .O..!..#sA......
+//   356:d=1  hl=2 l=  67 cons:  cont [ 5 ]
+//   358:d=2  hl=2 l=  65 prim:   OCTET STRING
+//       0000 - 92 d6 97 b3 83 df e7 8c-c7 bc 4a fc ea 76 c0 53 ..........J..v.S
+//       0010 - 66 bd 2c 1e 10 31 90 80-11 2d 08 4d 7c 39 76 dc f.,..1...-.M|9v.
+//       0020 - 73 e7 1c 16 62 d5 59 d7-49 2b 6a a2 36 67 57 d1 s...b.Y.I+j.6gW.
+//       0030 - f2 f9 af 13 d7 a3 e4 d3-39 5b 02 78 b1 e0 09 70 ........9[.x...p
+//       0040 - a2                                                .
+//   425:d=1  hl=2 l=   3 cons:  cont [ 6 ]
+//   427:d=2  hl=2 l=   1 prim:   ENUMERATED        :00
+//
+//     Signature Algorithm: ecdsa-with-SHA512
+//          30:45:02:21:00:b2:0b:bb:86:04:4e:38:55:db:f5:58:92:e1:
+//          c1:31:4e:79:57:e4:5f:bc:36:68:29:b4:89:a5:63:4e:67:19:
+//          8c:02:20:1b:5d:e6:35:c0:cd:9f:7b:ec:c0:01:52:b8:2f:81:
+//          f0:29:01:a0:17:19:83:ca:84:e2:ec:01:a8:b2:13:8a:b9
+constexpr uint8_t kExpectedX509P256Cert_DescriptorInput[923] = {
+    0x30, 0x82, 0x03, 0x97, 0x30, 0x82, 0x03, 0x3b, 0xa0, 0x03, 0x02, 0x01,
+    0x02, 0x02, 0x14, 0x2c, 0x0d, 0xe9, 0x55, 0xc4, 0xfa, 0x08, 0x2c, 0x2c,
+    0x3a, 0x0b, 0x40, 0x66, 0x59, 0xaf, 0xa1, 0xc1, 0xc0, 0x84, 0x6c, 0x30,
+    0x0c, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04, 0x05,
+    0x00, 0x30, 0x33, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x05,
+    0x13, 0x28, 0x31, 0x62, 0x65, 0x35, 0x36, 0x38, 0x37, 0x39, 0x33, 0x33,
+    0x64, 0x62, 0x33, 0x64, 0x39, 0x63, 0x64, 0x35, 0x66, 0x63, 0x61, 0x37,
+    0x32, 0x39, 0x65, 0x38, 0x31, 0x64, 0x36, 0x36, 0x38, 0x35, 0x34, 0x36,
+    0x35, 0x61, 0x37, 0x62, 0x66, 0x31, 0x30, 0x20, 0x17, 0x0d, 0x31, 0x38,
+    0x30, 0x33, 0x32, 0x32, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x18,
+    0x0f, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35,
+    0x39, 0x35, 0x39, 0x5a, 0x30, 0x33, 0x31, 0x31, 0x30, 0x2f, 0x06, 0x03,
+    0x55, 0x04, 0x05, 0x13, 0x28, 0x32, 0x63, 0x30, 0x64, 0x65, 0x39, 0x35,
+    0x35, 0x63, 0x34, 0x66, 0x61, 0x30, 0x38, 0x32, 0x63, 0x32, 0x63, 0x33,
+    0x61, 0x30, 0x62, 0x34, 0x30, 0x36, 0x36, 0x35, 0x39, 0x61, 0x66, 0x61,
+    0x31, 0x63, 0x31, 0x63, 0x30, 0x38, 0x34, 0x36, 0x63, 0x30, 0x59, 0x30,
+    0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08,
+    0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, 0x00, 0x04,
+    0x6d, 0x1e, 0xdd, 0x35, 0x38, 0x70, 0xc2, 0x8a, 0x01, 0xdf, 0x80, 0xb1,
+    0xa5, 0xae, 0x85, 0x4b, 0x7a, 0x12, 0xdd, 0x11, 0xf6, 0x97, 0x27, 0x44,
+    0x9b, 0x27, 0xf3, 0x87, 0x97, 0xb3, 0xe7, 0x36, 0xe6, 0x42, 0x87, 0x8c,
+    0x72, 0xde, 0xf7, 0xaf, 0x2d, 0xc6, 0x23, 0x00, 0xb1, 0x2b, 0x4e, 0x1c,
+    0xf3, 0xaf, 0x67, 0xf0, 0x9b, 0x88, 0x40, 0x79, 0x3b, 0x09, 0x78, 0x30,
+    0x51, 0x65, 0x38, 0x61, 0xa3, 0x82, 0x02, 0x27, 0x30, 0x82, 0x02, 0x23,
+    0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80,
+    0x14, 0x1b, 0xe5, 0x68, 0x79, 0x33, 0xdb, 0x3d, 0x9c, 0xd5, 0xfc, 0xa7,
+    0x29, 0xe8, 0x1d, 0x66, 0x85, 0x46, 0x5a, 0x7b, 0xf1, 0x30, 0x1d, 0x06,
+    0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x2c, 0x0d, 0xe9, 0x55,
+    0xc4, 0xfa, 0x08, 0x2c, 0x2c, 0x3a, 0x0b, 0x40, 0x66, 0x59, 0xaf, 0xa1,
+    0xc1, 0xc0, 0x84, 0x6c, 0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01,
+    0x01, 0xff, 0x04, 0x04, 0x03, 0x02, 0x02, 0x04, 0x30, 0x0f, 0x06, 0x03,
+    0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01,
+    0xff, 0x30, 0x82, 0x01, 0xbe, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01,
+    0xd6, 0x79, 0x02, 0x01, 0x18, 0x04, 0x82, 0x01, 0xae, 0x30, 0x82, 0x01,
+    0xaa, 0xa0, 0x42, 0x04, 0x40, 0xb7, 0xd4, 0x0c, 0xcb, 0x22, 0x5b, 0xa5,
+    0x78, 0x8f, 0x98, 0xff, 0x9e, 0x86, 0x93, 0x75, 0xf6, 0x90, 0xac, 0x50,
+    0xcf, 0x9e, 0xbd, 0x0a, 0xfe, 0xb1, 0xd9, 0xc2, 0x4e, 0x52, 0x19, 0xe4,
+    0xde, 0x29, 0xe5, 0x61, 0xf3, 0xf9, 0x29, 0xe8, 0x40, 0x87, 0x7a, 0xdd,
+    0x17, 0x48, 0x05, 0x89, 0x7e, 0x2b, 0xcb, 0x54, 0x79, 0xcc, 0x66, 0xf1,
+    0xb3, 0x13, 0x29, 0x0c, 0x68, 0x96, 0xb2, 0xbb, 0x8f, 0xa1, 0x66, 0x04,
+    0x64, 0x6c, 0x46, 0x01, 0x33, 0x26, 0x73, 0x4b, 0x22, 0x65, 0xfd, 0xfa,
+    0x58, 0xd7, 0x57, 0x3e, 0x95, 0x59, 0xe0, 0x3a, 0xc3, 0xb9, 0xf7, 0xc8,
+    0x0e, 0x98, 0x80, 0x8c, 0xf5, 0xc4, 0xb8, 0xaf, 0xe3, 0x16, 0x84, 0x25,
+    0xa5, 0x35, 0x5d, 0x17, 0x72, 0x56, 0x8f, 0x8e, 0xec, 0x2f, 0x5a, 0x74,
+    0x60, 0x77, 0x2a, 0x6e, 0x90, 0xc0, 0x4e, 0x9f, 0x87, 0x6b, 0xf4, 0x8d,
+    0x9c, 0x66, 0xe3, 0x0b, 0xd2, 0x10, 0x35, 0x21, 0xa8, 0x1d, 0xa2, 0x31,
+    0x17, 0xe7, 0x0c, 0xdf, 0x18, 0xf7, 0x94, 0xe4, 0xd1, 0xca, 0x32, 0x7d,
+    0xf2, 0x63, 0x23, 0x1d, 0xbc, 0x84, 0x74, 0x61, 0xdb, 0x87, 0xf2, 0xab,
+    0x72, 0xad, 0xaf, 0x08, 0xf8, 0xa2, 0x42, 0x04, 0x40, 0x45, 0x00, 0xe9,
+    0x5c, 0xbd, 0x00, 0x57, 0x04, 0x55, 0x87, 0x6c, 0xbd, 0x2f, 0xea, 0x41,
+    0x9c, 0x66, 0x42, 0x51, 0x41, 0xbb, 0x44, 0xed, 0x0e, 0xe9, 0x66, 0xcf,
+    0xd5, 0x10, 0x73, 0x0d, 0x4b, 0x48, 0xe4, 0x7a, 0x53, 0x35, 0x01, 0x0e,
+    0x6d, 0x15, 0x55, 0xc5, 0xb7, 0xd2, 0xd5, 0x36, 0xb6, 0xbc, 0x7e, 0xb0,
+    0xf3, 0x3d, 0xe6, 0x19, 0x78, 0x62, 0xeb, 0x02, 0x57, 0x39, 0x56, 0x73,
+    0x4f, 0xa3, 0x2a, 0x04, 0x28, 0x1b, 0x40, 0xc1, 0xa9, 0x77, 0x60, 0xeb,
+    0xc3, 0x67, 0xf0, 0x5f, 0x6a, 0xe1, 0x5e, 0x20, 0xc2, 0x51, 0x68, 0x4d,
+    0x82, 0x48, 0x8b, 0x03, 0x32, 0x16, 0x79, 0x88, 0x14, 0x37, 0x78, 0x7f,
+    0x16, 0x9a, 0x06, 0xfd, 0xc0, 0x8a, 0x15, 0x80, 0x62, 0xa4, 0x42, 0x04,
+    0x40, 0x22, 0x52, 0x60, 0x17, 0xef, 0x2c, 0xa1, 0xf6, 0xcb, 0xed, 0x39,
+    0xd5, 0xe2, 0xaa, 0x65, 0x20, 0xfb, 0xad, 0x82, 0x93, 0xe5, 0x78, 0x23,
+    0x22, 0x97, 0xc1, 0x6e, 0x6a, 0x4e, 0x36, 0xd7, 0x6a, 0x61, 0x39, 0x08,
+    0x21, 0xd4, 0xfe, 0x92, 0x5f, 0x36, 0x2d, 0xeb, 0x5d, 0xbb, 0x32, 0x8b,
+    0xe3, 0x94, 0x4f, 0xbe, 0x1b, 0x21, 0xf9, 0xcc, 0x23, 0x73, 0x41, 0xb6,
+    0xb9, 0xb6, 0x98, 0xd0, 0xbc, 0xa5, 0x43, 0x04, 0x41, 0x92, 0xd6, 0x97,
+    0xb3, 0x83, 0xdf, 0xe7, 0x8c, 0xc7, 0xbc, 0x4a, 0xfc, 0xea, 0x76, 0xc0,
+    0x53, 0x66, 0xbd, 0x2c, 0x1e, 0x10, 0x31, 0x90, 0x80, 0x11, 0x2d, 0x08,
+    0x4d, 0x7c, 0x39, 0x76, 0xdc, 0x73, 0xe7, 0x1c, 0x16, 0x62, 0xd5, 0x59,
+    0xd7, 0x49, 0x2b, 0x6a, 0xa2, 0x36, 0x67, 0x57, 0xd1, 0xf2, 0xf9, 0xaf,
+    0x13, 0xd7, 0xa3, 0xe4, 0xd3, 0x39, 0x5b, 0x02, 0x78, 0xb1, 0xe0, 0x09,
+    0x70, 0xa2, 0xa6, 0x03, 0x0a, 0x01, 0x00, 0x30, 0x0c, 0x06, 0x08, 0x2a,
+    0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x04, 0x05, 0x00, 0x03, 0x48, 0x00,
+    0x30, 0x45, 0x02, 0x21, 0x00, 0xb2, 0x0b, 0xbb, 0x86, 0x04, 0x4e, 0x38,
+    0x55, 0xdb, 0xf5, 0x58, 0x92, 0xe1, 0xc1, 0x31, 0x4e, 0x79, 0x57, 0xe4,
+    0x5f, 0xbc, 0x36, 0x68, 0x29, 0xb4, 0x89, 0xa5, 0x63, 0x4e, 0x67, 0x19,
+    0x8c, 0x02, 0x20, 0x1b, 0x5d, 0xe6, 0x35, 0xc0, 0xcd, 0x9f, 0x7b, 0xec,
+    0xc0, 0x01, 0x52, 0xb8, 0x2f, 0x81, 0xf0, 0x29, 0x01, 0xa0, 0x17, 0x19,
+    0x83, 0xca, 0x84, 0xe2, 0xec, 0x01, 0xa8, 0xb2, 0x13, 0x8a, 0xb9};
+
+constexpr uint8_t kExpectedCborEd25519Cert_DescriptorInput[667] = {
+    0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x02, 0x50, 0xab, 0x01, 0x78,
+    0x28, 0x34, 0x37, 0x35, 0x37, 0x30, 0x38, 0x65, 0x62, 0x33, 0x62, 0x34,
+    0x32, 0x36, 0x66, 0x33, 0x38, 0x36, 0x63, 0x66, 0x63, 0x65, 0x38, 0x66,
+    0x33, 0x62, 0x61, 0x66, 0x35, 0x34, 0x33, 0x39, 0x30, 0x34, 0x36, 0x32,
+    0x37, 0x38, 0x64, 0x66, 0x61, 0x02, 0x78, 0x28, 0x35, 0x32, 0x31, 0x66,
+    0x30, 0x33, 0x35, 0x63, 0x32, 0x31, 0x65, 0x33, 0x32, 0x66, 0x31, 0x36,
+    0x37, 0x34, 0x31, 0x63, 0x31, 0x65, 0x61, 0x65, 0x36, 0x62, 0x64, 0x65,
+    0x64, 0x39, 0x33, 0x63, 0x65, 0x33, 0x32, 0x31, 0x65, 0x30, 0x64, 0x66,
+    0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40, 0xb7, 0xd4, 0x0c, 0xcb, 0x22,
+    0x5b, 0xa5, 0x78, 0x8f, 0x98, 0xff, 0x9e, 0x86, 0x93, 0x75, 0xf6, 0x90,
+    0xac, 0x50, 0xcf, 0x9e, 0xbd, 0x0a, 0xfe, 0xb1, 0xd9, 0xc2, 0x4e, 0x52,
+    0x19, 0xe4, 0xde, 0x29, 0xe5, 0x61, 0xf3, 0xf9, 0x29, 0xe8, 0x40, 0x87,
+    0x7a, 0xdd, 0x17, 0x48, 0x05, 0x89, 0x7e, 0x2b, 0xcb, 0x54, 0x79, 0xcc,
+    0x66, 0xf1, 0xb3, 0x13, 0x29, 0x0c, 0x68, 0x96, 0xb2, 0xbb, 0x8f, 0x3a,
+    0x00, 0x47, 0x44, 0x51, 0x58, 0x64, 0x6c, 0x46, 0x01, 0x33, 0x26, 0x73,
+    0x4b, 0x22, 0x65, 0xfd, 0xfa, 0x58, 0xd7, 0x57, 0x3e, 0x95, 0x59, 0xe0,
+    0x3a, 0xc3, 0xb9, 0xf7, 0xc8, 0x0e, 0x98, 0x80, 0x8c, 0xf5, 0xc4, 0xb8,
+    0xaf, 0xe3, 0x16, 0x84, 0x25, 0xa5, 0x35, 0x5d, 0x17, 0x72, 0x56, 0x8f,
+    0x8e, 0xec, 0x2f, 0x5a, 0x74, 0x60, 0x77, 0x2a, 0x6e, 0x90, 0xc0, 0x4e,
+    0x9f, 0x87, 0x6b, 0xf4, 0x8d, 0x9c, 0x66, 0xe3, 0x0b, 0xd2, 0x10, 0x35,
+    0x21, 0xa8, 0x1d, 0xa2, 0x31, 0x17, 0xe7, 0x0c, 0xdf, 0x18, 0xf7, 0x94,
+    0xe4, 0xd1, 0xca, 0x32, 0x7d, 0xf2, 0x63, 0x23, 0x1d, 0xbc, 0x84, 0x74,
+    0x61, 0xdb, 0x87, 0xf2, 0xab, 0x72, 0xad, 0xaf, 0x08, 0xf8, 0x3a, 0x00,
+    0x47, 0x44, 0x53, 0x58, 0x28, 0x1b, 0x40, 0xc1, 0xa9, 0x77, 0x60, 0xeb,
+    0xc3, 0x67, 0xf0, 0x5f, 0x6a, 0xe1, 0x5e, 0x20, 0xc2, 0x51, 0x68, 0x4d,
+    0x82, 0x48, 0x8b, 0x03, 0x32, 0x16, 0x79, 0x88, 0x14, 0x37, 0x78, 0x7f,
+    0x16, 0x9a, 0x06, 0xfd, 0xc0, 0x8a, 0x15, 0x80, 0x62, 0x3a, 0x00, 0x47,
+    0x44, 0x52, 0x58, 0x40, 0x45, 0x00, 0xe9, 0x5c, 0xbd, 0x00, 0x57, 0x04,
+    0x55, 0x87, 0x6c, 0xbd, 0x2f, 0xea, 0x41, 0x9c, 0x66, 0x42, 0x51, 0x41,
+    0xbb, 0x44, 0xed, 0x0e, 0xe9, 0x66, 0xcf, 0xd5, 0x10, 0x73, 0x0d, 0x4b,
+    0x48, 0xe4, 0x7a, 0x53, 0x35, 0x01, 0x0e, 0x6d, 0x15, 0x55, 0xc5, 0xb7,
+    0xd2, 0xd5, 0x36, 0xb6, 0xbc, 0x7e, 0xb0, 0xf3, 0x3d, 0xe6, 0x19, 0x78,
+    0x62, 0xeb, 0x02, 0x57, 0x39, 0x56, 0x73, 0x4f, 0x3a, 0x00, 0x47, 0x44,
+    0x54, 0x58, 0x40, 0x22, 0x52, 0x60, 0x17, 0xef, 0x2c, 0xa1, 0xf6, 0xcb,
+    0xed, 0x39, 0xd5, 0xe2, 0xaa, 0x65, 0x20, 0xfb, 0xad, 0x82, 0x93, 0xe5,
+    0x78, 0x23, 0x22, 0x97, 0xc1, 0x6e, 0x6a, 0x4e, 0x36, 0xd7, 0x6a, 0x61,
+    0x39, 0x08, 0x21, 0xd4, 0xfe, 0x92, 0x5f, 0x36, 0x2d, 0xeb, 0x5d, 0xbb,
+    0x32, 0x8b, 0xe3, 0x94, 0x4f, 0xbe, 0x1b, 0x21, 0xf9, 0xcc, 0x23, 0x73,
+    0x41, 0xb6, 0xb9, 0xb6, 0x98, 0xd0, 0xbc, 0x3a, 0x00, 0x47, 0x44, 0x55,
+    0x58, 0x41, 0x92, 0xd6, 0x97, 0xb3, 0x83, 0xdf, 0xe7, 0x8c, 0xc7, 0xbc,
+    0x4a, 0xfc, 0xea, 0x76, 0xc0, 0x53, 0x66, 0xbd, 0x2c, 0x1e, 0x10, 0x31,
+    0x90, 0x80, 0x11, 0x2d, 0x08, 0x4d, 0x7c, 0x39, 0x76, 0xdc, 0x73, 0xe7,
+    0x1c, 0x16, 0x62, 0xd5, 0x59, 0xd7, 0x49, 0x2b, 0x6a, 0xa2, 0x36, 0x67,
+    0x57, 0xd1, 0xf2, 0xf9, 0xaf, 0x13, 0xd7, 0xa3, 0xe4, 0xd3, 0x39, 0x5b,
+    0x02, 0x78, 0xb1, 0xe0, 0x09, 0x70, 0xa2, 0x3a, 0x00, 0x47, 0x44, 0x56,
+    0x41, 0x00, 0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01,
+    0x03, 0x27, 0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20, 0x93, 0x7f,
+    0xd9, 0xc0, 0x4d, 0xc6, 0xbb, 0x2e, 0x1d, 0x11, 0x62, 0xcd, 0x5c, 0x76,
+    0x94, 0xc7, 0xdb, 0x02, 0x54, 0x0c, 0x85, 0x01, 0x3a, 0x01, 0xab, 0x37,
+    0xfa, 0xce, 0xf9, 0x6e, 0x62, 0x20, 0x3a, 0x00, 0x47, 0x44, 0x58, 0x41,
+    0x20, 0x58, 0x40, 0xf6, 0x01, 0x7a, 0xc0, 0xf7, 0x8b, 0xb4, 0xf9, 0xbf,
+    0x60, 0x98, 0x5b, 0xc5, 0x64, 0x15, 0x4f, 0xa3, 0x0d, 0x19, 0x3a, 0x5f,
+    0xb2, 0x46, 0x0c, 0xc8, 0xfc, 0x40, 0x47, 0xa5, 0x87, 0xef, 0xd2, 0xdb,
+    0xbd, 0x35, 0xb6, 0x87, 0x99, 0x22, 0xe1, 0x3f, 0x37, 0xe3, 0x71, 0x28,
+    0x5a, 0xfa, 0xca, 0xcd, 0x5d, 0x44, 0x58, 0x45, 0xdf, 0xbb, 0x3d, 0x08,
+    0x88, 0x9b, 0x0c, 0x3b, 0x06, 0x7c, 0x0e};
+
+constexpr uint8_t kExpectedCborP256Cert_DescriptorInput[0] = {};
+
+}  // namespace test
+}  // namespace dice
+
+#endif  // DICE_KNOWN_TEST_VALUES_H_
diff --git a/include/dice/mbedtls_ops.h b/include/dice/mbedtls_ops.h
new file mode 100644
index 0000000..783cd3e
--- /dev/null
+++ b/include/dice/mbedtls_ops.h
@@ -0,0 +1,48 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef DICE_MBEDTLS_OPS_H_
+#define DICE_MBEDTLS_OPS_H_
+
+#include "dice/dice.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// This is a DiceOps implementation which uses mbedtls for crypto and
+// certificate generation. These functions are documented as part of the DiceOps
+// struct in dice.h. The algorithms used are SHA512, HKDF-SHA512, and
+// deterministic ECDSA-P256-SHA512.
+DiceResult DiceMbedtlsHashOp(const DiceOps* ops, const uint8_t* input,
+                             size_t input_size, uint8_t output[DICE_HASH_SIZE]);
+
+DiceResult DiceMbedtlsKdfOp(const DiceOps* ops, size_t length,
+                            const uint8_t* ikm, size_t ikm_size,
+                            const uint8_t* salt, size_t salt_size,
+                            const uint8_t* info, size_t info_size,
+                            uint8_t* output);
+
+DiceResult DiceMbedtlsGenerateCertificateOp(
+    const DiceOps* ops,
+    const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+    const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+    const DiceInputValues* input_values, size_t certificate_buffer_size,
+    uint8_t* certificate, size_t* certificate_actual_size);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // DICE_MBEDTLS_OPS_H_
diff --git a/include/dice/template_cbor_cert_op.h b/include/dice/template_cbor_cert_op.h
new file mode 100644
index 0000000..0797024
--- /dev/null
+++ b/include/dice/template_cbor_cert_op.h
@@ -0,0 +1,52 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef DICE_TEMPLATE_CBOR_CERT_OP_H_
+#define DICE_TEMPLATE_CBOR_CERT_OP_H_
+
+#include "dice/dice.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// This function implements the 'DiceOps::generate_certificate' callback
+// documented in dice.h. It generates a CWT-style CBOR certificate based on a
+// template using the ED25519-SHA512 signature scheme.
+//
+// If no variable length descriptors are used in a DICE certificate, the
+// certificate can be constructed from a template instead of using a CBOR / COSE
+// library. This implementation includes only hashes and inline configuration in
+// the certificate fields. For convenience this uses the lower level curve25519
+// implementation in boringssl but does not use any CBOR or COSE library. This
+// approach may be especially useful in very low level components where
+// simplicity is paramount.
+//
+// This function will return kDiceResultInvalidInput if 'input_values' specifies
+// any variable length descriptors. In particular:
+//   * code_descriptor_size must be zero
+//   * authority_descriptor_size must be zero
+//   * config_type must be kDiceConfigTypeInline
+DiceResult DiceGenerateCborCertificateFromTemplateOp(
+    const DiceOps* ops,
+    const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+    const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+    const DiceInputValues* input_values, size_t certificate_buffer_size,
+    uint8_t* certificate, size_t* certificate_actual_size);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // DICE_TEMPLATE_CBOR_CERT_OP_H_
diff --git a/include/dice/template_cert_op.h b/include/dice/template_cert_op.h
new file mode 100644
index 0000000..53a2930
--- /dev/null
+++ b/include/dice/template_cert_op.h
@@ -0,0 +1,52 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef DICE_TEMPLATE_CERT_OP_H_
+#define DICE_TEMPLATE_CERT_OP_H_
+
+#include "dice/dice.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// This function implements the 'DiceOps::generate_certificate' callback
+// documented in dice.h. It generates an X.509 certificate based on a template
+// using the ED25519-SHA512 signature scheme.
+//
+// If no variable length descriptors are used in a DICE certificate, the
+// certificate can be constructed from a template instead of using an ASN.1
+// library. This implementation includes only hashes and inline configuration in
+// the DICE extension. For convenience this uses the lower level curve25519
+// implementation in boringssl but does not use anything else (no ASN.1, X.509,
+// etc). This approach may be especially useful in very low level components
+// where simplicity is paramount.
+//
+// This function will return kDiceResultInvalidInput if 'input_values' specifies
+// any variable length descriptors. In particular:
+//   * code_descriptor_size must be zero
+//   * authority_descriptor_size must be zero
+//   * config_type must be kDiceConfigTypeInline
+DiceResult DiceGenerateCertificateFromTemplateOp(
+    const DiceOps* ops,
+    const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+    const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+    const DiceInputValues* input_values, size_t certificate_buffer_size,
+    uint8_t* certificate, size_t* certificate_actual_size);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // DICE_TEMPLATE_CERT_OP_H_
diff --git a/include/dice/test_utils.h b/include/dice/test_utils.h
new file mode 100644
index 0000000..841f44f
--- /dev/null
+++ b/include/dice/test_utils.h
@@ -0,0 +1,67 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "dice/dice.h"
+
+namespace dice {
+namespace test {
+
+constexpr size_t kTestCertSize = 2048;
+
+enum CertificateType {
+  CertificateType_X509,
+  CertificateType_Cbor,
+};
+
+enum KeyType {
+  KeyType_Ed25519,
+  KeyType_P256,
+};
+
+struct DiceStateForTest {
+  uint8_t cdi_attest[DICE_CDI_SIZE];
+  uint8_t cdi_seal[DICE_CDI_SIZE];
+  uint8_t certificate[kTestCertSize];
+  size_t certificate_size;
+};
+
+// Dumps |state| to a set of files in the current directory with the given
+// |suffix|.
+void DumpState(CertificateType cert_type, KeyType key_type, const char* suffix,
+               const DiceStateForTest& state);
+
+// Deterministically derives |length| bytes from |seed|.
+void DeriveFakeInputValue(const char* seed, size_t length, uint8_t* output);
+
+// Generates a self-signed X.509 UDS certificate for the given |uds| value. The
+// signature scheme is ED25519-SHA512.
+void CreateFakeUdsCertificate(const DiceOps& ops, const uint8_t uds[32],
+                              CertificateType cert_type, KeyType key_type,
+                              uint8_t certificate[kTestCertSize],
+                              size_t* certificate_size);
+
+// Verifies a chain of CDI certificates given by |states| against
+// |root_certificate|. If |is_partial_chain| is set, then root_certificate does
+// not need to be self signed.
+bool VerifyCertificateChain(CertificateType cert_type,
+                            const uint8_t* root_certificate,
+                            size_t root_certificate_size,
+                            const DiceStateForTest states[],
+                            size_t num_dice_states, bool is_partial_chain);
+
+}  // namespace test
+}  // namespace dice
diff --git a/include/dice/utils.h b/include/dice/utils.h
new file mode 100644
index 0000000..5778259
--- /dev/null
+++ b/include/dice/utils.h
@@ -0,0 +1,41 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef DICE_UTILS_H_
+#define DICE_UTILS_H_
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include "dice/dice.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Converts arbitrary bytes to ascii hex, no NUL terminator is added. Up to
+// |num_bytes| from |in| will be converted, and up to |out_size| bytes will be
+// written to |out|. If |out_size| is less than |num_bytes| * 2, the output will
+// be truncated at |out_size|.
+void DiceHexEncode(const uint8_t* in, size_t num_bytes, void* out,
+                   size_t out_size);
+
+// A default implementation of DiceOps.clear_memory.
+void DiceClearMemory(const DiceOps* ops, size_t size, void* address);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif
+
+#endif  // DICE_UTILS_H_
diff --git a/navbar.md b/navbar.md
new file mode 100644
index 0000000..85ce696
--- /dev/null
+++ b/navbar.md
@@ -0,0 +1,3 @@
+*   [README](/README.md)
+*   [Specification](/docs/specification.md)
+*   [Contributing](/docs/contributing.md)
diff --git a/run_fuzzer.sh b/run_fuzzer.sh
new file mode 100755
index 0000000..3d47a43
--- /dev/null
+++ b/run_fuzzer.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+readonly FUZZER_DIR="./out/host_fuzz/obj/bin"
+
+if [[ $# -eq 0 ]]; then
+  echo "Usage: ${0} FUZZER"
+  echo "Available fuzzers:"
+  ls ${FUZZER_DIR} -1 | grep 'fuzzer$' | sed 's/^/  - /'
+  exit
+fi
+
+readonly ARTIFACTS_DIR="fuzzer_data/${1}_artifacts"
+readonly CORPUS_DIR="fuzzer_data/${1}_corpus"
+
+mkdir -p "${ARTIFACTS_DIR}"
+mkdir -p "${CORPUS_DIR}"
+
+"${FUZZER_DIR}/${1}" -artifact_prefix="${ARTIFACTS_DIR}/" -timeout=10 "${CORPUS_DIR}"
diff --git a/src/boringssl_cert_op.c b/src/boringssl_cert_op.c
new file mode 100644
index 0000000..4d75b22
--- /dev/null
+++ b/src/boringssl_cert_op.c
@@ -0,0 +1,585 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <stdint.h>
+
+#include "dice/boringssl_ops.h"
+#include "dice/dice.h"
+#include "dice/utils.h"
+#include "openssl/asn1.h"
+#include "openssl/asn1t.h"
+#include "openssl/bn.h"
+#include "openssl/curve25519.h"
+#include "openssl/evp.h"
+#include "openssl/is_boringssl.h"
+#include "openssl/objects.h"
+#include "openssl/x509.h"
+#include "openssl/x509v3.h"
+
+typedef struct DiceExtensionAsn1 {
+  ASN1_OCTET_STRING* code_hash;
+  ASN1_OCTET_STRING* code_descriptor;
+  ASN1_OCTET_STRING* config_hash;
+  ASN1_OCTET_STRING* config_descriptor;
+  ASN1_OCTET_STRING* authority_hash;
+  ASN1_OCTET_STRING* authority_descriptor;
+  ASN1_ENUMERATED* mode;
+} DiceExtensionAsn1;
+
+// clang-format off
+ASN1_SEQUENCE(DiceExtensionAsn1) = {
+    ASN1_EXP_OPT(DiceExtensionAsn1, code_hash, ASN1_OCTET_STRING, 0),
+    ASN1_EXP_OPT(DiceExtensionAsn1, code_descriptor, ASN1_OCTET_STRING, 1),
+    ASN1_EXP_OPT(DiceExtensionAsn1, config_hash, ASN1_OCTET_STRING, 2),
+    ASN1_EXP_OPT(DiceExtensionAsn1, config_descriptor, ASN1_OCTET_STRING, 3),
+    ASN1_EXP_OPT(DiceExtensionAsn1, authority_hash, ASN1_OCTET_STRING, 4),
+    ASN1_EXP_OPT(DiceExtensionAsn1, authority_descriptor, ASN1_OCTET_STRING, 5),
+    ASN1_EXP_OPT(DiceExtensionAsn1, mode, ASN1_ENUMERATED, 6),
+} ASN1_SEQUENCE_END(DiceExtensionAsn1)
+DECLARE_ASN1_FUNCTIONS(DiceExtensionAsn1)
+IMPLEMENT_ASN1_FUNCTIONS(DiceExtensionAsn1)
+
+static DiceResult AddStandardFields(X509* x509, const uint8_t subject_id[20],
+                                    const uint8_t authority_id[20]) {
+  // clang-format on
+  DiceResult result = kDiceResultOk;
+
+  // Initialize variables that are cleaned up on 'goto out'.
+  ASN1_INTEGER* serial = NULL;
+  BIGNUM* serial_bn = NULL;
+  X509_NAME* issuer_name = NULL;
+  X509_NAME* subject_name = NULL;
+  ASN1_TIME* not_before = NULL;
+  ASN1_TIME* not_after = NULL;
+
+  serial = ASN1_INTEGER_new();
+  if (!serial) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  issuer_name = X509_NAME_new();
+  if (!issuer_name) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  subject_name = X509_NAME_new();
+  if (!subject_name) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  not_before = ASN1_TIME_new();
+  if (!not_before) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  not_after = ASN1_TIME_new();
+  if (!not_after) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  if (!X509_set_version(x509, 2)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  serial_bn = BN_bin2bn(subject_id, 20, NULL);
+  if (!serial_bn) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  BN_to_ASN1_INTEGER(serial_bn, serial);
+  if (!X509_set_serialNumber(x509, serial)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  uint8_t id_hex[40];
+  DiceHexEncode(authority_id, 20, id_hex, sizeof(id_hex));
+  if (!X509_NAME_add_entry_by_NID(issuer_name, NID_serialNumber, MBSTRING_UTF8,
+                                  id_hex, sizeof(id_hex), 0, 0)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!X509_set_issuer_name(x509, issuer_name)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  DiceHexEncode(subject_id, 20, id_hex, sizeof(id_hex));
+  if (!X509_NAME_add_entry_by_NID(subject_name, NID_serialNumber, MBSTRING_UTF8,
+                                  id_hex, sizeof(id_hex), 0, 0)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!X509_set_subject_name(x509, subject_name)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  // '180322235959Z' is the date of publication of the DICE specification. Here
+  // it's used as a somewhat arbitrary backstop.
+  if (!ASN1_TIME_set_string(not_before, "180322235959Z")) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!X509_set_notBefore(x509, not_before)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  // '99991231235959Z' is suggested by RFC 5280 in cases where expiry is not
+  // meaningful. Basically, the certificate never expires.
+  if (!ASN1_TIME_set_string(not_after, "99991231235959Z")) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!X509_set_notAfter(x509, not_after)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+out:
+  if (serial) {
+    ASN1_INTEGER_free(serial);
+  }
+  if (serial_bn) {
+    BN_free(serial_bn);
+  }
+  if (issuer_name) {
+    X509_NAME_free(issuer_name);
+  }
+  if (subject_name) {
+    X509_NAME_free(subject_name);
+  }
+  if (not_before) {
+    ASN1_TIME_free(not_before);
+  }
+  if (not_after) {
+    ASN1_TIME_free(not_after);
+  }
+  return result;
+}
+
+static DiceResult AddStandardExtensions(X509* x509,
+                                        const uint8_t subject_id[20],
+                                        const uint8_t authority_id[20]) {
+  DiceResult result = kDiceResultOk;
+
+  // Initialize variables that are cleaned up on 'goto out'.
+  AUTHORITY_KEYID* authority_key_id = NULL;
+  ASN1_OCTET_STRING* subject_key_id = NULL;
+  ASN1_BIT_STRING* key_usage = NULL;
+  BASIC_CONSTRAINTS* basic_constraints = NULL;
+  X509_EXTENSION* authority_key_id_ext = NULL;
+  X509_EXTENSION* subject_key_id_ext = NULL;
+  X509_EXTENSION* key_usage_ext = NULL;
+  X509_EXTENSION* basic_constraints_ext = NULL;
+
+  // The authority key identifier extension contains the same raw authority id
+  // that appears in the issuer name.
+  authority_key_id = AUTHORITY_KEYID_new();
+  if (!authority_key_id) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  authority_key_id->keyid = ASN1_OCTET_STRING_new();
+  if (!authority_key_id->keyid) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!ASN1_OCTET_STRING_set(authority_key_id->keyid, authority_id, 20)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  // The subject key identifier extension contains the same raw subject id that
+  // appears in the serial number and the subject name.
+  subject_key_id = ASN1_OCTET_STRING_new();
+  if (!subject_key_id) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!ASN1_OCTET_STRING_set(subject_key_id, subject_id, 20)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  // The key usage extension contains only "keyCertSign".
+  key_usage = ASN1_BIT_STRING_new();
+  if (!key_usage) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  ASN1_BIT_STRING_set_bit(key_usage, 5 /*keyCertSign*/, 1);
+
+  // The basic constraints specify this is a CA with unspecified pathlen.
+  basic_constraints = BASIC_CONSTRAINTS_new();
+  if (!basic_constraints) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  basic_constraints->ca = 1;
+
+  // Encode all the extension objects.
+  authority_key_id_ext = X509V3_EXT_i2d(NID_authority_key_identifier,
+                                        /*crit=*/0, authority_key_id);
+  if (!authority_key_id_ext) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  subject_key_id_ext = X509V3_EXT_i2d(NID_subject_key_identifier,
+                                      /*crit=*/0, subject_key_id);
+  if (!subject_key_id_ext) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  key_usage_ext = X509V3_EXT_i2d(NID_key_usage, /*crit=*/1, key_usage);
+  if (!key_usage_ext) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  basic_constraints_ext = X509V3_EXT_i2d(NID_basic_constraints,
+                                         /*crit=*/1, basic_constraints);
+  if (!basic_constraints_ext) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  // Add all the extensions to the given X509 object.
+  if (!X509_add_ext(x509, authority_key_id_ext, -1)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!X509_add_ext(x509, subject_key_id_ext, -1)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!X509_add_ext(x509, key_usage_ext, -1)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!X509_add_ext(x509, basic_constraints_ext, -1)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+out:
+  if (authority_key_id) {
+    AUTHORITY_KEYID_free(authority_key_id);
+  }
+  if (subject_key_id) {
+    ASN1_OCTET_STRING_free(subject_key_id);
+  }
+  if (key_usage) {
+    ASN1_BIT_STRING_free(key_usage);
+  }
+  if (basic_constraints) {
+    BASIC_CONSTRAINTS_free(basic_constraints);
+  }
+  if (authority_key_id_ext) {
+    X509_EXTENSION_free(authority_key_id_ext);
+  }
+  if (subject_key_id_ext) {
+    X509_EXTENSION_free(subject_key_id_ext);
+  }
+  if (key_usage_ext) {
+    X509_EXTENSION_free(key_usage_ext);
+  }
+  if (basic_constraints_ext) {
+    X509_EXTENSION_free(basic_constraints_ext);
+  }
+  return result;
+}
+
+static DiceResult GetDiceExtensionData(const DiceInputValues* input_values,
+                                       size_t buffer_size, uint8_t* buffer,
+                                       size_t* actual_size) {
+  DiceResult result = kDiceResultOk;
+
+  DiceExtensionAsn1* asn1 = DiceExtensionAsn1_new();
+  if (!asn1) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  // Allocate required fields. Optional fields will be allocated as needed.
+  asn1->code_hash = ASN1_OCTET_STRING_new();
+  if (!asn1->code_hash) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  asn1->config_descriptor = ASN1_OCTET_STRING_new();
+  if (!asn1->config_descriptor) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  asn1->authority_hash = ASN1_OCTET_STRING_new();
+  if (!asn1->authority_hash) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  asn1->mode = ASN1_ENUMERATED_new();
+  if (!asn1->mode) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  // Encode code input.
+  if (!ASN1_OCTET_STRING_set(asn1->code_hash, input_values->code_hash,
+                             DICE_HASH_SIZE)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (input_values->code_descriptor_size > 0) {
+    asn1->code_descriptor = ASN1_OCTET_STRING_new();
+    if (!asn1->code_descriptor) {
+      result = kDiceResultPlatformError;
+      goto out;
+    }
+    if (!ASN1_OCTET_STRING_set(asn1->code_descriptor,
+                               input_values->code_descriptor,
+                               input_values->code_descriptor_size)) {
+      result = kDiceResultPlatformError;
+      goto out;
+    }
+  }
+
+  // Encode configuration inputs.
+  if (input_values->config_type == kDiceConfigTypeDescriptor) {
+    // The 'descriptor' type means the configuration input is in the descriptor
+    // field and the hash of this was used as the DICE input. In the extension
+    // both are stored.
+    uint8_t hash_buffer[DICE_HASH_SIZE];
+    asn1->config_hash = ASN1_OCTET_STRING_new();
+    if (!asn1->config_hash) {
+      result = kDiceResultPlatformError;
+      goto out;
+    }
+    if (!ASN1_OCTET_STRING_set(asn1->config_descriptor,
+                               input_values->config_descriptor,
+                               input_values->config_descriptor_size)) {
+      result = kDiceResultPlatformError;
+      goto out;
+    }
+    if (!ASN1_OCTET_STRING_set(
+            asn1->config_hash,
+            SHA512(input_values->config_descriptor,
+                   input_values->config_descriptor_size, hash_buffer),
+            DICE_HASH_SIZE)) {
+      result = kDiceResultPlatformError;
+      goto out;
+    }
+  } else if (input_values->config_type == kDiceConfigTypeInline) {
+    // The 'inline' type means the configuration value is 64 bytes and was used
+    // directly as the DICE input. In the extension this value is stored in the
+    // descriptor and the hash is omitted.
+    if (!ASN1_OCTET_STRING_set(asn1->config_descriptor,
+                               input_values->config_value,
+                               DICE_INLINE_CONFIG_SIZE)) {
+      result = kDiceResultPlatformError;
+      goto out;
+    }
+  } else {
+    result = kDiceResultInvalidInput;
+    goto out;
+  }
+
+  // Encode authority input.
+  if (!ASN1_OCTET_STRING_set(asn1->authority_hash, input_values->authority_hash,
+                             DICE_HASH_SIZE)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (input_values->authority_descriptor_size > 0) {
+    asn1->authority_descriptor = ASN1_OCTET_STRING_new();
+    if (!asn1->authority_descriptor) {
+      result = kDiceResultPlatformError;
+      goto out;
+    }
+    if (!ASN1_OCTET_STRING_set(asn1->authority_descriptor,
+                               input_values->authority_descriptor,
+                               input_values->authority_descriptor_size)) {
+      result = kDiceResultPlatformError;
+      goto out;
+    }
+  }
+
+  // Encode mode input.
+  if (!ASN1_ENUMERATED_set(asn1->mode, input_values->mode)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  *actual_size = i2d_DiceExtensionAsn1(asn1, NULL);
+  if (buffer_size < *actual_size) {
+    result = kDiceResultBufferTooSmall;
+    goto out;
+  }
+  i2d_DiceExtensionAsn1(asn1, &buffer);
+
+out:
+  if (asn1) {
+    DiceExtensionAsn1_free(asn1);
+  }
+  return result;
+}
+
+static DiceResult AddDiceExtension(const DiceInputValues* input_values,
+                                   X509* x509) {
+  const size_t kMaxExtensionSize = 2048;
+  const char* kDiceExtensionOid = "1.3.6.1.4.1.11129.2.1.24";
+
+  // Initialize variables that are cleaned up on 'goto out'.
+  ASN1_OBJECT* oid = NULL;
+  ASN1_OCTET_STRING* octets = NULL;
+  X509_EXTENSION* extension = NULL;
+
+  uint8_t extension_buffer[kMaxExtensionSize];
+  size_t extension_size = 0;
+  DiceResult result =
+      GetDiceExtensionData(input_values, sizeof(extension_buffer),
+                           extension_buffer, &extension_size);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  oid = OBJ_txt2obj(kDiceExtensionOid, 1);
+  if (!oid) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  octets = ASN1_OCTET_STRING_new();
+  if (!octets) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!ASN1_OCTET_STRING_set(octets, extension_buffer, extension_size)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  extension =
+      X509_EXTENSION_create_by_OBJ(/*ex=*/NULL, oid, /*crit=*/0, octets);
+  if (!extension) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  if (!X509_add_ext(x509, extension, -1)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+out:
+  if (oid) {
+    ASN1_OBJECT_free(oid);
+  }
+  if (octets) {
+    ASN1_OCTET_STRING_free(octets);
+  }
+  if (extension) {
+    X509_EXTENSION_free(extension);
+  }
+  return result;
+}
+
+static DiceResult GetIdFromKey(const DiceOps* ops, const EVP_PKEY* key,
+                               uint8_t id[20]) {
+  uint8_t raw_public_key[32];
+  size_t raw_public_key_size = sizeof(raw_public_key);
+  if (!EVP_PKEY_get_raw_public_key(key, raw_public_key, &raw_public_key_size)) {
+    return kDiceResultPlatformError;
+  }
+  return DiceDeriveCdiCertificateId(ops, raw_public_key, raw_public_key_size,
+                                    id);
+}
+
+DiceResult DiceBsslGenerateCertificateOp(
+    const DiceOps* ops,
+    const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+    const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+    const DiceInputValues* input_values, size_t certificate_buffer_size,
+    uint8_t* certificate, size_t* certificate_actual_size) {
+  DiceResult result = kDiceResultOk;
+
+  // Initialize variables that are cleaned up on 'goto out'.
+  X509* x509 = NULL;
+  EVP_PKEY* authority_key = NULL;
+  EVP_PKEY* subject_key = NULL;
+
+  x509 = X509_new();
+  if (!x509) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  authority_key = EVP_PKEY_new_raw_private_key(
+      EVP_PKEY_ED25519, NULL, authority_private_key, DICE_PRIVATE_KEY_SIZE);
+  if (!authority_key) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  subject_key = EVP_PKEY_new_raw_private_key(
+      EVP_PKEY_ED25519, NULL, subject_private_key, DICE_PRIVATE_KEY_SIZE);
+  if (!subject_key) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!X509_set_pubkey(x509, subject_key)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  uint8_t authority_id[20];
+  result = GetIdFromKey(ops, authority_key, authority_id);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+  uint8_t subject_id[20];
+  result = GetIdFromKey(ops, subject_key, subject_id);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  result = AddStandardFields(x509, subject_id, authority_id);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+  result = AddStandardExtensions(x509, subject_id, authority_id);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+  result = AddDiceExtension(input_values, x509);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+  if (!X509_sign(x509, authority_key, NULL /*ED25519 always uses SHA-512*/)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  *certificate_actual_size = i2d_X509(x509, NULL);
+  if (*certificate_actual_size > certificate_buffer_size) {
+    result = kDiceResultBufferTooSmall;
+    goto out;
+  }
+  *certificate_actual_size = i2d_X509(x509, &certificate);
+out:
+  if (x509) {
+    X509_free(x509);
+  }
+  if (authority_key) {
+    EVP_PKEY_free(authority_key);
+  }
+  if (subject_key) {
+    EVP_PKEY_free(subject_key);
+  }
+  return result;
+}
diff --git a/src/boringssl_hash_kdf_ops.c b/src/boringssl_hash_kdf_ops.c
new file mode 100644
index 0000000..fb4692b
--- /dev/null
+++ b/src/boringssl_hash_kdf_ops.c
@@ -0,0 +1,42 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <stdint.h>
+
+#include "dice/boringssl_ops.h"
+#include "dice/dice.h"
+#include "openssl/evp.h"
+#include "openssl/hkdf.h"
+#include "openssl/is_boringssl.h"
+#include "openssl/sha.h"
+
+DiceResult DiceBsslHashOp(const DiceOps* ops_not_used, const uint8_t* input,
+                          size_t input_size, uint8_t output[DICE_HASH_SIZE]) {
+  (void)ops_not_used;
+  SHA512(input, input_size, output);
+  return kDiceResultOk;
+}
+
+DiceResult DiceBsslKdfOp(const DiceOps* ops_not_used, size_t length,
+                         const uint8_t* ikm, size_t ikm_size,
+                         const uint8_t* salt, size_t salt_size,
+                         const uint8_t* info, size_t info_size,
+                         uint8_t* output) {
+  (void)ops_not_used;
+  if (!HKDF(output, length, EVP_sha512(), ikm, ikm_size, salt, salt_size, info,
+            info_size)) {
+    return kDiceResultPlatformError;
+  }
+  return kDiceResultOk;
+}
diff --git a/src/boringssl_ops_fuzzer.cc b/src/boringssl_ops_fuzzer.cc
new file mode 100644
index 0000000..643a834
--- /dev/null
+++ b/src/boringssl_ops_fuzzer.cc
@@ -0,0 +1,33 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/boringssl_ops.h"
+#include "dice/dice.h"
+#include "dice/fuzz_utils.h"
+#include "dice/utils.h"
+
+namespace {
+
+constexpr DiceOps kOps = {.context = NULL,
+                          .hash = DiceBsslHashOp,
+                          .kdf = DiceBsslKdfOp,
+                          .generate_certificate = DiceBsslGenerateCertificateOp,
+                          .clear_memory = DiceClearMemory};
+
+}  // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  dice::fuzz::FuzzDiceMainFlow(&kOps, data, size);
+  return 0;
+}
diff --git a/src/boringssl_ops_test.cc b/src/boringssl_ops_test.cc
new file mode 100644
index 0000000..9f3fe95
--- /dev/null
+++ b/src/boringssl_ops_test.cc
@@ -0,0 +1,253 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/boringssl_ops.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <memory>
+
+#include "dice/dice.h"
+#include "dice/known_test_values.h"
+#include "dice/test_utils.h"
+#include "dice/utils.h"
+#include "pw_string/format.h"
+#include "pw_unit_test/framework.h"
+
+namespace {
+
+using dice::test::CertificateType_X509;
+using dice::test::DeriveFakeInputValue;
+using dice::test::DiceStateForTest;
+using dice::test::DumpState;
+using dice::test::KeyType_Ed25519;
+
+constexpr DiceOps kOps = {.context = NULL,
+                          .hash = DiceBsslHashOp,
+                          .kdf = DiceBsslKdfOp,
+                          .generate_certificate = DiceBsslGenerateCertificateOp,
+                          .clear_memory = DiceClearMemory};
+
+TEST(DiceOpsTest, KnownAnswerZeroInput) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  DumpState(CertificateType_X509, KeyType_Ed25519, "zero_input", next_state);
+  // Both CDI values and the certificate should be deterministic.
+  EXPECT_EQ(0, memcmp(next_state.cdi_attest,
+                      dice::test::kExpectedCdiAttest_ZeroInput, DICE_CDI_SIZE));
+  EXPECT_EQ(0, memcmp(next_state.cdi_seal,
+                      dice::test::kExpectedCdiSeal_ZeroInput, DICE_CDI_SIZE));
+  ASSERT_EQ(sizeof(dice::test::kExpectedX509Ed25519Cert_ZeroInput),
+            next_state.certificate_size);
+  EXPECT_EQ(0, memcmp(dice::test::kExpectedX509Ed25519Cert_ZeroInput,
+                      next_state.certificate, next_state.certificate_size));
+}
+
+TEST(DiceOpsTest, KnownAnswerHashOnlyInput) {
+  DiceStateForTest current_state = {};
+  DeriveFakeInputValue("cdi_attest", DICE_CDI_SIZE, current_state.cdi_attest);
+  DeriveFakeInputValue("cdi_seal", DICE_CDI_SIZE, current_state.cdi_seal);
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DeriveFakeInputValue("code_hash", DICE_HASH_SIZE, input_values.code_hash);
+  DeriveFakeInputValue("authority_hash", DICE_HASH_SIZE,
+                       input_values.authority_hash);
+  input_values.config_type = kDiceConfigTypeInline;
+  DeriveFakeInputValue("inline_config", DICE_INLINE_CONFIG_SIZE,
+                       input_values.config_value);
+
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  DumpState(CertificateType_X509, KeyType_Ed25519, "hash_only_input",
+            next_state);
+  // Both CDI values and the certificate should be deterministic.
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_attest,
+                dice::test::kExpectedCdiAttest_HashOnlyInput, DICE_CDI_SIZE));
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_seal, dice::test::kExpectedCdiSeal_HashOnlyInput,
+                DICE_CDI_SIZE));
+  ASSERT_EQ(sizeof(dice::test::kExpectedX509Ed25519Cert_HashOnlyInput),
+            next_state.certificate_size);
+  EXPECT_EQ(0, memcmp(dice::test::kExpectedX509Ed25519Cert_HashOnlyInput,
+                      next_state.certificate, next_state.certificate_size));
+}
+
+TEST(DiceOpsTest, KnownAnswerDescriptorInput) {
+  DiceStateForTest current_state = {};
+  DeriveFakeInputValue("cdi_attest", DICE_CDI_SIZE, current_state.cdi_attest);
+  DeriveFakeInputValue("cdi_seal", DICE_CDI_SIZE, current_state.cdi_seal);
+
+  DiceStateForTest next_state = {};
+
+  DiceInputValues input_values = {};
+  DeriveFakeInputValue("code_hash", DICE_HASH_SIZE, input_values.code_hash);
+  uint8_t code_descriptor[100];
+  DeriveFakeInputValue("code_desc", sizeof(code_descriptor), code_descriptor);
+  input_values.code_descriptor = code_descriptor;
+  input_values.code_descriptor_size = sizeof(code_descriptor);
+
+  uint8_t config_descriptor[40];
+  DeriveFakeInputValue("config_desc", sizeof(config_descriptor),
+                       config_descriptor);
+  input_values.config_descriptor = config_descriptor;
+  input_values.config_descriptor_size = sizeof(config_descriptor);
+  input_values.config_type = kDiceConfigTypeDescriptor;
+
+  DeriveFakeInputValue("authority_hash", DICE_HASH_SIZE,
+                       input_values.authority_hash);
+  uint8_t authority_descriptor[65];
+  DeriveFakeInputValue("authority_desc", sizeof(authority_descriptor),
+                       authority_descriptor);
+  input_values.authority_descriptor = authority_descriptor;
+  input_values.authority_descriptor_size = sizeof(authority_descriptor);
+
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  DumpState(CertificateType_X509, KeyType_Ed25519, "descriptor_input",
+            next_state);
+  // Both CDI values and the certificate should be deterministic.
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_attest,
+                dice::test::kExpectedCdiAttest_DescriptorInput, DICE_CDI_SIZE));
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_seal,
+                dice::test::kExpectedCdiSeal_DescriptorInput, DICE_CDI_SIZE));
+  ASSERT_EQ(sizeof(dice::test::kExpectedX509Ed25519Cert_DescriptorInput),
+            next_state.certificate_size);
+  EXPECT_EQ(0, memcmp(dice::test::kExpectedX509Ed25519Cert_DescriptorInput,
+                      next_state.certificate, next_state.certificate_size));
+}
+
+TEST(DiceOpsTest, NonZeroMode) {
+  constexpr size_t kModeOffsetInCert = 0x230;
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  input_values.mode = kDiceModeDebug;
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  EXPECT_EQ(kDiceModeDebug, next_state.certificate[kModeOffsetInCert]);
+}
+
+TEST(DiceOpsTest, LargeInputs) {
+  constexpr uint8_t kBigBuffer[1024 * 1024] = {};
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  input_values.code_descriptor = kBigBuffer;
+  input_values.code_descriptor_size = sizeof(kBigBuffer);
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultBufferTooSmall, result);
+}
+
+TEST(DiceOpsTest, InvalidConfigType) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  input_values.config_type = (DiceConfigType)55;
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultInvalidInput, result);
+}
+
+TEST(DiceOpsTest, PartialCertChain) {
+  constexpr size_t kNumLayers = 7;
+  DiceStateForTest states[kNumLayers + 1] = {};
+  DiceInputValues inputs[kNumLayers] = {};
+  for (size_t i = 0; i < kNumLayers; ++i) {
+    char seed[40];
+    pw::string::Format(seed, "code_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].code_hash);
+    pw::string::Format(seed, "authority_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].authority_hash);
+    inputs[i].config_type = kDiceConfigTypeInline;
+    pw::string::Format(seed, "inline_config_%zu", i);
+    DeriveFakeInputValue(seed, DICE_INLINE_CONFIG_SIZE, inputs[i].config_value);
+    inputs[i].mode = kDiceModeNormal;
+    EXPECT_EQ(
+        kDiceResultOk,
+        DiceMainFlow(&kOps, states[i].cdi_attest, states[i].cdi_seal,
+                     &inputs[i], sizeof(states[i + 1].certificate),
+                     states[i + 1].certificate, &states[i + 1].certificate_size,
+                     states[i + 1].cdi_attest, states[i + 1].cdi_seal));
+    char suffix[40];
+    pw::string::Format(suffix, "part_cert_chain_%zu", i);
+    DumpState(CertificateType_X509, KeyType_Ed25519, suffix, states[i + 1]);
+  }
+  // Use the first derived CDI cert as the 'root' of partial chain.
+  EXPECT_TRUE(dice::test::VerifyCertificateChain(
+      CertificateType_X509, states[1].certificate, states[1].certificate_size,
+      &states[2], kNumLayers - 1, /*is_partial_chain=*/true));
+}
+
+TEST(DiceOpsTest, FullCertChain) {
+  constexpr size_t kNumLayers = 7;
+  DiceStateForTest states[kNumLayers + 1] = {};
+  DiceInputValues inputs[kNumLayers] = {};
+  for (size_t i = 0; i < kNumLayers; ++i) {
+    char seed[40];
+    pw::string::Format(seed, "code_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].code_hash);
+    pw::string::Format(seed, "authority_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].authority_hash);
+    inputs[i].config_type = kDiceConfigTypeInline;
+    pw::string::Format(seed, "inline_config_%zu", i);
+    DeriveFakeInputValue(seed, DICE_INLINE_CONFIG_SIZE, inputs[i].config_value);
+    inputs[i].mode = kDiceModeNormal;
+    EXPECT_EQ(
+        kDiceResultOk,
+        DiceMainFlow(&kOps, states[i].cdi_attest, states[i].cdi_seal,
+                     &inputs[i], sizeof(states[i + 1].certificate),
+                     states[i + 1].certificate, &states[i + 1].certificate_size,
+                     states[i + 1].cdi_attest, states[i + 1].cdi_seal));
+    char suffix[40];
+    pw::string::Format(suffix, "full_cert_chain_%zu", i);
+    DumpState(CertificateType_X509, KeyType_Ed25519, suffix, states[i + 1]);
+  }
+  // Use a fake self-signed UDS cert as the 'root'.
+  uint8_t root_certificate[dice::test::kTestCertSize];
+  size_t root_certificate_size = 0;
+  dice::test::CreateFakeUdsCertificate(
+      kOps, states[0].cdi_attest, CertificateType_X509, KeyType_Ed25519,
+      root_certificate, &root_certificate_size);
+  EXPECT_TRUE(dice::test::VerifyCertificateChain(
+      CertificateType_X509, root_certificate, root_certificate_size, &states[1],
+      kNumLayers,
+      /*is_partial_chain=*/false));
+}
+
+}  // namespace
diff --git a/src/cbor_cert_op.c b/src/cbor_cert_op.c
new file mode 100644
index 0000000..23b6f7f
--- /dev/null
+++ b/src/cbor_cert_op.c
@@ -0,0 +1,522 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/cbor_cert_op.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "cn-cbor/cn-cbor.h"
+#include "dice/dice.h"
+#include "dice/utils.h"
+#include "openssl/curve25519.h"
+#include "openssl/is_boringssl.h"
+#include "openssl/sha.h"
+
+// Max size of COSE_Sign1 including payload.
+static const size_t kMaxCertificateSize = 2048;
+// Max size of COSE_Key encoding.
+static const size_t kMaxPublicKeySize = 64;
+// Max size of the COSE_Sign1 protected attributes.
+static const size_t kMaxProtectedAttributesSize = 16;
+
+// Returns true on success.
+static bool AddToCborMap(int64_t label, cn_cbor* value, cn_cbor* map) {
+  cn_cbor_errback error_not_used;
+  if (!value) {
+    return false;
+  }
+  if (!cn_cbor_mapput_int(map, label, value, &error_not_used)) {
+    cn_cbor_free(value);
+    return false;
+  }
+  return true;
+}
+
+// Returns true on success.
+static bool AddToCborArray(cn_cbor* value, cn_cbor* array) {
+  cn_cbor_errback error_not_used;
+  if (!value) {
+    return false;
+  }
+  if (!cn_cbor_array_append(array, value, &error_not_used)) {
+    cn_cbor_free(value);
+    return false;
+  }
+  return true;
+}
+
+static DiceResult EncodeCbor(cn_cbor* cbor, size_t buffer_size, uint8_t* buffer,
+                             size_t* encoded_size) {
+  // Calculate the encoded size.
+  ssize_t result = cn_cbor_encoder_write(/*buf=*/NULL, /*buf_offset=*/0,
+                                         /*buf_size=*/0, cbor);
+  if (result < 0) {
+    return kDiceResultPlatformError;
+  }
+  *encoded_size = result;
+  if (*encoded_size > buffer_size) {
+    return kDiceResultBufferTooSmall;
+  }
+  result = cn_cbor_encoder_write(buffer, /*buf_offset=*/0, buffer_size, cbor);
+  if ((size_t)result != *encoded_size) {
+    return kDiceResultPlatformError;
+  }
+  return kDiceResultOk;
+}
+
+static DiceResult EncodeProtectedAttributes(size_t buffer_size, uint8_t* buffer,
+                                            size_t* encoded_size) {
+  // Constants per RFC 8152.
+  const int64_t kCoseHeaderAlgLabel = 1;
+  const int64_t kCoseAlgEdDSA = -8;
+
+  DiceResult result = kDiceResultOk;
+  cn_cbor_errback error_not_used;
+  cn_cbor* map = cn_cbor_map_create(&error_not_used);
+  if (!map) {
+    return kDiceResultPlatformError;
+  }
+  if (!AddToCborMap(kCoseHeaderAlgLabel,
+                    cn_cbor_int_create(kCoseAlgEdDSA, &error_not_used), map)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  result = EncodeCbor(map, buffer_size, buffer, encoded_size);
+
+out:
+  cn_cbor_free(map);
+  return result;
+}
+
+static DiceResult EncodePublicKey(uint8_t subject_public_key[32],
+                                  size_t buffer_size, uint8_t* buffer,
+                                  size_t* encoded_size) {
+  // Constants per RFC 8152.
+  const int64_t kCoseKeyKtyLabel = 1;
+  const int64_t kCoseKeyAlgLabel = 3;
+  const int64_t kCoseKeyOpsLabel = 4;
+  const int64_t kCoseOkpCrvLabel = -1;
+  const int64_t kCoseOkpXLabel = -2;
+  const int64_t kCoseKeyTypeOkp = 1;
+  const int64_t kCoseAlgEdDSA = -8;
+  const int64_t kCoseKeyOpsVerify = 2;
+  const int64_t kCoseCrvEd25519 = 6;
+
+  DiceResult result = kDiceResultOk;
+
+  cn_cbor_errback error_not_used;
+  cn_cbor* map = cn_cbor_map_create(&error_not_used);
+  cn_cbor* ops = cn_cbor_array_create(&error_not_used);
+  if (!map || !ops) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!AddToCborMap(kCoseKeyKtyLabel,
+                    cn_cbor_int_create(kCoseKeyTypeOkp, &error_not_used),
+                    map)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!AddToCborMap(kCoseKeyAlgLabel,
+                    cn_cbor_int_create(kCoseAlgEdDSA, &error_not_used), map)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!AddToCborArray(cn_cbor_int_create(kCoseKeyOpsVerify, &error_not_used),
+                      ops)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (AddToCborMap(kCoseKeyOpsLabel, ops, map)) {
+    // This is now owned by the map.
+    ops = NULL;
+  } else {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!AddToCborMap(kCoseOkpCrvLabel,
+                    cn_cbor_int_create(kCoseCrvEd25519, &error_not_used),
+                    map)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (!AddToCborMap(
+          kCoseOkpXLabel,
+          cn_cbor_data_create(subject_public_key, 32, &error_not_used), map)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  result = EncodeCbor(map, buffer_size, buffer, encoded_size);
+
+out:
+  if (map) {
+    cn_cbor_free(map);
+  }
+  if (ops) {
+    cn_cbor_free(ops);
+  }
+  return result;
+}
+
+// Encodes a CBOR Web Token (CWT) with an issuer, subject, and additional
+// fields.
+static DiceResult EncodeCwt(const DiceInputValues* input_values,
+                            const char* authority_id_hex,
+                            const char* subject_id_hex,
+                            const uint8_t* encoded_public_key,
+                            size_t encoded_public_key_size, size_t buffer_size,
+                            uint8_t* buffer, size_t* encoded_size) {
+  // Constants per RFC 8392.
+  const int64_t kCwtIssuerLabel = 1;
+  const int64_t kCwtSubjectLabel = 2;
+  // Constants per the Open Profile for DICE specification.
+  const int64_t kCodeHashLabel = -4670545;
+  const int64_t kCodeDescriptorLabel = -4670546;
+  const int64_t kConfigHashLabel = -4670547;
+  const int64_t kConfigDescriptorLabel = -4670548;
+  const int64_t kAuthorityHashLabel = -4670549;
+  const int64_t kAuthorityDescriptorLabel = -4670550;
+  const int64_t kModeLabel = -4670551;
+  const int64_t kSubjectPublicKeyLabel = -4670552;
+  const int64_t kKeyUsageLabel = -4670553;
+  // Key usage constant per RFC 5280.
+  const uint8_t kKeyUsageCertSign = 32;
+
+  DiceResult result = kDiceResultOk;
+
+  cn_cbor_errback error_not_used;
+  cn_cbor* cwt = cn_cbor_map_create(&error_not_used);
+  if (!cwt) {
+    return kDiceResultPlatformError;
+  }
+  // Add the issuer.
+  if (!AddToCborMap(kCwtIssuerLabel,
+                    cn_cbor_string_create(authority_id_hex, &error_not_used),
+                    cwt)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  // Add the subject.
+  if (!AddToCborMap(kCwtSubjectLabel,
+                    cn_cbor_string_create(subject_id_hex, &error_not_used),
+                    cwt)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  // Add the code inputs.
+  if (!AddToCborMap(kCodeHashLabel,
+                    cn_cbor_data_create(input_values->code_hash, DICE_HASH_SIZE,
+                                        &error_not_used),
+                    cwt)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (input_values->code_descriptor_size > 0) {
+    if (!AddToCborMap(kCodeDescriptorLabel,
+                      cn_cbor_data_create(input_values->code_descriptor,
+                                          input_values->code_descriptor_size,
+                                          &error_not_used),
+                      cwt)) {
+      result = kDiceResultPlatformError;
+      goto out;
+    }
+  }
+  // Add the config inputs.
+  uint8_t config_descriptor_hash[DICE_HASH_SIZE];
+  if (input_values->config_type == kDiceConfigTypeDescriptor) {
+    SHA512(input_values->config_descriptor,
+           input_values->config_descriptor_size, config_descriptor_hash);
+    if (!AddToCborMap(kConfigDescriptorLabel,
+                      cn_cbor_data_create(input_values->config_descriptor,
+                                          input_values->config_descriptor_size,
+                                          &error_not_used),
+                      cwt)) {
+      result = kDiceResultPlatformError;
+      goto out;
+    }
+    if (!AddToCborMap(kConfigHashLabel,
+                      cn_cbor_data_create(config_descriptor_hash,
+                                          DICE_HASH_SIZE, &error_not_used),
+                      cwt)) {
+      result = kDiceResultPlatformError;
+      goto out;
+    }
+  } else if (input_values->config_type == kDiceConfigTypeInline) {
+    if (!AddToCborMap(
+            kConfigDescriptorLabel,
+            cn_cbor_data_create(input_values->config_value,
+                                DICE_INLINE_CONFIG_SIZE, &error_not_used),
+            cwt)) {
+      result = kDiceResultPlatformError;
+      goto out;
+    }
+  }
+  // Add the authority inputs.
+  if (!AddToCborMap(kAuthorityHashLabel,
+                    cn_cbor_data_create(input_values->authority_hash,
+                                        DICE_HASH_SIZE, &error_not_used),
+                    cwt)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (input_values->authority_descriptor_size > 0) {
+    if (!AddToCborMap(
+            kAuthorityDescriptorLabel,
+            cn_cbor_data_create(input_values->authority_descriptor,
+                                input_values->authority_descriptor_size,
+                                &error_not_used),
+            cwt)) {
+      result = kDiceResultPlatformError;
+      goto out;
+    }
+  }
+  // Add the mode input.
+  uint8_t mode_byte = input_values->mode;
+  if (!AddToCborMap(kModeLabel,
+                    cn_cbor_data_create(&mode_byte, 1, &error_not_used), cwt)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  // Add the subject public key.
+  if (!AddToCborMap(
+          kSubjectPublicKeyLabel,
+          cn_cbor_data_create(encoded_public_key, encoded_public_key_size,
+                              &error_not_used),
+          cwt)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  // Add the key usage.
+  uint8_t key_usage = kKeyUsageCertSign;
+  if (!AddToCborMap(kKeyUsageLabel,
+                    cn_cbor_data_create(&key_usage, 1, &error_not_used), cwt)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  result = EncodeCbor(cwt, buffer_size, buffer, encoded_size);
+
+out:
+  cn_cbor_free(cwt);
+  return result;
+}
+
+static DiceResult EncodeCoseTbs(const uint8_t* protected_attributes,
+                                size_t protected_attributes_size,
+                                const uint8_t* payload, size_t payload_size,
+                                size_t buffer_size, uint8_t* buffer,
+                                size_t* encoded_size) {
+  DiceResult result = kDiceResultOk;
+
+  cn_cbor_errback error_not_used;
+  cn_cbor* array = cn_cbor_array_create(&error_not_used);
+  if (!array) {
+    return kDiceResultPlatformError;
+  }
+  // Context string field.
+  if (!AddToCborArray(cn_cbor_string_create("Signature1", &error_not_used),
+                      array)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  // Protected attributes from COSE_Sign1.
+  if (!AddToCborArray(
+          cn_cbor_data_create(protected_attributes, protected_attributes_size,
+                              &error_not_used),
+          array)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  // Empty application data.
+  if (!AddToCborArray(
+          cn_cbor_data_create(/*data=*/NULL, /*len=*/0, &error_not_used),
+          array)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  // Payload from COSE_Sign1.
+  if (!AddToCborArray(
+          cn_cbor_data_create(payload, payload_size, &error_not_used), array)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  result = EncodeCbor(array, buffer_size, buffer, encoded_size);
+
+out:
+  cn_cbor_free(array);
+  return result;
+}
+
+static DiceResult EncodeCoseSign1(const uint8_t* protected_attributes,
+                                  size_t protected_attributes_size,
+                                  const uint8_t* payload, size_t payload_size,
+                                  const uint8_t signature[64],
+                                  size_t buffer_size, uint8_t* buffer,
+                                  size_t* encoded_size) {
+  DiceResult result = kDiceResultOk;
+
+  cn_cbor_errback error_not_used;
+  cn_cbor* array = cn_cbor_array_create(&error_not_used);
+  if (!array) {
+    return kDiceResultPlatformError;
+  }
+  // Protected attributes.
+  if (!AddToCborArray(
+          cn_cbor_data_create(protected_attributes, protected_attributes_size,
+                              &error_not_used),
+          array)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  // Empty map for unprotected attributes.
+  if (!AddToCborArray(cn_cbor_map_create(&error_not_used), array)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  // Payload.
+  if (!AddToCborArray(
+          cn_cbor_data_create(payload, payload_size, &error_not_used), array)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  // Signature.
+  if (!AddToCborArray(cn_cbor_data_create(signature, 64, &error_not_used),
+                      array)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  result = EncodeCbor(array, buffer_size, buffer, encoded_size);
+
+out:
+  cn_cbor_free(array);
+  return result;
+}
+
+DiceResult DiceGenerateCborCertificateOp(
+    const DiceOps* ops,
+    const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+    const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+    const DiceInputValues* input_values, size_t certificate_buffer_size,
+    uint8_t* certificate, size_t* certificate_actual_size) {
+  DiceResult result = kDiceResultOk;
+
+  *certificate_actual_size = 0;
+  if (input_values->config_type != kDiceConfigTypeDescriptor &&
+      input_values->config_type != kDiceConfigTypeInline) {
+    return kDiceResultInvalidInput;
+  }
+
+  // Declare buffers which are cleared on 'goto out'.
+  uint8_t subject_bssl_private_key[64];
+  uint8_t authority_bssl_private_key[64];
+
+  // These are 'variably modified' types so need to be declared upfront.
+  uint8_t encoded_public_key[kMaxPublicKeySize];
+  uint8_t payload[kMaxCertificateSize];
+  uint8_t protected_attributes[kMaxProtectedAttributesSize];
+
+  // Derive public keys and IDs from the private keys. Note: the Boringssl
+  // implementation refers to the raw private key as a seed.
+  uint8_t subject_public_key[32];
+  ED25519_keypair_from_seed(subject_public_key, subject_bssl_private_key,
+                            subject_private_key);
+
+  uint8_t subject_id[20];
+  result = DiceDeriveCdiCertificateId(ops, subject_public_key, 32, subject_id);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+  char subject_id_hex[41];
+  DiceHexEncode(subject_id, sizeof(subject_id), subject_id_hex,
+                sizeof(subject_id_hex));
+  subject_id_hex[sizeof(subject_id_hex) - 1] = '\0';
+
+  uint8_t authority_public_key[32];
+  ED25519_keypair_from_seed(authority_public_key, authority_bssl_private_key,
+                            authority_private_key);
+
+  uint8_t authority_id[20];
+  result =
+      DiceDeriveCdiCertificateId(ops, authority_public_key, 32, authority_id);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+  char authority_id_hex[41];
+  DiceHexEncode(authority_id, sizeof(authority_id), authority_id_hex,
+                sizeof(authority_id_hex));
+  authority_id_hex[sizeof(authority_id_hex) - 1] = '\0';
+
+  // The encoded protected attributes are used in the TBS and the final
+  // COSE_Sign1 structure.
+  size_t protected_attributes_size = 0;
+  result = EncodeProtectedAttributes(sizeof(protected_attributes),
+                                     protected_attributes,
+                                     &protected_attributes_size);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  // The public key encoded as a COSE_Key structure is embedded in the CWT.
+  size_t encoded_public_key_size = 0;
+  result = EncodePublicKey(subject_public_key, sizeof(encoded_public_key),
+                           encoded_public_key, &encoded_public_key_size);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  // The CWT is the payload in both the TBS and the final COSE_Sign1 structure.
+  size_t payload_size = 0;
+  result = EncodeCwt(input_values, authority_id_hex, subject_id_hex,
+                     encoded_public_key, encoded_public_key_size,
+                     sizeof(payload), payload, &payload_size);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  // Construct a To-Be-Signed (TBS) structure based on the relevant fields of
+  // the COSE_Sign1.
+  result = EncodeCoseTbs(protected_attributes, protected_attributes_size,
+                         payload, payload_size, certificate_buffer_size,
+                         certificate, certificate_actual_size);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  // Sign the TBS with the authority key.
+  uint8_t signature[64];
+  if (1 != ED25519_sign(signature, certificate, *certificate_actual_size,
+                        authority_bssl_private_key)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (1 != ED25519_verify(certificate, *certificate_actual_size, signature,
+                          authority_public_key)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+  // The final certificate is an untagged COSE_Sign1 structure.
+  result = EncodeCoseSign1(
+      protected_attributes, protected_attributes_size, payload, payload_size,
+      signature, certificate_buffer_size, certificate, certificate_actual_size);
+
+out:
+  ops->clear_memory(ops, sizeof(subject_bssl_private_key),
+                    subject_bssl_private_key);
+  ops->clear_memory(ops, sizeof(authority_bssl_private_key),
+                    authority_bssl_private_key);
+
+  return result;
+}
diff --git a/src/cbor_cert_op_fuzzer.cc b/src/cbor_cert_op_fuzzer.cc
new file mode 100644
index 0000000..fb0fa53
--- /dev/null
+++ b/src/cbor_cert_op_fuzzer.cc
@@ -0,0 +1,34 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/boringssl_ops.h"
+#include "dice/cbor_cert_op.h"
+#include "dice/dice.h"
+#include "dice/fuzz_utils.h"
+#include "dice/utils.h"
+
+namespace {
+
+constexpr DiceOps kOps = {.context = NULL,
+                          .hash = DiceBsslHashOp,
+                          .kdf = DiceBsslKdfOp,
+                          .generate_certificate = DiceGenerateCborCertificateOp,
+                          .clear_memory = DiceClearMemory};
+
+}  // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  dice::fuzz::FuzzDiceMainFlow(&kOps, data, size);
+  return 0;
+}
diff --git a/src/cbor_cert_op_test.cc b/src/cbor_cert_op_test.cc
new file mode 100644
index 0000000..1d40bf5
--- /dev/null
+++ b/src/cbor_cert_op_test.cc
@@ -0,0 +1,252 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/cbor_cert_op.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <memory>
+
+#include "dice/boringssl_ops.h"
+#include "dice/dice.h"
+#include "dice/known_test_values.h"
+#include "dice/test_utils.h"
+#include "dice/utils.h"
+#include "pw_string/format.h"
+#include "pw_unit_test/framework.h"
+
+namespace {
+
+using dice::test::CertificateType_Cbor;
+using dice::test::DeriveFakeInputValue;
+using dice::test::DiceStateForTest;
+using dice::test::KeyType_Ed25519;
+
+constexpr DiceOps kOps = {.context = NULL,
+                          .hash = DiceBsslHashOp,
+                          .kdf = DiceBsslKdfOp,
+                          .generate_certificate = DiceGenerateCborCertificateOp,
+                          .clear_memory = DiceClearMemory};
+
+TEST(DiceOpsTest, KnownAnswerZeroInput) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  DumpState(CertificateType_Cbor, KeyType_Ed25519, "zero_input", next_state);
+  // Both CDI values and the certificate should be deterministic.
+  EXPECT_EQ(0, memcmp(next_state.cdi_attest,
+                      dice::test::kExpectedCdiAttest_ZeroInput, DICE_CDI_SIZE));
+  EXPECT_EQ(0, memcmp(next_state.cdi_seal,
+                      dice::test::kExpectedCdiSeal_ZeroInput, DICE_CDI_SIZE));
+  ASSERT_EQ(sizeof(dice::test::kExpectedCborEd25519Cert_ZeroInput),
+            next_state.certificate_size);
+  EXPECT_EQ(0, memcmp(dice::test::kExpectedCborEd25519Cert_ZeroInput,
+                      next_state.certificate, next_state.certificate_size));
+}
+
+TEST(DiceOpsTest, KnownAnswerHashOnlyInput) {
+  DiceStateForTest current_state = {};
+  DeriveFakeInputValue("cdi_attest", DICE_CDI_SIZE, current_state.cdi_attest);
+  DeriveFakeInputValue("cdi_seal", DICE_CDI_SIZE, current_state.cdi_seal);
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DeriveFakeInputValue("code_hash", DICE_HASH_SIZE, input_values.code_hash);
+  DeriveFakeInputValue("authority_hash", DICE_HASH_SIZE,
+                       input_values.authority_hash);
+  input_values.config_type = kDiceConfigTypeInline;
+  DeriveFakeInputValue("inline_config", DICE_INLINE_CONFIG_SIZE,
+                       input_values.config_value);
+
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  DumpState(CertificateType_Cbor, KeyType_Ed25519, "hash_only_input",
+            next_state);
+  // Both CDI values and the certificate should be deterministic.
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_attest,
+                dice::test::kExpectedCdiAttest_HashOnlyInput, DICE_CDI_SIZE));
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_seal, dice::test::kExpectedCdiSeal_HashOnlyInput,
+                DICE_CDI_SIZE));
+  ASSERT_EQ(sizeof(dice::test::kExpectedCborEd25519Cert_HashOnlyInput),
+            next_state.certificate_size);
+  EXPECT_EQ(0, memcmp(dice::test::kExpectedCborEd25519Cert_HashOnlyInput,
+                      next_state.certificate, next_state.certificate_size));
+}
+
+TEST(DiceOpsTest, KnownAnswerDescriptorInput) {
+  DiceStateForTest current_state = {};
+  DeriveFakeInputValue("cdi_attest", DICE_CDI_SIZE, current_state.cdi_attest);
+  DeriveFakeInputValue("cdi_seal", DICE_CDI_SIZE, current_state.cdi_seal);
+
+  DiceStateForTest next_state = {};
+
+  DiceInputValues input_values = {};
+  DeriveFakeInputValue("code_hash", DICE_HASH_SIZE, input_values.code_hash);
+  uint8_t code_descriptor[100];
+  DeriveFakeInputValue("code_desc", sizeof(code_descriptor), code_descriptor);
+  input_values.code_descriptor = code_descriptor;
+  input_values.code_descriptor_size = sizeof(code_descriptor);
+
+  uint8_t config_descriptor[40];
+  DeriveFakeInputValue("config_desc", sizeof(config_descriptor),
+                       config_descriptor);
+  input_values.config_descriptor = config_descriptor;
+  input_values.config_descriptor_size = sizeof(config_descriptor);
+  input_values.config_type = kDiceConfigTypeDescriptor;
+
+  DeriveFakeInputValue("authority_hash", DICE_HASH_SIZE,
+                       input_values.authority_hash);
+  uint8_t authority_descriptor[65];
+  DeriveFakeInputValue("authority_desc", sizeof(authority_descriptor),
+                       authority_descriptor);
+  input_values.authority_descriptor = authority_descriptor;
+  input_values.authority_descriptor_size = sizeof(authority_descriptor);
+
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  DumpState(CertificateType_Cbor, KeyType_Ed25519, "descriptor_input",
+            next_state);
+  // Both CDI values and the certificate should be deterministic.
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_attest,
+                dice::test::kExpectedCdiAttest_DescriptorInput, DICE_CDI_SIZE));
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_seal,
+                dice::test::kExpectedCdiSeal_DescriptorInput, DICE_CDI_SIZE));
+  ASSERT_EQ(sizeof(dice::test::kExpectedCborEd25519Cert_DescriptorInput),
+            next_state.certificate_size);
+  EXPECT_EQ(0, memcmp(dice::test::kExpectedCborEd25519Cert_DescriptorInput,
+                      next_state.certificate, next_state.certificate_size));
+}
+
+TEST(DiceOpsTest, NonZeroMode) {
+  constexpr size_t kModeOffsetInCert = 315;
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  input_values.mode = kDiceModeDebug;
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  EXPECT_EQ(kDiceModeDebug, next_state.certificate[kModeOffsetInCert]);
+}
+
+TEST(DiceOpsTest, LargeInputs) {
+  constexpr uint8_t kBigBuffer[1024 * 1024] = {};
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  input_values.code_descriptor = kBigBuffer;
+  input_values.code_descriptor_size = sizeof(kBigBuffer);
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultBufferTooSmall, result);
+}
+
+TEST(DiceOpsTest, InvalidConfigType) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  input_values.config_type = (DiceConfigType)55;
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultInvalidInput, result);
+}
+
+TEST(DiceOpsTest, PartialCertChain) {
+  constexpr size_t kNumLayers = 7;
+  DiceStateForTest states[kNumLayers + 1] = {};
+  DiceInputValues inputs[kNumLayers] = {};
+  for (size_t i = 0; i < kNumLayers; ++i) {
+    char seed[40];
+    pw::string::Format(seed, "code_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].code_hash);
+    pw::string::Format(seed, "authority_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].authority_hash);
+    inputs[i].config_type = kDiceConfigTypeInline;
+    pw::string::Format(seed, "inline_config_%zu", i);
+    DeriveFakeInputValue(seed, DICE_INLINE_CONFIG_SIZE, inputs[i].config_value);
+    inputs[i].mode = kDiceModeNormal;
+    EXPECT_EQ(
+        kDiceResultOk,
+        DiceMainFlow(&kOps, states[i].cdi_attest, states[i].cdi_seal,
+                     &inputs[i], sizeof(states[i + 1].certificate),
+                     states[i + 1].certificate, &states[i + 1].certificate_size,
+                     states[i + 1].cdi_attest, states[i + 1].cdi_seal));
+    char suffix[40];
+    pw::string::Format(suffix, "part_cert_chain_%zu", i);
+    DumpState(CertificateType_Cbor, KeyType_Ed25519, suffix, states[i + 1]);
+  }
+  // Use the first derived CDI cert as the 'root' of partial chain.
+  EXPECT_TRUE(dice::test::VerifyCertificateChain(
+      CertificateType_Cbor, states[1].certificate, states[1].certificate_size,
+      &states[2], kNumLayers - 1, /*is_partial_chain=*/true));
+}
+
+TEST(DiceOpsTest, FullCertChain) {
+  constexpr size_t kNumLayers = 7;
+  DiceStateForTest states[kNumLayers + 1] = {};
+  DiceInputValues inputs[kNumLayers] = {};
+  for (size_t i = 0; i < kNumLayers; ++i) {
+    char seed[40];
+    pw::string::Format(seed, "code_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].code_hash);
+    pw::string::Format(seed, "authority_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].authority_hash);
+    inputs[i].config_type = kDiceConfigTypeInline;
+    pw::string::Format(seed, "inline_config_%zu", i);
+    DeriveFakeInputValue(seed, DICE_INLINE_CONFIG_SIZE, inputs[i].config_value);
+    inputs[i].mode = kDiceModeNormal;
+    EXPECT_EQ(
+        kDiceResultOk,
+        DiceMainFlow(&kOps, states[i].cdi_attest, states[i].cdi_seal,
+                     &inputs[i], sizeof(states[i + 1].certificate),
+                     states[i + 1].certificate, &states[i + 1].certificate_size,
+                     states[i + 1].cdi_attest, states[i + 1].cdi_seal));
+    char suffix[40];
+    pw::string::Format(suffix, "full_cert_chain_%zu", i);
+    DumpState(CertificateType_Cbor, KeyType_Ed25519, suffix, states[i + 1]);
+  }
+  // Use a fake self-signed UDS cert as the 'root'.
+  uint8_t root_certificate[dice::test::kTestCertSize];
+  size_t root_certificate_size = 0;
+  dice::test::CreateFakeUdsCertificate(
+      kOps, states[0].cdi_attest, CertificateType_Cbor, KeyType_Ed25519,
+      root_certificate, &root_certificate_size);
+  EXPECT_TRUE(dice::test::VerifyCertificateChain(
+      CertificateType_Cbor, root_certificate, root_certificate_size, &states[1],
+      kNumLayers, /*is_partial_chain=*/false));
+}
+
+}  // namespace
diff --git a/src/dice.c b/src/dice.c
new file mode 100644
index 0000000..0b1ff64
--- /dev/null
+++ b/src/dice.c
@@ -0,0 +1,181 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/dice.h"
+
+#include <string.h>
+
+static const uint8_t kAsymSalt[] = {
+    0x63, 0xB6, 0xA0, 0x4D, 0x2C, 0x07, 0x7F, 0xC1, 0x0F, 0x63, 0x9F,
+    0x21, 0xDA, 0x79, 0x38, 0x44, 0x35, 0x6C, 0xC2, 0xB0, 0xB4, 0x41,
+    0xB3, 0xA7, 0x71, 0x24, 0x03, 0x5C, 0x03, 0xF8, 0xE1, 0xBE, 0x60,
+    0x35, 0xD3, 0x1F, 0x28, 0x28, 0x21, 0xA7, 0x45, 0x0A, 0x02, 0x22,
+    0x2A, 0xB1, 0xB3, 0xCF, 0xF1, 0x67, 0x9B, 0x05, 0xAB, 0x1C, 0xA5,
+    0xD1, 0xAF, 0xFB, 0x78, 0x9C, 0xCD, 0x2B, 0x0B, 0x3B};
+static const size_t kAsymSaltSize = 64;
+
+static const uint8_t kIdSalt[] = {
+    0xDB, 0xDB, 0xAE, 0xBC, 0x80, 0x20, 0xDA, 0x9F, 0xF0, 0xDD, 0x5A,
+    0x24, 0xC8, 0x3A, 0xA5, 0xA5, 0x42, 0x86, 0xDF, 0xC2, 0x63, 0x03,
+    0x1E, 0x32, 0x9B, 0x4D, 0xA1, 0x48, 0x43, 0x06, 0x59, 0xFE, 0x62,
+    0xCD, 0xB5, 0xB7, 0xE1, 0xE0, 0x0F, 0xC6, 0x80, 0x30, 0x67, 0x11,
+    0xEB, 0x44, 0x4A, 0xF7, 0x72, 0x09, 0x35, 0x94, 0x96, 0xFC, 0xFF,
+    0x1D, 0xB9, 0x52, 0x0B, 0xA5, 0x1C, 0x7B, 0x29, 0xEA};
+static const size_t kIdSaltSize = 64;
+
+DiceResult DiceDeriveCdiPrivateKey(const DiceOps* ops,
+                                   const uint8_t cdi_attest[DICE_CDI_SIZE],
+                                   uint8_t cdi_private_key[DICE_CDI_SIZE]) {
+  // Use the CDI as input key material, with fixed salt and info.
+  return ops->kdf(ops, /*length=*/DICE_PRIVATE_KEY_SIZE, cdi_attest,
+                  /*ikm_size=*/DICE_CDI_SIZE, kAsymSalt, kAsymSaltSize,
+                  /*info=*/(const uint8_t*)"Key Pair", /*info_size=*/8,
+                  cdi_private_key);
+}
+
+DiceResult DiceDeriveCdiCertificateId(const DiceOps* ops,
+                                      const uint8_t* cdi_public_key,
+                                      size_t cdi_public_key_size,
+                                      uint8_t id[20]) {
+  // Use the public key as input key material, with fixed salt and info.
+  DiceResult result =
+      ops->kdf(ops, /*length=*/20, cdi_public_key, cdi_public_key_size, kIdSalt,
+               kIdSaltSize,
+               /*info=*/(const uint8_t*)"ID", /*info_size=*/2, id);
+  if (result == kDiceResultOk) {
+    // Clear the top bit to keep the integer positive.
+    id[0] &= ~0x80;
+  }
+  return result;
+}
+
+DiceResult DiceMainFlow(const DiceOps* ops,
+                        const uint8_t current_cdi_attest[DICE_CDI_SIZE],
+                        const uint8_t current_cdi_seal[DICE_CDI_SIZE],
+                        const DiceInputValues* input_values,
+                        size_t next_cdi_certificate_buffer_size,
+                        uint8_t* next_cdi_certificate,
+                        size_t* next_cdi_certificate_actual_size,
+                        uint8_t next_cdi_attest[DICE_CDI_SIZE],
+                        uint8_t next_cdi_seal[DICE_CDI_SIZE]) {
+  // This implementation serializes the inputs for a one-shot hash. On some
+  // platforms, using a multi-part hash operation may be more optimal. The
+  // combined input buffer has this layout:
+  // ---------------------------------------------------------------------------
+  // | Code Input | Config Input | Authority Input | Mode Input | Hidden Input |
+  // ---------------------------------------------------------------------------
+  const size_t kCodeSize = DICE_HASH_SIZE;
+  const size_t kConfigSize = DICE_INLINE_CONFIG_SIZE;
+  const size_t kAuthoritySize = DICE_HASH_SIZE;
+  const size_t kModeSize = 1;
+  const size_t kHiddenSize = DICE_HIDDEN_SIZE;
+  const size_t kCodeOffset = 0;
+  const size_t kConfigOffset = kCodeOffset + kCodeSize;
+  const size_t kAuthorityOffset = kConfigOffset + kConfigSize;
+  const size_t kModeOffset = kAuthorityOffset + kAuthoritySize;
+  const size_t kHiddenOffset = kModeOffset + kModeSize;
+
+  DiceResult result = kDiceResultOk;
+
+  // Declare buffers that get cleaned up on 'goto out'.
+  uint8_t input_buffer[kCodeSize + kConfigSize + kAuthoritySize + kModeSize +
+                       kHiddenSize];
+  uint8_t attest_input_hash[DICE_HASH_SIZE];
+  uint8_t seal_input_hash[DICE_HASH_SIZE];
+  uint8_t current_cdi_private_key[DICE_PRIVATE_KEY_SIZE];
+  uint8_t next_cdi_private_key[DICE_PRIVATE_KEY_SIZE];
+
+  // Assemble the input buffer.
+  memcpy(&input_buffer[kCodeOffset], input_values->code_hash, kCodeSize);
+  if (input_values->config_type == kDiceConfigTypeInline) {
+    memcpy(&input_buffer[kConfigOffset], input_values->config_value,
+           kConfigSize);
+  } else if (!input_values->config_descriptor) {
+    result = kDiceResultInvalidInput;
+    goto out;
+  } else {
+    result = ops->hash(ops, input_values->config_descriptor,
+                       input_values->config_descriptor_size,
+                       &input_buffer[kConfigOffset]);
+    if (result != kDiceResultOk) {
+      goto out;
+    }
+  }
+  memcpy(&input_buffer[kAuthorityOffset], input_values->authority_hash,
+         kAuthoritySize);
+  input_buffer[kModeOffset] = input_values->mode;
+  memcpy(&input_buffer[kHiddenOffset], input_values->hidden, kHiddenSize);
+
+  // Hash the appropriate input values for both attestation and sealing. For
+  // attestation all the inputs are used, and for sealing only the authority,
+  // mode, and hidden inputs are used.
+  result =
+      ops->hash(ops, input_buffer, sizeof(input_buffer), attest_input_hash);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+  result = ops->hash(ops, &input_buffer[kAuthorityOffset],
+                     kAuthoritySize + kModeSize + kHiddenSize, seal_input_hash);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  // Compute the next CDI values. For each of these the current CDI value is
+  // used as input key material and the input hash is used as salt.
+  result = ops->kdf(ops, /*length=*/DICE_CDI_SIZE, current_cdi_attest,
+                    /*ikm_size=*/DICE_CDI_SIZE, attest_input_hash,
+                    /*salt_size=*/DICE_HASH_SIZE,
+                    /*info=*/(const uint8_t*)"CDI_Attest", /*info_size=*/10,
+                    next_cdi_attest);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+  result = ops->kdf(
+      ops, /*length=*/DICE_CDI_SIZE, current_cdi_seal,
+      /*ikm_size=*/DICE_CDI_SIZE, seal_input_hash, /*salt_size=*/DICE_HASH_SIZE,
+      /*info=*/(const uint8_t*)"CDI_Seal", /*info_size=*/8, next_cdi_seal);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  // Derive asymmetric private keys from the attestation CDI values.
+  result =
+      DiceDeriveCdiPrivateKey(ops, current_cdi_attest, current_cdi_private_key);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+  result = DiceDeriveCdiPrivateKey(ops, next_cdi_attest, next_cdi_private_key);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  // Generate a certificate for |next_cdi_private_key| with
+  // |current_cdi_private_key| as the authority.
+  result = ops->generate_certificate(
+      ops, next_cdi_private_key, current_cdi_private_key, input_values,
+      next_cdi_certificate_buffer_size, next_cdi_certificate,
+      next_cdi_certificate_actual_size);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+out:
+  // Clear sensitive memory.
+  ops->clear_memory(ops, sizeof(input_buffer), input_buffer);
+  ops->clear_memory(ops, sizeof(attest_input_hash), attest_input_hash);
+  ops->clear_memory(ops, sizeof(seal_input_hash), seal_input_hash);
+  ops->clear_memory(ops, sizeof(current_cdi_private_key),
+                    current_cdi_private_key);
+  ops->clear_memory(ops, sizeof(next_cdi_private_key), next_cdi_private_key);
+  return result;
+}
diff --git a/src/dice_standalone_main.c b/src/dice_standalone_main.c
new file mode 100644
index 0000000..605494f
--- /dev/null
+++ b/src/dice_standalone_main.c
@@ -0,0 +1,70 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <stdint.h>
+
+#include "dice/dice.h"
+#include "dice/utils.h"
+
+DiceResult FakeHash(const DiceOps* ops, const uint8_t* input, size_t input_size,
+                    uint8_t output[DICE_HASH_SIZE]) {
+  (void)ops;
+  (void)input;
+  (void)input_size;
+  (void)output;
+  return kDiceResultOk;
+}
+
+DiceResult FakeKdf(const DiceOps* ops, size_t length, const uint8_t* ikm,
+                   size_t ikm_size, const uint8_t* salt, size_t salt_size,
+                   const uint8_t* info, size_t info_size, uint8_t* output) {
+  (void)ops;
+  (void)length;
+  (void)ikm;
+  (void)ikm_size;
+  (void)salt;
+  (void)salt_size;
+  (void)info;
+  (void)info_size;
+  (void)output;
+  return kDiceResultOk;
+}
+
+DiceResult FakeCertificate(
+    const DiceOps* ops,
+    const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+    const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+    const DiceInputValues* input_values, size_t certificate_buffer_size,
+    uint8_t* certificate, size_t* certificate_actual_size) {
+  (void)ops;
+  (void)subject_private_key;
+  (void)authority_private_key;
+  (void)input_values;
+  (void)certificate_buffer_size;
+  (void)certificate;
+  (void)certificate_actual_size;
+  return kDiceResultOk;
+}
+
+int main(int argc, char** argv) {
+  (void)argc;
+  (void)argv;
+  const DiceOps ops = {0, FakeHash, FakeKdf, FakeCertificate, DiceClearMemory};
+  uint8_t cdi_buffer[DICE_CDI_SIZE];
+  uint8_t cert_buffer[2048];
+  size_t cert_size;
+  DiceInputValues input_values = {0};
+  return (int)DiceMainFlow(&ops, cdi_buffer, cdi_buffer, &input_values, 2048,
+                           cert_buffer, &cert_size, cdi_buffer, cdi_buffer);
+}
diff --git a/src/dice_test.cc b/src/dice_test.cc
new file mode 100644
index 0000000..e0e7965
--- /dev/null
+++ b/src/dice_test.cc
@@ -0,0 +1,224 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/dice.h"
+
+#include "dice/known_test_values.h"
+#include "dice/utils.h"
+#include "openssl/crypto.h"
+#include "openssl/digest.h"
+#include "openssl/hkdf.h"
+#include "openssl/sha.h"
+#include "pw_unit_test/framework.h"
+
+namespace {
+
+extern "C" {
+DiceResult FakeHash(const DiceOps* ops, const uint8_t* input, size_t input_size,
+                    uint8_t output[DICE_HASH_SIZE]);
+
+DiceResult FakeKdf(const DiceOps* ops, size_t length, const uint8_t* ikm,
+                   size_t ikm_size, const uint8_t* salt, size_t salt_size,
+                   const uint8_t* info, size_t info_size, uint8_t* output);
+
+DiceResult FakeGenerateCertificate(
+    const DiceOps* ops,
+    const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+    const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+    const DiceInputValues* input_values, size_t certificate_buffer_size,
+    uint8_t* certificate, size_t* certificate_actual_size);
+}  // extern "C"
+
+const size_t kFakeCertSize = 200;
+
+struct FakeDiceOps {
+  FakeDiceOps() { CRYPTO_library_init(); }
+  operator const DiceOps*() const { return &ops_; }
+
+  // DiceOps calls to |hash| forward here.
+  DiceResult Hash(const uint8_t* input, size_t input_size,
+                  uint8_t output[DICE_HASH_SIZE]) {
+    SHA512(input, input_size, output);
+    hash_count_++;
+    return hash_result_;
+  }
+
+  // DiceOps calls to |kdf| forward here.
+  DiceResult Kdf(size_t length, const uint8_t* ikm, size_t ikm_size,
+                 const uint8_t* salt, size_t salt_size, const uint8_t* info,
+                 size_t info_size, uint8_t* output) {
+    HKDF(output, length, EVP_sha512(), ikm, ikm_size, salt, salt_size, info,
+         info_size);
+    kdf_count_++;
+    return kdf_result_;
+  }
+
+  // DiceOps calls to |generate_certificate| forward here.
+  DiceResult GenerateCertificate(
+      const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+      const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+      const DiceInputValues* input_values, size_t certificate_buffer_size,
+      uint8_t* certificate, size_t* certificate_actual_size) {
+    const uint8_t kFakeCert[kFakeCertSize] = {};
+    (void)subject_private_key;
+    (void)authority_private_key;
+    (void)input_values;
+    generate_certificate_count_++;
+    if (certificate_buffer_size < kFakeCertSize) {
+      *certificate_actual_size = kFakeCertSize;
+      return kDiceResultBufferTooSmall;
+    }
+    *certificate_actual_size = kFakeCertSize;
+    memcpy(certificate, kFakeCert, kFakeCertSize);
+    return generate_certificate_result_;
+  }
+
+  // Set these in a test to induce errors.
+  DiceResult hash_result_ = kDiceResultOk;
+  DiceResult kdf_result_ = kDiceResultOk;
+  DiceResult generate_certificate_result_ = kDiceResultOk;
+
+  // These will be incremented on every DiceOps call.
+  int hash_count_ = 0;
+  int kdf_count_ = 0;
+  int generate_certificate_count_ = 0;
+
+  // This is used as the DiceOps argument for DiceMainFlow calls.
+  DiceOps ops_ = {.context = this,
+                  .hash = FakeHash,
+                  .kdf = FakeKdf,
+                  .generate_certificate = FakeGenerateCertificate,
+                  .clear_memory = DiceClearMemory};
+};
+
+// These callbacks forward to a FakeDiceOps instance.
+DiceResult FakeHash(const DiceOps* ops, const uint8_t* input, size_t input_size,
+                    uint8_t output[DICE_HASH_SIZE]) {
+  return reinterpret_cast<FakeDiceOps*>(ops->context)
+      ->Hash(input, input_size, output);
+}
+
+DiceResult FakeKdf(const DiceOps* ops, size_t length, const uint8_t* ikm,
+                   size_t ikm_size, const uint8_t* salt, size_t salt_size,
+                   const uint8_t* info, size_t info_size, uint8_t* output) {
+  return reinterpret_cast<FakeDiceOps*>(ops->context)
+      ->Kdf(length, ikm, ikm_size, salt, salt_size, info, info_size, output);
+}
+
+DiceResult FakeGenerateCertificate(
+    const DiceOps* ops,
+    const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+    const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+    const DiceInputValues* input_values, size_t certificate_buffer_size,
+    uint8_t* certificate, size_t* certificate_actual_size) {
+  return reinterpret_cast<FakeDiceOps*>(ops->context)
+      ->GenerateCertificate(subject_private_key, authority_private_key,
+                            input_values, certificate_buffer_size, certificate,
+                            certificate_actual_size);
+}
+
+struct DiceStateForTest {
+  uint8_t cdi_attest[DICE_CDI_SIZE];
+  uint8_t cdi_seal[DICE_CDI_SIZE];
+  uint8_t certificate[kFakeCertSize + 10];
+  size_t certificate_size;
+};
+
+TEST(DiceTest, KnownAnswer) {
+  FakeDiceOps ops;
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DiceResult result = DiceMainFlow(
+      ops, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  EXPECT_EQ(0, memcmp(next_state.cdi_attest,
+                      dice::test::kExpectedCdiAttest_ZeroInput, DICE_CDI_SIZE));
+  EXPECT_EQ(0, memcmp(next_state.cdi_seal,
+                      dice::test::kExpectedCdiSeal_ZeroInput, DICE_CDI_SIZE));
+  EXPECT_EQ(kFakeCertSize, next_state.certificate_size);
+}
+
+TEST(DiceTest, HashFail) {
+  FakeDiceOps ops;
+  ops.hash_result_ = kDiceResultPlatformError;
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DiceResult result = DiceMainFlow(
+      ops, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultPlatformError, result);
+}
+
+TEST(DiceTest, KdfFail) {
+  FakeDiceOps ops;
+  ops.kdf_result_ = kDiceResultPlatformError;
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DiceResult result = DiceMainFlow(
+      ops, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultPlatformError, result);
+}
+
+TEST(DiceTest, CertFail) {
+  FakeDiceOps ops;
+  ops.generate_certificate_result_ = kDiceResultPlatformError;
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DiceResult result = DiceMainFlow(
+      ops, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultPlatformError, result);
+}
+
+TEST(DiceTest, CertTooSmall) {
+  FakeDiceOps ops;
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DiceResult result = DiceMainFlow(
+      ops, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      kFakeCertSize - 1, next_state.certificate, &next_state.certificate_size,
+      next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultBufferTooSmall, result);
+  EXPECT_EQ(next_state.certificate_size, kFakeCertSize);
+}
+
+TEST(DiceTest, NoExtraneousOps) {
+  FakeDiceOps ops;
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DiceResult result = DiceMainFlow(
+      ops, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  // These are brittle, but can act as a good sanity check that we're not
+  // regressing in how many expensive operations we call.
+  EXPECT_LE(ops.hash_count_, 2);
+  EXPECT_LE(ops.kdf_count_, 4);
+  EXPECT_LE(ops.generate_certificate_count_, 1);
+}
+
+}  // namespace
diff --git a/src/dice_with_boringssl_ops_main.c b/src/dice_with_boringssl_ops_main.c
new file mode 100644
index 0000000..0be4639
--- /dev/null
+++ b/src/dice_with_boringssl_ops_main.c
@@ -0,0 +1,32 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <stdint.h>
+
+#include "dice/boringssl_ops.h"
+#include "dice/dice.h"
+#include "dice/utils.h"
+
+int main(int argc, char** argv) {
+  (void)argc;
+  (void)argv;
+  const DiceOps ops = {0, DiceBsslHashOp, DiceBsslKdfOp,
+                       DiceBsslGenerateCertificateOp, DiceClearMemory};
+  uint8_t cdi_buffer[DICE_CDI_SIZE];
+  uint8_t cert_buffer[2048];
+  size_t cert_size;
+  DiceInputValues input_values = {0};
+  return (int)DiceMainFlow(&ops, cdi_buffer, cdi_buffer, &input_values, 2048,
+                           cert_buffer, &cert_size, cdi_buffer, cdi_buffer);
+}
diff --git a/src/dice_with_cbor_cert_main.c b/src/dice_with_cbor_cert_main.c
new file mode 100644
index 0000000..ecc757f
--- /dev/null
+++ b/src/dice_with_cbor_cert_main.c
@@ -0,0 +1,33 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <stdint.h>
+
+#include "dice/boringssl_ops.h"
+#include "dice/cbor_cert_op.h"
+#include "dice/dice.h"
+#include "dice/utils.h"
+
+int main(int argc, char** argv) {
+  (void)argc;
+  (void)argv;
+  const DiceOps ops = {0, DiceBsslHashOp, DiceBsslKdfOp,
+                       DiceGenerateCborCertificateOp, DiceClearMemory};
+  uint8_t cdi_buffer[DICE_CDI_SIZE];
+  uint8_t cert_buffer[2048];
+  size_t cert_size;
+  DiceInputValues input_values = {0};
+  return (int)DiceMainFlow(&ops, cdi_buffer, cdi_buffer, &input_values, 2048,
+                           cert_buffer, &cert_size, cdi_buffer, cdi_buffer);
+}
diff --git a/src/dice_with_cbor_template_cert_main.c b/src/dice_with_cbor_template_cert_main.c
new file mode 100644
index 0000000..ecaea07
--- /dev/null
+++ b/src/dice_with_cbor_template_cert_main.c
@@ -0,0 +1,34 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <stdint.h>
+
+#include "dice/boringssl_ops.h"
+#include "dice/dice.h"
+#include "dice/template_cbor_cert_op.h"
+#include "dice/utils.h"
+
+int main(int argc, char** argv) {
+  (void)argc;
+  (void)argv;
+  const DiceOps ops = {0, DiceBsslHashOp, DiceBsslKdfOp,
+                       DiceGenerateCborCertificateFromTemplateOp,
+                       DiceClearMemory};
+  uint8_t cdi_buffer[DICE_CDI_SIZE];
+  uint8_t cert_buffer[2048];
+  size_t cert_size;
+  DiceInputValues input_values = {0};
+  return (int)DiceMainFlow(&ops, cdi_buffer, cdi_buffer, &input_values, 2048,
+                           cert_buffer, &cert_size, cdi_buffer, cdi_buffer);
+}
diff --git a/src/dice_with_mbedtls_ops_main.c b/src/dice_with_mbedtls_ops_main.c
new file mode 100644
index 0000000..60dc5d3
--- /dev/null
+++ b/src/dice_with_mbedtls_ops_main.c
@@ -0,0 +1,32 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <stdint.h>
+
+#include "dice/dice.h"
+#include "dice/mbedtls_ops.h"
+#include "dice/utils.h"
+
+int main(int argc, char** argv) {
+  (void)argc;
+  (void)argv;
+  const DiceOps ops = {0, DiceMbedtlsHashOp, DiceMbedtlsKdfOp,
+                       DiceMbedtlsGenerateCertificateOp, DiceClearMemory};
+  uint8_t cdi_buffer[DICE_CDI_SIZE];
+  uint8_t cert_buffer[2048];
+  size_t cert_size;
+  DiceInputValues input_values = {0};
+  return (int)DiceMainFlow(&ops, cdi_buffer, cdi_buffer, &input_values, 2048,
+                           cert_buffer, &cert_size, cdi_buffer, cdi_buffer);
+}
diff --git a/src/dice_with_x509_template_cert_main.c b/src/dice_with_x509_template_cert_main.c
new file mode 100644
index 0000000..d74f52e
--- /dev/null
+++ b/src/dice_with_x509_template_cert_main.c
@@ -0,0 +1,33 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <stdint.h>
+
+#include "dice/boringssl_ops.h"
+#include "dice/dice.h"
+#include "dice/template_cert_op.h"
+#include "dice/utils.h"
+
+int main(int argc, char** argv) {
+  (void)argc;
+  (void)argv;
+  const DiceOps ops = {0, DiceBsslHashOp, DiceBsslKdfOp,
+                       DiceGenerateCertificateFromTemplateOp, DiceClearMemory};
+  uint8_t cdi_buffer[DICE_CDI_SIZE];
+  uint8_t cert_buffer[2048];
+  size_t cert_size;
+  DiceInputValues input_values = {0};
+  return (int)DiceMainFlow(&ops, cdi_buffer, cdi_buffer, &input_values, 2048,
+                           cert_buffer, &cert_size, cdi_buffer, cdi_buffer);
+}
diff --git a/src/empty_main.c b/src/empty_main.c
new file mode 100644
index 0000000..8bd4b7d
--- /dev/null
+++ b/src/empty_main.c
@@ -0,0 +1,19 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+int main(int argc, char** argv) {
+  (void)argc;
+  (void)argv;
+  return 0;
+}
diff --git a/src/fuzz_utils.cc b/src/fuzz_utils.cc
new file mode 100644
index 0000000..b5cce1b
--- /dev/null
+++ b/src/fuzz_utils.cc
@@ -0,0 +1,112 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/fuzz_utils.h"
+
+#include "dice/dice.h"
+#include "fuzzer/FuzzedDataProvider.h"
+
+namespace {
+
+std::vector<uint8_t> ConsumeRandomLengthStringAsBytesFrom(
+    FuzzedDataProvider& fdp) {
+  auto s = fdp.ConsumeRandomLengthString();
+  return std::vector<uint8_t>(s.begin(), s.end());
+}
+
+struct FuzzedInputValues {
+  static FuzzedInputValues ConsumeFrom(FuzzedDataProvider& fdp) {
+    FuzzedInputValues fiv = {};
+    DiceInputValues& input_values = fiv.input_values_;
+
+    fdp.ConsumeData(&input_values.code_hash, DICE_HASH_SIZE);
+
+    fiv.code_descriptor_ = ConsumeRandomLengthStringAsBytesFrom(fdp);
+    input_values.code_descriptor = fiv.code_descriptor_.data();
+    input_values.code_descriptor_size = fiv.code_descriptor_.size();
+
+    input_values.config_type = (DiceConfigType)fdp.ConsumeIntegralInRange(0, 1);
+
+    fdp.ConsumeData(&input_values.config_value, DICE_INLINE_CONFIG_SIZE);
+
+    fiv.config_descriptor_ = ConsumeRandomLengthStringAsBytesFrom(fdp);
+    input_values.config_descriptor = fiv.config_descriptor_.data();
+    input_values.config_descriptor_size = fiv.config_descriptor_.size();
+
+    fdp.ConsumeData(&input_values.authority_hash, DICE_HASH_SIZE);
+
+    fiv.authority_descriptor_ = ConsumeRandomLengthStringAsBytesFrom(fdp);
+    input_values.authority_descriptor = fiv.authority_descriptor_.data();
+    input_values.authority_descriptor_size = fiv.authority_descriptor_.size();
+
+    input_values.mode = (DiceMode)fdp.ConsumeIntegralInRange(0, 3);
+
+    fdp.ConsumeData(&input_values.hidden, DICE_HIDDEN_SIZE);
+
+    return fiv;
+  }
+
+  operator const DiceInputValues*() const { return &input_values_; }
+
+  std::vector<uint8_t> code_descriptor_;
+  std::vector<uint8_t> config_descriptor_;
+  std::vector<uint8_t> authority_descriptor_;
+
+  DiceInputValues input_values_;
+};
+
+}  // namespace
+
+namespace dice {
+namespace fuzz {
+
+int FuzzDiceMainFlow(const DiceOps* ops, const uint8_t* data, size_t size) {
+  // Exit early if there might not be enough data to fill buffers.
+  if (size < 512) {
+    return 0;
+  }
+
+  FuzzedDataProvider fdp(data, size);
+
+  // Prepare the fuzzed inputs.
+  auto input_values = FuzzedInputValues::ConsumeFrom(fdp);
+  uint8_t current_cdi_attest[DICE_CDI_SIZE] = {};
+  uint8_t current_cdi_seal[DICE_CDI_SIZE] = {};
+
+  fdp.ConsumeData(&current_cdi_attest, sizeof(current_cdi_attest));
+  fdp.ConsumeData(&current_cdi_seal, sizeof(current_cdi_seal));
+
+  // Initialize output parameters with fuzz data in case they are wrongly being
+  // read from.
+  constexpr size_t kNextCdiCertificateBufferSize = 1024;
+  auto next_cdi_certificate_actual_size = fdp.ConsumeIntegral<size_t>();
+  uint8_t next_cdi_certificate[kNextCdiCertificateBufferSize] = {};
+  uint8_t next_cdi_attest[DICE_CDI_SIZE] = {};
+  uint8_t next_cdi_seal[DICE_CDI_SIZE] = {};
+
+  fdp.ConsumeData(&next_cdi_certificate, kNextCdiCertificateBufferSize);
+  fdp.ConsumeData(&next_cdi_attest, DICE_CDI_SIZE);
+  fdp.ConsumeData(&next_cdi_seal, DICE_CDI_SIZE);
+
+  // Fuzz the main flow.
+  DiceMainFlow(ops, current_cdi_attest, current_cdi_seal, input_values,
+               kNextCdiCertificateBufferSize, next_cdi_certificate,
+               &next_cdi_certificate_actual_size, next_cdi_attest,
+               next_cdi_seal);
+
+  return 0;
+}
+
+}  // namespace fuzz
+}  // namespace dice
diff --git a/src/mbedtls_ops.c b/src/mbedtls_ops.c
new file mode 100644
index 0000000..0d2a3c2
--- /dev/null
+++ b/src/mbedtls_ops.c
@@ -0,0 +1,466 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/mbedtls_ops.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include "dice/dice.h"
+#include "dice/utils.h"
+#include "mbedtls/asn1.h"
+#include "mbedtls/asn1write.h"
+#include "mbedtls/bignum.h"
+#include "mbedtls/ecdsa.h"
+#include "mbedtls/ecp.h"
+#include "mbedtls/hkdf.h"
+#include "mbedtls/hmac_drbg.h"
+#include "mbedtls/md.h"
+#include "mbedtls/oid.h"
+#include "mbedtls/pk.h"
+#include "mbedtls/x509.h"
+#include "mbedtls/x509_crt.h"
+
+static const size_t kMaxCertificateSize = 2048;
+static const size_t kMaxExtensionSize = 2048;
+static const size_t kMaxKeyIdSize = 40;
+
+static DiceResult SetupKeyPair(const uint8_t private_key[DICE_PRIVATE_KEY_SIZE],
+                               mbedtls_pk_context* context) {
+  if (0 !=
+      mbedtls_pk_setup(context, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY))) {
+    return kDiceResultPlatformError;
+  }
+  // Don't use the |private_key| directly, it may not be suitable. Rather use it
+  // to seed a PRNG which is then in turn used to generate the private key. This
+  // implementation uses HMAC_DRBG in a loop with no reduction, like RFC6979.
+  DiceResult result = kDiceResultOk;
+  mbedtls_hmac_drbg_context rng_context;
+  mbedtls_hmac_drbg_init(&rng_context);
+  if (0 != mbedtls_hmac_drbg_seed_buf(
+               &rng_context, mbedtls_md_info_from_type(MBEDTLS_MD_SHA512),
+               private_key, DICE_PRIVATE_KEY_SIZE)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (0 != mbedtls_ecp_gen_key(MBEDTLS_ECP_DP_SECP256R1,
+                               mbedtls_pk_ec(*context),
+                               mbedtls_hmac_drbg_random, &rng_context)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+
+out:
+  mbedtls_hmac_drbg_free(&rng_context);
+  return result;
+}
+
+static DiceResult GetIdFromKey(const DiceOps* ops,
+                               const mbedtls_pk_context* context,
+                               uint8_t id[20]) {
+  uint8_t raw_public_key[33];
+  size_t raw_public_key_size = 0;
+  mbedtls_ecp_keypair* key = mbedtls_pk_ec(*context);
+
+  if (0 != mbedtls_ecp_point_write_binary(
+               &key->grp, &key->Q, MBEDTLS_ECP_PF_COMPRESSED,
+               &raw_public_key_size, raw_public_key, sizeof(raw_public_key))) {
+    return kDiceResultPlatformError;
+  }
+  return DiceDeriveCdiCertificateId(ops, raw_public_key, raw_public_key_size,
+                                    id);
+}
+
+// 54 byte name is prefix (13), hex id (40), and a null terminator.
+static void GetNameFromId(const uint8_t id[20], char name[54]) {
+  strcpy(name, "serialNumber=");
+  DiceHexEncode(id, /*num_bytes=*/20, (uint8_t*)&name[13], /*out_size=*/40);
+  name[53] = '\0';
+}
+
+static DiceResult GetSubjectKeyIdFromId(const uint8_t id[20],
+                                        size_t buffer_size, uint8_t* buffer,
+                                        size_t* actual_size) {
+  uint8_t* pos = buffer + buffer_size;
+  int length_or_error = mbedtls_asn1_write_octet_string(&pos, buffer, id, 20);
+  if (length_or_error < 0) {
+    return kDiceResultPlatformError;
+  }
+  *actual_size = length_or_error;
+  memmove(buffer, pos, *actual_size);
+  return kDiceResultOk;
+}
+
+static int AddAuthorityKeyIdEncoding(uint8_t** pos, uint8_t* start,
+                                     int length) {
+  // From RFC 5280 4.2.1.1.
+  const int kKeyIdentifierTag = 0;
+
+  int ret = 0;  // Used by MBEDTLS_ASN1_CHK_ADD.
+  MBEDTLS_ASN1_CHK_ADD(length, mbedtls_asn1_write_len(pos, start, length));
+  MBEDTLS_ASN1_CHK_ADD(
+      length,
+      mbedtls_asn1_write_tag(
+          pos, start, MBEDTLS_ASN1_CONTEXT_SPECIFIC | kKeyIdentifierTag));
+
+  MBEDTLS_ASN1_CHK_ADD(length, mbedtls_asn1_write_len(pos, start, length));
+  MBEDTLS_ASN1_CHK_ADD(
+      length,
+      mbedtls_asn1_write_tag(pos, start,
+                             MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE));
+  return length;
+}
+
+static DiceResult GetAuthorityKeyIdFromId(const uint8_t id[20],
+                                          size_t buffer_size, uint8_t* buffer,
+                                          size_t* actual_size) {
+  uint8_t* pos = buffer + buffer_size;
+  int length_or_error = mbedtls_asn1_write_raw_buffer(&pos, buffer, id, 20);
+  if (length_or_error < 0) {
+    return kDiceResultPlatformError;
+  }
+  length_or_error = AddAuthorityKeyIdEncoding(&pos, buffer, length_or_error);
+  if (length_or_error < 0) {
+    return kDiceResultPlatformError;
+  }
+  *actual_size = length_or_error;
+  memmove(buffer, pos, *actual_size);
+  return kDiceResultOk;
+}
+
+static uint8_t GetFieldTag(uint8_t tag) {
+  return MBEDTLS_ASN1_CONTEXT_SPECIFIC | MBEDTLS_ASN1_CONSTRUCTED | tag;
+}
+
+// Can be used with MBEDTLS_ASN1_CHK_ADD.
+static int WriteExplicitOctetStringField(uint8_t tag, const uint8_t* value,
+                                         size_t value_size, uint8_t** pos,
+                                         uint8_t* start) {
+  int ret = 0;  // Used by MBEDTLS_ASN1_CHK_ADD.
+  int field_length = 0;
+  MBEDTLS_ASN1_CHK_ADD(field_length, mbedtls_asn1_write_octet_string(
+                                         pos, start, value, value_size));
+  // Explicitly tagged, so add the field tag too.
+  MBEDTLS_ASN1_CHK_ADD(field_length,
+                       mbedtls_asn1_write_len(pos, start, field_length));
+  MBEDTLS_ASN1_CHK_ADD(field_length,
+                       mbedtls_asn1_write_tag(pos, start, GetFieldTag(tag)));
+  return field_length;
+}
+
+static int GetDiceExtensionDataHelper(const DiceInputValues* input_values,
+                                      uint8_t** pos, uint8_t* start) {
+  // ASN.1 constants not defined by mbedtls.
+  const uint8_t kEnumTypeTag = 10;
+  // ASN.1 tags for extension fields.
+  const uint8_t kDiceFieldCodeHash = 0;
+  const uint8_t kDiceFieldCodeDescriptor = 1;
+  const uint8_t kDiceFieldConfigHash = 2;
+  const uint8_t kDiceFieldConfigDescriptor = 3;
+  const uint8_t kDiceFieldAuthorityHash = 4;
+  const uint8_t kDiceFieldAuthorityDescriptor = 5;
+  const uint8_t kDiceFieldMode = 6;
+
+  // Build up the extension ASN.1 in reverse order.
+  int ret = 0;  // Used by MBEDTLS_ASN1_CHK_ADD.
+  int length = 0;
+
+  // Add the mode field.
+  MBEDTLS_ASN1_CHK_ADD(length,
+                       mbedtls_asn1_write_int(pos, start, input_values->mode));
+  // Overwrite the 'int' type.
+  ++(*pos);
+  --length;
+  MBEDTLS_ASN1_CHK_ADD(length,
+                       mbedtls_asn1_write_tag(pos, start, kEnumTypeTag));
+
+  // Explicitly tagged, so add the field tag too.
+  MBEDTLS_ASN1_CHK_ADD(length, mbedtls_asn1_write_len(pos, start, length));
+  MBEDTLS_ASN1_CHK_ADD(
+      length, mbedtls_asn1_write_tag(pos, start, GetFieldTag(kDiceFieldMode)));
+
+  // Add the authorityDescriptor field, if applicable.
+  if (input_values->authority_descriptor_size > 0) {
+    MBEDTLS_ASN1_CHK_ADD(
+        length,
+        WriteExplicitOctetStringField(
+            kDiceFieldAuthorityDescriptor, input_values->authority_descriptor,
+            input_values->authority_descriptor_size, pos, start));
+  }
+
+  // Add the authorityHash field.
+  MBEDTLS_ASN1_CHK_ADD(
+      length, WriteExplicitOctetStringField(kDiceFieldAuthorityHash,
+                                            input_values->authority_hash,
+                                            DICE_HASH_SIZE, pos, start));
+
+  // Add the configurationDescriptor field (and configurationHash field, if
+  // applicable).
+  if (input_values->config_type == kDiceConfigTypeDescriptor) {
+    uint8_t hash[DICE_HASH_SIZE];
+    int result = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA512),
+                            input_values->config_descriptor,
+                            input_values->config_descriptor_size, hash);
+    if (result) {
+      return result;
+    }
+    MBEDTLS_ASN1_CHK_ADD(
+        length, WriteExplicitOctetStringField(
+                    kDiceFieldConfigDescriptor, input_values->config_descriptor,
+                    input_values->config_descriptor_size, pos, start));
+    MBEDTLS_ASN1_CHK_ADD(
+        length, WriteExplicitOctetStringField(kDiceFieldConfigHash, hash,
+                                              DICE_HASH_SIZE, pos, start));
+  } else if (input_values->config_type == kDiceConfigTypeInline) {
+    MBEDTLS_ASN1_CHK_ADD(
+        length, WriteExplicitOctetStringField(
+                    kDiceFieldConfigDescriptor, input_values->config_value,
+                    DICE_INLINE_CONFIG_SIZE, pos, start));
+  }
+
+  // Add the code descriptor field, if applicable.
+  if (input_values->code_descriptor_size > 0) {
+    MBEDTLS_ASN1_CHK_ADD(
+        length, WriteExplicitOctetStringField(
+                    kDiceFieldCodeDescriptor, input_values->code_descriptor,
+                    input_values->code_descriptor_size, pos, start));
+  }
+
+  // Add the code hash field.
+  MBEDTLS_ASN1_CHK_ADD(length, WriteExplicitOctetStringField(
+                                   kDiceFieldCodeHash, input_values->code_hash,
+                                   DICE_HASH_SIZE, pos, start));
+
+  // Add the sequence length and tag.
+  MBEDTLS_ASN1_CHK_ADD(length, mbedtls_asn1_write_len(pos, start, length));
+  MBEDTLS_ASN1_CHK_ADD(
+      length,
+      mbedtls_asn1_write_tag(pos, start,
+                             MBEDTLS_ASN1_CONSTRUCTED | MBEDTLS_ASN1_SEQUENCE));
+  return length;
+}
+
+static DiceResult GetDiceExtensionData(const DiceInputValues* input_values,
+                                       size_t buffer_size, uint8_t* buffer,
+                                       size_t* actual_size) {
+  uint8_t* pos = buffer + buffer_size;
+  int length_or_error = GetDiceExtensionDataHelper(input_values, &pos, buffer);
+  if (length_or_error == MBEDTLS_ERR_ASN1_BUF_TOO_SMALL) {
+    return kDiceResultBufferTooSmall;
+  } else if (length_or_error < 0) {
+    return kDiceResultPlatformError;
+  }
+  *actual_size = length_or_error;
+  memmove(buffer, pos, *actual_size);
+  return kDiceResultOk;
+}
+
+DiceResult DiceMbedtlsHashOp(const DiceOps* ops_not_used, const uint8_t* input,
+                             size_t input_size,
+                             uint8_t output[DICE_HASH_SIZE]) {
+  (void)ops_not_used;
+  if (0 != mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA512), input,
+                      input_size, output)) {
+    return kDiceResultPlatformError;
+  }
+  return kDiceResultOk;
+}
+
+DiceResult DiceMbedtlsKdfOp(const DiceOps* ops_not_used, size_t length,
+                            const uint8_t* ikm, size_t ikm_size,
+                            const uint8_t* salt, size_t salt_size,
+                            const uint8_t* info, size_t info_size,
+                            uint8_t* output) {
+  (void)ops_not_used;
+  if (0 != mbedtls_hkdf(mbedtls_md_info_from_type(MBEDTLS_MD_SHA512), salt,
+                        salt_size, ikm, ikm_size, info, info_size, output,
+                        length)) {
+    return kDiceResultPlatformError;
+  }
+  return kDiceResultOk;
+}
+
+DiceResult DiceMbedtlsGenerateCertificateOp(
+    const DiceOps* ops,
+    const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+    const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+    const DiceInputValues* input_values, size_t certificate_buffer_size,
+    uint8_t* certificate, size_t* certificate_actual_size) {
+  // 1.3.6.1.4.1.11129.2.1.24
+  // iso.org.dod.internet.private.enterprise.
+  //   google.googleSecurity.certificateExtensions.diceAttestationData
+  const char* kDiceExtensionOid =
+      MBEDTLS_OID_ISO_IDENTIFIED_ORG MBEDTLS_OID_ORG_DOD
+      "\x01\x04\x01\xd6\x79\x02\x01\x18";
+  const size_t kDiceExtensionOidLength = 10;
+
+  DiceResult result = kDiceResultOk;
+
+  // Initialize variables cleaned up on 'goto out'.
+  mbedtls_pk_context authority_key_context;
+  mbedtls_pk_init(&authority_key_context);
+  mbedtls_pk_context subject_key_context;
+  mbedtls_pk_init(&subject_key_context);
+  mbedtls_x509write_cert cert_context;
+  mbedtls_x509write_crt_init(&cert_context);
+  mbedtls_mpi serial_number;
+  mbedtls_mpi_init(&serial_number);
+
+  // These are 'variably modified' types so need to be declared upfront.
+  uint8_t authority_key_id[kMaxKeyIdSize];
+  uint8_t subject_key_id[kMaxKeyIdSize];
+  uint8_t dice_extension[kMaxExtensionSize];
+  uint8_t tmp_buffer[kMaxCertificateSize];
+
+  // Derive key pairs and IDs.
+  result = SetupKeyPair(authority_private_key, &authority_key_context);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  uint8_t authority_id[20];
+  result = GetIdFromKey(ops, &authority_key_context, authority_id);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  char authority_name[54];
+  GetNameFromId(authority_id, authority_name);
+
+  size_t authority_key_id_size = 0;
+  result = GetAuthorityKeyIdFromId(authority_id, sizeof(authority_key_id),
+                                   authority_key_id, &authority_key_id_size);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+  result = SetupKeyPair(subject_private_key, &subject_key_context);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  uint8_t subject_id[20];
+  result = GetIdFromKey(ops, &subject_key_context, subject_id);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  char subject_name[54];
+  GetNameFromId(subject_id, subject_name);
+
+  size_t subject_key_id_size = 0;
+  result = GetSubjectKeyIdFromId(subject_id, sizeof(subject_key_id),
+                                 subject_key_id, &subject_key_id_size);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  size_t dice_extension_size = 0;
+  result = GetDiceExtensionData(input_values, sizeof(dice_extension),
+                                dice_extension, &dice_extension_size);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+
+  // Construct the certificate.
+  mbedtls_x509write_crt_set_version(&cert_context, MBEDTLS_X509_CRT_VERSION_3);
+  if (0 !=
+      mbedtls_mpi_read_binary(&serial_number, subject_id, sizeof(subject_id))) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (0 != mbedtls_x509write_crt_set_serial(&cert_context, &serial_number)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  // '20180322235959' is the date of publication of the DICE specification. Here
+  // it's used as a somewhat arbitrary backstop. '99991231235959' is suggested
+  // by RFC 5280 in cases where expiry is not meaningful. Basically, the
+  // certificate never expires.
+  if (0 != mbedtls_x509write_crt_set_validity(&cert_context, "20180322235959",
+                                              "99991231235959")) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (0 !=
+      mbedtls_x509write_crt_set_issuer_name(&cert_context, authority_name)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (0 !=
+      mbedtls_x509write_crt_set_subject_name(&cert_context, subject_name)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  mbedtls_x509write_crt_set_subject_key(&cert_context, &subject_key_context);
+  mbedtls_x509write_crt_set_issuer_key(&cert_context, &authority_key_context);
+  mbedtls_x509write_crt_set_md_alg(&cert_context, MBEDTLS_MD_SHA512);
+  if (0 != mbedtls_x509write_crt_set_extension(
+               &cert_context, MBEDTLS_OID_AUTHORITY_KEY_IDENTIFIER,
+               MBEDTLS_OID_SIZE(MBEDTLS_OID_AUTHORITY_KEY_IDENTIFIER),
+               /*critical=*/0, authority_key_id, authority_key_id_size)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (0 != mbedtls_x509write_crt_set_extension(
+               &cert_context, MBEDTLS_OID_SUBJECT_KEY_IDENTIFIER,
+               MBEDTLS_OID_SIZE(MBEDTLS_OID_SUBJECT_KEY_IDENTIFIER),
+               /*critical=*/0, subject_key_id, subject_key_id_size)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (0 != mbedtls_x509write_crt_set_key_usage(&cert_context,
+                                               MBEDTLS_X509_KU_KEY_CERT_SIGN)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (0 != mbedtls_x509write_crt_set_basic_constraints(&cert_context,
+                                                       /*is_ca=*/1,
+                                                       /*max_pathlen=*/-1)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (0 != mbedtls_x509write_crt_set_extension(
+               &cert_context, kDiceExtensionOid, kDiceExtensionOidLength,
+               /*critical=*/0, dice_extension, dice_extension_size)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  // This implementation is deterministic and assumes entropy is not available.
+  // If this code is run where entropy is available, however, f_rng and p_rng
+  // should be set appropriately.
+  int length_or_error =
+      mbedtls_x509write_crt_der(&cert_context, tmp_buffer, sizeof(tmp_buffer),
+                                /*f_rng=*/NULL, /*p_rng=*/NULL);
+  if (length_or_error < 0) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  *certificate_actual_size = length_or_error;
+  if (*certificate_actual_size > certificate_buffer_size) {
+    result = kDiceResultBufferTooSmall;
+    goto out;
+  }
+  // The certificate has been written to the end of tmp_buffer. Skip unused
+  // buffer when copying.
+  memcpy(certificate,
+         &tmp_buffer[sizeof(tmp_buffer) - *certificate_actual_size],
+         *certificate_actual_size);
+
+out:
+  mbedtls_mpi_free(&serial_number);
+  mbedtls_x509write_crt_free(&cert_context);
+  mbedtls_pk_free(&authority_key_context);
+  mbedtls_pk_free(&subject_key_context);
+  return result;
+}
diff --git a/src/mbedtls_ops_fuzzer.cc b/src/mbedtls_ops_fuzzer.cc
new file mode 100644
index 0000000..5621aea
--- /dev/null
+++ b/src/mbedtls_ops_fuzzer.cc
@@ -0,0 +1,34 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/dice.h"
+#include "dice/fuzz_utils.h"
+#include "dice/mbedtls_ops.h"
+#include "dice/utils.h"
+
+namespace {
+
+constexpr DiceOps kOps = {
+    .context = NULL,
+    .hash = DiceMbedtlsHashOp,
+    .kdf = DiceMbedtlsKdfOp,
+    .generate_certificate = DiceMbedtlsGenerateCertificateOp,
+    .clear_memory = DiceClearMemory};
+
+}  // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  dice::fuzz::FuzzDiceMainFlow(&kOps, data, size);
+  return 0;
+}
diff --git a/src/mbedtls_ops_test.cc b/src/mbedtls_ops_test.cc
new file mode 100644
index 0000000..1b64922
--- /dev/null
+++ b/src/mbedtls_ops_test.cc
@@ -0,0 +1,251 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/mbedtls_ops.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <memory>
+
+#include "dice/dice.h"
+#include "dice/known_test_values.h"
+#include "dice/test_utils.h"
+#include "dice/utils.h"
+#include "pw_string/format.h"
+#include "pw_unit_test/framework.h"
+
+namespace {
+
+using dice::test::CertificateType_X509;
+using dice::test::DeriveFakeInputValue;
+using dice::test::DiceStateForTest;
+using dice::test::DumpState;
+using dice::test::KeyType_P256;
+
+constexpr DiceOps kOps = {
+    .context = nullptr,
+    .hash = DiceMbedtlsHashOp,
+    .kdf = DiceMbedtlsKdfOp,
+    .generate_certificate = DiceMbedtlsGenerateCertificateOp,
+    .clear_memory = DiceClearMemory};
+
+TEST(DiceOpsTest, KnownAnswerZeroInput) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  DumpState(CertificateType_X509, KeyType_P256, "zero_input", next_state);
+  // Both CDI values and the certificate should be deterministic.
+  EXPECT_EQ(0, memcmp(next_state.cdi_attest,
+                      dice::test::kExpectedCdiAttest_ZeroInput, DICE_CDI_SIZE));
+  EXPECT_EQ(0, memcmp(next_state.cdi_seal,
+                      dice::test::kExpectedCdiSeal_ZeroInput, DICE_CDI_SIZE));
+  ASSERT_EQ(sizeof(dice::test::kExpectedX509P256Cert_ZeroInput),
+            next_state.certificate_size);
+  EXPECT_EQ(0, memcmp(dice::test::kExpectedX509P256Cert_ZeroInput,
+                      next_state.certificate, next_state.certificate_size));
+}
+
+TEST(DiceOpsTest, KnownAnswerHashOnlyInput) {
+  DiceStateForTest current_state = {};
+  DeriveFakeInputValue("cdi_attest", DICE_CDI_SIZE, current_state.cdi_attest);
+  DeriveFakeInputValue("cdi_seal", DICE_CDI_SIZE, current_state.cdi_seal);
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DeriveFakeInputValue("code_hash", DICE_HASH_SIZE, input_values.code_hash);
+  DeriveFakeInputValue("authority_hash", DICE_HASH_SIZE,
+                       input_values.authority_hash);
+  input_values.config_type = kDiceConfigTypeInline;
+  DeriveFakeInputValue("inline_config", DICE_INLINE_CONFIG_SIZE,
+                       input_values.config_value);
+
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  DumpState(CertificateType_X509, KeyType_P256, "hash_only_input", next_state);
+  // Both CDI values and the certificate should be deterministic.
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_attest,
+                dice::test::kExpectedCdiAttest_HashOnlyInput, DICE_CDI_SIZE));
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_seal, dice::test::kExpectedCdiSeal_HashOnlyInput,
+                DICE_CDI_SIZE));
+  ASSERT_EQ(sizeof(dice::test::kExpectedX509P256Cert_HashOnlyInput),
+            next_state.certificate_size);
+  EXPECT_EQ(0, memcmp(dice::test::kExpectedX509P256Cert_HashOnlyInput,
+                      next_state.certificate, next_state.certificate_size));
+}
+
+TEST(DiceOpsTest, KnownAnswerDescriptorInput) {
+  DiceStateForTest current_state = {};
+  DeriveFakeInputValue("cdi_attest", DICE_CDI_SIZE, current_state.cdi_attest);
+  DeriveFakeInputValue("cdi_seal", DICE_CDI_SIZE, current_state.cdi_seal);
+
+  DiceStateForTest next_state = {};
+
+  DiceInputValues input_values = {};
+  DeriveFakeInputValue("code_hash", DICE_HASH_SIZE, input_values.code_hash);
+  uint8_t code_descriptor[100];
+  DeriveFakeInputValue("code_desc", sizeof(code_descriptor), code_descriptor);
+  input_values.code_descriptor = code_descriptor;
+  input_values.code_descriptor_size = sizeof(code_descriptor);
+
+  uint8_t config_descriptor[40];
+  DeriveFakeInputValue("config_desc", sizeof(config_descriptor),
+                       config_descriptor);
+  input_values.config_descriptor = config_descriptor;
+  input_values.config_descriptor_size = sizeof(config_descriptor);
+  input_values.config_type = kDiceConfigTypeDescriptor;
+
+  DeriveFakeInputValue("authority_hash", DICE_HASH_SIZE,
+                       input_values.authority_hash);
+  uint8_t authority_descriptor[65];
+  DeriveFakeInputValue("authority_desc", sizeof(authority_descriptor),
+                       authority_descriptor);
+  input_values.authority_descriptor = authority_descriptor;
+  input_values.authority_descriptor_size = sizeof(authority_descriptor);
+
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  DumpState(CertificateType_X509, KeyType_P256, "descriptor_input", next_state);
+  // Both CDI values and the certificate should be deterministic.
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_attest,
+                dice::test::kExpectedCdiAttest_DescriptorInput, DICE_CDI_SIZE));
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_seal,
+                dice::test::kExpectedCdiSeal_DescriptorInput, DICE_CDI_SIZE));
+  ASSERT_EQ(sizeof(dice::test::kExpectedX509P256Cert_DescriptorInput),
+            next_state.certificate_size);
+  EXPECT_EQ(0, memcmp(dice::test::kExpectedX509P256Cert_DescriptorInput,
+                      next_state.certificate, next_state.certificate_size));
+}
+
+TEST(DiceOpsTest, NonZeroMode) {
+  constexpr size_t kModeOffsetInCert = 0x266;
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  input_values.mode = kDiceModeDebug;
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  EXPECT_EQ(kDiceModeDebug, next_state.certificate[kModeOffsetInCert]);
+}
+
+TEST(DiceOpsTest, LargeInputs) {
+  constexpr uint8_t kBigBuffer[1024 * 1024] = {};
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  input_values.code_descriptor = kBigBuffer;
+  input_values.code_descriptor_size = sizeof(kBigBuffer);
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultBufferTooSmall, result);
+}
+
+TEST(DiceOpsTest, InvalidConfigType) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  input_values.config_type = (DiceConfigType)55;
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultInvalidInput, result);
+}
+
+TEST(DiceOpsTest, PartialCertChain) {
+  constexpr size_t kNumLayers = 7;
+  DiceStateForTest states[kNumLayers + 1] = {};
+  DiceInputValues inputs[kNumLayers] = {};
+  for (size_t i = 0; i < kNumLayers; ++i) {
+    char seed[40];
+    pw::string::Format(seed, "code_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].code_hash);
+    pw::string::Format(seed, "authority_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].authority_hash);
+    inputs[i].config_type = kDiceConfigTypeInline;
+    pw::string::Format(seed, "inline_config_%zu", i);
+    DeriveFakeInputValue(seed, DICE_INLINE_CONFIG_SIZE, inputs[i].config_value);
+    inputs[i].mode = kDiceModeNormal;
+    EXPECT_EQ(
+        kDiceResultOk,
+        DiceMainFlow(&kOps, states[i].cdi_attest, states[i].cdi_seal,
+                     &inputs[i], sizeof(states[i + 1].certificate),
+                     states[i + 1].certificate, &states[i + 1].certificate_size,
+                     states[i + 1].cdi_attest, states[i + 1].cdi_seal));
+    char suffix[40];
+    pw::string::Format(suffix, "part_cert_chain_%zu", i);
+    DumpState(CertificateType_X509, KeyType_P256, suffix, states[i + 1]);
+  }
+  // Use the first derived CDI cert as the 'root' of partial chain.
+  EXPECT_TRUE(dice::test::VerifyCertificateChain(
+      CertificateType_X509, states[1].certificate, states[1].certificate_size,
+      &states[2], kNumLayers - 1, /*is_partial_chain=*/true));
+}
+
+TEST(DiceOpsTest, FullCertChain) {
+  constexpr size_t kNumLayers = 7;
+  DiceStateForTest states[kNumLayers + 1] = {};
+  DiceInputValues inputs[kNumLayers] = {};
+  for (size_t i = 0; i < kNumLayers; ++i) {
+    char seed[40];
+    pw::string::Format(seed, "code_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].code_hash);
+    pw::string::Format(seed, "authority_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].authority_hash);
+    inputs[i].config_type = kDiceConfigTypeInline;
+    pw::string::Format(seed, "inline_config_%zu", i);
+    DeriveFakeInputValue(seed, DICE_INLINE_CONFIG_SIZE, inputs[i].config_value);
+    inputs[i].mode = kDiceModeNormal;
+    EXPECT_EQ(
+        kDiceResultOk,
+        DiceMainFlow(&kOps, states[i].cdi_attest, states[i].cdi_seal,
+                     &inputs[i], sizeof(states[i + 1].certificate),
+                     states[i + 1].certificate, &states[i + 1].certificate_size,
+                     states[i + 1].cdi_attest, states[i + 1].cdi_seal));
+    char suffix[40];
+    pw::string::Format(suffix, "full_cert_chain_%zu", i);
+    DumpState(CertificateType_X509, KeyType_P256, suffix, states[i + 1]);
+  }
+  // Use a fake self-signed UDS cert as the 'root'.
+  uint8_t root_certificate[dice::test::kTestCertSize];
+  size_t root_certificate_size = 0;
+  dice::test::CreateFakeUdsCertificate(
+      kOps, states[0].cdi_attest, dice::test::CertificateType_X509,
+      dice::test::KeyType_P256, root_certificate, &root_certificate_size);
+  EXPECT_TRUE(dice::test::VerifyCertificateChain(
+      CertificateType_X509, root_certificate, root_certificate_size, &states[1],
+      kNumLayers,
+      /*is_partial_chain=*/false));
+}
+}  // namespace
diff --git a/src/template_cbor_cert_op.c b/src/template_cbor_cert_op.c
new file mode 100644
index 0000000..cb66aae
--- /dev/null
+++ b/src/template_cbor_cert_op.c
@@ -0,0 +1,228 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+// If no variable length descriptors are used in a DICE certificate, the
+// certificate can be constructed from a template instead of using a CBOR
+// library. This implementation includes only hashes and inline configuration in
+// the DICE extension. For convenience this uses only the lower level curve25519
+// implementation in boringssl. This approach may be especially useful in very
+// low level components where simplicity is paramount.
+
+#include <stdint.h>
+#include <string.h>
+
+#include "dice/dice.h"
+#include "dice/utils.h"
+#include "openssl/curve25519.h"
+#include "openssl/is_boringssl.h"
+
+// A well-formed certificate, but with zeros in all fields to be filled.
+static const uint8_t kTemplate[441] = {
+    // Constant encoding.
+    0x84, 0x43, 0xa1, 0x01, 0x27, 0xa0, 0x59, 0x01, 0x6e,
+    // Offset 9: Payload starts here, 366 bytes.
+    0xa8, 0x01, 0x78, 0x28,
+    // Offset 13: CWT issuer, 40 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    // Constant encoding.
+    0x02, 0x78, 0x28,
+    // Offset 56: CWT subject, 40 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    // Constant encoding.
+    0x3a, 0x00, 0x47, 0x44, 0x50, 0x58, 0x40,
+    // Offset 103: Code hash, 64 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    // Constant encoding.
+    0x3a, 0x00, 0x47, 0x44, 0x53, 0x58, 0x40,
+    // Offset 174: Configuration value, 64 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    // Constant encoding.
+    0x3a, 0x00, 0x47, 0x44, 0x54, 0x58, 0x40,
+    // Offset 245: Authority hash, 64 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    // Constant encoding.
+    0x3a, 0x00, 0x47, 0x44, 0x56, 0x41,
+    // Offset 315: Mode, 1 byte.
+    0x00,
+    // Constant encoding.
+    0x3a, 0x00, 0x47, 0x44, 0x57, 0x58, 0x2d, 0xa5, 0x01, 0x01, 0x03, 0x27,
+    0x04, 0x81, 0x02, 0x20, 0x06, 0x21, 0x58, 0x20,
+    // Offset 336: Public key, 32 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    // Constant encoding (key usage).
+    0x3a, 0x00, 0x47, 0x44, 0x58, 0x41, 0x20,
+    // Offset 375: Payload ends here.
+    // Constant encoding.
+    0x58, 0x40,
+    // Offset 377: Signature, 64 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00};
+
+// The data to be signed is not the certificate, but the payload appended to
+// this header. This is the 'Sig_structure' for COSE_Sign1, per RFC 8152.
+static const uint8_t kTbsHeader[20] = {0x84, 0x6a, 0x53, 0x69, 0x67, 0x6e, 0x61,
+                                       0x74, 0x75, 0x72, 0x65, 0x31, 0x43, 0xa1,
+                                       0x01, 0x27, 0x40, 0x59, 0x01, 0x6e};
+// 20 bytes of header, 366 bytes of payload.
+static const size_t kTbsSize = 386;
+
+static const struct {
+  size_t offset;
+  size_t length;
+} kFieldTable[] = {{13, 40},   // Issuer
+                   {56, 40},   // Subject
+                   {103, 64},  // Code hash
+                   {174, 64},  // Config descriptor
+                   {245, 64},  // Authority hash
+                   {315, 1},   // Mode
+                   {336, 32},  // Public key
+                   {377, 64},  // Signature
+                   {9, 366}};  // Payload
+
+static const size_t kFieldIndexIssuer = 0;
+static const size_t kFieldIndexSubject = 1;
+static const size_t kFieldIndexCodeHash = 2;
+static const size_t kFieldIndexConfigDescriptor = 3;
+static const size_t kFieldIndexAuthorityHash = 4;
+static const size_t kFieldIndexMode = 5;
+static const size_t kFieldIndexSubjectPublicKey = 6;
+static const size_t kFieldIndexSignature = 7;
+static const size_t kFieldIndexPayload = 8;
+
+// |buffer| must point to the beginning of the template buffer and |src| must
+// point to at least <field-length> bytes.
+static void CopyField(const uint8_t* src, size_t index, uint8_t* buffer) {
+  memcpy(&buffer[kFieldTable[index].offset], src, kFieldTable[index].length);
+}
+
+DiceResult DiceGenerateCborCertificateFromTemplateOp(
+    const DiceOps* ops,
+    const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+    const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+    const DiceInputValues* input_values, size_t certificate_buffer_size,
+    uint8_t* certificate, size_t* certificate_actual_size) {
+  DiceResult result = kDiceResultOk;
+
+  // Variable length descriptors are not supported.
+  if (input_values->code_descriptor_size > 0 ||
+      input_values->config_type != kDiceConfigTypeInline ||
+      input_values->authority_descriptor_size > 0) {
+    return kDiceResultInvalidInput;
+  }
+
+  // We know the certificate size upfront so we can do the buffer size check.
+  *certificate_actual_size = sizeof(kTemplate);
+  if (certificate_buffer_size < sizeof(kTemplate)) {
+    return kDiceResultBufferTooSmall;
+  }
+
+  // Declare buffers which are cleared on 'goto out'.
+  uint8_t subject_bssl_private_key[64];
+  uint8_t authority_bssl_private_key[64];
+
+  // These are 'variably modified' types so need to be declared upfront.
+  uint8_t tbs[kTbsSize];
+
+  // Derive public keys and IDs from the private keys. Note: the Boringssl
+  // implementation refers to the raw private key as a seed.
+  uint8_t subject_public_key[32];
+  ED25519_keypair_from_seed(subject_public_key, subject_bssl_private_key,
+                            subject_private_key);
+
+  uint8_t subject_id[20];
+  result = DiceDeriveCdiCertificateId(ops, subject_public_key, 32, subject_id);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+  uint8_t subject_id_hex[40];
+  DiceHexEncode(subject_id, sizeof(subject_id), subject_id_hex,
+                sizeof(subject_id_hex));
+
+  uint8_t authority_public_key[32];
+  ED25519_keypair_from_seed(authority_public_key, authority_bssl_private_key,
+                            authority_private_key);
+
+  uint8_t authority_id[20];
+  result =
+      DiceDeriveCdiCertificateId(ops, authority_public_key, 32, authority_id);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+  uint8_t authority_id_hex[40];
+  DiceHexEncode(authority_id, sizeof(authority_id), authority_id_hex,
+                sizeof(authority_id_hex));
+
+  // First copy in the entire template, then fill in the fields.
+  memcpy(certificate, kTemplate, sizeof(kTemplate));
+  CopyField(authority_id_hex, kFieldIndexIssuer, certificate);
+  CopyField(subject_id_hex, kFieldIndexSubject, certificate);
+  CopyField(subject_public_key, kFieldIndexSubjectPublicKey, certificate);
+  CopyField(input_values->code_hash, kFieldIndexCodeHash, certificate);
+  CopyField(input_values->config_value, kFieldIndexConfigDescriptor,
+            certificate);
+  CopyField(input_values->authority_hash, kFieldIndexAuthorityHash,
+            certificate);
+  certificate[kFieldTable[kFieldIndexMode].offset] = input_values->mode;
+
+  // Fill the TBS structure using the payload from the certificate.
+  memcpy(tbs, kTbsHeader, sizeof(kTbsHeader));
+  memcpy(&tbs[sizeof(kTbsHeader)],
+         &certificate[kFieldTable[kFieldIndexPayload].offset],
+         kFieldTable[kFieldIndexPayload].length);
+
+  uint8_t signature[64];
+  if (1 != ED25519_sign(signature, tbs, kTbsSize, authority_bssl_private_key)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (1 != ED25519_verify(tbs, kTbsSize, signature, authority_public_key)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  CopyField(signature, kFieldIndexSignature, certificate);
+
+out:
+  ops->clear_memory(ops, sizeof(subject_bssl_private_key),
+                    subject_bssl_private_key);
+  ops->clear_memory(ops, sizeof(authority_bssl_private_key),
+                    authority_bssl_private_key);
+  return result;
+}
diff --git a/src/template_cbor_cert_op_fuzzer.cc b/src/template_cbor_cert_op_fuzzer.cc
new file mode 100644
index 0000000..a3913d6
--- /dev/null
+++ b/src/template_cbor_cert_op_fuzzer.cc
@@ -0,0 +1,35 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/boringssl_ops.h"
+#include "dice/dice.h"
+#include "dice/fuzz_utils.h"
+#include "dice/template_cbor_cert_op.h"
+#include "dice/utils.h"
+
+namespace {
+
+constexpr DiceOps kOps = {
+    .context = NULL,
+    .hash = DiceBsslHashOp,
+    .kdf = DiceBsslKdfOp,
+    .generate_certificate = DiceGenerateCborCertificateFromTemplateOp,
+    .clear_memory = DiceClearMemory};
+
+}  // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  dice::fuzz::FuzzDiceMainFlow(&kOps, data, size);
+  return 0;
+}
diff --git a/src/template_cbor_cert_op_test.cc b/src/template_cbor_cert_op_test.cc
new file mode 100644
index 0000000..5626224
--- /dev/null
+++ b/src/template_cbor_cert_op_test.cc
@@ -0,0 +1,241 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/template_cbor_cert_op.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <memory>
+
+#include "dice/boringssl_ops.h"
+#include "dice/dice.h"
+#include "dice/known_test_values.h"
+#include "dice/test_utils.h"
+#include "dice/utils.h"
+#include "pw_string/format.h"
+#include "pw_unit_test/framework.h"
+
+namespace {
+
+using dice::test::CertificateType_Cbor;
+using dice::test::DeriveFakeInputValue;
+using dice::test::DiceStateForTest;
+using dice::test::KeyType_Ed25519;
+
+constexpr DiceOps kOps = {
+    .context = NULL,
+    .hash = DiceBsslHashOp,
+    .kdf = DiceBsslKdfOp,
+    .generate_certificate = DiceGenerateCborCertificateFromTemplateOp,
+    .clear_memory = DiceClearMemory};
+
+TEST(DiceOpsTest, KnownAnswerZeroInput) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  // Both CDI values and the certificate should be deterministic.
+  EXPECT_EQ(0, memcmp(next_state.cdi_attest,
+                      dice::test::kExpectedCdiAttest_ZeroInput, DICE_CDI_SIZE));
+  EXPECT_EQ(0, memcmp(next_state.cdi_seal,
+                      dice::test::kExpectedCdiSeal_ZeroInput, DICE_CDI_SIZE));
+  ASSERT_EQ(sizeof(dice::test::kExpectedCborEd25519Cert_ZeroInput),
+            next_state.certificate_size);
+  EXPECT_EQ(0, memcmp(dice::test::kExpectedCborEd25519Cert_ZeroInput,
+                      next_state.certificate, next_state.certificate_size));
+}
+
+TEST(DiceOpsTest, KnownAnswerHashOnlyInput) {
+  DiceStateForTest current_state = {};
+  DeriveFakeInputValue("cdi_attest", DICE_CDI_SIZE, current_state.cdi_attest);
+  DeriveFakeInputValue("cdi_seal", DICE_CDI_SIZE, current_state.cdi_seal);
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DeriveFakeInputValue("code_hash", DICE_HASH_SIZE, input_values.code_hash);
+  DeriveFakeInputValue("authority_hash", DICE_HASH_SIZE,
+                       input_values.authority_hash);
+  input_values.config_type = kDiceConfigTypeInline;
+  DeriveFakeInputValue("inline_config", DICE_INLINE_CONFIG_SIZE,
+                       input_values.config_value);
+
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  // Both CDI values and the certificate should be deterministic.
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_attest,
+                dice::test::kExpectedCdiAttest_HashOnlyInput, DICE_CDI_SIZE));
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_seal, dice::test::kExpectedCdiSeal_HashOnlyInput,
+                DICE_CDI_SIZE));
+  ASSERT_EQ(sizeof(dice::test::kExpectedCborEd25519Cert_HashOnlyInput),
+            next_state.certificate_size);
+  EXPECT_EQ(0, memcmp(dice::test::kExpectedCborEd25519Cert_HashOnlyInput,
+                      next_state.certificate, next_state.certificate_size));
+}
+
+TEST(DiceOpsTest, WithCodeDescriptor) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  uint8_t descriptor[] = {0, 1, 2, 3};
+  input_values.code_descriptor = descriptor;
+  input_values.code_descriptor_size = sizeof(descriptor);
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultInvalidInput, result);
+}
+
+TEST(DiceOpsTest, WithConfigDescriptor) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  uint8_t descriptor[] = {0, 1, 2, 3};
+  input_values.config_descriptor = descriptor;
+  input_values.config_descriptor_size = sizeof(descriptor);
+  input_values.config_type = kDiceConfigTypeDescriptor;
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultInvalidInput, result);
+}
+
+TEST(DiceOpsTest, WithAuthorityDescriptor) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  uint8_t descriptor[] = {0, 1, 2, 3};
+  input_values.authority_descriptor = descriptor;
+  input_values.authority_descriptor_size = sizeof(descriptor);
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultInvalidInput, result);
+}
+
+TEST(DiceOpsTest, NonZeroMode) {
+  constexpr size_t kModeOffsetInCert = 315;
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  input_values.mode = kDiceModeDebug;
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  EXPECT_EQ(kDiceModeDebug, next_state.certificate[kModeOffsetInCert]);
+}
+
+TEST(DiceOpsTest, SmallCertBuffer) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      12 /*too small*/, next_state.certificate, &next_state.certificate_size,
+      next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultBufferTooSmall, result);
+  EXPECT_EQ(sizeof(dice::test::kExpectedCborEd25519Cert_ZeroInput),
+            next_state.certificate_size);
+}
+
+TEST(DiceOpsTest, InvalidConfigType) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  input_values.config_type = (DiceConfigType)55;
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultInvalidInput, result);
+}
+
+TEST(DiceOpsTest, PartialCertChain) {
+  constexpr size_t kNumLayers = 7;
+  DiceStateForTest states[kNumLayers + 1] = {};
+  DiceInputValues inputs[kNumLayers] = {};
+  for (size_t i = 0; i < kNumLayers; ++i) {
+    char seed[40];
+    pw::string::Format(seed, "code_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].code_hash);
+    pw::string::Format(seed, "authority_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].authority_hash);
+    inputs[i].config_type = kDiceConfigTypeInline;
+    pw::string::Format(seed, "inline_config_%zu", i);
+    DeriveFakeInputValue(seed, DICE_INLINE_CONFIG_SIZE, inputs[i].config_value);
+    inputs[i].mode = kDiceModeNormal;
+    EXPECT_EQ(
+        kDiceResultOk,
+        DiceMainFlow(&kOps, states[i].cdi_attest, states[i].cdi_seal,
+                     &inputs[i], sizeof(states[i + 1].certificate),
+                     states[i + 1].certificate, &states[i + 1].certificate_size,
+                     states[i + 1].cdi_attest, states[i + 1].cdi_seal));
+    char suffix[40];
+    pw::string::Format(suffix, "part_cert_chain_%zu", i);
+  }
+  // Use the first derived CDI cert as the 'root' of partial chain.
+  EXPECT_TRUE(dice::test::VerifyCertificateChain(
+      CertificateType_Cbor, states[1].certificate, states[1].certificate_size,
+      &states[2], kNumLayers - 1, /*is_partial_chain=*/true));
+}
+
+TEST(DiceOpsTest, FullCertChain) {
+  constexpr size_t kNumLayers = 7;
+  DiceStateForTest states[kNumLayers + 1] = {};
+  DiceInputValues inputs[kNumLayers] = {};
+  for (size_t i = 0; i < kNumLayers; ++i) {
+    char seed[40];
+    pw::string::Format(seed, "code_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].code_hash);
+    pw::string::Format(seed, "authority_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].authority_hash);
+    inputs[i].config_type = kDiceConfigTypeInline;
+    pw::string::Format(seed, "inline_config_%zu", i);
+    DeriveFakeInputValue(seed, DICE_INLINE_CONFIG_SIZE, inputs[i].config_value);
+    inputs[i].mode = kDiceModeNormal;
+    EXPECT_EQ(
+        kDiceResultOk,
+        DiceMainFlow(&kOps, states[i].cdi_attest, states[i].cdi_seal,
+                     &inputs[i], sizeof(states[i + 1].certificate),
+                     states[i + 1].certificate, &states[i + 1].certificate_size,
+                     states[i + 1].cdi_attest, states[i + 1].cdi_seal));
+    char suffix[40];
+    pw::string::Format(suffix, "full_cert_chain_%zu", i);
+  }
+  // Use a fake self-signed UDS cert as the 'root'.
+  uint8_t root_certificate[dice::test::kTestCertSize];
+  size_t root_certificate_size = 0;
+  dice::test::CreateFakeUdsCertificate(
+      kOps, states[0].cdi_attest, CertificateType_Cbor, KeyType_Ed25519,
+      root_certificate, &root_certificate_size);
+  EXPECT_TRUE(dice::test::VerifyCertificateChain(
+      CertificateType_Cbor, root_certificate, root_certificate_size, &states[1],
+      kNumLayers, /*is_partial_chain=*/false));
+}
+
+}  // namespace
diff --git a/src/template_cert_op.c b/src/template_cert_op.c
new file mode 100644
index 0000000..5ea0cdd
--- /dev/null
+++ b/src/template_cert_op.c
@@ -0,0 +1,243 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/template_cert_op.h"
+
+#include <stdint.h>
+#include <string.h>
+
+#include "dice/dice.h"
+#include "dice/utils.h"
+#include "openssl/curve25519.h"
+#include "openssl/is_boringssl.h"
+
+// A well-formed certificate, but with zeros in all fields to be filled.
+static const uint8_t kTemplate[635] = {
+    // Constant encoding.
+    0x30, 0x82, 0x02, 0x77,
+    // Offset 4: TBS starts here.
+    // Constant encoding.
+    0x30, 0x82, 0x02, 0x29, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x14,
+    // Offset 15: Serial number, 20 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    // Constant encoding.
+    0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x30, 0x33, 0x31, 0x31, 0x30,
+    0x2f, 0x06, 0x03, 0x55, 0x04, 0x05, 0x13, 0x28,
+    // Offset 55: Issuer name, 40 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    // Constant encoding.
+    0x30, 0x20, 0x17, 0x0d, 0x31, 0x38, 0x30, 0x33, 0x32, 0x32, 0x32, 0x33,
+    0x35, 0x39, 0x35, 0x39, 0x5a, 0x18, 0x0f, 0x39, 0x39, 0x39, 0x39, 0x31,
+    0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, 0x30, 0x33,
+    0x31, 0x31, 0x30, 0x2f, 0x06, 0x03, 0x55, 0x04, 0x05, 0x13, 0x28,
+    // Offset 142: Subject name, 40 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    // Constant encoding.
+    0x30, 0x2a, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x21, 0x00,
+    // Offset 194: Subject public key, 32 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    // Constant encoding.
+    0xa3, 0x82, 0x01, 0x4b, 0x30, 0x82, 0x01, 0x47, 0x30, 0x1f, 0x06, 0x03,
+    0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14,
+    // Offset 247: Authority key identifier, 20 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    // Constant encoding.
+    0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14,
+    // Offset 278: Subject key identifier, 20 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    // Constant encoding.
+    0x30, 0x0e, 0x06, 0x03, 0x55, 0x1d, 0x0f, 0x01, 0x01, 0xff, 0x04, 0x04,
+    0x03, 0x02, 0x02, 0x04, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01,
+    0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0x01, 0x30, 0x81, 0xe3,
+    0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xd6, 0x79, 0x02, 0x01, 0x18,
+    0x04, 0x81, 0xd4, 0x30, 0x81, 0xd1, 0xa0, 0x42, 0x04, 0x40,
+    // Offset 356: Code hash, 64 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    // Constant encoding.
+    0xa3, 0x42, 0x04, 0x40,
+    // Offset 424: Configuration value, 64 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    // Constant encoding.
+    0xa4, 0x42, 0x04, 0x40,
+    // Offset 492: Authority hash, 64 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00,
+    // Constant encoding.
+    0xa6, 0x03, 0x0a, 0x01,
+    // Offset 560: Mode, 1 byte.
+    0x00,
+    // Offset 561: TBS ends here.
+    // Constant encoding.
+    0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x03, 0x41, 0x00,
+    // Offset 571: Signature, 64 bytes.
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00};
+
+static const struct {
+  size_t offset;
+  size_t length;
+} kFieldTable[] = {{15, 20},   // Serial number
+                   {55, 40},   // Issuer name
+                   {142, 40},  // Subject name
+                   {194, 32},  // Subject public key
+                   {247, 20},  // Authority key id
+                   {278, 20},  // Subject key id
+                   {356, 64},  // Code hash
+                   {424, 64},  // Config descriptor
+                   {492, 64},  // Authority hash
+                   {560, 1},   // Mode
+                   {571, 64},  // Signature
+                   {4, 557}};  // Entire TBS
+
+static const size_t kFieldIndexSerial = 0;
+static const size_t kFieldIndexIssuerName = 1;
+static const size_t kFieldIndexSubjectName = 2;
+static const size_t kFieldIndexSubjectPublicKey = 3;
+static const size_t kFieldIndexAuthorityKeyId = 4;
+static const size_t kFieldIndexSubjectKeyId = 5;
+static const size_t kFieldIndexCodeHash = 6;
+static const size_t kFieldIndexConfigDescriptor = 7;
+static const size_t kFieldIndexAuthorityHash = 8;
+static const size_t kFieldIndexMode = 9;
+static const size_t kFieldIndexSignature = 10;
+static const size_t kFieldIndexTbs = 11;
+
+// |buffer| must point to the beginning of the template buffer and |src| must
+// point to at least <field-length> bytes.
+static void CopyField(const uint8_t* src, size_t index, uint8_t* buffer) {
+  memcpy(&buffer[kFieldTable[index].offset], src, kFieldTable[index].length);
+}
+
+DiceResult DiceGenerateCertificateFromTemplateOp(
+    const DiceOps* ops,
+    const uint8_t subject_private_key[DICE_PRIVATE_KEY_SIZE],
+    const uint8_t authority_private_key[DICE_PRIVATE_KEY_SIZE],
+    const DiceInputValues* input_values, size_t certificate_buffer_size,
+    uint8_t* certificate, size_t* certificate_actual_size) {
+  DiceResult result = kDiceResultOk;
+
+  // Variable length descriptors are not supported.
+  if (input_values->code_descriptor_size > 0 ||
+      input_values->config_type != kDiceConfigTypeInline ||
+      input_values->authority_descriptor_size > 0) {
+    return kDiceResultInvalidInput;
+  }
+
+  // We know the certificate size upfront so we can do the buffer size check.
+  *certificate_actual_size = sizeof(kTemplate);
+  if (certificate_buffer_size < sizeof(kTemplate)) {
+    return kDiceResultBufferTooSmall;
+  }
+
+  // Declare buffers which are cleared on 'goto out'.
+  uint8_t subject_bssl_private_key[64];
+  uint8_t authority_bssl_private_key[64];
+
+  // Derive public keys and IDs from the private keys. Note: the Boringssl
+  // implementation refers to the raw private key as a seed.
+  uint8_t subject_public_key[32];
+  ED25519_keypair_from_seed(subject_public_key, subject_bssl_private_key,
+                            subject_private_key);
+
+  uint8_t subject_id[20];
+  result = DiceDeriveCdiCertificateId(ops, subject_public_key, 32, subject_id);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+  uint8_t subject_id_hex[40];
+  DiceHexEncode(subject_id, sizeof(subject_id), subject_id_hex,
+                sizeof(subject_id_hex));
+
+  uint8_t authority_public_key[32];
+  ED25519_keypair_from_seed(authority_public_key, authority_bssl_private_key,
+                            authority_private_key);
+
+  uint8_t authority_id[20];
+  result =
+      DiceDeriveCdiCertificateId(ops, authority_public_key, 32, authority_id);
+  if (result != kDiceResultOk) {
+    goto out;
+  }
+  uint8_t authority_id_hex[40];
+  DiceHexEncode(authority_id, sizeof(authority_id), authority_id_hex,
+                sizeof(authority_id_hex));
+
+  // First copy in the entire template, then fill in the fields.
+  memcpy(certificate, kTemplate, sizeof(kTemplate));
+  CopyField(subject_id, kFieldIndexSerial, certificate);
+  CopyField(authority_id_hex, kFieldIndexIssuerName, certificate);
+  CopyField(subject_id_hex, kFieldIndexSubjectName, certificate);
+  CopyField(subject_public_key, kFieldIndexSubjectPublicKey, certificate);
+  CopyField(authority_id, kFieldIndexAuthorityKeyId, certificate);
+  CopyField(subject_id, kFieldIndexSubjectKeyId, certificate);
+  CopyField(input_values->code_hash, kFieldIndexCodeHash, certificate);
+  CopyField(input_values->config_value, kFieldIndexConfigDescriptor,
+            certificate);
+  CopyField(input_values->authority_hash, kFieldIndexAuthorityHash,
+            certificate);
+  certificate[kFieldTable[kFieldIndexMode].offset] = input_values->mode;
+
+  // All the TBS fields are filled in, we're ready to sign.
+  uint8_t signature[64];
+  if (1 != ED25519_sign(signature,
+                        &certificate[kFieldTable[kFieldIndexTbs].offset],
+                        kFieldTable[kFieldIndexTbs].length,
+                        authority_bssl_private_key)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  if (1 != ED25519_verify(&certificate[kFieldTable[kFieldIndexTbs].offset],
+                          kFieldTable[kFieldIndexTbs].length, signature,
+                          authority_public_key)) {
+    result = kDiceResultPlatformError;
+    goto out;
+  }
+  CopyField(signature, kFieldIndexSignature, certificate);
+
+out:
+  ops->clear_memory(ops, sizeof(subject_bssl_private_key),
+                    subject_bssl_private_key);
+  ops->clear_memory(ops, sizeof(authority_bssl_private_key),
+                    authority_bssl_private_key);
+  return result;
+}
diff --git a/src/template_cert_op_fuzzer.cc b/src/template_cert_op_fuzzer.cc
new file mode 100644
index 0000000..9c6a6ad
--- /dev/null
+++ b/src/template_cert_op_fuzzer.cc
@@ -0,0 +1,35 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/boringssl_ops.h"
+#include "dice/dice.h"
+#include "dice/fuzz_utils.h"
+#include "dice/template_cert_op.h"
+#include "dice/utils.h"
+
+namespace {
+
+constexpr DiceOps kOps = {
+    .context = NULL,
+    .hash = DiceBsslHashOp,
+    .kdf = DiceBsslKdfOp,
+    .generate_certificate = DiceGenerateCertificateFromTemplateOp,
+    .clear_memory = DiceClearMemory};
+
+}  // namespace
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+  dice::fuzz::FuzzDiceMainFlow(&kOps, data, size);
+  return 0;
+}
diff --git a/src/template_cert_op_test.cc b/src/template_cert_op_test.cc
new file mode 100644
index 0000000..8410438
--- /dev/null
+++ b/src/template_cert_op_test.cc
@@ -0,0 +1,236 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/template_cert_op.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <memory>
+
+#include "dice/boringssl_ops.h"
+#include "dice/dice.h"
+#include "dice/known_test_values.h"
+#include "dice/test_utils.h"
+#include "dice/utils.h"
+#include "pw_string/format.h"
+#include "pw_unit_test/framework.h"
+
+namespace {
+
+using dice::test::CertificateType_X509;
+using dice::test::DeriveFakeInputValue;
+using dice::test::DiceStateForTest;
+using dice::test::KeyType_Ed25519;
+
+constexpr DiceOps kOps = {
+    .context = NULL,
+    .hash = DiceBsslHashOp,
+    .kdf = DiceBsslKdfOp,
+    .generate_certificate = DiceGenerateCertificateFromTemplateOp,
+    .clear_memory = DiceClearMemory};
+
+TEST(DiceOpsTest, KnownAnswerZeroInput) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  EXPECT_EQ(0, memcmp(next_state.cdi_attest,
+                      dice::test::kExpectedCdiAttest_ZeroInput, DICE_CDI_SIZE));
+  EXPECT_EQ(0, memcmp(next_state.cdi_seal,
+                      dice::test::kExpectedCdiSeal_ZeroInput, DICE_CDI_SIZE));
+  ASSERT_EQ(sizeof(dice::test::kExpectedX509Ed25519Cert_ZeroInput),
+            next_state.certificate_size);
+  EXPECT_EQ(0, memcmp(dice::test::kExpectedX509Ed25519Cert_ZeroInput,
+                      next_state.certificate, next_state.certificate_size));
+}
+
+TEST(DiceOpsTest, KnownAnswerHashOnlyInput) {
+  DiceStateForTest current_state = {};
+  DeriveFakeInputValue("cdi_attest", DICE_CDI_SIZE, current_state.cdi_attest);
+  DeriveFakeInputValue("cdi_seal", DICE_CDI_SIZE, current_state.cdi_seal);
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DeriveFakeInputValue("code_hash", DICE_HASH_SIZE, input_values.code_hash);
+  DeriveFakeInputValue("authority_hash", DICE_HASH_SIZE,
+                       input_values.authority_hash);
+  input_values.config_type = kDiceConfigTypeInline;
+  DeriveFakeInputValue("inline_config", DICE_INLINE_CONFIG_SIZE,
+                       input_values.config_value);
+
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_attest,
+                dice::test::kExpectedCdiAttest_HashOnlyInput, DICE_CDI_SIZE));
+  EXPECT_EQ(
+      0, memcmp(next_state.cdi_seal, dice::test::kExpectedCdiSeal_HashOnlyInput,
+                DICE_CDI_SIZE));
+  ASSERT_EQ(sizeof(dice::test::kExpectedX509Ed25519Cert_HashOnlyInput),
+            next_state.certificate_size);
+  EXPECT_EQ(0, memcmp(dice::test::kExpectedX509Ed25519Cert_HashOnlyInput,
+                      next_state.certificate, next_state.certificate_size));
+}
+
+TEST(DiceOpsTest, WithCodeDescriptor) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  uint8_t descriptor[] = {0, 1, 2, 3};
+  input_values.code_descriptor = descriptor;
+  input_values.code_descriptor_size = sizeof(descriptor);
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultInvalidInput, result);
+}
+
+TEST(DiceOpsTest, WithConfigDescriptor) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  uint8_t descriptor[] = {0, 1, 2, 3};
+  input_values.config_descriptor = descriptor;
+  input_values.config_descriptor_size = sizeof(descriptor);
+  input_values.config_type = kDiceConfigTypeDescriptor;
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultInvalidInput, result);
+}
+
+TEST(DiceOpsTest, WithAuthorityDescriptor) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  uint8_t descriptor[] = {0, 1, 2, 3};
+  input_values.authority_descriptor = descriptor;
+  input_values.authority_descriptor_size = sizeof(descriptor);
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultInvalidInput, result);
+}
+
+TEST(DiceOpsTest, NonZeroMode) {
+  constexpr size_t kModeOffsetInCert = 0x230;
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  input_values.mode = kDiceModeDebug;
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultOk, result);
+  EXPECT_EQ(kDiceModeDebug, next_state.certificate[kModeOffsetInCert]);
+}
+
+TEST(DiceOpsTest, SmallCertBuffer) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      12 /* too small */, next_state.certificate, &next_state.certificate_size,
+      next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultBufferTooSmall, result);
+  EXPECT_EQ(sizeof(dice::test::kExpectedX509Ed25519Cert_ZeroInput),
+            next_state.certificate_size);
+}
+
+TEST(DiceOpsTest, InvalidConfigType) {
+  DiceStateForTest current_state = {};
+  DiceStateForTest next_state = {};
+  DiceInputValues input_values = {};
+  input_values.config_type = (DiceConfigType)55;
+  DiceResult result = DiceMainFlow(
+      &kOps, current_state.cdi_attest, current_state.cdi_seal, &input_values,
+      sizeof(next_state.certificate), next_state.certificate,
+      &next_state.certificate_size, next_state.cdi_attest, next_state.cdi_seal);
+  EXPECT_EQ(kDiceResultInvalidInput, result);
+}
+
+TEST(DiceOpsTest, PartialCertChain) {
+  constexpr size_t kNumLayers = 7;
+  DiceStateForTest states[kNumLayers + 1] = {};
+  DiceInputValues inputs[kNumLayers] = {};
+  for (size_t i = 0; i < kNumLayers; ++i) {
+    char seed[40];
+    pw::string::Format(seed, "code_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].code_hash);
+    pw::string::Format(seed, "authority_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].authority_hash);
+    inputs[i].config_type = kDiceConfigTypeInline;
+    pw::string::Format(seed, "inline_config_%zu", i);
+    DeriveFakeInputValue(seed, DICE_INLINE_CONFIG_SIZE, inputs[i].config_value);
+    inputs[i].mode = kDiceModeNormal;
+    EXPECT_EQ(
+        kDiceResultOk,
+        DiceMainFlow(&kOps, states[i].cdi_attest, states[i].cdi_seal,
+                     &inputs[i], sizeof(states[i + 1].certificate),
+                     states[i + 1].certificate, &states[i + 1].certificate_size,
+                     states[i + 1].cdi_attest, states[i + 1].cdi_seal));
+  }
+  // Use the first derived CDI cert as the 'root' of partial chain.
+  EXPECT_TRUE(dice::test::VerifyCertificateChain(
+      CertificateType_X509, states[1].certificate, states[1].certificate_size,
+      &states[2], kNumLayers - 1, /*is_partial_chain=*/true));
+}
+
+TEST(DiceOpsTest, FullCertChain) {
+  constexpr size_t kNumLayers = 7;
+  DiceStateForTest states[kNumLayers + 1] = {};
+  DiceInputValues inputs[kNumLayers] = {};
+  for (size_t i = 0; i < kNumLayers; ++i) {
+    char seed[40];
+    pw::string::Format(seed, "code_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].code_hash);
+    pw::string::Format(seed, "authority_hash_%zu", i);
+    DeriveFakeInputValue(seed, DICE_HASH_SIZE, inputs[i].authority_hash);
+    inputs[i].config_type = kDiceConfigTypeInline;
+    pw::string::Format(seed, "inline_config_%zu", i);
+    DeriveFakeInputValue(seed, DICE_INLINE_CONFIG_SIZE, inputs[i].config_value);
+    inputs[i].mode = kDiceModeNormal;
+    EXPECT_EQ(
+        kDiceResultOk,
+        DiceMainFlow(&kOps, states[i].cdi_attest, states[i].cdi_seal,
+                     &inputs[i], sizeof(states[i + 1].certificate),
+                     states[i + 1].certificate, &states[i + 1].certificate_size,
+                     states[i + 1].cdi_attest, states[i + 1].cdi_seal));
+  }
+  // Use a fake self-signed UDS cert as the 'root'.
+  uint8_t root_certificate[dice::test::kTestCertSize];
+  size_t root_certificate_size = 0;
+  dice::test::CreateFakeUdsCertificate(
+      kOps, states[0].cdi_attest, CertificateType_X509, KeyType_Ed25519,
+      root_certificate, &root_certificate_size);
+  EXPECT_TRUE(dice::test::VerifyCertificateChain(
+      CertificateType_X509, root_certificate, root_certificate_size, &states[1],
+      kNumLayers,
+      /*is_partial_chain=*/false));
+}
+
+}  // namespace
diff --git a/src/test_utils.cc b/src/test_utils.cc
new file mode 100644
index 0000000..373c0fd
--- /dev/null
+++ b/src/test_utils.cc
@@ -0,0 +1,662 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/test_utils.h"
+
+#include <stddef.h>
+#include <stdint.h>
+#include <string.h>
+
+#include <memory>
+
+#include "cose/cose.h"
+#include "dice/dice.h"
+#include "dice/utils.h"
+#include "openssl/asn1.h"
+#include "openssl/bn.h"
+#include "openssl/curve25519.h"
+#include "openssl/evp.h"
+#include "openssl/hmac.h"
+#include "openssl/is_boringssl.h"
+#include "openssl/mem.h"
+#include "openssl/sha.h"
+#include "openssl/x509.h"
+#include "openssl/x509_vfy.h"
+#include "openssl/x509v3.h"
+#include "pw_string/format.h"
+
+namespace {
+
+// A scoped pointer for cn_cbor.
+struct CborDeleter {
+  void operator()(cn_cbor* c) { cn_cbor_free(c); }
+};
+using ScopedCbor = std::unique_ptr<cn_cbor, CborDeleter>;
+
+const char* GetCertTypeStr(dice::test::CertificateType cert_type) {
+  switch (cert_type) {
+    case dice::test::CertificateType_Cbor:
+      return "CBOR";
+    case dice::test::CertificateType_X509:
+      return "X509";
+  }
+  return "";
+}
+
+const char* GetKeyTypeStr(dice::test::KeyType key_type) {
+  switch (key_type) {
+    case dice::test::KeyType_Ed25519:
+      return "Ed25519";
+    case dice::test::KeyType_P256:
+      return "P256";
+  }
+  return "";
+}
+
+bssl::UniquePtr<X509> ParseX509Certificate(const uint8_t* certificate,
+                                           size_t certificate_size) {
+  const uint8_t* asn1 = certificate;
+  return bssl::UniquePtr<X509>(
+      d2i_X509(/*X509=*/nullptr, &asn1, certificate_size));
+}
+
+void DumpToFile(const char* filename, const uint8_t* data, size_t size) {
+  FILE* fp = fopen(filename, "w");
+  if (fp) {
+    fwrite(data, size, 1, fp);
+    fclose(fp);
+  } else {
+    printf("WARNING: Failed to dump to file.\n");
+  }
+}
+
+// A simple hmac-drbg to help with deterministic ecdsa.
+class HmacSha512Drbg {
+ public:
+  HmacSha512Drbg(const uint8_t seed[32]) {
+    Init();
+    Update(seed, 32);
+  }
+  ~HmacSha512Drbg() { HMAC_CTX_cleanup(&ctx_); }
+
+  // Populates |num_bytes| random bytes into |buffer|.
+  void GetBytes(size_t num_bytes, uint8_t* buffer) {
+    size_t bytes_written = 0;
+    while (bytes_written < num_bytes) {
+      size_t bytes_to_copy = num_bytes - bytes_written;
+      if (bytes_to_copy > 64) {
+        bytes_to_copy = 64;
+      }
+      Hmac(v_, v_);
+      memcpy(&buffer[bytes_written], v_, bytes_to_copy);
+      bytes_written += bytes_to_copy;
+    }
+    Update0();
+  }
+
+ private:
+  void Init() {
+    memset(k_, 0, 64);
+    memset(v_, 1, 64);
+    HMAC_CTX_init(&ctx_);
+  }
+
+  void Hmac(uint8_t in[64], uint8_t out[64]) {
+    HmacStart();
+    HmacUpdate(in, 64);
+    HmacFinish(out);
+  }
+
+  void HmacStart() {
+    HMAC_Init_ex(&ctx_, k_, 64, EVP_sha512(), nullptr /* impl */);
+  }
+
+  void HmacUpdate(const uint8_t* data, size_t data_size) {
+    HMAC_Update(&ctx_, data, data_size);
+  }
+
+  void HmacUpdateByte(uint8_t byte) { HmacUpdate(&byte, 1); }
+
+  void HmacFinish(uint8_t out[64]) {
+    unsigned int out_len = 64;
+    HMAC_Final(&ctx_, out, &out_len);
+  }
+
+  void Update(const uint8_t* data, size_t data_size) {
+    HmacStart();
+    HmacUpdate(v_, 64);
+    HmacUpdateByte(0x00);
+    if (data_size > 0) {
+      HmacUpdate(data, data_size);
+    }
+    HmacFinish(k_);
+    Hmac(v_, v_);
+    if (data_size > 0) {
+      HmacStart();
+      HmacUpdate(v_, 64);
+      HmacUpdateByte(0x01);
+      HmacUpdate(data, data_size);
+      HmacFinish(k_);
+      Hmac(v_, v_);
+    }
+  }
+
+  void Update0() { Update(nullptr, 0); }
+
+  uint8_t k_[64];
+  uint8_t v_[64];
+  HMAC_CTX ctx_;
+};
+
+bssl::UniquePtr<EVP_PKEY> KeyFromRawKey(const uint8_t raw_key[32],
+                                        dice::test::KeyType key_type,
+                                        uint8_t raw_public_key[33],
+                                        size_t* raw_public_key_size) {
+  if (key_type == dice::test::KeyType_Ed25519) {
+    bssl::UniquePtr<EVP_PKEY> key(EVP_PKEY_new_raw_private_key(
+        EVP_PKEY_ED25519, /*unused=*/nullptr, raw_key, 32));
+    *raw_public_key_size = 32;
+    EVP_PKEY_get_raw_public_key(key.get(), raw_public_key, raw_public_key_size);
+    return key;
+  } else if (key_type == dice::test::KeyType_P256) {
+    bssl::UniquePtr<EC_KEY> key(EC_KEY_new_by_curve_name(NID_X9_62_prime256v1));
+    const EC_GROUP* group = EC_KEY_get0_group(key.get());
+    bssl::UniquePtr<EC_POINT> pub(EC_POINT_new(group));
+    // Match the algorithm described in RFC6979 and seed with the raw key.
+    HmacSha512Drbg drbg(raw_key);
+    while (true) {
+      uint8_t tmp[32];
+      drbg.GetBytes(32, tmp);
+      bssl::UniquePtr<BIGNUM> candidate(BN_bin2bn(tmp, 32, /*ret=*/nullptr));
+      if (BN_cmp(candidate.get(), EC_GROUP_get0_order(group)) < 0 &&
+          !BN_is_zero(candidate.get())) {
+        // Candidate is suitable.
+        EC_POINT_mul(group, pub.get(), candidate.get(), /*q=*/nullptr,
+                     /*m=*/nullptr,
+                     /*ctx=*/nullptr);
+        EC_KEY_set_public_key(key.get(), pub.get());
+        EC_KEY_set_private_key(key.get(), candidate.get());
+        break;
+      }
+    }
+    bssl::UniquePtr<EVP_PKEY> pkey(EVP_PKEY_new());
+    EVP_PKEY_set1_EC_KEY(pkey.get(), key.get());
+    *raw_public_key_size =
+        EC_POINT_point2oct(group, pub.get(), POINT_CONVERSION_COMPRESSED,
+                           raw_public_key, 33, /*ctx=*/nullptr);
+    return pkey;
+  }
+  printf("ERROR: Unsupported key type.\n");
+  return nullptr;
+}
+
+void CreateX509UdsCertificate(EVP_PKEY* key, const uint8_t id[20],
+                              uint8_t certificate[dice::test::kTestCertSize],
+                              size_t* certificate_size) {
+  bssl::UniquePtr<X509> x509(X509_new());
+  X509_set_version(x509.get(), 2);
+
+  bssl::UniquePtr<ASN1_INTEGER> serial(ASN1_INTEGER_new());
+  ASN1_INTEGER_set_uint64(serial.get(), 1);
+  X509_set_serialNumber(x509.get(), serial.get());
+
+  uint8_t id_hex[40];
+  DiceHexEncode(id, 20, id_hex, sizeof(id_hex));
+  bssl::UniquePtr<X509_NAME> issuer_name(X509_NAME_new());
+  X509_NAME_add_entry_by_NID(issuer_name.get(), NID_serialNumber, MBSTRING_UTF8,
+                             id_hex, sizeof(id_hex), 0, 0);
+  X509_set_issuer_name(x509.get(), issuer_name.get());
+  X509_set_subject_name(x509.get(), issuer_name.get());
+
+  bssl::UniquePtr<ASN1_TIME> not_before(ASN1_TIME_new());
+  ASN1_TIME_set_string(not_before.get(), "180322235959Z");
+  X509_set_notBefore(x509.get(), not_before.get());
+  bssl::UniquePtr<ASN1_TIME> not_after(ASN1_TIME_new());
+  ASN1_TIME_set_string(not_after.get(), "99991231235959Z");
+  X509_set_notAfter(x509.get(), not_after.get());
+
+  bssl::UniquePtr<ASN1_OCTET_STRING> subject_key_id(ASN1_OCTET_STRING_new());
+  ASN1_OCTET_STRING_set(subject_key_id.get(), id, 20);
+  bssl::UniquePtr<X509_EXTENSION> subject_key_id_ext(X509V3_EXT_i2d(
+      NID_subject_key_identifier, /*crit=*/0, subject_key_id.get()));
+  X509_add_ext(x509.get(), subject_key_id_ext.get(), /*loc=*/-1);
+
+  bssl::UniquePtr<AUTHORITY_KEYID> authority_key_id(AUTHORITY_KEYID_new());
+  authority_key_id->keyid = ASN1_OCTET_STRING_dup(subject_key_id.get());
+  bssl::UniquePtr<X509_EXTENSION> authority_key_id_ext(X509V3_EXT_i2d(
+      NID_authority_key_identifier, /*crit=*/0, authority_key_id.get()));
+  X509_add_ext(x509.get(), authority_key_id_ext.get(), /*loc=*/-1);
+
+  bssl::UniquePtr<ASN1_BIT_STRING> key_usage(ASN1_BIT_STRING_new());
+  ASN1_BIT_STRING_set_bit(key_usage.get(), 5 /*keyCertSign*/, 1);
+  bssl::UniquePtr<X509_EXTENSION> key_usage_ext(
+      X509V3_EXT_i2d(NID_key_usage, /*crit=*/1, key_usage.get()));
+  X509_add_ext(x509.get(), key_usage_ext.get(), /*loc=*/-1);
+
+  bssl::UniquePtr<BASIC_CONSTRAINTS> basic_constraints(BASIC_CONSTRAINTS_new());
+  basic_constraints->ca = 1;
+  bssl::UniquePtr<X509_EXTENSION> basic_constraints_ext(X509V3_EXT_i2d(
+      NID_basic_constraints, /*crit=*/1, basic_constraints.get()));
+  X509_add_ext(x509.get(), basic_constraints_ext.get(), /*loc=*/-1);
+
+  X509_set_pubkey(x509.get(), key);
+  // ED25519 always uses SHA-512 so md must be NULL.
+  const EVP_MD* md =
+      (EVP_PKEY_id(key) == EVP_PKEY_ED25519) ? nullptr : EVP_sha512();
+  X509_sign(x509.get(), key, md);
+  if (i2d_X509(x509.get(), /*out=*/nullptr) <=
+      static_cast<int>(dice::test::kTestCertSize)) {
+    uint8_t* p = certificate;
+    *certificate_size = i2d_X509(x509.get(), &p);
+  } else {
+    *certificate_size = 0;
+  }
+}
+
+bool VerifyX509CertificateChain(const uint8_t* root_certificate,
+                                size_t root_certificate_size,
+                                const dice::test::DiceStateForTest states[],
+                                size_t num_dice_states, bool is_partial_chain) {
+  bssl::UniquePtr<STACK_OF(X509)> trusted_certs(sk_X509_new_null());
+  bssl::PushToStack(trusted_certs.get(),
+                    bssl::UpRef(ParseX509Certificate(root_certificate,
+                                                     root_certificate_size)));
+  bssl::UniquePtr<STACK_OF(X509)> untrusted_certs(sk_X509_new_null());
+  for (size_t i = 0; i < num_dice_states - 1; ++i) {
+    bssl::PushToStack(untrusted_certs.get(),
+                      bssl::UpRef(ParseX509Certificate(
+                          states[i].certificate, states[i].certificate_size)));
+  }
+  bssl::UniquePtr<X509> leaf_cert(
+      ParseX509Certificate(states[num_dice_states - 1].certificate,
+                           states[num_dice_states - 1].certificate_size));
+  bssl::UniquePtr<X509_STORE> x509_store(X509_STORE_new());
+  bssl::UniquePtr<X509_STORE_CTX> x509_store_ctx(X509_STORE_CTX_new());
+  X509_STORE_CTX_init(x509_store_ctx.get(), x509_store.get(), leaf_cert.get(),
+                      untrusted_certs.get());
+  X509_STORE_CTX_trusted_stack(x509_store_ctx.get(), trusted_certs.get());
+  X509_VERIFY_PARAM* param = X509_VERIFY_PARAM_new();
+  X509_VERIFY_PARAM_set_time(param, 1577923199 /*1/1/2020*/);
+  X509_VERIFY_PARAM_set_depth(param, 10);
+  if (is_partial_chain) {
+    X509_VERIFY_PARAM_set_flags(param, X509_V_FLAG_PARTIAL_CHAIN);
+  }
+  X509_STORE_CTX_set0_param(x509_store_ctx.get(), param);
+  return (1 == X509_verify_cert(x509_store_ctx.get()));
+}
+
+void CreateCborUdsCertificate(const uint8_t private_key[32],
+                              const uint8_t id[20],
+                              uint8_t certificate[dice::test::kTestCertSize],
+                              size_t* certificate_size) {
+  const uint8_t kProtectedAttributesCbor[3] = {
+      0xa1 /* map(1) */, 0x01 /* alg(1) */, 0x27 /* EdDSA(-8) */};
+  const int64_t kCwtIssuerLabel = 1;
+  const int64_t kCwtSubjectLabel = 2;
+  const int64_t kUdsPublicKeyLabel = -4670552;
+  const int64_t kUdsKeyUsageLabel = -4670553;
+  const uint8_t kKeyUsageCertSign = 32;  // Bit 5.
+
+  // Public key encoded as a COSE_Key.
+  uint8_t public_key[32];
+  uint8_t bssl_private_key[64];
+  ED25519_keypair_from_seed(public_key, bssl_private_key, private_key);
+  cn_cbor_errback error;
+  ScopedCbor public_key_cbor(cn_cbor_map_create(&error));
+  // kty = okp
+  cn_cbor_mapput_int(public_key_cbor.get(), 1, cn_cbor_int_create(1, &error),
+                     &error);
+  // crv = ed25519
+  cn_cbor_mapput_int(public_key_cbor.get(), -1, cn_cbor_int_create(6, &error),
+                     &error);
+  // x = public_key
+  cn_cbor_mapput_int(public_key_cbor.get(), -2,
+                     cn_cbor_data_create(public_key, 32, &error), &error);
+  uint8_t encoded_public_key[100];
+  size_t encoded_public_key_size =
+      cn_cbor_encoder_write(encoded_public_key, 0, 100, public_key_cbor.get());
+
+  // Simple CWT payload with issuer, subject, and use the same subject public
+  // key field as a CDI certificate to make verification easy.
+  char id_hex[41];
+  DiceHexEncode(id, 20, id_hex, sizeof(id_hex));
+  id_hex[40] = '\0';
+  ScopedCbor cwt(cn_cbor_map_create(&error));
+  cn_cbor_mapput_int(cwt.get(), kCwtIssuerLabel,
+                     cn_cbor_string_create(id_hex, &error), &error);
+  cn_cbor_mapput_int(cwt.get(), kCwtSubjectLabel,
+                     cn_cbor_string_create(id_hex, &error), &error);
+  cn_cbor_mapput_int(
+      cwt.get(), kUdsPublicKeyLabel,
+      cn_cbor_data_create(encoded_public_key, encoded_public_key_size, &error),
+      &error);
+  uint8_t key_usage_byte = kKeyUsageCertSign;
+  cn_cbor_mapput_int(cwt.get(), kUdsKeyUsageLabel,
+                     cn_cbor_data_create(&key_usage_byte, 1, &error), &error);
+  uint8_t payload[dice::test::kTestCertSize];
+  size_t payload_size =
+      cn_cbor_encoder_write(payload, 0, dice::test::kTestCertSize, cwt.get());
+
+  // Signature over COSE Sign1 TBS.
+  ScopedCbor tbs_cbor(cn_cbor_array_create(&error));
+  cn_cbor_array_append(tbs_cbor.get(),
+                       cn_cbor_string_create("Signature1", &error), &error);
+  cn_cbor_array_append(tbs_cbor.get(),
+                       cn_cbor_data_create(kProtectedAttributesCbor, 3, &error),
+                       &error);
+  cn_cbor_array_append(tbs_cbor.get(), cn_cbor_data_create(NULL, 0, &error),
+                       &error);
+  cn_cbor_array_append(tbs_cbor.get(),
+                       cn_cbor_data_create(payload, payload_size, &error),
+                       &error);
+  uint8_t tbs[dice::test::kTestCertSize];
+  size_t tbs_size =
+      cn_cbor_encoder_write(tbs, 0, dice::test::kTestCertSize, tbs_cbor.get());
+  uint8_t signature[64];
+  ED25519_sign(signature, tbs, tbs_size, bssl_private_key);
+
+  // COSE Sign1.
+  ScopedCbor sign1(cn_cbor_array_create(&error));
+  cn_cbor_array_append(sign1.get(),
+                       cn_cbor_data_create(kProtectedAttributesCbor, 3, &error),
+                       &error);
+  cn_cbor_array_append(sign1.get(), cn_cbor_map_create(&error), &error);
+  cn_cbor_array_append(
+      sign1.get(), cn_cbor_data_create(payload, payload_size, &error), &error);
+  cn_cbor_array_append(sign1.get(), cn_cbor_data_create(signature, 64, &error),
+                       &error);
+  *certificate_size = cn_cbor_encoder_write(
+      certificate, 0, dice::test::kTestCertSize, sign1.get());
+}
+
+ScopedCbor ExtractCwtFromCborCertificate(const uint8_t* certificate,
+                                         size_t certificate_size) {
+  cn_cbor_errback error;
+  ScopedCbor sign1(cn_cbor_decode(certificate, certificate_size, &error));
+  if (!sign1 || sign1->type != CN_CBOR_ARRAY || sign1->length != 4) {
+    return nullptr;
+  }
+  cn_cbor* payload = cn_cbor_index(sign1.get(), 2);
+  if (!payload || payload->type != CN_CBOR_BYTES) {
+    return nullptr;
+  }
+  ScopedCbor cwt(cn_cbor_decode(payload->v.bytes, payload->length, &error));
+  if (cwt && cwt->type != CN_CBOR_MAP) {
+    return nullptr;
+  }
+  return cwt;
+}
+
+ScopedCbor ExtractPublicKeyFromCwt(const cn_cbor* cwt) {
+  cn_cbor_errback error;
+  cn_cbor* key_bytes = cn_cbor_mapget_int(cwt, -4670552);
+  if (!key_bytes || key_bytes->type != CN_CBOR_BYTES) {
+    return nullptr;
+  }
+  ScopedCbor key(cn_cbor_decode(key_bytes->v.bytes, key_bytes->length, &error));
+  if (key && key->type != CN_CBOR_MAP) {
+    return nullptr;
+  }
+  return key;
+}
+
+bool ExtractIdsFromCwt(const cn_cbor* cwt, char authority_id_hex[40],
+                       char subject_id_hex[40]) {
+  cn_cbor* authority_id_cbor = cn_cbor_mapget_int(cwt, 1);
+  cn_cbor* subject_id_cbor = cn_cbor_mapget_int(cwt, 2);
+  if (!authority_id_cbor || !subject_id_cbor) {
+    return false;
+  }
+  if (authority_id_cbor->type != CN_CBOR_TEXT ||
+      authority_id_cbor->length != 40 ||
+      subject_id_cbor->type != CN_CBOR_TEXT || subject_id_cbor->length != 40) {
+    return false;
+  }
+  memcpy(authority_id_hex, authority_id_cbor->v.str, 40);
+  memcpy(subject_id_hex, subject_id_cbor->v.str, 40);
+  return true;
+}
+
+bool ExtractKeyUsageFromCwt(const cn_cbor* cwt, uint64_t* key_usage) {
+  cn_cbor* key_usage_bytes = cn_cbor_mapget_int(cwt, -4670553);
+  if (!key_usage_bytes || key_usage_bytes->type != CN_CBOR_BYTES) {
+    return false;
+  }
+  // The highest key usage bit defined in RFC 5280 is 8.
+  if (key_usage_bytes->length > 2) {
+    return false;
+  }
+  if (key_usage_bytes->length == 0) {
+    *key_usage = 0;
+    return true;
+  }
+  *key_usage = key_usage_bytes->v.bytes[0];
+  if (key_usage_bytes->length == 2) {
+    uint64_t tmp = key_usage_bytes->v.bytes[1];
+    *key_usage += tmp >> 8;
+  }
+  return true;
+}
+
+bool ValidateCborCertificateCdiFields(const cn_cbor* cwt,
+                                      bool expect_cdi_certificate) {
+  cn_cbor* code_hash_bytes = cn_cbor_mapget_int(cwt, -4670545);
+  cn_cbor* code_desc_bytes = cn_cbor_mapget_int(cwt, -4670546);
+  cn_cbor* conf_hash_bytes = cn_cbor_mapget_int(cwt, -4670547);
+  cn_cbor* conf_desc_bytes = cn_cbor_mapget_int(cwt, -4670548);
+  cn_cbor* auth_hash_bytes = cn_cbor_mapget_int(cwt, -4670549);
+  cn_cbor* auth_desc_bytes = cn_cbor_mapget_int(cwt, -4670550);
+  cn_cbor* mode_bytes = cn_cbor_mapget_int(cwt, -4670551);
+  if (!expect_cdi_certificate) {
+    return (!code_hash_bytes && !code_desc_bytes && !conf_hash_bytes &&
+            !conf_desc_bytes && !auth_hash_bytes && !auth_desc_bytes &&
+            !mode_bytes);
+  }
+  if (!code_hash_bytes || !conf_desc_bytes || !auth_hash_bytes || !mode_bytes) {
+    return false;
+  }
+  if (code_hash_bytes->length != 64) {
+    return false;
+  }
+  if (conf_hash_bytes) {
+    if (conf_hash_bytes->length != 64) {
+      return false;
+    }
+  } else if (conf_desc_bytes->length != 64) {
+    return false;
+  }
+  if (auth_hash_bytes->length != 64) {
+    return false;
+  }
+  if (mode_bytes->length != 1) {
+    return false;
+  }
+  return true;
+}
+
+bool VerifySingleCborCertificate(const uint8_t* certificate,
+                                 size_t certificate_size,
+                                 const cn_cbor* authority_public_key,
+                                 const char authority_id_hex[40],
+                                 bool expect_cdi_certificate,
+                                 ScopedCbor* subject_public_key,
+                                 char subject_id_hex[40]) {
+  // Use the COSE-C library to decode and validate.
+  cose_errback error;
+  int struct_type = 0;
+  HCOSE_SIGN1 sign1 = (HCOSE_SIGN1)COSE_Decode(
+      certificate, certificate_size, &struct_type, COSE_sign1_object, &error);
+  if (!sign1) {
+    return false;
+  }
+  (void)authority_public_key;
+  bool result = COSE_Sign1_validate(sign1, authority_public_key, &error);
+  COSE_Sign1_Free(sign1);
+  if (!result) {
+    return false;
+  }
+
+  ScopedCbor cwt(ExtractCwtFromCborCertificate(certificate, certificate_size));
+  if (!cwt) {
+    return false;
+  }
+  char actual_authority_id[40];
+  char tmp_subject_id_hex[40];
+  if (!ExtractIdsFromCwt(cwt.get(), actual_authority_id, tmp_subject_id_hex)) {
+    return false;
+  }
+  if (0 != memcmp(authority_id_hex, actual_authority_id, 40)) {
+    return false;
+  }
+  memcpy(subject_id_hex, tmp_subject_id_hex, 40);
+  *subject_public_key = ExtractPublicKeyFromCwt(cwt.get());
+  if (!subject_public_key) {
+    return false;
+  }
+  uint64_t key_usage = 0;
+  const uint64_t kKeyUsageCertSign = 1 << 5;  // Bit 5.
+  if (!ExtractKeyUsageFromCwt(cwt.get(), &key_usage)) {
+    return false;
+  }
+  if (key_usage != kKeyUsageCertSign) {
+    return false;
+  }
+  if (!ValidateCborCertificateCdiFields(cwt.get(), expect_cdi_certificate)) {
+    return false;
+  }
+  return true;
+}
+
+bool VerifyCborCertificateChain(const uint8_t* root_certificate,
+                                size_t root_certificate_size,
+                                const dice::test::DiceStateForTest states[],
+                                size_t num_dice_states, bool is_partial_chain) {
+  ScopedCbor root_cwt =
+      ExtractCwtFromCborCertificate(root_certificate, root_certificate_size);
+  if (!root_cwt) {
+    return false;
+  }
+  ScopedCbor authority_public_key = ExtractPublicKeyFromCwt(root_cwt.get());
+  if (!authority_public_key) {
+    return false;
+  }
+  char expected_authority_id_hex[40];
+  char not_used[40];
+  if (!ExtractIdsFromCwt(root_cwt.get(), not_used, expected_authority_id_hex)) {
+    return false;
+  }
+  if (!is_partial_chain) {
+    // We can't verify the root certificate in a partial chain, we can only
+    // check that its public key certifies the other certificates. But with a
+    // full chain, we can expect the root to be self-signed.
+    if (!VerifySingleCborCertificate(
+            root_certificate, root_certificate_size, authority_public_key.get(),
+            expected_authority_id_hex, /*expect_cdi_certificate=*/false,
+            &authority_public_key, expected_authority_id_hex)) {
+      return false;
+    }
+  }
+  for (size_t i = 0; i < num_dice_states; ++i) {
+    if (!VerifySingleCborCertificate(
+            states[i].certificate, states[i].certificate_size,
+            authority_public_key.get(), expected_authority_id_hex,
+            /*expect_cdi_certificate=*/true, &authority_public_key,
+            expected_authority_id_hex)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+}  // namespace
+
+namespace dice {
+namespace test {
+
+void DumpState(CertificateType cert_type, KeyType key_type, const char* suffix,
+               const DiceStateForTest& state) {
+  char filename[100];
+  pw::string::Format(filename, "_attest_cdi_%s.bin", suffix);
+  DumpToFile(filename, state.cdi_attest, DICE_CDI_SIZE);
+  pw::string::Format(filename, "_seal_cdi_%s.bin", suffix);
+  DumpToFile(filename, state.cdi_seal, DICE_CDI_SIZE);
+  pw::string::Format(filename, "_%s_%s_cert_%s.cert", GetCertTypeStr(cert_type),
+                     GetKeyTypeStr(key_type), suffix);
+  DumpToFile(filename, state.certificate, state.certificate_size);
+}
+
+void DeriveFakeInputValue(const char* seed, size_t length, uint8_t* output) {
+  union {
+    uint8_t buffer[64];
+    uint64_t counter;
+  } context;
+  SHA512(reinterpret_cast<const uint8_t*>(seed), strlen(seed), context.buffer);
+  size_t output_pos = 0;
+  while (output_pos < length) {
+    uint8_t tmp[64];
+    SHA512(context.buffer, 64, tmp);
+    context.counter++;
+    size_t remaining = length - output_pos;
+    size_t to_copy = remaining < 64 ? remaining : 64;
+    memcpy(&output[output_pos], tmp, to_copy);
+    output_pos += to_copy;
+  }
+}
+
+void CreateFakeUdsCertificate(const DiceOps& ops, const uint8_t uds[32],
+                              CertificateType cert_type, KeyType key_type,
+                              uint8_t certificate[kTestCertSize],
+                              size_t* certificate_size) {
+  uint8_t raw_key[32];
+  DiceDeriveCdiPrivateKey(&ops, uds, raw_key);
+
+  uint8_t raw_public_key[33];
+  size_t raw_public_key_size = 0;
+  bssl::UniquePtr<EVP_PKEY> key(
+      KeyFromRawKey(raw_key, key_type, raw_public_key, &raw_public_key_size));
+
+  uint8_t id[20];
+  DiceDeriveCdiCertificateId(&ops, raw_public_key, raw_public_key_size, id);
+
+  if (cert_type == CertificateType_X509) {
+    CreateX509UdsCertificate(key.get(), id, certificate, certificate_size);
+  } else {
+    CreateCborUdsCertificate(raw_key, id, certificate, certificate_size);
+  }
+
+  char filename[100];
+  pw::string::Format(filename, "_%s_%s_uds_cert.cert",
+                     GetCertTypeStr(cert_type), GetKeyTypeStr(key_type));
+  DumpToFile(filename, certificate, *certificate_size);
+}
+
+bool VerifyCertificateChain(CertificateType cert_type,
+                            const uint8_t* root_certificate,
+                            size_t root_certificate_size,
+                            const DiceStateForTest states[],
+                            size_t num_dice_states, bool is_partial_chain) {
+  switch (cert_type) {
+    case CertificateType_Cbor:
+      return VerifyCborCertificateChain(root_certificate, root_certificate_size,
+                                        states, num_dice_states,
+                                        is_partial_chain);
+    case CertificateType_X509:
+      return VerifyX509CertificateChain(root_certificate, root_certificate_size,
+                                        states, num_dice_states,
+                                        is_partial_chain);
+  }
+  return false;
+}
+}  // namespace test
+}  // namespace dice
diff --git a/src/utils.c b/src/utils.c
new file mode 100644
index 0000000..ead530c
--- /dev/null
+++ b/src/utils.c
@@ -0,0 +1,39 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include "dice/utils.h"
+
+#include <stdint.h>
+
+void DiceHexEncode(const uint8_t* in, size_t num_bytes, void* out,
+                   size_t out_size) {
+  const uint8_t kHexMap[16] = "0123456789abcdef";
+  size_t in_pos = 0;
+  size_t out_pos = 0;
+  uint8_t* out_bytes = out;
+  for (in_pos = 0; in_pos < num_bytes && out_pos < out_size; ++in_pos) {
+    out_bytes[out_pos++] = kHexMap[(in[in_pos] >> 4)];
+    if (out_pos < out_size) {
+      out_bytes[out_pos++] = kHexMap[in[in_pos] & 0xF];
+    }
+  }
+}
+
+void DiceClearMemory(const DiceOps* ops, size_t size, void* address) {
+  (void)ops;
+  volatile uint8_t* p = address;
+  for (size_t i = 0; i < size; i++) {
+    p[i] = 0;
+  }
+}
diff --git a/third_party/boringssl/.gitignore b/third_party/boringssl/.gitignore
new file mode 100644
index 0000000..656f869
--- /dev/null
+++ b/third_party/boringssl/.gitignore
@@ -0,0 +1,10 @@
+# Generated files.
+BUILD.generated_tests.gni
+crypto_test_data.cc
+
+# Platform-specific generated files.
+ios-*
+linux-*
+mac-*
+win-*
+
diff --git a/third_party/boringssl/BUILD.generated.gni b/third_party/boringssl/BUILD.generated.gni
new file mode 100644
index 0000000..5fcc97c
--- /dev/null
+++ b/third_party/boringssl/BUILD.generated.gni
@@ -0,0 +1,619 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+# This file is created by generate_build_files.py. Do not edit manually.
+
+crypto_sources = [
+  "err_data.c",
+  "src/crypto/asn1/a_bitstr.c",
+  "src/crypto/asn1/a_bool.c",
+  "src/crypto/asn1/a_d2i_fp.c",
+  "src/crypto/asn1/a_dup.c",
+  "src/crypto/asn1/a_enum.c",
+  "src/crypto/asn1/a_gentm.c",
+  "src/crypto/asn1/a_i2d_fp.c",
+  "src/crypto/asn1/a_int.c",
+  "src/crypto/asn1/a_mbstr.c",
+  "src/crypto/asn1/a_object.c",
+  "src/crypto/asn1/a_octet.c",
+  "src/crypto/asn1/a_print.c",
+  "src/crypto/asn1/a_strnid.c",
+  "src/crypto/asn1/a_time.c",
+  "src/crypto/asn1/a_type.c",
+  "src/crypto/asn1/a_utctm.c",
+  "src/crypto/asn1/a_utf8.c",
+  "src/crypto/asn1/asn1_lib.c",
+  "src/crypto/asn1/asn1_locl.h",
+  "src/crypto/asn1/asn1_par.c",
+  "src/crypto/asn1/asn_pack.c",
+  "src/crypto/asn1/f_enum.c",
+  "src/crypto/asn1/f_int.c",
+  "src/crypto/asn1/f_string.c",
+  "src/crypto/asn1/tasn_dec.c",
+  "src/crypto/asn1/tasn_enc.c",
+  "src/crypto/asn1/tasn_fre.c",
+  "src/crypto/asn1/tasn_new.c",
+  "src/crypto/asn1/tasn_typ.c",
+  "src/crypto/asn1/tasn_utl.c",
+  "src/crypto/asn1/time_support.c",
+  "src/crypto/base64/base64.c",
+  "src/crypto/bio/bio.c",
+  "src/crypto/bio/bio_mem.c",
+  "src/crypto/bio/connect.c",
+  "src/crypto/bio/fd.c",
+  "src/crypto/bio/file.c",
+  "src/crypto/bio/hexdump.c",
+  "src/crypto/bio/internal.h",
+  "src/crypto/bio/pair.c",
+  "src/crypto/bio/printf.c",
+  "src/crypto/bio/socket.c",
+  "src/crypto/bio/socket_helper.c",
+  "src/crypto/bn_extra/bn_asn1.c",
+  "src/crypto/bn_extra/convert.c",
+  "src/crypto/buf/buf.c",
+  "src/crypto/bytestring/asn1_compat.c",
+  "src/crypto/bytestring/ber.c",
+  "src/crypto/bytestring/cbb.c",
+  "src/crypto/bytestring/cbs.c",
+  "src/crypto/bytestring/internal.h",
+  "src/crypto/bytestring/unicode.c",
+  "src/crypto/chacha/chacha.c",
+  "src/crypto/chacha/internal.h",
+  "src/crypto/cipher_extra/cipher_extra.c",
+  "src/crypto/cipher_extra/derive_key.c",
+  "src/crypto/cipher_extra/e_aesccm.c",
+  "src/crypto/cipher_extra/e_aesctrhmac.c",
+  "src/crypto/cipher_extra/e_aesgcmsiv.c",
+  "src/crypto/cipher_extra/e_chacha20poly1305.c",
+  "src/crypto/cipher_extra/e_null.c",
+  "src/crypto/cipher_extra/e_rc2.c",
+  "src/crypto/cipher_extra/e_rc4.c",
+  "src/crypto/cipher_extra/e_tls.c",
+  "src/crypto/cipher_extra/internal.h",
+  "src/crypto/cipher_extra/tls_cbc.c",
+  "src/crypto/cmac/cmac.c",
+  "src/crypto/conf/conf.c",
+  "src/crypto/conf/conf_def.h",
+  "src/crypto/conf/internal.h",
+  "src/crypto/cpu-aarch64-fuchsia.c",
+  "src/crypto/cpu-aarch64-linux.c",
+  "src/crypto/cpu-arm-linux.c",
+  "src/crypto/cpu-arm-linux.h",
+  "src/crypto/cpu-arm.c",
+  "src/crypto/cpu-intel.c",
+  "src/crypto/cpu-ppc64le.c",
+  "src/crypto/crypto.c",
+  "src/crypto/curve25519/curve25519.c",
+  "src/crypto/curve25519/curve25519_tables.h",
+  "src/crypto/curve25519/internal.h",
+  "src/crypto/curve25519/spake25519.c",
+  "src/crypto/dh/check.c",
+  "src/crypto/dh/dh.c",
+  "src/crypto/dh/dh_asn1.c",
+  "src/crypto/dh/params.c",
+  "src/crypto/digest_extra/digest_extra.c",
+  "src/crypto/dsa/dsa.c",
+  "src/crypto/dsa/dsa_asn1.c",
+  "src/crypto/dsa/internal.h",
+  "src/crypto/ec_extra/ec_asn1.c",
+  "src/crypto/ec_extra/ec_derive.c",
+  "src/crypto/ec_extra/hash_to_curve.c",
+  "src/crypto/ec_extra/internal.h",
+  "src/crypto/ecdh_extra/ecdh_extra.c",
+  "src/crypto/ecdsa_extra/ecdsa_asn1.c",
+  "src/crypto/engine/engine.c",
+  "src/crypto/err/err.c",
+  "src/crypto/err/internal.h",
+  "src/crypto/evp/digestsign.c",
+  "src/crypto/evp/evp.c",
+  "src/crypto/evp/evp_asn1.c",
+  "src/crypto/evp/evp_ctx.c",
+  "src/crypto/evp/internal.h",
+  "src/crypto/evp/p_dsa_asn1.c",
+  "src/crypto/evp/p_ec.c",
+  "src/crypto/evp/p_ec_asn1.c",
+  "src/crypto/evp/p_ed25519.c",
+  "src/crypto/evp/p_ed25519_asn1.c",
+  "src/crypto/evp/p_rsa.c",
+  "src/crypto/evp/p_rsa_asn1.c",
+  "src/crypto/evp/p_x25519.c",
+  "src/crypto/evp/p_x25519_asn1.c",
+  "src/crypto/evp/pbkdf.c",
+  "src/crypto/evp/print.c",
+  "src/crypto/evp/scrypt.c",
+  "src/crypto/evp/sign.c",
+  "src/crypto/ex_data.c",
+  "src/crypto/fipsmodule/aes/internal.h",
+  "src/crypto/fipsmodule/bcm.c",
+  "src/crypto/fipsmodule/bn/internal.h",
+  "src/crypto/fipsmodule/bn/rsaz_exp.h",
+  "src/crypto/fipsmodule/cipher/internal.h",
+  "src/crypto/fipsmodule/delocate.h",
+  "src/crypto/fipsmodule/des/internal.h",
+  "src/crypto/fipsmodule/digest/internal.h",
+  "src/crypto/fipsmodule/digest/md32_common.h",
+  "src/crypto/fipsmodule/ec/internal.h",
+  "src/crypto/fipsmodule/ec/p256-x86_64-table.h",
+  "src/crypto/fipsmodule/ec/p256-x86_64.h",
+  "src/crypto/fipsmodule/ec/p256_table.h",
+  "src/crypto/fipsmodule/fips_shared_support.c",
+  "src/crypto/fipsmodule/is_fips.c",
+  "src/crypto/fipsmodule/md5/internal.h",
+  "src/crypto/fipsmodule/modes/internal.h",
+  "src/crypto/fipsmodule/rand/fork_detect.h",
+  "src/crypto/fipsmodule/rand/getrandom_fillin.h",
+  "src/crypto/fipsmodule/rand/internal.h",
+  "src/crypto/fipsmodule/rsa/internal.h",
+  "src/crypto/fipsmodule/sha/internal.h",
+  "src/crypto/fipsmodule/tls/internal.h",
+  "src/crypto/hkdf/hkdf.c",
+  "src/crypto/hpke/hpke.c",
+  "src/crypto/hpke/internal.h",
+  "src/crypto/hrss/hrss.c",
+  "src/crypto/hrss/internal.h",
+  "src/crypto/internal.h",
+  "src/crypto/lhash/lhash.c",
+  "src/crypto/mem.c",
+  "src/crypto/obj/obj.c",
+  "src/crypto/obj/obj_dat.h",
+  "src/crypto/obj/obj_xref.c",
+  "src/crypto/pem/pem_all.c",
+  "src/crypto/pem/pem_info.c",
+  "src/crypto/pem/pem_lib.c",
+  "src/crypto/pem/pem_oth.c",
+  "src/crypto/pem/pem_pk8.c",
+  "src/crypto/pem/pem_pkey.c",
+  "src/crypto/pem/pem_x509.c",
+  "src/crypto/pem/pem_xaux.c",
+  "src/crypto/pkcs7/internal.h",
+  "src/crypto/pkcs7/pkcs7.c",
+  "src/crypto/pkcs7/pkcs7_x509.c",
+  "src/crypto/pkcs8/internal.h",
+  "src/crypto/pkcs8/p5_pbev2.c",
+  "src/crypto/pkcs8/pkcs8.c",
+  "src/crypto/pkcs8/pkcs8_x509.c",
+  "src/crypto/poly1305/internal.h",
+  "src/crypto/poly1305/poly1305.c",
+  "src/crypto/poly1305/poly1305_arm.c",
+  "src/crypto/poly1305/poly1305_vec.c",
+  "src/crypto/pool/internal.h",
+  "src/crypto/pool/pool.c",
+  "src/crypto/rand_extra/deterministic.c",
+  "src/crypto/rand_extra/forkunsafe.c",
+  "src/crypto/rand_extra/fuchsia.c",
+  "src/crypto/rand_extra/rand_extra.c",
+  "src/crypto/rand_extra/windows.c",
+  "src/crypto/rc4/rc4.c",
+  "src/crypto/refcount_c11.c",
+  "src/crypto/refcount_lock.c",
+  "src/crypto/rsa_extra/rsa_asn1.c",
+  "src/crypto/rsa_extra/rsa_print.c",
+  "src/crypto/siphash/siphash.c",
+  "src/crypto/stack/stack.c",
+  "src/crypto/thread.c",
+  "src/crypto/thread_none.c",
+  "src/crypto/thread_pthread.c",
+  "src/crypto/thread_win.c",
+  "src/crypto/trust_token/internal.h",
+  "src/crypto/trust_token/pmbtoken.c",
+  "src/crypto/trust_token/trust_token.c",
+  "src/crypto/trust_token/voprf.c",
+  "src/crypto/x509/a_digest.c",
+  "src/crypto/x509/a_sign.c",
+  "src/crypto/x509/a_strex.c",
+  "src/crypto/x509/a_verify.c",
+  "src/crypto/x509/algorithm.c",
+  "src/crypto/x509/asn1_gen.c",
+  "src/crypto/x509/by_dir.c",
+  "src/crypto/x509/by_file.c",
+  "src/crypto/x509/charmap.h",
+  "src/crypto/x509/i2d_pr.c",
+  "src/crypto/x509/internal.h",
+  "src/crypto/x509/rsa_pss.c",
+  "src/crypto/x509/t_crl.c",
+  "src/crypto/x509/t_req.c",
+  "src/crypto/x509/t_x509.c",
+  "src/crypto/x509/t_x509a.c",
+  "src/crypto/x509/vpm_int.h",
+  "src/crypto/x509/x509.c",
+  "src/crypto/x509/x509_att.c",
+  "src/crypto/x509/x509_cmp.c",
+  "src/crypto/x509/x509_d2.c",
+  "src/crypto/x509/x509_def.c",
+  "src/crypto/x509/x509_ext.c",
+  "src/crypto/x509/x509_lu.c",
+  "src/crypto/x509/x509_obj.c",
+  "src/crypto/x509/x509_r2x.c",
+  "src/crypto/x509/x509_req.c",
+  "src/crypto/x509/x509_set.c",
+  "src/crypto/x509/x509_trs.c",
+  "src/crypto/x509/x509_txt.c",
+  "src/crypto/x509/x509_v3.c",
+  "src/crypto/x509/x509_vfy.c",
+  "src/crypto/x509/x509_vpm.c",
+  "src/crypto/x509/x509cset.c",
+  "src/crypto/x509/x509name.c",
+  "src/crypto/x509/x509rset.c",
+  "src/crypto/x509/x509spki.c",
+  "src/crypto/x509/x_algor.c",
+  "src/crypto/x509/x_all.c",
+  "src/crypto/x509/x_attrib.c",
+  "src/crypto/x509/x_crl.c",
+  "src/crypto/x509/x_exten.c",
+  "src/crypto/x509/x_info.c",
+  "src/crypto/x509/x_name.c",
+  "src/crypto/x509/x_pkey.c",
+  "src/crypto/x509/x_pubkey.c",
+  "src/crypto/x509/x_req.c",
+  "src/crypto/x509/x_sig.c",
+  "src/crypto/x509/x_spki.c",
+  "src/crypto/x509/x_val.c",
+  "src/crypto/x509/x_x509.c",
+  "src/crypto/x509/x_x509a.c",
+  "src/crypto/x509v3/ext_dat.h",
+  "src/crypto/x509v3/internal.h",
+  "src/crypto/x509v3/pcy_cache.c",
+  "src/crypto/x509v3/pcy_data.c",
+  "src/crypto/x509v3/pcy_int.h",
+  "src/crypto/x509v3/pcy_lib.c",
+  "src/crypto/x509v3/pcy_map.c",
+  "src/crypto/x509v3/pcy_node.c",
+  "src/crypto/x509v3/pcy_tree.c",
+  "src/crypto/x509v3/v3_akey.c",
+  "src/crypto/x509v3/v3_akeya.c",
+  "src/crypto/x509v3/v3_alt.c",
+  "src/crypto/x509v3/v3_bcons.c",
+  "src/crypto/x509v3/v3_bitst.c",
+  "src/crypto/x509v3/v3_conf.c",
+  "src/crypto/x509v3/v3_cpols.c",
+  "src/crypto/x509v3/v3_crld.c",
+  "src/crypto/x509v3/v3_enum.c",
+  "src/crypto/x509v3/v3_extku.c",
+  "src/crypto/x509v3/v3_genn.c",
+  "src/crypto/x509v3/v3_ia5.c",
+  "src/crypto/x509v3/v3_info.c",
+  "src/crypto/x509v3/v3_int.c",
+  "src/crypto/x509v3/v3_lib.c",
+  "src/crypto/x509v3/v3_ncons.c",
+  "src/crypto/x509v3/v3_ocsp.c",
+  "src/crypto/x509v3/v3_pci.c",
+  "src/crypto/x509v3/v3_pcia.c",
+  "src/crypto/x509v3/v3_pcons.c",
+  "src/crypto/x509v3/v3_pmaps.c",
+  "src/crypto/x509v3/v3_prn.c",
+  "src/crypto/x509v3/v3_purp.c",
+  "src/crypto/x509v3/v3_skey.c",
+  "src/crypto/x509v3/v3_utl.c",
+  "src/third_party/fiat/curve25519_32.h",
+  "src/third_party/fiat/curve25519_64.h",
+  "src/third_party/fiat/p256_32.h",
+  "src/third_party/fiat/p256_64.h",
+]
+
+crypto_headers = [
+  "src/include/openssl/aead.h",
+  "src/include/openssl/aes.h",
+  "src/include/openssl/arm_arch.h",
+  "src/include/openssl/asn1.h",
+  "src/include/openssl/asn1_mac.h",
+  "src/include/openssl/asn1t.h",
+  "src/include/openssl/base.h",
+  "src/include/openssl/base64.h",
+  "src/include/openssl/bio.h",
+  "src/include/openssl/blowfish.h",
+  "src/include/openssl/bn.h",
+  "src/include/openssl/buf.h",
+  "src/include/openssl/buffer.h",
+  "src/include/openssl/bytestring.h",
+  "src/include/openssl/cast.h",
+  "src/include/openssl/chacha.h",
+  "src/include/openssl/cipher.h",
+  "src/include/openssl/cmac.h",
+  "src/include/openssl/conf.h",
+  "src/include/openssl/cpu.h",
+  "src/include/openssl/crypto.h",
+  "src/include/openssl/curve25519.h",
+  "src/include/openssl/des.h",
+  "src/include/openssl/dh.h",
+  "src/include/openssl/digest.h",
+  "src/include/openssl/dsa.h",
+  "src/include/openssl/e_os2.h",
+  "src/include/openssl/ec.h",
+  "src/include/openssl/ec_key.h",
+  "src/include/openssl/ecdh.h",
+  "src/include/openssl/ecdsa.h",
+  "src/include/openssl/engine.h",
+  "src/include/openssl/err.h",
+  "src/include/openssl/evp.h",
+  "src/include/openssl/ex_data.h",
+  "src/include/openssl/hkdf.h",
+  "src/include/openssl/hmac.h",
+  "src/include/openssl/hrss.h",
+  "src/include/openssl/is_boringssl.h",
+  "src/include/openssl/lhash.h",
+  "src/include/openssl/md4.h",
+  "src/include/openssl/md5.h",
+  "src/include/openssl/mem.h",
+  "src/include/openssl/nid.h",
+  "src/include/openssl/obj.h",
+  "src/include/openssl/obj_mac.h",
+  "src/include/openssl/objects.h",
+  "src/include/openssl/opensslconf.h",
+  "src/include/openssl/opensslv.h",
+  "src/include/openssl/ossl_typ.h",
+  "src/include/openssl/pem.h",
+  "src/include/openssl/pkcs12.h",
+  "src/include/openssl/pkcs7.h",
+  "src/include/openssl/pkcs8.h",
+  "src/include/openssl/poly1305.h",
+  "src/include/openssl/pool.h",
+  "src/include/openssl/rand.h",
+  "src/include/openssl/rc4.h",
+  "src/include/openssl/ripemd.h",
+  "src/include/openssl/rsa.h",
+  "src/include/openssl/safestack.h",
+  "src/include/openssl/sha.h",
+  "src/include/openssl/siphash.h",
+  "src/include/openssl/span.h",
+  "src/include/openssl/stack.h",
+  "src/include/openssl/thread.h",
+  "src/include/openssl/trust_token.h",
+  "src/include/openssl/type_check.h",
+  "src/include/openssl/x509.h",
+  "src/include/openssl/x509_vfy.h",
+  "src/include/openssl/x509v3.h",
+]
+
+ssl_sources = [
+  "src/ssl/bio_ssl.cc",
+  "src/ssl/d1_both.cc",
+  "src/ssl/d1_lib.cc",
+  "src/ssl/d1_pkt.cc",
+  "src/ssl/d1_srtp.cc",
+  "src/ssl/dtls_method.cc",
+  "src/ssl/dtls_record.cc",
+  "src/ssl/handoff.cc",
+  "src/ssl/handshake.cc",
+  "src/ssl/handshake_client.cc",
+  "src/ssl/handshake_server.cc",
+  "src/ssl/internal.h",
+  "src/ssl/s3_both.cc",
+  "src/ssl/s3_lib.cc",
+  "src/ssl/s3_pkt.cc",
+  "src/ssl/ssl_aead_ctx.cc",
+  "src/ssl/ssl_asn1.cc",
+  "src/ssl/ssl_buffer.cc",
+  "src/ssl/ssl_cert.cc",
+  "src/ssl/ssl_cipher.cc",
+  "src/ssl/ssl_file.cc",
+  "src/ssl/ssl_key_share.cc",
+  "src/ssl/ssl_lib.cc",
+  "src/ssl/ssl_privkey.cc",
+  "src/ssl/ssl_session.cc",
+  "src/ssl/ssl_stat.cc",
+  "src/ssl/ssl_transcript.cc",
+  "src/ssl/ssl_versions.cc",
+  "src/ssl/ssl_x509.cc",
+  "src/ssl/t1_enc.cc",
+  "src/ssl/t1_lib.cc",
+  "src/ssl/tls13_both.cc",
+  "src/ssl/tls13_client.cc",
+  "src/ssl/tls13_enc.cc",
+  "src/ssl/tls13_server.cc",
+  "src/ssl/tls_method.cc",
+  "src/ssl/tls_record.cc",
+]
+
+ssl_headers = [
+  "src/include/openssl/dtls1.h",
+  "src/include/openssl/srtp.h",
+  "src/include/openssl/ssl.h",
+  "src/include/openssl/ssl3.h",
+  "src/include/openssl/tls1.h",
+]
+
+crypto_sources_ios_aarch64 = [
+  "ios-aarch64/crypto/chacha/chacha-armv8.S",
+  "ios-aarch64/crypto/fipsmodule/aesv8-armx64.S",
+  "ios-aarch64/crypto/fipsmodule/armv8-mont.S",
+  "ios-aarch64/crypto/fipsmodule/ghash-neon-armv8.S",
+  "ios-aarch64/crypto/fipsmodule/ghashv8-armx64.S",
+  "ios-aarch64/crypto/fipsmodule/sha1-armv8.S",
+  "ios-aarch64/crypto/fipsmodule/sha256-armv8.S",
+  "ios-aarch64/crypto/fipsmodule/sha512-armv8.S",
+  "ios-aarch64/crypto/fipsmodule/vpaes-armv8.S",
+  "ios-aarch64/crypto/test/trampoline-armv8.S",
+]
+
+crypto_sources_ios_arm = [
+  "ios-arm/crypto/chacha/chacha-armv4.S",
+  "ios-arm/crypto/fipsmodule/aesv8-armx32.S",
+  "ios-arm/crypto/fipsmodule/armv4-mont.S",
+  "ios-arm/crypto/fipsmodule/bsaes-armv7.S",
+  "ios-arm/crypto/fipsmodule/ghash-armv4.S",
+  "ios-arm/crypto/fipsmodule/ghashv8-armx32.S",
+  "ios-arm/crypto/fipsmodule/sha1-armv4-large.S",
+  "ios-arm/crypto/fipsmodule/sha256-armv4.S",
+  "ios-arm/crypto/fipsmodule/sha512-armv4.S",
+  "ios-arm/crypto/fipsmodule/vpaes-armv7.S",
+  "ios-arm/crypto/test/trampoline-armv4.S",
+]
+
+crypto_sources_linux_aarch64 = [
+  "linux-aarch64/crypto/chacha/chacha-armv8.S",
+  "linux-aarch64/crypto/fipsmodule/aesv8-armx64.S",
+  "linux-aarch64/crypto/fipsmodule/armv8-mont.S",
+  "linux-aarch64/crypto/fipsmodule/ghash-neon-armv8.S",
+  "linux-aarch64/crypto/fipsmodule/ghashv8-armx64.S",
+  "linux-aarch64/crypto/fipsmodule/sha1-armv8.S",
+  "linux-aarch64/crypto/fipsmodule/sha256-armv8.S",
+  "linux-aarch64/crypto/fipsmodule/sha512-armv8.S",
+  "linux-aarch64/crypto/fipsmodule/vpaes-armv8.S",
+  "linux-aarch64/crypto/test/trampoline-armv8.S",
+]
+
+crypto_sources_linux_arm = [
+  "linux-arm/crypto/chacha/chacha-armv4.S",
+  "linux-arm/crypto/fipsmodule/aesv8-armx32.S",
+  "linux-arm/crypto/fipsmodule/armv4-mont.S",
+  "linux-arm/crypto/fipsmodule/bsaes-armv7.S",
+  "linux-arm/crypto/fipsmodule/ghash-armv4.S",
+  "linux-arm/crypto/fipsmodule/ghashv8-armx32.S",
+  "linux-arm/crypto/fipsmodule/sha1-armv4-large.S",
+  "linux-arm/crypto/fipsmodule/sha256-armv4.S",
+  "linux-arm/crypto/fipsmodule/sha512-armv4.S",
+  "linux-arm/crypto/fipsmodule/vpaes-armv7.S",
+  "linux-arm/crypto/test/trampoline-armv4.S",
+  "src/crypto/curve25519/asm/x25519-asm-arm.S",
+  "src/crypto/poly1305/poly1305_arm_asm.S",
+]
+
+crypto_sources_linux_ppc64le = [
+  "linux-ppc64le/crypto/fipsmodule/aesp8-ppc.S",
+  "linux-ppc64le/crypto/fipsmodule/ghashp8-ppc.S",
+  "linux-ppc64le/crypto/test/trampoline-ppc.S",
+]
+
+crypto_sources_linux_x86 = [
+  "linux-x86/crypto/chacha/chacha-x86.S",
+  "linux-x86/crypto/fipsmodule/aesni-x86.S",
+  "linux-x86/crypto/fipsmodule/bn-586.S",
+  "linux-x86/crypto/fipsmodule/co-586.S",
+  "linux-x86/crypto/fipsmodule/ghash-ssse3-x86.S",
+  "linux-x86/crypto/fipsmodule/ghash-x86.S",
+  "linux-x86/crypto/fipsmodule/md5-586.S",
+  "linux-x86/crypto/fipsmodule/sha1-586.S",
+  "linux-x86/crypto/fipsmodule/sha256-586.S",
+  "linux-x86/crypto/fipsmodule/sha512-586.S",
+  "linux-x86/crypto/fipsmodule/vpaes-x86.S",
+  "linux-x86/crypto/fipsmodule/x86-mont.S",
+  "linux-x86/crypto/test/trampoline-x86.S",
+]
+
+crypto_sources_linux_x86_64 = [
+  "linux-x86_64/crypto/chacha/chacha-x86_64.S",
+  "linux-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64.S",
+  "linux-x86_64/crypto/cipher_extra/chacha20_poly1305_x86_64.S",
+  "linux-x86_64/crypto/fipsmodule/aesni-gcm-x86_64.S",
+  "linux-x86_64/crypto/fipsmodule/aesni-x86_64.S",
+  "linux-x86_64/crypto/fipsmodule/ghash-ssse3-x86_64.S",
+  "linux-x86_64/crypto/fipsmodule/ghash-x86_64.S",
+  "linux-x86_64/crypto/fipsmodule/md5-x86_64.S",
+  "linux-x86_64/crypto/fipsmodule/p256-x86_64-asm.S",
+  "linux-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S",
+  "linux-x86_64/crypto/fipsmodule/rdrand-x86_64.S",
+  "linux-x86_64/crypto/fipsmodule/rsaz-avx2.S",
+  "linux-x86_64/crypto/fipsmodule/sha1-x86_64.S",
+  "linux-x86_64/crypto/fipsmodule/sha256-x86_64.S",
+  "linux-x86_64/crypto/fipsmodule/sha512-x86_64.S",
+  "linux-x86_64/crypto/fipsmodule/vpaes-x86_64.S",
+  "linux-x86_64/crypto/fipsmodule/x86_64-mont.S",
+  "linux-x86_64/crypto/fipsmodule/x86_64-mont5.S",
+  "linux-x86_64/crypto/test/trampoline-x86_64.S",
+  "src/crypto/hrss/asm/poly_rq_mul.S",
+]
+
+crypto_sources_mac_x86 = [
+  "mac-x86/crypto/chacha/chacha-x86.S",
+  "mac-x86/crypto/fipsmodule/aesni-x86.S",
+  "mac-x86/crypto/fipsmodule/bn-586.S",
+  "mac-x86/crypto/fipsmodule/co-586.S",
+  "mac-x86/crypto/fipsmodule/ghash-ssse3-x86.S",
+  "mac-x86/crypto/fipsmodule/ghash-x86.S",
+  "mac-x86/crypto/fipsmodule/md5-586.S",
+  "mac-x86/crypto/fipsmodule/sha1-586.S",
+  "mac-x86/crypto/fipsmodule/sha256-586.S",
+  "mac-x86/crypto/fipsmodule/sha512-586.S",
+  "mac-x86/crypto/fipsmodule/vpaes-x86.S",
+  "mac-x86/crypto/fipsmodule/x86-mont.S",
+  "mac-x86/crypto/test/trampoline-x86.S",
+]
+
+crypto_sources_mac_x86_64 = [
+  "mac-x86_64/crypto/chacha/chacha-x86_64.S",
+  "mac-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64.S",
+  "mac-x86_64/crypto/cipher_extra/chacha20_poly1305_x86_64.S",
+  "mac-x86_64/crypto/fipsmodule/aesni-gcm-x86_64.S",
+  "mac-x86_64/crypto/fipsmodule/aesni-x86_64.S",
+  "mac-x86_64/crypto/fipsmodule/ghash-ssse3-x86_64.S",
+  "mac-x86_64/crypto/fipsmodule/ghash-x86_64.S",
+  "mac-x86_64/crypto/fipsmodule/md5-x86_64.S",
+  "mac-x86_64/crypto/fipsmodule/p256-x86_64-asm.S",
+  "mac-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.S",
+  "mac-x86_64/crypto/fipsmodule/rdrand-x86_64.S",
+  "mac-x86_64/crypto/fipsmodule/rsaz-avx2.S",
+  "mac-x86_64/crypto/fipsmodule/sha1-x86_64.S",
+  "mac-x86_64/crypto/fipsmodule/sha256-x86_64.S",
+  "mac-x86_64/crypto/fipsmodule/sha512-x86_64.S",
+  "mac-x86_64/crypto/fipsmodule/vpaes-x86_64.S",
+  "mac-x86_64/crypto/fipsmodule/x86_64-mont.S",
+  "mac-x86_64/crypto/fipsmodule/x86_64-mont5.S",
+  "mac-x86_64/crypto/test/trampoline-x86_64.S",
+]
+
+crypto_sources_win_x86 = [
+  "win-x86/crypto/chacha/chacha-x86.asm",
+  "win-x86/crypto/fipsmodule/aesni-x86.asm",
+  "win-x86/crypto/fipsmodule/bn-586.asm",
+  "win-x86/crypto/fipsmodule/co-586.asm",
+  "win-x86/crypto/fipsmodule/ghash-ssse3-x86.asm",
+  "win-x86/crypto/fipsmodule/ghash-x86.asm",
+  "win-x86/crypto/fipsmodule/md5-586.asm",
+  "win-x86/crypto/fipsmodule/sha1-586.asm",
+  "win-x86/crypto/fipsmodule/sha256-586.asm",
+  "win-x86/crypto/fipsmodule/sha512-586.asm",
+  "win-x86/crypto/fipsmodule/vpaes-x86.asm",
+  "win-x86/crypto/fipsmodule/x86-mont.asm",
+  "win-x86/crypto/test/trampoline-x86.asm",
+]
+
+crypto_sources_win_x86_64 = [
+  "win-x86_64/crypto/chacha/chacha-x86_64.asm",
+  "win-x86_64/crypto/cipher_extra/aes128gcmsiv-x86_64.asm",
+  "win-x86_64/crypto/cipher_extra/chacha20_poly1305_x86_64.asm",
+  "win-x86_64/crypto/fipsmodule/aesni-gcm-x86_64.asm",
+  "win-x86_64/crypto/fipsmodule/aesni-x86_64.asm",
+  "win-x86_64/crypto/fipsmodule/ghash-ssse3-x86_64.asm",
+  "win-x86_64/crypto/fipsmodule/ghash-x86_64.asm",
+  "win-x86_64/crypto/fipsmodule/md5-x86_64.asm",
+  "win-x86_64/crypto/fipsmodule/p256-x86_64-asm.asm",
+  "win-x86_64/crypto/fipsmodule/p256_beeu-x86_64-asm.asm",
+  "win-x86_64/crypto/fipsmodule/rdrand-x86_64.asm",
+  "win-x86_64/crypto/fipsmodule/rsaz-avx2.asm",
+  "win-x86_64/crypto/fipsmodule/sha1-x86_64.asm",
+  "win-x86_64/crypto/fipsmodule/sha256-x86_64.asm",
+  "win-x86_64/crypto/fipsmodule/sha512-x86_64.asm",
+  "win-x86_64/crypto/fipsmodule/vpaes-x86_64.asm",
+  "win-x86_64/crypto/fipsmodule/x86_64-mont.asm",
+  "win-x86_64/crypto/fipsmodule/x86_64-mont5.asm",
+  "win-x86_64/crypto/test/trampoline-x86_64.asm",
+]
+
+fuzzers = [
+  "arm_cpuinfo",
+  "bn_div",
+  "bn_mod_exp",
+  "cert",
+  "client",
+  "dtls_client",
+  "dtls_server",
+  "pkcs12",
+  "pkcs8",
+  "privkey",
+  "read_pem",
+  "server",
+  "session",
+  "spki",
+  "ssl_ctx_api",
+]
diff --git a/third_party/boringssl/BUILD.gn b/third_party/boringssl/BUILD.gn
new file mode 100644
index 0000000..bde1a77
--- /dev/null
+++ b/third_party/boringssl/BUILD.gn
@@ -0,0 +1,46 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("//build_overrides/pigweed.gni")
+import("//third_party/boringssl/BUILD.generated.gni")
+import("$dir_pw_build/target_types.gni")
+
+config("external_config") {
+  include_dirs = [ "src/include" ]
+  cflags = [ "-Wno-cast-qual" ]
+  cflags_c = [ "-std=c17" ]
+}
+
+config("internal_config") {
+  visibility = [ ":*" ]  # Only targets in this file can depend on this.
+  defines = [
+    "BORINGSSL_ALLOW_CXX_RUNTIME",
+    "BORINGSSL_IMPLEMENTATION",
+    "BORINGSSL_NO_STATIC_INITIALIZER",
+    "OPENSSL_SMALL",
+    "OPENSSL_NO_ASM",
+    "OPENSSL_NO_THREADS_CORRUPT_MEMORY_AND_LEAK_SECRETS_IF_THREADED",
+  ]
+  cflags_c = [
+    "-Wno-unused-parameter",
+    "-no-pedantic",
+  ]
+}
+
+pw_static_library("crypto") {
+  sources = crypto_sources
+  public = crypto_headers
+  public_configs = [ ":external_config" ]
+  configs = [ ":internal_config" ]
+}
diff --git a/third_party/boringssl/README.md b/third_party/boringssl/README.md
new file mode 100644
index 0000000..bbeb404
--- /dev/null
+++ b/third_party/boringssl/README.md
@@ -0,0 +1,9 @@
+# Updating boringssl
+
+The boringssl repo is configured as a git submodule in
+`third_party/boringssl/src`. After updating the submodule, run:
+
+```
+cd third_party/boringssl
+python2 src/util/generate_build_files.py gn
+```
diff --git a/third_party/boringssl/err_data.c b/third_party/boringssl/err_data.c
new file mode 100644
index 0000000..4d7e6ab
--- /dev/null
+++ b/third_party/boringssl/err_data.c
@@ -0,0 +1,1453 @@
+/* Copyright (c) 2015, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+ /* This file was generated by err_data_generate.go. */
+
+#include <openssl/base.h>
+#include <openssl/err.h>
+#include <openssl/type_check.h>
+
+
+OPENSSL_STATIC_ASSERT(ERR_LIB_NONE == 1, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_SYS == 2, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_BN == 3, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_RSA == 4, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_DH == 5, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_EVP == 6, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_BUF == 7, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_OBJ == 8, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_PEM == 9, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_DSA == 10, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_X509 == 11, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_ASN1 == 12, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_CONF == 13, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_CRYPTO == 14, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_EC == 15, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_SSL == 16, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_BIO == 17, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_PKCS7 == 18, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_PKCS8 == 19, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_X509V3 == 20, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_RAND == 21, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_ENGINE == 22, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_OCSP == 23, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_UI == 24, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_COMP == 25, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_ECDSA == 26, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_ECDH == 27, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_HMAC == 28, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_DIGEST == 29, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_CIPHER == 30, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_HKDF == 31, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_TRUST_TOKEN == 32, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_LIB_USER == 33, "library value changed");
+OPENSSL_STATIC_ASSERT(ERR_NUM_LIBS == 34, "number of libraries changed");
+
+const uint32_t kOpenSSLReasonValues[] = {
+    0xc32083a,
+    0xc328854,
+    0xc330863,
+    0xc338873,
+    0xc340882,
+    0xc34889b,
+    0xc3508a7,
+    0xc3588c4,
+    0xc3608e4,
+    0xc3688f2,
+    0xc370902,
+    0xc37890f,
+    0xc38091f,
+    0xc38892a,
+    0xc390940,
+    0xc39894f,
+    0xc3a0963,
+    0xc3a8847,
+    0xc3b00ea,
+    0xc3b88d6,
+    0x10320847,
+    0x103295a9,
+    0x103315b5,
+    0x103395ce,
+    0x103415e1,
+    0x10348f27,
+    0x10350c60,
+    0x103595f4,
+    0x1036161e,
+    0x10369631,
+    0x10371650,
+    0x10379669,
+    0x1038167e,
+    0x1038969c,
+    0x103916ab,
+    0x103996c7,
+    0x103a16e2,
+    0x103a96f1,
+    0x103b170d,
+    0x103b9728,
+    0x103c174e,
+    0x103c80ea,
+    0x103d175f,
+    0x103d9773,
+    0x103e1792,
+    0x103e97a1,
+    0x103f17b8,
+    0x103f97cb,
+    0x10400c24,
+    0x104097de,
+    0x104117fc,
+    0x1041980f,
+    0x10421829,
+    0x10429839,
+    0x1043184d,
+    0x10439863,
+    0x1044187b,
+    0x10449890,
+    0x104518a4,
+    0x104598b6,
+    0x104605fd,
+    0x1046894f,
+    0x104718cb,
+    0x104798e2,
+    0x104818f7,
+    0x10489905,
+    0x10490e73,
+    0x1049973f,
+    0x104a1609,
+    0x14320c07,
+    0x14328c15,
+    0x14330c24,
+    0x14338c36,
+    0x143400ac,
+    0x143480ea,
+    0x18320083,
+    0x18328f7d,
+    0x183300ac,
+    0x18338f93,
+    0x18340fa7,
+    0x183480ea,
+    0x18350fc6,
+    0x18358fde,
+    0x18360ff3,
+    0x18369007,
+    0x1837102b,
+    0x18379041,
+    0x18381055,
+    0x18389065,
+    0x18390a75,
+    0x18399075,
+    0x183a109b,
+    0x183a90c1,
+    0x183b0c7f,
+    0x183b9110,
+    0x183c1122,
+    0x183c912d,
+    0x183d113d,
+    0x183d914e,
+    0x183e115f,
+    0x183e9171,
+    0x183f119a,
+    0x183f91b3,
+    0x184011cb,
+    0x184086d5,
+    0x184110e4,
+    0x184190af,
+    0x184210ce,
+    0x18428c6c,
+    0x1843108a,
+    0x184390f6,
+    0x18440fbc,
+    0x20321205,
+    0x203291f2,
+    0x24321211,
+    0x24328995,
+    0x24331223,
+    0x24339230,
+    0x2434123d,
+    0x2434924f,
+    0x2435125e,
+    0x2435927b,
+    0x24361288,
+    0x24369296,
+    0x243712a4,
+    0x243792b2,
+    0x243812bb,
+    0x243892c8,
+    0x243912db,
+    0x28320c54,
+    0x28328c7f,
+    0x28330c24,
+    0x28338c92,
+    0x28340c60,
+    0x283480ac,
+    0x283500ea,
+    0x28358c6c,
+    0x2c323095,
+    0x2c3292f2,
+    0x2c3330a3,
+    0x2c33b0b5,
+    0x2c3430c9,
+    0x2c34b0db,
+    0x2c3530f6,
+    0x2c35b108,
+    0x2c363138,
+    0x2c36832d,
+    0x2c373145,
+    0x2c37b171,
+    0x2c383196,
+    0x2c38b1ad,
+    0x2c3931cb,
+    0x2c39b1db,
+    0x2c3a31ed,
+    0x2c3ab201,
+    0x2c3b3212,
+    0x2c3bb231,
+    0x2c3c1304,
+    0x2c3c931a,
+    0x2c3d3245,
+    0x2c3d9333,
+    0x2c3e3262,
+    0x2c3eb270,
+    0x2c3f3288,
+    0x2c3fb2a0,
+    0x2c4032ca,
+    0x2c409205,
+    0x2c4132db,
+    0x2c41b2ee,
+    0x2c4211cb,
+    0x2c42b2ff,
+    0x2c430722,
+    0x2c43b223,
+    0x2c443184,
+    0x2c44b2ad,
+    0x2c45311b,
+    0x2c45b157,
+    0x2c4631bb,
+    0x30320000,
+    0x30328015,
+    0x3033001f,
+    0x30338038,
+    0x3034004a,
+    0x30348064,
+    0x3035006b,
+    0x30358083,
+    0x30360094,
+    0x303680ac,
+    0x303700b9,
+    0x303780c8,
+    0x303800ea,
+    0x303880f7,
+    0x3039010a,
+    0x30398125,
+    0x303a013a,
+    0x303a814e,
+    0x303b0162,
+    0x303b8173,
+    0x303c018c,
+    0x303c81a9,
+    0x303d01b7,
+    0x303d81cb,
+    0x303e01db,
+    0x303e81f4,
+    0x303f0204,
+    0x303f8217,
+    0x30400226,
+    0x30408232,
+    0x30410247,
+    0x30418257,
+    0x3042026e,
+    0x3042827b,
+    0x3043028e,
+    0x3043829d,
+    0x304402b2,
+    0x304482d3,
+    0x304502e6,
+    0x304582f9,
+    0x30460312,
+    0x3046832d,
+    0x3047034a,
+    0x3047835c,
+    0x3048036a,
+    0x3048837b,
+    0x3049038a,
+    0x304983a2,
+    0x304a03b4,
+    0x304a83c8,
+    0x304b03e0,
+    0x304b83f3,
+    0x304c03fe,
+    0x304c840f,
+    0x304d041b,
+    0x304d8431,
+    0x304e043f,
+    0x304e8455,
+    0x304f0467,
+    0x304f8479,
+    0x3050049c,
+    0x305084af,
+    0x305104c0,
+    0x305184d0,
+    0x305204e8,
+    0x305284fd,
+    0x30530515,
+    0x30538529,
+    0x30540541,
+    0x3054855a,
+    0x30550573,
+    0x30558590,
+    0x3056059b,
+    0x305685b3,
+    0x305705c3,
+    0x305785d4,
+    0x305805e7,
+    0x305885fd,
+    0x30590606,
+    0x3059861b,
+    0x305a062e,
+    0x305a863d,
+    0x305b065d,
+    0x305b866c,
+    0x305c068d,
+    0x305c86a9,
+    0x305d06b5,
+    0x305d86d5,
+    0x305e06f1,
+    0x305e8702,
+    0x305f0718,
+    0x305f8722,
+    0x3060048c,
+    0x34320b65,
+    0x34328b79,
+    0x34330b96,
+    0x34338ba9,
+    0x34340bb8,
+    0x34348bf1,
+    0x34350bd5,
+    0x3c320083,
+    0x3c328cbc,
+    0x3c330cd5,
+    0x3c338cf0,
+    0x3c340d0d,
+    0x3c348d37,
+    0x3c350d52,
+    0x3c358d78,
+    0x3c360d91,
+    0x3c368da9,
+    0x3c370dba,
+    0x3c378dc8,
+    0x3c380dd5,
+    0x3c388de9,
+    0x3c390c7f,
+    0x3c398e0c,
+    0x3c3a0e20,
+    0x3c3a890f,
+    0x3c3b0e30,
+    0x3c3b8e4b,
+    0x3c3c0e5d,
+    0x3c3c8e90,
+    0x3c3d0e9a,
+    0x3c3d8eae,
+    0x3c3e0ebc,
+    0x3c3e8ee1,
+    0x3c3f0ca8,
+    0x3c3f8eca,
+    0x3c4000ac,
+    0x3c4080ea,
+    0x3c410d28,
+    0x3c418d67,
+    0x3c420e73,
+    0x3c428dfd,
+    0x4032197b,
+    0x40329991,
+    0x403319bf,
+    0x403399c9,
+    0x403419e0,
+    0x403499fe,
+    0x40351a0e,
+    0x40359a20,
+    0x40361a2d,
+    0x40369a39,
+    0x40371a4e,
+    0x40379a60,
+    0x40381a6b,
+    0x40389a7d,
+    0x40390f27,
+    0x40399a8d,
+    0x403a1aa0,
+    0x403a9ac1,
+    0x403b1ad2,
+    0x403b9ae2,
+    0x403c0064,
+    0x403c8083,
+    0x403d1b43,
+    0x403d9b59,
+    0x403e1b68,
+    0x403e9ba0,
+    0x403f1bba,
+    0x403f9be2,
+    0x40401bf7,
+    0x40409c0b,
+    0x40411c46,
+    0x40419c61,
+    0x40421c7a,
+    0x40429c8d,
+    0x40431ca1,
+    0x40439cb9,
+    0x40441cd0,
+    0x404480ac,
+    0x40451ce5,
+    0x40459cf7,
+    0x40461d1b,
+    0x40469d3b,
+    0x40471d49,
+    0x40479d70,
+    0x40481de1,
+    0x40489e14,
+    0x40491e2b,
+    0x40499e45,
+    0x404a1e5c,
+    0x404a9e7a,
+    0x404b1e92,
+    0x404b9ebf,
+    0x404c1ed5,
+    0x404c9ee7,
+    0x404d1f08,
+    0x404d9f41,
+    0x404e1f55,
+    0x404e9f62,
+    0x404f1fa9,
+    0x404f9fef,
+    0x40502046,
+    0x4050a05a,
+    0x4051208d,
+    0x405220aa,
+    0x4052a0ce,
+    0x405320e6,
+    0x4053a0f9,
+    0x4054210e,
+    0x4054a131,
+    0x4055213f,
+    0x4055a17c,
+    0x40562189,
+    0x4056a1a2,
+    0x405721ba,
+    0x4057a1cd,
+    0x405821e2,
+    0x4058a209,
+    0x40592238,
+    0x4059a265,
+    0x405a2279,
+    0x405aa289,
+    0x405b22a1,
+    0x405ba2b2,
+    0x405c22c5,
+    0x405ca304,
+    0x405d2311,
+    0x405da336,
+    0x405e2374,
+    0x405e8ab3,
+    0x405f2395,
+    0x405fa3a2,
+    0x406023b0,
+    0x4060a3d2,
+    0x40612433,
+    0x4061a46b,
+    0x40622482,
+    0x4062a493,
+    0x406324e0,
+    0x4063a4f5,
+    0x4064250c,
+    0x4064a538,
+    0x40652553,
+    0x4065a56a,
+    0x40662582,
+    0x4066a5ac,
+    0x406725d7,
+    0x4067a61c,
+    0x40682664,
+    0x4068a685,
+    0x406926b7,
+    0x4069a6e5,
+    0x406a2706,
+    0x406aa726,
+    0x406b28ae,
+    0x406ba8d1,
+    0x406c28e7,
+    0x406cabd8,
+    0x406d2c07,
+    0x406dac2f,
+    0x406e2c5d,
+    0x406eacaa,
+    0x406f2d03,
+    0x406fad3b,
+    0x40702d4e,
+    0x4070ad6b,
+    0x40710802,
+    0x4071ad7d,
+    0x40722d90,
+    0x4072adc6,
+    0x40732dde,
+    0x40739504,
+    0x40742df2,
+    0x4074ae0c,
+    0x40752e1d,
+    0x4075ae31,
+    0x40762e3f,
+    0x407692c8,
+    0x40772e64,
+    0x4077ae86,
+    0x40782ea1,
+    0x4078aeda,
+    0x40792ef1,
+    0x4079af07,
+    0x407a2f33,
+    0x407aaf46,
+    0x407b2f5b,
+    0x407baf6d,
+    0x407c2f9e,
+    0x407cafa7,
+    0x407d26a0,
+    0x407d9fff,
+    0x407e2eb6,
+    0x407ea219,
+    0x407f1d5d,
+    0x407f9ea9,
+    0x40801fb9,
+    0x40809d85,
+    0x408120bc,
+    0x40819f93,
+    0x40822c48,
+    0x40829aee,
+    0x408321f4,
+    0x4083a51d,
+    0x40841d99,
+    0x4084a251,
+    0x408522d6,
+    0x4085a3fa,
+    0x40862356,
+    0x4086a019,
+    0x40872c8e,
+    0x4087a448,
+    0x40881b2c,
+    0x4088a62f,
+    0x40891b7b,
+    0x40899b08,
+    0x408a291f,
+    0x408a991c,
+    0x408b2f82,
+    0x408bad18,
+    0x408c22e6,
+    0x408c9938,
+    0x408d1dfa,
+    0x408d9dcb,
+    0x408e1f2a,
+    0x408ea15c,
+    0x408f2643,
+    0x408fa416,
+    0x409025f8,
+    0x4090a328,
+    0x40912907,
+    0x4091995e,
+    0x40921bc8,
+    0x4092acc9,
+    0x40932da9,
+    0x4093a02a,
+    0x40941dad,
+    0x4094a938,
+    0x409524a4,
+    0x4095af13,
+    0x40962c75,
+    0x40969fd2,
+    0x40972075,
+    0x40979f79,
+    0x40981c28,
+    0x4098a4b8,
+    0x40992ce5,
+    0x4099a09d,
+    0x41f427d9,
+    0x41f9286b,
+    0x41fe275e,
+    0x41feaa14,
+    0x41ff2b29,
+    0x420327f2,
+    0x42082814,
+    0x4208a850,
+    0x42092742,
+    0x4209a88a,
+    0x420a2799,
+    0x420aa779,
+    0x420b27b9,
+    0x420ba832,
+    0x420c2b45,
+    0x420ca948,
+    0x420d29fb,
+    0x420daa32,
+    0x42122a4c,
+    0x42172b0c,
+    0x4217aa8e,
+    0x421c2ab0,
+    0x421f2a6b,
+    0x42212bbd,
+    0x42262aef,
+    0x422b2b9b,
+    0x422ba9d6,
+    0x422c2b7d,
+    0x422ca989,
+    0x422d2962,
+    0x422dab5c,
+    0x422e29b5,
+    0x42302acb,
+    0x4432072d,
+    0x4432873c,
+    0x44330748,
+    0x44338756,
+    0x44340769,
+    0x4434877a,
+    0x44350781,
+    0x4435878b,
+    0x4436079e,
+    0x443687b4,
+    0x443707c6,
+    0x443787d3,
+    0x443807e2,
+    0x443887ea,
+    0x44390802,
+    0x44398810,
+    0x443a0823,
+    0x483212f2,
+    0x48329304,
+    0x4833131a,
+    0x48339333,
+    0x4c321358,
+    0x4c329368,
+    0x4c33137b,
+    0x4c33939b,
+    0x4c3400ac,
+    0x4c3480ea,
+    0x4c3513a7,
+    0x4c3593b5,
+    0x4c3613d1,
+    0x4c3693f7,
+    0x4c371406,
+    0x4c379414,
+    0x4c381429,
+    0x4c389435,
+    0x4c391455,
+    0x4c39947f,
+    0x4c3a1498,
+    0x4c3a94b1,
+    0x4c3b05fd,
+    0x4c3b94ca,
+    0x4c3c14dc,
+    0x4c3c94eb,
+    0x4c3d1504,
+    0x4c3d8c47,
+    0x4c3e1571,
+    0x4c3e9513,
+    0x4c3f1593,
+    0x4c3f92c8,
+    0x4c401529,
+    0x4c409344,
+    0x4c411561,
+    0x4c4193e4,
+    0x4c42154d,
+    0x50323311,
+    0x5032b320,
+    0x5033332b,
+    0x5033b33b,
+    0x50343354,
+    0x5034b36e,
+    0x5035337c,
+    0x5035b392,
+    0x503633a4,
+    0x5036b3ba,
+    0x503733d3,
+    0x5037b3e6,
+    0x503833fe,
+    0x5038b40f,
+    0x50393424,
+    0x5039b438,
+    0x503a3458,
+    0x503ab46e,
+    0x503b3486,
+    0x503bb498,
+    0x503c34b4,
+    0x503cb4cb,
+    0x503d34e4,
+    0x503db4fa,
+    0x503e3507,
+    0x503eb51d,
+    0x503f352f,
+    0x503f837b,
+    0x50403542,
+    0x5040b552,
+    0x5041356c,
+    0x5041b57b,
+    0x50423595,
+    0x5042b5b2,
+    0x504335c2,
+    0x5043b5d2,
+    0x504435e1,
+    0x50448431,
+    0x504535f5,
+    0x5045b613,
+    0x50463626,
+    0x5046b63c,
+    0x5047364e,
+    0x5047b663,
+    0x50483689,
+    0x5048b697,
+    0x504936aa,
+    0x5049b6bf,
+    0x504a36d5,
+    0x504ab6e5,
+    0x504b3705,
+    0x504bb718,
+    0x504c373b,
+    0x504cb769,
+    0x504d377b,
+    0x504db798,
+    0x504e37b3,
+    0x504eb7cf,
+    0x504f37e1,
+    0x504fb7f8,
+    0x50503807,
+    0x505086f1,
+    0x5051381a,
+    0x58320f65,
+    0x68320f27,
+    0x68328c7f,
+    0x68330c92,
+    0x68338f35,
+    0x68340f45,
+    0x683480ea,
+    0x6c320eed,
+    0x6c328c36,
+    0x6c330ef8,
+    0x6c338f11,
+    0x74320a1b,
+    0x743280ac,
+    0x74330c47,
+    0x78320980,
+    0x78328995,
+    0x783309a1,
+    0x78338083,
+    0x783409b0,
+    0x783489c5,
+    0x783509e4,
+    0x78358a06,
+    0x78360a1b,
+    0x78368a31,
+    0x78370a41,
+    0x78378a62,
+    0x78380a75,
+    0x78388a87,
+    0x78390a94,
+    0x78398ab3,
+    0x783a0ac8,
+    0x783a8ad6,
+    0x783b0ae0,
+    0x783b8af4,
+    0x783c0b0b,
+    0x783c8b20,
+    0x783d0b37,
+    0x783d8b4c,
+    0x783e0aa2,
+    0x783e8a54,
+    0x7c3211e1,
+    0x803213f7,
+    0x80328083,
+    0x80333064,
+    0x803380ac,
+    0x80343073,
+    0x8034afdb,
+    0x80352ff9,
+    0x8035b087,
+    0x8036303b,
+    0x8036afea,
+    0x8037302d,
+    0x8037afc8,
+    0x8038304e,
+    0x8038b00a,
+    0x8039301f,
+};
+
+const size_t kOpenSSLReasonValuesLen = sizeof(kOpenSSLReasonValues) / sizeof(kOpenSSLReasonValues[0]);
+
+const char kOpenSSLReasonStringData[] =
+    "ASN1_LENGTH_MISMATCH\0"
+    "AUX_ERROR\0"
+    "BAD_GET_ASN1_OBJECT_CALL\0"
+    "BAD_OBJECT_HEADER\0"
+    "BMPSTRING_IS_WRONG_LENGTH\0"
+    "BN_LIB\0"
+    "BOOLEAN_IS_WRONG_LENGTH\0"
+    "BUFFER_TOO_SMALL\0"
+    "CONTEXT_NOT_INITIALISED\0"
+    "DECODE_ERROR\0"
+    "DEPTH_EXCEEDED\0"
+    "DIGEST_AND_KEY_TYPE_NOT_SUPPORTED\0"
+    "ENCODE_ERROR\0"
+    "ERROR_GETTING_TIME\0"
+    "EXPECTING_AN_ASN1_SEQUENCE\0"
+    "EXPECTING_AN_INTEGER\0"
+    "EXPECTING_AN_OBJECT\0"
+    "EXPECTING_A_BOOLEAN\0"
+    "EXPECTING_A_TIME\0"
+    "EXPLICIT_LENGTH_MISMATCH\0"
+    "EXPLICIT_TAG_NOT_CONSTRUCTED\0"
+    "FIELD_MISSING\0"
+    "FIRST_NUM_TOO_LARGE\0"
+    "HEADER_TOO_LONG\0"
+    "ILLEGAL_BITSTRING_FORMAT\0"
+    "ILLEGAL_BOOLEAN\0"
+    "ILLEGAL_CHARACTERS\0"
+    "ILLEGAL_FORMAT\0"
+    "ILLEGAL_HEX\0"
+    "ILLEGAL_IMPLICIT_TAG\0"
+    "ILLEGAL_INTEGER\0"
+    "ILLEGAL_NESTED_TAGGING\0"
+    "ILLEGAL_NULL\0"
+    "ILLEGAL_NULL_VALUE\0"
+    "ILLEGAL_OBJECT\0"
+    "ILLEGAL_OPTIONAL_ANY\0"
+    "ILLEGAL_OPTIONS_ON_ITEM_TEMPLATE\0"
+    "ILLEGAL_TAGGED_ANY\0"
+    "ILLEGAL_TIME_VALUE\0"
+    "INTEGER_NOT_ASCII_FORMAT\0"
+    "INTEGER_TOO_LARGE_FOR_LONG\0"
+    "INVALID_BIT_STRING_BITS_LEFT\0"
+    "INVALID_BMPSTRING\0"
+    "INVALID_DIGIT\0"
+    "INVALID_MODIFIER\0"
+    "INVALID_NUMBER\0"
+    "INVALID_OBJECT_ENCODING\0"
+    "INVALID_SEPARATOR\0"
+    "INVALID_TIME_FORMAT\0"
+    "INVALID_UNIVERSALSTRING\0"
+    "INVALID_UTF8STRING\0"
+    "LIST_ERROR\0"
+    "MISSING_ASN1_EOS\0"
+    "MISSING_EOC\0"
+    "MISSING_SECOND_NUMBER\0"
+    "MISSING_VALUE\0"
+    "MSTRING_NOT_UNIVERSAL\0"
+    "MSTRING_WRONG_TAG\0"
+    "NESTED_ASN1_ERROR\0"
+    "NESTED_ASN1_STRING\0"
+    "NESTED_TOO_DEEP\0"
+    "NON_HEX_CHARACTERS\0"
+    "NOT_ASCII_FORMAT\0"
+    "NOT_ENOUGH_DATA\0"
+    "NO_MATCHING_CHOICE_TYPE\0"
+    "NULL_IS_WRONG_LENGTH\0"
+    "OBJECT_NOT_ASCII_FORMAT\0"
+    "ODD_NUMBER_OF_CHARS\0"
+    "SECOND_NUMBER_TOO_LARGE\0"
+    "SEQUENCE_LENGTH_MISMATCH\0"
+    "SEQUENCE_NOT_CONSTRUCTED\0"
+    "SEQUENCE_OR_SET_NEEDS_CONFIG\0"
+    "SHORT_LINE\0"
+    "STREAMING_NOT_SUPPORTED\0"
+    "STRING_TOO_LONG\0"
+    "STRING_TOO_SHORT\0"
+    "TAG_VALUE_TOO_HIGH\0"
+    "TIME_NOT_ASCII_FORMAT\0"
+    "TOO_LONG\0"
+    "TYPE_NOT_CONSTRUCTED\0"
+    "TYPE_NOT_PRIMITIVE\0"
+    "UNEXPECTED_EOC\0"
+    "UNIVERSALSTRING_IS_WRONG_LENGTH\0"
+    "UNKNOWN_FORMAT\0"
+    "UNKNOWN_MESSAGE_DIGEST_ALGORITHM\0"
+    "UNKNOWN_SIGNATURE_ALGORITHM\0"
+    "UNKNOWN_TAG\0"
+    "UNSUPPORTED_ANY_DEFINED_BY_TYPE\0"
+    "UNSUPPORTED_PUBLIC_KEY_TYPE\0"
+    "UNSUPPORTED_TYPE\0"
+    "WRONG_PUBLIC_KEY_TYPE\0"
+    "WRONG_TAG\0"
+    "WRONG_TYPE\0"
+    "BAD_FOPEN_MODE\0"
+    "BROKEN_PIPE\0"
+    "CONNECT_ERROR\0"
+    "ERROR_SETTING_NBIO\0"
+    "INVALID_ARGUMENT\0"
+    "IN_USE\0"
+    "KEEPALIVE\0"
+    "NBIO_CONNECT_ERROR\0"
+    "NO_HOSTNAME_SPECIFIED\0"
+    "NO_PORT_SPECIFIED\0"
+    "NO_SUCH_FILE\0"
+    "NULL_PARAMETER\0"
+    "SYS_LIB\0"
+    "UNABLE_TO_CREATE_SOCKET\0"
+    "UNINITIALIZED\0"
+    "UNSUPPORTED_METHOD\0"
+    "WRITE_TO_READ_ONLY_BIO\0"
+    "ARG2_LT_ARG3\0"
+    "BAD_ENCODING\0"
+    "BAD_RECIPROCAL\0"
+    "BIGNUM_TOO_LONG\0"
+    "BITS_TOO_SMALL\0"
+    "CALLED_WITH_EVEN_MODULUS\0"
+    "DIV_BY_ZERO\0"
+    "EXPAND_ON_STATIC_BIGNUM_DATA\0"
+    "INPUT_NOT_REDUCED\0"
+    "INVALID_INPUT\0"
+    "INVALID_RANGE\0"
+    "NEGATIVE_NUMBER\0"
+    "NOT_A_SQUARE\0"
+    "NOT_INITIALIZED\0"
+    "NO_INVERSE\0"
+    "PRIVATE_KEY_TOO_LARGE\0"
+    "P_IS_NOT_PRIME\0"
+    "TOO_MANY_ITERATIONS\0"
+    "TOO_MANY_TEMPORARY_VARIABLES\0"
+    "AES_KEY_SETUP_FAILED\0"
+    "BAD_DECRYPT\0"
+    "BAD_KEY_LENGTH\0"
+    "CTRL_NOT_IMPLEMENTED\0"
+    "CTRL_OPERATION_NOT_IMPLEMENTED\0"
+    "DATA_NOT_MULTIPLE_OF_BLOCK_LENGTH\0"
+    "INITIALIZATION_ERROR\0"
+    "INPUT_NOT_INITIALIZED\0"
+    "INVALID_AD_SIZE\0"
+    "INVALID_KEY_LENGTH\0"
+    "INVALID_NONCE\0"
+    "INVALID_NONCE_SIZE\0"
+    "INVALID_OPERATION\0"
+    "IV_TOO_LARGE\0"
+    "NO_CIPHER_SET\0"
+    "NO_DIRECTION_SET\0"
+    "OUTPUT_ALIASES_INPUT\0"
+    "TAG_TOO_LARGE\0"
+    "TOO_LARGE\0"
+    "UNSUPPORTED_AD_SIZE\0"
+    "UNSUPPORTED_INPUT_SIZE\0"
+    "UNSUPPORTED_KEY_SIZE\0"
+    "UNSUPPORTED_NONCE_SIZE\0"
+    "UNSUPPORTED_TAG_SIZE\0"
+    "WRONG_FINAL_BLOCK_LENGTH\0"
+    "LIST_CANNOT_BE_NULL\0"
+    "MISSING_CLOSE_SQUARE_BRACKET\0"
+    "MISSING_EQUAL_SIGN\0"
+    "NO_CLOSE_BRACE\0"
+    "UNABLE_TO_CREATE_NEW_SECTION\0"
+    "VARIABLE_EXPANSION_TOO_LONG\0"
+    "VARIABLE_HAS_NO_VALUE\0"
+    "BAD_GENERATOR\0"
+    "INVALID_PUBKEY\0"
+    "MODULUS_TOO_LARGE\0"
+    "NO_PRIVATE_VALUE\0"
+    "UNKNOWN_HASH\0"
+    "BAD_Q_VALUE\0"
+    "BAD_VERSION\0"
+    "INVALID_PARAMETERS\0"
+    "MISSING_PARAMETERS\0"
+    "NEED_NEW_SETUP_VALUES\0"
+    "BIGNUM_OUT_OF_RANGE\0"
+    "COORDINATES_OUT_OF_RANGE\0"
+    "D2I_ECPKPARAMETERS_FAILURE\0"
+    "EC_GROUP_NEW_BY_NAME_FAILURE\0"
+    "GROUP2PKPARAMETERS_FAILURE\0"
+    "GROUP_MISMATCH\0"
+    "I2D_ECPKPARAMETERS_FAILURE\0"
+    "INCOMPATIBLE_OBJECTS\0"
+    "INVALID_COFACTOR\0"
+    "INVALID_COMPRESSED_POINT\0"
+    "INVALID_COMPRESSION_BIT\0"
+    "INVALID_ENCODING\0"
+    "INVALID_FIELD\0"
+    "INVALID_FORM\0"
+    "INVALID_GROUP_ORDER\0"
+    "INVALID_PRIVATE_KEY\0"
+    "INVALID_SCALAR\0"
+    "MISSING_PRIVATE_KEY\0"
+    "NON_NAMED_CURVE\0"
+    "PKPARAMETERS2GROUP_FAILURE\0"
+    "POINT_AT_INFINITY\0"
+    "POINT_IS_NOT_ON_CURVE\0"
+    "PUBLIC_KEY_VALIDATION_FAILED\0"
+    "SLOT_FULL\0"
+    "UNDEFINED_GENERATOR\0"
+    "UNKNOWN_GROUP\0"
+    "UNKNOWN_ORDER\0"
+    "WRONG_CURVE_PARAMETERS\0"
+    "WRONG_ORDER\0"
+    "KDF_FAILED\0"
+    "POINT_ARITHMETIC_FAILURE\0"
+    "UNKNOWN_DIGEST_LENGTH\0"
+    "BAD_SIGNATURE\0"
+    "NOT_IMPLEMENTED\0"
+    "RANDOM_NUMBER_GENERATION_FAILED\0"
+    "OPERATION_NOT_SUPPORTED\0"
+    "COMMAND_NOT_SUPPORTED\0"
+    "DIFFERENT_KEY_TYPES\0"
+    "DIFFERENT_PARAMETERS\0"
+    "EMPTY_PSK\0"
+    "EXPECTING_AN_EC_KEY_KEY\0"
+    "EXPECTING_AN_RSA_KEY\0"
+    "EXPECTING_A_DSA_KEY\0"
+    "ILLEGAL_OR_UNSUPPORTED_PADDING_MODE\0"
+    "INVALID_DIGEST_LENGTH\0"
+    "INVALID_DIGEST_TYPE\0"
+    "INVALID_KEYBITS\0"
+    "INVALID_MGF1_MD\0"
+    "INVALID_PADDING_MODE\0"
+    "INVALID_PEER_KEY\0"
+    "INVALID_PSS_SALTLEN\0"
+    "INVALID_SIGNATURE\0"
+    "KEYS_NOT_SET\0"
+    "MEMORY_LIMIT_EXCEEDED\0"
+    "NOT_A_PRIVATE_KEY\0"
+    "NOT_XOF_OR_INVALID_LENGTH\0"
+    "NO_DEFAULT_DIGEST\0"
+    "NO_KEY_SET\0"
+    "NO_MDC2_SUPPORT\0"
+    "NO_NID_FOR_CURVE\0"
+    "NO_OPERATION_SET\0"
+    "NO_PARAMETERS_SET\0"
+    "OPERATION_NOT_SUPPORTED_FOR_THIS_KEYTYPE\0"
+    "OPERATON_NOT_INITIALIZED\0"
+    "UNKNOWN_PUBLIC_KEY_TYPE\0"
+    "UNSUPPORTED_ALGORITHM\0"
+    "OUTPUT_TOO_LARGE\0"
+    "INVALID_OID_STRING\0"
+    "UNKNOWN_NID\0"
+    "BAD_BASE64_DECODE\0"
+    "BAD_END_LINE\0"
+    "BAD_IV_CHARS\0"
+    "BAD_PASSWORD_READ\0"
+    "CIPHER_IS_NULL\0"
+    "ERROR_CONVERTING_PRIVATE_KEY\0"
+    "NOT_DEK_INFO\0"
+    "NOT_ENCRYPTED\0"
+    "NOT_PROC_TYPE\0"
+    "NO_START_LINE\0"
+    "READ_KEY\0"
+    "SHORT_HEADER\0"
+    "UNSUPPORTED_CIPHER\0"
+    "UNSUPPORTED_ENCRYPTION\0"
+    "BAD_PKCS7_VERSION\0"
+    "NOT_PKCS7_SIGNED_DATA\0"
+    "NO_CERTIFICATES_INCLUDED\0"
+    "NO_CRLS_INCLUDED\0"
+    "BAD_ITERATION_COUNT\0"
+    "BAD_PKCS12_DATA\0"
+    "BAD_PKCS12_VERSION\0"
+    "CIPHER_HAS_NO_OBJECT_IDENTIFIER\0"
+    "CRYPT_ERROR\0"
+    "ENCRYPT_ERROR\0"
+    "ERROR_SETTING_CIPHER_PARAMS\0"
+    "INCORRECT_PASSWORD\0"
+    "INVALID_CHARACTERS\0"
+    "KEYGEN_FAILURE\0"
+    "KEY_GEN_ERROR\0"
+    "METHOD_NOT_SUPPORTED\0"
+    "MISSING_MAC\0"
+    "MULTIPLE_PRIVATE_KEYS_IN_PKCS12\0"
+    "PKCS12_PUBLIC_KEY_INTEGRITY_NOT_SUPPORTED\0"
+    "PKCS12_TOO_DEEPLY_NESTED\0"
+    "PRIVATE_KEY_DECODE_ERROR\0"
+    "PRIVATE_KEY_ENCODE_ERROR\0"
+    "UNKNOWN_ALGORITHM\0"
+    "UNKNOWN_CIPHER\0"
+    "UNKNOWN_CIPHER_ALGORITHM\0"
+    "UNKNOWN_DIGEST\0"
+    "UNSUPPORTED_KEYLENGTH\0"
+    "UNSUPPORTED_KEY_DERIVATION_FUNCTION\0"
+    "UNSUPPORTED_OPTIONS\0"
+    "UNSUPPORTED_PRF\0"
+    "UNSUPPORTED_PRIVATE_KEY_ALGORITHM\0"
+    "UNSUPPORTED_SALT_TYPE\0"
+    "BAD_E_VALUE\0"
+    "BAD_FIXED_HEADER_DECRYPT\0"
+    "BAD_PAD_BYTE_COUNT\0"
+    "BAD_RSA_PARAMETERS\0"
+    "BLOCK_TYPE_IS_NOT_01\0"
+    "BLOCK_TYPE_IS_NOT_02\0"
+    "BN_NOT_INITIALIZED\0"
+    "CANNOT_RECOVER_MULTI_PRIME_KEY\0"
+    "CRT_PARAMS_ALREADY_GIVEN\0"
+    "CRT_VALUES_INCORRECT\0"
+    "DATA_LEN_NOT_EQUAL_TO_MOD_LEN\0"
+    "DATA_TOO_LARGE\0"
+    "DATA_TOO_LARGE_FOR_KEY_SIZE\0"
+    "DATA_TOO_LARGE_FOR_MODULUS\0"
+    "DATA_TOO_SMALL\0"
+    "DATA_TOO_SMALL_FOR_KEY_SIZE\0"
+    "DIGEST_TOO_BIG_FOR_RSA_KEY\0"
+    "D_E_NOT_CONGRUENT_TO_1\0"
+    "D_OUT_OF_RANGE\0"
+    "EMPTY_PUBLIC_KEY\0"
+    "FIRST_OCTET_INVALID\0"
+    "INCONSISTENT_SET_OF_CRT_VALUES\0"
+    "INTERNAL_ERROR\0"
+    "INVALID_MESSAGE_LENGTH\0"
+    "KEY_SIZE_TOO_SMALL\0"
+    "LAST_OCTET_INVALID\0"
+    "MUST_HAVE_AT_LEAST_TWO_PRIMES\0"
+    "NO_PUBLIC_EXPONENT\0"
+    "NULL_BEFORE_BLOCK_MISSING\0"
+    "N_NOT_EQUAL_P_Q\0"
+    "OAEP_DECODING_ERROR\0"
+    "ONLY_ONE_OF_P_Q_GIVEN\0"
+    "OUTPUT_BUFFER_TOO_SMALL\0"
+    "PADDING_CHECK_FAILED\0"
+    "PKCS_DECODING_ERROR\0"
+    "SLEN_CHECK_FAILED\0"
+    "SLEN_RECOVERY_FAILED\0"
+    "UNKNOWN_ALGORITHM_TYPE\0"
+    "UNKNOWN_PADDING_TYPE\0"
+    "VALUE_MISSING\0"
+    "WRONG_SIGNATURE_LENGTH\0"
+    "ALPN_MISMATCH_ON_EARLY_DATA\0"
+    "APPLICATION_DATA_INSTEAD_OF_HANDSHAKE\0"
+    "APPLICATION_DATA_ON_SHUTDOWN\0"
+    "APP_DATA_IN_HANDSHAKE\0"
+    "ATTEMPT_TO_REUSE_SESSION_IN_DIFFERENT_CONTEXT\0"
+    "BAD_ALERT\0"
+    "BAD_CHANGE_CIPHER_SPEC\0"
+    "BAD_DATA_RETURNED_BY_CALLBACK\0"
+    "BAD_DH_P_LENGTH\0"
+    "BAD_DIGEST_LENGTH\0"
+    "BAD_ECC_CERT\0"
+    "BAD_ECPOINT\0"
+    "BAD_HANDSHAKE_RECORD\0"
+    "BAD_HELLO_REQUEST\0"
+    "BAD_LENGTH\0"
+    "BAD_PACKET_LENGTH\0"
+    "BAD_RSA_ENCRYPT\0"
+    "BAD_SRTP_MKI_VALUE\0"
+    "BAD_SRTP_PROTECTION_PROFILE_LIST\0"
+    "BAD_SSL_FILETYPE\0"
+    "BAD_WRITE_RETRY\0"
+    "BIO_NOT_SET\0"
+    "BLOCK_CIPHER_PAD_IS_WRONG\0"
+    "CANNOT_HAVE_BOTH_PRIVKEY_AND_METHOD\0"
+    "CANNOT_PARSE_LEAF_CERT\0"
+    "CA_DN_LENGTH_MISMATCH\0"
+    "CA_DN_TOO_LONG\0"
+    "CCS_RECEIVED_EARLY\0"
+    "CERTIFICATE_AND_PRIVATE_KEY_MISMATCH\0"
+    "CERTIFICATE_VERIFY_FAILED\0"
+    "CERT_CB_ERROR\0"
+    "CERT_DECOMPRESSION_FAILED\0"
+    "CERT_LENGTH_MISMATCH\0"
+    "CHANNEL_ID_NOT_P256\0"
+    "CHANNEL_ID_SIGNATURE_INVALID\0"
+    "CIPHER_MISMATCH_ON_EARLY_DATA\0"
+    "CIPHER_OR_HASH_UNAVAILABLE\0"
+    "CLIENTHELLO_PARSE_FAILED\0"
+    "CLIENTHELLO_TLSEXT\0"
+    "CONNECTION_REJECTED\0"
+    "CONNECTION_TYPE_NOT_SET\0"
+    "CUSTOM_EXTENSION_ERROR\0"
+    "DATA_LENGTH_TOO_LONG\0"
+    "DECRYPTION_FAILED\0"
+    "DECRYPTION_FAILED_OR_BAD_RECORD_MAC\0"
+    "DH_PUBLIC_VALUE_LENGTH_IS_WRONG\0"
+    "DH_P_TOO_LONG\0"
+    "DIGEST_CHECK_FAILED\0"
+    "DOWNGRADE_DETECTED\0"
+    "DTLS_MESSAGE_TOO_BIG\0"
+    "DUPLICATE_EXTENSION\0"
+    "DUPLICATE_KEY_SHARE\0"
+    "DUPLICATE_SIGNATURE_ALGORITHM\0"
+    "EARLY_DATA_NOT_IN_USE\0"
+    "ECC_CERT_NOT_FOR_SIGNING\0"
+    "EMPTY_HELLO_RETRY_REQUEST\0"
+    "EMS_STATE_INCONSISTENT\0"
+    "ENCRYPTED_LENGTH_TOO_LONG\0"
+    "ERROR_ADDING_EXTENSION\0"
+    "ERROR_IN_RECEIVED_CIPHER_LIST\0"
+    "ERROR_PARSING_EXTENSION\0"
+    "EXCESSIVE_MESSAGE_SIZE\0"
+    "EXCESS_HANDSHAKE_DATA\0"
+    "EXTRA_DATA_IN_MESSAGE\0"
+    "FRAGMENT_MISMATCH\0"
+    "GOT_NEXT_PROTO_WITHOUT_EXTENSION\0"
+    "HANDSHAKE_FAILURE_ON_CLIENT_HELLO\0"
+    "HANDSHAKE_NOT_COMPLETE\0"
+    "HTTPS_PROXY_REQUEST\0"
+    "HTTP_REQUEST\0"
+    "INAPPROPRIATE_FALLBACK\0"
+    "INCONSISTENT_CLIENT_HELLO\0"
+    "INVALID_ALPN_PROTOCOL\0"
+    "INVALID_COMMAND\0"
+    "INVALID_COMPRESSION_LIST\0"
+    "INVALID_DELEGATED_CREDENTIAL\0"
+    "INVALID_MESSAGE\0"
+    "INVALID_OUTER_RECORD_TYPE\0"
+    "INVALID_SCT_LIST\0"
+    "INVALID_SIGNATURE_ALGORITHM\0"
+    "INVALID_SSL_SESSION\0"
+    "INVALID_TICKET_KEYS_LENGTH\0"
+    "KEY_USAGE_BIT_INCORRECT\0"
+    "LENGTH_MISMATCH\0"
+    "MISSING_ALPN\0"
+    "MISSING_EXTENSION\0"
+    "MISSING_KEY_SHARE\0"
+    "MISSING_RSA_CERTIFICATE\0"
+    "MISSING_TMP_DH_KEY\0"
+    "MISSING_TMP_ECDH_KEY\0"
+    "MIXED_SPECIAL_OPERATOR_WITH_GROUPS\0"
+    "MTU_TOO_SMALL\0"
+    "NEGOTIATED_BOTH_NPN_AND_ALPN\0"
+    "NEGOTIATED_TB_WITHOUT_EMS_OR_RI\0"
+    "NESTED_GROUP\0"
+    "NO_CERTIFICATES_RETURNED\0"
+    "NO_CERTIFICATE_ASSIGNED\0"
+    "NO_CERTIFICATE_SET\0"
+    "NO_CIPHERS_AVAILABLE\0"
+    "NO_CIPHERS_PASSED\0"
+    "NO_CIPHERS_SPECIFIED\0"
+    "NO_CIPHER_MATCH\0"
+    "NO_COMMON_SIGNATURE_ALGORITHMS\0"
+    "NO_COMPRESSION_SPECIFIED\0"
+    "NO_GROUPS_SPECIFIED\0"
+    "NO_METHOD_SPECIFIED\0"
+    "NO_P256_SUPPORT\0"
+    "NO_PRIVATE_KEY_ASSIGNED\0"
+    "NO_RENEGOTIATION\0"
+    "NO_REQUIRED_DIGEST\0"
+    "NO_SHARED_CIPHER\0"
+    "NO_SHARED_GROUP\0"
+    "NO_SUPPORTED_VERSIONS_ENABLED\0"
+    "NULL_SSL_CTX\0"
+    "NULL_SSL_METHOD_PASSED\0"
+    "OCSP_CB_ERROR\0"
+    "OLD_SESSION_CIPHER_NOT_RETURNED\0"
+    "OLD_SESSION_PRF_HASH_MISMATCH\0"
+    "OLD_SESSION_VERSION_NOT_RETURNED\0"
+    "PARSE_TLSEXT\0"
+    "PATH_TOO_LONG\0"
+    "PEER_DID_NOT_RETURN_A_CERTIFICATE\0"
+    "PEER_ERROR_UNSUPPORTED_CERTIFICATE_TYPE\0"
+    "PRE_SHARED_KEY_MUST_BE_LAST\0"
+    "PRIVATE_KEY_OPERATION_FAILED\0"
+    "PROTOCOL_IS_SHUTDOWN\0"
+    "PSK_IDENTITY_BINDER_COUNT_MISMATCH\0"
+    "PSK_IDENTITY_NOT_FOUND\0"
+    "PSK_NO_CLIENT_CB\0"
+    "PSK_NO_SERVER_CB\0"
+    "QUIC_INTERNAL_ERROR\0"
+    "QUIC_TRANSPORT_PARAMETERS_MISCONFIGURED\0"
+    "READ_TIMEOUT_EXPIRED\0"
+    "RECORD_LENGTH_MISMATCH\0"
+    "RECORD_TOO_LARGE\0"
+    "RENEGOTIATION_EMS_MISMATCH\0"
+    "RENEGOTIATION_ENCODING_ERR\0"
+    "RENEGOTIATION_MISMATCH\0"
+    "REQUIRED_CIPHER_MISSING\0"
+    "RESUMED_EMS_SESSION_WITHOUT_EMS_EXTENSION\0"
+    "RESUMED_NON_EMS_SESSION_WITH_EMS_EXTENSION\0"
+    "SCSV_RECEIVED_WHEN_RENEGOTIATING\0"
+    "SECOND_SERVERHELLO_VERSION_MISMATCH\0"
+    "SERVERHELLO_TLSEXT\0"
+    "SERVER_CERT_CHANGED\0"
+    "SERVER_ECHOED_INVALID_SESSION_ID\0"
+    "SESSION_ID_CONTEXT_UNINITIALIZED\0"
+    "SESSION_MAY_NOT_BE_CREATED\0"
+    "SHUTDOWN_WHILE_IN_INIT\0"
+    "SIGNATURE_ALGORITHMS_EXTENSION_SENT_BY_SERVER\0"
+    "SRTP_COULD_NOT_ALLOCATE_PROFILES\0"
+    "SRTP_UNKNOWN_PROTECTION_PROFILE\0"
+    "SSL3_EXT_INVALID_SERVERNAME\0"
+    "SSLV3_ALERT_BAD_CERTIFICATE\0"
+    "SSLV3_ALERT_BAD_RECORD_MAC\0"
+    "SSLV3_ALERT_CERTIFICATE_EXPIRED\0"
+    "SSLV3_ALERT_CERTIFICATE_REVOKED\0"
+    "SSLV3_ALERT_CERTIFICATE_UNKNOWN\0"
+    "SSLV3_ALERT_CLOSE_NOTIFY\0"
+    "SSLV3_ALERT_DECOMPRESSION_FAILURE\0"
+    "SSLV3_ALERT_HANDSHAKE_FAILURE\0"
+    "SSLV3_ALERT_ILLEGAL_PARAMETER\0"
+    "SSLV3_ALERT_NO_CERTIFICATE\0"
+    "SSLV3_ALERT_UNEXPECTED_MESSAGE\0"
+    "SSLV3_ALERT_UNSUPPORTED_CERTIFICATE\0"
+    "SSL_CTX_HAS_NO_DEFAULT_SSL_VERSION\0"
+    "SSL_HANDSHAKE_FAILURE\0"
+    "SSL_SESSION_ID_CONTEXT_TOO_LONG\0"
+    "SSL_SESSION_ID_TOO_LONG\0"
+    "TICKET_ENCRYPTION_FAILED\0"
+    "TLS13_DOWNGRADE\0"
+    "TLSV1_ALERT_ACCESS_DENIED\0"
+    "TLSV1_ALERT_BAD_CERTIFICATE_HASH_VALUE\0"
+    "TLSV1_ALERT_BAD_CERTIFICATE_STATUS_RESPONSE\0"
+    "TLSV1_ALERT_CERTIFICATE_REQUIRED\0"
+    "TLSV1_ALERT_CERTIFICATE_UNOBTAINABLE\0"
+    "TLSV1_ALERT_DECODE_ERROR\0"
+    "TLSV1_ALERT_DECRYPTION_FAILED\0"
+    "TLSV1_ALERT_DECRYPT_ERROR\0"
+    "TLSV1_ALERT_EXPORT_RESTRICTION\0"
+    "TLSV1_ALERT_INAPPROPRIATE_FALLBACK\0"
+    "TLSV1_ALERT_INSUFFICIENT_SECURITY\0"
+    "TLSV1_ALERT_INTERNAL_ERROR\0"
+    "TLSV1_ALERT_NO_APPLICATION_PROTOCOL\0"
+    "TLSV1_ALERT_NO_RENEGOTIATION\0"
+    "TLSV1_ALERT_PROTOCOL_VERSION\0"
+    "TLSV1_ALERT_RECORD_OVERFLOW\0"
+    "TLSV1_ALERT_UNKNOWN_CA\0"
+    "TLSV1_ALERT_UNKNOWN_PSK_IDENTITY\0"
+    "TLSV1_ALERT_UNRECOGNIZED_NAME\0"
+    "TLSV1_ALERT_UNSUPPORTED_EXTENSION\0"
+    "TLSV1_ALERT_USER_CANCELLED\0"
+    "TLS_PEER_DID_NOT_RESPOND_WITH_CERTIFICATE_LIST\0"
+    "TLS_RSA_ENCRYPTED_VALUE_LENGTH_IS_WRONG\0"
+    "TOO_MANY_EMPTY_FRAGMENTS\0"
+    "TOO_MANY_KEY_UPDATES\0"
+    "TOO_MANY_WARNING_ALERTS\0"
+    "TOO_MUCH_READ_EARLY_DATA\0"
+    "TOO_MUCH_SKIPPED_EARLY_DATA\0"
+    "UNABLE_TO_FIND_ECDH_PARAMETERS\0"
+    "UNCOMPRESSED_CERT_TOO_LARGE\0"
+    "UNEXPECTED_COMPATIBILITY_MODE\0"
+    "UNEXPECTED_EXTENSION\0"
+    "UNEXPECTED_EXTENSION_ON_EARLY_DATA\0"
+    "UNEXPECTED_MESSAGE\0"
+    "UNEXPECTED_OPERATOR_IN_GROUP\0"
+    "UNEXPECTED_RECORD\0"
+    "UNKNOWN_ALERT_TYPE\0"
+    "UNKNOWN_CERTIFICATE_TYPE\0"
+    "UNKNOWN_CERT_COMPRESSION_ALG\0"
+    "UNKNOWN_CIPHER_RETURNED\0"
+    "UNKNOWN_CIPHER_TYPE\0"
+    "UNKNOWN_KEY_EXCHANGE_TYPE\0"
+    "UNKNOWN_PROTOCOL\0"
+    "UNKNOWN_SSL_VERSION\0"
+    "UNKNOWN_STATE\0"
+    "UNSAFE_LEGACY_RENEGOTIATION_DISABLED\0"
+    "UNSUPPORTED_COMPRESSION_ALGORITHM\0"
+    "UNSUPPORTED_ELLIPTIC_CURVE\0"
+    "UNSUPPORTED_PROTOCOL\0"
+    "UNSUPPORTED_PROTOCOL_FOR_CUSTOM_KEY\0"
+    "WRONG_CERTIFICATE_TYPE\0"
+    "WRONG_CIPHER_RETURNED\0"
+    "WRONG_CURVE\0"
+    "WRONG_ENCRYPTION_LEVEL_RECEIVED\0"
+    "WRONG_MESSAGE_TYPE\0"
+    "WRONG_SIGNATURE_TYPE\0"
+    "WRONG_SSL_VERSION\0"
+    "WRONG_VERSION_NUMBER\0"
+    "WRONG_VERSION_ON_EARLY_DATA\0"
+    "X509_LIB\0"
+    "X509_VERIFICATION_SETUP_PROBLEMS\0"
+    "BAD_VALIDITY_CHECK\0"
+    "DECODE_FAILURE\0"
+    "INVALID_KEY_ID\0"
+    "INVALID_METADATA\0"
+    "INVALID_METADATA_KEY\0"
+    "INVALID_PROOF\0"
+    "INVALID_TOKEN\0"
+    "NO_KEYS_CONFIGURED\0"
+    "NO_SRR_KEY_CONFIGURED\0"
+    "OVER_BATCHSIZE\0"
+    "SRR_SIGNATURE_ERROR\0"
+    "TOO_MANY_KEYS\0"
+    "AKID_MISMATCH\0"
+    "BAD_X509_FILETYPE\0"
+    "BASE64_DECODE_ERROR\0"
+    "CANT_CHECK_DH_KEY\0"
+    "CERT_ALREADY_IN_HASH_TABLE\0"
+    "CRL_ALREADY_DELTA\0"
+    "CRL_VERIFY_FAILURE\0"
+    "DELTA_CRL_WITHOUT_CRL_NUMBER\0"
+    "IDP_MISMATCH\0"
+    "INVALID_DIRECTORY\0"
+    "INVALID_FIELD_FOR_VERSION\0"
+    "INVALID_FIELD_NAME\0"
+    "INVALID_PARAMETER\0"
+    "INVALID_PSS_PARAMETERS\0"
+    "INVALID_TRUST\0"
+    "INVALID_VERSION\0"
+    "ISSUER_MISMATCH\0"
+    "KEY_TYPE_MISMATCH\0"
+    "KEY_VALUES_MISMATCH\0"
+    "LOADING_CERT_DIR\0"
+    "LOADING_DEFAULTS\0"
+    "NAME_TOO_LONG\0"
+    "NEWER_CRL_NOT_NEWER\0"
+    "NO_CERT_SET_FOR_US_TO_VERIFY\0"
+    "NO_CRL_NUMBER\0"
+    "PUBLIC_KEY_DECODE_ERROR\0"
+    "PUBLIC_KEY_ENCODE_ERROR\0"
+    "SHOULD_RETRY\0"
+    "SIGNATURE_ALGORITHM_MISMATCH\0"
+    "UNKNOWN_KEY_TYPE\0"
+    "UNKNOWN_PURPOSE_ID\0"
+    "UNKNOWN_TRUST_ID\0"
+    "WRONG_LOOKUP_TYPE\0"
+    "BAD_IP_ADDRESS\0"
+    "BAD_OBJECT\0"
+    "BN_DEC2BN_ERROR\0"
+    "BN_TO_ASN1_INTEGER_ERROR\0"
+    "CANNOT_FIND_FREE_FUNCTION\0"
+    "DIRNAME_ERROR\0"
+    "DISTPOINT_ALREADY_SET\0"
+    "DUPLICATE_ZONE_ID\0"
+    "ERROR_CONVERTING_ZONE\0"
+    "ERROR_CREATING_EXTENSION\0"
+    "ERROR_IN_EXTENSION\0"
+    "EXPECTED_A_SECTION_NAME\0"
+    "EXTENSION_EXISTS\0"
+    "EXTENSION_NAME_ERROR\0"
+    "EXTENSION_NOT_FOUND\0"
+    "EXTENSION_SETTING_NOT_SUPPORTED\0"
+    "EXTENSION_VALUE_ERROR\0"
+    "ILLEGAL_EMPTY_EXTENSION\0"
+    "ILLEGAL_HEX_DIGIT\0"
+    "INCORRECT_POLICY_SYNTAX_TAG\0"
+    "INVALID_BOOLEAN_STRING\0"
+    "INVALID_EXTENSION_STRING\0"
+    "INVALID_MULTIPLE_RDNS\0"
+    "INVALID_NAME\0"
+    "INVALID_NULL_ARGUMENT\0"
+    "INVALID_NULL_NAME\0"
+    "INVALID_NULL_VALUE\0"
+    "INVALID_NUMBERS\0"
+    "INVALID_OBJECT_IDENTIFIER\0"
+    "INVALID_OPTION\0"
+    "INVALID_POLICY_IDENTIFIER\0"
+    "INVALID_PROXY_POLICY_SETTING\0"
+    "INVALID_PURPOSE\0"
+    "INVALID_SECTION\0"
+    "INVALID_SYNTAX\0"
+    "ISSUER_DECODE_ERROR\0"
+    "NEED_ORGANIZATION_AND_NUMBERS\0"
+    "NO_CONFIG_DATABASE\0"
+    "NO_ISSUER_CERTIFICATE\0"
+    "NO_ISSUER_DETAILS\0"
+    "NO_POLICY_IDENTIFIER\0"
+    "NO_PROXY_CERT_POLICY_LANGUAGE_DEFINED\0"
+    "NO_PUBLIC_KEY\0"
+    "NO_SUBJECT_DETAILS\0"
+    "ODD_NUMBER_OF_DIGITS\0"
+    "OPERATION_NOT_DEFINED\0"
+    "OTHERNAME_ERROR\0"
+    "POLICY_LANGUAGE_ALREADY_DEFINED\0"
+    "POLICY_PATH_LENGTH\0"
+    "POLICY_PATH_LENGTH_ALREADY_DEFINED\0"
+    "POLICY_WHEN_PROXY_LANGUAGE_REQUIRES_NO_POLICY\0"
+    "SECTION_NOT_FOUND\0"
+    "UNABLE_TO_GET_ISSUER_DETAILS\0"
+    "UNABLE_TO_GET_ISSUER_KEYID\0"
+    "UNKNOWN_BIT_STRING_ARGUMENT\0"
+    "UNKNOWN_EXTENSION\0"
+    "UNKNOWN_EXTENSION_NAME\0"
+    "UNKNOWN_OPTION\0"
+    "UNSUPPORTED_OPTION\0"
+    "USER_TOO_LONG\0"
+    "";
+
diff --git a/third_party/boringssl/src b/third_party/boringssl/src
new file mode 160000
index 0000000..a673d02
--- /dev/null
+++ b/third_party/boringssl/src
@@ -0,0 +1 @@
+Subproject commit a673d02458b1b7d897084266b93d5c610e36bd17
diff --git a/third_party/cn-cbor/BUILD.gn b/third_party/cn-cbor/BUILD.gn
new file mode 100644
index 0000000..62423cf
--- /dev/null
+++ b/third_party/cn-cbor/BUILD.gn
@@ -0,0 +1,45 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("//build_overrides/pigweed.gni")
+import("$dir_pw_build/target_types.gni")
+
+config("external_config") {
+  include_dirs = [ "src/include" ]
+}
+
+config("internal_config") {
+  visibility = [ ":*" ]  # Only targets in this file can depend on this.
+  defines = [ "CBOR_NO_FLOAT" ]
+  cflags = [
+    "-Wno-cast-qual",
+    "-Wno-implicit-fallthrough",
+  ]
+  cflags_c = [ "-no-pedantic" ]
+}
+
+pw_static_library("cn-cbor") {
+  public = [ "src/include/cn-cbor/cn-cbor.h" ]
+  sources = [
+    "src/src/cn-cbor.c",
+    "src/src/cn-create.c",
+    "src/src/cn-encoder.c",
+    "src/src/cn-error.c",
+    "src/src/cn-get.c",
+    "src/src/cn-print.c",
+  ]
+
+  public_configs = [ ":external_config" ]
+  configs = [ ":internal_config" ]
+}
diff --git a/third_party/cn-cbor/src b/third_party/cn-cbor/src
new file mode 160000
index 0000000..f713bf6
--- /dev/null
+++ b/third_party/cn-cbor/src
@@ -0,0 +1 @@
+Subproject commit f713bf67bcf3e076d47e474ce060252ef8be48c7
diff --git a/third_party/cose-c/BUILD.gn b/third_party/cose-c/BUILD.gn
new file mode 100644
index 0000000..917e647
--- /dev/null
+++ b/third_party/cose-c/BUILD.gn
@@ -0,0 +1,46 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("//build_overrides/pigweed.gni")
+import("$dir_pw_build/target_types.gni")
+
+config("external_config") {
+  include_dirs = [
+    "src/include",
+    "include",
+  ]
+}
+
+config("internal_config") {
+  visibility = [ ":*" ]  # Only targets in this file can depend on this.
+  include_dirs = [ "src/src" ]
+  cflags = [ "-Wno-cast-qual" ]
+}
+
+pw_static_library("cose-c") {
+  public = [ "src/include/cose/cose.h" ]
+  sources = [
+    "cose_deps.cc",
+    "src/src/Cose.cpp",
+    "src/src/CoseKey.cpp",
+    "src/src/Sign1.cpp",
+    "src/src/cbor.cpp",
+  ]
+  public_configs = [ ":external_config" ]
+  configs = [ ":internal_config" ]
+  public_deps = [
+    "//third_party/boringssl:crypto",
+    "//third_party/cn-cbor:cn-cbor",
+  ]
+}
diff --git a/third_party/cose-c/cose_deps.cc b/third_party/cose-c/cose_deps.cc
new file mode 100644
index 0000000..986b2b0
--- /dev/null
+++ b/third_party/cose-c/cose_deps.cc
@@ -0,0 +1,104 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#include <stdint.h>
+#include <string.h>
+
+#include "cose/cose.h"
+#include "cose/cose_configure.h"
+#include "cose_int.h"
+#include "openssl/curve25519.h"
+#include "openssl/is_boringssl.h"
+
+// Gets the public key from a well-formed Ed25519 COSE_Key. On success populates
+// |public_key| and returns true.
+static bool GetPublicKeyFromCbor(const cn_cbor *key, uint8_t public_key[32]) {
+  const int64_t kCoseKeyAlgLabel = 3;
+  const int64_t kCoseKeyOpsLabel = 4;
+  const uint64_t kCoseKeyOpsVerify = 2;
+  const int64_t kCoseAlgEdDSA = -8;
+
+  // Mandatory attributes.
+  cn_cbor *type = cn_cbor_mapget_int(key, COSE_Key_Type);
+  cn_cbor *curve = cn_cbor_mapget_int(key, COSE_Key_OPK_Curve);
+  cn_cbor *x = cn_cbor_mapget_int(key, COSE_Key_OPK_X);
+  if (!type || !curve || !x) {
+    return false;
+  }
+  if (type->type != CN_CBOR_UINT || type->v.uint != COSE_Key_Type_OKP) {
+    return false;
+  }
+  if (curve->type != CN_CBOR_UINT || curve->v.uint != COSE_Curve_Ed25519) {
+    return false;
+  }
+  if (x->type != CN_CBOR_BYTES || x->length != 32) {
+    return false;
+  }
+  // Optional attributes.
+  cn_cbor *alg = cn_cbor_mapget_int(key, kCoseKeyAlgLabel);
+  if (alg) {
+    if (alg->type != CN_CBOR_INT || alg->v.sint != kCoseAlgEdDSA) {
+      return false;
+    }
+  }
+  cn_cbor *ops = cn_cbor_mapget_int(key, kCoseKeyOpsLabel);
+  if (ops) {
+    if (ops->type != CN_CBOR_ARRAY || ops->length == 0) {
+      return false;
+    }
+    bool found_verify = false;
+    for (size_t i = 0; i < ops->length; ++i) {
+      cn_cbor *item = cn_cbor_index(ops, i);
+      if (!item || item->type != CN_CBOR_UINT) {
+        return false;
+      }
+      if (item->v.uint == kCoseKeyOpsVerify) {
+        found_verify = true;
+      }
+    }
+    if (!found_verify) {
+      return false;
+    }
+  }
+
+  memcpy(public_key, x->v.bytes, 32);
+  return true;
+}
+
+// A simple implementation of 'EdDSA_Verify' using boringssl. This function is
+// required by 'COSE_Sign1_validate'.
+bool EdDSA_Verify(COSE *cose_signer, int signature_index, COSE_KEY *cose_key,
+                  const byte *message, size_t message_size, cose_errback *) {
+  cn_cbor *signature = _COSE_arrayget_int(cose_signer, signature_index);
+  cn_cbor *key = cose_key->m_cborKey;
+  if (!signature || !key) {
+    return false;
+  }
+  if (signature->type != CN_CBOR_BYTES || signature->length != 64) {
+    return false;
+  }
+  uint8_t public_key[32];
+  if (!GetPublicKeyFromCbor(key, public_key)) {
+    return false;
+  }
+  return (1 == ED25519_verify(message, message_size, signature->v.bytes,
+                              public_key));
+}
+
+// A stub for 'EdDSA_Sign'. This is unused, but helps make linkers happy.
+bool EdDSA_Sign(COSE * /*cose_signer*/, int /*signature_index*/,
+                COSE_KEY * /*cose_key*/, const byte * /*message*/,
+                size_t /*message_size*/, cose_errback *) {
+  return false;
+}
diff --git a/third_party/cose-c/include/cose/cose_configure.h b/third_party/cose-c/include/cose/cose_configure.h
new file mode 100644
index 0000000..c874231
--- /dev/null
+++ b/third_party/cose-c/include/cose/cose_configure.h
@@ -0,0 +1,10 @@
+#define USE_EDDSA
+
+#define INCLUDE_ENCRYPT 0
+#define INCLUDE_ENCRYPT0 0
+#define INCLUDE_MAC 0
+#define INCLUDE_MAC0 0
+#define INCLUDE_SIGN 0
+#define INCLUDE_SIGN1 1
+#define INCLUDE_COUNTERSIGNATURE 0
+#define INCLUDE_COUNTERSIGNATURE1 0
diff --git a/third_party/cose-c/src b/third_party/cose-c/src
new file mode 160000
index 0000000..97d1805
--- /dev/null
+++ b/third_party/cose-c/src
@@ -0,0 +1 @@
+Subproject commit 97d1805e71b7a6770093c5e6790d46611680d563
diff --git a/third_party/mbedtls/.clang-format b/third_party/mbedtls/.clang-format
new file mode 100644
index 0000000..fe98bac
--- /dev/null
+++ b/third_party/mbedtls/.clang-format
@@ -0,0 +1,5 @@
+---
+Language: Cpp
+DisableFormat: true
+SortIncludes: false
+---
diff --git a/third_party/mbedtls/0001-Mark-basic-constraints-critical-as-appropriate.patch b/third_party/mbedtls/0001-Mark-basic-constraints-critical-as-appropriate.patch
new file mode 100644
index 0000000..097eb17
--- /dev/null
+++ b/third_party/mbedtls/0001-Mark-basic-constraints-critical-as-appropriate.patch
@@ -0,0 +1,27 @@
+From d5cbe3484248ee5f44543b1b50604bcd5739cc85 Mon Sep 17 00:00:00 2001
+From: Darren Krahn <dkrahn@google.com>
+Date: Fri, 10 Jul 2020 17:03:57 -0700
+Subject: [PATCH] Mark basic constraints critical as appropriate.
+
+Per RFC 5280 4.2.1.9 if the 'cA' field is set to true, the extension
+must be marked critical.
+---
+ library/x509write_crt.c | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/library/x509write_crt.c b/library/x509write_crt.c
+index 32c655096..498b8b0a0 100644
+--- a/library/x509write_crt.c
++++ b/library/x509write_crt.c
+@@ -163,7 +163,7 @@ int mbedtls_x509write_crt_set_basic_constraints( mbedtls_x509write_cert *ctx,
+     return(
+         mbedtls_x509write_crt_set_extension( ctx, MBEDTLS_OID_BASIC_CONSTRAINTS,
+                              MBEDTLS_OID_SIZE( MBEDTLS_OID_BASIC_CONSTRAINTS ),
+-                             0, buf + sizeof(buf) - len, len ) );
++                             is_ca, buf + sizeof(buf) - len, len ) );
+ }
+ 
+ #if defined(MBEDTLS_SHA1_C)
+-- 
+2.29.0.rc1.297.gfa9743e501-goog
+
diff --git a/third_party/mbedtls/BUILD.gn b/third_party/mbedtls/BUILD.gn
new file mode 100644
index 0000000..e77c1b2
--- /dev/null
+++ b/third_party/mbedtls/BUILD.gn
@@ -0,0 +1,52 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("//build_overrides/pigweed.gni")
+import("$dir_pw_build/target_types.gni")
+
+config("external_config") {
+  include_dirs = [ "src/include" ]
+}
+
+config("internal_config") {
+  visibility = [ ":*" ]  # Only targets in this file can depend on this.
+  defines = [ "MBEDTLS_CONFIG_FILE=\"custom_config.h\"" ]
+  include_dirs = [ get_path_info(get_path_info("BUILD.gn", "abspath"), "dir") ]
+  cflags = [ "-Wno-cast-qual" ]
+}
+
+pw_static_library("mbedcrypto") {
+  sources = [
+    "src/library/asn1parse.c",
+    "src/library/asn1write.c",
+    "src/library/bignum.c",
+    "src/library/ecdsa.c",
+    "src/library/ecp.c",
+    "src/library/ecp_curves.c",
+    "src/library/hkdf.c",
+    "src/library/hmac_drbg.c",
+    "src/library/md.c",
+    "src/library/oid.c",
+    "src/library/pk.c",
+    "src/library/pk_wrap.c",
+    "src/library/pkwrite.c",
+    "src/library/platform_util.c",
+    "src/library/sha512.c",
+    "src/library/x509_create.c",
+    "src/library/x509write_crt.c",
+  ]
+
+  public_configs = [ ":external_config" ]
+  configs = [ ":internal_config" ]
+}
diff --git a/third_party/mbedtls/custom_config.h b/third_party/mbedtls/custom_config.h
new file mode 100644
index 0000000..ff71785
--- /dev/null
+++ b/third_party/mbedtls/custom_config.h
@@ -0,0 +1,42 @@
+// Copyright 2020 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License"); you may not
+// use this file except in compliance with the License. You may obtain a copy of
+// the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations under
+// the License.
+
+#ifndef MBEDTLS_CONFIG_H
+#define MBEDTLS_CONFIG_H
+
+/* mbed TLS feature support */
+#define MBEDTLS_DEPRECATED_REMOVED
+#define MBEDTLS_ECDSA_DETERMINISTIC
+#define MBEDTLS_ECP_DP_SECP256R1_ENABLED
+#define MBEDTLS_SHA512_NO_SHA384
+
+/* mbed TLS modules */
+#define MBEDTLS_ASN1_PARSE_C
+#define MBEDTLS_ASN1_WRITE_C
+#define MBEDTLS_BIGNUM_C
+#define MBEDTLS_ECDSA_C
+#define MBEDTLS_ECP_C
+#define MBEDTLS_HKDF_C
+#define MBEDTLS_HMAC_DRBG_C
+#define MBEDTLS_MD_C
+#define MBEDTLS_OID_C
+#define MBEDTLS_PK_C
+#define MBEDTLS_PK_WRITE_C
+#define MBEDTLS_SHA512_C
+#define MBEDTLS_X509_CREATE_C
+#define MBEDTLS_X509_CRT_WRITE_C
+
+#include "mbedtls/check_config.h"
+
+#endif /* MBEDTLS_CONFIG_H */
diff --git a/third_party/mbedtls/src b/third_party/mbedtls/src
new file mode 160000
index 0000000..523f055
--- /dev/null
+++ b/third_party/mbedtls/src
@@ -0,0 +1 @@
+Subproject commit 523f0554b6cdc7ace5d360885c3f5bbcc73ec0e8
diff --git a/third_party/pigweed/src b/third_party/pigweed/src
new file mode 160000
index 0000000..32a9c5a
--- /dev/null
+++ b/third_party/pigweed/src
@@ -0,0 +1 @@
+Subproject commit 32a9c5ad6edddcc3e2e76836082c3eed1e3e69c7
diff --git a/toolchains/BUILD.gn b/toolchains/BUILD.gn
new file mode 100644
index 0000000..6f74757
--- /dev/null
+++ b/toolchains/BUILD.gn
@@ -0,0 +1,123 @@
+# Copyright 2020 Google LLC
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may not
+# use this file except in compliance with the License. You may obtain a copy of
+# the License at
+#
+#     https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations under
+# the License.
+
+import("//build_overrides/pigweed.gni")
+import("$dir_pigweed/targets/host/target_toolchains.gni")
+import("$dir_pw_toolchain/generate_toolchain.gni")
+
+declare_args() {
+  # These aren't used but they are set by the Pigweed toolchain. Declaring them
+  # here silences gn warnings.
+  pw_trace_BACKEND = ""
+  pw_trace_tokenizer_time = ""
+}
+
+config("common_config") {
+  include_dirs = [ "//include" ]
+
+  # No language extensions, to promote portability.
+  cflags_c = [
+    "-std=c99",
+    "-pedantic",
+  ]
+}
+
+config("enable_sanitizers") {
+  filter_path = rebase_path("sanitize_filter.txt", root_build_dir)
+  cflags = [
+    "-fsanitize=address,undefined,integer",
+    "-fsanitize-blacklist=$filter_path",
+  ]
+  ldflags = cflags
+  inputs = [ "sanitize_filter.txt" ]
+}
+
+config("enable_fuzzer") {
+  cflags = [
+    "-fsanitize=fuzzer",
+    "-O1",
+    "-fno-omit-frame-pointer",
+    "-fno-optimize-sibling-calls",
+  ]
+  ldflags = cflags
+}
+
+# Define a scope for each toolchain that can be fed into generate_toolchain.
+_host_debug = {
+  # Use Pigweed's host_clang_debug toolchain as a base.
+  _toolchain_base = pw_target_toolchain_host.clang_debug
+
+  # Forward everything except the defaults scope from that toolchain.
+  forward_variables_from(_toolchain_base, "*", [ "defaults" ])
+
+  defaults = {
+    # Forward everything from the base toolchain's defaults.
+    forward_variables_from(_toolchain_base.defaults, "*")
+
+    # Extend with custom build arguments for the target.
+    default_configs += [
+      "//toolchains:common_config",
+      "//toolchains:enable_sanitizers",
+    ]
+  }
+}
+
+_host_fuzz = {
+  # Use Pigweed's host_clang_debug toolchain as a base.
+  _toolchain_base = pw_target_toolchain_host.clang_debug
+
+  # Forward everything except the defaults scope from that toolchain.
+  forward_variables_from(_toolchain_base, "*", [ "defaults" ])
+
+  defaults = {
+    # Forward everything from the base toolchain's defaults.
+    forward_variables_from(_toolchain_base.defaults, "*")
+
+    # Extend with custom build arguments for the target.
+    default_configs += [
+      "//toolchains:common_config",
+      "//toolchains:enable_sanitizers",
+      "//toolchains:enable_fuzzer",
+    ]
+  }
+}
+
+_host_optimized = {
+  # Use Pigweed's host_clang_size_optimized toolchain as a base.
+  _toolchain_base = pw_target_toolchain_host.clang_size_optimized
+
+  # Forward everything except the defaults scope from that toolchain.
+  forward_variables_from(_toolchain_base, "*", [ "defaults" ])
+
+  defaults = {
+    # Forward everything from the base toolchain's defaults.
+    forward_variables_from(_toolchain_base.defaults, "*")
+
+    # Extend with custom build arguments for the target.
+    default_configs += [ "//toolchains:common_config" ]
+  }
+}
+
+# Create the actual GN toolchains.
+generate_toolchain("host_debug") {
+  forward_variables_from(_host_debug, "*")
+}
+
+generate_toolchain("host_fuzz") {
+  forward_variables_from(_host_fuzz, "*")
+}
+
+generate_toolchain("host_optimized") {
+  forward_variables_from(_host_optimized, "*")
+}
diff --git a/toolchains/sanitize_filter.txt b/toolchains/sanitize_filter.txt
new file mode 100644
index 0000000..eacb0d4
--- /dev/null
+++ b/toolchains/sanitize_filter.txt
@@ -0,0 +1 @@
+src:*third_party/*/src/*