1 | // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | package org.chromium.chrome.browser; |
6 | |
7 | import android.app.Dialog; |
8 | import android.content.Context; |
9 | import android.graphics.Typeface; |
10 | import android.net.http.SslCertificate; |
11 | import android.text.format.DateFormat; |
12 | import android.util.Log; |
13 | import android.view.View; |
14 | import android.widget.AdapterView; |
15 | import android.widget.AdapterView.OnItemSelectedListener; |
16 | import android.widget.ArrayAdapter; |
17 | import android.widget.LinearLayout; |
18 | import android.widget.ScrollView; |
19 | import android.widget.Spinner; |
20 | import android.widget.TextView; |
21 | |
22 | import org.chromium.chrome.R; |
23 | |
24 | import java.io.ByteArrayInputStream; |
25 | import java.security.MessageDigest; |
26 | import java.security.cert.Certificate; |
27 | import java.security.cert.CertificateException; |
28 | import java.security.cert.CertificateFactory; |
29 | import java.security.cert.X509Certificate; |
30 | import java.util.ArrayList; |
31 | |
32 | /** |
33 | * UI component for displaying certificate information. |
34 | */ |
35 | class CertificateViewer implements OnItemSelectedListener { |
36 | private static final String X_509 = "X.509"; |
37 | private final Context mContext; |
38 | private final ArrayList<LinearLayout> mViews; |
39 | private final ArrayList<String> mTitles; |
40 | private final int mPadding; |
41 | private CertificateFactory mCertificateFactory; |
42 | |
43 | /** |
44 | * Show a dialog with the provided certificate information. |
45 | * |
46 | * @param context The context this view should display in. |
47 | * @param derData DER-encoded data representing a X509 certificate chain. |
48 | */ |
49 | public static void showCertificateChain(Context context, byte[][] derData) { |
50 | CertificateViewer viewer = new CertificateViewer(context); |
51 | viewer.showCertificateChain(derData); |
52 | } |
53 | |
54 | private CertificateViewer(Context context) { |
55 | mContext = context; |
56 | mViews = new ArrayList<LinearLayout>(); |
57 | mTitles = new ArrayList<String>(); |
58 | mPadding = (int) context.getResources().getDimension( |
59 | R.dimen.certificate_viewer_padding) / 2; |
60 | } |
61 | |
62 | // Show information about an array of DER-encoded data representing a X509 certificate chain. |
63 | // A spinner will be displayed allowing the user to select which certificate to display. |
64 | private void showCertificateChain(byte[][] derData) { |
65 | for (int i = 0; i < derData.length; i++) { |
66 | addCertificate(derData[i]); |
67 | } |
68 | ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>(mContext, |
69 | android.R.layout.simple_spinner_item, |
70 | mTitles); |
71 | arrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); |
72 | |
73 | Spinner spinner = new Spinner(mContext); |
74 | spinner.setAdapter(arrayAdapter); |
75 | spinner.setOnItemSelectedListener(this); |
76 | |
77 | LinearLayout container = new LinearLayout(mContext); |
78 | container.setOrientation(LinearLayout.VERTICAL); |
79 | container.addView(spinner); |
80 | |
81 | for (int i = 0; i < mViews.size(); ++i) { |
82 | LinearLayout certificateView = mViews.get(i); |
83 | if (i != 0) { |
84 | certificateView.setVisibility(LinearLayout.GONE); |
85 | } |
86 | container.addView(certificateView); |
87 | } |
88 | |
89 | showDialogForView(container); |
90 | } |
91 | |
92 | // Displays a dialog with scrolling for the given view. |
93 | private void showDialogForView(View view) { |
94 | Dialog dialog = new Dialog(mContext); |
95 | dialog.setTitle(R.string.certtitle); |
96 | ScrollView scrollView = new ScrollView(mContext); |
97 | scrollView.addView(view); |
98 | dialog.addContentView(scrollView, |
99 | new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, |
100 | LinearLayout.LayoutParams.MATCH_PARENT)); |
101 | dialog.show(); |
102 | } |
103 | |
104 | private void addCertificate(byte[] derData) { |
105 | try { |
106 | if (mCertificateFactory == null) { |
107 | mCertificateFactory = CertificateFactory.getInstance(X_509); |
108 | } |
109 | Certificate cert = mCertificateFactory.generateCertificate( |
110 | new ByteArrayInputStream(derData)); |
111 | addCertificateDetails(cert, getDigest(derData, "SHA-256"), getDigest(derData, "SHA-1")); |
112 | } catch (CertificateException e) { |
113 | Log.e("CertViewer", "Error parsing certificate" + e.toString()); |
114 | } |
115 | } |
116 | |
117 | private void addCertificateDetails(Certificate cert, byte[] sha256Digest, byte[] sha1Digest) { |
118 | LinearLayout certificateView = new LinearLayout(mContext); |
119 | mViews.add(certificateView); |
120 | certificateView.setOrientation(LinearLayout.VERTICAL); |
121 | |
122 | X509Certificate x509 = (X509Certificate) cert; |
123 | SslCertificate sslCert = new SslCertificate(x509); |
124 | |
125 | mTitles.add(sslCert.getIssuedTo().getCName()); |
126 | |
127 | addSectionTitle(certificateView, nativeGetCertIssuedToText()); |
128 | addItem(certificateView, nativeGetCertInfoCommonNameText(), |
129 | sslCert.getIssuedTo().getCName()); |
130 | addItem(certificateView, nativeGetCertInfoOrganizationText(), |
131 | sslCert.getIssuedTo().getOName()); |
132 | addItem(certificateView, nativeGetCertInfoOrganizationUnitText(), |
133 | sslCert.getIssuedTo().getUName()); |
134 | addItem(certificateView, nativeGetCertInfoSerialNumberText(), |
135 | formatBytes(x509.getSerialNumber().toByteArray(), ':')); |
136 | |
137 | addSectionTitle(certificateView, nativeGetCertIssuedByText()); |
138 | addItem(certificateView, nativeGetCertInfoCommonNameText(), |
139 | sslCert.getIssuedBy().getCName()); |
140 | addItem(certificateView, nativeGetCertInfoOrganizationText(), |
141 | sslCert.getIssuedBy().getOName()); |
142 | addItem(certificateView, nativeGetCertInfoOrganizationUnitText(), |
143 | sslCert.getIssuedBy().getUName()); |
144 | |
145 | addSectionTitle(certificateView, nativeGetCertValidityText()); |
146 | java.text.DateFormat dateFormat = DateFormat.getDateFormat(mContext); |
147 | addItem(certificateView, nativeGetCertIssuedOnText(), |
148 | dateFormat.format(sslCert.getValidNotBeforeDate())); |
149 | addItem(certificateView, nativeGetCertExpiresOnText(), |
150 | dateFormat.format(sslCert.getValidNotAfterDate())); |
151 | |
152 | addSectionTitle(certificateView, nativeGetCertFingerprintsText()); |
153 | addItem(certificateView, nativeGetCertSHA256FingerprintText(), |
154 | formatBytes(sha256Digest, ' ')); |
155 | addItem(certificateView, nativeGetCertSHA1FingerprintText(), |
156 | formatBytes(sha1Digest, ' ')); |
157 | } |
158 | |
159 | private void addSectionTitle(LinearLayout certificateView, String label) { |
160 | TextView title = addLabel(certificateView, label); |
161 | title.setAllCaps(true); |
162 | } |
163 | |
164 | private void addItem(LinearLayout certificateView, String label, String value) { |
165 | if (value.isEmpty()) return; |
166 | |
167 | addLabel(certificateView, label); |
168 | addValue(certificateView, value); |
169 | } |
170 | |
171 | private TextView addLabel(LinearLayout certificateView, String label) { |
172 | TextView t = new TextView(mContext); |
173 | t.setPadding(mPadding, mPadding / 2, mPadding, 0); |
174 | t.setText(label); |
175 | t.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD)); |
176 | certificateView.addView(t); |
177 | return t; |
178 | } |
179 | |
180 | private void addValue(LinearLayout certificateView, String value) { |
181 | TextView t = new TextView(mContext); |
182 | t.setText(value); |
183 | t.setPadding(mPadding, 0, mPadding, mPadding / 2); |
184 | certificateView.addView(t); |
185 | } |
186 | |
187 | private static String formatBytes(byte[] bytes, char separator) { |
188 | StringBuilder sb = new StringBuilder(); |
189 | for (int i = 0; i < bytes.length; i++) { |
190 | sb.append(String.format("%02X", bytes[i])); |
191 | if (i != bytes.length - 1) { |
192 | sb.append(separator); |
193 | } |
194 | } |
195 | return sb.toString(); |
196 | } |
197 | |
198 | private static byte[] getDigest(byte[] bytes, String algorithm) { |
199 | try { |
200 | MessageDigest md = MessageDigest.getInstance(algorithm); |
201 | md.update(bytes); |
202 | return md.digest(); |
203 | } catch (java.security.NoSuchAlgorithmException e) { |
204 | return null; |
205 | } |
206 | } |
207 | |
208 | @Override |
209 | public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { |
210 | for (int i = 0; i < mViews.size(); ++i) { |
211 | mViews.get(i).setVisibility( |
212 | i == position ? LinearLayout.VISIBLE : LinearLayout.GONE); |
213 | } |
214 | } |
215 | |
216 | @Override |
217 | public void onNothingSelected(AdapterView<?> parent) { |
218 | } |
219 | |
220 | private static native String nativeGetCertIssuedToText(); |
221 | private static native String nativeGetCertInfoCommonNameText(); |
222 | private static native String nativeGetCertInfoOrganizationText(); |
223 | private static native String nativeGetCertInfoSerialNumberText(); |
224 | private static native String nativeGetCertInfoOrganizationUnitText(); |
225 | private static native String nativeGetCertIssuedByText(); |
226 | private static native String nativeGetCertValidityText(); |
227 | private static native String nativeGetCertIssuedOnText(); |
228 | private static native String nativeGetCertExpiresOnText(); |
229 | private static native String nativeGetCertFingerprintsText(); |
230 | private static native String nativeGetCertSHA256FingerprintText(); |
231 | private static native String nativeGetCertSHA1FingerprintText(); |
232 | } |