Commit 71f4b278 authored by ppi@chromium.org's avatar ppi@chromium.org

Return specific cert verification errors on Android

To implement CertVerifyProc on Android we refer to the Java 
side to query the platform trust managers. Currently the 
information we get from the platform is binary - each 
certificate chain is either identified as trusted or not, in 
which case we assume that this is due to not-trusted root.

This patch provides better granularity distinguishing the 
following cases: expired, not yet valid, incorrect (could not 
be parsed), not trusted root.

This allowed to reenable two net unittests:
- CertVerifyProcTest.ExtraneousMD5RootCert
- CertVerifyProcTest.IntermediateCARequireExplicitPolicy

The following net unittest had to be disabled as it joins 
the club of CertVerifyProc tests failing on bots with 
incorrect time/date settings:
- CertVerifyProcTest.InvalidKeyUsage

BUG=169762

Review URL: https://chromiumcodereview.appspot.com/12212135

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@182280 0039d316-1c4b-4281-b951-d872f2087c98
parent 3f83628a
# List of suppressions. # List of suppressions.
CertVerifyProcTest.ExtraneousMD5RootCert
CertVerifyProcTest.IntermediateCARequireExplicitPolicy
CertVerifyProcTest.PublicKeyHashes CertVerifyProcTest.PublicKeyHashes
CertVerifyProcTest.RejectWeakKeys CertVerifyProcTest.RejectWeakKeys
CertVerifyProcTest.VerifyReturnChainBasic CertVerifyProcTest.VerifyReturnChainBasic
...@@ -40,6 +38,7 @@ VerifyRoot/CertVerifyProcWeakDigestTest.Verify/2 ...@@ -40,6 +38,7 @@ VerifyRoot/CertVerifyProcWeakDigestTest.Verify/2
# Fail only on bots. # Fail only on bots.
CertVerifyProcTest.TestKnownRoot CertVerifyProcTest.TestKnownRoot
CertVerifyProcTest.WithoutRevocationChecking CertVerifyProcTest.WithoutRevocationChecking
CertVerifyProcTest.InvalidKeyUsage
HttpCache.RangeGET_Cancel HttpCache.RangeGET_Cancel
HttpCache.RangeGET_Cancel2 HttpCache.RangeGET_Cancel2
HttpCache.RangeGET_OK HttpCache.RangeGET_OK
......
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef NET_ANDROID_CERT_VERIFY_RESULT_ANDROID_H_
#define NET_ANDROID_CERT_VERIFY_RESULT_ANDROID_H_
#include "base/basictypes.h"
#include "base/platform_file.h"
#include "net/base/net_export.h"
namespace net {
namespace android {
enum CertVerifyResultAndroid {
#define CERT_VERIFY_RESULT_ANDROID(label, value) VERIFY_ ## label = value,
#include "net/android/cert_verify_result_android_list.h"
#undef CERT_VERIFY_RESULT_ANDROID
};
} // namespace android
} // namespace net
#endif // NET_ANDROID_CERT_VERIFY_RESULT_ANDROID_H_
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This file intentionally does not have header guards, it's included
// inside a macro to generate enum.
// This file contains the list of certificate verification results returned
// from Java side to the C++ side.
// Certificate is trusted.
CERT_VERIFY_RESULT_ANDROID(OK, 0)
// Certificate verification could not be conducted.
CERT_VERIFY_RESULT_ANDROID(FAILED, -1)
// Certificate is not trusted due to non-trusted root of the certificate chain.
CERT_VERIFY_RESULT_ANDROID(NO_TRUSTED_ROOT, -2)
// Certificate is not trusted because it has expired.
CERT_VERIFY_RESULT_ANDROID(EXPIRED, -3)
// Certificate is not trusted because it is not valid yet.
CERT_VERIFY_RESULT_ANDROID(NOT_YET_VALID, -4)
// Certificate is not trusted because it could not be parsed.
CERT_VERIFY_RESULT_ANDROID(UNABLE_TO_PARSE, -5)
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.net;
public class CertVerifyResultAndroid {
#define CERT_VERIFY_RESULT_ANDROID(name, value) public static final int VERIFY_##name = value;
#include "net/android/cert_verify_result_android_list.h"
}
...@@ -10,9 +10,10 @@ import android.content.Intent; ...@@ -10,9 +10,10 @@ import android.content.Intent;
import android.security.KeyChain; import android.security.KeyChain;
import android.util.Log; import android.util.Log;
import org.chromium.net.CertificateMimeType;
import org.chromium.base.CalledByNative; import org.chromium.base.CalledByNative;
import org.chromium.base.CalledByNativeUnchecked; import org.chromium.base.CalledByNativeUnchecked;
import org.chromium.net.CertVerifyResultAndroid;
import org.chromium.net.CertificateMimeType;
import java.net.Inet6Address; import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
...@@ -66,7 +67,7 @@ class AndroidNetworkLibrary { ...@@ -66,7 +67,7 @@ class AndroidNetworkLibrary {
* PKCS#12 keychain) through the system's CertInstaller activity. * PKCS#12 keychain) through the system's CertInstaller activity.
* *
* @param context: current application context. * @param context: current application context.
* @param file_type: cryptographic file type. E.g. CertificateMimeType.X509_USER_CERT * @param cert_type: cryptographic file type. E.g. CertificateMimeType.X509_USER_CERT
* @param data: certificate/keychain data bytes. * @param data: certificate/keychain data bytes.
* @return true on success, false on failure. * @return true on success, false on failure.
* *
...@@ -196,15 +197,17 @@ class AndroidNetworkLibrary { ...@@ -196,15 +197,17 @@ class AndroidNetworkLibrary {
* *
* @param certChain The ASN.1 DER encoded bytes for certificates. * @param certChain The ASN.1 DER encoded bytes for certificates.
* @param authType The key exchange algorithm name (e.g. RSA) * @param authType The key exchange algorithm name (e.g. RSA)
* @return true if the server is trusted * @return Android certificate verification result code.
* @throws CertificateException,KeyStoreException,NoSuchAlgorithmException
* on error initializing the TrustManager or reading the
* certChain
*/ */
@CalledByNativeUnchecked @CalledByNative
public static boolean verifyServerCertificates(byte[][] certChain, String authType) public static int verifyServerCertificates(byte[][] certChain, String authType) {
throws CertificateException, KeyStoreException, NoSuchAlgorithmException { try {
return X509Util.verifyServerCertificates(certChain, authType); return X509Util.verifyServerCertificates(certChain, authType);
} catch (KeyStoreException e) {
return CertVerifyResultAndroid.VERIFY_FAILED;
} catch (NoSuchAlgorithmException e) {
return CertVerifyResultAndroid.VERIFY_FAILED;
}
} }
/** /**
...@@ -226,4 +229,4 @@ class AndroidNetworkLibrary { ...@@ -226,4 +229,4 @@ class AndroidNetworkLibrary {
CertificateException, KeyStoreException { CertificateException, KeyStoreException {
X509Util.clearTestRootCertificates(); X509Util.clearTestRootCertificates();
} }
} }
\ No newline at end of file
...@@ -6,12 +6,16 @@ package org.chromium.net; ...@@ -6,12 +6,16 @@ package org.chromium.net;
import android.util.Log; import android.util.Log;
import org.chromium.net.CertVerifyResultAndroid;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.IOException; import java.io.IOException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
...@@ -107,7 +111,7 @@ public class X509Util { ...@@ -107,7 +111,7 @@ public class X509Util {
KeyStoreException, NoSuchAlgorithmException { KeyStoreException, NoSuchAlgorithmException {
ensureInitialized(); ensureInitialized();
X509Certificate rootCert = createCertificateFromBytes(rootCertBytes); X509Certificate rootCert = createCertificateFromBytes(rootCertBytes);
synchronized(sLock) { synchronized (sLock) {
sTestKeyStore.setCertificateEntry( sTestKeyStore.setCertificateEntry(
"root_cert_" + Integer.toString(sTestKeyStore.size()), rootCert); "root_cert_" + Integer.toString(sTestKeyStore.size()), rootCert);
reloadTestTrustManager(); reloadTestTrustManager();
...@@ -117,45 +121,64 @@ public class X509Util { ...@@ -117,45 +121,64 @@ public class X509Util {
public static void clearTestRootCertificates() throws NoSuchAlgorithmException, public static void clearTestRootCertificates() throws NoSuchAlgorithmException,
CertificateException, KeyStoreException { CertificateException, KeyStoreException {
ensureInitialized(); ensureInitialized();
synchronized(sLock) { synchronized (sLock) {
try { try {
sTestKeyStore.load(null); sTestKeyStore.load(null);
reloadTestTrustManager(); reloadTestTrustManager();
} catch(IOException e) {} // No IO operation is attempted. } catch (IOException e) {} // No IO operation is attempted.
} }
} }
public static boolean verifyServerCertificates(byte[][] certChain, String authType) public static int verifyServerCertificates(byte[][] certChain, String authType)
throws CertificateException, KeyStoreException, NoSuchAlgorithmException { throws KeyStoreException, NoSuchAlgorithmException {
if (certChain == null || certChain.length == 0 || certChain[0] == null) { if (certChain == null || certChain.length == 0 || certChain[0] == null) {
throw new IllegalArgumentException("Expected non-null and non-empty certificate " + throw new IllegalArgumentException("Expected non-null and non-empty certificate " +
"chain passed as |certChain|. |certChain|=" + certChain); "chain passed as |certChain|. |certChain|=" + certChain);
} }
ensureInitialized(); try {
ensureInitialized();
} catch (CertificateException e) {
return CertVerifyResultAndroid.VERIFY_FAILED;
}
X509Certificate[] serverCertificates = new X509Certificate[certChain.length]; X509Certificate[] serverCertificates = new X509Certificate[certChain.length];
for (int i = 0; i < certChain.length; ++i) { try {
serverCertificates[i] = createCertificateFromBytes(certChain[i]); for (int i = 0; i < certChain.length; ++i) {
serverCertificates[i] = createCertificateFromBytes(certChain[i]);
}
} catch (CertificateException e) {
return CertVerifyResultAndroid.VERIFY_UNABLE_TO_PARSE;
}
// Expired and not yet valid certificates would be rejected by the trust managers, but the
// trust managers report all certificate errors using the general CertificateException. In
// order to get more granular error information, cert validity time range is being checked
// separately.
try {
serverCertificates[0].checkValidity();
} catch (CertificateExpiredException e) {
return CertVerifyResultAndroid.VERIFY_EXPIRED;
} catch (CertificateNotYetValidException e) {
return CertVerifyResultAndroid.VERIFY_NOT_YET_VALID;
} }
synchronized (sLock) { synchronized (sLock) {
try { try {
sDefaultTrustManager.checkServerTrusted(serverCertificates, authType); sDefaultTrustManager.checkServerTrusted(serverCertificates, authType);
return true; return CertVerifyResultAndroid.VERIFY_OK;
} catch (CertificateException eDefaultManager) { } catch (CertificateException eDefaultManager) {
try { try {
sTestTrustManager.checkServerTrusted(serverCertificates, authType); sTestTrustManager.checkServerTrusted(serverCertificates, authType);
return true; return CertVerifyResultAndroid.VERIFY_OK;
} catch (CertificateException eTestManager) { } catch (CertificateException eTestManager) {
/* // Neither of the trust managers confirms the validity of the certificate chain,
* Neither of the trust managers confirms the validity of the certificate // log the error message returned by the system trust manager.
* chain, we emit the error message returned by the system trust manager. Log.i(TAG, "Failed to validate the certificate chain, error: " +
*/ eDefaultManager.getMessage());
Log.i(TAG, "failed to validate the certificate chain, error: " + return CertVerifyResultAndroid.VERIFY_NO_TRUSTED_ROOT;
eDefaultManager.getMessage());
} }
} }
} }
return false;
} }
} }
\ No newline at end of file
...@@ -23,8 +23,9 @@ using base::android::ToJavaByteArray; ...@@ -23,8 +23,9 @@ using base::android::ToJavaByteArray;
namespace net { namespace net {
namespace android { namespace android {
VerifyResult VerifyX509CertChain(const std::vector<std::string>& cert_chain, CertVerifyResultAndroid VerifyX509CertChain(
const std::string& auth_type) { const std::vector<std::string>& cert_chain,
const std::string& auth_type) {
JNIEnv* env = AttachCurrentThread(); JNIEnv* env = AttachCurrentThread();
ScopedJavaLocalRef<jobjectArray> chain_byte_array = ScopedJavaLocalRef<jobjectArray> chain_byte_array =
...@@ -35,12 +36,10 @@ VerifyResult VerifyX509CertChain(const std::vector<std::string>& cert_chain, ...@@ -35,12 +36,10 @@ VerifyResult VerifyX509CertChain(const std::vector<std::string>& cert_chain,
ConvertUTF8ToJavaString(env, auth_type); ConvertUTF8ToJavaString(env, auth_type);
DCHECK(!auth_string.is_null()); DCHECK(!auth_string.is_null());
jboolean trusted = Java_AndroidNetworkLibrary_verifyServerCertificates( jint result = Java_AndroidNetworkLibrary_verifyServerCertificates(
env, chain_byte_array.obj(), auth_string.obj()); env, chain_byte_array.obj(), auth_string.obj());
if (ClearException(env))
return VERIFY_INVOCATION_ERROR;
return trusted ? VERIFY_OK : VERIFY_NO_TRUSTED_ROOT; return static_cast<CertVerifyResultAndroid>(result);
} }
void AddTestRootCertificate(const uint8* cert, size_t len) { void AddTestRootCertificate(const uint8* cert, size_t len) {
......
...@@ -11,28 +11,19 @@ ...@@ -11,28 +11,19 @@
#include <vector> #include <vector>
#include "base/basictypes.h" #include "base/basictypes.h"
#include "net/android/cert_verify_result_android.h"
#include "net/base/mime_util.h" #include "net/base/mime_util.h"
#include "net/base/net_export.h" #include "net/base/net_export.h"
namespace net { namespace net {
namespace android { namespace android {
enum VerifyResult {
// Certificate verification was successful.
VERIFY_OK,
// Certificate verification was failed. There is no detail error information
// given by Android API.
VERIFY_NO_TRUSTED_ROOT,
// Error occurs when invoke JNI methods.
VERIFY_INVOCATION_ERROR,
};
// |cert_chain| is DER encoded chain of certificates, with the server's own // |cert_chain| is DER encoded chain of certificates, with the server's own
// certificate listed first. // certificate listed first.
// |auth_type| is as per the Java X509Certificate.checkServerTrusted method. // |auth_type| is as per the Java X509Certificate.checkServerTrusted method.
CertVerifyResultAndroid VerifyX509CertChain(
VerifyResult VerifyX509CertChain(const std::vector<std::string>& cert_chain, const std::vector<std::string>& cert_chain,
const std::string& auth_type); const std::string& auth_type);
// Adds a certificate as a root trust certificate to the trust manager. // Adds a certificate as a root trust certificate to the trust manager.
// |cert| is DER encoded certificate, |len| is its length in bytes. // |cert| is DER encoded certificate, |len| is its length in bytes.
......
...@@ -8,6 +8,7 @@ ...@@ -8,6 +8,7 @@
#include <vector> #include <vector>
#include "base/logging.h" #include "base/logging.h"
#include "net/android/cert_verify_result_android.h"
#include "net/android/network_library.h" #include "net/android/network_library.h"
#include "net/base/cert_status_flags.h" #include "net/base/cert_status_flags.h"
#include "net/base/cert_verify_result.h" #include "net/base/cert_verify_result.h"
...@@ -23,23 +24,29 @@ namespace { ...@@ -23,23 +24,29 @@ namespace {
bool VerifyFromAndroidTrustManager(const std::vector<std::string>& cert_bytes, bool VerifyFromAndroidTrustManager(const std::vector<std::string>& cert_bytes,
CertVerifyResult* verify_result) { CertVerifyResult* verify_result) {
// TODO(joth): Fetch the authentication type from SSL rather than hardcode. // TODO(joth): Fetch the authentication type from SSL rather than hardcode.
bool verified = true; android::CertVerifyResultAndroid android_result =
android::VerifyResult result =
android::VerifyX509CertChain(cert_bytes, "RSA"); android::VerifyX509CertChain(cert_bytes, "RSA");
switch (result) { switch (android_result) {
case android::VERIFY_FAILED:
return false;
case android::VERIFY_OK: case android::VERIFY_OK:
break; break;
case android::VERIFY_NO_TRUSTED_ROOT: case android::VERIFY_NO_TRUSTED_ROOT:
verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID; verify_result->cert_status |= CERT_STATUS_AUTHORITY_INVALID;
break; break;
case android::VERIFY_INVOCATION_ERROR: case android::VERIFY_EXPIRED:
verified = false; case android::VERIFY_NOT_YET_VALID:
verify_result->cert_status |= CERT_STATUS_DATE_INVALID;
break;
case android::VERIFY_UNABLE_TO_PARSE:
verify_result->cert_status |= CERT_STATUS_INVALID;
break; break;
default: default:
NOTREACHED();
verify_result->cert_status |= CERT_STATUS_INVALID; verify_result->cert_status |= CERT_STATUS_INVALID;
break; break;
} }
return verified; return true;
} }
bool GetChainDEREncodedBytes(X509Certificate* cert, bool GetChainDEREncodedBytes(X509Certificate* cert,
......
...@@ -131,7 +131,7 @@ TEST_F(CertVerifyProcTest, PaypalNullCertParsing) { ...@@ -131,7 +131,7 @@ TEST_F(CertVerifyProcTest, PaypalNullCertParsing) {
CertVerifyResult verify_result; CertVerifyResult verify_result;
int error = Verify(paypal_null_cert, "www.paypal.com", flags, NULL, int error = Verify(paypal_null_cert, "www.paypal.com", flags, NULL,
&verify_result); &verify_result);
#if defined(USE_NSS) || defined(OS_IOS) #if defined(USE_NSS) || defined(OS_IOS) || defined(OS_ANDROID)
EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error); EXPECT_EQ(ERR_CERT_COMMON_NAME_INVALID, error);
#else #else
// TOOD(bulach): investigate why macosx and win aren't returning // TOOD(bulach): investigate why macosx and win aren't returning
......
...@@ -56,12 +56,14 @@ ...@@ -56,12 +56,14 @@
'net_resources', 'net_resources',
], ],
'sources': [ 'sources': [
'android/cert_verify_result_android.h',
'android/cert_verify_result_android_list.h',
'android/gurl_utils.cc',
'android/gurl_utils.h',
'android/keystore.cc', 'android/keystore.cc',
'android/keystore.h', 'android/keystore.h',
'android/keystore_openssl.cc', 'android/keystore_openssl.cc',
'android/keystore_openssl.h', 'android/keystore_openssl.h',
'android/gurl_utils.cc',
'android/gurl_utils.h',
'android/net_jni_registrar.cc', 'android/net_jni_registrar.cc',
'android/net_jni_registrar.h', 'android/net_jni_registrar.h',
'android/network_change_notifier_android.cc', 'android/network_change_notifier_android.cc',
...@@ -2383,8 +2385,9 @@ ...@@ -2383,8 +2385,9 @@
}, },
'dependencies': [ 'dependencies': [
'../base/base.gyp:base', '../base/base.gyp:base',
'net_errors_java', 'cert_verify_result_android_java',
'certificate_mime_types_java', 'certificate_mime_types_java',
'net_errors_java',
'private_key_types_java', 'private_key_types_java',
], ],
'includes': [ '../build/java.gypi' ], 'includes': [ '../build/java.gypi' ],
...@@ -2436,6 +2439,18 @@ ...@@ -2436,6 +2439,18 @@
}, },
'includes': [ '../build/android/java_cpp_template.gypi' ], 'includes': [ '../build/android/java_cpp_template.gypi' ],
}, },
{
'target_name': 'cert_verify_result_android_java',
'type': 'none',
'sources': [
'android/java/CertVerifyResultAndroid.template',
],
'variables': {
'package_name': 'org.chromium.net',
'template_deps': ['android/cert_verify_result_android_list.h'],
},
'includes': [ '../build/android/java_cpp_template.gypi' ],
},
{ {
'target_name': 'private_key_types_java', 'target_name': 'private_key_types_java',
'type': 'none', 'type': 'none',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment