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