aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEran Messeri <eranm@google.com>2022-12-16 12:08:25 +0000
committerEran Messeri <eranm@google.com>2022-12-16 12:08:40 +0000
commit5c9e36b036aa09554095f0228d73048b9cbe8ea9 (patch)
tree377bb113af9125b399c3ef49013385a6bd620dd5
parent95e6c433f58f876697aca881fb4348b01a9f541a (diff)
parented76d62a0f8ab6a012a5c405b56eb69c35ef2c7c (diff)
downloadandroid-key-attestation-5c9e36b036aa09554095f0228d73048b9cbe8ea9.tar.gz
Merge remote-tracking branch 'aosp/upstream-master' into merge_android_ka
Test: N/A Bug: 257009517 Bug: 225139985 Change-Id: I2393efad3792dc10affc8c4b6ad0666a6c0ff773
-rw-r--r--.gitignore29
-rw-r--r--CONTRIBUTING.md34
-rw-r--r--LICENSE191
-rw-r--r--README.md60
-rw-r--r--WORKSPACE45
-rw-r--r--server/README.md82
-rw-r--r--server/build.gradle36
-rw-r--r--server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert0.derbin0 -> 1015 bytes
-rw-r--r--server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert1.derbin0 -> 566 bytes
-rw-r--r--server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert2.derbin0 -> 987 bytes
-rw-r--r--server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert3.derbin0 -> 1379 bytes
-rw-r--r--server/examples/der/algorithm_EC_SecurityLevel_TEE/cert0.derbin0 -> 1010 bytes
-rw-r--r--server/examples/der/algorithm_EC_SecurityLevel_TEE/cert1.derbin0 -> 554 bytes
-rw-r--r--server/examples/der/algorithm_EC_SecurityLevel_TEE/cert2.derbin0 -> 981 bytes
-rw-r--r--server/examples/der/algorithm_EC_SecurityLevel_TEE/cert3.derbin0 -> 1380 bytes
-rw-r--r--server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert0.derbin0 -> 1426 bytes
-rw-r--r--server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert1.derbin0 -> 1185 bytes
-rw-r--r--server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert2.derbin0 -> 1417 bytes
-rw-r--r--server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert3.derbin0 -> 1379 bytes
-rw-r--r--server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert0.derbin0 -> 1548 bytes
-rw-r--r--server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert1.derbin0 -> 1298 bytes
-rw-r--r--server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert2.derbin0 -> 1411 bytes
-rw-r--r--server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert3.derbin0 -> 1380 bytes
-rw-r--r--server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert0.pem20
-rw-r--r--server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert1.pem12
-rw-r--r--server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert2.pem20
-rw-r--r--server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert3.pem27
-rw-r--r--server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert0.pem20
-rw-r--r--server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert1.pem12
-rw-r--r--server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert2.pem20
-rw-r--r--server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert3.pem27
-rw-r--r--server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert0.pem28
-rw-r--r--server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert1.pem23
-rw-r--r--server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert2.pem27
-rw-r--r--server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert3.pem27
-rw-r--r--server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert0.pem30
-rw-r--r--server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert1.pem25
-rw-r--r--server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert2.pem27
-rw-r--r--server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert3.pem27
-rw-r--r--server/gradle/wrapper/gradle-wrapper.jarbin0 -> 53636 bytes
-rw-r--r--server/gradle/wrapper/gradle-wrapper.properties23
-rwxr-xr-xserver/gradlew160
-rw-r--r--server/gradlew.bat90
-rw-r--r--server/settings.gradle16
-rw-r--r--server/src/main/java/com/android/example/KeyAttestationExample.java278
-rw-r--r--server/src/main/java/com/google/android/attestation/ASN1Parsing.java45
-rw-r--r--server/src/main/java/com/google/android/attestation/AttestationApplicationId.java185
-rw-r--r--server/src/main/java/com/google/android/attestation/AuthorizationList.java747
-rw-r--r--server/src/main/java/com/google/android/attestation/BUILD19
-rw-r--r--server/src/main/java/com/google/android/attestation/CertificateRevocationStatus.java135
-rw-r--r--server/src/main/java/com/google/android/attestation/Constants.java125
-rw-r--r--server/src/main/java/com/google/android/attestation/ParsedAttestationRecord.java203
-rw-r--r--server/src/main/java/com/google/android/attestation/RootOfTrust.java126
-rw-r--r--server/src/test/java/com/google/android/attestation/AttestationApplicationIdTest.java106
-rw-r--r--server/src/test/java/com/google/android/attestation/AuthorizationListTest.java183
-rw-r--r--server/src/test/java/com/google/android/attestation/BUILD47
-rw-r--r--server/src/test/java/com/google/android/attestation/CertificateRevocationStatusTest.java103
-rw-r--r--server/src/test/java/com/google/android/attestation/ParsedAttestationRecordTest.java136
-rw-r--r--server/src/test/java/com/google/android/attestation/RootOfTrustTest.java68
-rw-r--r--server/src/test/resources/status.json25
60 files changed, 3669 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..28038a7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,29 @@
+# built application files
+*.apk
+*.ap_
+# files for the dex VM
+*.dex
+# Java class files
+*.class
+# generated files
+bin/
+build/
+gen/
+out/
+# Local configuration file (sdk path, etc)
+local.properties
+# Eclipse project files
+.classpath
+.project
+# Windows thumbnail db
+.DS_Store
+# Idea project fileS
+*.iml
+*.ipr
+*.iws
+.idea/
+# Gradle config files
+.gradle/
+# Sandbox stuff
+_sandbox
+bazel-*/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
new file mode 100644
index 0000000..a507046
--- /dev/null
+++ b/CONTRIBUTING.md
@@ -0,0 +1,34 @@
+# How to become a contributor and submit your own code
+
+## Contributor License Agreements
+
+We'd love to accept your sample apps and patches! Before we can take them, we
+have to jump a couple of legal hurdles.
+
+Please fill out either the individual or corporate Contributor License Agreement
+(CLA).
+
+ * If you are an individual writing original source code and you're sure you
+ own the intellectual property, then you'll need to sign an [individual CLA]
+ (http://code.google.com/legal/individual-cla-v1.0.html).
+ * If you work for a company that wants to allow you to contribute your work,
+ then you'll need to sign a [corporate CLA]
+ (http://code.google.com/legal/corporate-cla-v1.0.html).
+
+Follow either of the two links above to access the appropriate CLA and
+instructions for how to sign and return it. Once we receive it, we'll be able to
+accept your pull requests.
+
+## Contributing a Patch
+
+1. Sign a Contributor License Agreement, if you have not yet done so (see
+ details above).
+1. Create your change to the repo in question.
+ * Fork the desired repo, develop and test your code changes.
+ * Ensure that your code is clear and comprehensible.
+ * Ensure that your code has an appropriate set of unit tests which all pass.
+1. Submit a pull request.
+1. The repo owner will review your request. If it is approved, the change will
+ be merged. If it needs additional work, the repo owner will respond with
+ useful comments.
+
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..37ec93a
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,191 @@
+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:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+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
+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..399bc72
--- /dev/null
+++ b/README.md
@@ -0,0 +1,60 @@
+Android Key Attestation Library
+===================================
+
+This library uses the [Bouncy Castle ASN.1][1] parser to extract information
+from an Android attestation data structure to verify that a key pair has been
+generated in a hardware-protected environment of an Android device. It is
+maintained in tandem with Android's key attestation capabilities and is meant
+for production use.
+
+This repository contains a [server](server/src/main/java/com/android/example/)
+sample code that shows how to validate an Android attestation certificate chain
+outside the Android framework. This is the recommended best practice, since if
+the Android device is rooted or otherwise compromised, on-device validation of
+the attestation may be inaccurate.
+
+The entry point into the
+[library itself](server/src/main/java/com/google/android/attestation/)
+is `com.google.android.attestation.ParsedAttestationRecord.createParsedAttestationRecord`.
+
+For more details, see the documentation and the guide at
+https://developer.android.com/training/articles/security-key-attestation.html .
+
+[1]: https://www.bouncycastle.org/
+
+
+Getting Started
+---------------
+
+See the [server](server/) sample for details.
+
+Support
+-------
+
+- Stack Overflow: http://stackoverflow.com/questions/tagged/android
+
+If you've found an error in this sample, please file an issue:
+https://github.com/google/android-key-attestation
+
+Patches are encouraged, and may be submitted by forking this project and
+submitting a pull request through GitHub. Please see CONTRIBUTING.md for more details.
+
+License
+-------
+
+Copyright 2016, The Android Open Source Project, Inc.
+
+Licensed to the Apache Software Foundation (ASF) under one or more contributor
+license agreements. See the NOTICE file distributed with this work for
+additional information regarding copyright ownership. The ASF licenses this
+file to you 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/WORKSPACE b/WORKSPACE
new file mode 100644
index 0000000..4012114
--- /dev/null
+++ b/WORKSPACE
@@ -0,0 +1,45 @@
+workspace(
+ name = "android-key-attestation",
+)
+
+load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
+
+RULES_JVM_EXTERNAL_TAG = "4.2"
+
+RULES_JVM_EXTERNAL_SHA = "cd1a77b7b02e8e008439ca76fd34f5b07aecb8c752961f9640dea15e9e5ba1ca"
+
+http_archive(
+ name = "rules_jvm_external",
+ sha256 = RULES_JVM_EXTERNAL_SHA,
+ strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG,
+ url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG,
+)
+
+load("@rules_jvm_external//:repositories.bzl", "rules_jvm_external_deps")
+
+rules_jvm_external_deps()
+
+load("@rules_jvm_external//:setup.bzl", "rules_jvm_external_setup")
+
+rules_jvm_external_setup()
+
+load("@rules_jvm_external//:defs.bzl", "maven_install")
+
+maven_install(
+ artifacts = [
+ # Bouncy Castle Cryptography APIs used for certificate verification
+ "org.bouncycastle:bcpkix-jdk15on:1.61",
+ "org.bouncycastle:bcprov-jdk15on:1.61",
+
+ # Gson used for decoding certificate status list
+ "com.google.code.gson:gson:2.8.5",
+
+ # Test libraries
+ "junit:junit:4.12",
+ "com.google.truth:truth:1.0",
+ "com.google.truth.extensions:truth-java8-extension:1.0",
+ ],
+ repositories = [
+ "https://repo1.maven.org/maven2/",
+ ],
+)
diff --git a/server/README.md b/server/README.md
new file mode 100644
index 0000000..5764e74
--- /dev/null
+++ b/server/README.md
@@ -0,0 +1,82 @@
+Android Key Attestation Sample
+==============================
+
+This sample illustrates how to use the [Bouncy Castle ASN.1][1] parser to extract information
+from an Android attestation data structure to verify that a key pair has been
+generated in an Android device. This sample demonstrates how to verify a certificate on a server.
+
+[1]: https://www.bouncycastle.org/
+
+Introduction
+------------
+
+This example demonstrates the following tasks:
+
+1. Loading the certificates from [PEM/DER][2]-encoded strings.
+1. Verifying the [X.509][3] certificate chain, up to the root. This includes checking that the root certificate is one of Google's root certificates listed in https://developer.android.com/training/articles/security-key-attestation.
+1. Extracting the attestation extension data from the attestation
+ certificate.
+1. Verifying (and printing) data elements from the attestation extension.
+
+For more information about the process of extracting attestation certificate
+extension data, as well as the extension data schema, see the
+[Key Attestation][4] Android developer training article.
+
+Note that this sample demonstrates the verification of a certificate on a server and not
+on the Android framework. Although you can test the certificate and extensions directly
+on a device, it is safer to run these checks on a separate server you can trust.
+
+[2]: https://developer.android.com/reference/java/security/KeyStore.html#getCertificateChain(java.lang.String)
+[3]: https://developer.android.com/reference/javax/security/cert/X509Certificate.html
+[4]: https://developer.android.com/training/articles/security-key-attestation.html
+
+Pre-requisites
+--------------
+
+- Up-to-date Java JDK
+- [Bouncy Castle Cryptography Java APIs][5] (included as dependency in gradle build configuration).
+
+[5]: https://www.bouncycastle.org/java.html
+
+Getting Started
+---------------
+
+This sample uses the Gradle build system. To build this project, use the
+`gradlew build` command or use "Import Project" in IntelliJ or Android Studio.
+
+Run the main method in `KeyAttestationExample` directly or use the
+`gradlew run --args="<cert-directory>"` task to execute this sample. The `cert-directory` must
+contain the certificate chain, one certificate per file in either DER or PEM encoding and the files
+are read in alphabetical order. For example the provided
+`/examples/pem/algorithm_EC_SecurityLevel_StrongBox/` can be used.
+
+Support
+-------
+
+- Stack Overflow: http://stackoverflow.com/questions/tagged/android
+
+If you've found an error in this sample, please file an issue:
+https://github.com/googlesamples/android-key-attestation
+
+Patches are encouraged, and may be submitted by forking this project and
+submitting a pull request through GitHub. Please see CONTRIBUTING.md for more details.
+
+License
+-------
+
+Copyright 2016, The Android Open Source Project, Inc.
+
+Licensed to the Apache Software Foundation (ASF) under one or more contributor
+license agreements. See the NOTICE file distributed with this work for
+additional information regarding copyright ownership. The ASF licenses this
+file to you 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/server/build.gradle b/server/build.gradle
new file mode 100644
index 0000000..d5cb1cc
--- /dev/null
+++ b/server/build.gradle
@@ -0,0 +1,36 @@
+/* Copyright 2016, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+apply plugin: 'java'
+apply plugin: 'application'
+
+// Set the main class for for Gradle 'run' task.
+//noinspection GroovyUnusedAssignment
+mainClassName = 'com.android.example.KeyAttestationExample'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ // Bouncy Castle Cryptography APIs used for certificate verification
+ compile 'org.bouncycastle:bcpkix-jdk15on:1.61'
+ // Gson used for decoding certificate status list
+ compile 'com.google.code.gson:gson:2.8.5'
+ // JUnit, Truth and Truth8 used for testing
+ testCompile 'junit:junit:4.12'
+ testCompile 'com.google.truth:truth:1.0'
+ testCompile 'com.google.truth.extensions:truth-java8-extension:1.0'
+}
diff --git a/server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert0.der b/server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert0.der
new file mode 100644
index 0000000..1d6e33a
--- /dev/null
+++ b/server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert0.der
Binary files differ
diff --git a/server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert1.der b/server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert1.der
new file mode 100644
index 0000000..09e20d9
--- /dev/null
+++ b/server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert1.der
Binary files differ
diff --git a/server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert2.der b/server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert2.der
new file mode 100644
index 0000000..477c57f
--- /dev/null
+++ b/server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert2.der
Binary files differ
diff --git a/server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert3.der b/server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert3.der
new file mode 100644
index 0000000..14ffdaa
--- /dev/null
+++ b/server/examples/der/algorithm_EC_SecurityLevel_StrongBox/cert3.der
Binary files differ
diff --git a/server/examples/der/algorithm_EC_SecurityLevel_TEE/cert0.der b/server/examples/der/algorithm_EC_SecurityLevel_TEE/cert0.der
new file mode 100644
index 0000000..a6417a4
--- /dev/null
+++ b/server/examples/der/algorithm_EC_SecurityLevel_TEE/cert0.der
Binary files differ
diff --git a/server/examples/der/algorithm_EC_SecurityLevel_TEE/cert1.der b/server/examples/der/algorithm_EC_SecurityLevel_TEE/cert1.der
new file mode 100644
index 0000000..44b459a
--- /dev/null
+++ b/server/examples/der/algorithm_EC_SecurityLevel_TEE/cert1.der
Binary files differ
diff --git a/server/examples/der/algorithm_EC_SecurityLevel_TEE/cert2.der b/server/examples/der/algorithm_EC_SecurityLevel_TEE/cert2.der
new file mode 100644
index 0000000..18edca3
--- /dev/null
+++ b/server/examples/der/algorithm_EC_SecurityLevel_TEE/cert2.der
Binary files differ
diff --git a/server/examples/der/algorithm_EC_SecurityLevel_TEE/cert3.der b/server/examples/der/algorithm_EC_SecurityLevel_TEE/cert3.der
new file mode 100644
index 0000000..af8de75
--- /dev/null
+++ b/server/examples/der/algorithm_EC_SecurityLevel_TEE/cert3.der
Binary files differ
diff --git a/server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert0.der b/server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert0.der
new file mode 100644
index 0000000..32e5967
--- /dev/null
+++ b/server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert0.der
Binary files differ
diff --git a/server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert1.der b/server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert1.der
new file mode 100644
index 0000000..a3e5d72
--- /dev/null
+++ b/server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert1.der
Binary files differ
diff --git a/server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert2.der b/server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert2.der
new file mode 100644
index 0000000..2e6b13b
--- /dev/null
+++ b/server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert2.der
Binary files differ
diff --git a/server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert3.der b/server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert3.der
new file mode 100644
index 0000000..14ffdaa
--- /dev/null
+++ b/server/examples/der/algorithm_RSA_SecurityLevel_StrongBox/cert3.der
Binary files differ
diff --git a/server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert0.der b/server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert0.der
new file mode 100644
index 0000000..929e614
--- /dev/null
+++ b/server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert0.der
Binary files differ
diff --git a/server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert1.der b/server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert1.der
new file mode 100644
index 0000000..7131c99
--- /dev/null
+++ b/server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert1.der
Binary files differ
diff --git a/server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert2.der b/server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert2.der
new file mode 100644
index 0000000..f404a78
--- /dev/null
+++ b/server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert2.der
Binary files differ
diff --git a/server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert3.der b/server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert3.der
new file mode 100644
index 0000000..af8de75
--- /dev/null
+++ b/server/examples/der/algorithm_RSA_SecurityLevel_TEE/cert3.der
Binary files differ
diff --git a/server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert0.pem b/server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert0.pem
new file mode 100644
index 0000000..755aeba
--- /dev/null
+++ b/server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert0.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIID8zCCA5egAwIBAgIBATAMBggqhkjOPQQDAgUAMC8xGTAXBgNVBAUTEDY5N2JjNjRiNmNkNGMw
+MWUxEjAQBgNVBAwMCVN0cm9uZ0JveDAeFw03MDAxMDEwMDAwMDBaFw0yODA1MjMyMzU5NTlaMB8x
+HTAbBgNVBAMMFEFuZHJvaWQgS2V5c3RvcmUgS2V5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE
+M8o810z1VgBTtio2H1Gh5vA3ySYQ0/RIfn/uPQRCiHGZ1K7tvhQobsfa04rM5PAPuaZDmDnD86C5
+T9SL+msTVqOCArAwggKsMA4GA1UdDwEB/wQEAwIHgDCCApgGCisGAQQB1nkCAREEggKIMIIChAIB
+AwoBAgIBBAoBAgQDYWJjBAAwggHNv4U9CAIGAWvSW/8Tv4VFggG7BIIBtzCCAbMxggGLMAwEB2Fu
+ZHJvaWQCAR0wGQQUY29tLmFuZHJvaWQua2V5Y2hhaW4CAR0wGQQUY29tLmFuZHJvaWQuc2V0dGlu
+Z3MCAR0wGQQUY29tLnF0aS5kaWFnc2VydmljZXMCAR0wGgQVY29tLmFuZHJvaWQuZHluc3lzdGVt
+AgEdMB0EGGNvbS5hbmRyb2lkLmlucHV0ZGV2aWNlcwIBHTAfBBpjb20uYW5kcm9pZC5sb2NhbHRy
+YW5zcG9ydAIBHTAfBBpjb20uYW5kcm9pZC5sb2NhdGlvbi5mdXNlZAIBHTAfBBpjb20uYW5kcm9p
+ZC5zZXJ2ZXIudGVsZWNvbQIBHTAgBBtjb20uYW5kcm9pZC53YWxscGFwZXJiYWNrdXACAR0wIQQc
+Y29tLmdvb2dsZS5TU1Jlc3RhcnREZXRlY3RvcgIBHTAiBB1jb20uZ29vZ2xlLmFuZHJvaWQuaGlk
+ZGVubWVudQIBATAjBB5jb20uYW5kcm9pZC5wcm92aWRlcnMuc2V0dGluZ3MCAR0xIgQgMBqjywgR
+NFAcRfFCKrxmwkIk/V3tX9yPF+aXF2/YZqowgZ2hCDEGAgECAgEDogMCAQOjBAICAQClBTEDAgEE
+v4N3AgUAv4U+AwIBAL+FQEwwSgQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAK
+AQIEIHKNsSdPHxzxVx3kOAsEilVKxKOA529TVQg1KQhKk3gBv4VBAwIBAL+FQgUCAwMUs7+FTgYC
+BAE0FfG/hU8GAgQBNBXsMAwGCCqGSM49BAMCBQADSAAwRQIhAN82bz9RzrMXznZKgu61ktdu397w
+VvW2Fj/ZKOkcy8p/AiAFhziu1TGVBklOdPH4usrPM/FxAvlOSUDQwj4HP/9PSg==
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert1.pem b/server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert1.pem
new file mode 100644
index 0000000..091d5b0
--- /dev/null
+++ b/server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert1.pem
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIICMjCCAbegAwIBAgIKFClzmQlEZCZgeDAKBggqhkjOPQQDAjAvMRkwFwYDVQQFExA2OTdiYzY0
+YjZjZDRjMDFlMRIwEAYDVQQMDAlTdHJvbmdCb3gwHhcNMTgwMzIxMDQwOTE5WhcNMjgwMzE4MDQw
+OTE5WjAvMRkwFwYDVQQFExBjY2NlMjYzZmQwOGRhYzNhMRIwEAYDVQQMDAlTdHJvbmdCb3gwWTAT
+BgcqhkjOPQIBBggqhkjOPQMBBwNCAARpw1ZoXy8BqjHbt0eMA+8QRMoK5aqokaKxWSMAqhkUisyx
+jx3eqq5drIA6kYCBcbMeEaK1wskkdE4GnCXoLCOso4G6MIG3MB0GA1UdDgQWBBRR/V0W3sbZqtUt
+A0+VyFs/c1wixTAfBgNVHSMEGDAWgBTdlrDyGscOhnpN5p1KzdLVm6b0+DAPBgNVHRMBAf8EBTAD
+AQH/MA4GA1UdDwEB/wQEAwICBDBUBgNVHR8ETTBLMEmgR6BFhkNodHRwczovL2FuZHJvaWQuZ29v
+Z2xlYXBpcy5jb20vYXR0ZXN0YXRpb24vY3JsLzE0Mjk3Mzk5MDk0NDY0MjY2MDc4MAoGCCqGSM49
+BAMCA2kAMGYCMQDID/CfPKBG4jksFjWd2uRWxcJcp3/pR1WorB2OmikDznwKNpyTUUnxw442Icwy
+PkICMQCGvhARszM/jpxz5WdgbZNsKRVW6fLXlU1bXurrp4Cr36SkunBOrWXaX1Q9/hdjilk=
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert2.pem b/server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert2.pem
new file mode 100644
index 0000000..b4c7aa0
--- /dev/null
+++ b/server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert2.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIID1zCCAb+gAwIBAgIKBpaXYEQ3RICBojANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQFExBlMzVk
+MzhjNjg5N2Q0N2U4MB4XDTE4MDMyMTA0MDkxOFoXDTI4MDMxODA0MDkxOFowLzEZMBcGA1UEBRMQ
+Njk3YmM2NGI2Y2Q0YzAxZTESMBAGA1UEDAwJU3Ryb25nQm94MHYwEAYHKoZIzj0CAQYFK4EEACID
+YgAEh8FMICv77jLds6f1v7+i24HWvfgCwr7KtZFjLfsNhgfVXbvH3uOJIHjrgIemkhtEGXVTs+PM
+df4M9b+czx0q7UiavVrDy+EAV0EzBqAlL3HMjHoTZ/svm5ZqS5rzDravo4G2MIGzMB0GA1UdDgQW
+BBTdlrDyGscOhnpN5p1KzdLVm6b0+DAfBgNVHSMEGDAWgBRTDkUbExXTrVzqjui8W7YUis0d6DAP
+BgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwICBDBQBgNVHR8ESTBHMEWgQ6BBhj9odHRwczov
+L2FuZHJvaWQuZ29vZ2xlYXBpcy5jb20vYXR0ZXN0YXRpb24vY3JsLzM1MDVBMEEyRkRDQzUxRTQw
+DQYJKoZIhvcNAQELBQADggIBAA71Fgy7j41XkCE0CtKcM7wVM5E5s1ErWEbsRu8oWoLOy3Ty6GTU
+VcNj08BXGaP1Rx0E7sub/U7cbsALeVG7mdzBWS9hW9GFkiiXYbDRN8eAk8EJcmWYe0j2P4EMgPM9
+rTm1lJRPNjCieahD/9QipC/RFpb7nmp5Ftx2Cb/On4qYJFuGTT/0uofZ87omU0d6bpaDw/5Fo6MI
+24XoaxVfP02Zvni5DkvGFFLsYRsYyCTij1x3sWBYlkIjiahDOzj271SpkxqwqF4AYSVvsIExa677
+vonOxgPSAeS+48qx3MVW0eI3RsTAOPlcYgI98feD5HN7x3ZWD+uBYaLUul43mLamjJXK0HdH8rGm
+8jGBCNKKTPNWVzQEMfVxo4wzPTbjMMuUbN7g8RBDf4wxhG+PSwEO3jwiHAvDfD5F9HUsffeX1cHZ
+pMFiojvT6XLh2YsDHqtjRfSIY7AfVRl8TlnsF4nRDR4RrmYBmGeq/QWgZf0KOCWfjroBJbGN3GJx
+9OPxig4VrEyzW3yWWjPuy5+F90eL5XXtp0DReY3zoNBeG7Ie9egkPWl2K4+AoU3eEuoZFE8FBkkX
+9D5lhlWCfSdYRrVo46Psx8/cdTcHHJNi/cuZLszcDSXTGtZ8lWz1qgggWoT4F8gplodevGYAtntt
+19XvsVkCRbdaELNUoxuli/LP
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert3.pem b/server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert3.pem
new file mode 100644
index 0000000..07018b2
--- /dev/null
+++ b/server/examples/pem/algorithm_EC_SecurityLevel_StrongBox/cert3.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIFXzCCA0egAwIBAgIINQWgov3MUeQwDQYJKoZIhvcNAQELBQAwGzEZMBcGA1UEBRMQZTM1ZDM4
+YzY4OTdkNDdlODAeFw0xODAzMjEwMzU1MDFaFw0yODAzMTgwMzU1MDFaMBsxGTAXBgNVBAUTEGUz
+NWQzOGM2ODk3ZDQ3ZTgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCoQi070/6PH9BA
+uJiBcTp8j5R2/Fj6kXFaSxsvUjJKRdi/FCOwUFBJfyhHiWJhga2iguIjAJuhZx5XlMj0pSY7buJi
+sPqFknZhKdvfoi4C54j54D+XxCky1APVjD5uc203H+hrRlhh6x4/LTzSXWvb0YLjfOK07HvSddSR
+CyKnPydI5bhyCb5QMtVKHzC4Axgx+BihwG1B4UQjOpZpXBHIFZ6EK/XBFeJX0tgrx0MCczkc1X0O
+hNFKAYumCKcKyh4q2cGh7UwTRJIT4beIJVrOKDVwo3Fc50k3ICpOAc1zRGzRwupIKKGtW455KW1O
+IyAtTJ4NqfIwrkQy5EJ9w/zDzjqFiDNdgTXaMtsz52jHUndfO3lzfvVdBjt2FWLRkWrFmd1tjQ6L
+QQFPrSvUHo0XN5kIiOYdNlAyIwBZ51Zn/clU9he78UYAmXHdjfh04DXWgf+GGIFFdmg9tbiHwdqi
+8yEcakKHL2TYNylmFJuKOkvLIS5iqEMu29OQyv+BJ1NNQtyn8o1D5f3K2a5qyoUcrL0Je2w28ByL
+1JeFURO7wbzkXx9FWwH0stCs/dYq4JKJ8GxKN8aBOl/51atA1cdZAyI55B9ueAYwPevteORGDhEf
+cKRaGiWJpSzIFLDmbL9uSpCAw5uqx5h1IRAX1OLi56EsVaFUedt8IqUaCkUcMQIDAQABo4GmMIGj
+MB0GA1UdDgQWBBRTDkUbExXTrVzqjui8W7YUis0d6DAfBgNVHSMEGDAWgBRTDkUbExXTrVzqjui8
+W7YUis0d6DAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwICBDBABgNVHR8EOTA3MDWgM6Ax
+hi9odHRwczovL2FuZHJvaWQuZ29vZ2xlYXBpcy5jb20vYXR0ZXN0YXRpb24vY3JsLzANBgkqhkiG
+9w0BAQsFAAOCAgEACHPElcXZEY6jItn3SnVn6gdOCvuFIrhB4J4fhxa6OlDCbxsylHHAbpyoOSJD
+0R5dIBTRvz49ElfCpI9yp4l5tUOjWj2K3tMfQjhKN277jApdxbZ6MNac2u+Z3dwS+34YiiCJLVKh
+VzWanXk/+9rmVnBzm7qvgan90j2SmH1oYIA3GowJL+1OWSjj6cBH1VrU7guVd7q+aCEQPhTesfbm
+UkdUbM/TjUMyhf5SZ7WB8A28MnyYQcFttmGDsE1HZx4fTz8iXxjaph33alasmFUWGtG7KyzvbYUw
+wUOmpPd32eT8ocx3B0Z3g+Vo2Cv2LzRRNp15FOf/Ag5HWloyIv3xrsBA2fa4xzOm3/t1Aq0ZViXL
+LerKKmU/EJKo7t5gE0wtNebicNhUJaX2C+vShjfleBBwhs33a8b7dBFX/JMwU5+09N83WHRPZjCr
+YuK3Uu4qANfJ+f1k+weqh6MpRxAZ6TT0OQt1XQz7rDAlq7AopfXRXI74OCNJf8d+2CPNVW8i5IHN
+FjvyEz8ms1ETdw5n3uRbYIgYuCV2Pud3MCwaMTv2E0dLAqg8uJo0JYlhH7wbqidMm2ODRL5FaqWb
+b2pfW0vbpCyGfyw0RQGTJAE7xVdvzfdun9HATDm6TbJFcejBdJImfwpZmDOBtDPIbsg5+QVE3lLF
+6Licq13i5tIg5yQ=
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert0.pem b/server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert0.pem
new file mode 100644
index 0000000..6d2a613
--- /dev/null
+++ b/server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert0.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIID7jCCA5SgAwIBAgIBATAKBggqhkjOPQQDAjApMRkwFwYDVQQFExAyZGM1OGIyZDFhMjQxMzI2
+MQwwCgYDVQQMDANURUUwIBcNNzAwMTAxMDAwMDAwWhgPMjEwNjAyMDcwNjI4MTVaMB8xHTAbBgNV
+BAMMFEFuZHJvaWQgS2V5c3RvcmUgS2V5MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEHkyl3epG
+PODlaNT50JG1QK/DTFIz5vkasDfsOMQiKlcrbKwmCTfFJqJcz6z/CKt6x5edTL66YxaQ430d0Is3
+JKOCArMwggKvMA4GA1UdDwEB/wQEAwIHgDCCApsGCisGAQQB1nkCAREEggKLMIIChwIBAwoBAQIB
+BAoBAQQDYWJjBAAwggHNv4U9CAIGAWTmEWf/v4VFggG7BIIBtzCCAbMxggGLMAwEB2FuZHJvaWQC
+AR0wGQQUY29tLmFuZHJvaWQua2V5Y2hhaW4CAR0wGQQUY29tLmFuZHJvaWQuc2V0dGluZ3MCAR0w
+GQQUY29tLnF0aS5kaWFnc2VydmljZXMCAR0wGgQVY29tLmFuZHJvaWQuZHluc3lzdGVtAgEdMB0E
+GGNvbS5hbmRyb2lkLmlucHV0ZGV2aWNlcwIBHTAfBBpjb20uYW5kcm9pZC5sb2NhbHRyYW5zcG9y
+dAIBHTAfBBpjb20uYW5kcm9pZC5sb2NhdGlvbi5mdXNlZAIBHTAfBBpjb20uYW5kcm9pZC5zZXJ2
+ZXIudGVsZWNvbQIBHTAgBBtjb20uYW5kcm9pZC53YWxscGFwZXJiYWNrdXACAR0wIQQcY29tLmdv
+b2dsZS5TU1Jlc3RhcnREZXRlY3RvcgIBHTAiBB1jb20uZ29vZ2xlLmFuZHJvaWQuaGlkZGVubWVu
+dQIBATAjBB5jb20uYW5kcm9pZC5wcm92aWRlcnMuc2V0dGluZ3MCAR0xIgQgMBqjywgRNFAcRfFC
+KrxmwkIk/V3tX9yPF+aXF2/YZqowgaChCDEGAgECAgEDogMCAQOjBAICAQClBTEDAgEEqgMCAQG/
+g3cCBQC/hT4DAgEAv4VATDBKBCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAoB
+AgQgco2xJ08fHPFXHeQ4CwSKVUrEo4Dnb1NVCDUpCEqTeAG/hUEDAgEAv4VCBQIDAxSzv4VOBQID
+AxSzv4VPBQIDAxSzMAoGCCqGSM49BAMCA0gAMEUCIDsINbPXvgn8qN2V74vvO9RcuXe17dswxNkm
+1vyx1BqCAiEAkicGYwvPJp4jAIZbD9++D+kQJQSJZyE3kT9ukSeSfHs=
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert1.pem b/server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert1.pem
new file mode 100644
index 0000000..b8c664d
--- /dev/null
+++ b/server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert1.pem
@@ -0,0 +1,12 @@
+-----BEGIN CERTIFICATE-----
+MIICJjCCAaugAwIBAgIKEyBjEXiWOIIJETAKBggqhkjOPQQDAjApMRkwFwYDVQQFExAyOTYwY2I5
+YWViZDUwNWY0MQwwCgYDVQQMDANURUUwHhcNMTgwMzIxMjA1ODU4WhcNMjgwMzE4MjA1ODU4WjAp
+MRkwFwYDVQQFExAyZGM1OGIyZDFhMjQxMzI2MQwwCgYDVQQMDANURUUwWTATBgcqhkjOPQIBBggq
+hkjOPQMBBwNCAATvMEBAHYIMvvupiQYIRlRceTcA6ZARblK+YPHYI01TXd3qVfLImEdHSp2LiG+c
+8Xk4ORhOE9MfT/yukMlqDUSAo4G6MIG3MB0GA1UdDgQWBBT3sqQpymXuuJ9rHrXvzF6qRvAoJzAf
+BgNVHSMEGDAWgBTZCjmyzP+wdzrAqIzS/zURX5DNxjAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB
+/wQEAwICBDBUBgNVHR8ETTBLMEmgR6BFhkNodHRwczovL2FuZHJvaWQuZ29vZ2xlYXBpcy5jb20v
+YXR0ZXN0YXRpb24vY3JsLzEzMjA2MzExNzg5NjM4ODIwOTExMAoGCCqGSM49BAMCA2kAMGYCMQDW
+h8lKjA7mypbTxqgg9kGPSnVuSKPrW1aOzO+ml3r1D1BJaYsTn+v0gPWaAyG09tQCMQCqP4ulQISn
+1qhfvcSKYtgATw90q811e8LUvdfid9h6Ew15S9Q5Fs9x+lnnLKtGHoo=
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert2.pem b/server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert2.pem
new file mode 100644
index 0000000..1d0bf11
--- /dev/null
+++ b/server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert2.pem
@@ -0,0 +1,20 @@
+-----BEGIN CERTIFICATE-----
+MIID0TCCAbmgAwIBAgIKA4gmZ2BliZaFfTANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQFExBmOTIw
+MDllODUzYjZiMDQ1MB4XDTE4MDMyMTIwNTM1M1oXDTI4MDMxODIwNTM1M1owKTEZMBcGA1UEBRMQ
+Mjk2MGNiOWFlYmQ1MDVmNDEMMAoGA1UEDAwDVEVFMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEjXEL
+w7zFszCPjAS+Qn6zEFajdp6hD+53zSpN7egUzWfkFEh+wcLa4k67Fv2tKuekmLIhr4xidPllElgi
+wAyeB33abMJ5BckSX1ayj4lnpMVvYRovl/5ZHIKNIJMN/hC6o4G2MIGzMB0GA1UdDgQWBBTZCjmy
+zP+wdzrAqIzS/zURX5DNxjAfBgNVHSMEGDAWgBQ2YeEAfIgFCVGLRGxH/xpMyepPEjAPBgNVHRMB
+Af8EBTADAQH/MA4GA1UdDwEB/wQEAwICBDBQBgNVHR8ESTBHMEWgQ6BBhj9odHRwczovL2FuZHJv
+aWQuZ29vZ2xlYXBpcy5jb20vYXR0ZXN0YXRpb24vY3JsL0U4RkExOTYzMTREMkZBMTgwDQYJKoZI
+hvcNAQELBQADggIBAJKrsCoJam9qc1ZaHeQWf2TAjHDDVylLuUvczTSuu8VhZJiU0+yKGbTNnA13
+n/dvn7rmMj8W6ObWL582Se9MBBdKtAVeCG7qYFq+m6kYFIfL8sOuutKXG5eW+8VY4he0hu+5eZiS
+2e3mAZGYF9JwQTAv0H//6+9eLm/+j+L5tr2X4b7H6k2h4YW5HTIzIbVMvp/ic/zmv+RvrmYyXlia
+z1xglRqrJ6EiFI9/KsYYsSSVgorlrXPyKOBeXGc2SFIVQmLwfW4gc9BC8V4qBDcpFK1kxOfnkyXl
+oVZeat4yuYFSIdtJq9FErMFHfp+pDjIKRGeeWWrPzkbELqtHUSeSktbqN229c+86YRfScxpx5iJ0
+Jp2YsHkiYDFs+0LOex2RNdOKErGKZ+8QbMtVxUIQARTlhZkeLTLAWufFcclEbZkpoe6jo131VjAl
+UrkQfK6dVVMx+gLhOffLjH9EmUOD8Y1an9DQCsZl1tpBN4WgInbRMlzBsKF17Z9BEW1f2A+Gn0mn
+znZoq6t0OxsvRJTEHEgswQh4Vkr77UYSdcwQ0flrpD9Oe6ObnB0VSSCVo1yhPC39R35Z5KDhiNS8
+BkuPaJoUK8RBbkl5ay4O86LFfY9tdAoHT0A/2hmq/NZhz48K1C9AJGtS+0+zHwSatjWJ7rM/KXAh
+J0Nln/nMaeteQy2f
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert3.pem b/server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert3.pem
new file mode 100644
index 0000000..faab920
--- /dev/null
+++ b/server/examples/pem/algorithm_EC_SecurityLevel_TEE/cert3.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAUTEGY5MjAw
+OWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYyODUyWjAbMRkwFwYDVQQFExBm
+OTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHs
+K7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfd
+nJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL
+/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04
+T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+R
+hhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1
+Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgp
+Zrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6
+tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8
+Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAaOBpjCB
+ozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYDVR0jBBgwFoAUNmHhAHyIBQlRi0Rs
+R/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOg
+MYYvaHR0cHM6Ly9hbmRyb2lkLmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZI
+hvcNAQELBQADggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfB
+Pb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00mqC0w/Zwvju1t
+wb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rYDBJDcR9W62BW9jfIoBQcxUCU
+JouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPmQUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhu
+Kug2jITV0QkXvaJWF4nUaHOTNA4uJU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsY
+gBt6tKxxWH00XcyDCdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ
+79IyZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxDqwLqRBYk
+A3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23UaicMDSXYrB4I4WHXPGjxhZu
+CuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1wDB5y0USicV3YgYGmi+NZfhA4URSh77Y
+d6uuJOJENRaNVTzk
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert0.pem b/server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert0.pem
new file mode 100644
index 0000000..da4c811
--- /dev/null
+++ b/server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert0.pem
@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE-----
+MIIFjjCCBHagAwIBAgIBATANBgkqhkiG9w0BAQsFADAvMRkwFwYDVQQFExBjY2NlMjYzZmQwOGRh
+YzNhMRIwEAYDVQQMDAlTdHJvbmdCb3gwHhcNNzAwMTAxMDAwMDAwWhcNMjgwNTIzMjM1OTU5WjAf
+MR0wGwYDVQQDDBRBbmRyb2lkIEtleXN0b3JlIEtleTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
+AQoCggEBAN/ps3b6b+nFF1AdwOApHr3zG8icT4AIkuMztfzTusXP88wDbzgh1Nsf/T4M9xaTy/jH
+z4wukPjxAX88/u0mDinKb9SrgzDLfXyhV7PPus0wEqUfGaN62Bwx2ei3UmZCvyzLruCUUauABZsy
+H7Gudj7F4yfwvveLlHEpRx4Up9UzrnkKV3Kgd8mQf67CVBE66AGRPXJzyBu7+4WDAA0lhAvE0XOy
+Kac6Hl9CRxSBDX2QzAooVsjOaWjg6eKt+ed1lNYNdxNGNK4UX2e9LEWAJlI50p5jLx3g5Uksu1S9
+WS1u9OU5L52ccGa/OSkPON/qxVf/3Yx6DuuuupPs01RL/J8CAwEAAaOCAsMwggK/MA4GA1UdDwEB
+/wQEAwIHgDCCAqsGCisGAQQB1nkCAREEggKbMIIClwIBAwoBAgIBBAoBAgQDYWJjBAAwggHNv4U9
+CAIGAWv2IqDMv4VFggG7BIIBtzCCAbMxggGLMAwEB2FuZHJvaWQCAR0wGQQUY29tLmFuZHJvaWQu
+a2V5Y2hhaW4CAR0wGQQUY29tLmFuZHJvaWQuc2V0dGluZ3MCAR0wGQQUY29tLnF0aS5kaWFnc2Vy
+dmljZXMCAR0wGgQVY29tLmFuZHJvaWQuZHluc3lzdGVtAgEdMB0EGGNvbS5hbmRyb2lkLmlucHV0
+ZGV2aWNlcwIBHTAfBBpjb20uYW5kcm9pZC5sb2NhbHRyYW5zcG9ydAIBHTAfBBpjb20uYW5kcm9p
+ZC5sb2NhdGlvbi5mdXNlZAIBHTAfBBpjb20uYW5kcm9pZC5zZXJ2ZXIudGVsZWNvbQIBHTAgBBtj
+b20uYW5kcm9pZC53YWxscGFwZXJiYWNrdXACAR0wIQQcY29tLmdvb2dsZS5TU1Jlc3RhcnREZXRl
+Y3RvcgIBHTAiBB1jb20uZ29vZ2xlLmFuZHJvaWQuaGlkZGVubWVudQIBATAjBB5jb20uYW5kcm9p
+ZC5wcm92aWRlcnMuc2V0dGluZ3MCAR0xIgQgMBqjywgRNFAcRfFCKrxmwkIk/V3tX9yPF+aXF2/Y
+ZqowgbChCDEGAgECAgEDogMCAQGjBAICCAClBTEDAgEEpggxBgIBAwIBBb+BSAUCAwEAAb+DdwIF
+AL+FPgMCAQC/hUBMMEoEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEACgECBCBy
+jbEnTx8c8Vcd5DgLBIpVSsSjgOdvU1UINSkISpN4Ab+FQQMCAQC/hUIFAgMDFLO/hU4GAgQBNBXx
+v4VPBgIEATQV7DANBgkqhkiG9w0BAQsFAAOCAQEAmu4L5Rs3q0EjeSJYIJVzGi4R/e9uNpS1IR5p
+6sjwsWXIuS+KGNjE/bD9hiDDNW3EglUGU2Xp9cW/PhVTAp6hnappa1dZb5W4EUAwtedUsLsgBva1
+YKJD5/EB+6vaktJLaMqFRK1x8V11SAVi45hBU8wgALmGTIN9n0w+mxs4isab+unysXpnzcd7PRN/
+SdbyQ1AY2P52FDemV/VvMqUgfQTHLkf0Sjk3ydWDtpQ8xAppxhssHofWeh84FXSIJCSQ59FUCgb+
+aPdyvKNZPDoUfaX098cAs7JXTFRhfW64jOmoaNdcPfzSGFF7pMcsOWTo2vmCvKO+BRxsWDhPfB/k
+1Q==
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert1.pem b/server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert1.pem
new file mode 100644
index 0000000..a9a4ba7
--- /dev/null
+++ b/server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert1.pem
@@ -0,0 +1,23 @@
+-----BEGIN CERTIFICATE-----
+MIIEnTCCAoWgAwIBAgIKFYRggggVNRl4dzANBgkqhkiG9w0BAQsFADAvMRkwFwYDVQQFExA2OTdi
+YzY0YjZjZDRjMDFlMRIwEAYDVQQMDAlTdHJvbmdCb3gwHhcNMTgwMzIxMDQwOTE4WhcNMjgwMzE4
+MDQwOTE4WjAvMRkwFwYDVQQFExBjY2NlMjYzZmQwOGRhYzNhMRIwEAYDVQQMDAlTdHJvbmdCb3gw
+ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC44jdUqUTiyjmNPPY3KlN088t8B7qsmChF
+fIbZuNBHgr8mYY1Zhjs170QO9OqXUtjhmWN1dAl9NtEe0dL+RgaOYD+DcgjObj4VuCvxo6iMLd5A
+yPUQT/js61g3mwC2XqZEEJPhen/KIX1cfDYYng4m3n8/IU/uVC1E1MaTMJDBZj+eaa9NOStB/Srj
+/oQHl+3kzU72BtLL4vrmfbYhACv1RHRhA1CXIZeW6G8rlPeMCO1tjyXOQttG2vr/Dj/dnRFOFIQA
+XCiIw3v63qah7eSWeS59/WncAS7VG8iEPqb/NwBdnGFiJkagsADsFWJO8HJ1PIpwQ1xC/qnALLP9
+Ec65AgMBAAGjgbowgbcwHQYDVR0OBBYEFAd28hRYKlUaWnBgG+hRJkH2Axy2MB8GA1UdIwQYMBaA
+FCwYZ+XKkRf4peQMHg55sQUm/p/vMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgIEMFQG
+A1UdHwRNMEswSaBHoEWGQ2h0dHBzOi8vYW5kcm9pZC5nb29nbGVhcGlzLmNvbS9hdHRlc3RhdGlv
+bi9jcmwvMTU4NDYwODIwODE1MzUxOTc4NzcwDQYJKoZIhvcNAQELBQADggIBACrYYBCoxXuVBmsH
+v8rCDAsw0RNow3nJgGZjVlzPGO/Gpu/P1qkKb+ugu7BIvUdc4MScbxjy/9JXUZGwTwski4n3rTwm
+SaMt5RP+eLTdKoLVkW1nh+FD6el3nKigN/vFqoAaHv3ayA/7ERGEN5MU8vIQKu4BajPjP2RALHV8
+vTBm0JZbv/fByrS55kqnNLlWGEgmyEC0oK+eG3sa/+ux3RJWgd6VyHhr8TdvV8NwLmEEa19DMiOT
+SC/vEPytwRj33FHGpubLpGuoAV3KpCXXHBVNChL7nn1bjnvHY2XA2xpbWp9gdJvDk2f9ODNIpQPu
+f0fwWUeYyG+xof3BynJkCcfxmT04oDEFT1ANO2+cHL6cE2hJ9vJ8nblqvNOfyRgNmYghVYXC19aq
+lr6wJHun5Lw2Ldd7AGU4Rfvb8ckgf8tGs2mz4XbFLlOZVbDC9bRjwZ7F7ufPgDm0gN9qJHgG5dFe
+60nnm6Veq2VGnRz2w8GtjGb1sRFae/tiMt7eRwCHHVGPXC7fCCWvWNbHn/2NhL+GzbGnkcU0wNEv
+uCn2kFEmY/q0SZp10qBTC+ztAG4FXUVxdmDomq3sJXm3hxtMXvI948rGC1+C/xrrumUHLLZxJuQ/
+FB1p5pnJKaFKNZjhuNBraB4/sXn9BcJ+XiqHnpludX2/IVddtXiyKwNbsC1X
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert2.pem b/server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert2.pem
new file mode 100644
index 0000000..8153217
--- /dev/null
+++ b/server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert2.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIFhTCCA22gAwIBAgIKBpaXYEQ3RICBoTANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQFExBlMzVk
+MzhjNjg5N2Q0N2U4MB4XDTE4MDMyMTA0MDkxOFoXDTI4MDMxODA0MDkxOFowLzEZMBcGA1UEBRMQ
+Njk3YmM2NGI2Y2Q0YzAxZTESMBAGA1UEDAwJU3Ryb25nQm94MIICIjANBgkqhkiG9w0BAQEFAAOC
+Ag8AMIICCgKCAgEAxVPgrTkUevIEJF4VU1JUKet/hsNmQcpl0OwSKEpEFAj8lMusW5LV9U7ZZ6dX
+K1SrJKQsC2Z9bFWL3oqDfy+oJnliwgtrSdttIOcGEE7m9QFh+UPhcQplIqCdyf4IGu8kxx2pWDD0
+ZLGG19B/NMiyTUKG9zSEkrOOd6t19KOxk+uVbfgQKRPAX4B9nq+EDZ4aH0mACFQomNpD3ndUMWzv
+xFRR24xn0TjnYBfnxLqCYwfLhWpHpwKol1GQi/trUoCwsAsyO7bNQXnVomQa3D5yf8Z0a+xG8qXL
+unWa4ydl/yEfWO+ahxov30dMzos1dD+gjc59Z4FkGZPoLZWh0CSKWbbVb0gL8aEKB9EMFQUlDqpO
+hVzpWQ7xQTkUPlGiuUU0uPtCFe8SU1ZCpwD3nyArVgqTKoH20fciFSjlIIdjgXRZsVL0skO4ZmSm
+fFndn25imE+dBbRUooV8sIdytDFnNxBritjM3gPD0LGqIqpykJkFeHfIe2jT1f+iEK50P/cgCOtZ
+SpGbL6x9Vg2/2lPQjygynVsJ6U00fW1Z/s+hW8WxnfcRXSWBK1/2AC/6wNNpSriZJqjV0tFLO6WJ
+vp8kOcUev3/lqgafgQHrwSJIRKGLTfaSlzfsghzNtCmawLDSs6ASv0PnYabxk4KbbhvdiL2eHysU
+5G+Tk0HMeX24lPECAwEAAaOBtjCBszAdBgNVHQ4EFgQULBhn5cqRF/il5AweDnmxBSb+n+8wHwYD
+VR0jBBgwFoAUUw5FGxMV061c6o7ovFu2FIrNHegwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8E
+BAMCAgQwUAYDVR0fBEkwRzBFoEOgQYY/aHR0cHM6Ly9hbmRyb2lkLmdvb2dsZWFwaXMuY29tL2F0
+dGVzdGF0aW9uL2NybC8zNTA1QTBBMkZEQ0M1MUU0MA0GCSqGSIb3DQEBCwUAA4ICAQCDsknVUCZN
+IsdCxDechWvMXb+1lfYrKzc9MMstTHM5DNWR20Z7ZkC4dHRgYyhbdAIJCwGLkRhZ7+mIwKaz0Mkx
+GB09mZd6bF0vzflDRbOdXb5Yo1rtL0caxUbkpSwNSvVVaIyCvNNfvwZSbqvRWyT8QXyijs5jsJyl
+2UX7fFy01YXfW6TFMBwagDVS9R5iuvpGQqGRaoCAK5I0jY3h84fJkZM+eCb/WiwT6fpBSIpyMtgo
+WadNKwsqiX0MWa40k/Gx0v0zK4esfYBO9pumZyL8TnnSZEKjwJOR9hSlB+i2c2I/Scc3gLKXirgF
+6ZDM5wFYJJpEq3SVOYYUNxXYrw+RzHo/ostjjgrbJUACLR4kbRh/nZQFYCB9+N6E9QzA60VHySRY
+V21UJQN6iqHcRUVz50tfmtF7raE2E6GfDmnAxBlvhPRQVgD7NLs8JNTKuZ4gyJ/r4Dx+V8MpJAyC
+DarlCm+Mcl5c2pfjXa64tRSEvQ6/yJDAeBSylBb19ptIqDQLC09Q3PjNziRO4uLggRPZf/FBXBqT
+trqp+rF2fQorTCWdspcoPzCOnswSZvYeixBNdUUbeXnDfRFgeH8uBsd0yOf3pAKLUPJfXfHwY0/9
+tzBe3iC8nFC+MkbZQHoklOovI67sfOmSbEKDtUffMf+oAHSxXpNxjY8CBVC8cFkEdA==
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert3.pem b/server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert3.pem
new file mode 100644
index 0000000..07018b2
--- /dev/null
+++ b/server/examples/pem/algorithm_RSA_SecurityLevel_StrongBox/cert3.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIFXzCCA0egAwIBAgIINQWgov3MUeQwDQYJKoZIhvcNAQELBQAwGzEZMBcGA1UEBRMQZTM1ZDM4
+YzY4OTdkNDdlODAeFw0xODAzMjEwMzU1MDFaFw0yODAzMTgwMzU1MDFaMBsxGTAXBgNVBAUTEGUz
+NWQzOGM2ODk3ZDQ3ZTgwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQCoQi070/6PH9BA
+uJiBcTp8j5R2/Fj6kXFaSxsvUjJKRdi/FCOwUFBJfyhHiWJhga2iguIjAJuhZx5XlMj0pSY7buJi
+sPqFknZhKdvfoi4C54j54D+XxCky1APVjD5uc203H+hrRlhh6x4/LTzSXWvb0YLjfOK07HvSddSR
+CyKnPydI5bhyCb5QMtVKHzC4Axgx+BihwG1B4UQjOpZpXBHIFZ6EK/XBFeJX0tgrx0MCczkc1X0O
+hNFKAYumCKcKyh4q2cGh7UwTRJIT4beIJVrOKDVwo3Fc50k3ICpOAc1zRGzRwupIKKGtW455KW1O
+IyAtTJ4NqfIwrkQy5EJ9w/zDzjqFiDNdgTXaMtsz52jHUndfO3lzfvVdBjt2FWLRkWrFmd1tjQ6L
+QQFPrSvUHo0XN5kIiOYdNlAyIwBZ51Zn/clU9he78UYAmXHdjfh04DXWgf+GGIFFdmg9tbiHwdqi
+8yEcakKHL2TYNylmFJuKOkvLIS5iqEMu29OQyv+BJ1NNQtyn8o1D5f3K2a5qyoUcrL0Je2w28ByL
+1JeFURO7wbzkXx9FWwH0stCs/dYq4JKJ8GxKN8aBOl/51atA1cdZAyI55B9ueAYwPevteORGDhEf
+cKRaGiWJpSzIFLDmbL9uSpCAw5uqx5h1IRAX1OLi56EsVaFUedt8IqUaCkUcMQIDAQABo4GmMIGj
+MB0GA1UdDgQWBBRTDkUbExXTrVzqjui8W7YUis0d6DAfBgNVHSMEGDAWgBRTDkUbExXTrVzqjui8
+W7YUis0d6DAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwICBDBABgNVHR8EOTA3MDWgM6Ax
+hi9odHRwczovL2FuZHJvaWQuZ29vZ2xlYXBpcy5jb20vYXR0ZXN0YXRpb24vY3JsLzANBgkqhkiG
+9w0BAQsFAAOCAgEACHPElcXZEY6jItn3SnVn6gdOCvuFIrhB4J4fhxa6OlDCbxsylHHAbpyoOSJD
+0R5dIBTRvz49ElfCpI9yp4l5tUOjWj2K3tMfQjhKN277jApdxbZ6MNac2u+Z3dwS+34YiiCJLVKh
+VzWanXk/+9rmVnBzm7qvgan90j2SmH1oYIA3GowJL+1OWSjj6cBH1VrU7guVd7q+aCEQPhTesfbm
+UkdUbM/TjUMyhf5SZ7WB8A28MnyYQcFttmGDsE1HZx4fTz8iXxjaph33alasmFUWGtG7KyzvbYUw
+wUOmpPd32eT8ocx3B0Z3g+Vo2Cv2LzRRNp15FOf/Ag5HWloyIv3xrsBA2fa4xzOm3/t1Aq0ZViXL
+LerKKmU/EJKo7t5gE0wtNebicNhUJaX2C+vShjfleBBwhs33a8b7dBFX/JMwU5+09N83WHRPZjCr
+YuK3Uu4qANfJ+f1k+weqh6MpRxAZ6TT0OQt1XQz7rDAlq7AopfXRXI74OCNJf8d+2CPNVW8i5IHN
+FjvyEz8ms1ETdw5n3uRbYIgYuCV2Pud3MCwaMTv2E0dLAqg8uJo0JYlhH7wbqidMm2ODRL5FaqWb
+b2pfW0vbpCyGfyw0RQGTJAE7xVdvzfdun9HATDm6TbJFcejBdJImfwpZmDOBtDPIbsg5+QVE3lLF
+6Licq13i5tIg5yQ=
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert0.pem b/server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert0.pem
new file mode 100644
index 0000000..1afa630
--- /dev/null
+++ b/server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert0.pem
@@ -0,0 +1,30 @@
+-----BEGIN CERTIFICATE-----
+MIIGCDCCBHCgAwIBAgIBATANBgkqhkiG9w0BAQsFADApMRkwFwYDVQQFExAyZGM1OGIyZDFhMjQx
+MzI2MQwwCgYDVQQMDANURUUwIBcNNzAwMTAxMDAwMDAwWhgPMjEwNjAyMDcwNjI4MTVaMB8xHTAb
+BgNVBAMMFEFuZHJvaWQgS2V5c3RvcmUgS2V5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC
+AQEApNVcnyN40MANMbbo2nMGNq2NNysDSjfLm0W3i6wPKf0ffCYkhWM4dCmQKKf50uAZTBeTit4c
+NwXeZn3qellMlOsIN3Qc384rfN/8cikrRvUAgibz0Jy7STykjwa7x6tKwqITxbO8HqAhKo8/BQXU
+xzrOdIg5ciy+UM7Vgh7a7ogen0KL2iGgrsalb1ti7Vlzb6vIJ4WzIC3TGD2sCkoPahghwqFDZZCo
+/FzaLoNY0jAUX2mL+kf8aUaoxz7xA9FTvgara+1pLBR1s4c8xPS2HdZipcVXWfey0wujv1VAKs4+
+tXjKlHkYBHBBceEjxUtEmrapSQEdpHPv7Xh9Uanq4QIDAQABo4ICwTCCAr0wDgYDVR0PAQH/BAQD
+AgeAMIICqQYKKwYBBAHWeQIBEQSCApkwggKVAgEDCgEBAgEECgEBBANhYmMEADCCAc2/hT0IAgYB
+ZOYGEYe/hUWCAbsEggG3MIIBszGCAYswDAQHYW5kcm9pZAIBHTAZBBRjb20uYW5kcm9pZC5rZXlj
+aGFpbgIBHTAZBBRjb20uYW5kcm9pZC5zZXR0aW5ncwIBHTAZBBRjb20ucXRpLmRpYWdzZXJ2aWNl
+cwIBHTAaBBVjb20uYW5kcm9pZC5keW5zeXN0ZW0CAR0wHQQYY29tLmFuZHJvaWQuaW5wdXRkZXZp
+Y2VzAgEdMB8EGmNvbS5hbmRyb2lkLmxvY2FsdHJhbnNwb3J0AgEdMB8EGmNvbS5hbmRyb2lkLmxv
+Y2F0aW9uLmZ1c2VkAgEdMB8EGmNvbS5hbmRyb2lkLnNlcnZlci50ZWxlY29tAgEdMCAEG2NvbS5h
+bmRyb2lkLndhbGxwYXBlcmJhY2t1cAIBHTAhBBxjb20uZ29vZ2xlLlNTUmVzdGFydERldGVjdG9y
+AgEdMCIEHWNvbS5nb29nbGUuYW5kcm9pZC5oaWRkZW5tZW51AgEBMCMEHmNvbS5hbmRyb2lkLnBy
+b3ZpZGVycy5zZXR0aW5ncwIBHTEiBCAwGqPLCBE0UBxF8UIqvGbCQiT9Xe1f3I8X5pcXb9hmqjCB
+rqEIMQYCAQICAQOiAwIBAaMEAgIIAKUFMQMCAQSmCDEGAgEDAgEFv4FIBQIDAQABv4N3AgUAv4U+
+AwIBAL+FQEwwSgQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAKAQIEIHKNsSdP
+HxzxVx3kOAsEilVKxKOA529TVQg1KQhKk3gBv4VBAwIBAL+FQgUCAwMUs7+FTgUCAwMUs7+FTwUC
+AwMUszANBgkqhkiG9w0BAQsFAAOCAYEAJMIuzdNUdfrE6sIdmsnMn/scSG2odbphj8FkX9JGdF2S
+OT599HuDY9qhvkru2Dza4sLKK3f4ViBhuR9lpfeprKvstxbtBO7jkLYfVn0ZRzHRHVEyiW5IVKh+
+qOXVJ9S1lMShOTlsaYJytLKIlcrRAZBEXZiNbzTuVh1CH6X9Ni1dog14snm+lcOeORdL9fht2CHa
+u/caRnpWiZbjoAoJp0O89uBrRkXPpln51+3jPY6AFny30grNAvKguauDcPPhNV1yR+ylSsQi2gm3
+Rs4pgtlxFLMfZLgT0cbkl+9zk/QUqlpBP8ftUBsOI0ARr8xhFN3cvq9kXGLtJ9hEP9PRaflAFREk
+DK3IBIbVcAFZBFoAQOdE9zy0+F5bQrznPGaZg4Dzhcx33qMDUTgHtWoy+k3ePGQMEtmoTTLgQywW
+OIkXEoFqqGi9GKJXUT1KYi5NsigaYqu7FoN4Qsvs61pMUEfZSPP2AFwkA8uNFbmb9uxcxaGHCA8i
+3i9VM6yOLIrP
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert1.pem b/server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert1.pem
new file mode 100644
index 0000000..afa0fa3
--- /dev/null
+++ b/server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert1.pem
@@ -0,0 +1,25 @@
+-----BEGIN CERTIFICATE-----
+MIIFDjCCAvagAwIBAgIJFIcgYhN4mUUVMA0GCSqGSIb3DQEBCwUAMCkxGTAXBgNVBAUTEDI5NjBj
+YjlhZWJkNTA1ZjQxDDAKBgNVBAwMA1RFRTAeFw0xODAzMjEyMDU4NDhaFw0yODAzMTgyMDU4NDha
+MCkxGTAXBgNVBAUTEDJkYzU4YjJkMWEyNDEzMjYxDDAKBgNVBAwMA1RFRTCCAaIwDQYJKoZIhvcN
+AQEBBQADggGPADCCAYoCggGBALTJjMPjzdG6i/oPf+OuQpE0Pbamcs+t1F1jAeNJXcCSN+hL5kTz
+PBto7WUX64+D74/SbwbHzSNsRptOwus2Fj4MXxdCcNjA+nYT19H++30ecY932WYtP6geamSZlZrN
+rh4DRFGuSfOtQ2juw+alEEzuAfS5DigRP8RUjURyLXAQMIVGuIZoT1voB+H6OEv8ICallFkf7xI8
+ycKVYhOCNpNuf3tKELhaNqpZh7mAuaXMrxhsc7WiFsm6cBGY/UpZk/3koHUrHgCJxo3CJEYQUFEE
++du4yJ4KRSJabC/3GzE415gKPPvcFRm7/9yQWt4VY8X9F6EDkfsEYawDN+ZFN7sQSRuaFVA1+6U1
+UA0TlFjFCvAbMvlvb0RywIvaylpVQxCaFHtwMnOeQoMfic9baiinUHqeDdV85YtK5ZAXBkv0Cty/
+43Xlq4zJ4O/TZa/dqqrtYkdmzGjUu5ubtiT2AknFJjS/TwtDI7obVOgQYnStWQjZWBafVrm6l62B
+eFuEqQIDAQABo4G4MIG1MB0GA1UdDgQWBBRopWEF5KUGUKoHHaveKWTVGPzLDDAfBgNVHSMEGDAW
+gBT8ZOhxpzVk8i/b9lrISMYu2Rl7JDAPBgNVHRMBAf8EBTADAQH/MA4GA1UdDwEB/wQEAwICBDBS
+BgNVHR8ESzBJMEegRaBDhkFodHRwczovL2FuZHJvaWQuZ29vZ2xlYXBpcy5jb20vYXR0ZXN0YXRp
+b24vY3JsLzE0ODcyMDYyMTM3ODk5NDUxNTANBgkqhkiG9w0BAQsFAAOCAgEAfdEIlzih1i06VvWE
++ZutfqoV5mvcFQb1ey8lzrvPhEPjm4SeKi6wLR1f1O12uOhYbAQzzOTmEuYsFqje3N6WGZVnXSXI
+vxlUT0D3kVqgM48+dt+a27xxTMy3QEJNYzFdgOrrRIAzen5sXYqEhynP4pD40VEz1p0VFgQtGgtV
+Bk/+jFg/aAZGmL2YRGeF4mIygSVsW0QCvoodNbPZ6qyZUIxDK1fTFXPy31PXvrP2e8IxMDMkD3aR
+h3sKdzc498k/NrBbQG8lWwL/Fldx/BSORNNpJzXpHUYgPThaicZhsOesMBQoa+Mhl6czaCDQ8rQo
+/lGs+wN7iyQ9o07/6uN7x4j1Olhh0bEH/edZenZEx3Jnmmfea42vck83L6SvHgprkIlGiq1vKoTk
+703RsIS23RivRq9g3bI2DnVEDW3ZlbY/i3LxTj1d2DRuiOY6LTtqiIL+OPXAeeB4pIQyyTqtl8kK
+xrOTSxZb+BCIKosrxFTaUlIjFeCexvQso9DMSOkt2LCx/ckJYxLZ+UW/azbzhikbRwSkTTD6GQ1X
+WpLGm/DH+PZT6BRQTJt3H9b/IfCNI0j/Z+p7s4tug3x5yp2lFT/Yfz7lC7+nuX6EsAV3EwPSotY5
+eRNOwzMcuoQfW+Ngz5juQd4pixo/f6DLJTcZwO+uHSVSWRKtaODAtMw8sOQ=
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert2.pem b/server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert2.pem
new file mode 100644
index 0000000..0819e4c
--- /dev/null
+++ b/server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert2.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIFfzCCA2egAwIBAgIKA4gmZ2BliZaFfDANBgkqhkiG9w0BAQsFADAbMRkwFwYDVQQFExBmOTIw
+MDllODUzYjZiMDQ1MB4XDTE4MDMyMTIwNTMyMVoXDTI4MDMxODIwNTMyMVowKTEZMBcGA1UEBRMQ
+Mjk2MGNiOWFlYmQ1MDVmNDEMMAoGA1UEDAwDVEVFMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+CgKCAgEArsj0RmYrjTICBSQVVcaXdb0lM/mrDf4hbQGxgzmBshG5hoIGmR3rWQ0iL6NNXELhsTji
+f/15kPsSkZKtiCz9S1/1ZJeYP2BhenkNE7CesaYqDU52+IU2tXv91ee+AfELlp4jmyb4BbB7S8mg
+4kB1VRyYhbelUsQZ/AB6SnPzqrhnIW5NR8fGV+1hkCQ/KlSc6JRUkcVAWtB/jvF/k8ji573tJG8q
+56Qu5q4HX+zOeo8X6XcCqalO9uea8MTSs68gAJtWGCmSfqTpkoayTCz7/P02Ya5TbxncwMieOmVw
+4iWI3VFRqS9x3GZx89ySECU+O1UkF/tOzYjCNWMCdbJRN5BgXIottTg/EdMQjheaRrWE9lD9DLzc
+BH0rWDB3tzoNmdBAK3S5C6M/XtH+qu/whheEFkWmSkfK4N/rURfvILnahaaO1luWwBI4T/7jPykc
+UYNyBcIKITT1U1LkTjsOEqMZXmwL+yOUsb6QoaFQdyj8QaFU39S6nIWPhgedzocLrXGJ1wSkt5si
+d1Kd+PL5i2/foWdEkaXFd5SwLX72JmJbJ8kxbxJpUtfzXjH3awd6R8DyQCDHUCblOpaZOJOjbHmz
+hQmdyqXpYdbjfVlb0y/Xlg2j4Z20RzXbv8wylAnvxuLeoqlGljRxaEOFZDBzWSJrrsLJOLqc23eW
+lB7KAnMCAwEAAaOBtjCBszAdBgNVHQ4EFgQU/GTocac1ZPIv2/ZayEjGLtkZeyQwHwYDVR0jBBgw
+FoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAgQw
+UAYDVR0fBEkwRzBFoEOgQYY/aHR0cHM6Ly9hbmRyb2lkLmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0
+aW9uL2NybC9FOEZBMTk2MzE0RDJGQTE4MA0GCSqGSIb3DQEBCwUAA4ICAQCqHtl3I1gfODJVBMZy
+IsHouloSr2KlpLhyvC0gPdgvN5jkNtJPYzcfjtHyNDEBv1QRu1ezdb95kk4sU7o9M4YuwA9gOb2Q
+guJ/ihaTSOW0hszqHjIX8HuYls/7D/mtdO1OQ5JD8a38hR9uhlNgn+mSiBvwr9gySVGWWSAu6O9e
+ZYOaCfTgc2tx0iqNltXNbL0OJZ8wYoBOo2fT72/EjOsQDoDPILrEfl/B2ek1cEFcB7iSEWrf3tyq
+hq1zpaRs5k2fWYqRuXGDg+B0QUh6cpC9bwbK5sBcOSOqtEZHBjEhIGZ1Sn6dRTCSk15yrMnl0U3E
+BXFxWrecqr6o4VsMCvXFazpmDVutU1t9TUV8NaIFL2Yh7D5BnctIe8Q/zRTn/XBLy1IOu2b971el
+Ms1BCd6T4KD5H9pT1RG8q8aN+tyu2lj3i0dx6ktwQKGPHeJJYW+1NZqFzBoLijnMNhV4lQhBHmdm
+uy8YPDGZIwsiojNsJ+3lXM1mgypFrDj5d7bFerN4eB/8XsiCIf+j4dIeYMIrAIee4P/KH9N2eXaA
+4O/EyojDlrlIKv0NHd50SZnAkxYJ8livW2wTyJd0SFu3go5kDpjFxFuI+cBPUZSMtFhjwCvZHv1n
+mNrJGk4diUD3Taxpdax8Y49Ik7iLI2Gy7vKtXwIzR9iyBKCOtvbTlhJWlQ==
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert3.pem b/server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert3.pem
new file mode 100644
index 0000000..faab920
--- /dev/null
+++ b/server/examples/pem/algorithm_RSA_SecurityLevel_TEE/cert3.pem
@@ -0,0 +1,27 @@
+-----BEGIN CERTIFICATE-----
+MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNVBAUTEGY5MjAw
+OWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYyODUyWjAbMRkwFwYDVQQFExBm
+OTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHs
+K7Qui8xUFmOr75gvMsd/dTEDDJdSSxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfd
+nJLmN0pTy/4lj4/7tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL
+/ggjnar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGqC4FSYa04
+T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQoVJYnFPlXTcHYvASLu+R
+hhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+OJtvsBslHZvPBKCOdT0MS+tgSOIfga+z1
+Z1g7+DVagf7quvmag8jfPioyKvxnK/EgsTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgp
+Zrt3i5MIlCaY504LzSRiigHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6
+tUXHI/+MRPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9EaDK8
+Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5UmAGMCAwEAAaOBpjCB
+ozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYDVR0jBBgwFoAUNmHhAHyIBQlRi0Rs
+R/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOg
+MYYvaHR0cHM6Ly9hbmRyb2lkLmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZI
+hvcNAQELBQADggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfB
+Pb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00mqC0w/Zwvju1t
+wb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rYDBJDcR9W62BW9jfIoBQcxUCU
+JouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPmQUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhu
+Kug2jITV0QkXvaJWF4nUaHOTNA4uJU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsY
+gBt6tKxxWH00XcyDCdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ
+79IyZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxDqwLqRBYk
+A3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23UaicMDSXYrB4I4WHXPGjxhZu
+CuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1wDB5y0USicV3YgYGmi+NZfhA4URSh77Y
+d6uuJOJENRaNVTzk
+-----END CERTIFICATE----- \ No newline at end of file
diff --git a/server/gradle/wrapper/gradle-wrapper.jar b/server/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..13372ae
--- /dev/null
+++ b/server/gradle/wrapper/gradle-wrapper.jar
Binary files differ
diff --git a/server/gradle/wrapper/gradle-wrapper.properties b/server/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..3e98a23
--- /dev/null
+++ b/server/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,23 @@
+#
+# Copyright 2016 Google Inc. All Rights Reserved.
+#
+# 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.
+#
+#
+
+#Thu Aug 11 15:22:08 AEST 2016
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-5.5-bin.zip
diff --git a/server/gradlew b/server/gradlew
new file mode 100755
index 0000000..9d82f78
--- /dev/null
+++ b/server/gradlew
@@ -0,0 +1,160 @@
+#!/usr/bin/env bash
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn ( ) {
+ echo "$*"
+}
+
+die ( ) {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+esac
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
+function splitJvmOpts() {
+ JVM_OPTS=("$@")
+}
+eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
+JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+
+exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
diff --git a/server/gradlew.bat b/server/gradlew.bat
new file mode 100644
index 0000000..aec9973
--- /dev/null
+++ b/server/gradlew.bat
@@ -0,0 +1,90 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windowz variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+if "%@eval[2+2]" == "4" goto 4NT_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+goto execute
+
+:4NT_args
+@rem Get arguments from the 4NT Shell from JP Software
+set CMD_LINE_ARGS=%$
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/server/settings.gradle b/server/settings.gradle
new file mode 100644
index 0000000..a97dce9
--- /dev/null
+++ b/server/settings.gradle
@@ -0,0 +1,16 @@
+/* Copyright 2016, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+rootProject.name = 'server'
diff --git a/server/src/main/java/com/android/example/KeyAttestationExample.java b/server/src/main/java/com/android/example/KeyAttestationExample.java
new file mode 100644
index 0000000..a503dc7
--- /dev/null
+++ b/server/src/main/java/com/android/example/KeyAttestationExample.java
@@ -0,0 +1,278 @@
+/* Copyright 2016, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+package com.android.example;
+
+import static com.google.android.attestation.Constants.GOOGLE_ROOT_CERTIFICATE;
+import static com.google.android.attestation.ParsedAttestationRecord.createParsedAttestationRecord;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.android.attestation.AttestationApplicationId;
+import com.google.android.attestation.AttestationApplicationId.AttestationPackageInfo;
+import com.google.android.attestation.CertificateRevocationStatus;
+import com.google.android.attestation.AuthorizationList;
+import com.google.android.attestation.ParsedAttestationRecord;
+import com.google.android.attestation.RootOfTrust;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.SignatureException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.bouncycastle.util.encoders.Base64;
+
+/**
+ * This is an illustration of how you can use the Bouncy Castle ASN.1 parser to extract information
+ * from an Android attestation data structure. On a secure server that you trust, create similar
+ * logic to verify that a key pair has been generated in an Android device. The app on the device
+ * must retrieve the key's certificate chain using KeyStore.getCertificateChain(), then send the
+ * contents to the trusted server.
+ *
+ * <p>In this example, the certificate chain includes hard-coded excerpts of each certificate.
+ *
+ * <p>This example demonstrates the following tasks:
+ *
+ * <p>1. Loading the certificates from PEM-encoded strings.
+ *
+ * <p>2. Verifying the certificate chain, up to the root. Note that this example does NOT require
+ * the root certificate to appear within Google's list of root certificates. However, if you're
+ * verifying the properties of hardware-backed keys on a device that ships with hardware-level key
+ * attestation, Android 7.0 (API level 24) or higher, and Google Play services, your production code
+ * should enforce this requirement.
+ *
+ * <p>3. Checking if any certificate in the chain has been revoked or suspended.
+ *
+ * <p>4. Extracting the attestation extension data from the attestation certificate.
+ *
+ * <p>5. Verifying (and printing) several important data elements from the attestation extension.
+ *
+ */
+@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+public class KeyAttestationExample {
+
+ private KeyAttestationExample() {}
+
+ public static void main(String[] args)
+ throws CertificateException, IOException, NoSuchProviderException, NoSuchAlgorithmException,
+ InvalidKeyException, SignatureException {
+ X509Certificate[] certs;
+ if (args.length == 1) {
+ String certFilesDir = args[0];
+ certs = loadCertificates(certFilesDir);
+ } else {
+ throw new IOException("Expected path to a directory containing certificates as an argument.");
+ }
+
+ verifyCertificateChain(certs);
+
+ ParsedAttestationRecord parsedAttestationRecord = createParsedAttestationRecord(certs[0]);
+
+ System.out.println("Attestation version: " + parsedAttestationRecord.attestationVersion);
+ System.out.println(
+ "Attestation Security Level: " + parsedAttestationRecord.attestationSecurityLevel.name());
+ System.out.println("Keymaster Version: " + parsedAttestationRecord.keymasterVersion);
+ System.out.println(
+ "Keymaster Security Level: " + parsedAttestationRecord.keymasterSecurityLevel.name());
+
+ System.out.println(
+ "Attestation Challenge: "
+ + new String(parsedAttestationRecord.attestationChallenge, UTF_8));
+ System.out.println("Unique ID: " + Arrays.toString(parsedAttestationRecord.uniqueId));
+
+ System.out.println("Software Enforced Authorization List:");
+ AuthorizationList softwareEnforced = parsedAttestationRecord.softwareEnforced;
+ printAuthorizationList(softwareEnforced, "\t");
+
+ System.out.println("TEE Enforced Authorization List:");
+ AuthorizationList teeEnforced = parsedAttestationRecord.teeEnforced;
+ printAuthorizationList(teeEnforced, "\t");
+ }
+
+ private static void printAuthorizationList(AuthorizationList authorizationList, String indent) {
+ // Detailed explanation of the keys and their values can be found here:
+ // https://source.android.com/security/keystore/tags
+ printOptional(authorizationList.purpose, indent + "Purpose(s)");
+ printOptional(authorizationList.algorithm, indent + "Algorithm");
+ printOptional(authorizationList.keySize, indent + "Key Size");
+ printOptional(authorizationList.digest, indent + "Digest");
+ printOptional(authorizationList.padding, indent + "Padding");
+ printOptional(authorizationList.ecCurve, indent + "EC Curve");
+ printOptional(authorizationList.rsaPublicExponent, indent + "RSA Public Exponent");
+ System.out.println(indent + "Rollback Resistance: " + authorizationList.rollbackResistance);
+ printOptional(authorizationList.activeDateTime, indent + "Active DateTime");
+ printOptional(
+ authorizationList.originationExpireDateTime, indent + "Origination Expire DateTime");
+ printOptional(authorizationList.usageExpireDateTime, indent + "Usage Expire DateTime");
+ System.out.println(indent + "No Auth Required: " + authorizationList.noAuthRequired);
+ printOptional(authorizationList.userAuthType, indent + "User Auth Type");
+ printOptional(authorizationList.authTimeout, indent + "Auth Timeout");
+ System.out.println(indent + "Allow While On Body: " + authorizationList.allowWhileOnBody);
+ System.out.println(
+ indent
+ + "Trusted User Presence Required: "
+ + authorizationList.trustedUserPresenceRequired);
+ System.out.println(
+ indent + "Trusted Confirmation Required: " + authorizationList.trustedConfirmationRequired);
+ System.out.println(
+ indent + "Unlocked Device Required: " + authorizationList.unlockedDeviceRequired);
+ System.out.println(indent + "All Applications: " + authorizationList.allApplications);
+ printOptional(authorizationList.applicationId, indent + "Application ID");
+ printOptional(authorizationList.creationDateTime, indent + "Creation DateTime");
+ printOptional(authorizationList.origin, indent + "Origin");
+ System.out.println(indent + "Rollback Resistant: " + authorizationList.rollbackResistant);
+ if (authorizationList.rootOfTrust.isPresent()) {
+ System.out.println(indent + "Root Of Trust:");
+ printRootOfTrust(authorizationList.rootOfTrust, indent + "\t");
+ }
+ printOptional(authorizationList.osVersion, indent + "OS Version");
+ printOptional(authorizationList.osPatchLevel, indent + "OS Patch Level");
+ if (authorizationList.attestationApplicationId.isPresent()) {
+ System.out.println(indent + "Attestation Application ID:");
+ printAttestationApplicationId(authorizationList.attestationApplicationId, indent + "\t");
+ }
+ printOptional(
+ authorizationList.attestationApplicationIdBytes,
+ indent + "Attestation Application ID Bytes");
+ printOptional(authorizationList.attestationIdBrand, indent + "Attestation ID Brand");
+ printOptional(authorizationList.attestationIdDevice, indent + "Attestation ID Device");
+ printOptional(authorizationList.attestationIdProduct, indent + "Attestation ID Product");
+ printOptional(authorizationList.attestationIdSerial, indent + "Attestation ID Serial");
+ printOptional(authorizationList.attestationIdImei, indent + "Attestation ID IMEI");
+ printOptional(authorizationList.attestationIdMeid, indent + "Attestation ID MEID");
+ printOptional(
+ authorizationList.attestationIdManufacturer, indent + "Attestation ID Manufacturer");
+ printOptional(authorizationList.attestationIdModel, indent + "Attestation ID Model");
+ printOptional(authorizationList.vendorPatchLevel, indent + "Vendor Patch Level");
+ printOptional(authorizationList.bootPatchLevel, indent + "Boot Patch Level");
+ }
+
+ private static void printRootOfTrust(Optional<RootOfTrust> rootOfTrust, String indent) {
+ if (rootOfTrust.isPresent()) {
+ System.out.println(
+ indent
+ + "Verified Boot Key: "
+ + Base64.toBase64String(rootOfTrust.get().verifiedBootKey));
+ System.out.println(indent + "Device Locked: " + rootOfTrust.get().deviceLocked);
+ System.out.println(
+ indent + "Verified Boot State: " + rootOfTrust.get().verifiedBootState.name());
+ System.out.println(
+ indent
+ + "Verified Boot Hash: "
+ + Base64.toBase64String(rootOfTrust.get().verifiedBootHash));
+ }
+ }
+
+ private static void printAttestationApplicationId(
+ Optional<AttestationApplicationId> attestationApplicationId, String indent) {
+ if (attestationApplicationId.isPresent()) {
+ System.out.println(indent + "Package Infos (<package name>, <version>): ");
+ for (AttestationPackageInfo info : attestationApplicationId.get().packageInfos) {
+ System.out.println(indent + "\t" + info.packageName + ", " + info.version);
+ }
+ System.out.println(indent + "Signature Digests:");
+ for (byte[] digest : attestationApplicationId.get().signatureDigests) {
+ System.out.println(indent + "\t" + Base64.toBase64String(digest));
+ }
+ }
+ }
+
+ private static <T> void printOptional(Optional<T> optional, String caption) {
+ if (optional.isPresent()) {
+ if (optional.get() instanceof byte[]) {
+ System.out.println(caption + ": " + Base64.toBase64String((byte[]) optional.get()));
+ } else {
+ System.out.println(caption + ": " + optional.get());
+ }
+ }
+ }
+
+ private static void verifyCertificateChain(X509Certificate[] certs)
+ throws CertificateException, NoSuchAlgorithmException, InvalidKeyException,
+ NoSuchProviderException, SignatureException, IOException {
+ X509Certificate parent = certs[certs.length - 1];
+ for (int i = certs.length - 1; i >= 0; i--) {
+ X509Certificate cert = certs[i];
+ // Verify that the certificate has not expired.
+ cert.checkValidity();
+ cert.verify(parent.getPublicKey());
+ parent = cert;
+ try {
+ CertificateRevocationStatus certStatus = CertificateRevocationStatus
+ .fetchStatus(cert.getSerialNumber());
+ if (certStatus != null) {
+ throw new CertificateException(
+ "Certificate revocation status is " + certStatus.status.name());
+ }
+ } catch (IOException e) {
+ throw new IOException("Unable to fetch certificate status. Check connectivity.");
+ }
+ }
+
+ // If the attestation is trustworthy and the device ships with hardware-
+ // level key attestation, Android 7.0 (API level 24) or higher, and
+ // Google Play services, the root certificate should be signed with the
+ // Google attestation root key.
+ X509Certificate secureRoot =
+ (X509Certificate)
+ CertificateFactory.getInstance("X.509")
+ .generateCertificate(
+ new ByteArrayInputStream(GOOGLE_ROOT_CERTIFICATE.getBytes(UTF_8)));
+ if (Arrays.equals(
+ secureRoot.getPublicKey().getEncoded(),
+ certs[certs.length - 1].getPublicKey().getEncoded())) {
+ System.out.println(
+ "The root certificate is correct, so this attestation is trustworthy, as long as none of"
+ + " the certificates in the chain have been revoked. A production-level system"
+ + " should check the certificate revocation lists using the distribution points that"
+ + " are listed in the intermediate and root certificates.");
+ } else {
+ System.out.println(
+ "The root certificate is NOT correct. The attestation was probably generated by"
+ + " software, not in secure hardware. This means that, although the attestation"
+ + " contents are probably valid and correct, there is no proof that they are in fact"
+ + " correct. If you're using a production-level system, you should now treat the"
+ + " properties of this attestation certificate as advisory only, and you shouldn't"
+ + " rely on this attestation certificate to provide security guarantees.");
+ }
+ }
+
+ private static X509Certificate[] loadCertificates(String certFilesDir)
+ throws CertificateException, IOException {
+ // Load the attestation certificates from the directory in alphabetic order.
+ List<Path> records;
+ try (Stream<Path> pathStream = Files.walk(Paths.get(certFilesDir))) {
+ records = pathStream.filter(Files::isRegularFile).sorted().collect(Collectors.toList());
+ }
+ X509Certificate[] certs = new X509Certificate[records.size()];
+ CertificateFactory factory = CertificateFactory.getInstance("X.509");
+ for (int i = 0; i < records.size(); ++i) {
+ byte[] encodedCert = Files.readAllBytes(records.get(i));
+ ByteArrayInputStream inputStream = new ByteArrayInputStream(encodedCert);
+ certs[i] = (X509Certificate) factory.generateCertificate(inputStream);
+ }
+ return certs;
+ }
+}
diff --git a/server/src/main/java/com/google/android/attestation/ASN1Parsing.java b/server/src/main/java/com/google/android/attestation/ASN1Parsing.java
new file mode 100644
index 0000000..1735822
--- /dev/null
+++ b/server/src/main/java/com/google/android/attestation/ASN1Parsing.java
@@ -0,0 +1,45 @@
+/* Copyright 2019, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+package com.google.android.attestation;
+
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1Integer;
+
+/** Utils to get java representation of ASN1 types. */
+class ASN1Parsing {
+
+ static boolean getBooleanFromAsn1(ASN1Encodable asn1Value) {
+ if (asn1Value instanceof ASN1Boolean) {
+ return ((ASN1Boolean) asn1Value).isTrue();
+ } else {
+ throw new RuntimeException(
+ "Boolean value expected; found " + asn1Value.getClass().getName() + " instead.");
+ }
+ }
+
+ static int getIntegerFromAsn1(ASN1Encodable asn1Value) {
+ if (asn1Value instanceof ASN1Integer) {
+ return ((ASN1Integer) asn1Value).getValue().intValueExact();
+ } else if (asn1Value instanceof ASN1Enumerated) {
+ return ((ASN1Enumerated) asn1Value).getValue().intValueExact();
+ } else {
+ throw new IllegalArgumentException(
+ "Integer value expected; found " + asn1Value.getClass().getName() + " instead.");
+ }
+ }
+}
diff --git a/server/src/main/java/com/google/android/attestation/AttestationApplicationId.java b/server/src/main/java/com/google/android/attestation/AttestationApplicationId.java
new file mode 100644
index 0000000..ead446d
--- /dev/null
+++ b/server/src/main/java/com/google/android/attestation/AttestationApplicationId.java
@@ -0,0 +1,185 @@
+/* Copyright 2019, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+package com.google.android.attestation;
+
+import static com.google.android.attestation.Constants.ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX;
+import static com.google.android.attestation.Constants.ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX;
+import static com.google.android.attestation.Constants.ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX;
+import static com.google.android.attestation.Constants.ATTESTATION_PACKAGE_INFO_VERSION_INDEX;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.DEROctetString;
+
+/**
+ * This data structure reflects the Android platform's belief as to which apps are allowed to use
+ * the secret key material under attestation. The ID can comprise multiple packages if and only if
+ * multiple packages share the same UID.
+ */
+public class AttestationApplicationId implements Comparable<AttestationApplicationId> {
+ public final List<AttestationPackageInfo> packageInfos;
+ public final List<byte[]> signatureDigests;
+
+ private AttestationApplicationId(DEROctetString attestationApplicationId) throws IOException {
+ ASN1Sequence attestationApplicationIdSequence =
+ (ASN1Sequence) ASN1Sequence.fromByteArray(attestationApplicationId.getOctets());
+ ASN1Set attestationPackageInfos =
+ (ASN1Set)
+ attestationApplicationIdSequence.getObjectAt(
+ ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX);
+ this.packageInfos = new ArrayList<>();
+ for (ASN1Encodable packageInfo : attestationPackageInfos) {
+ this.packageInfos.add(new AttestationPackageInfo((ASN1Sequence) packageInfo));
+ }
+
+ ASN1Set digests =
+ (ASN1Set)
+ attestationApplicationIdSequence.getObjectAt(
+ ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX);
+ this.signatureDigests = new ArrayList<>();
+ for (ASN1Encodable digest : digests) {
+ this.signatureDigests.add(((ASN1OctetString) digest).getOctets());
+ }
+ }
+
+ AttestationApplicationId(
+ List<AttestationPackageInfo> packageInfos, List<byte[]> signatureDigests) {
+ this.packageInfos = packageInfos;
+ this.signatureDigests = signatureDigests;
+ }
+
+ static AttestationApplicationId createAttestationApplicationId(
+ DEROctetString attestationApplicationId) {
+ if (attestationApplicationId == null) {
+ return null;
+ }
+ try {
+ return new AttestationApplicationId(attestationApplicationId);
+ } catch (IOException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public int compareTo(AttestationApplicationId other) {
+ int res = Integer.compare(packageInfos.size(), other.packageInfos.size());
+ if (res != 0) {
+ return res;
+ }
+ for (int i = 0; i < packageInfos.size(); ++i) {
+ res = packageInfos.get(i).compareTo(other.packageInfos.get(i));
+ if (res != 0) {
+ return res;
+ }
+ }
+ res = Integer.compare(signatureDigests.size(), other.signatureDigests.size());
+ if (res != 0) {
+ return res;
+ }
+ ByteArrayComparator cmp = new ByteArrayComparator();
+ for (int i = 0; i < signatureDigests.size(); ++i) {
+ res = cmp.compare(signatureDigests.get(i), other.signatureDigests.get(i));
+ if (res != 0) {
+ return res;
+ }
+ }
+ return res;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof AttestationApplicationId)
+ && (compareTo((AttestationApplicationId) o) == 0);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(packageInfos, Arrays.deepHashCode(signatureDigests.toArray()));
+ }
+
+ /** Provides package's name and version number. */
+ public static class AttestationPackageInfo implements Comparable<AttestationPackageInfo> {
+ public final String packageName;
+ public final long version;
+
+ private AttestationPackageInfo(ASN1Sequence packageInfo) {
+ this.packageName =
+ new String(
+ ((ASN1OctetString)
+ packageInfo.getObjectAt(ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX))
+ .getOctets(),
+ UTF_8);
+ this.version =
+ ((ASN1Integer) packageInfo.getObjectAt(ATTESTATION_PACKAGE_INFO_VERSION_INDEX))
+ .getValue()
+ .longValue();
+ }
+
+ AttestationPackageInfo(String packageName, long version) {
+ this.packageName = packageName;
+ this.version = version;
+ }
+
+ @Override
+ public int compareTo(AttestationPackageInfo other) {
+ int res = packageName.compareTo(other.packageName);
+ if (res != 0) {
+ return res;
+ }
+ res = Long.compare(version, other.version);
+ if (res != 0) {
+ return res;
+ }
+ return res;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return (o instanceof AttestationPackageInfo) && (compareTo((AttestationPackageInfo) o) == 0);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(packageName, version);
+ }
+ }
+
+ private static class ByteArrayComparator implements java.util.Comparator<byte[]> {
+ @Override
+ public int compare(byte[] a, byte[] b) {
+ int res = Integer.compare(a.length, b.length);
+ if (res != 0) {
+ return res;
+ }
+ for (int i = 0; i < a.length; ++i) {
+ res = Byte.compare(a[i], b[i]);
+ if (res != 0) {
+ return res;
+ }
+ }
+ return res;
+ }
+ }
+}
diff --git a/server/src/main/java/com/google/android/attestation/AuthorizationList.java b/server/src/main/java/com/google/android/attestation/AuthorizationList.java
new file mode 100644
index 0000000..d86922d
--- /dev/null
+++ b/server/src/main/java/com/google/android/attestation/AuthorizationList.java
@@ -0,0 +1,747 @@
+/* Copyright 2019, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+package com.google.android.attestation;
+
+import static com.google.android.attestation.AuthorizationList.UserAuthType.FINGERPRINT;
+import static com.google.android.attestation.AuthorizationList.UserAuthType.PASSWORD;
+import static com.google.android.attestation.AuthorizationList.UserAuthType.USER_AUTH_TYPE_ANY;
+import static com.google.android.attestation.AuthorizationList.UserAuthType.USER_AUTH_TYPE_NONE;
+import static com.google.android.attestation.Constants.KM_TAG_ACTIVE_DATE_TIME;
+import static com.google.android.attestation.Constants.KM_TAG_ALGORITHM;
+import static com.google.android.attestation.Constants.KM_TAG_ALLOW_WHILE_ON_BODY;
+import static com.google.android.attestation.Constants.KM_TAG_ALL_APPLICATIONS;
+import static com.google.android.attestation.Constants.KM_TAG_APPLICATION_ID;
+import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_APPLICATION_ID;
+import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_BRAND;
+import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_DEVICE;
+import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_IMEI;
+import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_MANUFACTURER;
+import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_MEID;
+import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_MODEL;
+import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_PRODUCT;
+import static com.google.android.attestation.Constants.KM_TAG_ATTESTATION_ID_SERIAL;
+import static com.google.android.attestation.Constants.KM_TAG_AUTH_TIMEOUT;
+import static com.google.android.attestation.Constants.KM_TAG_BOOT_PATCH_LEVEL;
+import static com.google.android.attestation.Constants.KM_TAG_CREATION_DATE_TIME;
+import static com.google.android.attestation.Constants.KM_TAG_DEVICE_UNIQUE_ATTESTATION;
+import static com.google.android.attestation.Constants.KM_TAG_DIGEST;
+import static com.google.android.attestation.Constants.KM_TAG_EC_CURVE;
+import static com.google.android.attestation.Constants.KM_TAG_KEY_SIZE;
+import static com.google.android.attestation.Constants.KM_TAG_NO_AUTH_REQUIRED;
+import static com.google.android.attestation.Constants.KM_TAG_ORIGIN;
+import static com.google.android.attestation.Constants.KM_TAG_ORIGINATION_EXPIRE_DATE_TIME;
+import static com.google.android.attestation.Constants.KM_TAG_OS_PATCH_LEVEL;
+import static com.google.android.attestation.Constants.KM_TAG_OS_VERSION;
+import static com.google.android.attestation.Constants.KM_TAG_PADDING;
+import static com.google.android.attestation.Constants.KM_TAG_PURPOSE;
+import static com.google.android.attestation.Constants.KM_TAG_ROLLBACK_RESISTANCE;
+import static com.google.android.attestation.Constants.KM_TAG_ROLLBACK_RESISTANT;
+import static com.google.android.attestation.Constants.KM_TAG_ROOT_OF_TRUST;
+import static com.google.android.attestation.Constants.KM_TAG_RSA_PUBLIC_EXPONENT;
+import static com.google.android.attestation.Constants.KM_TAG_TRUSTED_CONFIRMATION_REQUIRED;
+import static com.google.android.attestation.Constants.KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED;
+import static com.google.android.attestation.Constants.KM_TAG_UNLOCKED_DEVICE_REQUIRED;
+import static com.google.android.attestation.Constants.KM_TAG_USAGE_EXPIRE_DATE_TIME;
+import static com.google.android.attestation.Constants.KM_TAG_USER_AUTH_TYPE;
+import static com.google.android.attestation.Constants.KM_TAG_VENDOR_PATCH_LEVEL;
+import static com.google.android.attestation.Constants.UINT32_MAX;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1EncodableVector;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DERNull;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+import org.bouncycastle.asn1.DERSet;
+import org.bouncycastle.asn1.DERTaggedObject;
+
+/**
+ * This data structure contains the key pair's properties themselves, as defined in the Keymaster
+ * hardware abstraction layer (HAL). You compare these values to the device's current state or to a
+ * set of expected values to verify that a key pair is still valid for use in your app.
+ */
+@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
+public class AuthorizationList {
+ /** Specifies the types of user authenticators that may be used to authorize this key. */
+ public enum UserAuthType {
+ USER_AUTH_TYPE_NONE,
+ PASSWORD,
+ FINGERPRINT,
+ USER_AUTH_TYPE_ANY
+ }
+
+ public final Optional<Set<Integer>> purpose;
+ public final Optional<Integer> algorithm;
+ public final Optional<Integer> keySize;
+ public final Optional<Set<Integer>> digest;
+ public final Optional<Set<Integer>> padding;
+ public final Optional<Integer> ecCurve;
+ public final Optional<Long> rsaPublicExponent;
+ public final boolean rollbackResistance;
+ public final Optional<Instant> activeDateTime;
+ public final Optional<Instant> originationExpireDateTime;
+ public final Optional<Instant> usageExpireDateTime;
+ public final boolean noAuthRequired;
+ public final Optional<Set<UserAuthType>> userAuthType;
+ public final Optional<Duration> authTimeout;
+ public final boolean allowWhileOnBody;
+ public final boolean trustedUserPresenceRequired;
+ public final boolean trustedConfirmationRequired;
+ public final boolean unlockedDeviceRequired;
+ public final boolean allApplications;
+ public final Optional<byte[]> applicationId;
+ public final Optional<Instant> creationDateTime;
+ public final Optional<Integer> origin;
+ public final boolean rollbackResistant;
+ public final Optional<RootOfTrust> rootOfTrust;
+ public final Optional<Integer> osVersion;
+ public final Optional<Integer> osPatchLevel;
+ public final Optional<AttestationApplicationId> attestationApplicationId;
+ public final Optional<byte[]> attestationApplicationIdBytes;
+ public final Optional<byte[]> attestationIdBrand;
+ public final Optional<byte[]> attestationIdDevice;
+ public final Optional<byte[]> attestationIdProduct;
+ public final Optional<byte[]> attestationIdSerial;
+ public final Optional<byte[]> attestationIdImei;
+ public final Optional<byte[]> attestationIdMeid;
+ public final Optional<byte[]> attestationIdManufacturer;
+ public final Optional<byte[]> attestationIdModel;
+ public final Optional<Integer> vendorPatchLevel;
+ public final Optional<Integer> bootPatchLevel;
+ public final boolean individualAttestation;
+
+ private AuthorizationList(ASN1Encodable[] authorizationList, int attestationVersion) {
+ Map<Integer, ASN1Primitive> authorizationMap = getAuthorizationMap(authorizationList);
+ this.purpose = findOptionalIntegerSetAuthorizationListEntry(authorizationMap, KM_TAG_PURPOSE);
+ this.algorithm = findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_ALGORITHM);
+ this.keySize = findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_KEY_SIZE);
+ this.digest = findOptionalIntegerSetAuthorizationListEntry(authorizationMap, KM_TAG_DIGEST);
+ this.padding = findOptionalIntegerSetAuthorizationListEntry(authorizationMap, KM_TAG_PADDING);
+ this.ecCurve = findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_EC_CURVE);
+ this.rsaPublicExponent =
+ findOptionalLongAuthorizationListEntry(authorizationMap, KM_TAG_RSA_PUBLIC_EXPONENT);
+ this.rollbackResistance =
+ findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_ROLLBACK_RESISTANCE);
+ this.activeDateTime =
+ findOptionalInstantMillisAuthorizationListEntry(authorizationMap, KM_TAG_ACTIVE_DATE_TIME);
+ this.originationExpireDateTime =
+ findOptionalInstantMillisAuthorizationListEntry(
+ authorizationMap, KM_TAG_ORIGINATION_EXPIRE_DATE_TIME);
+ this.usageExpireDateTime =
+ findOptionalInstantMillisAuthorizationListEntry(
+ authorizationMap, KM_TAG_USAGE_EXPIRE_DATE_TIME);
+ this.noAuthRequired =
+ findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_NO_AUTH_REQUIRED);
+ this.userAuthType = findOptionalUserAuthType(authorizationMap, KM_TAG_USER_AUTH_TYPE);
+ this.authTimeout =
+ findOptionalDurationSecondsAuthorizationListEntry(authorizationMap, KM_TAG_AUTH_TIMEOUT);
+ this.allowWhileOnBody =
+ findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_ALLOW_WHILE_ON_BODY);
+ this.trustedUserPresenceRequired =
+ findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED);
+ this.trustedConfirmationRequired =
+ findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_TRUSTED_CONFIRMATION_REQUIRED);
+ this.unlockedDeviceRequired =
+ findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_UNLOCKED_DEVICE_REQUIRED);
+ this.allApplications =
+ findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_ALL_APPLICATIONS);
+ this.applicationId =
+ findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_APPLICATION_ID);
+ this.creationDateTime =
+ findOptionalInstantMillisAuthorizationListEntry(
+ authorizationMap, KM_TAG_CREATION_DATE_TIME);
+ this.origin = findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_ORIGIN);
+ this.rollbackResistant =
+ findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_ROLLBACK_RESISTANT);
+ this.rootOfTrust =
+ Optional.ofNullable(
+ RootOfTrust.createRootOfTrust(
+ (ASN1Sequence) findAuthorizationListEntry(authorizationMap, KM_TAG_ROOT_OF_TRUST),
+ attestationVersion));
+ this.osVersion = findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_OS_VERSION);
+ this.osPatchLevel =
+ findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_OS_PATCH_LEVEL);
+ this.attestationApplicationId =
+ Optional.ofNullable(
+ AttestationApplicationId.createAttestationApplicationId(
+ (DEROctetString)
+ findAuthorizationListEntry(
+ authorizationMap, KM_TAG_ATTESTATION_APPLICATION_ID)));
+ this.attestationApplicationIdBytes =
+ findOptionalByteArrayAuthorizationListEntry(
+ authorizationMap, KM_TAG_ATTESTATION_APPLICATION_ID);
+ this.attestationIdBrand =
+ findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_BRAND);
+ this.attestationIdDevice =
+ findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_DEVICE);
+ this.attestationIdProduct =
+ findOptionalByteArrayAuthorizationListEntry(
+ authorizationMap, KM_TAG_ATTESTATION_ID_PRODUCT);
+ this.attestationIdSerial =
+ findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_SERIAL);
+ this.attestationIdImei =
+ findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_IMEI);
+ this.attestationIdMeid =
+ findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_MEID);
+ this.attestationIdManufacturer =
+ findOptionalByteArrayAuthorizationListEntry(
+ authorizationMap, KM_TAG_ATTESTATION_ID_MANUFACTURER);
+ this.attestationIdModel =
+ findOptionalByteArrayAuthorizationListEntry(authorizationMap, KM_TAG_ATTESTATION_ID_MODEL);
+ this.vendorPatchLevel =
+ findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_VENDOR_PATCH_LEVEL);
+ this.bootPatchLevel =
+ findOptionalIntegerAuthorizationListEntry(authorizationMap, KM_TAG_BOOT_PATCH_LEVEL);
+ this.individualAttestation =
+ findBooleanAuthorizationListEntry(authorizationMap, KM_TAG_DEVICE_UNIQUE_ATTESTATION);
+ }
+
+ private AuthorizationList(Builder builder) {
+ this.purpose = Optional.ofNullable(builder.purpose);
+ this.algorithm = Optional.ofNullable(builder.algorithm);
+ this.keySize = Optional.ofNullable(builder.keySize);
+ this.digest = Optional.ofNullable(builder.digest);
+ this.padding = Optional.ofNullable(builder.padding);
+ this.ecCurve = Optional.ofNullable(builder.ecCurve);
+ this.rsaPublicExponent = Optional.ofNullable(builder.rsaPublicExponent);
+ this.rollbackResistance = builder.rollbackResistance;
+ this.activeDateTime = Optional.ofNullable(builder.activeDateTime);
+ this.originationExpireDateTime = Optional.ofNullable(builder.originationExpireDateTime);
+ this.usageExpireDateTime = Optional.ofNullable(builder.usageExpireDateTime);
+ this.noAuthRequired = builder.noAuthRequired;
+ this.userAuthType = Optional.ofNullable(builder.userAuthType);
+ this.authTimeout = Optional.ofNullable(builder.authTimeout);
+ this.allowWhileOnBody = builder.allowWhileOnBody;
+ this.trustedUserPresenceRequired = builder.trustedUserPresenceRequired;
+ this.trustedConfirmationRequired = builder.trustedConfirmationRequired;
+ this.unlockedDeviceRequired = builder.unlockedDeviceRequired;
+ this.allApplications = builder.allApplications;
+ this.applicationId = Optional.ofNullable(builder.applicationId);
+ this.creationDateTime = Optional.ofNullable(builder.creationDateTime);
+ this.origin = Optional.ofNullable(builder.origin);
+ this.rollbackResistant = builder.rollbackResistant;
+ this.rootOfTrust = Optional.ofNullable(builder.rootOfTrust);
+ this.osVersion = Optional.ofNullable(builder.osVersion);
+ this.osPatchLevel = Optional.ofNullable(builder.osPatchLevel);
+ this.attestationApplicationId = Optional.ofNullable(builder.attestationApplicationId);
+ this.attestationApplicationIdBytes = Optional.ofNullable(builder.attestationApplicationIdBytes);
+ this.attestationIdBrand = Optional.ofNullable(builder.attestationIdBrand);
+ this.attestationIdDevice = Optional.ofNullable(builder.attestationIdDevice);
+ this.attestationIdProduct = Optional.ofNullable(builder.attestationIdProduct);
+ this.attestationIdSerial = Optional.ofNullable(builder.attestationIdSerial);
+ this.attestationIdImei = Optional.ofNullable(builder.attestationIdImei);
+ this.attestationIdMeid = Optional.ofNullable(builder.attestationIdMeid);
+ this.attestationIdManufacturer = Optional.ofNullable(builder.attestationIdManufacturer);
+ this.attestationIdModel = Optional.ofNullable(builder.attestationIdModel);
+ this.vendorPatchLevel = Optional.ofNullable(builder.vendorPatchLevel);
+ this.bootPatchLevel = Optional.ofNullable(builder.bootPatchLevel);
+ this.individualAttestation = builder.individualAttestation;
+ }
+
+ static AuthorizationList createAuthorizationList(
+ ASN1Encodable[] authorizationList, int attestationVersion) {
+ return new AuthorizationList(authorizationList, attestationVersion);
+ }
+
+ private static Map<Integer, ASN1Primitive> getAuthorizationMap(
+ ASN1Encodable[] authorizationList) {
+ Map<Integer, ASN1Primitive> authorizationMap = new HashMap<>();
+ for (ASN1Encodable entry : authorizationList) {
+ ASN1TaggedObject taggedEntry = (ASN1TaggedObject) entry;
+ authorizationMap.put(taggedEntry.getTagNo(), taggedEntry.getObject());
+ }
+ return authorizationMap;
+ }
+
+ private static ASN1Primitive findAuthorizationListEntry(
+ Map<Integer, ASN1Primitive> authorizationMap, int tag) {
+ return authorizationMap.getOrDefault(tag, null);
+ }
+
+ private static Optional<Set<Integer>> findOptionalIntegerSetAuthorizationListEntry(
+ Map<Integer, ASN1Primitive> authorizationMap, int tag) {
+ ASN1Set asn1Set = (ASN1Set) findAuthorizationListEntry(authorizationMap, tag);
+ if (asn1Set == null) {
+ return Optional.empty();
+ }
+ Set<Integer> entrySet = new HashSet<>();
+ for (ASN1Encodable value : asn1Set) {
+ entrySet.add(ASN1Parsing.getIntegerFromAsn1(value));
+ }
+ return Optional.of(entrySet);
+ }
+
+ private static Optional<Duration> findOptionalDurationSecondsAuthorizationListEntry(
+ Map<Integer, ASN1Primitive> authorizationMap, int tag) {
+ Optional<Integer> seconds = findOptionalIntegerAuthorizationListEntry(authorizationMap, tag);
+ return seconds.map(Duration::ofSeconds);
+ }
+
+ private static Optional<Integer> findOptionalIntegerAuthorizationListEntry(
+ Map<Integer, ASN1Primitive> authorizationMap, int tag) {
+ ASN1Primitive entry = findAuthorizationListEntry(authorizationMap, tag);
+ return Optional.ofNullable(entry).map(ASN1Parsing::getIntegerFromAsn1);
+ }
+
+ private static Optional<Instant> findOptionalInstantMillisAuthorizationListEntry(
+ Map<Integer, ASN1Primitive> authorizationMap, int tag) {
+ Optional<Long> millis = findOptionalLongAuthorizationListEntry(authorizationMap, tag);
+ return millis.map(Instant::ofEpochMilli);
+ }
+
+ private static Optional<Long> findOptionalLongAuthorizationListEntry(
+ Map<Integer, ASN1Primitive> authorizationMap, int tag) {
+ ASN1Integer longEntry = ((ASN1Integer) findAuthorizationListEntry(authorizationMap, tag));
+ return Optional.ofNullable(longEntry).map(value -> value.getValue().longValue());
+ }
+
+ private static boolean findBooleanAuthorizationListEntry(
+ Map<Integer, ASN1Primitive> authorizationMap, int tag) {
+ return null != findAuthorizationListEntry(authorizationMap, tag);
+ }
+
+ private static Optional<byte[]> findOptionalByteArrayAuthorizationListEntry(
+ Map<Integer, ASN1Primitive> authorizationMap, int tag) {
+ ASN1OctetString entry = (ASN1OctetString) findAuthorizationListEntry(authorizationMap, tag);
+ return Optional.ofNullable(entry).map(ASN1OctetString::getOctets);
+ }
+
+ private static Optional<Set<UserAuthType>> findOptionalUserAuthType(
+ Map<Integer, ASN1Primitive> authorizationMap, int tag) {
+ Optional<Long> userAuthType = findOptionalLongAuthorizationListEntry(authorizationMap, tag);
+ return userAuthType.map(AuthorizationList::userAuthTypeToEnum);
+ }
+
+ // Visible for testing.
+ static Set<UserAuthType> userAuthTypeToEnum(long userAuthType) {
+ if (userAuthType == 0) {
+ return Set.of(USER_AUTH_TYPE_NONE);
+ }
+
+ Set<UserAuthType> result = new HashSet<>();
+
+ if ((userAuthType & 1L) == 1L) {
+ result.add(PASSWORD);
+ }
+ if ((userAuthType & 2L) == 2L) {
+ result.add(FINGERPRINT);
+ }
+ if (userAuthType == UINT32_MAX) {
+ result.add(USER_AUTH_TYPE_ANY);
+ }
+
+ if (result.isEmpty()) {
+ throw new IllegalArgumentException("Invalid User Auth Type.");
+ }
+
+ return result;
+ }
+
+ private static Long userAuthTypeToLong(Set<UserAuthType> userAuthType) {
+ if (userAuthType.contains(USER_AUTH_TYPE_NONE)) {
+ return 0L;
+ }
+
+ Long result = 0L;
+
+ for (UserAuthType type : userAuthType) {
+ switch (type) {
+ case PASSWORD:
+ result |= 1L;
+ break;
+ case FINGERPRINT:
+ result |= 2L;
+ break;
+ case USER_AUTH_TYPE_ANY:
+ result |= UINT32_MAX;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (result == 0) {
+ throw new IllegalArgumentException("Invalid User Auth Type.");
+ }
+
+ return result;
+ }
+
+ public ASN1Sequence toAsn1Sequence() {
+ ASN1EncodableVector vector = new ASN1EncodableVector();
+ addOptionalIntegerSet(KM_TAG_PURPOSE, this.purpose, vector);
+ addOptionalInteger(KM_TAG_ALGORITHM, this.algorithm, vector);
+ addOptionalInteger(KM_TAG_KEY_SIZE, this.keySize, vector);
+ addOptionalIntegerSet(KM_TAG_DIGEST, this.digest, vector);
+ addOptionalIntegerSet(KM_TAG_PADDING, this.padding, vector);
+ addOptionalInteger(KM_TAG_EC_CURVE, this.ecCurve, vector);
+ addOptionalLong(KM_TAG_RSA_PUBLIC_EXPONENT, this.rsaPublicExponent, vector);
+ addBoolean(KM_TAG_ROLLBACK_RESISTANCE, this.rollbackResistance, vector);
+ addOptionalInstant(KM_TAG_ACTIVE_DATE_TIME, this.activeDateTime, vector);
+ addOptionalInstant(KM_TAG_ORIGINATION_EXPIRE_DATE_TIME, this.originationExpireDateTime, vector);
+ addOptionalInstant(KM_TAG_USAGE_EXPIRE_DATE_TIME, this.usageExpireDateTime, vector);
+ addBoolean(KM_TAG_NO_AUTH_REQUIRED, this.noAuthRequired, vector);
+ addOptionalUserAuthType(KM_TAG_USER_AUTH_TYPE, this.userAuthType, vector);
+ addOptionalDuration(KM_TAG_AUTH_TIMEOUT, this.authTimeout, vector);
+ addBoolean(KM_TAG_ALLOW_WHILE_ON_BODY, this.allowWhileOnBody, vector);
+ addBoolean(KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED, this.trustedUserPresenceRequired, vector);
+ addBoolean(KM_TAG_TRUSTED_CONFIRMATION_REQUIRED, this.trustedConfirmationRequired, vector);
+ addBoolean(KM_TAG_UNLOCKED_DEVICE_REQUIRED, this.unlockedDeviceRequired, vector);
+ addBoolean(KM_TAG_ALL_APPLICATIONS, this.allApplications, vector);
+ addOptionalOctetString(KM_TAG_APPLICATION_ID, this.applicationId, vector);
+ addOptionalInstant(KM_TAG_CREATION_DATE_TIME, this.creationDateTime, vector);
+ addOptionalInteger(KM_TAG_ORIGIN, this.origin, vector);
+ addBoolean(KM_TAG_ROLLBACK_RESISTANT, this.rollbackResistant, vector);
+ addOptionalRootOfTrust(KM_TAG_ROOT_OF_TRUST, this.rootOfTrust, vector);
+ addOptionalInteger(KM_TAG_OS_VERSION, this.osVersion, vector);
+ addOptionalInteger(KM_TAG_OS_PATCH_LEVEL, this.osPatchLevel, vector);
+ addOptionalOctetString(
+ KM_TAG_ATTESTATION_APPLICATION_ID, this.attestationApplicationIdBytes, vector);
+ addOptionalOctetString(KM_TAG_ATTESTATION_ID_BRAND, this.attestationIdBrand, vector);
+ addOptionalOctetString(KM_TAG_ATTESTATION_ID_DEVICE, this.attestationIdDevice, vector);
+ addOptionalOctetString(KM_TAG_ATTESTATION_ID_PRODUCT, this.attestationIdProduct, vector);
+ addOptionalOctetString(KM_TAG_ATTESTATION_ID_SERIAL, this.attestationIdSerial, vector);
+ addOptionalOctetString(KM_TAG_ATTESTATION_ID_IMEI, this.attestationIdImei, vector);
+ addOptionalOctetString(KM_TAG_ATTESTATION_ID_MEID, this.attestationIdMeid, vector);
+ addOptionalOctetString(
+ KM_TAG_ATTESTATION_ID_MANUFACTURER, this.attestationIdManufacturer, vector);
+ addOptionalOctetString(KM_TAG_ATTESTATION_ID_MODEL, this.attestationIdModel, vector);
+ addOptionalInteger(KM_TAG_VENDOR_PATCH_LEVEL, this.vendorPatchLevel, vector);
+ addOptionalInteger(KM_TAG_BOOT_PATCH_LEVEL, this.bootPatchLevel, vector);
+ addBoolean(KM_TAG_DEVICE_UNIQUE_ATTESTATION, this.individualAttestation, vector);
+ return new DERSequence(vector);
+ }
+
+ private static void addOptionalIntegerSet(
+ int tag, Optional<Set<Integer>> entry, ASN1EncodableVector vector) {
+ if (entry.isPresent()) {
+ ASN1EncodableVector tmp = new ASN1EncodableVector();
+ entry.get().forEach((Integer value) -> tmp.add(new ASN1Integer(value.longValue())));
+ vector.add(new DERTaggedObject(tag, new DERSet(tmp)));
+ }
+ }
+
+ private static void addOptionalInstant(
+ int tag, Optional<Instant> entry, ASN1EncodableVector vector) {
+ if (entry.isPresent()) {
+ vector.add(new DERTaggedObject(tag, new ASN1Integer(entry.get().toEpochMilli())));
+ }
+ }
+
+ private static void addOptionalDuration(
+ int tag, Optional<Duration> entry, ASN1EncodableVector vector) {
+ if (entry.isPresent()) {
+ vector.add(new DERTaggedObject(tag, new ASN1Integer(entry.get().getSeconds())));
+ }
+ }
+
+ private static void addBoolean(int tag, boolean entry, ASN1EncodableVector vector) {
+ if (entry) {
+ vector.add(new DERTaggedObject(tag, DERNull.INSTANCE));
+ }
+ }
+
+ private static void addOptionalInteger(
+ int tag, Optional<Integer> entry, ASN1EncodableVector vector) {
+ if (entry.isPresent()) {
+ vector.add(new DERTaggedObject(tag, new ASN1Integer(entry.get())));
+ }
+ }
+
+ private static void addOptionalLong(int tag, Optional<Long> entry, ASN1EncodableVector vector) {
+ if (entry.isPresent()) {
+ vector.add(new DERTaggedObject(tag, new ASN1Integer(entry.get())));
+ }
+ }
+
+ private static void addOptionalOctetString(
+ int tag, Optional<byte[]> entry, ASN1EncodableVector vector) {
+ if (entry.isPresent()) {
+ vector.add(new DERTaggedObject(tag, new DEROctetString(entry.get())));
+ }
+ }
+
+ private static void addOptionalUserAuthType(
+ int tag, Optional<Set<UserAuthType>> entry, ASN1EncodableVector vector) {
+ if (entry.isPresent()) {
+ vector.add(new DERTaggedObject(tag, new ASN1Integer(userAuthTypeToLong(entry.get()))));
+ }
+ }
+
+ private static void addOptionalRootOfTrust(
+ int tag, Optional<RootOfTrust> entry, ASN1EncodableVector vector) {
+ if (entry.isPresent()) {
+ vector.add(new DERTaggedObject(tag, entry.get().toAsn1Sequence()));
+ }
+ }
+
+ public static Builder builder() {
+ return new Builder();
+ }
+
+ /**
+ * Builder for an AuthorizationList. Any field not set will be made an Optional.empty or set with
+ * the default value.
+ */
+ public static final class Builder {
+
+ Set<Integer> purpose;
+ Integer algorithm;
+ Integer keySize;
+ Set<Integer> digest;
+ Set<Integer> padding;
+ Integer ecCurve;
+ Long rsaPublicExponent;
+ boolean rollbackResistance;
+ Instant activeDateTime;
+ Instant originationExpireDateTime;
+ Instant usageExpireDateTime;
+ boolean noAuthRequired;
+ Set<UserAuthType> userAuthType;
+ Duration authTimeout;
+ boolean allowWhileOnBody;
+ boolean trustedUserPresenceRequired;
+ boolean trustedConfirmationRequired;
+ boolean unlockedDeviceRequired;
+ boolean allApplications;
+ byte[] applicationId;
+ Instant creationDateTime;
+ Integer origin;
+ boolean rollbackResistant;
+ RootOfTrust rootOfTrust;
+ Integer osVersion;
+ Integer osPatchLevel;
+ AttestationApplicationId attestationApplicationId;
+ byte[] attestationApplicationIdBytes;
+ byte[] attestationIdBrand;
+ byte[] attestationIdDevice;
+ byte[] attestationIdProduct;
+ byte[] attestationIdSerial;
+ byte[] attestationIdImei;
+ byte[] attestationIdMeid;
+ byte[] attestationIdManufacturer;
+ byte[] attestationIdModel;
+ Integer vendorPatchLevel;
+ Integer bootPatchLevel;
+ boolean individualAttestation;
+
+ public Builder setPurpose(Set<Integer> purpose) {
+ this.purpose = purpose;
+ return this;
+ }
+
+ public Builder setAlgorithm(Integer algorithm) {
+ this.algorithm = algorithm;
+ return this;
+ }
+
+ public Builder setKeySize(Integer keySize) {
+ this.keySize = keySize;
+ return this;
+ }
+
+ public Builder setDigest(Set<Integer> digest) {
+ this.digest = digest;
+ return this;
+ }
+
+ public Builder setPadding(Set<Integer> padding) {
+ this.padding = padding;
+ return this;
+ }
+
+ public Builder setEcCurve(Integer ecCurve) {
+ this.ecCurve = ecCurve;
+ return this;
+ }
+
+ public Builder setRsaPublicExponent(Long rsaPublicExponent) {
+ this.rsaPublicExponent = rsaPublicExponent;
+ return this;
+ }
+
+ public Builder setRollbackResistance(boolean rollbackResistance) {
+ this.rollbackResistance = rollbackResistance;
+ return this;
+ }
+
+ public Builder setActiveDateTime(Instant activeDateTime) {
+ this.activeDateTime = activeDateTime;
+ return this;
+ }
+
+ public Builder setOriginationExpireDateTime(Instant originationExpireDateTime) {
+ this.originationExpireDateTime = originationExpireDateTime;
+ return this;
+ }
+
+ public Builder setUsageExpireDateTime(Instant usageExpireDateTime) {
+ this.usageExpireDateTime = usageExpireDateTime;
+ return this;
+ }
+
+ public Builder setNoAuthRequired(boolean noAuthRequired) {
+ this.noAuthRequired = noAuthRequired;
+ return this;
+ }
+
+ public Builder setUserAuthType(Set<UserAuthType> userAuthType) {
+ this.userAuthType = userAuthType;
+ return this;
+ }
+
+ public Builder setAuthTimeout(Duration authTimeout) {
+ this.authTimeout = authTimeout;
+ return this;
+ }
+
+ public Builder setAllowWhileOnBody(boolean allowWhileOnBody) {
+ this.allowWhileOnBody = allowWhileOnBody;
+ return this;
+ }
+
+ public Builder setTrustedUserPresenceRequired(boolean trustedUserPresenceRequired) {
+ this.trustedUserPresenceRequired = trustedUserPresenceRequired;
+ return this;
+ }
+
+ public Builder setTrustedConfirmationRequired(boolean trustedConfirmationRequired) {
+ this.trustedConfirmationRequired = trustedConfirmationRequired;
+ return this;
+ }
+
+ public Builder setUnlockedDeviceRequired(boolean unlockedDeviceRequired) {
+ this.unlockedDeviceRequired = unlockedDeviceRequired;
+ return this;
+ }
+
+ public Builder setAllApplications(boolean allApplications) {
+ this.allApplications = allApplications;
+ return this;
+ }
+
+ public Builder setApplicationId(byte[] applicationId) {
+ this.applicationId = applicationId;
+ return this;
+ }
+
+ public Builder setCreationDateTime(Instant creationDateTime) {
+ this.creationDateTime = creationDateTime;
+ return this;
+ }
+
+ public Builder setOrigin(Integer origin) {
+ this.origin = origin;
+ return this;
+ }
+
+ public Builder setRollbackResistant(boolean rollbackResistant) {
+ this.rollbackResistant = rollbackResistant;
+ return this;
+ }
+
+ public Builder setRootOfTrust(RootOfTrust rootOfTrust) {
+ this.rootOfTrust = rootOfTrust;
+ return this;
+ }
+
+ public Builder setOsVersion(Integer osVersion) {
+ this.osVersion = osVersion;
+ return this;
+ }
+
+ public Builder setOsPatchLevel(Integer osPatchLevel) {
+ this.osPatchLevel = osPatchLevel;
+ return this;
+ }
+
+ public Builder setAttestationApplicationId(AttestationApplicationId attestationApplicationId) {
+ this.attestationApplicationId = attestationApplicationId;
+ return this;
+ }
+
+ public Builder setAttestationApplicationIdBytes(byte[] attestationApplicationIdBytes) {
+ this.attestationApplicationIdBytes = attestationApplicationIdBytes;
+ return this;
+ }
+
+ public Builder setAttestationIdBrand(byte[] attestationIdBrand) {
+ this.attestationIdBrand = attestationIdBrand;
+ return this;
+ }
+
+ public Builder setAttestationIdProduct(byte[] attestationIdProduct) {
+ this.attestationIdProduct = attestationIdProduct;
+ return this;
+ }
+
+ public Builder setAttestationIdSerial(byte[] attestationIdSerial) {
+ this.attestationIdSerial = attestationIdSerial;
+ return this;
+ }
+
+ public Builder setAttestationIdImei(byte[] attestationIdImei) {
+ this.attestationIdImei = attestationIdImei;
+ return this;
+ }
+
+ public Builder setAttestationIdMeid(byte[] attestationIdMeid) {
+ this.attestationIdMeid = attestationIdMeid;
+ return this;
+ }
+
+ public Builder setAttestationIdManufacturer(byte[] attestationIdManufacturer) {
+ this.attestationIdManufacturer = attestationIdManufacturer;
+ return this;
+ }
+
+ public Builder setAttestationIdModel(byte[] attestationIdModel) {
+ this.attestationIdModel = attestationIdModel;
+ return this;
+ }
+
+ public Builder setVendorPatchLevel(Integer vendorPatchLevel) {
+ this.vendorPatchLevel = vendorPatchLevel;
+ return this;
+ }
+
+ public Builder setBootPatchLevel(Integer bootPatchLevel) {
+ this.bootPatchLevel = bootPatchLevel;
+ return this;
+ }
+
+ public Builder setIndividualAttestation(boolean individualAttestation) {
+ this.individualAttestation = individualAttestation;
+ return this;
+ }
+
+ public AuthorizationList build() {
+ return new AuthorizationList(this);
+ }
+ }
+}
diff --git a/server/src/main/java/com/google/android/attestation/BUILD b/server/src/main/java/com/google/android/attestation/BUILD
new file mode 100644
index 0000000..d2c2ae2
--- /dev/null
+++ b/server/src/main/java/com/google/android/attestation/BUILD
@@ -0,0 +1,19 @@
+package(default_visibility = ["//visibility:public"])
+
+java_library(
+ name = "attestation",
+ srcs = [
+ "ASN1Parsing.java",
+ "AttestationApplicationId.java",
+ "AuthorizationList.java",
+ "CertificateRevocationStatus.java",
+ "Constants.java",
+ "ParsedAttestationRecord.java",
+ "RootOfTrust.java",
+ ],
+ deps = [
+ "@maven//:com_google_code_gson_gson",
+ "@maven//:org_bouncycastle_bcpkix_jdk15on",
+ "@maven//:org_bouncycastle_bcprov_jdk15on",
+ ],
+)
diff --git a/server/src/main/java/com/google/android/attestation/CertificateRevocationStatus.java b/server/src/main/java/com/google/android/attestation/CertificateRevocationStatus.java
new file mode 100644
index 0000000..d31b2d8
--- /dev/null
+++ b/server/src/main/java/com/google/android/attestation/CertificateRevocationStatus.java
@@ -0,0 +1,135 @@
+/* Copyright 2019, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+package com.google.android.attestation;
+
+import com.google.gson.Gson;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.io.ByteArrayInputStream;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.math.BigInteger;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.nio.file.Files;
+import java.util.HashMap;
+
+
+/**
+ * Utils for fetching and decoding attestation certificate status.
+ */
+public class CertificateRevocationStatus {
+
+ private static final String STATUS_URL = "https://android.googleapis.com/attestation/status";
+ public final Status status;
+ public final Reason reason;
+ public final String comment;
+ public final String expires;
+
+ public static HashMap<String, CertificateRevocationStatus> fetchAllEntries() throws IOException {
+ URL url = new URL(STATUS_URL);
+ InputStreamReader statusListReader = new InputStreamReader(url.openStream());
+ return getEntryToStatusMap(statusListReader);
+ }
+
+ public static HashMap<String, CertificateRevocationStatus> loadAllEntriesFromFile(String filePath)
+ throws IOException {
+ FileReader reader = new FileReader(filePath);
+ return getEntryToStatusMap(reader);
+ }
+
+ private static HashMap<String, CertificateRevocationStatus> getEntryToStatusMap(
+ Reader statusListReader) {
+ JsonObject entries =
+ new JsonParser().parse(statusListReader).getAsJsonObject().getAsJsonObject("entries");
+
+ HashMap<String, CertificateRevocationStatus> serialNumberToStatus = new HashMap<>();
+ for (String serialNumber : entries.keySet()) {
+ serialNumberToStatus.put(
+ serialNumber,
+ new Gson().fromJson(entries.get(serialNumber), CertificateRevocationStatus.class));
+ }
+
+ return serialNumberToStatus;
+ }
+
+ public static CertificateRevocationStatus loadStatusFromFile(BigInteger serialNumber,
+ String filePath)
+ throws IOException {
+ return loadStatusFromFile(serialNumber.toString(16), filePath);
+ }
+
+ public static CertificateRevocationStatus loadStatusFromFile(String serialNumber, String filePath)
+ throws IOException {
+ FileReader reader = new FileReader(filePath);
+ return decodeStatus(serialNumber, reader);
+ }
+
+
+ public static CertificateRevocationStatus fetchStatus(BigInteger serialNumber)
+ throws IOException {
+ return fetchStatus(serialNumber.toString(16));
+ }
+
+ public static CertificateRevocationStatus fetchStatus(String serialNumber) throws IOException {
+ URL url;
+ try {
+ url = new URL(STATUS_URL);
+ } catch (MalformedURLException e) {
+ throw new IllegalStateException(e);
+ }
+
+ InputStreamReader statusListReader = new InputStreamReader(url.openStream());
+
+ return decodeStatus(serialNumber, statusListReader);
+
+ }
+
+ private static CertificateRevocationStatus decodeStatus(String serialNumber,
+ Reader statusListReader) {
+ if (serialNumber == null) {
+ throw new IllegalArgumentException("serialNumber cannot be null");
+ }
+ serialNumber = serialNumber.toLowerCase();
+
+ JsonObject entries = new JsonParser().parse(statusListReader)
+ .getAsJsonObject()
+ .getAsJsonObject("entries");
+
+ if (!entries.has(serialNumber)) {
+ return null;
+ }
+
+ return new Gson().fromJson(entries.get(serialNumber), CertificateRevocationStatus.class);
+ }
+
+ public enum Status {
+ REVOKED, SUSPENDED
+ }
+
+ public enum Reason {
+ UNSPECIFIED, KEY_COMPROMISE, CA_COMPROMISE, SUPERSEDED, SOFTWARE_FLAW
+ }
+
+ public CertificateRevocationStatus() {
+ status = Status.REVOKED;
+ reason = Reason.UNSPECIFIED;
+ comment = null;
+ expires = null;
+ }
+}
diff --git a/server/src/main/java/com/google/android/attestation/Constants.java b/server/src/main/java/com/google/android/attestation/Constants.java
new file mode 100644
index 0000000..7db2744
--- /dev/null
+++ b/server/src/main/java/com/google/android/attestation/Constants.java
@@ -0,0 +1,125 @@
+/* Copyright 2019, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+package com.google.android.attestation;
+
+/** Key Attestation constants */
+public class Constants {
+
+ // The Google root certificate that must have been used to sign the root
+ // certificate in a real attestation certificate chain from a compliant
+ // device.
+ // (Note, the sample chain used here is not signed with this certificate.)
+ public static final String GOOGLE_ROOT_CERTIFICATE =
+ "-----BEGIN CERTIFICATE-----\n"
+ + "MIIFYDCCA0igAwIBAgIJAOj6GWMU0voYMA0GCSqGSIb3DQEBCwUAMBsxGTAXBgNV"
+ + "BAUTEGY5MjAwOWU4NTNiNmIwNDUwHhcNMTYwNTI2MTYyODUyWhcNMjYwNTI0MTYy"
+ + "ODUyWjAbMRkwFwYDVQQFExBmOTIwMDllODUzYjZiMDQ1MIICIjANBgkqhkiG9w0B"
+ + "AQEFAAOCAg8AMIICCgKCAgEAr7bHgiuxpwHsK7Qui8xUFmOr75gvMsd/dTEDDJdS"
+ + "Sxtf6An7xyqpRR90PL2abxM1dEqlXnf2tqw1Ne4Xwl5jlRfdnJLmN0pTy/4lj4/7"
+ + "tv0Sk3iiKkypnEUtR6WfMgH0QZfKHM1+di+y9TFRtv6y//0rb+T+W8a9nsNL/ggj"
+ + "nar86461qO0rOs2cXjp3kOG1FEJ5MVmFmBGtnrKpa73XpXyTqRxB/M0n1n/W9nGq"
+ + "C4FSYa04T6N5RIZGBN2z2MT5IKGbFlbC8UrW0DxW7AYImQQcHtGl/m00QLVWutHQ"
+ + "oVJYnFPlXTcHYvASLu+RhhsbDmxMgJJ0mcDpvsC4PjvB+TxywElgS70vE0XmLD+O"
+ + "JtvsBslHZvPBKCOdT0MS+tgSOIfga+z1Z1g7+DVagf7quvmag8jfPioyKvxnK/Eg"
+ + "sTUVi2ghzq8wm27ud/mIM7AY2qEORR8Go3TVB4HzWQgpZrt3i5MIlCaY504LzSRi"
+ + "igHCzAPlHws+W0rB5N+er5/2pJKnfBSDiCiFAVtCLOZ7gLiMm0jhO2B6tUXHI/+M"
+ + "RPjy02i59lINMRRev56GKtcd9qO/0kUJWdZTdA2XoS82ixPvZtXQpUpuL12ab+9E"
+ + "aDK8Z4RHJYYfCT3Q5vNAXaiWQ+8PTWm2QgBR/bkwSWc+NpUFgNPN9PvQi8WEg5Um"
+ + "AGMCAwEAAaOBpjCBozAdBgNVHQ4EFgQUNmHhAHyIBQlRi0RsR/8aTMnqTxIwHwYD"
+ + "VR0jBBgwFoAUNmHhAHyIBQlRi0RsR/8aTMnqTxIwDwYDVR0TAQH/BAUwAwEB/zAO"
+ + "BgNVHQ8BAf8EBAMCAYYwQAYDVR0fBDkwNzA1oDOgMYYvaHR0cHM6Ly9hbmRyb2lk"
+ + "Lmdvb2dsZWFwaXMuY29tL2F0dGVzdGF0aW9uL2NybC8wDQYJKoZIhvcNAQELBQAD"
+ + "ggIBACDIw41L3KlXG0aMiS//cqrG+EShHUGo8HNsw30W1kJtjn6UBwRM6jnmiwfB"
+ + "Pb8VA91chb2vssAtX2zbTvqBJ9+LBPGCdw/E53Rbf86qhxKaiAHOjpvAy5Y3m00m"
+ + "qC0w/Zwvju1twb4vhLaJ5NkUJYsUS7rmJKHHBnETLi8GFqiEsqTWpG/6ibYCv7rY"
+ + "DBJDcR9W62BW9jfIoBQcxUCUJouMPH25lLNcDc1ssqvC2v7iUgI9LeoM1sNovqPm"
+ + "QUiG9rHli1vXxzCyaMTjwftkJLkf6724DFhuKug2jITV0QkXvaJWF4nUaHOTNA4u"
+ + "JU9WDvZLI1j83A+/xnAJUucIv/zGJ1AMH2boHqF8CY16LpsYgBt6tKxxWH00XcyD"
+ + "CdW2KlBCeqbQPcsFmWyWugxdcekhYsAWyoSf818NUsZdBWBaR/OukXrNLfkQ79Iy"
+ + "ZohZbvabO/X+MVT3rriAoKc8oE2Uws6DF+60PV7/WIPjNvXySdqspImSN78mflxD"
+ + "qwLqRBYkA3I75qppLGG9rp7UCdRjxMl8ZDBld+7yvHVgt1cVzJx9xnyGCC23Uaic"
+ + "MDSXYrB4I4WHXPGjxhZuCuPBLTdOLU8YRvMYdEvYebWHMpvwGCF6bAx3JBpIeOQ1"
+ + "wDB5y0USicV3YgYGmi+NZfhA4URSh77Yd6uuJOJENRaNVTzk\n"
+ + "-----END CERTIFICATE-----";
+ static final String KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17";
+ static final int ATTESTATION_VERSION_INDEX = 0;
+ static final int ATTESTATION_SECURITY_LEVEL_INDEX = 1;
+ static final int KEYMASTER_VERSION_INDEX = 2;
+ static final int KEYMASTER_SECURITY_LEVEL_INDEX = 3;
+ static final int ATTESTATION_CHALLENGE_INDEX = 4;
+ static final int UNIQUE_ID_INDEX = 5;
+ static final int SW_ENFORCED_INDEX = 6;
+ static final int TEE_ENFORCED_INDEX = 7;
+ // Authorization list tags. The list is in this AOSP file:
+ // hardware/libhardware/include/hardware/keymaster_defs.h
+ static final int KM_TAG_PURPOSE = 1;
+ static final int KM_TAG_ALGORITHM = 2;
+ static final int KM_TAG_KEY_SIZE = 3;
+ static final int KM_TAG_DIGEST = 5;
+ static final int KM_TAG_PADDING = 6;
+ static final int KM_TAG_EC_CURVE = 10;
+ static final int KM_TAG_RSA_PUBLIC_EXPONENT = 200;
+ static final int KM_TAG_ROLLBACK_RESISTANCE = 303;
+ static final int KM_TAG_ACTIVE_DATE_TIME = 400;
+ static final int KM_TAG_ORIGINATION_EXPIRE_DATE_TIME = 401;
+ static final int KM_TAG_USAGE_EXPIRE_DATE_TIME = 402;
+ static final int KM_TAG_NO_AUTH_REQUIRED = 503;
+ static final int KM_TAG_USER_AUTH_TYPE = 504;
+ static final int KM_TAG_AUTH_TIMEOUT = 505;
+ static final int KM_TAG_ALLOW_WHILE_ON_BODY = 506;
+ static final int KM_TAG_TRUSTED_USER_PRESENCE_REQUIRED = 507;
+ static final int KM_TAG_TRUSTED_CONFIRMATION_REQUIRED = 508;
+ static final int KM_TAG_UNLOCKED_DEVICE_REQUIRED = 509;
+ static final int KM_TAG_ALL_APPLICATIONS = 600;
+ static final int KM_TAG_APPLICATION_ID = 601;
+ static final int KM_TAG_CREATION_DATE_TIME = 701;
+ static final int KM_TAG_ORIGIN = 702;
+ static final int KM_TAG_ROLLBACK_RESISTANT = 703;
+ static final int KM_TAG_ROOT_OF_TRUST = 704;
+ static final int KM_TAG_OS_VERSION = 705;
+ static final int KM_TAG_OS_PATCH_LEVEL = 706;
+ static final int KM_TAG_ATTESTATION_APPLICATION_ID = 709;
+ static final int KM_TAG_ATTESTATION_ID_BRAND = 710;
+ static final int KM_TAG_ATTESTATION_ID_DEVICE = 711;
+ static final int KM_TAG_ATTESTATION_ID_PRODUCT = 712;
+ static final int KM_TAG_ATTESTATION_ID_SERIAL = 713;
+ static final int KM_TAG_ATTESTATION_ID_IMEI = 714;
+ static final int KM_TAG_ATTESTATION_ID_MEID = 715;
+ static final int KM_TAG_ATTESTATION_ID_MANUFACTURER = 716;
+ static final int KM_TAG_ATTESTATION_ID_MODEL = 717;
+ static final int KM_TAG_VENDOR_PATCH_LEVEL = 718;
+ static final int KM_TAG_BOOT_PATCH_LEVEL = 719;
+ static final int KM_TAG_DEVICE_UNIQUE_ATTESTATION = 720;
+ static final int ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX = 0;
+ static final int ROOT_OF_TRUST_DEVICE_LOCKED_INDEX = 1;
+ static final int ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX = 2;
+ static final int ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX = 3;
+ static final int ATTESTATION_APPLICATION_ID_PACKAGE_INFOS_INDEX = 0;
+ static final int ATTESTATION_APPLICATION_ID_SIGNATURE_DIGESTS_INDEX = 1;
+ static final int ATTESTATION_PACKAGE_INFO_PACKAGE_NAME_INDEX = 0;
+ static final int ATTESTATION_PACKAGE_INFO_VERSION_INDEX = 1;
+ // Some security values. The complete list is in this AOSP file:
+ // hardware/libhardware/include/hardware/keymaster_defs.h
+ static final int KM_SECURITY_LEVEL_SOFTWARE = 0;
+ static final int KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT = 1;
+ static final int KM_SECURITY_LEVEL_STRONG_BOX = 2;
+ static final int KM_VERIFIED_BOOT_STATE_VERIFIED = 0;
+ static final int KM_VERIFIED_BOOT_STATE_SELF_SIGNED = 1;
+ static final int KM_VERIFIED_BOOT_STATE_UNVERIFIED = 2;
+ static final int KM_VERIFIED_BOOT_STATE_FAILED = 3;
+ // Unsigned max value of 32-bit integer, 2^32 - 1
+ static final long UINT32_MAX = (((long) Integer.MAX_VALUE) << 1) + 1;
+}
diff --git a/server/src/main/java/com/google/android/attestation/ParsedAttestationRecord.java b/server/src/main/java/com/google/android/attestation/ParsedAttestationRecord.java
new file mode 100644
index 0000000..053baae
--- /dev/null
+++ b/server/src/main/java/com/google/android/attestation/ParsedAttestationRecord.java
@@ -0,0 +1,203 @@
+/* Copyright 2019, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+package com.google.android.attestation;
+
+import static com.google.android.attestation.Constants.ATTESTATION_CHALLENGE_INDEX;
+import static com.google.android.attestation.Constants.ATTESTATION_SECURITY_LEVEL_INDEX;
+import static com.google.android.attestation.Constants.ATTESTATION_VERSION_INDEX;
+import static com.google.android.attestation.Constants.KEYMASTER_SECURITY_LEVEL_INDEX;
+import static com.google.android.attestation.Constants.KEYMASTER_VERSION_INDEX;
+import static com.google.android.attestation.Constants.KEY_DESCRIPTION_OID;
+import static com.google.android.attestation.Constants.KM_SECURITY_LEVEL_SOFTWARE;
+import static com.google.android.attestation.Constants.KM_SECURITY_LEVEL_STRONG_BOX;
+import static com.google.android.attestation.Constants.KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT;
+import static com.google.android.attestation.Constants.SW_ENFORCED_INDEX;
+import static com.google.android.attestation.Constants.TEE_ENFORCED_INDEX;
+import static com.google.android.attestation.Constants.UNIQUE_ID_INDEX;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+
+/** Java representation of Key Attestation extension data. */
+public class ParsedAttestationRecord {
+
+ public final int attestationVersion;
+ public final SecurityLevel attestationSecurityLevel;
+ public final int keymasterVersion;
+ public final SecurityLevel keymasterSecurityLevel;
+ public final byte[] attestationChallenge;
+ public final byte[] uniqueId;
+ public final AuthorizationList softwareEnforced;
+ public final AuthorizationList teeEnforced;
+
+ private ParsedAttestationRecord(ASN1Sequence extensionData) {
+ this.attestationVersion =
+ ASN1Parsing.getIntegerFromAsn1(extensionData.getObjectAt(ATTESTATION_VERSION_INDEX));
+ this.attestationSecurityLevel =
+ securityLevelToEnum(
+ ASN1Parsing.getIntegerFromAsn1(
+ extensionData.getObjectAt(ATTESTATION_SECURITY_LEVEL_INDEX)));
+ this.keymasterVersion =
+ ASN1Parsing.getIntegerFromAsn1(extensionData.getObjectAt(KEYMASTER_VERSION_INDEX));
+ this.keymasterSecurityLevel =
+ securityLevelToEnum(
+ ASN1Parsing.getIntegerFromAsn1(
+ extensionData.getObjectAt(KEYMASTER_SECURITY_LEVEL_INDEX)));
+ this.attestationChallenge =
+ ((ASN1OctetString) extensionData.getObjectAt(ATTESTATION_CHALLENGE_INDEX)).getOctets();
+ this.uniqueId = ((ASN1OctetString) extensionData.getObjectAt(UNIQUE_ID_INDEX)).getOctets();
+ this.softwareEnforced =
+ AuthorizationList.createAuthorizationList(
+ ((ASN1Sequence) extensionData.getObjectAt(SW_ENFORCED_INDEX)).toArray(),
+ attestationVersion);
+ this.teeEnforced =
+ AuthorizationList.createAuthorizationList(
+ ((ASN1Sequence) extensionData.getObjectAt(TEE_ENFORCED_INDEX)).toArray(),
+ attestationVersion);
+ }
+
+ private ParsedAttestationRecord(
+ int attestationVersion,
+ SecurityLevel attestationSecurityLevel,
+ int keymasterVersion,
+ SecurityLevel keymasterSecurityLevel,
+ byte[] attestationChallenge,
+ byte[] uniqueId,
+ AuthorizationList softwareEnforced,
+ AuthorizationList teeEnforced) {
+ this.attestationVersion = attestationVersion;
+ this.attestationSecurityLevel = attestationSecurityLevel;
+ this.keymasterVersion = keymasterVersion;
+ this.keymasterSecurityLevel = keymasterSecurityLevel;
+ this.attestationChallenge = attestationChallenge;
+ this.uniqueId = uniqueId;
+ this.softwareEnforced = softwareEnforced;
+ this.teeEnforced = teeEnforced;
+ }
+
+ public static ParsedAttestationRecord createParsedAttestationRecord(X509Certificate cert)
+ throws IOException {
+ ASN1Sequence extensionData = extractAttestationSequence(cert);
+ return new ParsedAttestationRecord(extensionData);
+ }
+
+ public static ParsedAttestationRecord create(ASN1Sequence extensionData) {
+ return new ParsedAttestationRecord(extensionData);
+ }
+
+ public static ParsedAttestationRecord create(
+ int attestationVersion,
+ SecurityLevel attestationSecurityLevel,
+ int keymasterVersion,
+ SecurityLevel keymasterSecurityLevel,
+ byte[] attestationChallenge,
+ byte[] uniqueId,
+ AuthorizationList softwareEnforced,
+ AuthorizationList teeEnforced) {
+ return new ParsedAttestationRecord(
+ attestationVersion,
+ attestationSecurityLevel,
+ keymasterVersion,
+ keymasterSecurityLevel,
+ attestationChallenge,
+ uniqueId,
+ softwareEnforced,
+ teeEnforced);
+ }
+
+ private static SecurityLevel securityLevelToEnum(int securityLevel) {
+ switch (securityLevel) {
+ case KM_SECURITY_LEVEL_SOFTWARE:
+ return SecurityLevel.SOFTWARE;
+ case KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT:
+ return SecurityLevel.TRUSTED_ENVIRONMENT;
+ case KM_SECURITY_LEVEL_STRONG_BOX:
+ return SecurityLevel.STRONG_BOX;
+ default:
+ throw new IllegalArgumentException("Invalid security level.");
+ }
+ }
+
+ private static int securityLevelToInt(SecurityLevel securityLevel) {
+ switch (securityLevel) {
+ case SOFTWARE:
+ return KM_SECURITY_LEVEL_SOFTWARE;
+ case TRUSTED_ENVIRONMENT:
+ return KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT;
+ case STRONG_BOX:
+ return KM_SECURITY_LEVEL_STRONG_BOX;
+ }
+ throw new IllegalArgumentException("Invalid security level.");
+ }
+
+ private static ASN1Sequence extractAttestationSequence(X509Certificate attestationCert)
+ throws IOException {
+ byte[] attestationExtensionBytes = attestationCert.getExtensionValue(KEY_DESCRIPTION_OID);
+ if (attestationExtensionBytes == null || attestationExtensionBytes.length == 0) {
+ throw new IllegalArgumentException("Couldn't find the keystore attestation extension data.");
+ }
+
+ ASN1Sequence decodedSequence;
+ try (ASN1InputStream asn1InputStream = new ASN1InputStream(attestationExtensionBytes)) {
+ // The extension contains one object, a sequence, in the
+ // Distinguished Encoding Rules (DER)-encoded form. Get the DER
+ // bytes.
+ byte[] derSequenceBytes = ((ASN1OctetString) asn1InputStream.readObject()).getOctets();
+ // Decode the bytes as an ASN1 sequence object.
+ try (ASN1InputStream seqInputStream = new ASN1InputStream(derSequenceBytes)) {
+ decodedSequence = (ASN1Sequence) seqInputStream.readObject();
+ }
+ }
+ return decodedSequence;
+ }
+
+ public ASN1Sequence toAsn1Sequence() {
+ ASN1Encodable[] vector = new ASN1Encodable[8];
+ vector[ATTESTATION_VERSION_INDEX] = new ASN1Integer(this.attestationVersion);
+ vector[ATTESTATION_SECURITY_LEVEL_INDEX] =
+ new ASN1Enumerated(securityLevelToInt(this.attestationSecurityLevel));
+ vector[KEYMASTER_VERSION_INDEX] = new ASN1Integer(this.keymasterVersion);
+ vector[KEYMASTER_SECURITY_LEVEL_INDEX] =
+ new ASN1Enumerated(securityLevelToInt(this.keymasterSecurityLevel));
+ vector[ATTESTATION_CHALLENGE_INDEX] = new DEROctetString(this.attestationChallenge);
+ vector[UNIQUE_ID_INDEX] = new DEROctetString(this.uniqueId);
+ if (this.softwareEnforced != null) {
+ vector[SW_ENFORCED_INDEX] = this.softwareEnforced.toAsn1Sequence();
+ }
+ if (this.teeEnforced != null) {
+ vector[TEE_ENFORCED_INDEX] = this.teeEnforced.toAsn1Sequence();
+ }
+ return new DERSequence(vector);
+ }
+
+ /**
+ * This indicates the extent to which a software feature, such as a key pair, is protected based
+ * on its location within the device.
+ */
+ public enum SecurityLevel {
+ SOFTWARE,
+ TRUSTED_ENVIRONMENT,
+ STRONG_BOX
+ }
+}
diff --git a/server/src/main/java/com/google/android/attestation/RootOfTrust.java b/server/src/main/java/com/google/android/attestation/RootOfTrust.java
new file mode 100644
index 0000000..7345cb8
--- /dev/null
+++ b/server/src/main/java/com/google/android/attestation/RootOfTrust.java
@@ -0,0 +1,126 @@
+/* Copyright 2019, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+package com.google.android.attestation;
+
+import static com.google.android.attestation.Constants.KM_VERIFIED_BOOT_STATE_FAILED;
+import static com.google.android.attestation.Constants.KM_VERIFIED_BOOT_STATE_SELF_SIGNED;
+import static com.google.android.attestation.Constants.KM_VERIFIED_BOOT_STATE_UNVERIFIED;
+import static com.google.android.attestation.Constants.KM_VERIFIED_BOOT_STATE_VERIFIED;
+import static com.google.android.attestation.Constants.ROOT_OF_TRUST_DEVICE_LOCKED_INDEX;
+import static com.google.android.attestation.Constants.ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX;
+import static com.google.android.attestation.Constants.ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX;
+import static com.google.android.attestation.Constants.ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX;
+
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.asn1.DERSequence;
+
+/** This collection of values defines key information about the device's status. */
+public class RootOfTrust {
+
+ public final byte[] verifiedBootKey;
+ public final boolean deviceLocked;
+ public final VerifiedBootState verifiedBootState;
+ public final byte[] verifiedBootHash;
+
+ private RootOfTrust(ASN1Sequence rootOfTrust, int attestationVersion) {
+ this.verifiedBootKey =
+ ((ASN1OctetString) rootOfTrust.getObjectAt(ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX))
+ .getOctets();
+ this.deviceLocked =
+ ASN1Parsing.getBooleanFromAsn1(rootOfTrust.getObjectAt(ROOT_OF_TRUST_DEVICE_LOCKED_INDEX));
+ this.verifiedBootState =
+ verifiedBootStateToEnum(
+ ASN1Parsing.getIntegerFromAsn1(
+ rootOfTrust.getObjectAt(ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX)));
+ if (attestationVersion >= 3) {
+ this.verifiedBootHash =
+ ((ASN1OctetString) rootOfTrust.getObjectAt(ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX))
+ .getOctets();
+ } else {
+ this.verifiedBootHash = null;
+ }
+ }
+
+ static RootOfTrust createRootOfTrust(ASN1Sequence rootOfTrust, int attestationVersion) {
+ if (rootOfTrust == null) {
+ return null;
+ }
+ return new RootOfTrust(rootOfTrust, attestationVersion);
+ }
+
+ private static VerifiedBootState verifiedBootStateToEnum(int securityLevel) {
+ switch (securityLevel) {
+ case KM_VERIFIED_BOOT_STATE_VERIFIED:
+ return VerifiedBootState.VERIFIED;
+ case KM_VERIFIED_BOOT_STATE_SELF_SIGNED:
+ return VerifiedBootState.SELF_SIGNED;
+ case KM_VERIFIED_BOOT_STATE_UNVERIFIED:
+ return VerifiedBootState.UNVERIFIED;
+ case KM_VERIFIED_BOOT_STATE_FAILED:
+ return VerifiedBootState.FAILED;
+ default:
+ throw new IllegalArgumentException("Invalid verified boot state.");
+ }
+ }
+
+ private static int verifiedBootStateToInt(VerifiedBootState verifiedBootState) {
+ switch (verifiedBootState) {
+ case VERIFIED:
+ return KM_VERIFIED_BOOT_STATE_VERIFIED;
+ case SELF_SIGNED:
+ return KM_VERIFIED_BOOT_STATE_SELF_SIGNED;
+ case UNVERIFIED:
+ return KM_VERIFIED_BOOT_STATE_UNVERIFIED;
+ case FAILED:
+ return KM_VERIFIED_BOOT_STATE_FAILED;
+ }
+ throw new IllegalArgumentException("Invalid verified boot state.");
+ }
+
+ /**
+ * This provides the device's current boot state, which represents the level of protection
+ * provided to the user and to apps after the device finishes booting.
+ */
+ public enum VerifiedBootState {
+ VERIFIED,
+ SELF_SIGNED,
+ UNVERIFIED,
+ FAILED
+ }
+
+ public ASN1Sequence toAsn1Sequence() {
+ ASN1Encodable[] rootOfTrustElements;
+ if (this.verifiedBootHash != null) {
+ rootOfTrustElements = new ASN1Encodable[4];
+ rootOfTrustElements[ROOT_OF_TRUST_VERIFIED_BOOT_HASH_INDEX] =
+ new DEROctetString(this.verifiedBootHash);
+ } else {
+ rootOfTrustElements = new ASN1Encodable[3];
+ }
+ rootOfTrustElements[ROOT_OF_TRUST_VERIFIED_BOOT_KEY_INDEX] =
+ new DEROctetString(this.verifiedBootKey);
+ rootOfTrustElements[ROOT_OF_TRUST_DEVICE_LOCKED_INDEX] =
+ ASN1Boolean.getInstance(this.deviceLocked);
+ rootOfTrustElements[ROOT_OF_TRUST_VERIFIED_BOOT_STATE_INDEX] =
+ new ASN1Enumerated(verifiedBootStateToInt(this.verifiedBootState));
+ return new DERSequence(rootOfTrustElements);
+ }
+}
diff --git a/server/src/test/java/com/google/android/attestation/AttestationApplicationIdTest.java b/server/src/test/java/com/google/android/attestation/AttestationApplicationIdTest.java
new file mode 100644
index 0000000..5d18f06
--- /dev/null
+++ b/server/src/test/java/com/google/android/attestation/AttestationApplicationIdTest.java
@@ -0,0 +1,106 @@
+/* Copyright 2019, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+package com.google.android.attestation;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.android.attestation.AttestationApplicationId.AttestationPackageInfo;
+import com.google.common.collect.ImmutableList;
+import org.bouncycastle.asn1.DEROctetString;
+import org.bouncycastle.util.encoders.Base64;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test for {@link AttestationApplicationId}. */
+@RunWith(JUnit4.class)
+public class AttestationApplicationIdTest {
+
+ // Generated from certificate with RSA Algorithm and StrongBox Security Level
+ private static final DEROctetString ATTESTATION_APPLICATION_ID =
+ new DEROctetString(
+ Base64.decode(
+ "MIIBszGCAYswDAQHYW5kcm9pZAIBHTAZBBRjb20uYW5kcm9pZC5rZXljaGFpbgIBHTAZBBRjb20uYW5kcm9p"
+ + "ZC5zZXR0aW5ncwIBHTAZBBRjb20ucXRpLmRpYWdzZXJ2aWNlcwIBHTAaBBVjb20uYW5kcm9pZC5keW"
+ + "5zeXN0ZW0CAR0wHQQYY29tLmFuZHJvaWQuaW5wdXRkZXZpY2VzAgEdMB8EGmNvbS5hbmRyb2lkLmxv"
+ + "Y2FsdHJhbnNwb3J0AgEdMB8EGmNvbS5hbmRyb2lkLmxvY2F0aW9uLmZ1c2VkAgEdMB8EGmNvbS5hbm"
+ + "Ryb2lkLnNlcnZlci50ZWxlY29tAgEdMCAEG2NvbS5hbmRyb2lkLndhbGxwYXBlcmJhY2t1cAIBHTAh"
+ + "BBxjb20uZ29vZ2xlLlNTUmVzdGFydERldGVjdG9yAgEdMCIEHWNvbS5nb29nbGUuYW5kcm9pZC5oaW"
+ + "RkZW5tZW51AgEBMCMEHmNvbS5hbmRyb2lkLnByb3ZpZGVycy5zZXR0aW5ncwIBHTEiBCAwGqPLCBE0"
+ + "UBxF8UIqvGbCQiT9Xe1f3I8X5pcXb9hmqg=="));
+
+ private static final ImmutableList<AttestationPackageInfo> EXPECTED_PACKAGE_INFOS =
+ ImmutableList.of(
+ new AttestationPackageInfo("android", 29L),
+ new AttestationPackageInfo("com.android.keychain", 29L),
+ new AttestationPackageInfo("com.android.settings", 29L),
+ new AttestationPackageInfo("com.qti.diagservices", 29L),
+ new AttestationPackageInfo("com.android.dynsystem", 29L),
+ new AttestationPackageInfo("com.android.inputdevices", 29L),
+ new AttestationPackageInfo("com.android.localtransport", 29L),
+ new AttestationPackageInfo("com.android.location.fused", 29L),
+ new AttestationPackageInfo("com.android.server.telecom", 29L),
+ new AttestationPackageInfo("com.android.wallpaperbackup", 29L),
+ new AttestationPackageInfo("com.google.SSRestartDetector", 29L),
+ new AttestationPackageInfo("com.google.android.hiddenmenu", 1L),
+ new AttestationPackageInfo("com.android.providers.settings", 29L));
+ private static final ImmutableList<byte[]> EXPECTED_SIGNATURE_DIGESTS =
+ ImmutableList.of(Base64.decode("MBqjywgRNFAcRfFCKrxmwkIk/V3tX9yPF+aXF2/YZqo=\n"));
+
+ private static final AttestationApplicationId EXPECTED_ATTESTATION_APPLICATION_ID =
+ new AttestationApplicationId(EXPECTED_PACKAGE_INFOS, EXPECTED_SIGNATURE_DIGESTS);
+
+ @Test
+ public void testCreateAttestationApplicationId() {
+ AttestationApplicationId attestationApplicationId =
+ AttestationApplicationId.createAttestationApplicationId(ATTESTATION_APPLICATION_ID);
+ assertThat(attestationApplicationId).isEqualTo(EXPECTED_ATTESTATION_APPLICATION_ID);
+ }
+
+ @Test
+ public void testCreateEmptyAttestationApplicationIdFromEmptyOrInvalidInput() {
+ assertThat(AttestationApplicationId.createAttestationApplicationId(null)).isNull();
+ assertThat(
+ AttestationApplicationId.createAttestationApplicationId(
+ new DEROctetString("Invalid DEROctet String".getBytes(UTF_8))))
+ .isNull();
+ }
+
+ @Test
+ public void testEquals() {
+ AttestationApplicationId attestationApplicationId =
+ AttestationApplicationId.createAttestationApplicationId(ATTESTATION_APPLICATION_ID);
+ AttestationApplicationId emptyAttestationApplicationId =
+ new AttestationApplicationId(ImmutableList.of(), ImmutableList.of());
+
+ assertThat(attestationApplicationId.equals(EXPECTED_ATTESTATION_APPLICATION_ID)).isTrue();
+ assertThat(EXPECTED_ATTESTATION_APPLICATION_ID.equals(attestationApplicationId)).isTrue();
+
+ assertThat(attestationApplicationId.equals(emptyAttestationApplicationId)).isFalse();
+ assertThat(emptyAttestationApplicationId.equals(attestationApplicationId)).isFalse();
+ }
+
+ @Test
+ public void testEqualObjectsHaveEqualHashCodes() {
+ AttestationApplicationId attestationApplicationId =
+ AttestationApplicationId.createAttestationApplicationId(ATTESTATION_APPLICATION_ID);
+
+ assertThat(attestationApplicationId.equals(EXPECTED_ATTESTATION_APPLICATION_ID)).isTrue();
+ assertThat(attestationApplicationId.hashCode())
+ .isEqualTo(EXPECTED_ATTESTATION_APPLICATION_ID.hashCode());
+ }
+}
diff --git a/server/src/test/java/com/google/android/attestation/AuthorizationListTest.java b/server/src/test/java/com/google/android/attestation/AuthorizationListTest.java
new file mode 100644
index 0000000..637e8ca
--- /dev/null
+++ b/server/src/test/java/com/google/android/attestation/AuthorizationListTest.java
@@ -0,0 +1,183 @@
+/* Copyright 2019, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+package com.google.android.attestation;
+
+import static com.google.android.attestation.AuthorizationList.UserAuthType.FINGERPRINT;
+import static com.google.android.attestation.AuthorizationList.UserAuthType.PASSWORD;
+import static com.google.android.attestation.AuthorizationList.UserAuthType.USER_AUTH_TYPE_ANY;
+import static com.google.android.attestation.AuthorizationList.UserAuthType.USER_AUTH_TYPE_NONE;
+import static com.google.android.attestation.AuthorizationList.userAuthTypeToEnum;
+import static com.google.android.attestation.Constants.UINT32_MAX;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth8.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableSet;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.time.Instant;
+import java.util.Set;
+
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.util.encoders.Base64;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test for {@link AuthorizationList}. */
+@RunWith(JUnit4.class)
+public class AuthorizationListTest {
+
+ // Generated from certificate with RSA Algorithm and StrongBox Security Level
+ private static final String SW_ENFORCED_EXTENSION_DATA =
+ "MIIBzb+FPQgCBgFr9iKgzL+FRYIBuwSCAbcwggGzMYIBizAMBAdhbmRyb2lkAgEdMBkEFGNvbS5hbmRyb2lkLmtleWNo"
+ + "YWluAgEdMBkEFGNvbS5hbmRyb2lkLnNldHRpbmdzAgEdMBkEFGNvbS5xdGkuZGlhZ3NlcnZpY2VzAgEdMBoEFW"
+ + "NvbS5hbmRyb2lkLmR5bnN5c3RlbQIBHTAdBBhjb20uYW5kcm9pZC5pbnB1dGRldmljZXMCAR0wHwQaY29tLmFu"
+ + "ZHJvaWQubG9jYWx0cmFuc3BvcnQCAR0wHwQaY29tLmFuZHJvaWQubG9jYXRpb24uZnVzZWQCAR0wHwQaY29tLm"
+ + "FuZHJvaWQuc2VydmVyLnRlbGVjb20CAR0wIAQbY29tLmFuZHJvaWQud2FsbHBhcGVyYmFja3VwAgEdMCEEHGNv"
+ + "bS5nb29nbGUuU1NSZXN0YXJ0RGV0ZWN0b3ICAR0wIgQdY29tLmdvb2dsZS5hbmRyb2lkLmhpZGRlbm1lbnUCAQ"
+ + "EwIwQeY29tLmFuZHJvaWQucHJvdmlkZXJzLnNldHRpbmdzAgEdMSIEIDAao8sIETRQHEXxQiq8ZsJCJP1d7V/c"
+ + "jxfmlxdv2Gaq";
+ private static final String TEE_ENFORCED_EXTENSION_DATA =
+ "MIGwoQgxBgIBAgIBA6IDAgEBowQCAggApQUxAwIBBKYIMQYCAQMCAQW/gUgFAgMBAAG/g3cCBQC/hT4DAgEAv4VATDBK"
+ + "BCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAoBAgQgco2xJ08fHPFXHeQ4CwSKVUrEo4Dnb1"
+ + "NVCDUpCEqTeAG/hUEDAgEAv4VCBQIDAxSzv4VOBgIEATQV8b+FTwYCBAE0Few=";
+ private static final int ATTESTATION_VERSION = 3;
+
+ // Some enum values, complete list can be found at:
+ // https://source.android.com/security/keystore/tags
+ private static final int PURPOSE_SIGN = 2;
+ private static final int PURPOSE_VERIFY = 3;
+ private static final int ALGORITHM_RSA = 1;
+ private static final int DIGEST_SHA_2_256 = 4;
+ private static final int PADDING_RSA_PSS = 3;
+ private static final int PADDING_RSA_1_5_SIGN = 5;
+ private static final int ORIGIN_GENERATED = 0;
+
+ // 2019-07-15T14:56:32.972Z
+ private static final Instant EXPECTED_SW_CREATION_DATETIME = Instant.ofEpochMilli(1563202592972L);
+ private static final byte[] EXPECTED_SW_ATTESTATION_APPLICATION_ID_BYTES =
+ Base64.decode(
+ "MIIBszGCAYswDAQHYW5kcm9pZAIBHTAZBBRjb20uYW5kcm9pZC5rZXljaGFpbgIBHTAZBBRjb20uYW5kcm9pZC5z"
+ + "ZXR0aW5ncwIBHTAZBBRjb20ucXRpLmRpYWdzZXJ2aWNlcwIBHTAaBBVjb20uYW5kcm9pZC5keW5zeXN0ZW"
+ + "0CAR0wHQQYY29tLmFuZHJvaWQuaW5wdXRkZXZpY2VzAgEdMB8EGmNvbS5hbmRyb2lkLmxvY2FsdHJhbnNw"
+ + "b3J0AgEdMB8EGmNvbS5hbmRyb2lkLmxvY2F0aW9uLmZ1c2VkAgEdMB8EGmNvbS5hbmRyb2lkLnNlcnZlci"
+ + "50ZWxlY29tAgEdMCAEG2NvbS5hbmRyb2lkLndhbGxwYXBlcmJhY2t1cAIBHTAhBBxjb20uZ29vZ2xlLlNT"
+ + "UmVzdGFydERldGVjdG9yAgEdMCIEHWNvbS5nb29nbGUuYW5kcm9pZC5oaWRkZW5tZW51AgEBMCMEHmNvbS"
+ + "5hbmRyb2lkLnByb3ZpZGVycy5zZXR0aW5ncwIBHTEiBCAwGqPLCBE0UBxF8UIqvGbCQiT9Xe1f3I8X5pcX"
+ + "b9hmqg==");
+ private static final ImmutableSet<Integer> EXPECTED_TEE_PURPOSE =
+ ImmutableSet.of(PURPOSE_SIGN, PURPOSE_VERIFY);
+ private static final Integer EXPECTED_TEE_ALGORITHM = ALGORITHM_RSA;
+ private static final Integer EXPECTED_TEE_KEY_SIZE = 2048;
+ private static final ImmutableSet<Integer> EXPECTED_TEE_DIGEST =
+ ImmutableSet.of(DIGEST_SHA_2_256);
+ private static final ImmutableSet<Integer> EXPECTED_TEE_PADDING =
+ ImmutableSet.of(PADDING_RSA_PSS, PADDING_RSA_1_5_SIGN);
+ private static final Long EXPECTED_TEE_RSA_PUBLIC_COMPONENT = 65537L;
+ private static final Integer EXPECTED_TEE_ORIGIN = ORIGIN_GENERATED;
+ private static final Integer EXPECTED_TEE_OS_VERSION = 0;
+ private static final Integer EXPECTED_TEE_OS_PATCH_LEVEL = 201907;
+ private static final Integer EXPECTED_TEE_VENDOR_PATCH_LEVEL = 20190705;
+ private static final Integer EXPECTED_TEE_BOOT_PATCH_LEVEL = 20190700;
+
+ private static ASN1Encodable[] getEncodableAuthorizationList(String extensionData)
+ throws IOException {
+ byte[] extensionDataBytes = Base64.decode(extensionData);
+ return ((ASN1Sequence) ASN1Sequence.fromByteArray(extensionDataBytes)).toArray();
+ }
+
+ @Test
+ public void testCanParseAuthorizationListFromSwEnforced() throws IOException {
+ AuthorizationList authorizationList =
+ AuthorizationList.createAuthorizationList(
+ getEncodableAuthorizationList(SW_ENFORCED_EXTENSION_DATA), ATTESTATION_VERSION);
+
+ assertThat(authorizationList.creationDateTime).hasValue(EXPECTED_SW_CREATION_DATETIME);
+ assertThat(authorizationList.rootOfTrust).isEmpty();
+ assertThat(authorizationList.attestationApplicationId).isPresent();
+ assertThat(authorizationList.attestationApplicationIdBytes)
+ .hasValue(EXPECTED_SW_ATTESTATION_APPLICATION_ID_BYTES);
+ assertThat(authorizationList.individualAttestation).isFalse();
+ }
+
+ @Test
+ public void testCanParseAuthorizationListFromTeeEnforced() throws IOException {
+ AuthorizationList authorizationList =
+ AuthorizationList.createAuthorizationList(
+ getEncodableAuthorizationList(TEE_ENFORCED_EXTENSION_DATA), ATTESTATION_VERSION);
+
+ assertThat(authorizationList.purpose).hasValue(EXPECTED_TEE_PURPOSE);
+ assertThat(authorizationList.algorithm).hasValue(EXPECTED_TEE_ALGORITHM);
+ assertThat(authorizationList.keySize).hasValue(EXPECTED_TEE_KEY_SIZE);
+ assertThat(authorizationList.digest).hasValue(EXPECTED_TEE_DIGEST);
+ assertThat(authorizationList.padding).hasValue(EXPECTED_TEE_PADDING);
+ assertThat(authorizationList.rsaPublicExponent).hasValue(EXPECTED_TEE_RSA_PUBLIC_COMPONENT);
+ assertThat(authorizationList.noAuthRequired).isTrue();
+ assertThat(authorizationList.origin).hasValue(EXPECTED_TEE_ORIGIN);
+ assertThat(authorizationList.rootOfTrust).isPresent();
+ assertThat(authorizationList.osVersion).hasValue(EXPECTED_TEE_OS_VERSION);
+ assertThat(authorizationList.osPatchLevel).hasValue(EXPECTED_TEE_OS_PATCH_LEVEL);
+ assertThat(authorizationList.vendorPatchLevel).hasValue(EXPECTED_TEE_VENDOR_PATCH_LEVEL);
+ assertThat(authorizationList.bootPatchLevel).hasValue(EXPECTED_TEE_BOOT_PATCH_LEVEL);
+ assertThat(authorizationList.individualAttestation).isFalse();
+ }
+
+ @Test
+ public void testUserAuthTypeToEnum() {
+ assertThat(userAuthTypeToEnum(0L)).isEqualTo(Set.of(USER_AUTH_TYPE_NONE));
+ assertThat(userAuthTypeToEnum(1L)).isEqualTo(Set.of(PASSWORD));
+ assertThat(userAuthTypeToEnum(2L)).isEqualTo(Set.of(FINGERPRINT));
+ assertThat(userAuthTypeToEnum(3L)).isEqualTo(Set.of(PASSWORD, FINGERPRINT));
+ assertThat(userAuthTypeToEnum(UINT32_MAX)).isEqualTo(Set.of(PASSWORD, FINGERPRINT, USER_AUTH_TYPE_ANY));
+
+
+ try {
+ userAuthTypeToEnum(4L);
+ fail();
+ } catch (IllegalArgumentException expected) {
+ assertThat(expected).hasMessageThat().contains("Invalid User Auth Type.");
+ }
+ }
+
+ private static final String EXTENTION_DATA_WITH_INDIVIDUAL_ATTESTATION =
+ "MIH0oQgxBgIBAgIBA6IDAgEBowQCAggApQUxAwIBBKYIMQYCAQMCAQW/gUgFAgMBAAG/g3cCBQC/hT4DAgEAv4VATDBK"
+ + "BCAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEBAAoBAgQgEvR7Lf1t9nD6P2qyUmgiQ0mG+RixYn"
+ + "glj2TaAMZmHn2/hUEFAgMBrbC/hUIFAgMDFRi/hUYIBAZnb29nbGW/hUcHBAVzYXJnb7+FSAcEBXNhcmdvv4VM"
+ + "CAQGR29vZ2xlv4VNCgQIUGl4ZWwgM2G/hU4GAgQBND1lv4VPBgIEATQ9Zb+FUAIFAA==";
+
+ @Test
+ public void testCanParseIndividualAttestation() throws IOException {
+ AuthorizationList authorizationList =
+ AuthorizationList.createAuthorizationList(
+ getEncodableAuthorizationList(EXTENTION_DATA_WITH_INDIVIDUAL_ATTESTATION),
+ ATTESTATION_VERSION);
+
+ assertThat(authorizationList.individualAttestation).isTrue();
+ }
+
+ @Test
+ public void testCreateAndParse() throws IOException {
+ AuthorizationList authorizationList =
+ AuthorizationList.createAuthorizationList(
+ getEncodableAuthorizationList(EXTENTION_DATA_WITH_INDIVIDUAL_ATTESTATION),
+ ATTESTATION_VERSION);
+ ASN1Sequence seq = authorizationList.toAsn1Sequence();
+ assertThat(seq.getEncoded("DER"))
+ .isEqualTo(Base64.decode(EXTENTION_DATA_WITH_INDIVIDUAL_ATTESTATION));
+ }
+}
diff --git a/server/src/test/java/com/google/android/attestation/BUILD b/server/src/test/java/com/google/android/attestation/BUILD
new file mode 100644
index 0000000..680103e
--- /dev/null
+++ b/server/src/test/java/com/google/android/attestation/BUILD
@@ -0,0 +1,47 @@
+java_test(
+ name = "AttestationApplicationIdTest",
+ srcs = ["AttestationApplicationIdTest.java"],
+ deps = [
+ "//server/src/main/java/com/google/android/attestation",
+ "@maven//:com_google_truth_extensions_truth_java8_extension",
+ "@maven//:com_google_truth_truth",
+ "@maven//:junit_junit",
+ "@maven//:org_bouncycastle_bcprov_jdk15on",
+ ],
+)
+
+java_test(
+ name = "AuthorizationListTest",
+ srcs = ["AuthorizationListTest.java"],
+ deps = [
+ "//server/src/main/java/com/google/android/attestation",
+ "@maven//:com_google_truth_extensions_truth_java8_extension",
+ "@maven//:com_google_truth_truth",
+ "@maven//:junit_junit",
+ "@maven//:org_bouncycastle_bcprov_jdk15on",
+ ],
+)
+
+java_test(
+ name = "ParsedAttestationRecordTest",
+ srcs = ["ParsedAttestationRecordTest.java"],
+ deps = [
+ "//server/src/main/java/com/google/android/attestation",
+ "@maven//:com_google_truth_extensions_truth_java8_extension",
+ "@maven//:com_google_truth_truth",
+ "@maven//:junit_junit",
+ "@maven//:org_bouncycastle_bcprov_jdk15on",
+ ],
+)
+
+java_test(
+ name = "RootOfTrustTest",
+ srcs = ["RootOfTrustTest.java"],
+ deps = [
+ "//server/src/main/java/com/google/android/attestation",
+ "@maven//:com_google_truth_extensions_truth_java8_extension",
+ "@maven//:com_google_truth_truth",
+ "@maven//:junit_junit",
+ "@maven//:org_bouncycastle_bcprov_jdk15on",
+ ],
+)
diff --git a/server/src/test/java/com/google/android/attestation/CertificateRevocationStatusTest.java b/server/src/test/java/com/google/android/attestation/CertificateRevocationStatusTest.java
new file mode 100644
index 0000000..fdeee19
--- /dev/null
+++ b/server/src/test/java/com/google/android/attestation/CertificateRevocationStatusTest.java
@@ -0,0 +1,103 @@
+/* Copyright 2019, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+package com.google.android.attestation;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.android.attestation.CertificateRevocationStatus.Reason;
+import com.google.android.attestation.CertificateRevocationStatus.Status;
+import java.io.ByteArrayInputStream;
+import java.math.BigInteger;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.HashMap;
+import java.util.HashSet;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Test for {@link CertificateRevocationStatus}.
+ */
+@RunWith(JUnit4.class)
+public final class CertificateRevocationStatusTest {
+
+ private static final String TEST_STATUS_LIST_PATH = "src/test/resources/status.json";
+
+ // Certificate generated by TestDPC with RSA Algorithm and StrongBox Security Level
+ private static final String TEST_CERT =
+ "-----BEGIN CERTIFICATE-----\n"
+ + "MIIB8zCCAXqgAwIBAgIRAMxm6ak3E7bmQ7JsFYeXhvcwCgYIKoZIzj0EAwIwOTEM"
+ + "MAoGA1UEDAwDVEVFMSkwJwYDVQQFEyA0ZjdlYzg1N2U4MDU3NDdjMWIxZWRhYWVm"
+ + "ODk1NDk2ZDAeFw0xOTA4MTQxOTU0MTBaFw0yOTA4MTExOTU0MTBaMDkxDDAKBgNV"
+ + "BAwMA1RFRTEpMCcGA1UEBRMgMzJmYmJiNmRiOGM5MTdmMDdhYzlhYjZhZTQ4MTAz"
+ + "YWEwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQzg+sx9lLrkNIZwLYZerzL1bPK"
+ + "2zi75zFEuuI0fIr35DJND1B4Z8RPZ3djzo3FOdAObqvoZ4CZVxcY3iQ1ffMMo2Mw"
+ + "YTAdBgNVHQ4EFgQUzZOUqhJOO7wttSe9hYemjceVsgIwHwYDVR0jBBgwFoAUWlnI"
+ + "9iPzasns60heYXIP+h+Hz8owDwYDVR0TAQH/BAUwAwEB/zAOBgNVHQ8BAf8EBAMC"
+ + "AgQwCgYIKoZIzj0EAwIDZwAwZAIwUFz/AKheCOPaBiRGDk7LaSEDXVYmTr0VoU8T"
+ + "bIqrKGWiiMwsGEmW+Jdo8EcKVPIwAjAoO7n1ruFh+6mEaTAukc6T5BW4MnmYadkk"
+ + "FSIjzDAaJ6lAq+nmmGQ1KlZpqi4Z/VI=\n"
+ + "-----END CERTIFICATE-----";
+
+ @Test
+ public void loadTestSerial() throws Exception {
+ CertificateFactory factory = CertificateFactory.getInstance("X509");
+ X509Certificate cert =
+ (X509Certificate)
+ factory.generateCertificate(new ByteArrayInputStream(TEST_CERT.getBytes(UTF_8)));
+ BigInteger serialNumber = cert.getSerialNumber();
+ CertificateRevocationStatus statusEntry = CertificateRevocationStatus
+ .loadStatusFromFile(serialNumber, TEST_STATUS_LIST_PATH);
+ assertThat(statusEntry).isNotNull();
+ assertThat(statusEntry.status).isEqualTo(Status.SUSPENDED);
+ assertThat(statusEntry.reason).isEqualTo(Reason.KEY_COMPROMISE);
+ }
+
+ @Test
+ public void loadBadSerial() throws Exception {
+ assertThat(CertificateRevocationStatus.loadStatusFromFile("badbeef", TEST_STATUS_LIST_PATH))
+ .isNull();
+ assertThat(CertificateRevocationStatus.loadStatusFromFile(BigInteger.valueOf(0xbadbeef), TEST_STATUS_LIST_PATH))
+ .isNull();
+ }
+
+ @Test
+ public void loadAllTestEntries() throws Exception {
+ HashSet<String> allTestSerialNumbers = new HashSet<>();
+ allTestSerialNumbers.add("6681152659205225093");
+ allTestSerialNumbers.add("8350192447815228107");
+ allTestSerialNumbers.add("9408173275444922801");
+ allTestSerialNumbers.add("11244410301401252959");
+ allTestSerialNumbers.add("cc66e9a93713b6e643b26c15879786f7");
+
+ HashMap<String, CertificateRevocationStatus> statusMap =
+ CertificateRevocationStatus.loadAllEntriesFromFile(TEST_STATUS_LIST_PATH);
+
+ assertThat(statusMap.keySet()).isEqualTo(allTestSerialNumbers);
+ assertThat(statusMap.get("8350192447815228107").status)
+ .isEqualTo(CertificateRevocationStatus.Status.REVOKED);
+ assertThat(statusMap.get("8350192447815228107").reason)
+ .isEqualTo(CertificateRevocationStatus.Reason.KEY_COMPROMISE);
+ assertThat(statusMap.get("cc66e9a93713b6e643b26c15879786f7").status)
+ .isEqualTo(CertificateRevocationStatus.Status.SUSPENDED);
+ assertThat(statusMap.get("cc66e9a93713b6e643b26c15879786f7").reason)
+ .isEqualTo(CertificateRevocationStatus.Reason.KEY_COMPROMISE);
+ assertThat(statusMap.get("cc66e9a93713b6e643b26c15879786f7").comment)
+ .isEqualTo("Entry for testing only");
+ }
+}
diff --git a/server/src/test/java/com/google/android/attestation/ParsedAttestationRecordTest.java b/server/src/test/java/com/google/android/attestation/ParsedAttestationRecordTest.java
new file mode 100644
index 0000000..678ea9a
--- /dev/null
+++ b/server/src/test/java/com/google/android/attestation/ParsedAttestationRecordTest.java
@@ -0,0 +1,136 @@
+/* Copyright 2019, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+package com.google.android.attestation;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.android.attestation.AuthorizationList.UserAuthType;
+import com.google.android.attestation.ParsedAttestationRecord.SecurityLevel;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Set;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test for {@link ParsedAttestationRecord}. */
+@RunWith(JUnit4.class)
+public class ParsedAttestationRecordTest {
+
+ // Certificate generated by TestDPC with RSA Algorithm and StrongBox Security Level
+ private static final String CERT =
+ "-----BEGIN CERTIFICATE-----\n"
+ + "MIIGCDCCBHCgAwIBAgIBATANBgkqhkiG9w0BAQsFADApMRkwFwYDVQQFExAyZGM1OGIyZDFhMjQx"
+ + "MzI2MQwwCgYDVQQMDANURUUwIBcNNzAwMTAxMDAwMDAwWhgPMjEwNjAyMDcwNjI4MTVaMB8xHTAb"
+ + "BgNVBAMMFEFuZHJvaWQgS2V5c3RvcmUgS2V5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKC"
+ + "AQEApNVcnyN40MANMbbo2nMGNq2NNysDSjfLm0W3i6wPKf0ffCYkhWM4dCmQKKf50uAZTBeTit4c"
+ + "NwXeZn3qellMlOsIN3Qc384rfN/8cikrRvUAgibz0Jy7STykjwa7x6tKwqITxbO8HqAhKo8/BQXU"
+ + "xzrOdIg5ciy+UM7Vgh7a7ogen0KL2iGgrsalb1ti7Vlzb6vIJ4WzIC3TGD2sCkoPahghwqFDZZCo"
+ + "/FzaLoNY0jAUX2mL+kf8aUaoxz7xA9FTvgara+1pLBR1s4c8xPS2HdZipcVXWfey0wujv1VAKs4+"
+ + "tXjKlHkYBHBBceEjxUtEmrapSQEdpHPv7Xh9Uanq4QIDAQABo4ICwTCCAr0wDgYDVR0PAQH/BAQD"
+ + "AgeAMIICqQYKKwYBBAHWeQIBEQSCApkwggKVAgEDCgEBAgEECgEBBANhYmMEADCCAc2/hT0IAgYB"
+ + "ZOYGEYe/hUWCAbsEggG3MIIBszGCAYswDAQHYW5kcm9pZAIBHTAZBBRjb20uYW5kcm9pZC5rZXlj"
+ + "aGFpbgIBHTAZBBRjb20uYW5kcm9pZC5zZXR0aW5ncwIBHTAZBBRjb20ucXRpLmRpYWdzZXJ2aWNl"
+ + "cwIBHTAaBBVjb20uYW5kcm9pZC5keW5zeXN0ZW0CAR0wHQQYY29tLmFuZHJvaWQuaW5wdXRkZXZp"
+ + "Y2VzAgEdMB8EGmNvbS5hbmRyb2lkLmxvY2FsdHJhbnNwb3J0AgEdMB8EGmNvbS5hbmRyb2lkLmxv"
+ + "Y2F0aW9uLmZ1c2VkAgEdMB8EGmNvbS5hbmRyb2lkLnNlcnZlci50ZWxlY29tAgEdMCAEG2NvbS5h"
+ + "bmRyb2lkLndhbGxwYXBlcmJhY2t1cAIBHTAhBBxjb20uZ29vZ2xlLlNTUmVzdGFydERldGVjdG9y"
+ + "AgEdMCIEHWNvbS5nb29nbGUuYW5kcm9pZC5oaWRkZW5tZW51AgEBMCMEHmNvbS5hbmRyb2lkLnBy"
+ + "b3ZpZGVycy5zZXR0aW5ncwIBHTEiBCAwGqPLCBE0UBxF8UIqvGbCQiT9Xe1f3I8X5pcXb9hmqjCB"
+ + "rqEIMQYCAQICAQOiAwIBAaMEAgIIAKUFMQMCAQSmCDEGAgEDAgEFv4FIBQIDAQABv4N3AgUAv4U+"
+ + "AwIBAL+FQEwwSgQgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAQAKAQIEIHKNsSdP"
+ + "HxzxVx3kOAsEilVKxKOA529TVQg1KQhKk3gBv4VBAwIBAL+FQgUCAwMUs7+FTgUCAwMUs7+FTwUC"
+ + "AwMUszANBgkqhkiG9w0BAQsFAAOCAYEAJMIuzdNUdfrE6sIdmsnMn/scSG2odbphj8FkX9JGdF2S"
+ + "OT599HuDY9qhvkru2Dza4sLKK3f4ViBhuR9lpfeprKvstxbtBO7jkLYfVn0ZRzHRHVEyiW5IVKh+"
+ + "qOXVJ9S1lMShOTlsaYJytLKIlcrRAZBEXZiNbzTuVh1CH6X9Ni1dog14snm+lcOeORdL9fht2CHa"
+ + "u/caRnpWiZbjoAoJp0O89uBrRkXPpln51+3jPY6AFny30grNAvKguauDcPPhNV1yR+ylSsQi2gm3"
+ + "Rs4pgtlxFLMfZLgT0cbkl+9zk/QUqlpBP8ftUBsOI0ARr8xhFN3cvq9kXGLtJ9hEP9PRaflAFREk"
+ + "DK3IBIbVcAFZBFoAQOdE9zy0+F5bQrznPGaZg4Dzhcx33qMDUTgHtWoy+k3ePGQMEtmoTTLgQywW"
+ + "OIkXEoFqqGi9GKJXUT1KYi5NsigaYqu7FoN4Qsvs61pMUEfZSPP2AFwkA8uNFbmb9uxcxaGHCA8i"
+ + "3i9VM6yOLIrP\n"
+ + "-----END CERTIFICATE-----";
+
+ private static final int EXPECTED_ATTESTATION_VERSION = 3;
+ private static final SecurityLevel EXPECTED_ATTESTATION_SECURITY_LEVEL =
+ SecurityLevel.TRUSTED_ENVIRONMENT;
+ private static final int EXPECTED_KEYMASTER_VERSION = 4;
+ private static final SecurityLevel EXPECTED_KEYMASTER_SECURITY_LEVEL =
+ SecurityLevel.TRUSTED_ENVIRONMENT;
+ private static final byte[] EXPECTED_ATTESTATION_CHALLENGE = "abc".getBytes(UTF_8);
+ private static final byte[] EXPECTED_UNIQUE_ID = "".getBytes(UTF_8);
+
+ private static X509Certificate getAttestationRecord(String certStr) throws CertificateException {
+ CertificateFactory factory = CertificateFactory.getInstance("X509");
+ X509Certificate cert =
+ (X509Certificate)
+ factory.generateCertificate(new ByteArrayInputStream(certStr.getBytes(UTF_8)));
+ cert.checkValidity();
+ return cert;
+ }
+
+ @Test
+ public void testParseAttestationRecord() throws CertificateException, IOException {
+ X509Certificate x509Certificate = getAttestationRecord(CERT);
+ ParsedAttestationRecord attestationRecord =
+ ParsedAttestationRecord.createParsedAttestationRecord(x509Certificate);
+
+ assertThat(attestationRecord.attestationVersion).isEqualTo(EXPECTED_ATTESTATION_VERSION);
+ assertThat(attestationRecord.attestationSecurityLevel)
+ .isEqualTo(EXPECTED_ATTESTATION_SECURITY_LEVEL);
+ assertThat(attestationRecord.keymasterVersion).isEqualTo(EXPECTED_KEYMASTER_VERSION);
+ assertThat(attestationRecord.keymasterSecurityLevel)
+ .isEqualTo(EXPECTED_KEYMASTER_SECURITY_LEVEL);
+ assertThat(attestationRecord.attestationChallenge).isEqualTo(EXPECTED_ATTESTATION_CHALLENGE);
+ assertThat(attestationRecord.uniqueId).isEqualTo(EXPECTED_UNIQUE_ID);
+ assertThat(attestationRecord.softwareEnforced).isNotNull();
+ assertThat(attestationRecord.teeEnforced).isNotNull();
+ }
+
+ @Test
+ public void testCreateAndParseAttestationRecord() {
+ AuthorizationList.Builder teeEnforcedBuilder = AuthorizationList.builder();
+ teeEnforcedBuilder.userAuthType = Set.of(UserAuthType.FINGERPRINT);
+ teeEnforcedBuilder.attestationIdBrand = "free food".getBytes(UTF_8);
+ ParsedAttestationRecord expected =
+ ParsedAttestationRecord.create(
+ /* attestationVersion= */ 2,
+ /* attestationSecurityLevel= */ SecurityLevel.TRUSTED_ENVIRONMENT,
+ /* keymasterVersion= */ 4,
+ /* keymasterSecurityLevel= */ SecurityLevel.SOFTWARE,
+ /* attestationChallenge= */ "abc".getBytes(UTF_8),
+ /* uniqueId= */ "foodplease".getBytes(UTF_8),
+ /* softwareEnforced= */ AuthorizationList.builder().build(),
+ /* teeEnforced= */ AuthorizationList.builder()
+ .setUserAuthType(Set.of(UserAuthType.FINGERPRINT))
+ .setAttestationIdBrand("free food".getBytes(UTF_8)).build());
+ ASN1Sequence seq = expected.toAsn1Sequence();
+ ParsedAttestationRecord actual = ParsedAttestationRecord.create(seq);
+ assertThat(actual.attestationVersion).isEqualTo(expected.attestationVersion);
+ assertThat(actual.attestationSecurityLevel).isEqualTo(expected.attestationSecurityLevel);
+ assertThat(actual.keymasterVersion).isEqualTo(expected.keymasterVersion);
+ assertThat(actual.keymasterSecurityLevel).isEqualTo(expected.keymasterSecurityLevel);
+ assertThat(actual.attestationChallenge).isEqualTo(expected.attestationChallenge);
+ assertThat(actual.uniqueId).isEqualTo(expected.uniqueId);
+ assertThat(actual.teeEnforced.userAuthType).isEqualTo(expected.teeEnforced.userAuthType);
+ assertThat(actual.teeEnforced.attestationIdBrand)
+ .isEqualTo(expected.teeEnforced.attestationIdBrand);
+ }
+}
diff --git a/server/src/test/java/com/google/android/attestation/RootOfTrustTest.java b/server/src/test/java/com/google/android/attestation/RootOfTrustTest.java
new file mode 100644
index 0000000..3f952a6
--- /dev/null
+++ b/server/src/test/java/com/google/android/attestation/RootOfTrustTest.java
@@ -0,0 +1,68 @@
+/* Copyright 2019, The Android Open Source Project, Inc.
+ *
+ * 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.
+ */
+
+package com.google.android.attestation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.android.attestation.RootOfTrust.VerifiedBootState;
+import java.io.IOException;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.util.encoders.Base64;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/** Test for {@link RootOfTrust}. */
+@RunWith(JUnit4.class)
+public class RootOfTrustTest {
+
+ // Generated from certificate with EC Algorithm and StrongBox Security Level
+ private static final String ROOT_OF_TRUST =
+ "MEoEIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAQEACgECBCByjbEnTx8c8Vcd5DgLBIpVSsSjgOdvU1UI"
+ + "NSkISpN4AQ==\n";
+ private static final int ATTESTATION_VERSION = 3;
+
+ private static final byte[] EXPECTED_VERIFIED_BOOT_KEY =
+ Base64.decode("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=");
+ private static final boolean EXPECTED_DEVICE_LOCKED = false;
+ private static final VerifiedBootState EXPECTED_VERIFIED_BOOT_STATE =
+ VerifiedBootState.UNVERIFIED;
+ private static final byte[] EXPECTED_VERIFIED_BOOT_HASH =
+ Base64.decode("co2xJ08fHPFXHeQ4CwSKVUrEo4Dnb1NVCDUpCEqTeAE=");
+
+ private static ASN1Sequence getRootOfTrustSequence(String rootOfTrustB64) throws IOException {
+ byte[] rootOfTrustBytes = Base64.decode(rootOfTrustB64);
+ return (ASN1Sequence) ASN1Sequence.fromByteArray(rootOfTrustBytes);
+ }
+
+ @Test
+ public void testCreateRootOfTrust() throws IOException {
+ ASN1Sequence rootOfTrustSequence = getRootOfTrustSequence(ROOT_OF_TRUST);
+ RootOfTrust rootOfTrust =
+ RootOfTrust.createRootOfTrust(rootOfTrustSequence, ATTESTATION_VERSION);
+
+ assertThat(rootOfTrust).isNotNull();
+ assertThat(rootOfTrust.verifiedBootKey).isEqualTo(EXPECTED_VERIFIED_BOOT_KEY);
+ assertThat(rootOfTrust.deviceLocked).isEqualTo(EXPECTED_DEVICE_LOCKED);
+ assertThat(rootOfTrust.verifiedBootState).isEqualTo(EXPECTED_VERIFIED_BOOT_STATE);
+ assertThat(rootOfTrust.verifiedBootHash).isEqualTo(EXPECTED_VERIFIED_BOOT_HASH);
+ }
+
+ @Test
+ public void testCreateEmptyRootOfTrust() {
+ assertThat(RootOfTrust.createRootOfTrust(null, ATTESTATION_VERSION)).isNull();
+ }
+}
diff --git a/server/src/test/resources/status.json b/server/src/test/resources/status.json
new file mode 100644
index 0000000..74db10c
--- /dev/null
+++ b/server/src/test/resources/status.json
@@ -0,0 +1,25 @@
+{
+ "entries": {
+ "6681152659205225093" : {
+ "status": "REVOKED",
+ "reason": "KEY_COMPROMISE"
+ },
+ "8350192447815228107" : {
+ "status": "REVOKED",
+ "reason": "KEY_COMPROMISE"
+ },
+ "9408173275444922801" : {
+ "status": "REVOKED",
+ "reason": "KEY_COMPROMISE"
+ },
+ "11244410301401252959" : {
+ "status": "REVOKED",
+ "reason": "KEY_COMPROMISE"
+ },
+ "cc66e9a93713b6e643b26c15879786f7" : {
+ "status": "SUSPENDED",
+ "reason": "KEY_COMPROMISE",
+ "comment": "Entry for testing only"
+ }
+ }
+}