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