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 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