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