1 /* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.security.net.config; 18 19 import android.os.Environment; 20 import android.os.UserHandle; 21 import android.util.ArraySet; 22 import android.util.Log; 23 import android.util.Pair; 24 import java.io.BufferedInputStream; 25 import java.io.File; 26 import java.io.FileInputStream; 27 import java.io.InputStream; 28 import java.io.IOException; 29 import java.security.cert.Certificate; 30 import java.security.cert.CertificateException; 31 import java.security.cert.CertificateFactory; 32 import java.security.cert.X509Certificate; 33 import java.util.Collections; 34 import java.util.Set; 35 import libcore.io.IoUtils; 36 37 import com.android.org.conscrypt.Hex; 38 import com.android.org.conscrypt.NativeCrypto; 39 40 import javax.security.auth.x500.X500Principal; 41 42 /** 43 * {@link CertificateSource} based on a directory where certificates are stored as individual files 44 * named after a hash of their SubjectName for more efficient lookups. 45 * @hide 46 */ 47 abstract class DirectoryCertificateSource implements CertificateSource { 48 private static final String LOG_TAG = "DirectoryCertificateSrc"; 49 private final File mDir; 50 private final Object mLock = new Object(); 51 private final CertificateFactory mCertFactory; 52 53 private Set<X509Certificate> mCertificates; 54 DirectoryCertificateSource(File caDir)55 protected DirectoryCertificateSource(File caDir) { 56 mDir = caDir; 57 try { 58 mCertFactory = CertificateFactory.getInstance("X.509"); 59 } catch (CertificateException e) { 60 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); 61 } 62 } 63 isCertMarkedAsRemoved(String caFile)64 protected abstract boolean isCertMarkedAsRemoved(String caFile); 65 66 @Override getCertificates()67 public Set<X509Certificate> getCertificates() { 68 // TODO: loading all of these is wasteful, we should instead use a keystore style API. 69 synchronized (mLock) { 70 if (mCertificates != null) { 71 return mCertificates; 72 } 73 74 Set<X509Certificate> certs = new ArraySet<X509Certificate>(); 75 if (mDir.isDirectory()) { 76 for (String caFile : mDir.list()) { 77 if (isCertMarkedAsRemoved(caFile)) { 78 continue; 79 } 80 X509Certificate cert = readCertificate(caFile); 81 if (cert != null) { 82 certs.add(cert); 83 } 84 } 85 } 86 mCertificates = certs; 87 return mCertificates; 88 } 89 } 90 91 @Override findBySubjectAndPublicKey(final X509Certificate cert)92 public X509Certificate findBySubjectAndPublicKey(final X509Certificate cert) { 93 return findCert(cert.getSubjectX500Principal(), new CertSelector() { 94 @Override 95 public boolean match(X509Certificate ca) { 96 return ca.getPublicKey().equals(cert.getPublicKey()); 97 } 98 }); 99 } 100 101 @Override 102 public X509Certificate findByIssuerAndSignature(final X509Certificate cert) { 103 return findCert(cert.getIssuerX500Principal(), new CertSelector() { 104 @Override 105 public boolean match(X509Certificate ca) { 106 try { 107 cert.verify(ca.getPublicKey()); 108 return true; 109 } catch (Exception e) { 110 return false; 111 } 112 } 113 }); 114 } 115 116 @Override 117 public Set<X509Certificate> findAllByIssuerAndSignature(final X509Certificate cert) { 118 return findCerts(cert.getIssuerX500Principal(), new CertSelector() { 119 @Override 120 public boolean match(X509Certificate ca) { 121 try { 122 cert.verify(ca.getPublicKey()); 123 return true; 124 } catch (Exception e) { 125 return false; 126 } 127 } 128 }); 129 } 130 131 @Override 132 public void handleTrustStorageUpdate() { 133 synchronized (mLock) { 134 mCertificates = null; 135 } 136 } 137 138 private static interface CertSelector { 139 boolean match(X509Certificate cert); 140 } 141 142 private Set<X509Certificate> findCerts(X500Principal subj, CertSelector selector) { 143 String hash = getHash(subj); 144 Set<X509Certificate> certs = null; 145 for (int index = 0; index >= 0; index++) { 146 String fileName = hash + "." + index; 147 if (!new File(mDir, fileName).exists()) { 148 break; 149 } 150 if (isCertMarkedAsRemoved(fileName)) { 151 continue; 152 } 153 X509Certificate cert = readCertificate(fileName); 154 if (cert == null) { 155 continue; 156 } 157 if (!subj.equals(cert.getSubjectX500Principal())) { 158 continue; 159 } 160 if (selector.match(cert)) { 161 if (certs == null) { 162 certs = new ArraySet<X509Certificate>(); 163 } 164 certs.add(cert); 165 } 166 } 167 return certs != null ? certs : Collections.<X509Certificate>emptySet(); 168 } 169 170 private X509Certificate findCert(X500Principal subj, CertSelector selector) { 171 String hash = getHash(subj); 172 for (int index = 0; index >= 0; index++) { 173 String fileName = hash + "." + index; 174 if (!new File(mDir, fileName).exists()) { 175 break; 176 } 177 if (isCertMarkedAsRemoved(fileName)) { 178 continue; 179 } 180 X509Certificate cert = readCertificate(fileName); 181 if (cert == null) { 182 continue; 183 } 184 if (!subj.equals(cert.getSubjectX500Principal())) { 185 continue; 186 } 187 if (selector.match(cert)) { 188 return cert; 189 } 190 } 191 return null; 192 } 193 194 private String getHash(X500Principal name) { 195 int hash = NativeCrypto.X509_NAME_hash_old(name); 196 return Hex.intToHexString(hash, 8); 197 } 198 199 private X509Certificate readCertificate(String file) { 200 InputStream is = null; 201 try { 202 is = new BufferedInputStream(new FileInputStream(new File(mDir, file))); 203 return (X509Certificate) mCertFactory.generateCertificate(is); 204 } catch (CertificateException | IOException e) { 205 Log.e(LOG_TAG, "Failed to read certificate from " + file, e); 206 return null; 207 } finally { 208 IoUtils.closeQuietly(is); 209 } 210 } 211 } 212