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 * - common name (CN), 338 * - organization (O), 339 * - organizational unit (OU) 340 */ 341 public class DName { 342 /** 343 * Distinguished name (normally includes CN, O, and OU names) 344 */ 345 private String mDName; 346 347 /** 348 * Common-name (CN) component of the name 349 */ 350 private String mCName; 351 352 /** 353 * Organization (O) component of the name 354 */ 355 private String mOName; 356 357 /** 358 * Organizational Unit (OU) component of the name 359 */ 360 private String mUName; 361 362 /** 363 * Creates a new distinguished name 364 * @param dName The distinguished name 365 */ 366 public DName(String dName) { 367 if (dName != null) { 368 mDName = dName; 369 try { 370 X509Name x509Name = new X509Name(dName); 371 372 Vector val = x509Name.getValues(); 373 Vector oid = x509Name.getOIDs(); 374 375 for (int i = 0; i < oid.size(); i++) { 376 if (oid.elementAt(i).equals(X509Name.CN)) { 377 mCName = (String) val.elementAt(i); 378 continue; 379 } 380 381 if (oid.elementAt(i).equals(X509Name.O)) { 382 mOName = (String) val.elementAt(i); 383 continue; 384 } 385 386 if (oid.elementAt(i).equals(X509Name.OU)) { 387 mUName = (String) val.elementAt(i); 388 continue; 389 } 390 } 391 } catch (IllegalArgumentException ex) { 392 // thrown if there is an error parsing the string 393 } 394 } 395 } 396 397 /** 398 * @return The distinguished name (normally includes CN, O, and OU names) 399 */ 400 public String getDName() { 401 return mDName != null ? mDName : ""; 402 } 403 404 /** 405 * @return The Common-name (CN) component of this name 406 */ 407 public String getCName() { 408 return mCName != null ? mCName : ""; 409 } 410 411 /** 412 * @return The Organization (O) component of this name 413 */ 414 public String getOName() { 415 return mOName != null ? mOName : ""; 416 } 417 418 /** 419 * @return The Organizational Unit (OU) component of this name 420 */ 421 public String getUName() { 422 return mUName != null ? mUName : ""; 423 } 424 } 425 426 /** 427 * Inflates the SSL certificate view (helper method). 428 * @return The resultant certificate view with issued-to, issued-by, 429 * issued-on, expires-on, and possibly other fields set. 430 * 431 * @hide Used by Browser and Settings 432 */ 433 public View inflateCertificateView(Context context) { 434 LayoutInflater factory = LayoutInflater.from(context); 435 436 View certificateView = factory.inflate( 437 com.android.internal.R.layout.ssl_certificate, null); 438 439 // issued to: 440 SslCertificate.DName issuedTo = getIssuedTo(); 441 if (issuedTo != null) { 442 ((TextView) certificateView.findViewById(com.android.internal.R.id.to_common)) 443 .setText(issuedTo.getCName()); 444 ((TextView) certificateView.findViewById(com.android.internal.R.id.to_org)) 445 .setText(issuedTo.getOName()); 446 ((TextView) certificateView.findViewById(com.android.internal.R.id.to_org_unit)) 447 .setText(issuedTo.getUName()); 448 } 449 // serial number: 450 ((TextView) certificateView.findViewById(com.android.internal.R.id.serial_number)) 451 .setText(getSerialNumber(mX509Certificate)); 452 453 // issued by: 454 SslCertificate.DName issuedBy = getIssuedBy(); 455 if (issuedBy != null) { 456 ((TextView) certificateView.findViewById(com.android.internal.R.id.by_common)) 457 .setText(issuedBy.getCName()); 458 ((TextView) certificateView.findViewById(com.android.internal.R.id.by_org)) 459 .setText(issuedBy.getOName()); 460 ((TextView) certificateView.findViewById(com.android.internal.R.id.by_org_unit)) 461 .setText(issuedBy.getUName()); 462 } 463 464 // issued on: 465 String issuedOn = formatCertificateDate(context, getValidNotBeforeDate()); 466 ((TextView) certificateView.findViewById(com.android.internal.R.id.issued_on)) 467 .setText(issuedOn); 468 469 // expires on: 470 String expiresOn = formatCertificateDate(context, getValidNotAfterDate()); 471 ((TextView) certificateView.findViewById(com.android.internal.R.id.expires_on)) 472 .setText(expiresOn); 473 474 // fingerprints: 475 ((TextView) certificateView.findViewById(com.android.internal.R.id.sha256_fingerprint)) 476 .setText(getDigest(mX509Certificate, "SHA256")); 477 ((TextView) certificateView.findViewById(com.android.internal.R.id.sha1_fingerprint)) 478 .setText(getDigest(mX509Certificate, "SHA1")); 479 480 return certificateView; 481 } 482 483 /** 484 * Formats the certificate date to a properly localized date string. 485 * @return Properly localized version of the certificate date string and 486 * the "" if it fails to localize. 487 */ 488 private String formatCertificateDate(Context context, Date certificateDate) { 489 if (certificateDate == null) { 490 return ""; 491 } 492 return DateFormat.getDateFormat(context).format(certificateDate); 493 } 494 } 495