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