1// Copyright 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
5package org.chromium.chrome.browser;
6
7import android.app.Dialog;
8import android.content.Context;
9import android.graphics.Typeface;
10import android.net.http.SslCertificate;
11import android.text.format.DateFormat;
12import android.util.Log;
13import android.view.View;
14import android.widget.AdapterView;
15import android.widget.AdapterView.OnItemSelectedListener;
16import android.widget.ArrayAdapter;
17import android.widget.LinearLayout;
18import android.widget.ScrollView;
19import android.widget.Spinner;
20import android.widget.TextView;
21
22import org.chromium.chrome.R;
23
24import java.io.ByteArrayInputStream;
25import java.security.MessageDigest;
26import java.security.cert.Certificate;
27import java.security.cert.CertificateException;
28import java.security.cert.CertificateFactory;
29import java.security.cert.X509Certificate;
30import java.util.ArrayList;
31
32/**
33 * UI component for displaying certificate information.
34 */
35class 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_wide) / 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}
233