2 * Copyright (C) 2006 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package android
.net
.http
;
19 import com
.android
.internal
.util
.HexDump
;
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
;
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
;
42 import com
.android
.org
.bouncycastle
.asn1
.x509
.X509Name
;
45 * SSL certificate info (certificate details) class
47 public class SslCertificate
{
50 * SimpleDateFormat pattern for an ISO 8601 date
52 private static String ISO_8601_DATE_FORMAT
= "yyyy-MM-dd HH:mm:ssZ";
55 * Name of the entity this certificate is issued to
57 private final DName mIssuedTo
;
60 * Name of the entity this certificate is issued by
62 private final DName mIssuedBy
;
65 * Not-before date from the validity period
67 private final Date mValidNotBefore
;
70 * Not-after date from the validity period
72 private final Date mValidNotAfter
;
75 * The original source certificate, if available.
77 * TODO If deprecated constructors are removed, this should always
78 * be available, and saveState and restoreState can be simplified
79 * to be unconditional.
81 private final X509Certificate mX509Certificate
;
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";
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
97 public static Bundle
saveState(SslCertificate certificate
) {
98 if (certificate
== null) {
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) {
109 bundle
.putByteArray(X509_CERTIFICATE
, x509Certificate
.getEncoded());
110 } catch (CertificateEncodingException ignored
) {
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
121 public static SslCertificate
restoreState(Bundle bundle
) {
122 if (bundle
== null) {
125 X509Certificate x509Certificate
;
126 byte[] bytes
= bundle
.getByteArray(X509_CERTIFICATE
);
128 x509Certificate
= null;
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;
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
)),
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)}
156 public SslCertificate(
157 String issuedTo
, String issuedBy
, String validNotBefore
, String validNotAfter
) {
158 this(issuedTo
, issuedBy
, parseDate(validNotBefore
), parseDate(validNotAfter
), null);
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)}
170 public SslCertificate(
171 String issuedTo
, String issuedBy
, Date validNotBefore
, Date validNotAfter
) {
172 this(issuedTo
, issuedBy
, validNotBefore
, validNotAfter
, null);
176 * Creates a new SSL certificate object from an X509 certificate
177 * @param certificate X509 certificate
179 public SslCertificate(X509Certificate certificate
) {
180 this(certificate
.getSubjectDN().getName(),
181 certificate
.getIssuerDN().getName(),
182 certificate
.getNotBefore(),
183 certificate
.getNotAfter(),
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
;
199 * @return Not-before date from the certificate validity period or
200 * "" if none has been set
202 public Date
getValidNotBeforeDate() {
203 return cloneDate(mValidNotBefore
);
207 * @return Not-before date from the certificate validity period in
208 * ISO 8601 format or "" if none has been set
210 * @deprecated Use {@link #getValidNotBeforeDate()}
213 public String
getValidNotBefore() {
214 return formatDate(mValidNotBefore
);
218 * @return Not-after date from the certificate validity period or
219 * "" if none has been set
221 public Date
getValidNotAfterDate() {
222 return cloneDate(mValidNotAfter
);
226 * @return Not-after date from the certificate validity period in
227 * ISO 8601 format or "" if none has been set
229 * @deprecated Use {@link #getValidNotAfterDate()}
232 public String
getValidNotAfter() {
233 return formatDate(mValidNotAfter
);
237 * @return Issued-to distinguished name or null if none has been set
239 public DName
getIssuedTo() {
244 * @return Issued-by distinguished name or null if none has been set
246 public DName
getIssuedBy() {
251 * Convenience for UI presentation, not intended as public API.
253 private static String
getSerialNumber(X509Certificate x509Certificate
) {
254 if (x509Certificate
== null) {
257 BigInteger serialNumber
= x509Certificate
.getSerialNumber();
258 if (serialNumber
== null) {
261 return fingerprint(serialNumber
.toByteArray());
265 * Convenience for UI presentation, not intended as public API.
267 private static String
getDigest(X509Certificate x509Certificate
, String algorithm
) {
268 if (x509Certificate
== null) {
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
) {
278 } catch (NoSuchAlgorithmException ignored
) {
283 private static final String
fingerprint(byte[] bytes
) {
287 StringBuilder sb
= new StringBuilder();
288 for (int i
= 0; i
< bytes
.length
; i
++) {
290 HexDump
.appendByteAsHex(sb
, b
, true);
291 if (i
+1 != bytes
.length
) {
295 return sb
.toString();
299 * @return A string representation of this certificate for debugging
301 public String
toString() {
302 return ("Issued to: " + mIssuedTo
.getDName() + ";\n"
303 + "Issued by: " + mIssuedBy
.getDName() + ";\n");
307 * Parse an ISO 8601 date converting ParseExceptions to a null result;
309 private static Date
parseDate(String string
) {
311 return new SimpleDateFormat(ISO_8601_DATE_FORMAT
).parse(string
);
312 } catch (ParseException e
) {
318 * Format a date as an ISO 8601 string, return "" for a null date
320 private static String
formatDate(Date date
) {
324 return new SimpleDateFormat(ISO_8601_DATE_FORMAT
).format(date
);
328 * Clone a possibly null Date
330 private static Date
cloneDate(Date date
) {
334 return (Date
) date
.clone();
338 * A distinguished name helper class: a 3-tuple of:
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>
347 * Distinguished name (normally includes CN, O, and OU names)
349 private String mDName
;
352 * Common-name (CN) component of the name
354 private String mCName
;
357 * Organization (O) component of the name
359 private String mOName
;
362 * Organizational Unit (OU) component of the name
364 private String mUName
;
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
377 public DName(String dName
) {
381 X509Name x509Name
= new X509Name(dName
);
383 Vector val
= x509Name
.getValues();
384 Vector oid
= x509Name
.getOIDs();
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
);
394 if (oid
.elementAt(i
).equals(X509Name
.O
)) {
395 if (mOName
== null) {
396 mOName
= (String
) val
.elementAt(i
);
401 if (oid
.elementAt(i
).equals(X509Name
.OU
)) {
402 if (mUName
== null) {
403 mUName
= (String
) val
.elementAt(i
);
408 } catch (IllegalArgumentException ex
) {
409 // thrown if there is an error parsing the string
415 * @return The distinguished name (normally includes CN, O, and OU names)
417 public String
getDName() {
418 return mDName
!= null ? mDName
: "";
422 * @return The most specific Common-name (CN) component of this name
424 public String
getCName() {
425 return mCName
!= null ? mCName
: "";
429 * @return The most specific Organization (O) component of this name
431 public String
getOName() {
432 return mOName
!= null ? mOName
: "";
436 * @return The most specific Organizational Unit (OU) component of this name
438 public String
getUName() {
439 return mUName
!= null ? mUName
: "";
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.
448 * @hide Used by Browser and Settings
450 public View
inflateCertificateView(Context context
) {
451 LayoutInflater factory
= LayoutInflater
.from(context
);
453 View certificateView
= factory
.inflate(
454 com
.android
.internal
.R
.layout
.ssl_certificate
, null);
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());
467 ((TextView
) certificateView
.findViewById(com
.android
.internal
.R
.id
.serial_number
))
468 .setText(getSerialNumber(mX509Certificate
));
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());
482 String issuedOn
= formatCertificateDate(context
, getValidNotBeforeDate());
483 ((TextView
) certificateView
.findViewById(com
.android
.internal
.R
.id
.issued_on
))
487 String expiresOn
= formatCertificateDate(context
, getValidNotAfterDate());
488 ((TextView
) certificateView
.findViewById(com
.android
.internal
.R
.id
.expires_on
))
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"));
497 return certificateView
;
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.
505 private String
formatCertificateDate(Context context
, Date certificateDate
) {
506 if (certificateDate
== null) {
509 return DateFormat
.getDateFormat(context
).format(certificateDate
);