SslCertificate.java revision 6da00334478df64921b68fcbb45c9d1eef6f35bd
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
17package android.net.http;
18
19import android.content.Context;
20import android.os.Bundle;
21import android.text.format.DateFormat;
22import android.view.LayoutInflater;
23import android.view.View;
24import android.widget.TextView;
25
26import java.security.cert.X509Certificate;
27import java.text.ParseException;
28import java.text.SimpleDateFormat;
29import java.util.Date;
30import java.util.Vector;
31
32import com.android.org.bouncycastle.asn1.DERObjectIdentifier;
33import com.android.org.bouncycastle.asn1.x509.X509Name;
34
35/**
36 * SSL certificate info (certificate details) class
37 */
38public class SslCertificate {
39
40    /**
41     * SimpleDateFormat pattern for an ISO 8601 date
42     */
43    private static String ISO_8601_DATE_FORMAT = "yyyy-MM-dd HH:mm:ssZ";
44
45    /**
46     * Name of the entity this certificate is issued to
47     */
48    private DName mIssuedTo;
49
50    /**
51     * Name of the entity this certificate is issued by
52     */
53    private DName mIssuedBy;
54
55    /**
56     * Not-before date from the validity period
57     */
58    private Date mValidNotBefore;
59
60    /**
61     * Not-after date from the validity period
62     */
63    private Date mValidNotAfter;
64
65    /**
66     * Bundle key names
67     */
68    private static final String ISSUED_TO = "issued-to";
69    private static final String ISSUED_BY = "issued-by";
70    private static final String VALID_NOT_BEFORE = "valid-not-before";
71    private static final String VALID_NOT_AFTER = "valid-not-after";
72
73    /**
74     * Saves the certificate state to a bundle
75     * @param certificate The SSL certificate to store
76     * @return A bundle with the certificate stored in it or null if fails
77     */
78    public static Bundle saveState(SslCertificate certificate) {
79        Bundle bundle = null;
80
81        if (certificate != null) {
82            bundle = new Bundle();
83
84            bundle.putString(ISSUED_TO, certificate.getIssuedTo().getDName());
85            bundle.putString(ISSUED_BY, certificate.getIssuedBy().getDName());
86
87            bundle.putString(VALID_NOT_BEFORE, certificate.getValidNotBefore());
88            bundle.putString(VALID_NOT_AFTER, certificate.getValidNotAfter());
89        }
90
91        return bundle;
92    }
93
94    /**
95     * Restores the certificate stored in the bundle
96     * @param bundle The bundle with the certificate state stored in it
97     * @return The SSL certificate stored in the bundle or null if fails
98     */
99    public static SslCertificate restoreState(Bundle bundle) {
100        if (bundle != null) {
101            return new SslCertificate(
102                bundle.getString(ISSUED_TO),
103                bundle.getString(ISSUED_BY),
104                bundle.getString(VALID_NOT_BEFORE),
105                bundle.getString(VALID_NOT_AFTER));
106        }
107
108        return null;
109    }
110
111    /**
112     * Creates a new SSL certificate object
113     * @param issuedTo The entity this certificate is issued to
114     * @param issuedBy The entity that issued this certificate
115     * @param validNotBefore The not-before date from the certificate
116     *     validity period in ISO 8601 format
117     * @param validNotAfter The not-after date from the certificate
118     *     validity period in ISO 8601 format
119     * @deprecated Use {@link #SslCertificate(X509Certificate)}
120     */
121    @Deprecated
122    public SslCertificate(
123            String issuedTo, String issuedBy, String validNotBefore, String validNotAfter) {
124        this(issuedTo, issuedBy, parseDate(validNotBefore), parseDate(validNotAfter));
125    }
126
127    /**
128     * Creates a new SSL certificate object
129     * @param issuedTo The entity this certificate is issued to
130     * @param issuedBy The entity that issued this certificate
131     * @param validNotBefore The not-before date from the certificate validity period
132     * @param validNotAfter The not-after date from the certificate validity period
133     * @deprecated Use {@link #SslCertificate(X509Certificate)}
134     */
135    @Deprecated
136    public SslCertificate(
137            String issuedTo, String issuedBy, Date validNotBefore, Date validNotAfter) {
138        mIssuedTo = new DName(issuedTo);
139        mIssuedBy = new DName(issuedBy);
140        mValidNotBefore = cloneDate(validNotBefore);
141        mValidNotAfter  = cloneDate(validNotAfter);
142    }
143
144    /**
145     * Creates a new SSL certificate object from an X509 certificate
146     * @param certificate X509 certificate
147     */
148    public SslCertificate(X509Certificate certificate) {
149        this(certificate.getSubjectDN().getName(),
150             certificate.getIssuerDN().getName(),
151             certificate.getNotBefore(),
152             certificate.getNotAfter());
153    }
154
155    /**
156     * @return Not-before date from the certificate validity period or
157     * "" if none has been set
158     */
159    public Date getValidNotBeforeDate() {
160        return cloneDate(mValidNotBefore);
161    }
162
163    /**
164     * @return Not-before date from the certificate validity period in
165     * ISO 8601 format or "" if none has been set
166     *
167     * @deprecated Use {@link #getValidNotBeforeDate()}
168     */
169    @Deprecated
170    public String getValidNotBefore() {
171        return formatDate(mValidNotBefore);
172    }
173
174    /**
175     * @return Not-after date from the certificate validity period or
176     * "" if none has been set
177     */
178    public Date getValidNotAfterDate() {
179        return cloneDate(mValidNotAfter);
180    }
181
182    /**
183     * @return Not-after date from the certificate validity period in
184     * ISO 8601 format or "" if none has been set
185     *
186     * @deprecated Use {@link #getValidNotAfterDate()}
187     */
188    @Deprecated
189    public String getValidNotAfter() {
190        return formatDate(mValidNotAfter);
191    }
192
193    /**
194     * @return Issued-to distinguished name or null if none has been set
195     */
196    public DName getIssuedTo() {
197        return mIssuedTo;
198    }
199
200    /**
201     * @return Issued-by distinguished name or null if none has been set
202     */
203    public DName getIssuedBy() {
204        return mIssuedBy;
205    }
206
207    /**
208     * @return A string representation of this certificate for debugging
209     */
210    public String toString() {
211        return ("Issued to: " + mIssuedTo.getDName() + ";\n"
212                + "Issued by: " + mIssuedBy.getDName() + ";\n");
213    }
214
215    /**
216     * Parse an ISO 8601 date converting ParseExceptions to a null result;
217     */
218    private static Date parseDate(String string) {
219        try {
220            return new SimpleDateFormat(ISO_8601_DATE_FORMAT).parse(string);
221        } catch (ParseException e) {
222            return null;
223        }
224    }
225
226    /**
227     * Format a date as an ISO 8601 string, return "" for a null date
228     */
229    private static String formatDate(Date date) {
230        if (date == null) {
231            return "";
232        }
233        return new SimpleDateFormat(ISO_8601_DATE_FORMAT).format(date);
234    }
235
236    /**
237     * Clone a possibly null Date
238     */
239    private static Date cloneDate(Date date) {
240        if (date == null) {
241            return null;
242        }
243        return (Date) date.clone();
244    }
245
246    /**
247     * A distinguished name helper class: a 3-tuple of:
248     * - common name (CN),
249     * - organization (O),
250     * - organizational unit (OU)
251     */
252    public class DName {
253        /**
254         * Distinguished name (normally includes CN, O, and OU names)
255         */
256        private String mDName;
257
258        /**
259         * Common-name (CN) component of the name
260         */
261        private String mCName;
262
263        /**
264         * Organization (O) component of the name
265         */
266        private String mOName;
267
268        /**
269         * Organizational Unit (OU) component of the name
270         */
271        private String mUName;
272
273        /**
274         * Creates a new distinguished name
275         * @param dName The distinguished name
276         */
277        public DName(String dName) {
278            if (dName != null) {
279                mDName = dName;
280                try {
281                    X509Name x509Name = new X509Name(dName);
282
283                    Vector val = x509Name.getValues();
284                    Vector oid = x509Name.getOIDs();
285
286                    for (int i = 0; i < oid.size(); i++) {
287                        if (oid.elementAt(i).equals(X509Name.CN)) {
288                            mCName = (String) val.elementAt(i);
289                            continue;
290                        }
291
292                        if (oid.elementAt(i).equals(X509Name.O)) {
293                            mOName = (String) val.elementAt(i);
294                            continue;
295                        }
296
297                        if (oid.elementAt(i).equals(X509Name.OU)) {
298                            mUName = (String) val.elementAt(i);
299                            continue;
300                        }
301                    }
302                } catch (IllegalArgumentException ex) {
303                    // thrown if there is an error parsing the string
304                }
305            }
306        }
307
308        /**
309         * @return The distinguished name (normally includes CN, O, and OU names)
310         */
311        public String getDName() {
312            return mDName != null ? mDName : "";
313        }
314
315        /**
316         * @return The Common-name (CN) component of this name
317         */
318        public String getCName() {
319            return mCName != null ? mCName : "";
320        }
321
322        /**
323         * @return The Organization (O) component of this name
324         */
325        public String getOName() {
326            return mOName != null ? mOName : "";
327        }
328
329        /**
330         * @return The Organizational Unit (OU) component of this name
331         */
332        public String getUName() {
333            return mUName != null ? mUName : "";
334        }
335    }
336
337    /**
338     * Inflates the SSL certificate view (helper method).
339     * @return The resultant certificate view with issued-to, issued-by,
340     * issued-on, expires-on, and possibly other fields set.
341     * If the input certificate is null, returns null.
342     *
343     * @hide Used by Browser and Settings
344     */
345    public View inflateCertificateView(Context context) {
346        LayoutInflater factory = LayoutInflater.from(context);
347
348        View certificateView = factory.inflate(
349            com.android.internal.R.layout.ssl_certificate, null);
350
351        // issued to:
352        SslCertificate.DName issuedTo = getIssuedTo();
353        if (issuedTo != null) {
354            ((TextView) certificateView.findViewById(com.android.internal.R.id.to_common))
355                .setText(issuedTo.getCName());
356            ((TextView) certificateView.findViewById(com.android.internal.R.id.to_org))
357                .setText(issuedTo.getOName());
358            ((TextView) certificateView.findViewById(com.android.internal.R.id.to_org_unit))
359                .setText(issuedTo.getUName());
360        }
361
362        // issued by:
363        SslCertificate.DName issuedBy = getIssuedBy();
364        if (issuedBy != null) {
365            ((TextView) certificateView.findViewById(com.android.internal.R.id.by_common))
366                .setText(issuedBy.getCName());
367            ((TextView) certificateView.findViewById(com.android.internal.R.id.by_org))
368                .setText(issuedBy.getOName());
369            ((TextView) certificateView.findViewById(com.android.internal.R.id.by_org_unit))
370                .setText(issuedBy.getUName());
371        }
372
373        // issued on:
374        String issuedOn = formatCertificateDate(context, getValidNotBeforeDate());
375        ((TextView) certificateView.findViewById(com.android.internal.R.id.issued_on))
376            .setText(issuedOn);
377
378        // expires on:
379        String expiresOn = formatCertificateDate(context, getValidNotAfterDate());
380        ((TextView) certificateView.findViewById(com.android.internal.R.id.expires_on))
381            .setText(expiresOn);
382
383        return certificateView;
384    }
385
386    /**
387     * Formats the certificate date to a properly localized date string.
388     * @return Properly localized version of the certificate date string and
389     * the "" if it fails to localize.
390     */
391    private String formatCertificateDate(Context context, Date certificateDate) {
392        if (certificateDate == null) {
393            return "";
394        }
395        return DateFormat.getDateFormat(context).format(certificateDate);
396    }
397}
398