OC-3208: Unify both SSL dialogs taking information from X509Certificate in just one
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / dialog / SslUntrustedCertDialog.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012-2014 ownCloud Inc.
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2,
6 * as published by the Free Software Foundation.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 */
17 package com.owncloud.android.ui.dialog;
18
19 import java.io.IOException;
20 import java.security.GeneralSecurityException;
21 import java.security.KeyStoreException;
22 import java.security.NoSuchAlgorithmException;
23 import java.security.cert.CertificateException;
24 import java.security.cert.X509Certificate;
25 import java.util.Date;
26 import java.util.HashMap;
27 import java.util.Map;
28
29 import javax.security.auth.x500.X500Principal;
30
31 import com.actionbarsherlock.app.SherlockActivity;
32 import com.actionbarsherlock.app.SherlockDialogFragment;
33 import com.owncloud.android.R;
34 import com.owncloud.android.lib.common.network.CertificateCombinedException;
35 import com.owncloud.android.lib.common.network.NetworkUtils;
36 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
37 import com.owncloud.android.utils.Log_OC;
38
39 import android.app.Activity;
40 import android.app.Dialog;
41 import android.net.http.SslError;
42 import android.os.Bundle;
43 import android.view.LayoutInflater;
44 import android.view.View;
45 import android.view.View.OnClickListener;
46 import android.view.ViewGroup;
47 import android.view.Window;
48 import android.widget.Button;
49 import android.widget.TextView;
50
51 /**
52 * Dialog to show an Untrusted Certificate
53 *
54 * @author masensio
55 * @author David A. Velasco
56 *
57 */
58 public class SslUntrustedCertDialog extends SherlockDialogFragment{
59
60 private final static String TAG = SslUntrustedCertDialog.class.getSimpleName();
61
62 private X509Certificate mCertificate;
63 private View mView;
64 private OnSslUntrustedCertListener mListener;
65 private SslError mError;
66 private CertificateCombinedException mException = null;
67
68 public SslUntrustedCertDialog() {
69 }
70
71 public SslUntrustedCertDialog(X509Certificate cert, SslError error) {
72 mCertificate = cert;
73 mError = error;
74 }
75
76 /**
77 * Private constructor.
78 *
79 * Instances have to be created through static {@link SslUntrustedCertDialog#newInstance}.
80 *
81 * @param context Android context where the dialog will live
82 * @param e Exception causing the need of prompt the user about the server certificate.
83 * @param listener Object to notice when the server certificate was added to the local certificates store.
84 */
85 private SslUntrustedCertDialog(RemoteOperationResult result, OnSslUntrustedCertListener listener) {
86 mListener = listener;
87 if (result.isSslRecoverableException()) {
88 mException = (CertificateCombinedException) result.getException();
89 mCertificate = mException.getServerCertificate();
90 }
91 }
92
93
94 public static SslUntrustedCertDialog newInstance(X509Certificate cert, SslError error) {
95 if (cert != null){
96 SslUntrustedCertDialog dialog = new SslUntrustedCertDialog(cert, error);
97 return dialog;
98 } else { // TODO Review this case
99 SslUntrustedCertDialog dialog = new SslUntrustedCertDialog();
100 return dialog;
101 }
102 }
103
104
105
106 /**
107 * Creates a new SslUntrustedCertDialog to ask the user if an untrusted certificate from a server should
108 * be trusted.
109 *
110 * @param context Android context where the dialog will live.
111 * @param result Result of a failed remote operation.
112 * @param listener Object to notice when the server certificate was added to the local certificates store.
113 * @return A new SslUntrustedCertDialog instance. NULL if the operation can not be recovered
114 * by setting the certificate as reliable.
115 */
116 public static SslUntrustedCertDialog newInstance(RemoteOperationResult result, OnSslUntrustedCertListener listener) {
117 if (result != null && result.isSslRecoverableException()) {
118 SslUntrustedCertDialog dialog = new SslUntrustedCertDialog(result, listener);
119 return dialog;
120 } else {
121 return null;
122 }
123 }
124
125
126 @Override
127 public void onCreate(Bundle savedInstanceState) {
128 super.onCreate(savedInstanceState);
129 setRetainInstance(true);
130 setCancelable(true);
131 }
132
133 @Override
134 public void onAttach(Activity activity) {
135 super.onAttach(activity);
136 if (activity instanceof SherlockActivity) {
137 mListener = (OnSslUntrustedCertListener) activity;
138 }
139 }
140
141 @Override
142 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
143 // Create a view by inflating desired layout
144 mView = inflater.inflate(R.layout.ssl_untrusted_cert_layout, container, false);
145
146 updateException(mException);
147
148 Button ok = (Button) mView.findViewById(R.id.ok);
149 ok.setOnClickListener(new OnClickListener() {
150
151 @Override
152 public void onClick(View v) {
153 try {
154 saveServerCert();
155 dismiss();
156 if (mListener != null) {
157 mListener.onSavedCertificate();
158 }
159 else
160 Log_OC.d(TAG, "Nobody there to notify the certificate was saved");
161
162 } catch (GeneralSecurityException e) {
163 dismiss();
164 if (mListener != null) {
165 mListener.onFailedSavingCertificate();
166 }
167 Log_OC.e(TAG, "Server certificate could not be saved in the known servers trust store ", e);
168
169 } catch (IOException e) {
170 dismiss();
171 if (mListener != null) {
172 mListener.onFailedSavingCertificate();
173 }
174 Log_OC.e(TAG, "Server certificate could not be saved in the known servers trust store ", e);
175 }
176
177 }
178 });
179
180 Button cancel = (Button) mView.findViewById(R.id.cancel);
181 cancel.setOnClickListener(new OnClickListener() {
182
183 @Override
184 public void onClick(View v) {
185 getDialog().cancel();
186 mListener.onCancelCertificate();
187 }
188 });
189
190 Button details = (Button) mView.findViewById(R.id.details_btn);
191 details.setOnClickListener(new OnClickListener() {
192
193 @Override
194 public void onClick(View v) {
195 View detailsScroll = mView.findViewById(R.id.details_scroll);
196 if (detailsScroll.getVisibility() == View.VISIBLE) {
197 detailsScroll.setVisibility(View.GONE);
198 ((Button) v).setText(R.string.ssl_validator_btn_details_see);
199
200 } else {
201 detailsScroll.setVisibility(View.VISIBLE);
202 ((Button) v).setText(R.string.ssl_validator_btn_details_hide);
203
204 showCertificateData(mCertificate);
205 }
206
207 }
208 });
209
210 return mView;
211 }
212
213 @Override
214 public Dialog onCreateDialog(Bundle savedInstanceState) {
215 final Dialog dialog = super.onCreateDialog(savedInstanceState);
216 dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
217
218 return dialog;
219 }
220
221 @Override
222 public void onDestroyView() {
223 if (getDialog() != null && getRetainInstance())
224 getDialog().setDismissMessage(null);
225 super.onDestroyView();
226 }
227
228
229 private void updateException(CertificateCombinedException exception) {
230
231 /// clean
232 mView.findViewById(R.id.reason_cert_not_trusted).setVisibility(View.GONE);
233 mView.findViewById(R.id.reason_cert_expired).setVisibility(View.GONE);
234 mView.findViewById(R.id.reason_cert_not_yet_valid).setVisibility(View.GONE);
235 mView.findViewById(R.id.reason_hostname_not_verified).setVisibility(View.GONE);
236 mView.findViewById(R.id.details_scroll).setVisibility(View.GONE);
237
238
239 if (mException != null) {
240
241 /// refresh
242 if (mException.getCertPathValidatorException() != null) {
243 ((TextView)mView.findViewById(R.id.reason_cert_not_trusted)).setVisibility(View.VISIBLE);
244 }
245
246 if (mException.getCertificateExpiredException() != null) {
247 ((TextView)mView.findViewById(R.id.reason_cert_expired)).setVisibility(View.VISIBLE);
248 }
249
250 if (mException.getCertificateNotYetValidException() != null) {
251 ((TextView)mView.findViewById(R.id.reason_cert_not_yet_valid)).setVisibility(View.VISIBLE);
252 }
253
254 if (mException.getSslPeerUnverifiedException() != null ) {
255 ((TextView)mView.findViewById(R.id.reason_hostname_not_verified)).setVisibility(View.VISIBLE);
256 }
257
258 }
259
260 }
261
262 private void showCertificateData(X509Certificate cert) {
263
264 TextView nullCerView = (TextView) mView.findViewById(R.id.null_cert);
265
266 if (cert != null) {
267 nullCerView.setVisibility(View.GONE);
268 showSubject(cert.getSubjectX500Principal());
269 showIssuer(cert.getIssuerX500Principal());
270 showValidity(cert.getNotBefore(), cert.getNotAfter());
271 showSignature(cert);
272
273 } else {
274 nullCerView.setVisibility(View.VISIBLE);
275 }
276 }
277
278 private void showSignature(X509Certificate cert) {
279 TextView sigView = ((TextView)mView.findViewById(R.id.value_signature));
280 TextView algorithmView = ((TextView)mView.findViewById(R.id.value_signature_algorithm));
281 sigView.setText(getHex(cert.getSignature()));
282 algorithmView.setText(cert.getSigAlgName());
283 }
284
285 public String getHex(final byte [] raw) {
286 if (raw == null) {
287 return null;
288 }
289 final StringBuilder hex = new StringBuilder(2 * raw.length);
290 for (final byte b : raw) {
291 final int hiVal = (b & 0xF0) >> 4;
292 final int loVal = b & 0x0F;
293 hex.append((char) ('0' + (hiVal + (hiVal / 10 * 7))));
294 hex.append((char) ('0' + (loVal + (loVal / 10 * 7))));
295 }
296 return hex.toString();
297 }
298
299 @SuppressWarnings("deprecation")
300 private void showValidity(Date notBefore, Date notAfter) {
301 TextView fromView = ((TextView)mView.findViewById(R.id.value_validity_from));
302 TextView toView = ((TextView)mView.findViewById(R.id.value_validity_to));
303 fromView.setText(notBefore.toLocaleString());
304 toView.setText(notAfter.toLocaleString());
305 }
306
307 private void showSubject(X500Principal subject) {
308 Map<String, String> s = parsePrincipal(subject);
309 TextView cnView = ((TextView)mView.findViewById(R.id.value_subject_CN));
310 TextView oView = ((TextView)mView.findViewById(R.id.value_subject_O));
311 TextView ouView = ((TextView)mView.findViewById(R.id.value_subject_OU));
312 TextView cView = ((TextView)mView.findViewById(R.id.value_subject_C));
313 TextView stView = ((TextView)mView.findViewById(R.id.value_subject_ST));
314 TextView lView = ((TextView)mView.findViewById(R.id.value_subject_L));
315
316 if (s.get("CN") != null) {
317 cnView.setText(s.get("CN"));
318 cnView.setVisibility(View.VISIBLE);
319 } else {
320 cnView.setVisibility(View.GONE);
321 }
322 if (s.get("O") != null) {
323 oView.setText(s.get("O"));
324 oView.setVisibility(View.VISIBLE);
325 } else {
326 oView.setVisibility(View.GONE);
327 }
328 if (s.get("OU") != null) {
329 ouView.setText(s.get("OU"));
330 ouView.setVisibility(View.VISIBLE);
331 } else {
332 ouView.setVisibility(View.GONE);
333 }
334 if (s.get("C") != null) {
335 cView.setText(s.get("C"));
336 cView.setVisibility(View.VISIBLE);
337 } else {
338 cView.setVisibility(View.GONE);
339 }
340 if (s.get("ST") != null) {
341 stView.setText(s.get("ST"));
342 stView.setVisibility(View.VISIBLE);
343 } else {
344 stView.setVisibility(View.GONE);
345 }
346 if (s.get("L") != null) {
347 lView.setText(s.get("L"));
348 lView.setVisibility(View.VISIBLE);
349 } else {
350 lView.setVisibility(View.GONE);
351 }
352 }
353
354 private void showIssuer(X500Principal issuer) {
355 Map<String, String> s = parsePrincipal(issuer);
356 TextView cnView = ((TextView)mView.findViewById(R.id.value_issuer_CN));
357 TextView oView = ((TextView)mView.findViewById(R.id.value_issuer_O));
358 TextView ouView = ((TextView)mView.findViewById(R.id.value_issuer_OU));
359 TextView cView = ((TextView)mView.findViewById(R.id.value_issuer_C));
360 TextView stView = ((TextView)mView.findViewById(R.id.value_issuer_ST));
361 TextView lView = ((TextView)mView.findViewById(R.id.value_issuer_L));
362
363 if (s.get("CN") != null) {
364 cnView.setText(s.get("CN"));
365 cnView.setVisibility(View.VISIBLE);
366 } else {
367 cnView.setVisibility(View.GONE);
368 }
369 if (s.get("O") != null) {
370 oView.setText(s.get("O"));
371 oView.setVisibility(View.VISIBLE);
372 } else {
373 oView.setVisibility(View.GONE);
374 }
375 if (s.get("OU") != null) {
376 ouView.setText(s.get("OU"));
377 ouView.setVisibility(View.VISIBLE);
378 } else {
379 ouView.setVisibility(View.GONE);
380 }
381 if (s.get("C") != null) {
382 cView.setText(s.get("C"));
383 cView.setVisibility(View.VISIBLE);
384 } else {
385 cView.setVisibility(View.GONE);
386 }
387 if (s.get("ST") != null) {
388 stView.setText(s.get("ST"));
389 stView.setVisibility(View.VISIBLE);
390 } else {
391 stView.setVisibility(View.GONE);
392 }
393 if (s.get("L") != null) {
394 lView.setText(s.get("L"));
395 lView.setVisibility(View.VISIBLE);
396 } else {
397 lView.setVisibility(View.GONE);
398 }
399 }
400
401
402 private Map<String, String> parsePrincipal(X500Principal principal) {
403 Map<String, String> result = new HashMap<String, String>();
404 String toParse = principal.getName();
405 String[] pieces = toParse.split(",");
406 String[] tokens = {"CN", "O", "OU", "C", "ST", "L"};
407 for (int i=0; i < pieces.length ; i++) {
408 for (int j=0; j<tokens.length; j++) {
409 if (pieces[i].startsWith(tokens[j] + "=")) {
410 result.put(tokens[j], pieces[i].substring(tokens[j].length()+1));
411 }
412 }
413 }
414 return result;
415 }
416
417 private void saveServerCert() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
418 if (mCertificate != null) {
419 // TODO make this asynchronously, it can take some time
420 NetworkUtils.addCertToKnownServersStore(mCertificate, getSherlockActivity());
421 }
422 }
423
424
425 public interface OnSslUntrustedCertListener {
426 public void onSavedCertificate();
427 public void onCancelCertificate();
428 public void onFailedSavingCertificate();
429 }
430
431
432
433
434 }