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