1 /* 2 * Copyright (C) 2013 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 org.conscrypt; 18 19 import java.io.ByteArrayInputStream; 20 import java.io.ByteArrayOutputStream; 21 import java.io.InputStream; 22 import java.math.BigInteger; 23 import java.security.InvalidKeyException; 24 import java.security.NoSuchAlgorithmException; 25 import java.security.NoSuchProviderException; 26 import java.security.Principal; 27 import java.security.PublicKey; 28 import java.security.Signature; 29 import java.security.SignatureException; 30 import java.security.cert.CRLException; 31 import java.security.cert.Certificate; 32 import java.security.cert.X509CRL; 33 import java.security.cert.X509CRLEntry; 34 import java.security.cert.X509Certificate; 35 import java.util.ArrayList; 36 import java.util.Arrays; 37 import java.util.Calendar; 38 import java.util.Date; 39 import java.util.HashSet; 40 import java.util.List; 41 import java.util.Set; 42 import java.util.TimeZone; 43 import javax.security.auth.x500.X500Principal; 44 import org.conscrypt.OpenSSLX509CertificateFactory.ParsingException; 45 46 /** 47 * An implementation of {@link X509CRL} based on BoringSSL. 48 */ 49 final class OpenSSLX509CRL extends X509CRL { 50 private final long mContext; 51 private final Date thisUpdate; 52 private final Date nextUpdate; 53 54 private OpenSSLX509CRL(long ctx) throws ParsingException { 55 mContext = ctx; 56 // The legacy X509 OpenSSL APIs don't validate ASN1_TIME structures until access, so 57 // parse them here because this is the only time we're allowed to throw ParsingException 58 thisUpdate = toDate(NativeCrypto.X509_CRL_get_lastUpdate(mContext, this)); 59 nextUpdate = toDate(NativeCrypto.X509_CRL_get_nextUpdate(mContext, this)); 60 } 61 62 // Package-visible because it's also used by OpenSSLX509CRLEntry 63 static Date toDate(long asn1time) throws ParsingException { 64 Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 65 calendar.set(Calendar.MILLISECOND, 0); 66 NativeCrypto.ASN1_TIME_to_Calendar(asn1time, calendar); 67 return calendar.getTime(); 68 } 69 70 static OpenSSLX509CRL fromX509DerInputStream(InputStream is) throws ParsingException { 71 final OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true); 72 73 try { 74 final long crlCtx = NativeCrypto.d2i_X509_CRL_bio(bis.getBioContext()); 75 if (crlCtx == 0) { 76 return null; 77 } 78 return new OpenSSLX509CRL(crlCtx); 79 } catch (Exception e) { 80 throw new ParsingException(e); 81 } finally { 82 bis.release(); 83 } 84 } 85 86 static List<OpenSSLX509CRL> fromPkcs7DerInputStream(InputStream is) 87 throws ParsingException { 88 OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true); 89 90 final long[] certRefs; 91 try { 92 certRefs = NativeCrypto.d2i_PKCS7_bio(bis.getBioContext(), NativeCrypto.PKCS7_CRLS); 93 } catch (Exception e) { 94 throw new ParsingException(e); 95 } finally { 96 bis.release(); 97 } 98 99 final List<OpenSSLX509CRL> certs = new ArrayList<OpenSSLX509CRL>(certRefs.length); 100 for (int i = 0; i < certRefs.length; i++) { 101 if (certRefs[i] == 0) { 102 continue; 103 } 104 certs.add(new OpenSSLX509CRL(certRefs[i])); 105 } 106 return certs; 107 } 108 109 static OpenSSLX509CRL fromX509PemInputStream(InputStream is) throws ParsingException { 110 final OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true); 111 112 try { 113 final long crlCtx = NativeCrypto.PEM_read_bio_X509_CRL(bis.getBioContext()); 114 if (crlCtx == 0) { 115 return null; 116 } 117 return new OpenSSLX509CRL(crlCtx); 118 } catch (Exception e) { 119 throw new ParsingException(e); 120 } finally { 121 bis.release(); 122 } 123 } 124 125 static List<OpenSSLX509CRL> fromPkcs7PemInputStream(InputStream is) 126 throws ParsingException { 127 OpenSSLBIOInputStream bis = new OpenSSLBIOInputStream(is, true); 128 129 final long[] certRefs; 130 try { 131 certRefs = NativeCrypto.PEM_read_bio_PKCS7(bis.getBioContext(), 132 NativeCrypto.PKCS7_CRLS); 133 } catch (Exception e) { 134 throw new ParsingException(e); 135 } finally { 136 bis.release(); 137 } 138 139 final List<OpenSSLX509CRL> certs = new ArrayList<OpenSSLX509CRL>(certRefs.length); 140 for (int i = 0; i < certRefs.length; i++) { 141 if (certRefs[i] == 0) { 142 continue; 143 } 144 certs.add(new OpenSSLX509CRL(certRefs[i])); 145 } 146 return certs; 147 } 148 149 @Override 150 public Set<String> getCriticalExtensionOIDs() { 151 String[] critOids = 152 NativeCrypto.get_X509_CRL_ext_oids(mContext, this, NativeCrypto.EXTENSION_TYPE_CRITICAL); 153 154 /* 155 * This API has a special case that if there are no extensions, we 156 * should return null. So if we have no critical extensions, we'll check 157 * non-critical extensions. 158 */ 159 if ((critOids.length == 0) 160 && (NativeCrypto.get_X509_CRL_ext_oids(mContext, this, 161 NativeCrypto.EXTENSION_TYPE_NON_CRITICAL).length == 0)) { 162 return null; 163 } 164 165 return new HashSet<String>(Arrays.asList(critOids)); 166 } 167 168 @Override 169 public byte[] getExtensionValue(String oid) { 170 return NativeCrypto.X509_CRL_get_ext_oid(mContext, this, oid); 171 } 172 173 @Override 174 public Set<String> getNonCriticalExtensionOIDs() { 175 String[] nonCritOids = 176 NativeCrypto.get_X509_CRL_ext_oids(mContext, this, 177 NativeCrypto.EXTENSION_TYPE_NON_CRITICAL); 178 179 /* 180 * This API has a special case that if there are no extensions, we 181 * should return null. So if we have no non-critical extensions, we'll 182 * check critical extensions. 183 */ 184 if ((nonCritOids.length == 0) 185 && (NativeCrypto.get_X509_CRL_ext_oids(mContext, this, 186 NativeCrypto.EXTENSION_TYPE_CRITICAL).length == 0)) { 187 return null; 188 } 189 190 return new HashSet<String>(Arrays.asList(nonCritOids)); 191 } 192 193 @Override 194 public boolean hasUnsupportedCriticalExtension() { 195 final String[] criticalOids = 196 NativeCrypto.get_X509_CRL_ext_oids(mContext, this, NativeCrypto.EXTENSION_TYPE_CRITICAL); 197 for (String oid : criticalOids) { 198 final long extensionRef = NativeCrypto.X509_CRL_get_ext(mContext, this, oid); 199 if (NativeCrypto.X509_supported_extension(extensionRef) != 1) { 200 return true; 201 } 202 } 203 204 return false; 205 } 206 207 @Override 208 public byte[] getEncoded() throws CRLException { 209 return NativeCrypto.i2d_X509_CRL(mContext, this); 210 } 211 212 private void verifyOpenSSL(OpenSSLKey pkey) throws CRLException, NoSuchAlgorithmException, 213 InvalidKeyException, NoSuchProviderException, SignatureException { 214 NativeCrypto.X509_CRL_verify(mContext, this, pkey.getNativeRef()); 215 } 216 217 private void verifyInternal(PublicKey key, String sigProvider) throws CRLException, 218 NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, 219 SignatureException { 220 String sigAlg = getSigAlgName(); 221 if (sigAlg == null) { 222 sigAlg = getSigAlgOID(); 223 } 224 225 final Signature sig; 226 if (sigProvider == null) { 227 sig = Signature.getInstance(sigAlg); 228 } else { 229 sig = Signature.getInstance(sigAlg, sigProvider); 230 } 231 232 sig.initVerify(key); 233 sig.update(getTBSCertList()); 234 if (!sig.verify(getSignature())) { 235 throw new SignatureException("signature did not verify"); 236 } 237 } 238 239 @Override 240 public void verify(PublicKey key) throws CRLException, NoSuchAlgorithmException, 241 InvalidKeyException, NoSuchProviderException, SignatureException { 242 if (key instanceof OpenSSLKeyHolder) { 243 OpenSSLKey pkey = ((OpenSSLKeyHolder) key).getOpenSSLKey(); 244 verifyOpenSSL(pkey); 245 return; 246 } 247 248 verifyInternal(key, null); 249 } 250 251 @Override 252 public void verify(PublicKey key, String sigProvider) throws CRLException, 253 NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, 254 SignatureException { 255 verifyInternal(key, sigProvider); 256 } 257 258 @Override 259 public int getVersion() { 260 return (int) NativeCrypto.X509_CRL_get_version(mContext, this) + 1; 261 } 262 263 @Override 264 public Principal getIssuerDN() { 265 return getIssuerX500Principal(); 266 } 267 268 @Override 269 public X500Principal getIssuerX500Principal() { 270 final byte[] issuer = NativeCrypto.X509_CRL_get_issuer_name(mContext, this); 271 return new X500Principal(issuer); 272 } 273 274 @Override 275 public Date getThisUpdate() { 276 return (Date) thisUpdate.clone(); 277 } 278 279 @Override 280 public Date getNextUpdate() { 281 return (Date) nextUpdate.clone(); 282 } 283 284 @Override 285 public X509CRLEntry getRevokedCertificate(BigInteger serialNumber) { 286 final long revokedRef = NativeCrypto.X509_CRL_get0_by_serial(mContext, this, 287 serialNumber.toByteArray()); 288 if (revokedRef == 0) { 289 return null; 290 } 291 try { 292 return new OpenSSLX509CRLEntry(NativeCrypto.X509_REVOKED_dup(revokedRef)); 293 } catch (ParsingException e) { 294 return null; 295 } 296 } 297 298 @Override 299 public X509CRLEntry getRevokedCertificate(X509Certificate certificate) { 300 if (certificate instanceof OpenSSLX509Certificate) { 301 OpenSSLX509Certificate osslCert = (OpenSSLX509Certificate) certificate; 302 final long x509RevokedRef = NativeCrypto.X509_CRL_get0_by_cert(mContext, this, 303 osslCert.getContext(), osslCert); 304 305 if (x509RevokedRef == 0) { 306 return null; 307 } 308 309 try { 310 return new OpenSSLX509CRLEntry(NativeCrypto.X509_REVOKED_dup(x509RevokedRef)); 311 } catch (ParsingException e) { 312 return null; 313 } 314 } 315 316 return getRevokedCertificate(certificate.getSerialNumber()); 317 } 318 319 @Override 320 public Set<? extends X509CRLEntry> getRevokedCertificates() { 321 final long[] entryRefs = NativeCrypto.X509_CRL_get_REVOKED(mContext, this); 322 if (entryRefs == null || entryRefs.length == 0) { 323 return null; 324 } 325 326 final Set<OpenSSLX509CRLEntry> crlSet = new HashSet<OpenSSLX509CRLEntry>(); 327 for (long entryRef : entryRefs) { 328 try { 329 crlSet.add(new OpenSSLX509CRLEntry(entryRef)); 330 } catch (ParsingException e) { 331 // Skip this entry 332 } 333 } 334 335 return crlSet; 336 } 337 338 @Override 339 public byte[] getTBSCertList() throws CRLException { 340 return NativeCrypto.get_X509_CRL_crl_enc(mContext, this); 341 } 342 343 @Override 344 public byte[] getSignature() { 345 return NativeCrypto.get_X509_CRL_signature(mContext, this); 346 } 347 348 @Override 349 public String getSigAlgName() { 350 String oid = getSigAlgOID(); 351 String algName = Platform.oidToAlgorithmName(oid); 352 if (algName != null) { 353 return algName; 354 } 355 return oid; 356 } 357 358 @Override 359 public String getSigAlgOID() { 360 return NativeCrypto.get_X509_CRL_sig_alg_oid(mContext, this); 361 } 362 363 @Override 364 public byte[] getSigAlgParams() { 365 return NativeCrypto.get_X509_CRL_sig_alg_parameter(mContext, this); 366 } 367 368 @Override 369 public boolean isRevoked(Certificate cert) { 370 if (!(cert instanceof X509Certificate)) { 371 return false; 372 } 373 374 final OpenSSLX509Certificate osslCert; 375 if (cert instanceof OpenSSLX509Certificate) { 376 osslCert = (OpenSSLX509Certificate) cert; 377 } else { 378 try { 379 osslCert = OpenSSLX509Certificate.fromX509DerInputStream(new ByteArrayInputStream( 380 cert.getEncoded())); 381 } catch (Exception e) { 382 throw new RuntimeException("cannot convert certificate", e); 383 } 384 } 385 386 final long x509RevokedRef = NativeCrypto.X509_CRL_get0_by_cert(mContext, this, 387 osslCert.getContext(), osslCert); 388 389 return x509RevokedRef != 0; 390 } 391 392 @Override 393 public String toString() { 394 ByteArrayOutputStream os = new ByteArrayOutputStream(); 395 final long bioCtx = NativeCrypto.create_BIO_OutputStream(os); 396 try { 397 NativeCrypto.X509_CRL_print(bioCtx, mContext, this); 398 return os.toString(); 399 } finally { 400 NativeCrypto.BIO_free_all(bioCtx); 401 } 402 } 403 404 @Override 405 protected void finalize() throws Throwable { 406 try { 407 if (mContext != 0) { 408 NativeCrypto.X509_CRL_free(mContext, this); 409 } 410 } finally { 411 super.finalize(); 412 } 413 } 414 415 } 416