Home | History | Annotate | Download | only in http
      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      */
     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      */
    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
    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
    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      */
    179     public SslCertificate(X509Certificate certificate) {
    180         this(certificate.getSubjectDN().getName(),
    181              certificate.getIssuerDN().getName(),
    182              certificate.getNotBefore(),
    183              certificate.getNotAfter(),
    184              certificate);
    185     }
    186 
    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      */
    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
    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      */
    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
    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      */
    239     public DName getIssuedTo() {
    240         return mIssuedTo;
    241     }
    242 
    243     /**
    244      * @return Issued-by distinguished name or null if none has been set
    245      */
    246     public DName getIssuedBy() {
    247         return mIssuedBy;
    248     }
    249 
    250     /**
    251      * Convenience for UI presentation, not intended as public API.
    252      */
    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      */
    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 
    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      */
    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      */
    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      */
    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      */
    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          */
    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          */
    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          */
    424         public String getCName() {
    425             return mCName != null ? mCName : "";
    426         }
    427 
    428         /**
    429          * @return The most specific Organization (O) component of this name
    430          */
    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          */
    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      */
    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      */
    505     private String formatCertificateDate(Context context, Date certificateDate) {
    506         if (certificateDate == null) {
    507             return "";
    508         }
    509         return DateFormat.getMediumDateFormat(context).format(certificateDate);
    510     }
    511 }
    512