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