Add certificate utils to keystore2 crypto
This patch adds code for certificate generation with boringssl.
This is required for the Keymaster to KeyMint legacy wrapper.
Bug: 171351607
Test: keystore2_crypto_test
Change-Id: Id2d35be04cb5ab8c4e6b0597f5a970150ab9e69b
diff --git a/keystore2/Android.bp b/keystore2/Android.bp
index 526fd86..5939adf 100644
--- a/keystore2/Android.bp
+++ b/keystore2/Android.bp
@@ -57,7 +57,11 @@
cc_library {
name: "libkeystore2_crypto",
- srcs: ["src/crypto.cpp"],
+ srcs: [
+ "src/crypto.cpp",
+ "src/certificate_utils.cpp",
+ ],
+ export_include_dirs: ["include",],
shared_libs: [
"libcrypto",
"liblog",
@@ -84,6 +88,25 @@
init_rc: ["keystore2.rc"],
}
+cc_test {
+ cflags: [
+ "-Wall",
+ "-Werror",
+ "-Wextra",
+ ],
+ srcs: [
+ "src/tests/certificate_utils_test.cpp",
+ "src/tests/gtest_main.cpp",
+ ],
+ static_libs: [
+ "libkeystore2_crypto",
+ ],
+ shared_libs: [
+ "libcrypto",
+ ],
+ name: "keystore2_crypto_test",
+}
+
// This is a placeholder for the libraries that will be generated from the AIDL specs
// eventually.
rust_library {
@@ -97,3 +120,4 @@
"liblazy_static",
],
}
+
diff --git a/keystore2/include/certificate_utils.h b/keystore2/include/certificate_utils.h
new file mode 100644
index 0000000..9d41eb8
--- /dev/null
+++ b/keystore2/include/certificate_utils.h
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma once
+
+#include <openssl/err.h>
+#include <openssl/x509.h>
+#include <stdint.h>
+
+#include <memory>
+#include <optional>
+#include <variant>
+
+namespace keystore {
+// We use boringssl error codes. Error codes that we add are folded into LIB_USER.
+// The CertificateUtilsInternallErrorCodes enum should not be used by callers, instead use the
+// BoringSslError constant definitions below for error codes.
+using BoringSslError = unsigned long;
+
+#define DEFINE_OPENSSL_OBJECT_POINTER(name) using name##_Ptr = bssl::UniquePtr<name>
+
+DEFINE_OPENSSL_OBJECT_POINTER(ASN1_BIT_STRING);
+DEFINE_OPENSSL_OBJECT_POINTER(ASN1_STRING);
+DEFINE_OPENSSL_OBJECT_POINTER(ASN1_INTEGER);
+DEFINE_OPENSSL_OBJECT_POINTER(ASN1_OCTET_STRING);
+DEFINE_OPENSSL_OBJECT_POINTER(ASN1_TIME);
+DEFINE_OPENSSL_OBJECT_POINTER(EVP_PKEY);
+DEFINE_OPENSSL_OBJECT_POINTER(X509);
+DEFINE_OPENSSL_OBJECT_POINTER(X509_EXTENSION);
+DEFINE_OPENSSL_OBJECT_POINTER(X509_NAME);
+DEFINE_OPENSSL_OBJECT_POINTER(EVP_PKEY_CTX);
+
+class CertUtilsError {
+ public:
+ enum Error {
+ Ok = 0,
+ BoringSsl,
+ Encoding,
+ MemoryAllocation,
+ InvalidArgument,
+ UnexpectedNullPointer,
+ };
+
+ private:
+ Error e_;
+
+ public:
+ constexpr CertUtilsError(Error e) : e_(e) {}
+ explicit constexpr operator bool() const { return e_ != Ok; }
+};
+
+struct KeyUsageExtension {
+ bool isSigningKey;
+ bool isEncryptionKey;
+ bool isCertificationKey;
+};
+
+struct BasicConstraintsExtension {
+ bool isCa;
+ std::optional<int> pathLength;
+};
+
+/**
+ * This function allocates and prepares an X509 certificate structure with all of the information
+ * given. Next steps would be to set an Issuer with `setIssuer` and sign it with either
+ * `signCert` or `signCertWith`.
+ * @param evp_pkey The public key that the certificate is issued for.
+ * @param serial The certificate serial number.
+ * @param subject The subject common name.
+ * @param activeDateTimeMilliSeconds The not before date in epoch milliseconds.
+ * @param usageExpireDateTimeMilliSeconds The not after date in epoch milliseconds.
+ * @param addSubjectKeyIdEx If true, adds the subject key id extension.
+ * @param keyUsageEx If given adds, the key usage extension with the given flags.
+ * @param basicConstraints If given, adds the basic constraints extension with the given data.
+ * @return CertUtilsError::Ok on success.
+ */
+std::variant<CertUtilsError, X509_Ptr>
+makeCert(const EVP_PKEY* evp_pkey, //
+ const uint32_t serial, //
+ const char subject[], //
+ const uint64_t activeDateTimeMilliSeconds, //
+ const uint64_t usageExpireDateTimeMilliSeconds, //
+ bool addSubjectKeyIdEx, //
+ std::optional<KeyUsageExtension> keyUsageEx, //
+ std::optional<BasicConstraintsExtension> basicConstraints); //
+
+/**
+ * Takes the subject name from `signingCert` and sets it as issuer name in `cert`.
+ * if `addAuthKeyExt` is true it also generates the digest of the signing certificates's public key
+ * and sets it as authority key id extension in `cert`.
+ * For self signed certificates pass the same pointer to both `cert` and `signingCert`.
+ *
+ * @param cert
+ * @param signingCert
+ * @param addAuthKeyExt
+ * @return CertUtilsError::Ok on success.
+ */
+CertUtilsError setIssuer(X509* cert, const X509* signingCert, bool addAuthKeyExt);
+
+/**
+ * Takes a certificate, and private signing_key.
+ * Signs the certificate with the latter.
+ */
+CertUtilsError signCert(X509* certificate, EVP_PKEY* signing_key);
+
+enum class Digest {
+ SHA1,
+ SHA224,
+ SHA256,
+ SHA384,
+ SHA512,
+};
+
+enum class Algo {
+ ECDSA,
+ RSA,
+};
+
+enum class Padding {
+ Ignored,
+ PKCS1_5,
+ PSS,
+};
+
+/**
+ * Sets the signature specifier of the certificate and the signature according to the parameters
+ * c. Then it signs the certificate with the `sign` callback.
+ * IMPORTANT: The parameters `algo`, `padding`, and `digest` do not control the actual signing
+ * algorithm. The caller is responsible to provide a callback that actually performs the signature
+ * as described by this triplet.
+ * The `padding` argument is ignored if `algo` is Algo::EC.
+ * The `digest` field controls the message digest used, and, in case of RSA with PSS padding,
+ * also the MGF1 digest.
+ *
+ * @param certificate X509 certificate structure to be signed.
+ * @param sign Callback function used to digest and sign the DER encoded to-be-signed certificate.
+ * @param algo Algorithm specifier used to encode the signing algorithm id of the X509 certificate.
+ * @param padding Padding specifier used to encode the signing algorithm id of the X509 certificate.
+ * @param digest Digest specifier used to encode the signing algorithm id of the X509 certificate.
+ * @return CertUtilsError::Ok on success.
+ */
+CertUtilsError signCertWith(X509* certificate,
+ std::function<std::vector<uint8_t>(const uint8_t*, size_t)> sign,
+ Algo algo, Padding padding, Digest digest);
+
+/**
+ * Generates the DER representation of the given signed X509 certificate structure.
+ * @param certificate
+ * @return std::vector<uint8_t> with the DER encoded certificate on success. An error code
+ * otherwise.
+ */
+std::variant<CertUtilsError, std::vector<uint8_t>> encodeCert(X509* certificate);
+
+} // namespace keystore
diff --git a/keystore2/src/certificate_utils.cpp b/keystore2/src/certificate_utils.cpp
new file mode 100644
index 0000000..56dd3f4
--- /dev/null
+++ b/keystore2/src/certificate_utils.cpp
@@ -0,0 +1,558 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include <certificate_utils.h>
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/mem.h>
+#include <openssl/x509v3.h>
+
+#include <functional>
+#include <limits>
+#include <string>
+#include <variant>
+#include <vector>
+
+namespace keystore {
+
+namespace {
+
+constexpr int kDigitalSignatureKeyUsageBit = 0;
+constexpr int kKeyEnciphermentKeyUsageBit = 2;
+constexpr int kDataEnciphermentKeyUsageBit = 3;
+constexpr int kKeyCertSignBit = 5;
+constexpr int kMaxKeyUsageBit = 8;
+
+DEFINE_OPENSSL_OBJECT_POINTER(ASN1_STRING);
+DEFINE_OPENSSL_OBJECT_POINTER(RSA_PSS_PARAMS);
+DEFINE_OPENSSL_OBJECT_POINTER(AUTHORITY_KEYID);
+DEFINE_OPENSSL_OBJECT_POINTER(BASIC_CONSTRAINTS);
+DEFINE_OPENSSL_OBJECT_POINTER(X509_ALGOR);
+
+} // namespace
+
+std::variant<CertUtilsError, X509_NAME_Ptr> makeCommonName(const std::string& name) {
+ X509_NAME_Ptr x509_name(X509_NAME_new());
+ if (!x509_name) {
+ return CertUtilsError::BoringSsl;
+ }
+ if (!X509_NAME_add_entry_by_txt(x509_name.get(), "CN", MBSTRING_ASC,
+ reinterpret_cast<const uint8_t*>(name.c_str()), name.length(),
+ -1 /* loc */, 0 /* set */)) {
+ return CertUtilsError::BoringSsl;
+ }
+ return x509_name;
+}
+
+std::variant<CertUtilsError, std::vector<uint8_t>> makeKeyId(const X509* cert) {
+ std::vector<uint8_t> keyid(20);
+ unsigned int len;
+ if (!X509_pubkey_digest(cert, EVP_sha1(), keyid.data(), &len)) {
+ return CertUtilsError::Encoding;
+ }
+ return keyid;
+}
+
+std::variant<CertUtilsError, AUTHORITY_KEYID_Ptr>
+makeAuthorityKeyExtension(const std::vector<uint8_t>& keyid) {
+ AUTHORITY_KEYID_Ptr auth_key(AUTHORITY_KEYID_new());
+ if (!auth_key) {
+ return CertUtilsError::MemoryAllocation;
+ }
+
+ auth_key->keyid = ASN1_OCTET_STRING_new();
+ if (auth_key->keyid == nullptr) {
+ return CertUtilsError::MemoryAllocation;
+ }
+
+ if (!ASN1_OCTET_STRING_set(auth_key->keyid, keyid.data(), keyid.size())) {
+ return CertUtilsError::BoringSsl;
+ }
+
+ return auth_key;
+}
+
+std::variant<CertUtilsError, ASN1_OCTET_STRING_Ptr>
+makeSubjectKeyExtension(const std::vector<uint8_t>& keyid) {
+
+ // Build OCTET_STRING
+ ASN1_OCTET_STRING_Ptr keyid_str(ASN1_OCTET_STRING_new());
+ if (!keyid_str || !ASN1_OCTET_STRING_set(keyid_str.get(), keyid.data(), keyid.size())) {
+ return CertUtilsError::BoringSsl;
+ }
+
+ return keyid_str;
+}
+
+std::variant<CertUtilsError, BASIC_CONSTRAINTS_Ptr>
+makeBasicConstraintsExtension(bool is_ca, std::optional<int> path_length) {
+
+ BASIC_CONSTRAINTS_Ptr bcons(BASIC_CONSTRAINTS_new());
+ if (!bcons) {
+ return CertUtilsError::MemoryAllocation;
+ }
+
+ bcons->ca = is_ca;
+ bcons->pathlen = nullptr;
+ if (path_length) {
+ bcons->pathlen = ASN1_INTEGER_new();
+ if (bcons->pathlen == nullptr || !ASN1_INTEGER_set(bcons->pathlen, *path_length)) {
+ return CertUtilsError::BoringSsl;
+ }
+ }
+
+ return bcons;
+}
+
+std::variant<CertUtilsError, ASN1_BIT_STRING_Ptr>
+makeKeyUsageExtension(bool is_signing_key, bool is_encryption_key, bool is_cert_key) {
+ // Build BIT_STRING with correct contents.
+ ASN1_BIT_STRING_Ptr key_usage(ASN1_BIT_STRING_new());
+ if (!key_usage) {
+ return CertUtilsError::BoringSsl;
+ }
+
+ for (size_t i = 0; i <= kMaxKeyUsageBit; ++i) {
+ if (!ASN1_BIT_STRING_set_bit(key_usage.get(), i, 0)) {
+ return CertUtilsError::BoringSsl;
+ }
+ }
+
+ if (is_signing_key) {
+ if (!ASN1_BIT_STRING_set_bit(key_usage.get(), kDigitalSignatureKeyUsageBit, 1)) {
+ return CertUtilsError::BoringSsl;
+ }
+ }
+
+ if (is_encryption_key) {
+ if (!ASN1_BIT_STRING_set_bit(key_usage.get(), kKeyEnciphermentKeyUsageBit, 1) ||
+ !ASN1_BIT_STRING_set_bit(key_usage.get(), kDataEnciphermentKeyUsageBit, 1)) {
+ return CertUtilsError::BoringSsl;
+ }
+ }
+
+ if (is_cert_key) {
+ if (!ASN1_BIT_STRING_set_bit(key_usage.get(), kKeyCertSignBit, 1)) {
+ return CertUtilsError::BoringSsl;
+ }
+ }
+
+ return key_usage;
+}
+
+// Creates a rump certificate structure with serial, subject and issuer names, as well as
+// activation and expiry date.
+// Callers should pass an empty X509_Ptr and check the return value for CertUtilsError::Ok (0)
+// before accessing the result.
+std::variant<CertUtilsError, X509_Ptr>
+makeCertRump(const uint32_t serial, const char subject[], const uint64_t activeDateTimeMilliSeconds,
+ const uint64_t usageExpireDateTimeMilliSeconds) {
+
+ // Sanitize pointer arguments.
+ if (!subject || strlen(subject) == 0) {
+ return CertUtilsError::InvalidArgument;
+ }
+
+ // Create certificate structure.
+ X509_Ptr certificate(X509_new());
+ if (!certificate) {
+ return CertUtilsError::BoringSsl;
+ }
+
+ // Set the X509 version.
+ if (!X509_set_version(certificate.get(), 2 /* version 3, but zero-based */)) {
+ return CertUtilsError::BoringSsl;
+ }
+
+ // Set the certificate serialNumber
+ ASN1_INTEGER_Ptr serialNumber(ASN1_INTEGER_new());
+ if (!serialNumber || !ASN1_INTEGER_set(serialNumber.get(), serial) ||
+ !X509_set_serialNumber(certificate.get(), serialNumber.get() /* Don't release; copied */))
+ return CertUtilsError::BoringSsl;
+
+ // Set Subject Name
+ auto subjectName = makeCommonName(subject);
+ if (auto x509_subject = std::get_if<X509_NAME_Ptr>(&subjectName)) {
+ if (!X509_set_subject_name(certificate.get(), x509_subject->get() /* copied */)) {
+ return CertUtilsError::BoringSsl;
+ }
+ } else {
+ return std::get<CertUtilsError>(subjectName);
+ }
+
+ // Set activation date.
+ ASN1_TIME_Ptr notBefore(ASN1_TIME_new());
+ if (!notBefore || !ASN1_TIME_set(notBefore.get(), activeDateTimeMilliSeconds / 1000) ||
+ !X509_set_notBefore(certificate.get(), notBefore.get() /* Don't release; copied */))
+ return CertUtilsError::BoringSsl;
+
+ // Set expiration date.
+ time_t notAfterTime;
+ notAfterTime = (time_t)std::min((uint64_t)std::numeric_limits<time_t>::max(),
+ usageExpireDateTimeMilliSeconds / 1000);
+
+ ASN1_TIME_Ptr notAfter(ASN1_TIME_new());
+ if (!notAfter || !ASN1_TIME_set(notAfter.get(), notAfterTime) ||
+ !X509_set_notAfter(certificate.get(), notAfter.get() /* Don't release; copied */)) {
+ return CertUtilsError::BoringSsl;
+ }
+
+ return certificate;
+}
+
+std::variant<CertUtilsError, X509_Ptr>
+makeCert(const EVP_PKEY* evp_pkey, const uint32_t serial, const char subject[],
+ const uint64_t activeDateTimeMilliSeconds, const uint64_t usageExpireDateTimeMilliSeconds,
+ bool addSubjectKeyIdEx, std::optional<KeyUsageExtension> keyUsageEx,
+ std::optional<BasicConstraintsExtension> basicConstraints) {
+
+ // Make the rump certificate with serial, subject, not before and not after dates.
+ auto certificateV =
+ makeCertRump(serial, subject, activeDateTimeMilliSeconds, usageExpireDateTimeMilliSeconds);
+ if (auto error = std::get_if<CertUtilsError>(&certificateV)) {
+ return *error;
+ }
+ auto certificate = std::move(std::get<X509_Ptr>(certificateV));
+
+ // Set the public key.
+ if (!X509_set_pubkey(certificate.get(), const_cast<EVP_PKEY*>(evp_pkey))) {
+ return CertUtilsError::BoringSsl;
+ }
+
+ if (keyUsageEx) {
+ // Make and add the key usage extension.
+ auto key_usage_extensionV = makeKeyUsageExtension(
+ keyUsageEx->isSigningKey, keyUsageEx->isEncryptionKey, keyUsageEx->isCertificationKey);
+ if (auto error = std::get_if<CertUtilsError>(&key_usage_extensionV)) {
+ return *error;
+ }
+ auto key_usage_extension = std::move(std::get<ASN1_BIT_STRING_Ptr>(key_usage_extensionV));
+ if (!X509_add1_ext_i2d(certificate.get(), NID_key_usage,
+ key_usage_extension.get() /* Don't release; copied */,
+ true /* critical */, 0 /* flags */)) {
+ return CertUtilsError::BoringSsl;
+ }
+ }
+
+ if (basicConstraints) {
+ // Make and add basic constraints
+ auto basic_constraints_extensionV =
+ makeBasicConstraintsExtension(basicConstraints->isCa, basicConstraints->pathLength);
+ if (auto error = std::get_if<CertUtilsError>(&basic_constraints_extensionV)) {
+ return *error;
+ }
+ auto basic_constraints_extension =
+ std::move(std::get<BASIC_CONSTRAINTS_Ptr>(basic_constraints_extensionV));
+ if (!X509_add1_ext_i2d(certificate.get(), NID_basic_constraints,
+ basic_constraints_extension.get() /* Don't release; copied */,
+ true /* critical */, 0 /* flags */)) {
+ return CertUtilsError::BoringSsl;
+ }
+ }
+
+ if (addSubjectKeyIdEx) {
+ // Make and add subject key id extension.
+ auto keyidV = makeKeyId(certificate.get());
+ if (auto error = std::get_if<CertUtilsError>(&keyidV)) {
+ return *error;
+ }
+ auto& keyid = std::get<std::vector<uint8_t>>(keyidV);
+
+ auto subject_key_extensionV = makeSubjectKeyExtension(keyid);
+ if (auto error = std::get_if<CertUtilsError>(&subject_key_extensionV)) {
+ return *error;
+ }
+ auto subject_key_extension =
+ std::move(std::get<ASN1_OCTET_STRING_Ptr>(subject_key_extensionV));
+ if (!X509_add1_ext_i2d(certificate.get(), NID_subject_key_identifier,
+ subject_key_extension.get() /* Don't release; copied */,
+ false /* critical */, 0 /* flags */)) {
+ return CertUtilsError::BoringSsl;
+ }
+ }
+
+ return certificate;
+}
+
+CertUtilsError setIssuer(X509* cert, const X509* signingCert, bool addAuthKeyExt) {
+
+ X509_NAME* issuerName(X509_get_subject_name(signingCert));
+
+ // Set Issuer Name
+ if (issuerName) {
+ if (!X509_set_issuer_name(cert, issuerName /* copied */)) {
+ return CertUtilsError::BoringSsl;
+ }
+ } else {
+ return CertUtilsError::Encoding;
+ }
+
+ if (addAuthKeyExt) {
+ // Make and add authority key extension - self signed.
+ auto keyidV = makeKeyId(signingCert);
+ if (auto error = std::get_if<CertUtilsError>(&keyidV)) {
+ return *error;
+ }
+ auto& keyid = std::get<std::vector<uint8_t>>(keyidV);
+
+ auto auth_key_extensionV = makeAuthorityKeyExtension(keyid);
+ if (auto error = std::get_if<CertUtilsError>(&auth_key_extensionV)) {
+ return *error;
+ }
+ auto auth_key_extension = std::move(std::get<AUTHORITY_KEYID_Ptr>(auth_key_extensionV));
+ if (!X509_add1_ext_i2d(cert, NID_authority_key_identifier, auth_key_extension.get(), false,
+ 0)) {
+ return CertUtilsError::BoringSsl;
+ }
+ }
+ return CertUtilsError::Ok;
+}
+
+// Takes a certificate a signing certificate and the raw private signing_key. And signs
+// the certificate with the latter.
+CertUtilsError signCert(X509* certificate, EVP_PKEY* signing_key) {
+
+ if (certificate == nullptr) {
+ return CertUtilsError::UnexpectedNullPointer;
+ }
+
+ if (!X509_sign(certificate, signing_key, EVP_sha256())) {
+ return CertUtilsError::BoringSsl;
+ }
+
+ return CertUtilsError::Ok;
+}
+
+std::variant<CertUtilsError, std::vector<uint8_t>> encodeCert(X509* certificate) {
+ int len = i2d_X509(certificate, nullptr);
+ if (len < 0) {
+ return CertUtilsError::BoringSsl;
+ }
+
+ auto result = std::vector<uint8_t>(len);
+ uint8_t* p = result.data();
+
+ if (i2d_X509(certificate, &p) < 0) {
+ return CertUtilsError::BoringSsl;
+ }
+ return result;
+}
+
+CertUtilsError setRsaDigestAlgorField(X509_ALGOR** alg_ptr, const EVP_MD* digest) {
+ if (alg_ptr == nullptr || digest == nullptr) {
+ return CertUtilsError::UnexpectedNullPointer;
+ }
+ *alg_ptr = X509_ALGOR_new();
+ if (*alg_ptr == nullptr) {
+ return CertUtilsError::MemoryAllocation;
+ }
+ X509_ALGOR_set_md(*alg_ptr, digest);
+ return CertUtilsError::Ok;
+}
+
+CertUtilsError setPssMaskGeneratorField(X509_ALGOR** alg_ptr, const EVP_MD* digest) {
+ X509_ALGOR* mgf1_digest = nullptr;
+ if (auto error = setRsaDigestAlgorField(&mgf1_digest, digest)) {
+ return error;
+ }
+ X509_ALGOR_Ptr mgf1_digest_ptr(mgf1_digest);
+
+ ASN1_OCTET_STRING* mgf1_digest_algor_str = nullptr;
+ if (!ASN1_item_pack(mgf1_digest, ASN1_ITEM_rptr(X509_ALGOR), &mgf1_digest_algor_str)) {
+ return CertUtilsError::Encoding;
+ }
+ ASN1_OCTET_STRING_Ptr mgf1_digest_algor_str_ptr(mgf1_digest_algor_str);
+
+ *alg_ptr = X509_ALGOR_new();
+ if (*alg_ptr == nullptr) {
+ return CertUtilsError::MemoryAllocation;
+ }
+ X509_ALGOR_set0(*alg_ptr, OBJ_nid2obj(NID_mgf1), V_ASN1_SEQUENCE, mgf1_digest_algor_str);
+ // *alg_ptr took ownership of the octet string
+ mgf1_digest_algor_str_ptr.release();
+ return CertUtilsError::Ok;
+}
+
+static CertUtilsError setSaltLength(RSA_PSS_PARAMS* pss_params, unsigned length) {
+ pss_params->saltLength = ASN1_INTEGER_new();
+ if (pss_params->saltLength == nullptr) {
+ return CertUtilsError::MemoryAllocation;
+ }
+ if (!ASN1_INTEGER_set(pss_params->saltLength, length)) {
+ return CertUtilsError::Encoding;
+ };
+ return CertUtilsError::Ok;
+}
+
+std::variant<CertUtilsError, ASN1_STRING_Ptr> buildRsaPssParameter(Digest digest) {
+ RSA_PSS_PARAMS_Ptr pss(RSA_PSS_PARAMS_new());
+ if (!pss) {
+ return CertUtilsError::MemoryAllocation;
+ }
+
+ const EVP_MD* md = nullptr;
+
+ switch (digest) {
+ case Digest::SHA1:
+ break;
+ case Digest::SHA224:
+ md = EVP_sha224();
+ break;
+ case Digest::SHA256:
+ md = EVP_sha256();
+ break;
+ case Digest::SHA384:
+ md = EVP_sha384();
+ break;
+ case Digest::SHA512:
+ md = EVP_sha512();
+ break;
+ default:
+ return CertUtilsError::InvalidArgument;
+ }
+
+ if (md != nullptr) {
+ if (auto error = setSaltLength(pss.get(), EVP_MD_size(md))) {
+ return error;
+ }
+ if (auto error = setRsaDigestAlgorField(&pss->hashAlgorithm, md)) {
+ return error;
+ }
+ if (auto error = setPssMaskGeneratorField(&pss->maskGenAlgorithm, md)) {
+ return error;
+ }
+ }
+
+ ASN1_STRING* algo_str = nullptr;
+ if (!ASN1_item_pack(pss.get(), ASN1_ITEM_rptr(RSA_PSS_PARAMS), &algo_str)) {
+ return CertUtilsError::BoringSsl;
+ }
+
+ return ASN1_STRING_Ptr(algo_str);
+}
+
+CertUtilsError makeAndSetAlgo(X509_ALGOR* algo_field, Algo algo, Padding padding, Digest digest) {
+ if (algo_field == nullptr) {
+ return CertUtilsError::UnexpectedNullPointer;
+ }
+ ASN1_STRING_Ptr param;
+ int param_type = V_ASN1_UNDEF;
+ int nid = 0;
+ switch (algo) {
+ case Algo::ECDSA:
+ switch (digest) {
+ case Digest::SHA1:
+ nid = NID_ecdsa_with_SHA1;
+ break;
+ case Digest::SHA224:
+ nid = NID_ecdsa_with_SHA224;
+ break;
+ case Digest::SHA256:
+ nid = NID_ecdsa_with_SHA256;
+ break;
+ case Digest::SHA384:
+ nid = NID_ecdsa_with_SHA384;
+ break;
+ case Digest::SHA512:
+ nid = NID_ecdsa_with_SHA512;
+ break;
+ default:
+ return CertUtilsError::InvalidArgument;
+ }
+ break;
+ case Algo::RSA:
+ switch (padding) {
+ case Padding::PKCS1_5:
+ param_type = V_ASN1_NULL;
+ switch (digest) {
+ case Digest::SHA1:
+ nid = NID_sha1WithRSAEncryption;
+ break;
+ case Digest::SHA224:
+ nid = NID_sha224WithRSAEncryption;
+ break;
+ case Digest::SHA256:
+ nid = NID_sha256WithRSAEncryption;
+ break;
+ case Digest::SHA384:
+ nid = NID_sha384WithRSAEncryption;
+ break;
+ case Digest::SHA512:
+ nid = NID_sha512WithRSAEncryption;
+ break;
+ default:
+ return CertUtilsError::InvalidArgument;
+ }
+ break;
+ case Padding::PSS: {
+ auto v = buildRsaPssParameter(digest);
+ if (auto param_str = std::get_if<ASN1_STRING_Ptr>(&v)) {
+ param = std::move(*param_str);
+ param_type = V_ASN1_SEQUENCE;
+ nid = NID_rsassaPss;
+ } else {
+ return std::get<CertUtilsError>(v);
+ }
+ break;
+ }
+ default:
+ return CertUtilsError::InvalidArgument;
+ }
+ break;
+ default:
+ return CertUtilsError::InvalidArgument;
+ }
+
+ if (!X509_ALGOR_set0(algo_field, OBJ_nid2obj(nid), param_type, param.get())) {
+ return CertUtilsError::Encoding;
+ }
+ // The X509 struct took ownership.
+ param.release();
+ return CertUtilsError::Ok;
+}
+
+// This function allows for signing a
+CertUtilsError signCertWith(X509* certificate,
+ std::function<std::vector<uint8_t>(const uint8_t*, size_t)> sign,
+ Algo algo, Padding padding, Digest digest) {
+ if (auto error = makeAndSetAlgo(certificate->sig_alg, algo, padding, digest)) {
+ return error;
+ }
+ if (auto error = makeAndSetAlgo(certificate->cert_info->signature, algo, padding, digest)) {
+ return error;
+ }
+
+ uint8_t* cert_buf = nullptr;
+ size_t buf_len = i2d_re_X509_tbs(certificate, &cert_buf);
+ if (buf_len < 0) {
+ return CertUtilsError::Encoding;
+ }
+
+ bssl::UniquePtr<uint8_t> free_cert_buf(cert_buf);
+ auto signature = sign(cert_buf, buf_len);
+
+ if (!ASN1_STRING_set(certificate->signature, signature.data(), signature.size())) {
+ return CertUtilsError::BoringSsl;
+ }
+
+ certificate->signature->flags &= ~(0x07);
+ certificate->signature->flags |= ASN1_STRING_FLAG_BITS_LEFT;
+
+ return CertUtilsError::Ok;
+}
+
+} // namespace keystore
diff --git a/keystore2/src/tests/certificate_utils_test.cpp b/keystore2/src/tests/certificate_utils_test.cpp
new file mode 100644
index 0000000..2df9ce5
--- /dev/null
+++ b/keystore2/src/tests/certificate_utils_test.cpp
@@ -0,0 +1,317 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+
+#include "certificate_utils.h"
+
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/mem.h>
+
+#include <iomanip>
+#include <iostream>
+#include <sstream>
+#include <variant>
+
+#include "test_keys.h"
+
+using namespace keystore;
+
+// I leave these here in case they are needed for debugging.
+namespace debug_utils {
+
+void log_ssl_error() {
+ unsigned long error = ERR_peek_last_error();
+
+ char buf[128];
+ ERR_error_string_n(error, buf, sizeof(buf));
+ std::cout << "BoringSslError: " << buf << std::endl;
+}
+
+std::string hexdump(const std::vector<uint8_t>& data) {
+ std::stringstream s;
+ size_t column_count = 0;
+ for (auto& c : data) {
+ s << std::setw(2) << std::setfill('0') << std::hex << (unsigned int)c;
+ if (++column_count % 40 == 0) s << "\n";
+ }
+ return s.str();
+}
+
+} // namespace debug_utils
+
+constexpr uint64_t kValidity = 24 * 60 * 60 * 1000; // 24 hours in milliseconds
+
+const EVP_MD* getMD(Digest digest) {
+ switch (digest) {
+ case Digest::SHA1:
+ return EVP_sha1();
+ case Digest::SHA224:
+ return EVP_sha224();
+ case Digest::SHA256:
+ return EVP_sha256();
+ case Digest::SHA384:
+ return EVP_sha384();
+ case Digest::SHA512:
+ return EVP_sha512();
+ }
+}
+
+std::array<Digest, 5> digests = {
+ Digest::SHA1, Digest::SHA224, Digest::SHA256, Digest::SHA384, Digest::SHA512,
+};
+
+static const char* toString(Digest d) {
+ switch (d) {
+ case Digest::SHA1:
+ return "SHA1";
+ case Digest::SHA224:
+ return "SHA224";
+ case Digest::SHA256:
+ return "SHA256";
+ case Digest::SHA384:
+ return "SHA384";
+ case Digest::SHA512:
+ return "SHA512";
+ }
+}
+
+std::array<Padding, 2> rsa_paddings = {
+ Padding::PSS,
+ Padding::PKCS1_5,
+};
+
+enum class EcCurve {
+ P224,
+ P256,
+ P384,
+ P521,
+};
+
+std::array<int, 4> ec_curves = {
+ NID_secp224r1,
+ NID_X9_62_prime256v1,
+ NID_secp384r1,
+ NID_secp521r1,
+};
+
+static const char* curveNidToString(int nid) {
+ switch (nid) {
+ case NID_secp224r1:
+ return "P224";
+ case NID_X9_62_prime256v1:
+ return "P256";
+ case NID_secp384r1:
+ return "P384";
+ case NID_secp521r1:
+ return "P521";
+ default:
+ return "Unknown";
+ }
+}
+
+std::array<long, 2> rsa_key_sizes = {
+ 2048,
+ 4096,
+};
+
+using EcParam = std::tuple<int /* EC curve NID */, Digest>;
+
+class CertificateUtilsWithEcCurve : public testing::TestWithParam<EcParam> {};
+
+static std::string paramToStringEc(testing::TestParamInfo<EcParam> param) {
+ std::stringstream s;
+ auto [curve_nid, digest] = param.param;
+ s << param.index << "_" << curveNidToString(curve_nid) << "_" << toString(digest);
+ return s.str();
+}
+
+INSTANTIATE_TEST_SUITE_P(CertSigningWithCallbackEC, CertificateUtilsWithEcCurve,
+ testing::Combine(testing::ValuesIn(ec_curves), testing::ValuesIn(digests)),
+ paramToStringEc);
+
+TEST_P(CertificateUtilsWithEcCurve, CertSigningWithCallbackEC) {
+ // Structured decomposition (e.g.: auto [a, b, c] = ...) does not work here because
+ // names bound this way cannot be captured in lambda expressions so we use std::tie instead.
+ int curve_nid;
+ Digest digest;
+ std::tie(curve_nid, digest) = GetParam();
+ EVP_PKEY_CTX_Ptr pkey_ctx(EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL));
+ ASSERT_TRUE((bool)pkey_ctx);
+ ASSERT_TRUE(EVP_PKEY_keygen_init(pkey_ctx.get()));
+ ASSERT_TRUE(EVP_PKEY_CTX_set_ec_paramgen_curve_nid(pkey_ctx.get(), curve_nid));
+
+ EVP_PKEY* pkey_ptr = nullptr;
+ ASSERT_TRUE(EVP_PKEY_keygen(pkey_ctx.get(), &pkey_ptr));
+ EVP_PKEY_Ptr pkey(pkey_ptr);
+ ASSERT_TRUE(pkey);
+
+ uint64_t now_ms = (uint64_t)time(nullptr) * 1000;
+
+ BasicConstraintsExtension bcons{
+ .isCa = true,
+ .pathLength = {},
+ };
+
+ KeyUsageExtension keyUsage{
+ .isSigningKey = true,
+ .isEncryptionKey = false,
+ .isCertificationKey = true,
+ };
+
+ auto certV = makeCert(pkey.get(), 1, "Me", now_ms - kValidity, now_ms + kValidity,
+ true /* subject key id extension */, keyUsage, bcons);
+ ASSERT_TRUE(std::holds_alternative<X509_Ptr>(certV));
+ auto& cert = std::get<X509_Ptr>(certV);
+ ASSERT_TRUE(!setIssuer(cert.get(), cert.get(), true));
+
+ ASSERT_TRUE(!signCertWith(
+ cert.get(),
+ [&](const uint8_t* data, size_t len) {
+ bssl::ScopedEVP_MD_CTX sign_ctx;
+ EXPECT_TRUE(
+ EVP_DigestSignInit(sign_ctx.get(), nullptr, getMD(digest), nullptr, pkey.get()));
+
+ std::vector<uint8_t> sig_buf(512);
+ size_t sig_len = 512;
+ EVP_DigestSign(sign_ctx.get(), sig_buf.data(), &sig_len, data, len);
+ sig_buf.resize(sig_len);
+ return sig_buf;
+ },
+ Algo::ECDSA, Padding::Ignored, digest));
+
+ auto encCertV = encodeCert(cert.get());
+ ASSERT_TRUE(std::holds_alternative<std::vector<uint8_t>>(encCertV));
+
+ auto& encCert = std::get<1>(encCertV);
+ // Uncomment the next line to dump the DER encoded signed certificate as hex string.
+ // You can pipe this dump into "xxd -r -p | openssl x509 -inform der -text -noout"
+ // to inspect the certificate.
+ // std::cout << "DER encoded cert:\n" << debug_utils::hexdump(encCert) << std::endl;
+
+ const uint8_t* p = encCert.data();
+ X509_Ptr decoded_cert(d2i_X509(nullptr, &p, (long)encCert.size()));
+ EVP_PKEY_Ptr decoded_pkey(X509_get_pubkey(decoded_cert.get()));
+ ASSERT_TRUE(X509_verify(decoded_cert.get(), decoded_pkey.get()));
+}
+
+using RsaParams = std::tuple<long /* key size */, Padding, Digest>;
+
+class CertificateUtilsWithRsa : public testing::TestWithParam<RsaParams> {};
+
+static std::string paramsToStringRsa(testing::TestParamInfo<RsaParams> param) {
+ std::stringstream s;
+ auto [key_size, padding, digest] = param.param;
+ s << param.index << "_" << key_size << "_";
+ switch (padding) {
+ case Padding::PSS:
+ s << "PSS";
+ break;
+ case Padding::PKCS1_5:
+ s << "PKCS1_5";
+ break;
+ case Padding::Ignored:
+ s << "Ignored";
+ }
+ s << "_" << toString(digest);
+ return s.str();
+}
+
+INSTANTIATE_TEST_SUITE_P(CertSigningWithCallbackRsa, CertificateUtilsWithRsa,
+ testing::Combine(testing::ValuesIn(rsa_key_sizes),
+ testing::ValuesIn(rsa_paddings),
+ testing::ValuesIn(digests)),
+ paramsToStringRsa);
+
+TEST_P(CertificateUtilsWithRsa, CertSigningWithCallbackRsa) {
+ // Structured decomposition (e.g.: auto [a, b, c] = ...) does not work here because
+ // names bound this way cannot be captured in lambda expressions so we use std::tie instead.
+ long key_size;
+ Padding padding;
+ Digest digest;
+ std::tie(key_size, padding, digest) = GetParam();
+
+ CBS cbs;
+ switch (key_size) {
+ case 2048:
+ CBS_init(&cbs, rsa_key_2k, rsa_key_2k_len);
+ break;
+ case 4096:
+ CBS_init(&cbs, rsa_key_4k, rsa_key_4k_len);
+ break;
+ default:
+ FAIL();
+ }
+ EVP_PKEY_Ptr pkey(EVP_parse_private_key(&cbs));
+ ASSERT_TRUE(pkey);
+
+ uint64_t now_ms = (uint64_t)time(nullptr) * 1000;
+
+ BasicConstraintsExtension bcons{
+ .isCa = true,
+ .pathLength = 0,
+ };
+
+ KeyUsageExtension keyUsage{
+ .isSigningKey = true,
+ .isEncryptionKey = false,
+ .isCertificationKey = true,
+ };
+
+ auto certV = makeCert(pkey.get(), 1, "Me", now_ms - kValidity, now_ms + kValidity,
+ true /* subject key id extension */, keyUsage, bcons);
+ ASSERT_TRUE(std::holds_alternative<X509_Ptr>(certV));
+ auto& cert = std::get<X509_Ptr>(certV);
+ ASSERT_TRUE(!setIssuer(cert.get(), cert.get(), true));
+
+ ASSERT_TRUE(!signCertWith(
+ cert.get(),
+ [&](const uint8_t* data, size_t len) {
+ bssl::ScopedEVP_MD_CTX sign_ctx;
+ EVP_PKEY_CTX* pkey_sign_ctx_ptr;
+ EXPECT_TRUE(EVP_DigestSignInit(sign_ctx.get(), &pkey_sign_ctx_ptr, getMD(digest),
+ nullptr, pkey.get()));
+
+ if (padding == Padding::PSS) {
+ EXPECT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pkey_sign_ctx_ptr, RSA_PKCS1_PSS_PADDING));
+ EXPECT_TRUE(EVP_PKEY_CTX_set_rsa_pss_saltlen(pkey_sign_ctx_ptr, -1));
+ } else {
+ EXPECT_TRUE(EVP_PKEY_CTX_set_rsa_padding(pkey_sign_ctx_ptr, RSA_PKCS1_PADDING));
+ }
+
+ std::vector<uint8_t> sig_buf(1024);
+ size_t sig_len = 1024;
+ EVP_DigestSign(sign_ctx.get(), sig_buf.data(), &sig_len, data, len);
+ sig_buf.resize(sig_len);
+ return sig_buf;
+ },
+ Algo::RSA, padding, digest));
+
+ auto encCertV = encodeCert(cert.get());
+ ASSERT_TRUE(std::holds_alternative<std::vector<uint8_t>>(encCertV));
+
+ auto& encCert = std::get<1>(encCertV);
+ // Uncomment the next line to dump the DER encoded signed certificate as hex string.
+ // You can pipe this dump into "xxd -r -p | openssl x509 -inform der -text -noout"
+ // to inspect the certificate.
+ // std::cout << "DER encoded cert:\n" << debug_utils::hexdump(encCert) << std::endl;
+
+ const uint8_t* p = encCert.data();
+ X509_Ptr decoded_cert(d2i_X509(nullptr, &p, (long)encCert.size()));
+ EVP_PKEY_Ptr decoded_pkey(X509_get_pubkey(decoded_cert.get()));
+ ASSERT_TRUE(X509_verify(decoded_cert.get(), decoded_pkey.get()));
+}
diff --git a/keystore2/src/tests/gtest_main.cpp b/keystore2/src/tests/gtest_main.cpp
new file mode 100644
index 0000000..149cbbc
--- /dev/null
+++ b/keystore2/src/tests/gtest_main.cpp
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * 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.
+ */
+
+#include <gtest/gtest.h>
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/keystore2/src/tests/test_keys.h b/keystore2/src/tests/test_keys.h
new file mode 100644
index 0000000..d3b1175
--- /dev/null
+++ b/keystore2/src/tests/test_keys.h
@@ -0,0 +1,249 @@
+/*
+ * Copyright 2020, The Android Open Source Project
+ *
+ * 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.
+ */
+
+#pragma once
+
+constexpr const unsigned char rsa_key_4k[] = {
+ 0x30, 0x82, 0x09, 0x41, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 0x09, 0x2b, 0x30, 0x82, 0x09, 0x27, 0x02, 0x01,
+ 0x00, 0x02, 0x82, 0x02, 0x01, 0x00, 0xac, 0x07, 0x4e, 0xc6, 0x20, 0xb9, 0x78, 0x03, 0xd0, 0x53,
+ 0x22, 0xa4, 0xef, 0x6d, 0xd1, 0x7a, 0x7e, 0x5e, 0x5e, 0x1d, 0xe7, 0x9b, 0x30, 0x31, 0x99, 0xe8,
+ 0x1c, 0xdf, 0x32, 0x0c, 0xdb, 0xdb, 0xc2, 0x4c, 0x22, 0x10, 0x71, 0xd0, 0x76, 0x83, 0xb0, 0x50,
+ 0x66, 0xa4, 0x43, 0x75, 0xe8, 0x46, 0xde, 0x58, 0x9e, 0x31, 0x82, 0x81, 0x8c, 0x36, 0x20, 0xbb,
+ 0x42, 0xcd, 0xb9, 0x21, 0x29, 0x67, 0x39, 0x4d, 0x2a, 0x1c, 0xc2, 0x89, 0x48, 0x23, 0x5f, 0xdd,
+ 0x60, 0xc4, 0x04, 0x42, 0xbe, 0x6f, 0x01, 0xa5, 0xf8, 0xac, 0xba, 0x27, 0xd1, 0x87, 0x08, 0x6b,
+ 0xe6, 0x1b, 0xfc, 0xba, 0xff, 0x7e, 0xc9, 0xaf, 0x76, 0x28, 0xe8, 0x93, 0x2c, 0xbf, 0x6f, 0xed,
+ 0xf3, 0x6f, 0x21, 0xc1, 0xe7, 0x56, 0xf5, 0x15, 0x56, 0xa8, 0xa1, 0xfd, 0xb7, 0xb3, 0x8c, 0x3d,
+ 0x1f, 0xf3, 0x80, 0xea, 0x79, 0xff, 0x0c, 0xc6, 0xb6, 0x59, 0xa0, 0x3f, 0x13, 0xb0, 0x4f, 0xb3,
+ 0x1c, 0xe1, 0x2a, 0xa5, 0x45, 0x02, 0x51, 0xa3, 0x74, 0x15, 0xee, 0xf0, 0xca, 0xba, 0x7d, 0xca,
+ 0xc6, 0x87, 0xd6, 0x12, 0x9b, 0xbb, 0x4b, 0x86, 0xc7, 0xcb, 0x88, 0xc6, 0x10, 0xee, 0x34, 0x51,
+ 0x4b, 0xe9, 0x9d, 0xbb, 0x59, 0x03, 0xff, 0xbf, 0x98, 0x0a, 0xfd, 0xf2, 0xd0, 0x06, 0x0e, 0x51,
+ 0xd1, 0xbb, 0x8b, 0xbd, 0xca, 0x26, 0x4d, 0x05, 0x0e, 0xee, 0x82, 0x0f, 0xf6, 0x38, 0x46, 0x2a,
+ 0x7f, 0xfd, 0x37, 0xe6, 0xcf, 0xaa, 0x6a, 0x84, 0xbc, 0xa7, 0xd2, 0x06, 0x29, 0x49, 0x4c, 0xd2,
+ 0x28, 0xd4, 0x48, 0x09, 0xfc, 0x91, 0x1e, 0xb0, 0x06, 0xf9, 0x5b, 0xbe, 0xd9, 0xb8, 0x01, 0xec,
+ 0x3a, 0xc3, 0xa2, 0x1d, 0x89, 0x0b, 0x22, 0xf8, 0xf0, 0x8e, 0x95, 0xe0, 0x3d, 0x5b, 0x80, 0x38,
+ 0x21, 0x80, 0x10, 0x04, 0x54, 0xaa, 0xa5, 0x81, 0x8b, 0x47, 0x73, 0x54, 0xcb, 0x06, 0xa9, 0x01,
+ 0xf5, 0x57, 0x72, 0xdd, 0x5f, 0x9f, 0xe1, 0x14, 0x1f, 0xc1, 0x3b, 0xda, 0xd1, 0xad, 0xc7, 0x9c,
+ 0x70, 0x18, 0xfd, 0x07, 0x7e, 0x37, 0x0d, 0xb4, 0x30, 0xff, 0x6d, 0x4b, 0x99, 0xdb, 0x3f, 0x23,
+ 0xd7, 0x15, 0x3d, 0x4c, 0x14, 0x63, 0x98, 0xf5, 0x03, 0x63, 0x4e, 0x63, 0x15, 0x6a, 0xce, 0x7f,
+ 0xfc, 0x4f, 0xa0, 0x72, 0x49, 0x3f, 0xd9, 0xa2, 0xde, 0x65, 0xac, 0x35, 0x88, 0x03, 0x55, 0xf5,
+ 0x27, 0x57, 0x4e, 0x4b, 0xef, 0x44, 0x1d, 0xd1, 0xe9, 0xaf, 0x89, 0x7e, 0x19, 0x2a, 0xc4, 0x48,
+ 0x80, 0x8d, 0x22, 0xd6, 0x4f, 0x79, 0x39, 0x15, 0x00, 0xc0, 0xa8, 0x93, 0x5a, 0x05, 0xce, 0xc8,
+ 0x3f, 0x27, 0x40, 0x09, 0x1f, 0x2a, 0x0c, 0x32, 0x6a, 0x2d, 0xef, 0x51, 0x79, 0x51, 0xf1, 0xfe,
+ 0xdb, 0x2c, 0xb5, 0x2f, 0x4d, 0x94, 0x1d, 0xd2, 0xb8, 0xed, 0x53, 0x58, 0x3e, 0xb4, 0x65, 0xfb,
+ 0xca, 0x9f, 0xfe, 0x5c, 0x68, 0x20, 0x58, 0xd9, 0xfd, 0xc7, 0x4d, 0x0f, 0x03, 0x37, 0x61, 0xaf,
+ 0x2b, 0x90, 0xe1, 0xfc, 0x15, 0xa6, 0xf1, 0xac, 0x5d, 0xad, 0xb4, 0xbe, 0xf6, 0xb4, 0x7b, 0x42,
+ 0xe6, 0x17, 0xe9, 0x79, 0xde, 0x65, 0x50, 0x70, 0x6b, 0x46, 0x24, 0x73, 0x4d, 0xa3, 0x54, 0x3a,
+ 0x21, 0xc2, 0x1c, 0x80, 0x1d, 0xf6, 0xd4, 0x0e, 0x7a, 0xa5, 0x4e, 0x97, 0xc1, 0x15, 0x37, 0x02,
+ 0xea, 0x55, 0xff, 0xad, 0x59, 0x95, 0xa0, 0x24, 0xd0, 0x58, 0x59, 0xf7, 0x9a, 0x4e, 0x2b, 0x78,
+ 0x89, 0x91, 0x9c, 0x67, 0x14, 0xe4, 0x24, 0xc3, 0xb8, 0xd3, 0x7d, 0x06, 0xaf, 0x46, 0x8f, 0xe7,
+ 0x07, 0x88, 0xf0, 0x4c, 0xf4, 0xb8, 0x3a, 0x2a, 0x9c, 0x89, 0x2f, 0x7c, 0x32, 0xfd, 0x52, 0x55,
+ 0xd8, 0xe1, 0x58, 0x46, 0xe5, 0xc3, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x82, 0x02, 0x00, 0x08,
+ 0x79, 0xcf, 0xb2, 0xc9, 0x57, 0xfa, 0x06, 0xce, 0x13, 0xda, 0x88, 0x1f, 0xd7, 0xdc, 0x53, 0x59,
+ 0xb8, 0x92, 0x90, 0x8c, 0xa1, 0xc3, 0xcd, 0x1d, 0xd5, 0x26, 0xdf, 0x04, 0x5b, 0x47, 0xd5, 0xdb,
+ 0x0b, 0xdf, 0x3d, 0xca, 0x2d, 0xc8, 0x39, 0x12, 0xcd, 0xd3, 0x50, 0xd2, 0x96, 0x13, 0x9c, 0xb2,
+ 0x45, 0xd1, 0x7d, 0x84, 0xfd, 0x97, 0x07, 0xef, 0xb2, 0xea, 0x46, 0xb2, 0x91, 0x64, 0xb5, 0xd6,
+ 0x47, 0xec, 0x04, 0x40, 0xbd, 0x7c, 0xd5, 0x69, 0x5a, 0xc1, 0xf2, 0xc6, 0x76, 0xf7, 0x65, 0x06,
+ 0xc2, 0xc3, 0xae, 0xd6, 0xf9, 0x31, 0x44, 0xa2, 0xf0, 0x96, 0x04, 0xd8, 0xfd, 0xe9, 0xaa, 0xb8,
+ 0x8b, 0x31, 0x9a, 0x30, 0x63, 0x57, 0xf8, 0x12, 0xae, 0xb9, 0xa8, 0xc7, 0x14, 0x03, 0xae, 0xf0,
+ 0x22, 0x5e, 0x03, 0xae, 0xff, 0x8e, 0x36, 0x85, 0x79, 0x59, 0x82, 0xa8, 0xde, 0x64, 0xa6, 0x61,
+ 0x5d, 0xc5, 0x0c, 0x43, 0x6d, 0xf8, 0x2d, 0x5e, 0xaf, 0xe7, 0x83, 0x5c, 0x93, 0x8a, 0x03, 0xe4,
+ 0x3b, 0xd6, 0x73, 0x62, 0x33, 0x70, 0xf9, 0xa1, 0x4b, 0x05, 0x5f, 0x19, 0xf8, 0x0e, 0xbe, 0x3a,
+ 0xa9, 0x68, 0x5c, 0xa9, 0xdf, 0x80, 0x64, 0x0c, 0x25, 0xd9, 0x44, 0xa8, 0x65, 0xdb, 0xab, 0xeb,
+ 0xc4, 0xe7, 0xdb, 0xda, 0xc9, 0x44, 0xe0, 0x97, 0x82, 0x06, 0x80, 0x64, 0x11, 0x34, 0xcd, 0x90,
+ 0x4b, 0xe8, 0x81, 0x6d, 0xdd, 0x15, 0x77, 0x8e, 0x55, 0x77, 0xba, 0xe5, 0x2f, 0x35, 0x1a, 0x23,
+ 0x67, 0x68, 0xd8, 0x27, 0xeb, 0xef, 0xca, 0xd1, 0xc3, 0x25, 0x09, 0xd8, 0x86, 0xcd, 0x6f, 0xe5,
+ 0x00, 0x2d, 0x47, 0xc9, 0xf2, 0x6c, 0x4d, 0xb9, 0xa2, 0x86, 0xfe, 0xae, 0x95, 0x1f, 0xf4, 0x71,
+ 0x83, 0xac, 0x6e, 0x8a, 0x09, 0xe3, 0x5c, 0x07, 0xc4, 0x3c, 0x3a, 0x50, 0x0f, 0xb6, 0x90, 0x21,
+ 0x25, 0xdf, 0x2d, 0x61, 0x89, 0xb6, 0x3c, 0xb3, 0xc3, 0xc0, 0xd9, 0x2e, 0x73, 0x76, 0xf6, 0x46,
+ 0x46, 0xf8, 0x01, 0xbd, 0x0b, 0x94, 0x80, 0xf6, 0x94, 0x5b, 0x17, 0x9e, 0xbc, 0xdb, 0x9d, 0xfd,
+ 0xc2, 0x3e, 0x56, 0xa1, 0xa0, 0x1a, 0xed, 0x82, 0xcb, 0xb0, 0xb3, 0xd4, 0x58, 0xb4, 0x91, 0x8e,
+ 0xd9, 0x84, 0xb8, 0x94, 0xc2, 0x86, 0x42, 0xa6, 0x86, 0xf2, 0x9c, 0x1d, 0xc7, 0xc9, 0xed, 0x6d,
+ 0x0d, 0x8d, 0x98, 0x08, 0xf3, 0x52, 0xe7, 0x7f, 0xfe, 0xe0, 0x9f, 0xdf, 0x43, 0x8e, 0xbc, 0x3f,
+ 0x93, 0x41, 0x44, 0x7b, 0x26, 0x0e, 0x2e, 0xc1, 0xc2, 0x4e, 0xc7, 0xb4, 0x5c, 0xc5, 0x55, 0xae,
+ 0xbb, 0xb4, 0x2f, 0xd5, 0x1f, 0x34, 0xce, 0x94, 0x66, 0x12, 0x41, 0x8a, 0x15, 0x8b, 0xcb, 0xdd,
+ 0x00, 0xf9, 0xa0, 0x55, 0xa6, 0x8b, 0xe9, 0x13, 0x14, 0xc6, 0x0b, 0x98, 0x8f, 0xff, 0x2f, 0x9a,
+ 0xe8, 0x78, 0x68, 0x94, 0xeb, 0x53, 0xeb, 0x4b, 0xed, 0x5a, 0x6d, 0x1c, 0xe2, 0xe5, 0xc7, 0xde,
+ 0x13, 0xa9, 0xaf, 0xa3, 0xc7, 0x54, 0x26, 0xa0, 0x3f, 0x44, 0xdd, 0x9f, 0x14, 0xe2, 0xc4, 0x9a,
+ 0xf5, 0x30, 0xea, 0x2e, 0x75, 0xc6, 0xd3, 0xf8, 0x5e, 0x9b, 0x02, 0x0d, 0xc8, 0x81, 0x6c, 0x13,
+ 0x3b, 0x9c, 0x89, 0x54, 0x52, 0x41, 0x62, 0xad, 0xa0, 0x52, 0xaa, 0x2c, 0xc4, 0x2c, 0x63, 0x58,
+ 0xf6, 0xb1, 0xfa, 0xdc, 0x77, 0xed, 0x3e, 0x8c, 0x12, 0x94, 0x09, 0x57, 0x18, 0xb3, 0x04, 0x53,
+ 0x3c, 0xad, 0xa8, 0x45, 0x4b, 0x19, 0xa0, 0xcb, 0x8f, 0x6f, 0x5d, 0x2d, 0xcc, 0xe1, 0xfe, 0x4a,
+ 0xe6, 0x20, 0xed, 0x9a, 0x76, 0x4f, 0x17, 0xfd, 0xed, 0x1e, 0x6e, 0x41, 0x21, 0x43, 0xe4, 0xe9,
+ 0x47, 0x01, 0x1f, 0x76, 0x68, 0x4d, 0xbb, 0xd3, 0xba, 0x68, 0x34, 0x06, 0x6a, 0xf5, 0xe9, 0x02,
+ 0x82, 0x01, 0x01, 0x00, 0xdb, 0xc5, 0x0d, 0x95, 0x88, 0x5f, 0x2c, 0xa9, 0x7c, 0xb7, 0x06, 0xd8,
+ 0x20, 0x60, 0x72, 0x80, 0xec, 0xae, 0xf4, 0xdc, 0x48, 0x5d, 0x9d, 0x6d, 0x2a, 0x54, 0x36, 0xe1,
+ 0x32, 0x25, 0x10, 0x4d, 0x56, 0x0f, 0x51, 0xe9, 0xbc, 0x95, 0x3f, 0x38, 0x43, 0xed, 0xc6, 0x6a,
+ 0x16, 0xb0, 0x06, 0x90, 0x85, 0x51, 0x07, 0xea, 0x7b, 0x1d, 0x64, 0x12, 0x39, 0x7e, 0x32, 0x92,
+ 0x3e, 0xcc, 0x97, 0x91, 0x02, 0x6c, 0xf8, 0x95, 0xc7, 0x39, 0xa1, 0x5c, 0xd3, 0x62, 0xcc, 0x29,
+ 0x83, 0x27, 0xe6, 0x36, 0xba, 0xf6, 0x62, 0x80, 0xf6, 0x15, 0xda, 0x0f, 0xc4, 0x47, 0x81, 0x44,
+ 0x96, 0xba, 0xec, 0x37, 0x8c, 0x95, 0xb2, 0x30, 0x12, 0xc0, 0x4d, 0x62, 0x88, 0x5e, 0x7d, 0x13,
+ 0x6d, 0x83, 0x14, 0x1e, 0xd7, 0xc2, 0xeb, 0x9d, 0xbd, 0xa4, 0x70, 0x78, 0xf3, 0xb1, 0x6b, 0x6c,
+ 0xe9, 0x37, 0x63, 0x47, 0x6f, 0xed, 0xea, 0xd6, 0x32, 0x80, 0x71, 0xa2, 0x7f, 0x38, 0x67, 0xb4,
+ 0x1f, 0xf0, 0x7f, 0xb6, 0xb7, 0x56, 0xe7, 0x24, 0xaa, 0x2f, 0xb9, 0x93, 0xb8, 0xab, 0x2a, 0xec,
+ 0xc4, 0x19, 0x9a, 0xea, 0x5d, 0x01, 0x1e, 0xdd, 0x5c, 0x05, 0x37, 0x12, 0x04, 0x32, 0xb2, 0x5d,
+ 0x16, 0xdc, 0x88, 0x0c, 0xfe, 0x1e, 0xf6, 0x9f, 0x63, 0xbf, 0xb2, 0x00, 0x6b, 0x99, 0xbe, 0x42,
+ 0x96, 0x41, 0xe0, 0x3d, 0xd1, 0x35, 0x6a, 0xf1, 0x81, 0x09, 0xd6, 0x4c, 0xdd, 0xc0, 0x20, 0x6e,
+ 0x0d, 0xcb, 0x30, 0x11, 0x73, 0x11, 0x82, 0x5e, 0x1b, 0x9a, 0xa0, 0x57, 0xcd, 0xd7, 0xab, 0xa2,
+ 0xe9, 0xed, 0xe7, 0x26, 0x8d, 0x30, 0x09, 0xe7, 0x5a, 0xa1, 0xd0, 0x62, 0xac, 0x7f, 0x15, 0xd1,
+ 0x9a, 0x0a, 0x97, 0x5d, 0x8a, 0xc0, 0x47, 0xa4, 0xa8, 0x8b, 0x26, 0x75, 0x35, 0x5d, 0xa9, 0xf1,
+ 0x1a, 0x61, 0xdc, 0xbf, 0x02, 0x82, 0x01, 0x01, 0x00, 0xc8, 0x63, 0x6e, 0x9c, 0x88, 0xcd, 0x33,
+ 0xef, 0x72, 0x80, 0x00, 0x6b, 0x8e, 0xb0, 0xd2, 0xa9, 0x5c, 0x78, 0xf7, 0x25, 0x8f, 0xba, 0x49,
+ 0x60, 0xa3, 0x33, 0xf4, 0x16, 0x1c, 0x81, 0xad, 0x82, 0x39, 0xa5, 0xa8, 0x12, 0xc2, 0x7e, 0x05,
+ 0x33, 0x9c, 0xd9, 0xa9, 0xa4, 0x02, 0xf8, 0x43, 0xdf, 0xa6, 0x39, 0xdb, 0xc0, 0x60, 0xdc, 0x53,
+ 0x76, 0x7c, 0xef, 0xf0, 0x58, 0x44, 0x3d, 0xc7, 0x77, 0xe6, 0x6f, 0x2a, 0xbd, 0x89, 0x8b, 0x40,
+ 0xe3, 0x02, 0x80, 0x8c, 0x46, 0x24, 0x4c, 0x63, 0xc4, 0x06, 0xb1, 0x48, 0x92, 0x0a, 0x86, 0x58,
+ 0xe3, 0x08, 0x2a, 0x2e, 0x4b, 0xdd, 0xa6, 0x64, 0x68, 0xe9, 0xac, 0x07, 0x2b, 0x3d, 0x2f, 0x42,
+ 0x68, 0x5a, 0x42, 0xdb, 0xb7, 0x5a, 0x67, 0xc4, 0x5f, 0x19, 0xba, 0xff, 0xed, 0x17, 0x0b, 0xf8,
+ 0x26, 0x36, 0xd7, 0x9d, 0x3a, 0x3d, 0x0e, 0x5f, 0x31, 0x29, 0x3f, 0xf4, 0x24, 0x2f, 0x0a, 0xa2,
+ 0x21, 0x0e, 0x6e, 0x86, 0xf5, 0x1e, 0xb7, 0xce, 0x81, 0x95, 0xd0, 0xcd, 0x97, 0xd1, 0x2c, 0xd4,
+ 0x9f, 0x95, 0xd6, 0x7c, 0x68, 0x80, 0x4f, 0xe6, 0x5c, 0xab, 0xfe, 0x6b, 0xf9, 0x8b, 0x12, 0x99,
+ 0xad, 0xc9, 0x64, 0x21, 0x99, 0xef, 0xfb, 0xd3, 0x16, 0x8b, 0x95, 0x93, 0xa7, 0xb7, 0x24, 0x36,
+ 0x00, 0xb2, 0xa2, 0xbe, 0x79, 0x4e, 0x66, 0xbd, 0xe1, 0x2d, 0xd0, 0xa9, 0x76, 0xe1, 0x13, 0x0d,
+ 0xb5, 0x4b, 0xc5, 0xe9, 0x63, 0x23, 0x45, 0xcf, 0x5f, 0x0d, 0x5c, 0xce, 0x93, 0x7f, 0xa9, 0x68,
+ 0xf6, 0xfc, 0x2c, 0x54, 0x40, 0x43, 0xca, 0x9d, 0xfd, 0x43, 0x81, 0x4d, 0xbe, 0x98, 0x87, 0xca,
+ 0x2c, 0x82, 0x32, 0xb7, 0xcf, 0xa1, 0xc5, 0xf8, 0x55, 0xea, 0x2b, 0x6d, 0xfc, 0xe7, 0x5d, 0xcf,
+ 0xde, 0xf8, 0x15, 0xc2, 0xc3, 0xa2, 0xe6, 0x83, 0xfd, 0x02, 0x82, 0x01, 0x00, 0x2c, 0x83, 0x3a,
+ 0xff, 0x20, 0x81, 0xf6, 0x6f, 0xd5, 0xbc, 0xd4, 0x7c, 0x0e, 0x02, 0xba, 0xee, 0x76, 0x01, 0xf1,
+ 0xc2, 0x74, 0x3d, 0xd1, 0xd6, 0xfc, 0x8d, 0xd6, 0x17, 0xc2, 0xaa, 0x53, 0x24, 0xf6, 0xdb, 0x5f,
+ 0x81, 0xf2, 0x1a, 0x60, 0x95, 0xaa, 0xdc, 0x8c, 0x25, 0x8c, 0xb6, 0xd6, 0x7d, 0x8b, 0x23, 0x20,
+ 0x71, 0x53, 0xc2, 0x5e, 0x34, 0x7a, 0xc4, 0x9e, 0xc5, 0x94, 0x46, 0xa8, 0x24, 0x4c, 0xd3, 0x79,
+ 0x7e, 0x0c, 0xbe, 0x15, 0x7a, 0xd1, 0xad, 0xdf, 0x20, 0x41, 0x5a, 0x61, 0x7c, 0x90, 0x5d, 0xbb,
+ 0x11, 0xd7, 0xc6, 0x11, 0x46, 0xc4, 0x40, 0x9f, 0x64, 0x1f, 0x0b, 0x79, 0x30, 0xbf, 0x1e, 0xca,
+ 0xda, 0x85, 0xd1, 0xc1, 0x5a, 0xc5, 0xb8, 0x2d, 0xa9, 0x33, 0xb3, 0x2a, 0xee, 0x1c, 0x51, 0x74,
+ 0x9b, 0x9c, 0x7f, 0xa3, 0xf0, 0x3b, 0x9b, 0xa1, 0xe0, 0x8b, 0x54, 0x16, 0x9d, 0xaf, 0x84, 0x06,
+ 0xde, 0x9f, 0x97, 0xf8, 0x6c, 0x2b, 0x4c, 0x67, 0x64, 0xca, 0x5b, 0x51, 0xe2, 0xd6, 0x3b, 0x99,
+ 0xd1, 0x89, 0x4e, 0xe5, 0x4d, 0x90, 0x47, 0xcb, 0x07, 0xed, 0xa8, 0x2a, 0x02, 0x72, 0x17, 0xfa,
+ 0x02, 0x67, 0xd2, 0xfe, 0x96, 0x7d, 0x97, 0x2f, 0x1d, 0x3f, 0xb6, 0x27, 0x30, 0x4a, 0x80, 0x46,
+ 0xff, 0x7d, 0x9a, 0xa4, 0x19, 0x05, 0xb2, 0x3c, 0x21, 0x0c, 0x82, 0x07, 0x43, 0x3e, 0x0e, 0x8d,
+ 0xbc, 0xa0, 0xa0, 0x37, 0x71, 0x96, 0x30, 0x85, 0xe1, 0x04, 0x96, 0x35, 0x04, 0x33, 0xc4, 0x46,
+ 0x1d, 0x7d, 0x85, 0xd2, 0x18, 0x36, 0xaf, 0x0a, 0x2a, 0x93, 0x2b, 0x06, 0x78, 0x7e, 0x7c, 0x4e,
+ 0x65, 0x37, 0xac, 0x32, 0xa2, 0xe9, 0xc1, 0x4b, 0xd0, 0x0a, 0x5d, 0x3e, 0xcf, 0x49, 0x7d, 0x2c,
+ 0x85, 0xa3, 0x45, 0x9b, 0xe2, 0x7d, 0x8e, 0x9d, 0x0f, 0x22, 0x82, 0xd3, 0xcd, 0x02, 0x82, 0x01,
+ 0x00, 0x46, 0xe8, 0x18, 0x3d, 0xbf, 0x92, 0x8c, 0xec, 0x0f, 0xa2, 0x07, 0x84, 0x07, 0xab, 0xbd,
+ 0xff, 0x3b, 0xbf, 0x7a, 0x04, 0x8a, 0x85, 0x2a, 0x6d, 0xcd, 0x92, 0x16, 0xae, 0xb4, 0x4b, 0x96,
+ 0xaf, 0xdb, 0xe2, 0x28, 0x44, 0xeb, 0x19, 0x58, 0x91, 0xd8, 0xd0, 0x94, 0x5c, 0x7a, 0xc8, 0x8a,
+ 0x8b, 0xda, 0xef, 0xe2, 0x38, 0x82, 0x8d, 0xb3, 0xe2, 0xdb, 0x76, 0xb3, 0x9f, 0x28, 0x16, 0x8c,
+ 0x3c, 0x7b, 0x07, 0x9f, 0x22, 0x0e, 0x47, 0x7e, 0x20, 0x55, 0xc4, 0x52, 0xde, 0x86, 0xfd, 0x98,
+ 0xd7, 0xc6, 0x5e, 0x79, 0x05, 0x64, 0x40, 0x01, 0xb7, 0xe4, 0x2d, 0xb8, 0xd0, 0x13, 0x90, 0x4b,
+ 0x3b, 0x6c, 0x63, 0xf8, 0xed, 0x6d, 0xeb, 0x09, 0x1e, 0x8f, 0xc1, 0xd4, 0xa9, 0x5e, 0x8e, 0x15,
+ 0x48, 0x69, 0x7c, 0x68, 0x0e, 0xe6, 0xf6, 0xcf, 0x4a, 0x06, 0x61, 0xe9, 0x3a, 0xb0, 0x5c, 0x23,
+ 0x86, 0xeb, 0xc7, 0xbb, 0x86, 0x0a, 0x37, 0x43, 0x03, 0x5b, 0x6d, 0xf4, 0xc7, 0x4b, 0xa5, 0x52,
+ 0xa7, 0x3b, 0xf1, 0xf4, 0xad, 0xe1, 0xd0, 0x71, 0x34, 0x3e, 0xfa, 0xf4, 0x6e, 0xad, 0xe8, 0x97,
+ 0xe4, 0xf6, 0xdf, 0x42, 0x29, 0xbc, 0xf2, 0x49, 0xfa, 0xda, 0xa6, 0x59, 0xd5, 0x74, 0xbb, 0xb1,
+ 0x07, 0xeb, 0x40, 0x74, 0x4d, 0x06, 0x5b, 0x03, 0xd8, 0xdf, 0x5d, 0x02, 0xf5, 0x3d, 0xae, 0xd1,
+ 0x45, 0x9a, 0xc6, 0x99, 0x10, 0x7d, 0xb8, 0x16, 0x43, 0xae, 0x9a, 0x4b, 0x69, 0x4f, 0x13, 0xe6,
+ 0xbb, 0x05, 0xa9, 0x6f, 0x57, 0x75, 0xf6, 0xe6, 0x33, 0x6f, 0x2b, 0xe8, 0x6c, 0x0d, 0x10, 0xe7,
+ 0x32, 0xb4, 0xee, 0x4e, 0x2a, 0x41, 0x22, 0xdb, 0x81, 0x40, 0x58, 0xdd, 0xfd, 0xd4, 0x8a, 0x8e,
+ 0xc3, 0x27, 0xe7, 0x52, 0x36, 0x09, 0x50, 0x82, 0xbb, 0xad, 0x21, 0x56, 0x17, 0x8f, 0xce, 0xed,
+ 0xa9, 0x02, 0x82, 0x01, 0x00, 0x78, 0x0c, 0x4f, 0x07, 0x4a, 0x2d, 0x91, 0xa3, 0xfd, 0x1c, 0xb3,
+ 0xb5, 0xcb, 0x11, 0x1c, 0x04, 0xc9, 0x7f, 0xbf, 0x86, 0x7d, 0x5e, 0x2e, 0xf4, 0x2c, 0xad, 0x9e,
+ 0x35, 0x74, 0x2e, 0x55, 0xc6, 0xaf, 0x1c, 0x76, 0xb9, 0x3d, 0x44, 0xe7, 0xdd, 0x5f, 0x32, 0x0e,
+ 0xce, 0x4b, 0x43, 0x6d, 0x7c, 0x10, 0xd3, 0x01, 0x6f, 0x22, 0xa5, 0xb3, 0xaf, 0x40, 0x80, 0x57,
+ 0xf0, 0x96, 0x7c, 0xb8, 0xae, 0xbc, 0x35, 0x8b, 0xa1, 0x59, 0xdc, 0xc4, 0xf7, 0x8b, 0xda, 0xe7,
+ 0x91, 0x9b, 0xa7, 0x54, 0x61, 0xad, 0x9d, 0x0a, 0x68, 0xc3, 0xc5, 0x10, 0x9e, 0x39, 0x16, 0x9c,
+ 0x3b, 0xf3, 0x3a, 0xdf, 0xb9, 0x72, 0xed, 0x4f, 0x56, 0x1e, 0x5c, 0x8b, 0x35, 0x0b, 0xa6, 0x08,
+ 0x83, 0x96, 0xd4, 0x6d, 0x3f, 0xe3, 0x6a, 0x25, 0xa2, 0xe8, 0x0f, 0xce, 0x0c, 0x26, 0x85, 0x02,
+ 0xba, 0xe1, 0xfc, 0xdc, 0xa0, 0x18, 0xfc, 0x7c, 0x87, 0xa6, 0x6b, 0xf5, 0x92, 0xa8, 0x83, 0x5f,
+ 0xbf, 0xa5, 0xe3, 0x0e, 0x21, 0x10, 0xbf, 0x44, 0x49, 0xc7, 0x62, 0x36, 0x46, 0x0b, 0x97, 0xdc,
+ 0xd4, 0x30, 0x02, 0xd4, 0x04, 0xcb, 0x8f, 0xfe, 0xde, 0x76, 0x44, 0x9f, 0xd6, 0x55, 0x8e, 0x45,
+ 0xaa, 0x10, 0xd4, 0xa0, 0x2c, 0x49, 0x72, 0x6e, 0x5f, 0xf1, 0xd5, 0x0a, 0x1e, 0xba, 0xc6, 0xb4,
+ 0xb6, 0x93, 0x9d, 0x6a, 0xe4, 0xaa, 0x61, 0x3a, 0xca, 0x77, 0xa4, 0x16, 0xa2, 0x4f, 0x2f, 0xad,
+ 0xe6, 0xb5, 0x64, 0x93, 0x37, 0xf9, 0xdc, 0x28, 0x70, 0x54, 0x0b, 0xb2, 0x15, 0x96, 0x60, 0xbc,
+ 0x86, 0x16, 0xfc, 0x5c, 0xf0, 0x75, 0xc2, 0x6f, 0x51, 0x4f, 0x71, 0xbb, 0x77, 0xed, 0xe8, 0x2b,
+ 0xf2, 0xe3, 0x7d, 0x02, 0xa8, 0xfe, 0x26, 0x8a, 0x40, 0x63, 0x8f, 0x14, 0x84, 0xd2, 0x00, 0x70,
+ 0x6a, 0xf3, 0xde, 0xf8, 0x66};
+constexpr const unsigned int rsa_key_4k_len = sizeof(rsa_key_4k);
+
+constexpr const unsigned char rsa_key_2k[] = {
+ 0x30, 0x82, 0x04, 0xbd, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7,
+ 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x04, 0x82, 0x04, 0xa7, 0x30, 0x82, 0x04, 0xa3, 0x02, 0x01,
+ 0x00, 0x02, 0x82, 0x01, 0x01, 0x00, 0x9d, 0xd0, 0xca, 0x63, 0xaa, 0x59, 0x73, 0x13, 0x55, 0x53,
+ 0xdc, 0x5d, 0x4d, 0xf9, 0x5c, 0x4f, 0x31, 0xe3, 0x34, 0x45, 0xbf, 0xc5, 0x8b, 0x43, 0x8f, 0x10,
+ 0xb6, 0x22, 0xaf, 0xe0, 0x12, 0xff, 0xf5, 0xa8, 0x71, 0x0a, 0xe9, 0x67, 0x8d, 0x05, 0xd2, 0x69,
+ 0x35, 0x32, 0x8d, 0x94, 0x9f, 0x52, 0x63, 0x4e, 0x54, 0x0b, 0x29, 0x3d, 0x7b, 0x5e, 0xc0, 0x98,
+ 0x94, 0x23, 0x43, 0x46, 0x39, 0xce, 0xc2, 0xe1, 0x2b, 0xcd, 0x00, 0x6c, 0x9d, 0xe7, 0x81, 0xb9,
+ 0x6b, 0x97, 0xea, 0x3a, 0xe7, 0x09, 0x06, 0x6b, 0xf3, 0xe1, 0x7a, 0x03, 0xcd, 0x51, 0x2c, 0x16,
+ 0x19, 0x6c, 0xdf, 0x2c, 0xd5, 0x96, 0x92, 0x19, 0xaa, 0x91, 0xe3, 0xa5, 0xc7, 0xe1, 0x0b, 0x07,
+ 0xb6, 0x84, 0xd3, 0xa7, 0x1f, 0x0d, 0x22, 0xb9, 0xc1, 0x76, 0x16, 0x81, 0x53, 0x50, 0x7d, 0x54,
+ 0x2a, 0x26, 0x9e, 0xfa, 0xb1, 0xb7, 0x83, 0x05, 0x24, 0x81, 0xea, 0x5a, 0x6c, 0xb5, 0x92, 0x69,
+ 0x63, 0x35, 0xfa, 0x04, 0xae, 0xee, 0xc5, 0xdb, 0xf0, 0x9b, 0xfe, 0xe6, 0xc4, 0x73, 0x18, 0x3d,
+ 0xd3, 0x01, 0xaf, 0x03, 0x43, 0x95, 0xca, 0xab, 0x43, 0x04, 0x64, 0x49, 0xe7, 0x47, 0xf8, 0x97,
+ 0xe5, 0x63, 0xd1, 0x3d, 0x21, 0x9b, 0xd5, 0x13, 0x2a, 0xb0, 0xf1, 0xf9, 0xff, 0xd2, 0xb7, 0x12,
+ 0xa8, 0xa0, 0x20, 0x38, 0xde, 0x41, 0x8e, 0xa3, 0xc7, 0xce, 0x5b, 0x9c, 0x30, 0x1a, 0xaf, 0x13,
+ 0x11, 0xd1, 0xd0, 0x71, 0x7f, 0x1e, 0x47, 0xa8, 0x32, 0x3b, 0x4a, 0x36, 0xa8, 0x6d, 0x8c, 0xd7,
+ 0x5f, 0x93, 0x95, 0xa8, 0xe0, 0xfa, 0x59, 0xb5, 0x6c, 0x1f, 0xfb, 0x01, 0x64, 0xf5, 0x5d, 0xf5,
+ 0x17, 0x75, 0x53, 0xfb, 0xc3, 0x3f, 0xd7, 0xc5, 0x45, 0x53, 0xb4, 0xa2, 0xcf, 0xa5, 0x71, 0xf0,
+ 0x7a, 0x8b, 0x66, 0x09, 0xfa, 0x4d, 0x02, 0x03, 0x01, 0x00, 0x01, 0x02, 0x82, 0x01, 0x00, 0x03,
+ 0xe9, 0x06, 0x4d, 0x6a, 0xe2, 0xa0, 0x2b, 0x24, 0xa1, 0x99, 0x23, 0xb2, 0x08, 0xbc, 0x2f, 0x4a,
+ 0xd2, 0xa6, 0x30, 0x6b, 0x91, 0xd6, 0xf8, 0xb1, 0x0f, 0x9e, 0x71, 0x04, 0x94, 0xe8, 0xe8, 0xf1,
+ 0x00, 0x1f, 0xf0, 0xea, 0x70, 0x97, 0x8f, 0x6d, 0xde, 0x65, 0x24, 0x35, 0x5a, 0xd9, 0xdf, 0x13,
+ 0x8e, 0x7f, 0x74, 0x44, 0x02, 0x28, 0x7a, 0x39, 0x61, 0x19, 0x1b, 0xe3, 0x3b, 0xac, 0x62, 0x01,
+ 0x9f, 0xcc, 0xfd, 0xdd, 0x85, 0x53, 0x72, 0x46, 0xdb, 0x69, 0x1d, 0x90, 0xa9, 0xad, 0xf6, 0x22,
+ 0x1e, 0x6e, 0xf8, 0x06, 0xc0, 0x6d, 0x08, 0x16, 0x5a, 0x0e, 0x7e, 0x37, 0xec, 0xbc, 0xa1, 0x68,
+ 0x49, 0xa7, 0x84, 0x1e, 0xdf, 0xb4, 0x30, 0xa6, 0x1d, 0x26, 0x4f, 0x6b, 0x39, 0x1d, 0xad, 0x58,
+ 0x7a, 0x16, 0xca, 0x38, 0xc0, 0xdd, 0x3c, 0xc6, 0x26, 0x32, 0xe1, 0xd5, 0xc2, 0xeb, 0x6a, 0xa6,
+ 0x70, 0x1f, 0x50, 0x82, 0x2d, 0xf6, 0x09, 0x27, 0x56, 0x4f, 0xf1, 0xed, 0x62, 0x60, 0xe5, 0x55,
+ 0x0f, 0x8d, 0xbe, 0xd7, 0x5a, 0xb7, 0x7c, 0x57, 0x25, 0xe5, 0xa3, 0x46, 0xb6, 0x97, 0xe0, 0x87,
+ 0x43, 0x04, 0x46, 0xd8, 0x4f, 0xbe, 0x80, 0x75, 0x40, 0x48, 0x2d, 0xc0, 0x57, 0xf8, 0x76, 0xbd,
+ 0xd8, 0x14, 0xa0, 0x7b, 0x39, 0x4f, 0xcf, 0xdc, 0x34, 0x5b, 0x1e, 0x91, 0xef, 0xa7, 0xc8, 0x82,
+ 0x2c, 0xe8, 0xe4, 0x01, 0x2b, 0xa6, 0x92, 0x4e, 0x0b, 0xd1, 0x98, 0xdc, 0x45, 0x46, 0x3f, 0x89,
+ 0xb1, 0x01, 0xb5, 0xa7, 0xeb, 0x71, 0x2f, 0x09, 0x10, 0x3c, 0x71, 0x14, 0xf3, 0x86, 0x18, 0xa8,
+ 0x5a, 0x30, 0xef, 0xfe, 0x87, 0x65, 0xb1, 0xaf, 0x9c, 0x8d, 0x3e, 0xc8, 0x8d, 0x72, 0xf5, 0x16,
+ 0xcb, 0x3a, 0xb8, 0xb1, 0x18, 0xa5, 0x43, 0x1d, 0x24, 0xa1, 0x1c, 0x2d, 0x2d, 0x87, 0xc1, 0x02,
+ 0x81, 0x81, 0x00, 0xde, 0x87, 0xff, 0x74, 0x9d, 0x86, 0x9b, 0x0b, 0x18, 0xb7, 0xa4, 0x50, 0xda,
+ 0x2d, 0x27, 0xff, 0x0e, 0x4d, 0xae, 0x40, 0x21, 0x92, 0x0b, 0x1d, 0x8b, 0xdd, 0x81, 0xdc, 0x40,
+ 0x1c, 0xed, 0x7d, 0x39, 0xd6, 0x1d, 0xdd, 0x88, 0xd0, 0x92, 0xee, 0x74, 0xca, 0x96, 0xa7, 0x6a,
+ 0x58, 0xd3, 0xc6, 0xf4, 0x3e, 0x93, 0x43, 0x54, 0x07, 0x72, 0x3a, 0x8d, 0x3b, 0x09, 0xe0, 0x16,
+ 0x16, 0x71, 0xf1, 0xae, 0xc4, 0xc0, 0x46, 0xce, 0x40, 0x6d, 0xba, 0xf0, 0x4d, 0x1d, 0x04, 0x9a,
+ 0x32, 0x31, 0xe1, 0x07, 0x9b, 0x90, 0x7c, 0xc2, 0xb2, 0xfc, 0x4e, 0xb9, 0x61, 0xd4, 0xdb, 0x4a,
+ 0xa1, 0xb9, 0x78, 0x46, 0x98, 0xa9, 0xfb, 0x21, 0x60, 0xb8, 0x07, 0x6b, 0x24, 0x4d, 0x4d, 0x35,
+ 0x83, 0x0b, 0x21, 0xac, 0xdf, 0x93, 0x2f, 0xb5, 0xec, 0xe5, 0x99, 0x1a, 0x59, 0xaa, 0xd3, 0xbb,
+ 0x8f, 0xe9, 0xad, 0x02, 0x81, 0x81, 0x00, 0xb5, 0x8d, 0x14, 0x8e, 0x85, 0xd9, 0x7c, 0x9e, 0xfc,
+ 0xa1, 0x1f, 0x1a, 0x84, 0x31, 0x07, 0x71, 0x1a, 0x72, 0x91, 0xc0, 0xc5, 0xac, 0x8f, 0xa7, 0x0f,
+ 0x37, 0x48, 0x51, 0x12, 0xda, 0x0d, 0x30, 0x6d, 0x97, 0x21, 0x20, 0x90, 0x49, 0x4d, 0x2b, 0xc1,
+ 0x89, 0x8e, 0x00, 0x66, 0x18, 0x47, 0xd5, 0x68, 0x62, 0xe7, 0x29, 0xf4, 0x95, 0x59, 0x5b, 0xba,
+ 0x4b, 0xc2, 0x20, 0xda, 0xef, 0x4f, 0x33, 0x0e, 0x99, 0xfe, 0x6c, 0xec, 0xf9, 0xd8, 0x81, 0x3a,
+ 0x46, 0x1a, 0xbd, 0xba, 0xf7, 0xfc, 0xd7, 0x19, 0xf8, 0x2d, 0xd1, 0x81, 0x88, 0xce, 0x55, 0x98,
+ 0xe6, 0xbc, 0x25, 0x67, 0xa6, 0xbe, 0x2b, 0x0f, 0x1d, 0xb6, 0x0d, 0xea, 0xc6, 0xb6, 0x95, 0xee,
+ 0x42, 0x3e, 0x1b, 0xf5, 0x8c, 0xf3, 0x19, 0x8e, 0x59, 0xfc, 0xe1, 0x42, 0x1b, 0x26, 0x20, 0x09,
+ 0x8a, 0x1b, 0x57, 0x8e, 0xe1, 0xa7, 0x21, 0x02, 0x81, 0x80, 0x78, 0xc7, 0x33, 0x7d, 0x25, 0xaa,
+ 0x53, 0x28, 0x38, 0xa8, 0x23, 0x84, 0xc6, 0x85, 0xcf, 0xb9, 0x7d, 0x17, 0xe8, 0x45, 0x62, 0x73,
+ 0x13, 0x99, 0x5b, 0xba, 0x43, 0xab, 0x39, 0x18, 0xfa, 0x45, 0x07, 0x49, 0x11, 0x38, 0x95, 0xf3,
+ 0x2e, 0x6c, 0x41, 0xf3, 0x5a, 0xc5, 0x4e, 0xd1, 0x1b, 0x50, 0x56, 0x6c, 0x48, 0x1d, 0x38, 0xd4,
+ 0x39, 0xc9, 0x51, 0xb2, 0x03, 0x70, 0x1e, 0x4c, 0xdc, 0x57, 0x22, 0x56, 0x23, 0x4d, 0xca, 0xcf,
+ 0xe9, 0x3e, 0x97, 0x02, 0x23, 0x87, 0xc5, 0xf1, 0x0c, 0x65, 0x68, 0x6d, 0xa4, 0x84, 0x32, 0x60,
+ 0x56, 0xd4, 0x9b, 0x85, 0x5f, 0xb4, 0x0d, 0xd3, 0xad, 0x08, 0x7c, 0xb8, 0x8b, 0x39, 0x84, 0x2a,
+ 0x2c, 0x77, 0xca, 0x4d, 0x0f, 0xaf, 0xa2, 0x25, 0x97, 0xbb, 0x15, 0x4a, 0xdb, 0x65, 0xff, 0xc5,
+ 0xad, 0xef, 0xe4, 0xff, 0x59, 0xda, 0x45, 0x68, 0x9c, 0x99, 0x02, 0x81, 0x80, 0x16, 0x45, 0x0a,
+ 0xfb, 0x7c, 0x91, 0xb4, 0x06, 0xb0, 0x88, 0x77, 0x0f, 0x42, 0x9d, 0xdd, 0x02, 0xd3, 0xb2, 0xb0,
+ 0x0c, 0x4c, 0x73, 0x21, 0x5f, 0xe5, 0xae, 0xeb, 0x50, 0xfe, 0x95, 0xfe, 0xbe, 0x2d, 0x03, 0x37,
+ 0xce, 0x0d, 0xc4, 0xe0, 0x11, 0x78, 0xf9, 0x0d, 0x91, 0x20, 0xf4, 0xe3, 0x82, 0xda, 0xfe, 0x1e,
+ 0xca, 0xf7, 0xb4, 0x86, 0x34, 0x89, 0x42, 0x97, 0xba, 0x7e, 0x00, 0x92, 0xdf, 0x79, 0x70, 0x0c,
+ 0x54, 0x82, 0x31, 0x17, 0x8c, 0xaa, 0x80, 0x44, 0xf1, 0x77, 0x08, 0xca, 0x5b, 0xfc, 0x54, 0x84,
+ 0x12, 0x49, 0xe8, 0x65, 0x1e, 0xfc, 0xd5, 0x78, 0xc8, 0xc1, 0xd1, 0x23, 0x4c, 0x96, 0xdb, 0x17,
+ 0x24, 0xd7, 0xe2, 0xae, 0x2c, 0xef, 0xff, 0xf2, 0x2c, 0x6d, 0xcf, 0x6f, 0x56, 0x78, 0x2e, 0xb3,
+ 0xa5, 0x51, 0xfd, 0x90, 0x8c, 0xa7, 0x7e, 0xe8, 0x61, 0xb2, 0x26, 0x1d, 0xe1, 0x02, 0x81, 0x81,
+ 0x00, 0x88, 0xf6, 0x9a, 0xec, 0xab, 0xb6, 0x25, 0x2c, 0x12, 0x4e, 0x90, 0x8f, 0xea, 0xa2, 0x7e,
+ 0x62, 0x41, 0xd7, 0xfd, 0x7c, 0x5d, 0xaa, 0x83, 0xfa, 0xc7, 0x48, 0x51, 0x54, 0xed, 0x72, 0x59,
+ 0x95, 0xc0, 0x61, 0xdc, 0xfa, 0xc6, 0xb8, 0xd5, 0x5f, 0x9a, 0xd0, 0x7e, 0xcb, 0x3f, 0xc4, 0xfd,
+ 0xb4, 0x4d, 0x46, 0x74, 0xc4, 0xf0, 0x45, 0xd4, 0x62, 0xdc, 0x27, 0x37, 0x4a, 0x8a, 0xcf, 0x27,
+ 0x1a, 0x6f, 0x00, 0x50, 0x12, 0x99, 0x8d, 0xd7, 0xa8, 0xdf, 0xf3, 0xa4, 0x61, 0x18, 0x4f, 0xed,
+ 0x1d, 0xac, 0x51, 0xd4, 0x44, 0x2f, 0x73, 0xea, 0xe7, 0xd7, 0xd2, 0x81, 0xe1, 0xe8, 0x4e, 0x0a,
+ 0xeb, 0xbd, 0x40, 0x2b, 0x62, 0xb3, 0x43, 0x60, 0x72, 0x73, 0x31, 0xc0, 0x7a, 0x16, 0x15, 0x83,
+ 0x71, 0x2f, 0x2e, 0xb4, 0x17, 0x43, 0x12, 0x30, 0x08, 0xef, 0x72, 0xdb, 0xc9, 0x50, 0x28, 0x5a,
+ 0xda};
+constexpr const unsigned int rsa_key_2k_len = sizeof(rsa_key_2k);
\ No newline at end of file