summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--luni/src/main/java/libcore/io/DropBox.java72
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java9
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinFailureLogger.java70
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinListEntry.java26
-rw-r--r--luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java52
-rw-r--r--luni/src/test/java/org/apache/harmony/xnet/provider/jsse/CertPinManagerTest.java14
-rw-r--r--luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImplTest.java23
7 files changed, 229 insertions, 37 deletions
diff --git a/luni/src/main/java/libcore/io/DropBox.java b/luni/src/main/java/libcore/io/DropBox.java
new file mode 100644
index 0000000..cf88106
--- /dev/null
+++ b/luni/src/main/java/libcore/io/DropBox.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+package libcore.io;
+
+public final class DropBox {
+
+ /**
+ * Hook for customizing how events are reported.
+ */
+ private static volatile Reporter REPORTER = new DefaultReporter();
+
+ /**
+ * Used to replace default Reporter for logging events. Must be non-null.
+ */
+ public static void setReporter(Reporter reporter) {
+ if (reporter == null) {
+ throw new NullPointerException("reporter == null");
+ }
+ REPORTER = reporter;
+ }
+
+ /**
+ * Returns non-null Reporter.
+ */
+ public static Reporter getReporter() {
+ return REPORTER;
+ }
+
+ /**
+ * Interface to allow customization of reporting behavior.
+ */
+ public static interface Reporter {
+ public void addData(String tag, byte[] data, int flags);
+ public void addText(String tag, String data);
+ }
+
+ /**
+ * Default Reporter which reports events to the log.
+ */
+ private static final class DefaultReporter implements Reporter {
+
+ public void addData(String tag, byte[] data, int flags) {
+ System.out.println(tag + ": " + Base64.encode(data));
+ }
+
+ public void addText(String tag, String data) {
+ System.out.println(tag + ": " + data);
+ }
+ }
+
+ public static void addData(String tag, byte[] data, int flags) {
+ getReporter().addData(tag, data, flags);
+ }
+
+ public static void addText(String tag, String data) {
+ getReporter().addText(tag, data);
+ }
+}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java
index aa956dd..4cdd8d2 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/CertPinManager.java
@@ -42,18 +42,21 @@ public class CertPinManager {
private static final boolean DEBUG = false;
private final File pinFile;
+ private final TrustedCertificateStore certStore;
- public CertPinManager() throws PinManagerException {
+ public CertPinManager(TrustedCertificateStore store) throws PinManagerException {
pinFile = new File("/data/misc/keychain/pins");
+ certStore = store;
rebuild();
}
/** Test only */
- public CertPinManager(String path) throws PinManagerException {
+ public CertPinManager(String path, TrustedCertificateStore store) throws PinManagerException {
if (path == null) {
throw new NullPointerException("path == null");
}
pinFile = new File(path);
+ certStore = store;
rebuild();
}
@@ -86,7 +89,7 @@ public class CertPinManager {
// rebuild the pinned certs
for (String entry : getPinFileEntries(pinFileContents)) {
try {
- PinListEntry pin = new PinListEntry(entry);
+ PinListEntry pin = new PinListEntry(entry, certStore);
entries.put(pin.getCommonName(), pin);
} catch (PinEntryException e) {
log("Pinlist contains a malformed pin: " + entry, e);
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinFailureLogger.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinFailureLogger.java
new file mode 100644
index 0000000..40b1838
--- /dev/null
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinFailureLogger.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2012 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.
+ */
+
+package org.apache.harmony.xnet.provider.jsse;
+
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.X509Certificate;
+import java.util.List;
+import libcore.io.Base64;
+import libcore.io.DropBox;
+
+public class PinFailureLogger {
+
+ private static final long LOG_INTERVAL_NANOS = 1000 * 1000 * 1000 * 60 * 60;
+
+ private static long lastLoggedNanos = 0;
+
+ public static synchronized void log(String cn, boolean chainContainsUserCert,
+ boolean pinIsEnforcing,
+ List<X509Certificate> chain) {
+ // if we've logged recently, don't do it again
+ if (!timeToLog()) {
+ return;
+ }
+ // otherwise, log the event
+ writeToLog(cn, chainContainsUserCert, pinIsEnforcing, chain);
+ // update the last logged time
+ lastLoggedNanos = System.nanoTime();
+ }
+
+ protected static synchronized void writeToLog(String cn, boolean chainContainsUserCert,
+ boolean pinIsEnforcing,
+ List<X509Certificate> chain) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(cn);
+ sb.append("|");
+ sb.append(chainContainsUserCert);
+ sb.append("|");
+ sb.append(pinIsEnforcing);
+ sb.append("|");
+ for (X509Certificate cert : chain) {
+ try {
+ sb.append(Base64.encode(cert.getEncoded()));
+ } catch (CertificateEncodingException e) {
+ sb.append("Error: could not encode certificate");
+ }
+ sb.append("|");
+ }
+ DropBox.addText("cert_pin_failure", sb.toString());
+ }
+
+ protected static boolean timeToLog() {
+ long currentTimeNanos = System.nanoTime();
+ return ((currentTimeNanos - lastLoggedNanos) > LOG_INTERVAL_NANOS);
+ }
+}
+
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinListEntry.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinListEntry.java
index 15a07c1..c05a391 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinListEntry.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/PinListEntry.java
@@ -46,6 +46,8 @@ public class PinListEntry {
private static final boolean DEBUG = false;
+ private final TrustedCertificateStore certStore;
+
public String getCommonName() {
return cn;
}
@@ -54,10 +56,11 @@ public class PinListEntry {
return enforcing;
}
- public PinListEntry(String entry) throws PinEntryException {
+ public PinListEntry(String entry, TrustedCertificateStore store) throws PinEntryException {
if (entry == null) {
throw new NullPointerException("entry == null");
}
+ certStore = store;
// Examples:
// *.google.com=true|34c8a0d...9e04ca05f,9e04ca05f...34c8a0d
// *.android.com=true|ca05f...8a0d34c
@@ -98,7 +101,7 @@ public class PinListEntry {
return false;
}
}
- logPinFailure(cn, chain);
+ logPinFailure(chain);
return enforcing;
}
@@ -133,13 +136,20 @@ public class PinListEntry {
}
}
- private void logPinFailure(String cn, List<X509Certificate> chain) {
- Object[] values = new Object[chain.size() + 1];
- values[0] = (Object) cn;
- for (int i=0; i < chain.size(); i++) {
- values[i+1] = chain.get(i).toString();
+ private boolean chainContainsUserCert(List<X509Certificate> chain) {
+ if (certStore == null) {
+ return false;
}
- EventLogger.writeEvent(90100, values);
+ for (X509Certificate cert : chain) {
+ if (certStore.isUserAddedCertificate(cert)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private void logPinFailure(List<X509Certificate> chain) {
+ PinFailureLogger.log(cn, chainContainsUserCert(chain), enforcing, chain);
}
}
diff --git a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java
index 48dbaba..0218249 100644
--- a/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java
+++ b/luni/src/main/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImpl.java
@@ -129,7 +129,7 @@ public final class TrustManagerImpl implements X509TrustManager {
this.pinManager = manager;
} else {
try {
- pinManager = new CertPinManager();
+ pinManager = new CertPinManager(trustedCertificateStoreLocal);
} catch (PinManagerException e) {
throw new SecurityException("Could not initialize CertPinManager", e);
}
@@ -214,17 +214,43 @@ public final class TrustManagerImpl implements X509TrustManager {
throw new CertificateException(err);
}
- // get the cleaned up chain and trust anchors
- Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
- X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchors);
+ // get the cleaned up chain and trust anchor
+ Set<TrustAnchor> trustAnchor = new HashSet<TrustAnchor>(); // there can only be one!
+ X509Certificate[] newChain = cleanupCertChainAndFindTrustAnchors(chain, trustAnchor);
- // build the whole chain including all the trust anchors
+ // add the first trust anchor to the chain, which may be an intermediate
List<X509Certificate> wholeChain = new ArrayList<X509Certificate>();
wholeChain.addAll(Arrays.asList(newChain));
- for (TrustAnchor trust : trustAnchors) {
+ // trustAnchor is actually just a single element
+ for (TrustAnchor trust : trustAnchor) {
wholeChain.add(trust.getTrustedCert());
}
+ // add all the cached certificates from the cert index, avoiding loops
+ // this gives us a full chain from leaf to root, which we use for cert pinning and pass
+ // back out to callers when we return.
+ X509Certificate last = wholeChain.get(wholeChain.size() - 1);
+ while (true) {
+ TrustAnchor cachedTrust = trustedCertificateIndex.findByIssuerAndSignature(last);
+ // the cachedTrust can be null if there isn't anything in the index or if a user has
+ // trusted a non-self-signed cert.
+ if (cachedTrust == null) {
+ break;
+ }
+
+ // at this point we have a cached trust anchor, but don't know if its one we got from
+ // the server. Extract the cert, compare it to the last element in the chain, and add it
+ // if we haven't seen it before.
+ X509Certificate next = cachedTrust.getTrustedCert();
+ if (next != last) {
+ wholeChain.add(next);
+ last = next;
+ } else {
+ // if next == last then we found a self-signed cert and the chain is done
+ break;
+ }
+ }
+
// build the cert path from the array of certs sans trust anchors
CertPath certPath = factory.generateCertPath(Arrays.asList(newChain));
@@ -236,7 +262,6 @@ public final class TrustManagerImpl implements X509TrustManager {
throw new CertificateException(e);
}
if (chainIsNotPinned) {
- EventLogger.writeEvent(90102, chainContainsUserCert(wholeChain));
throw new CertificateException(new CertPathValidatorException(
"Certificate path is not properly pinned.", null, certPath, -1));
}
@@ -247,13 +272,13 @@ public final class TrustManagerImpl implements X509TrustManager {
return wholeChain;
}
- if (trustAnchors.isEmpty()) {
+ if (trustAnchor.isEmpty()) {
throw new CertificateException(new CertPathValidatorException(
"Trust anchor for certification path not found.", null, certPath, -1));
}
try {
- PKIXParameters params = new PKIXParameters(trustAnchors);
+ PKIXParameters params = new PKIXParameters(trustAnchor);
params.setRevocationEnabled(false);
validator.validate(certPath, params);
// Add intermediate CAs to the index to tolerate sites
@@ -398,13 +423,4 @@ public final class TrustManagerImpl implements X509TrustManager {
@Override public X509Certificate[] getAcceptedIssuers() {
return (acceptedIssuers != null) ? acceptedIssuers.clone() : acceptedIssuers(rootKeyStore);
}
-
- private boolean chainContainsUserCert(List<X509Certificate> chain) {
- for (X509Certificate cert : chain) {
- if (trustedCertificateStore.isUserAddedCertificate(cert)) {
- return true;
- }
- }
- return false;
- }
}
diff --git a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/CertPinManagerTest.java b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/CertPinManagerTest.java
index 7d37492..8359c99 100644
--- a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/CertPinManagerTest.java
+++ b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/CertPinManagerTest.java
@@ -92,7 +92,7 @@ public class CertPinManagerTest extends TestCase {
// create the pinFile
String path = writeTmpPinFile(shortEntry + "\n" + longEntry);
- CertPinManager pf = new CertPinManager(path);
+ CertPinManager pf = new CertPinManager(path, new TrustedCertificateStore());
// verify that the shorter chain doesn't work for a name matching the longer
assertTrue("short chain long uri failed",
@@ -112,7 +112,7 @@ public class CertPinManagerTest extends TestCase {
// set up the pinEntry with a bogus entry
String entry = "*.google.com=";
try {
- new PinListEntry(entry);
+ new PinListEntry(entry, new TrustedCertificateStore());
fail("Accepted an empty pin list entry.");
} catch (PinEntryException expected) {
}
@@ -122,7 +122,7 @@ public class CertPinManagerTest extends TestCase {
// set up the pinEntry with a bogus entry
String entry = null;
try {
- new PinListEntry(entry);
+ new PinListEntry(entry, new TrustedCertificateStore());
fail("Accepted a basically wholly bogus entry.");
} catch (NullPointerException expected) {
}
@@ -131,7 +131,7 @@ public class CertPinManagerTest extends TestCase {
public void testPinEntryEmpty() throws Exception {
// set up the pinEntry with a bogus entry
try {
- new PinListEntry("");
+ new PinListEntry("", new TrustedCertificateStore());
fail("Accepted an empty entry.");
} catch (PinEntryException expected) {
}
@@ -142,7 +142,7 @@ public class CertPinManagerTest extends TestCase {
String shortEntry = "*.google.com=true|" + shortPin;
// set up the pinEntry with a pinlist that doesn't match what we'll give it
- PinListEntry e = new PinListEntry(shortEntry);
+ PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore());
assertTrue("Not enforcing!", e.getEnforcing());
// verify that it doesn't accept
boolean retval = e.chainIsNotPinned(longChain);
@@ -154,7 +154,7 @@ public class CertPinManagerTest extends TestCase {
String shortEntry = "*.google.com=true|" + shortPin;
// set up the pinEntry with a pinlist that matches what we'll give it
- PinListEntry e = new PinListEntry(shortEntry);
+ PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore());
assertTrue("Not enforcing!", e.getEnforcing());
// verify that it accepts
boolean retval = e.chainIsNotPinned(shortChain);
@@ -166,7 +166,7 @@ public class CertPinManagerTest extends TestCase {
String shortEntry = "*.google.com=false|" + shortPin;
// set up the pinEntry with a pinlist that matches what we'll give it
- PinListEntry e = new PinListEntry(shortEntry);
+ PinListEntry e = new PinListEntry(shortEntry, new TrustedCertificateStore());
assertFalse("Enforcing!", e.getEnforcing());
// verify that it accepts
boolean retval = e.chainIsNotPinned(shortChain);
diff --git a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImplTest.java b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImplTest.java
index 97032d9..fe5f4f0 100644
--- a/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImplTest.java
+++ b/luni/src/test/java/org/apache/harmony/xnet/provider/jsse/TrustManagerImplTest.java
@@ -98,6 +98,27 @@ public class TrustManagerImplTest extends TestCase {
assertValid(chain1, tm);
}
+ public void testGetFullChain() throws Exception {
+ // build the trust manager
+ KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
+ X509Certificate[] chain3 = (X509Certificate[])pke.getCertificateChain();
+ X509Certificate root = chain3[2];
+ X509TrustManager tm = trustManager(root);
+
+ // build the chains we'll use for testing
+ X509Certificate intermediate = chain3[1];
+ X509Certificate server = chain3[0];
+ X509Certificate[] chain2 = new X509Certificate[] { server, intermediate };
+ X509Certificate[] chain1 = new X509Certificate[] { server };
+
+ assertTrue(tm instanceof TrustManagerImpl);
+ TrustManagerImpl tmi = (TrustManagerImpl) tm;
+ List<X509Certificate> certs = tmi.checkServerTrusted(chain2, "RSA", "purple.com");
+ assertEquals(Arrays.asList(chain3), certs);
+ certs = tmi.checkServerTrusted(chain1, "RSA", "purple.com");
+ assertEquals(Arrays.asList(chain3), certs);
+ }
+
public void testCertPinning() throws Exception {
// chain3 should be server/intermediate/root
KeyStore.PrivateKeyEntry pke = TestKeyStore.getServer().getPrivateKey("RSA", "RSA");
@@ -151,7 +172,7 @@ public class TrustManagerImplTest extends TestCase {
// write it to a pinfile
String path = writeTmpPinFile(pinString);
// build the certpinmanager
- return new CertPinManager(path);
+ return new CertPinManager(path, new TrustedCertificateStore());
}
private void assertValid(X509Certificate[] chain, X509TrustManager tm) throws Exception {