Merge pull request #627 from owncloud/better_loggin_system
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / dialog / SslValidatorDialog.java
1 /* ownCloud Android client application
2 * Copyright (C) 2012-2013 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
18 package com.owncloud.android.ui.dialog;
19
20 import java.io.IOException;
21 import java.security.GeneralSecurityException;
22 import java.security.KeyStoreException;
23 import java.security.NoSuchAlgorithmException;
24 import java.security.cert.CertificateException;
25 import java.security.cert.X509Certificate;
26 import java.util.Date;
27 import java.util.HashMap;
28 import java.util.Map;
29
30 import javax.security.auth.x500.X500Principal;
31
32 import com.owncloud.android.R;
33
34 import android.app.Dialog;
35 import android.content.Context;
36 import android.os.Bundle;
37 import android.view.View;
38 import android.view.Window;
39 import android.widget.Button;
40 import android.widget.TextView;
41
42 import com.owncloud.android.lib.common.network.CertificateCombinedException;
43 import com.owncloud.android.lib.common.network.NetworkUtils;
44 import com.owncloud.android.lib.common.operations.RemoteOperationResult;
45 import com.owncloud.android.lib.common.utils.Log_OC;
46
47 /**
48 * Dialog to request the user about a certificate that could not be validated with the certificates store in the system.
49 *
50 * @author David A. Velasco
51 */
52 public class SslValidatorDialog extends Dialog {
53
54 private final static String TAG = SslValidatorDialog.class.getSimpleName();
55
56 private OnSslValidatorListener mListener;
57 private CertificateCombinedException mException = null;
58 private View mView;
59
60
61 /**
62 * Creates a new SslValidatorDialog to ask the user if an untrusted certificate from a server should
63 * be trusted.
64 *
65 * @param context Android context where the dialog will live.
66 * @param result Result of a failed remote operation.
67 * @param listener Object to notice when the server certificate was added to the local certificates store.
68 * @return A new SslValidatorDialog instance. NULL if the operation can not be recovered
69 * by setting the certificate as reliable.
70 */
71 public static SslValidatorDialog newInstance(Context context, RemoteOperationResult result, OnSslValidatorListener listener) {
72 if (result != null && result.isSslRecoverableException()) {
73 SslValidatorDialog dialog = new SslValidatorDialog(context, listener);
74 return dialog;
75 } else {
76 return null;
77 }
78 }
79
80 /**
81 * Private constructor.
82 *
83 * Instances have to be created through static {@link SslValidatorDialog#newInstance}.
84 *
85 * @param context Android context where the dialog will live
86 * @param e Exception causing the need of prompt the user about the server certificate.
87 * @param listener Object to notice when the server certificate was added to the local certificates store.
88 */
89 private SslValidatorDialog(Context context, OnSslValidatorListener listener) {
90 super(context);
91 mListener = listener;
92 }
93
94
95 /**
96 * {@inheritDoc}
97 */
98 protected void onCreate(Bundle savedInstanceState) {
99 super.onCreate(savedInstanceState);
100 requestWindowFeature(Window.FEATURE_NO_TITLE);
101 mView = getLayoutInflater().inflate(R.layout.ssl_validator_layout, null);
102 setContentView(mView);
103
104 mView.findViewById(R.id.ok).setOnClickListener(
105 new View.OnClickListener() {
106 @Override
107 public void onClick(View v) {
108 try {
109 saveServerCert();
110 dismiss();
111 if (mListener != null)
112 mListener.onSavedCertificate();
113 else
114 Log_OC.d(TAG, "Nobody there to notify the certificate was saved");
115
116 } catch (GeneralSecurityException e) {
117 dismiss();
118 if (mListener != null)
119 mListener.onFailedSavingCertificate();
120 Log_OC.e(TAG, "Server certificate could not be saved in the known servers trust store ", e);
121
122 } catch (IOException e) {
123 dismiss();
124 if (mListener != null)
125 mListener.onFailedSavingCertificate();
126 Log_OC.e(TAG, "Server certificate could not be saved in the known servers trust store ", e);
127 }
128 }
129 });
130
131 mView.findViewById(R.id.cancel).setOnClickListener(
132 new View.OnClickListener() {
133 @Override
134 public void onClick(View v) {
135 cancel();
136 }
137 });
138
139 mView.findViewById(R.id.details_btn).setOnClickListener(
140 new View.OnClickListener() {
141 @Override
142 public void onClick(View v) {
143 View detailsScroll = findViewById(R.id.details_scroll);
144 if (detailsScroll.getVisibility() == View.VISIBLE) {
145 detailsScroll.setVisibility(View.GONE);
146 ((Button) v).setText(R.string.ssl_validator_btn_details_see);
147
148 } else {
149 detailsScroll.setVisibility(View.VISIBLE);
150 ((Button) v).setText(R.string.ssl_validator_btn_details_hide);
151 }
152 }
153 });
154 }
155
156
157 public void updateResult(RemoteOperationResult result) {
158 if (result.isSslRecoverableException()) {
159 mException = (CertificateCombinedException) result.getException();
160
161 /// clean
162 mView.findViewById(R.id.reason_cert_not_trusted).setVisibility(View.GONE);
163 mView.findViewById(R.id.reason_cert_expired).setVisibility(View.GONE);
164 mView.findViewById(R.id.reason_cert_not_yet_valid).setVisibility(View.GONE);
165 mView.findViewById(R.id.reason_hostname_not_verified).setVisibility(View.GONE);
166 mView.findViewById(R.id.details_scroll).setVisibility(View.GONE);
167
168 /// refresh
169 if (mException.getCertPathValidatorException() != null) {
170 ((TextView)mView.findViewById(R.id.reason_cert_not_trusted)).setVisibility(View.VISIBLE);
171 }
172
173 if (mException.getCertificateExpiredException() != null) {
174 ((TextView)mView.findViewById(R.id.reason_cert_expired)).setVisibility(View.VISIBLE);
175 }
176
177 if (mException.getCertificateNotYetValidException() != null) {
178 ((TextView)mView.findViewById(R.id.reason_cert_not_yet_valid)).setVisibility(View.VISIBLE);
179 }
180
181 if (mException.getSslPeerUnverifiedException() != null ) {
182 ((TextView)mView.findViewById(R.id.reason_hostname_not_verified)).setVisibility(View.VISIBLE);
183 }
184
185
186 showCertificateData(mException.getServerCertificate());
187 }
188
189 }
190
191 private void showCertificateData(X509Certificate cert) {
192
193 if (cert != null) {
194 showSubject(cert.getSubjectX500Principal());
195 showIssuer(cert.getIssuerX500Principal());
196 showValidity(cert.getNotBefore(), cert.getNotAfter());
197 showSignature(cert);
198
199 } else {
200 // this should not happen
201 // TODO
202 }
203 }
204
205 private void showSignature(X509Certificate cert) {
206 TextView sigView = ((TextView)mView.findViewById(R.id.value_signature));
207 TextView algorithmView = ((TextView)mView.findViewById(R.id.value_signature_algorithm));
208 sigView.setText(getHex(cert.getSignature()));
209 algorithmView.setText(cert.getSigAlgName());
210 }
211
212 public String getHex(final byte [] raw) {
213 if (raw == null) {
214 return null;
215 }
216 final StringBuilder hex = new StringBuilder(2 * raw.length);
217 for (final byte b : raw) {
218 final int hiVal = (b & 0xF0) >> 4;
219 final int loVal = b & 0x0F;
220 hex.append((char) ('0' + (hiVal + (hiVal / 10 * 7))));
221 hex.append((char) ('0' + (loVal + (loVal / 10 * 7))));
222 }
223 return hex.toString();
224 }
225
226 @SuppressWarnings("deprecation")
227 private void showValidity(Date notBefore, Date notAfter) {
228 TextView fromView = ((TextView)mView.findViewById(R.id.value_validity_from));
229 TextView toView = ((TextView)mView.findViewById(R.id.value_validity_to));
230 fromView.setText(notBefore.toLocaleString());
231 toView.setText(notAfter.toLocaleString());
232 }
233
234 private void showSubject(X500Principal subject) {
235 Map<String, String> s = parsePrincipal(subject);
236 TextView cnView = ((TextView)mView.findViewById(R.id.value_subject_CN));
237 TextView oView = ((TextView)mView.findViewById(R.id.value_subject_O));
238 TextView ouView = ((TextView)mView.findViewById(R.id.value_subject_OU));
239 TextView cView = ((TextView)mView.findViewById(R.id.value_subject_C));
240 TextView stView = ((TextView)mView.findViewById(R.id.value_subject_ST));
241 TextView lView = ((TextView)mView.findViewById(R.id.value_subject_L));
242
243 if (s.get("CN") != null) {
244 cnView.setText(s.get("CN"));
245 cnView.setVisibility(View.VISIBLE);
246 } else {
247 cnView.setVisibility(View.GONE);
248 }
249 if (s.get("O") != null) {
250 oView.setText(s.get("O"));
251 oView.setVisibility(View.VISIBLE);
252 } else {
253 oView.setVisibility(View.GONE);
254 }
255 if (s.get("OU") != null) {
256 ouView.setText(s.get("OU"));
257 ouView.setVisibility(View.VISIBLE);
258 } else {
259 ouView.setVisibility(View.GONE);
260 }
261 if (s.get("C") != null) {
262 cView.setText(s.get("C"));
263 cView.setVisibility(View.VISIBLE);
264 } else {
265 cView.setVisibility(View.GONE);
266 }
267 if (s.get("ST") != null) {
268 stView.setText(s.get("ST"));
269 stView.setVisibility(View.VISIBLE);
270 } else {
271 stView.setVisibility(View.GONE);
272 }
273 if (s.get("L") != null) {
274 lView.setText(s.get("L"));
275 lView.setVisibility(View.VISIBLE);
276 } else {
277 lView.setVisibility(View.GONE);
278 }
279 }
280
281 private void showIssuer(X500Principal issuer) {
282 Map<String, String> s = parsePrincipal(issuer);
283 TextView cnView = ((TextView)mView.findViewById(R.id.value_issuer_CN));
284 TextView oView = ((TextView)mView.findViewById(R.id.value_issuer_O));
285 TextView ouView = ((TextView)mView.findViewById(R.id.value_issuer_OU));
286 TextView cView = ((TextView)mView.findViewById(R.id.value_issuer_C));
287 TextView stView = ((TextView)mView.findViewById(R.id.value_issuer_ST));
288 TextView lView = ((TextView)mView.findViewById(R.id.value_issuer_L));
289
290 if (s.get("CN") != null) {
291 cnView.setText(s.get("CN"));
292 cnView.setVisibility(View.VISIBLE);
293 } else {
294 cnView.setVisibility(View.GONE);
295 }
296 if (s.get("O") != null) {
297 oView.setText(s.get("O"));
298 oView.setVisibility(View.VISIBLE);
299 } else {
300 oView.setVisibility(View.GONE);
301 }
302 if (s.get("OU") != null) {
303 ouView.setText(s.get("OU"));
304 ouView.setVisibility(View.VISIBLE);
305 } else {
306 ouView.setVisibility(View.GONE);
307 }
308 if (s.get("C") != null) {
309 cView.setText(s.get("C"));
310 cView.setVisibility(View.VISIBLE);
311 } else {
312 cView.setVisibility(View.GONE);
313 }
314 if (s.get("ST") != null) {
315 stView.setText(s.get("ST"));
316 stView.setVisibility(View.VISIBLE);
317 } else {
318 stView.setVisibility(View.GONE);
319 }
320 if (s.get("L") != null) {
321 lView.setText(s.get("L"));
322 lView.setVisibility(View.VISIBLE);
323 } else {
324 lView.setVisibility(View.GONE);
325 }
326 }
327
328
329 private Map<String, String> parsePrincipal(X500Principal principal) {
330 Map<String, String> result = new HashMap<String, String>();
331 String toParse = principal.getName();
332 String[] pieces = toParse.split(",");
333 String[] tokens = {"CN", "O", "OU", "C", "ST", "L"};
334 for (int i=0; i < pieces.length ; i++) {
335 for (int j=0; j<tokens.length; j++) {
336 if (pieces[i].startsWith(tokens[j] + "=")) {
337 result.put(tokens[j], pieces[i].substring(tokens[j].length()+1));
338 }
339 }
340 }
341 return result;
342 }
343
344 private void saveServerCert() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
345 if (mException.getServerCertificate() != null) {
346 // TODO make this asynchronously, it can take some time
347 NetworkUtils.addCertToKnownServersStore(mException.getServerCertificate(), getContext());
348 }
349 }
350
351
352 public interface OnSslValidatorListener {
353 public void onSavedCertificate();
354 public void onFailedSavingCertificate();
355 }
356 }
357