ec33ddf0b0a069d21f3c2f47084f57a2cacf483b
[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 updateException(mException);
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 updateException(CertificateCombinedException exception) {
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.details_scroll).setVisibility(View.GONE);
235
236
237 if (mException != null) {
238
239 /// refresh
240 if (mException.getCertPathValidatorException() != null) {
241 ((TextView)mView.findViewById(R.id.reason_cert_not_trusted)).setVisibility(View.VISIBLE);
242 }
243
244 if (mException.getCertificateExpiredException() != null) {
245 ((TextView)mView.findViewById(R.id.reason_cert_expired)).setVisibility(View.VISIBLE);
246 }
247
248 if (mException.getCertificateNotYetValidException() != null) {
249 ((TextView)mView.findViewById(R.id.reason_cert_not_yet_valid)).setVisibility(View.VISIBLE);
250 }
251
252 if (mException.getSslPeerUnverifiedException() != null ) {
253 ((TextView)mView.findViewById(R.id.reason_hostname_not_verified)).setVisibility(View.VISIBLE);
254 }
255
256 }
257
258 }
259
260 private void showCertificateData(X509Certificate cert) {
261
262 TextView nullCerView = (TextView) mView.findViewById(R.id.null_cert);
263
264 if (cert != null) {
265 nullCerView.setVisibility(View.GONE);
266 showSubject(cert.getSubjectX500Principal());
267 showIssuer(cert.getIssuerX500Principal());
268 showValidity(cert.getNotBefore(), cert.getNotAfter());
269 showSignature(cert);
270
271 } else {
272 nullCerView.setVisibility(View.VISIBLE);
273 }
274 }
275
276 private void showSignature(X509Certificate cert) {
277 TextView sigView = ((TextView)mView.findViewById(R.id.value_signature));
278 TextView algorithmView = ((TextView)mView.findViewById(R.id.value_signature_algorithm));
279 sigView.setText(getHex(cert.getSignature()));
280 algorithmView.setText(cert.getSigAlgName());
281 }
282
283 public String getHex(final byte [] raw) {
284 if (raw == null) {
285 return null;
286 }
287 final StringBuilder hex = new StringBuilder(2 * raw.length);
288 for (final byte b : raw) {
289 final int hiVal = (b & 0xF0) >> 4;
290 final int loVal = b & 0x0F;
291 hex.append((char) ('0' + (hiVal + (hiVal / 10 * 7))));
292 hex.append((char) ('0' + (loVal + (loVal / 10 * 7))));
293 }
294 return hex.toString();
295 }
296
297 @SuppressWarnings("deprecation")
298 private void showValidity(Date notBefore, Date notAfter) {
299 TextView fromView = ((TextView)mView.findViewById(R.id.value_validity_from));
300 TextView toView = ((TextView)mView.findViewById(R.id.value_validity_to));
301 fromView.setText(notBefore.toLocaleString());
302 toView.setText(notAfter.toLocaleString());
303 }
304
305 private void showSubject(X500Principal subject) {
306 Map<String, String> s = parsePrincipal(subject);
307 TextView cnView = ((TextView)mView.findViewById(R.id.value_subject_CN));
308 TextView oView = ((TextView)mView.findViewById(R.id.value_subject_O));
309 TextView ouView = ((TextView)mView.findViewById(R.id.value_subject_OU));
310 TextView cView = ((TextView)mView.findViewById(R.id.value_subject_C));
311 TextView stView = ((TextView)mView.findViewById(R.id.value_subject_ST));
312 TextView lView = ((TextView)mView.findViewById(R.id.value_subject_L));
313
314 if (s.get("CN") != null) {
315 cnView.setText(s.get("CN"));
316 cnView.setVisibility(View.VISIBLE);
317 } else {
318 cnView.setVisibility(View.GONE);
319 }
320 if (s.get("O") != null) {
321 oView.setText(s.get("O"));
322 oView.setVisibility(View.VISIBLE);
323 } else {
324 oView.setVisibility(View.GONE);
325 }
326 if (s.get("OU") != null) {
327 ouView.setText(s.get("OU"));
328 ouView.setVisibility(View.VISIBLE);
329 } else {
330 ouView.setVisibility(View.GONE);
331 }
332 if (s.get("C") != null) {
333 cView.setText(s.get("C"));
334 cView.setVisibility(View.VISIBLE);
335 } else {
336 cView.setVisibility(View.GONE);
337 }
338 if (s.get("ST") != null) {
339 stView.setText(s.get("ST"));
340 stView.setVisibility(View.VISIBLE);
341 } else {
342 stView.setVisibility(View.GONE);
343 }
344 if (s.get("L") != null) {
345 lView.setText(s.get("L"));
346 lView.setVisibility(View.VISIBLE);
347 } else {
348 lView.setVisibility(View.GONE);
349 }
350 }
351
352 private void showIssuer(X500Principal issuer) {
353 Map<String, String> s = parsePrincipal(issuer);
354 TextView cnView = ((TextView)mView.findViewById(R.id.value_issuer_CN));
355 TextView oView = ((TextView)mView.findViewById(R.id.value_issuer_O));
356 TextView ouView = ((TextView)mView.findViewById(R.id.value_issuer_OU));
357 TextView cView = ((TextView)mView.findViewById(R.id.value_issuer_C));
358 TextView stView = ((TextView)mView.findViewById(R.id.value_issuer_ST));
359 TextView lView = ((TextView)mView.findViewById(R.id.value_issuer_L));
360
361 if (s.get("CN") != null) {
362 cnView.setText(s.get("CN"));
363 cnView.setVisibility(View.VISIBLE);
364 } else {
365 cnView.setVisibility(View.GONE);
366 }
367 if (s.get("O") != null) {
368 oView.setText(s.get("O"));
369 oView.setVisibility(View.VISIBLE);
370 } else {
371 oView.setVisibility(View.GONE);
372 }
373 if (s.get("OU") != null) {
374 ouView.setText(s.get("OU"));
375 ouView.setVisibility(View.VISIBLE);
376 } else {
377 ouView.setVisibility(View.GONE);
378 }
379 if (s.get("C") != null) {
380 cView.setText(s.get("C"));
381 cView.setVisibility(View.VISIBLE);
382 } else {
383 cView.setVisibility(View.GONE);
384 }
385 if (s.get("ST") != null) {
386 stView.setText(s.get("ST"));
387 stView.setVisibility(View.VISIBLE);
388 } else {
389 stView.setVisibility(View.GONE);
390 }
391 if (s.get("L") != null) {
392 lView.setText(s.get("L"));
393 lView.setVisibility(View.VISIBLE);
394 } else {
395 lView.setVisibility(View.GONE);
396 }
397 }
398
399
400 private Map<String, String> parsePrincipal(X500Principal principal) {
401 Map<String, String> result = new HashMap<String, String>();
402 String toParse = principal.getName();
403 String[] pieces = toParse.split(",");
404 String[] tokens = {"CN", "O", "OU", "C", "ST", "L"};
405 for (int i=0; i < pieces.length ; i++) {
406 for (int j=0; j<tokens.length; j++) {
407 if (pieces[i].startsWith(tokens[j] + "=")) {
408 result.put(tokens[j], pieces[i].substring(tokens[j].length()+1));
409 }
410 }
411 }
412 return result;
413 }
414
415 private void saveServerCert() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
416 if (mCertificate != null) {
417 // TODO make this asynchronously, it can take some time
418 NetworkUtils.addCertToKnownServersStore(mCertificate, getSherlockActivity());
419 }
420 }
421
422 public interface OnSslUntrustedCertListener {
423 public void onSavedCertificate();
424 public void onCancelCertificate();
425 public void onFailedSavingCertificate();
426 }
427 }