PR Code Suggestions ✨
Explore these optional code suggestions:
| Category | Suggestion | Impact |
| High-level |
Eliminate server-supplied private keysThe current implementation incorrectly accepts a private key from the enrollment Examples:src/net/android/java/src/org/chromium/net/WootzDeviceEnrollment.java [301-309]String dicCertificate = WootzEnrollmentUtils.extractJsonValue(response, "dicCertificate"); String dicPrivateKey = WootzEnrollmentUtils.extractJsonValue(response, "dicPrivateKey"); String expiresAt = WootzEnrollmentUtils.extractJsonValue(response, "expiresAt"); String issuedAt = WootzEnrollmentUtils.extractJsonValue(response, "issuedAt"); String stepCaUrl = WootzEnrollmentUtils.extractJsonValue(response, "stepCaUrl"); // Store the DIC certificate and associate it with the hardware key boolean dicStored = WootzHardwareKeyStore.storeDicCertificate( deviceId, dicCertificate, dicPrivateKey, expiresAt, issuedAt, stepCaUrl); src/net/android/java/src/org/chromium/net/WootzHardwareKeyStore.java [432-471]public static boolean storeDicCertificate(String deviceId, String dicCertificatePem, String dicPrivateKeyPem, String expiresAt, String issuedAt, String stepCaUrl) { try { // Parse and validate the DIC certificate X509Certificate dicCert = WootzCertificateUtils.parsePemCertificate(dicCertificatePem); if (dicCert == null) { return false; } ... (clipped 30 lines) Solution Walkthrough:Before:// In WootzDeviceEnrollment.java handleEnrollmentSuccessResponse(String response) { // ... String dicPrivateKey = extractJsonValue(response, "dicPrivateKey"); // ... WootzHardwareKeyStore.storeDicCertificate( deviceId, dicCertificate, dicPrivateKey, ...); } // In WootzHardwareKeyStore.java public static boolean storeDicCertificate(String deviceId, String dicCertificatePem, String dicPrivateKeyPem, ...) { // ... PrivateKey dicPrivateKey = WootzCertificateUtils.parsePemPrivateKey(dicPrivateKeyPem); // ... keyStore.setKeyEntry(WOOTZ_DIC_ALIAS, dicPrivateKey, null, certChain); // ... } After:// In WootzDeviceEnrollment.java handleEnrollmentSuccessResponse(String response) { // ... // String dicPrivateKey = ...; // REMOVED // ... WootzHardwareKeyStore.storeDicCertificate( deviceId, dicCertificate, ...); // No private key passed } // In WootzHardwareKeyStore.java public static boolean storeDicCertificate(String deviceId, String dicCertificatePem, ...) { // ... // PrivateKey dicPrivateKey = ...; // REMOVED // ... // Verify the public key in `dicCert` matches the public key of `WOOTZ_KEY_ALIAS` PublicKey hardwarePubKey = keyStore.getCertificate(WOOTZ_KEY_ALIAS).getPublicKey(); if (!hardwarePubKey.equals(dicCert.getPublicKey())) { return false; // Public keys do not match! } // Store only the certificate chain against the hardware key alias keyStore.setKeyEntry(WOOTZ_KEY_ALIAS, getPrivateKeyInternal(), null, certChain); // ... } Suggestion importance[1-10]: 10__ Why: The suggestion correctly identifies a critical security flaw where a server-provided private key is imported, completely undermining the non-exportable TEE key guarantee and the entire attestation-based security model. | High |
| Possible issue |
Use matching key for mTLSThe TLS handshake signature must be verifiable with the certificate presented to src/net/android/java/src/org/chromium/net/WootzHardwareKeyStore.java [697-721] @CalledByNative
private static byte[] signMTLSHandshake(byte[] handshakeData) {
try {
if (!isDicAvailableForMTLS()) {
Log.e(TAG, "DIC not available for mTLS handshake signing");
return null;
}
-
- // Use the hardware key to sign the handshake data
- // This ensures the private key never leaves the TEE/Strongbox
- byte[] signature = signWithHardwareKey(handshakeData);
-
+
+ // Sign using the DIC private key so the signature matches the presented certificate.
+ byte[] signature = signWithDicKey(handshakeData);
+
if (signature != null) {
- Log.i(TAG, "Successfully signed mTLS handshake with hardware key");
+ Log.i(TAG, "Successfully signed mTLS handshake with DIC private key");
} else {
- Log.e(TAG, "Failed to sign mTLS handshake with hardware key");
+ Log.e(TAG, "Failed to sign mTLS handshake with DIC private key");
}
-
+
return signature;
-
+
} catch (Exception e) {
Log.e(TAG, "Error during mTLS handshake signing", e);
return null;
}
}
Suggestion importance[1-10]: 10__ Why: This suggestion correctly identifies a critical bug where the mTLS handshake is signed with the hardware attestation key instead of the DIC private key, which would cause all mTLS authentications to fail. | High |
Enforce supported TLS algorithmsOnly advertise and accept algorithms actually supported by the presented key. src/chrome/browser/wootz_client_cert_identity.cc [38-69] std::vector<uint16_t> GetAlgorithmPreferences() override {
- // Support common signature algorithms for hardware keys
+ // Only advertise the algorithm we actually support with the DIC key.
return {
SSL_SIGN_ECDSA_SECP256R1_SHA256,
- SSL_SIGN_ECDSA_SECP384R1_SHA384,
- SSL_SIGN_RSA_PKCS1_SHA256,
- SSL_SIGN_RSA_PKCS1_SHA384,
};
}
void Sign(uint16_t algorithm,
base::span<const uint8_t> input,
SignCallback callback) override {
DVLOG(1) << "WootzSSLPrivateKey::Sign called";
-
+
#if BUILDFLAG(IS_ANDROID)
- // Delegate directly to our existing hardware signing function
+ if (algorithm != SSL_SIGN_ECDSA_SECP256R1_SHA256) {
+ LOG(ERROR) << "Unsupported signature algorithm: " << algorithm;
+ std::move(callback).Run(net::ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED, std::vector<uint8_t>());
+ return;
+ }
+
+ // Delegate to mTLS signer (now using DIC key on the Java side).
std::vector<uint8_t> signature = net::android::wootz::SignMTLSHandshake(input);
-
if (!signature.empty()) {
- DVLOG(1) << "Wootz hardware key signing successful";
+ DVLOG(1) << "Wootz key signing successful";
std::move(callback).Run(net::OK, std::move(signature));
} else {
- LOG(ERROR) << "Wootz hardware key signing failed";
- std::move(callback).Run(net::ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED,
- std::vector<uint8_t>());
+ LOG(ERROR) << "Wootz key signing failed";
+ std::move(callback).Run(net::ERR_SSL_CLIENT_AUTH_SIGNATURE_FAILED, std::vector<uint8_t>());
}
#else
LOG(ERROR) << "Wootz hardware key only supported on Android";
std::move(callback).Run(net::ERR_NOT_IMPLEMENTED, std::vector<uint8_t>());
#endif
}
Suggestion importance[1-10]: 9__ Why: The suggestion correctly points out that the code advertises unsupported signature algorithms, which could lead to TLS negotiation failures, and provides a robust fix by restricting advertised algorithms and verifying the selected one. | High | |
Fix hardware backing detectionOn Android < S, both methods currently return false, falsely treating TEE-backed src/net/android/java/src/org/chromium/net/WootzHardwareKeyStore.java [305-356] private static boolean isStrongBoxBacked(PrivateKey privateKey) {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) { // API 31+ for getSecurityLevel in KeyInfo
+ // StrongBox security level is only available on API 31+.
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S || privateKey == null) {
return false;
}
-
try {
- // Use the public KeyInfo API to get security level
KeyFactory factory = KeyFactory.getInstance(privateKey.getAlgorithm(), ANDROID_KEYSTORE);
KeyInfo keyInfo = factory.getKeySpec(privateKey, KeyInfo.class);
-
int securityLevel = keyInfo.getSecurityLevel();
return securityLevel == KeyProperties.SECURITY_LEVEL_STRONGBOX;
-
- } catch (InvalidKeySpecException e) {
- Log.w(TAG, "Key does not expose KeyInfo for Strongbox check", e);
- return false;
} catch (Exception e) {
Log.w(TAG, "Could not determine Strongbox backing: " + e.getMessage());
return false;
}
}
private static boolean isHardwareBacked(PrivateKey privateKey) {
if (privateKey == null) {
return false;
}
-
- // Check if it's AndroidKeyStore key
+ // Must be an AndroidKeyStore key.
String className = privateKey.getClass().getName();
if (!className.contains("AndroidKeyStore")) {
return false;
}
-
- // For API 31+, use KeyInfo.getSecurityLevel() - the proper public API
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- try {
- KeyFactory factory = KeyFactory.getInstance(privateKey.getAlgorithm(), ANDROID_KEYSTORE);
- KeyInfo keyInfo = factory.getKeySpec(privateKey, KeyInfo.class);
-
+ try {
+ KeyFactory factory = KeyFactory.getInstance(privateKey.getAlgorithm(), ANDROID_KEYSTORE);
+ KeyInfo keyInfo = factory.getKeySpec(privateKey, KeyInfo.class);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
int securityLevel = keyInfo.getSecurityLevel();
- return securityLevel == KeyProperties.SECURITY_LEVEL_STRONGBOX ||
- securityLevel == KeyProperties.SECURITY_LEVEL_TRUSTED_ENVIRONMENT;
-
- } catch (InvalidKeySpecException e) {
- Log.w(TAG, "Key does not expose KeyInfo for hardware backing check", e);
- } catch (Exception e) {
- Log.w(TAG, "Could not determine security level: " + e.getMessage());
+ return securityLevel == KeyProperties.SECURITY_LEVEL_STRONGBOX
+ || securityLevel == KeyProperties.SECURITY_LEVEL_TRUSTED_ENVIRONMENT;
}
+ // Fallback for API 23–30: detect TEE via isInsideSecureHardware().
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ return keyInfo.isInsideSecureHardware();
+ }
+ } catch (Exception e) {
+ Log.w(TAG, "Could not determine hardware backing: " + e.getMessage());
}
-
return false;
}
Suggestion importance[1-10]: 9__ Why: The suggestion correctly identifies that hardware-backing detection fails on Android versions older than S (API 31), which would incorrectly disable the feature, and proposes a valid fallback using | High | |
Export keystore API symbolsExport these functions from the net component to avoid unresolved symbols in src/net/android/wootz_keystore.h [31-124] #include "base/android/scoped_java_ref.h" #include "base/containers/span.h" +#include "net/base/net_export.h" // Wootz hardware keystore functions for secure key operations. // These functions provide access to hardware-backed keys (Strongbox/TEE) // with proper attestation support for maximum security. namespace net::android::wootz { -bool InitializeHardwareKeystore(); -bool GenerateHardwareBackedKeyWithAttestation( +NET_EXPORT bool InitializeHardwareKeystore(); +NET_EXPORT bool GenerateHardwareBackedKeyWithAttestation( base::span<const uint8_t> attestation_challenge); -std::string GetDevicePublicKeyPem(); -std::string GetAttestationChainPem(); -bool IsKeyStrongboxBacked(); -bool IsKeyHardwareBacked(); -std::vector<uint8_t> SignWithHardwareKey(base::span<const uint8_t> data); +NET_EXPORT std::string GetDevicePublicKeyPem(); +NET_EXPORT std::string GetAttestationChainPem(); +NET_EXPORT bool IsKeyStrongboxBacked(); +NET_EXPORT bool IsKeyHardwareBacked(); +NET_EXPORT std::vector<uint8_t> SignWithHardwareKey(base::span<const uint8_t> data); ...
Suggestion importance[1-10]: 9__ Why: This is a critical fix for component builds, as the new functions in the | High | |
| ||