1 /* 2 * Copyright (C) 2006 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.net.http; 18 19 import android.annotation.Nullable; 20 import android.compat.annotation.UnsupportedAppUsage; 21 import android.content.Context; 22 import android.os.Bundle; 23 import android.text.format.DateFormat; 24 import android.view.LayoutInflater; 25 import android.view.View; 26 import android.widget.TextView; 27 28 import com.android.internal.util.HexDump; 29 import com.android.internal.org.bouncycastle.asn1.x509.X509Name; 30 31 import java.io.ByteArrayInputStream; 32 import java.math.BigInteger; 33 import java.security.MessageDigest; 34 import java.security.NoSuchAlgorithmException; 35 import java.security.cert.Certificate; 36 import java.security.cert.CertificateEncodingException; 37 import java.security.cert.CertificateException; 38 import java.security.cert.CertificateFactory; 39 import java.security.cert.X509Certificate; 40 import java.text.ParseException; 41 import java.text.SimpleDateFormat; 42 import java.util.Date; 43 import java.util.Vector; 44 45 /** 46 * SSL certificate info (certificate details) class 47 */ 48 public class SslCertificate { 49 50 /** 51 * SimpleDateFormat pattern for an ISO 8601 date 52 */ 53 private static String ISO_8601_DATE_FORMAT = "yyyy-MM-dd HH:mm:ssZ"; 54 55 /** 56 * Name of the entity this certificate is issued to 57 */ 58 private final DName mIssuedTo; 59 60 /** 61 * Name of the entity this certificate is issued by 62 */ 63 private final DName mIssuedBy; 64 65 /** 66 * Not-before date from the validity period 67 */ 68 private final Date mValidNotBefore; 69 70 /** 71 * Not-after date from the validity period 72 */ 73 private final Date mValidNotAfter; 74 75 /** 76 * The original source certificate, if available. 77 * 78 * TODO If deprecated constructors are removed, this should always 79 * be available, and saveState and restoreState can be simplified 80 * to be unconditional. 81 */ 82 @UnsupportedAppUsage 83 private final X509Certificate mX509Certificate; 84 85 /** 86 * Bundle key names 87 */ 88 private static final String ISSUED_TO = "issued-to"; 89 private static final String ISSUED_BY = "issued-by"; 90 private static final String VALID_NOT_BEFORE = "valid-not-before"; 91 private static final String VALID_NOT_AFTER = "valid-not-after"; 92 private static final String X509_CERTIFICATE = "x509-certificate"; 93 94 /** 95 * Saves the certificate state to a bundle 96 * @param certificate The SSL certificate to store 97 * @return A bundle with the certificate stored in it or null if fails 98 */ saveState(SslCertificate certificate)99 public static Bundle saveState(SslCertificate certificate) { 100 if (certificate == null) { 101 return null; 102 } 103 Bundle bundle = new Bundle(); 104 bundle.putString(ISSUED_TO, certificate.getIssuedTo().getDName()); 105 bundle.putString(ISSUED_BY, certificate.getIssuedBy().getDName()); 106 bundle.putString(VALID_NOT_BEFORE, certificate.getValidNotBefore()); 107 bundle.putString(VALID_NOT_AFTER, certificate.getValidNotAfter()); 108 X509Certificate x509Certificate = certificate.mX509Certificate; 109 if (x509Certificate != null) { 110 try { 111 bundle.putByteArray(X509_CERTIFICATE, x509Certificate.getEncoded()); 112 } catch (CertificateEncodingException ignored) { 113 } 114 } 115 return bundle; 116 } 117 118 /** 119 * Restores the certificate stored in the bundle 120 * @param bundle The bundle with the certificate state stored in it 121 * @return The SSL certificate stored in the bundle or null if fails 122 */ restoreState(Bundle bundle)123 public static SslCertificate restoreState(Bundle bundle) { 124 if (bundle == null) { 125 return null; 126 } 127 X509Certificate x509Certificate; 128 byte[] bytes = bundle.getByteArray(X509_CERTIFICATE); 129 if (bytes == null) { 130 x509Certificate = null; 131 } else { 132 try { 133 CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); 134 Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(bytes)); 135 x509Certificate = (X509Certificate) cert; 136 } catch (CertificateException e) { 137 x509Certificate = null; 138 } 139 } 140 return new SslCertificate(bundle.getString(ISSUED_TO), 141 bundle.getString(ISSUED_BY), 142 parseDate(bundle.getString(VALID_NOT_BEFORE)), 143 parseDate(bundle.getString(VALID_NOT_AFTER)), 144 x509Certificate); 145 } 146 147 /** 148 * Creates a new SSL certificate object 149 * @param issuedTo The entity this certificate is issued to 150 * @param issuedBy The entity that issued this certificate 151 * @param validNotBefore The not-before date from the certificate 152 * validity period in ISO 8601 format 153 * @param validNotAfter The not-after date from the certificate 154 * validity period in ISO 8601 format 155 * @deprecated Use {@link #SslCertificate(X509Certificate)} 156 */ 157 @Deprecated SslCertificate( String issuedTo, String issuedBy, String validNotBefore, String validNotAfter)158 public SslCertificate( 159 String issuedTo, String issuedBy, String validNotBefore, String validNotAfter) { 160 this(issuedTo, issuedBy, parseDate(validNotBefore), parseDate(validNotAfter), null); 161 } 162 163 /** 164 * Creates a new SSL certificate object 165 * @param issuedTo The entity this certificate is issued to 166 * @param issuedBy The entity that issued this certificate 167 * @param validNotBefore The not-before date from the certificate validity period 168 * @param validNotAfter The not-after date from the certificate validity period 169 * @deprecated Use {@link #SslCertificate(X509Certificate)} 170 */ 171 @Deprecated SslCertificate( String issuedTo, String issuedBy, Date validNotBefore, Date validNotAfter)172 public SslCertificate( 173 String issuedTo, String issuedBy, Date validNotBefore, Date validNotAfter) { 174 this(issuedTo, issuedBy, validNotBefore, validNotAfter, null); 175 } 176 177 /** 178 * Creates a new SSL certificate object from an X509 certificate 179 * @param certificate X509 certificate 180 */ SslCertificate(X509Certificate certificate)181 public SslCertificate(X509Certificate certificate) { 182 this(certificate.getSubjectDN().getName(), 183 certificate.getIssuerDN().getName(), 184 certificate.getNotBefore(), 185 certificate.getNotAfter(), 186 certificate); 187 } 188 SslCertificate( String issuedTo, String issuedBy, Date validNotBefore, Date validNotAfter, X509Certificate x509Certificate)189 private SslCertificate( 190 String issuedTo, String issuedBy, 191 Date validNotBefore, Date validNotAfter, 192 X509Certificate x509Certificate) { 193 mIssuedTo = new DName(issuedTo); 194 mIssuedBy = new DName(issuedBy); 195 mValidNotBefore = cloneDate(validNotBefore); 196 mValidNotAfter = cloneDate(validNotAfter); 197 mX509Certificate = x509Certificate; 198 } 199 200 /** 201 * @return Not-before date from the certificate validity period or 202 * "" if none has been set 203 */ getValidNotBeforeDate()204 public Date getValidNotBeforeDate() { 205 return cloneDate(mValidNotBefore); 206 } 207 208 /** 209 * @return Not-before date from the certificate validity period in 210 * ISO 8601 format or "" if none has been set 211 * 212 * @deprecated Use {@link #getValidNotBeforeDate()} 213 */ 214 @Deprecated getValidNotBefore()215 public String getValidNotBefore() { 216 return formatDate(mValidNotBefore); 217 } 218 219 /** 220 * @return Not-after date from the certificate validity period or 221 * "" if none has been set 222 */ getValidNotAfterDate()223 public Date getValidNotAfterDate() { 224 return cloneDate(mValidNotAfter); 225 } 226 227 /** 228 * @return Not-after date from the certificate validity period in 229 * ISO 8601 format or "" if none has been set 230 * 231 * @deprecated Use {@link #getValidNotAfterDate()} 232 */ 233 @Deprecated getValidNotAfter()234 public String getValidNotAfter() { 235 return formatDate(mValidNotAfter); 236 } 237 238 /** 239 * @return Issued-to distinguished name or null if none has been set 240 */ getIssuedTo()241 public DName getIssuedTo() { 242 return mIssuedTo; 243 } 244 245 /** 246 * @return Issued-by distinguished name or null if none has been set 247 */ getIssuedBy()248 public DName getIssuedBy() { 249 return mIssuedBy; 250 } 251 252 /** 253 * @return The {@code X509Certificate} used to create this {@code SslCertificate} or 254 * {@code null} if no certificate was provided. 255 */ getX509Certificate()256 public @Nullable X509Certificate getX509Certificate() { 257 return mX509Certificate; 258 } 259 260 /** 261 * Convenience for UI presentation, not intended as public API. 262 */ 263 @UnsupportedAppUsage getSerialNumber(X509Certificate x509Certificate)264 private static String getSerialNumber(X509Certificate x509Certificate) { 265 if (x509Certificate == null) { 266 return ""; 267 } 268 BigInteger serialNumber = x509Certificate.getSerialNumber(); 269 if (serialNumber == null) { 270 return ""; 271 } 272 return fingerprint(serialNumber.toByteArray()); 273 } 274 275 /** 276 * Convenience for UI presentation, not intended as public API. 277 */ 278 @UnsupportedAppUsage getDigest(X509Certificate x509Certificate, String algorithm)279 private static String getDigest(X509Certificate x509Certificate, String algorithm) { 280 if (x509Certificate == null) { 281 return ""; 282 } 283 try { 284 byte[] bytes = x509Certificate.getEncoded(); 285 MessageDigest md = MessageDigest.getInstance(algorithm); 286 byte[] digest = md.digest(bytes); 287 return fingerprint(digest); 288 } catch (CertificateEncodingException ignored) { 289 return ""; 290 } catch (NoSuchAlgorithmException ignored) { 291 return ""; 292 } 293 } 294 fingerprint(byte[] bytes)295 private static final String fingerprint(byte[] bytes) { 296 if (bytes == null) { 297 return ""; 298 } 299 StringBuilder sb = new StringBuilder(); 300 for (int i = 0; i < bytes.length; i++) { 301 byte b = bytes[i]; 302 HexDump.appendByteAsHex(sb, b, true); 303 if (i+1 != bytes.length) { 304 sb.append(':'); 305 } 306 } 307 return sb.toString(); 308 } 309 310 /** 311 * @return A string representation of this certificate for debugging 312 */ toString()313 public String toString() { 314 return ("Issued to: " + mIssuedTo.getDName() + ";\n" 315 + "Issued by: " + mIssuedBy.getDName() + ";\n"); 316 } 317 318 /** 319 * Parse an ISO 8601 date converting ParseExceptions to a null result; 320 */ parseDate(String string)321 private static Date parseDate(String string) { 322 try { 323 return new SimpleDateFormat(ISO_8601_DATE_FORMAT).parse(string); 324 } catch (ParseException e) { 325 return null; 326 } 327 } 328 329 /** 330 * Format a date as an ISO 8601 string, return "" for a null date 331 */ formatDate(Date date)332 private static String formatDate(Date date) { 333 if (date == null) { 334 return ""; 335 } 336 return new SimpleDateFormat(ISO_8601_DATE_FORMAT).format(date); 337 } 338 339 /** 340 * Clone a possibly null Date 341 */ cloneDate(Date date)342 private static Date cloneDate(Date date) { 343 if (date == null) { 344 return null; 345 } 346 return (Date) date.clone(); 347 } 348 349 /** 350 * A distinguished name helper class: a 3-tuple of: 351 * <ul> 352 * <li>the most specific common name (CN)</li> 353 * <li>the most specific organization (O)</li> 354 * <li>the most specific organizational unit (OU)</li> 355 * <ul> 356 */ 357 public class DName { 358 /** 359 * Distinguished name (normally includes CN, O, and OU names) 360 */ 361 private String mDName; 362 363 /** 364 * Common-name (CN) component of the name 365 */ 366 private String mCName; 367 368 /** 369 * Organization (O) component of the name 370 */ 371 private String mOName; 372 373 /** 374 * Organizational Unit (OU) component of the name 375 */ 376 private String mUName; 377 378 /** 379 * Creates a new {@code DName} from a string. The attributes 380 * are assumed to come in most significant to least 381 * significant order which is true of human readable values 382 * returned by methods such as {@code X500Principal.getName()}. 383 * Be aware that the underlying sources of distinguished names 384 * such as instances of {@code X509Certificate} are encoded in 385 * least significant to most significant order, so make sure 386 * the value passed here has the expected ordering of 387 * attributes. 388 */ DName(String dName)389 public DName(String dName) { 390 if (dName != null) { 391 mDName = dName; 392 try { 393 X509Name x509Name = new X509Name(dName); 394 395 Vector val = x509Name.getValues(); 396 Vector oid = x509Name.getOIDs(); 397 398 for (int i = 0; i < oid.size(); i++) { 399 if (oid.elementAt(i).equals(X509Name.CN)) { 400 if (mCName == null) { 401 mCName = (String) val.elementAt(i); 402 } 403 continue; 404 } 405 406 if (oid.elementAt(i).equals(X509Name.O)) { 407 if (mOName == null) { 408 mOName = (String) val.elementAt(i); 409 continue; 410 } 411 } 412 413 if (oid.elementAt(i).equals(X509Name.OU)) { 414 if (mUName == null) { 415 mUName = (String) val.elementAt(i); 416 continue; 417 } 418 } 419 } 420 } catch (IllegalArgumentException ex) { 421 // thrown if there is an error parsing the string 422 } 423 } 424 } 425 426 /** 427 * @return The distinguished name (normally includes CN, O, and OU names) 428 */ getDName()429 public String getDName() { 430 return mDName != null ? mDName : ""; 431 } 432 433 /** 434 * @return The most specific Common-name (CN) component of this name 435 */ getCName()436 public String getCName() { 437 return mCName != null ? mCName : ""; 438 } 439 440 /** 441 * @return The most specific Organization (O) component of this name 442 */ getOName()443 public String getOName() { 444 return mOName != null ? mOName : ""; 445 } 446 447 /** 448 * @return The most specific Organizational Unit (OU) component of this name 449 */ getUName()450 public String getUName() { 451 return mUName != null ? mUName : ""; 452 } 453 } 454 455 /** 456 * Inflates the SSL certificate view (helper method). 457 * @return The resultant certificate view with issued-to, issued-by, 458 * issued-on, expires-on, and possibly other fields set. 459 * 460 * @hide Used by Browser and Settings 461 */ 462 @UnsupportedAppUsage inflateCertificateView(Context context)463 public View inflateCertificateView(Context context) { 464 LayoutInflater factory = LayoutInflater.from(context); 465 466 View certificateView = factory.inflate( 467 com.android.internal.R.layout.ssl_certificate, null); 468 469 // issued to: 470 SslCertificate.DName issuedTo = getIssuedTo(); 471 if (issuedTo != null) { 472 ((TextView) certificateView.findViewById(com.android.internal.R.id.to_common)) 473 .setText(issuedTo.getCName()); 474 ((TextView) certificateView.findViewById(com.android.internal.R.id.to_org)) 475 .setText(issuedTo.getOName()); 476 ((TextView) certificateView.findViewById(com.android.internal.R.id.to_org_unit)) 477 .setText(issuedTo.getUName()); 478 } 479 // serial number: 480 ((TextView) certificateView.findViewById(com.android.internal.R.id.serial_number)) 481 .setText(getSerialNumber(mX509Certificate)); 482 483 // issued by: 484 SslCertificate.DName issuedBy = getIssuedBy(); 485 if (issuedBy != null) { 486 ((TextView) certificateView.findViewById(com.android.internal.R.id.by_common)) 487 .setText(issuedBy.getCName()); 488 ((TextView) certificateView.findViewById(com.android.internal.R.id.by_org)) 489 .setText(issuedBy.getOName()); 490 ((TextView) certificateView.findViewById(com.android.internal.R.id.by_org_unit)) 491 .setText(issuedBy.getUName()); 492 } 493 494 // issued on: 495 String issuedOn = formatCertificateDate(context, getValidNotBeforeDate()); 496 ((TextView) certificateView.findViewById(com.android.internal.R.id.issued_on)) 497 .setText(issuedOn); 498 499 // expires on: 500 String expiresOn = formatCertificateDate(context, getValidNotAfterDate()); 501 ((TextView) certificateView.findViewById(com.android.internal.R.id.expires_on)) 502 .setText(expiresOn); 503 504 // fingerprints: 505 ((TextView) certificateView.findViewById(com.android.internal.R.id.sha256_fingerprint)) 506 .setText(getDigest(mX509Certificate, "SHA256")); 507 ((TextView) certificateView.findViewById(com.android.internal.R.id.sha1_fingerprint)) 508 .setText(getDigest(mX509Certificate, "SHA1")); 509 510 return certificateView; 511 } 512 513 /** 514 * Formats the certificate date to a properly localized date string. 515 * @return Properly localized version of the certificate date string and 516 * the "" if it fails to localize. 517 */ formatCertificateDate(Context context, Date certificateDate)518 private String formatCertificateDate(Context context, Date certificateDate) { 519 if (certificateDate == null) { 520 return ""; 521 } 522 return DateFormat.getMediumDateFormat(context).format(certificateDate); 523 } 524 } 525