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 17package android.security.net.config; 18 19import android.os.Environment; 20import android.os.UserHandle; 21import android.util.ArraySet; 22import android.util.Pair; 23import java.io.BufferedInputStream; 24import java.io.File; 25import java.io.FileInputStream; 26import java.io.InputStream; 27import java.io.IOException; 28import java.security.cert.Certificate; 29import java.security.cert.CertificateException; 30import java.security.cert.CertificateFactory; 31import java.security.cert.X509Certificate; 32import java.util.Collections; 33import java.util.Set; 34import libcore.io.IoUtils; 35 36import com.android.org.conscrypt.Hex; 37import com.android.org.conscrypt.NativeCrypto; 38 39import javax.security.auth.x500.X500Principal; 40 41/** 42 * {@link CertificateSource} based on a directory where certificates are stored as individual files 43 * named after a hash of their SubjectName for more efficient lookups. 44 * @hide 45 */ 46abstract class DirectoryCertificateSource implements CertificateSource { 47 private final File mDir; 48 private final Object mLock = new Object(); 49 private final CertificateFactory mCertFactory; 50 51 private Set<X509Certificate> mCertificates; 52 53 protected DirectoryCertificateSource(File caDir) { 54 mDir = caDir; 55 try { 56 mCertFactory = CertificateFactory.getInstance("X.509"); 57 } catch (CertificateException e) { 58 throw new RuntimeException("Failed to obtain X.509 CertificateFactory", e); 59 } 60 } 61 62 protected abstract boolean isCertMarkedAsRemoved(String caFile); 63 64 @Override 65 public Set<X509Certificate> getCertificates() { 66 // TODO: loading all of these is wasteful, we should instead use a keystore style API. 67 synchronized (mLock) { 68 if (mCertificates != null) { 69 return mCertificates; 70 } 71 72 Set<X509Certificate> certs = new ArraySet<X509Certificate>(); 73 if (mDir.isDirectory()) { 74 for (String caFile : mDir.list()) { 75 if (isCertMarkedAsRemoved(caFile)) { 76 continue; 77 } 78 X509Certificate cert = readCertificate(caFile); 79 if (cert != null) { 80 certs.add(cert); 81 } 82 } 83 } 84 mCertificates = certs; 85 return mCertificates; 86 } 87 } 88 89 @Override 90 public X509Certificate findBySubjectAndPublicKey(final X509Certificate cert) { 91 return findCert(cert.getSubjectX500Principal(), new CertSelector() { 92 @Override 93 public boolean match(X509Certificate ca) { 94 return ca.getPublicKey().equals(cert.getPublicKey()); 95 } 96 }); 97 } 98 99 @Override 100 public X509Certificate findByIssuerAndSignature(final X509Certificate cert) { 101 return findCert(cert.getIssuerX500Principal(), new CertSelector() { 102 @Override 103 public boolean match(X509Certificate ca) { 104 try { 105 cert.verify(ca.getPublicKey()); 106 return true; 107 } catch (Exception e) { 108 return false; 109 } 110 } 111 }); 112 } 113 114 @Override 115 public Set<X509Certificate> findAllByIssuerAndSignature(final X509Certificate cert) { 116 return findCerts(cert.getIssuerX500Principal(), new CertSelector() { 117 @Override 118 public boolean match(X509Certificate ca) { 119 try { 120 cert.verify(ca.getPublicKey()); 121 return true; 122 } catch (Exception e) { 123 return false; 124 } 125 } 126 }); 127 } 128 129 @Override 130 public void handleTrustStorageUpdate() { 131 synchronized (mLock) { 132 mCertificates = null; 133 } 134 } 135 136 private static interface CertSelector { 137 boolean match(X509Certificate cert); 138 } 139 140 private Set<X509Certificate> findCerts(X500Principal subj, CertSelector selector) { 141 String hash = getHash(subj); 142 Set<X509Certificate> certs = null; 143 for (int index = 0; index >= 0; index++) { 144 String fileName = hash + "." + index; 145 if (!new File(mDir, fileName).exists()) { 146 break; 147 } 148 if (isCertMarkedAsRemoved(fileName)) { 149 continue; 150 } 151 X509Certificate cert = readCertificate(fileName); 152 if (!subj.equals(cert.getSubjectX500Principal())) { 153 continue; 154 } 155 if (selector.match(cert)) { 156 if (certs == null) { 157 certs = new ArraySet<X509Certificate>(); 158 } 159 certs.add(cert); 160 } 161 } 162 return certs != null ? certs : Collections.<X509Certificate>emptySet(); 163 } 164 165 private X509Certificate findCert(X500Principal subj, CertSelector selector) { 166 String hash = getHash(subj); 167 for (int index = 0; index >= 0; index++) { 168 String fileName = hash + "." + index; 169 if (!new File(mDir, fileName).exists()) { 170 break; 171 } 172 if (isCertMarkedAsRemoved(fileName)) { 173 continue; 174 } 175 X509Certificate cert = readCertificate(fileName); 176 if (!subj.equals(cert.getSubjectX500Principal())) { 177 continue; 178 } 179 if (selector.match(cert)) { 180 return cert; 181 } 182 } 183 return null; 184 } 185 186 private String getHash(X500Principal name) { 187 int hash = NativeCrypto.X509_NAME_hash_old(name); 188 return Hex.intToHexString(hash, 8); 189 } 190 191 private X509Certificate readCertificate(String file) { 192 InputStream is = null; 193 try { 194 is = new BufferedInputStream(new FileInputStream(new File(mDir, file))); 195 return (X509Certificate) mCertFactory.generateCertificate(is); 196 } catch (CertificateException | IOException e) { 197 return null; 198 } finally { 199 IoUtils.closeQuietly(is); 200 } 201 } 202} 203