/* * WPA Supplicant - Certificate utils * Copyright (c) 2022, Google Inc. All rights reserved. * * This software may be distributed under the terms of the BSD license. * See README for more details. */ #include "certificate_utils.h" #define AT __func__ << ":" << __LINE__ << " " namespace ks2 = aidl::android::system::keystore2; namespace KMV1 = aidl::android::hardware::security::keymint; using aidl::android::hardware::wifi::supplicant::INonStandardCertCallback; namespace { constexpr const int64_t KS2_NAMESPACE_WIFI = 102; constexpr const char kKeystore2ServiceName[] = "android.system.keystore2.IKeystoreService/default"; const std::string keystore2_grant_id_prefix("ks2_keystore-engine_grant_id:"); ks2::KeyDescriptor mkKeyDescriptor(const std::string& alias) { // If the key_id starts with the grant id prefix, we parse the following string as numeric // grant id. We can then use the grant domain without alias to load the designated key. if (::android::base::StartsWith(alias, keystore2_grant_id_prefix)) { std::stringstream s(alias.substr(keystore2_grant_id_prefix.size())); uint64_t tmp; s >> std::hex >> tmp; if (s.fail() || !s.eof()) { wpa_printf(MSG_ERROR, "Couldn't parse grant name: %s", alias.c_str()); } return { .domain = ks2::Domain::GRANT, .nspace = static_cast(tmp), .alias = std::nullopt, .blob = std::nullopt, }; } else { return { .domain = ks2::Domain::SELINUX, .nspace = KS2_NAMESPACE_WIFI, .alias = alias, .blob = std::nullopt, }; } } // Helper method to convert certs in DER format to PEM format required by // openssl library used by supplicant. If boringssl cannot parse the input as one or more // X509 certificates in DER encoding, this function returns the input as-is. The assumption in // that case is that either the `cert_bytes` is already PEM encoded, or `cert_bytes` is something // completely different that was intentionally installed by the Wi-Fi subsystem and it must not // be changed here. // If any error occurs during PEM encoding, this function returns std::nullopt and logs an error. std::optional> convertDerCertToPemOrPassthrough( const std::vector& cert_bytes) { // If cert_bytes is a DER encoded X509 certificate, it must be reencoded as PEM, because // wpa_supplicant only understand PEM. Otherwise the cert_bytes are returned as is. const uint8_t* cert_current = cert_bytes.data(); const uint8_t* cert_end = cert_current + cert_bytes.size(); bssl::UniquePtr pem_bio(BIO_new(BIO_s_mem())); while (cert_current < cert_end) { auto cert = bssl::UniquePtr(d2i_X509(nullptr, &cert_current, cert_end - cert_current)); // If part of the bytes cannot be parsed as X509 DER certificate, the original blob // shall be returned as-is. if (!cert) { wpa_printf(MSG_WARNING, "Could not parse DER X509 cert from buffer. Returning blob as is."); return cert_bytes; } if (!PEM_write_bio_X509(pem_bio.get(), cert.get())) { wpa_printf(MSG_ERROR, "Could not convert cert to PEM format."); return std::nullopt; } } const uint8_t* pem_bytes; size_t pem_len; if (!BIO_mem_contents(pem_bio.get(), &pem_bytes, &pem_len)) { wpa_printf(MSG_ERROR, "Could not extract pem_bytes from BIO."); return std::nullopt; } return {{pem_bytes, pem_bytes + pem_len}}; } std::optional> getKeystore2Cert(const std::string& key) { ::ndk::SpAIBinder keystoreBinder(AServiceManager_checkService(kKeystore2ServiceName)); auto keystore2 = ks2::IKeystoreService::fromBinder(keystoreBinder); if (!keystore2) { wpa_printf(MSG_WARNING, "Unable to connect to Keystore 2."); return {}; } bool ca_cert = false; std::string alias = key.c_str(); if (::android::base::StartsWith(alias, "CACERT_")) { alias = alias.substr(7); ca_cert = true; } else if (::android::base::StartsWith(alias, "USRCERT_")) { alias = alias.substr(8); } ks2::KeyDescriptor descriptor = mkKeyDescriptor(alias); // If the key_id starts with the grant id prefix, we parse the following string as numeric // grant id. We can then use the grant domain without alias to load the designated key. if (::android::base::StartsWith(alias, keystore2_grant_id_prefix)) { std::stringstream s(alias.substr(keystore2_grant_id_prefix.size())); uint64_t tmp; s >> std::hex >> tmp; if (s.fail() || !s.eof()) { wpa_printf(MSG_ERROR, "Couldn't parse grant name: %s", alias.c_str()); } descriptor.nspace = static_cast(tmp); descriptor.domain = ks2::Domain::GRANT; descriptor.alias = std::nullopt; } ks2::KeyEntryResponse response; auto rc = keystore2->getKeyEntry(descriptor, &response); if (!rc.isOk()) { if (rc.getServiceSpecificError() != int32_t(ks2::ResponseCode::KEY_NOT_FOUND)) { wpa_printf(MSG_WARNING, "Entry not found in Keystore 2."); } else { wpa_printf(MSG_WARNING, "Keystore 2 getKeyEntry failed error: %s", rc.getDescription().c_str()); } return {}; } if (ca_cert && response.metadata.certificateChain) { return std::move(*response.metadata.certificateChain); } else if (!ca_cert && response.metadata.certificate) { return std::move(*response.metadata.certificate); } else { wpa_printf(MSG_WARNING, "No %s certificate found.", (ca_cert ? "CA" : "client")); return {}; } } std::optional> getNonStandardCert(const std::string& alias, const std::shared_ptr &non_standard_callback) { if (non_standard_callback == nullptr) { wpa_printf(MSG_ERROR, "Non-standard cert callback is not available"); return std::nullopt; } std::vector blob; const auto& status = non_standard_callback->getBlob(alias, &blob); if (!status.isOk()) { wpa_printf(MSG_ERROR, "Cert callback error, code=%d", status.getServiceSpecificError()); return std::nullopt; } return blob; } } // namespace namespace aidl { namespace android { namespace hardware { namespace wifi { namespace supplicant { namespace certificate_utils { std::optional> getCertificate(const std::string& alias, const std::shared_ptr &non_standard_callback) { std::vector cert; if (auto ks2_cert = getKeystore2Cert(alias)) { cert = std::move(*ks2_cert); } else if (auto blob = getNonStandardCert(alias, non_standard_callback)) { cert = std::move(*blob); } else { wpa_printf(MSG_ERROR, "Failed to get certificate."); return std::nullopt; } if (auto result_cert = convertDerCertToPemOrPassthrough(cert)) { return result_cert; } else { wpa_printf(MSG_ERROR, "Conversion to PEM failed."); return std::nullopt; } } std::optional> listAliases(const std::string& prefix, const std::shared_ptr &non_standard_callback) { if (non_standard_callback == nullptr) { wpa_printf(MSG_ERROR, "Non-standard cert callback is not available"); return std::nullopt; } std::vector aliases; const auto& status = non_standard_callback->listAliases(prefix, &aliases); if (!status.isOk()) { wpa_printf(MSG_ERROR, "Unable to retrieve aliases"); return std::nullopt; } return aliases; } } // namespace certificate_utils } // namespace supplicant } // namespace wifi } // namespace hardware } // namespace android } // namespace aidl