This wraps the android.util.logging into Log_OC which , if its enabled
[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 as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 *
17 */
18
19 package com.owncloud.android.ui.dialog;
20
21 import java.io.IOException;
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.util.Log;
36 import android.view.View;
37 import android.view.Window;
38 import android.widget.Button;
39 import android.widget.TextView;
40
41 import com.owncloud.android.Log_OC;
42 import com.owncloud.android.R;
43 import com.owncloud.android.network.CertificateCombinedException;
44 import com.owncloud.android.network.OwnCloudClientUtils;
45 import com.owncloud.android.operations.RemoteOperationResult;
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, or 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 (Exception 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 }
123 });
124
125 mView.findViewById(R.id.cancel).setOnClickListener(
126 new View.OnClickListener() {
127 @Override
128 public void onClick(View v) {
129 cancel();
130 }
131 });
132
133 mView.findViewById(R.id.details_btn).setOnClickListener(
134 new View.OnClickListener() {
135 @Override
136 public void onClick(View v) {
137 View detailsScroll = findViewById(R.id.details_scroll);
138 if (detailsScroll.getVisibility() == View.VISIBLE) {
139 detailsScroll.setVisibility(View.GONE);
140 ((Button)v).setText(R.string.ssl_validator_btn_details_see);
141
142 } else {
143 detailsScroll.setVisibility(View.VISIBLE);
144 ((Button)v).setText(R.string.ssl_validator_btn_details_hide);
145 }
146 }
147 });
148 }
149
150
151 public void updateResult(RemoteOperationResult result) {
152 if (result.isSslRecoverableException()) {
153 mException = (CertificateCombinedException) result.getException();
154
155 /// clean
156 mView.findViewById(R.id.reason_cert_not_trusted).setVisibility(View.GONE);
157 mView.findViewById(R.id.reason_cert_expired).setVisibility(View.GONE);
158 mView.findViewById(R.id.reason_cert_not_yet_valid).setVisibility(View.GONE);
159 mView.findViewById(R.id.reason_hostname_not_verified).setVisibility(View.GONE);
160 mView.findViewById(R.id.details_scroll).setVisibility(View.GONE);
161
162 /// refresh
163 if (mException.getCertPathValidatorException() != null) {
164 ((TextView)mView.findViewById(R.id.reason_cert_not_trusted)).setVisibility(View.VISIBLE);
165 }
166
167 if (mException.getCertificateExpiredException() != null) {
168 ((TextView)mView.findViewById(R.id.reason_cert_expired)).setVisibility(View.VISIBLE);
169 }
170
171 if (mException.getCertificateNotYetValidException() != null) {
172 ((TextView)mView.findViewById(R.id.reason_cert_not_yet_valid)).setVisibility(View.VISIBLE);
173 }
174
175 if (mException.getSslPeerUnverifiedException() != null ) {
176 ((TextView)mView.findViewById(R.id.reason_hostname_not_verified)).setVisibility(View.VISIBLE);
177 }
178
179
180 showCertificateData(mException.getServerCertificate());
181 }
182
183 }
184
185 private void showCertificateData(X509Certificate cert) {
186
187 if (cert != null) {
188 showSubject(cert.getSubjectX500Principal());
189 showIssuer(cert.getIssuerX500Principal());
190 showValidity(cert.getNotBefore(), cert.getNotAfter());
191 showSignature(cert);
192
193 } else {
194 // this should not happen
195 // TODO
196 }
197 }
198
199 private void showSignature(X509Certificate cert) {
200 TextView sigView = ((TextView)mView.findViewById(R.id.value_signature));
201 TextView algorithmView = ((TextView)mView.findViewById(R.id.value_signature_algorithm));
202 sigView.setText(getHex(cert.getSignature()));
203 algorithmView.setText(cert.getSigAlgName());
204 }
205
206 public String getHex(final byte [] raw) {
207 if (raw == null) {
208 return null;
209 }
210 final StringBuilder hex = new StringBuilder(2 * raw.length);
211 for (final byte b : raw) {
212 final int hiVal = (b & 0xF0) >> 4;
213 final int loVal = b & 0x0F;
214 hex.append((char) ('0' + (hiVal + (hiVal / 10 * 7))));
215 hex.append((char) ('0' + (loVal + (loVal / 10 * 7))));
216 }
217 return hex.toString();
218 }
219
220 private void showValidity(Date notBefore, Date notAfter) {
221 TextView fromView = ((TextView)mView.findViewById(R.id.value_validity_from));
222 TextView toView = ((TextView)mView.findViewById(R.id.value_validity_to));
223 fromView.setText(notBefore.toLocaleString());
224 toView.setText(notAfter.toLocaleString());
225 }
226
227 private void showSubject(X500Principal subject) {
228 Map<String, String> s = parsePrincipal(subject);
229 TextView cnView = ((TextView)mView.findViewById(R.id.value_subject_CN));
230 TextView oView = ((TextView)mView.findViewById(R.id.value_subject_O));
231 TextView ouView = ((TextView)mView.findViewById(R.id.value_subject_OU));
232 TextView cView = ((TextView)mView.findViewById(R.id.value_subject_C));
233 TextView stView = ((TextView)mView.findViewById(R.id.value_subject_ST));
234 TextView lView = ((TextView)mView.findViewById(R.id.value_subject_L));
235
236 if (s.get("CN") != null) {
237 cnView.setText(s.get("CN"));
238 cnView.setVisibility(View.VISIBLE);
239 } else {
240 cnView.setVisibility(View.GONE);
241 }
242 if (s.get("O") != null) {
243 oView.setText(s.get("O"));
244 oView.setVisibility(View.VISIBLE);
245 } else {
246 oView.setVisibility(View.GONE);
247 }
248 if (s.get("OU") != null) {
249 ouView.setText(s.get("OU"));
250 ouView.setVisibility(View.VISIBLE);
251 } else {
252 ouView.setVisibility(View.GONE);
253 }
254 if (s.get("C") != null) {
255 cView.setText(s.get("C"));
256 cView.setVisibility(View.VISIBLE);
257 } else {
258 cView.setVisibility(View.GONE);
259 }
260 if (s.get("ST") != null) {
261 stView.setText(s.get("ST"));
262 stView.setVisibility(View.VISIBLE);
263 } else {
264 stView.setVisibility(View.GONE);
265 }
266 if (s.get("L") != null) {
267 lView.setText(s.get("L"));
268 lView.setVisibility(View.VISIBLE);
269 } else {
270 lView.setVisibility(View.GONE);
271 }
272 }
273
274 private void showIssuer(X500Principal issuer) {
275 Map<String, String> s = parsePrincipal(issuer);
276 TextView cnView = ((TextView)mView.findViewById(R.id.value_issuer_CN));
277 TextView oView = ((TextView)mView.findViewById(R.id.value_issuer_O));
278 TextView ouView = ((TextView)mView.findViewById(R.id.value_issuer_OU));
279 TextView cView = ((TextView)mView.findViewById(R.id.value_issuer_C));
280 TextView stView = ((TextView)mView.findViewById(R.id.value_issuer_ST));
281 TextView lView = ((TextView)mView.findViewById(R.id.value_issuer_L));
282
283 if (s.get("CN") != null) {
284 cnView.setText(s.get("CN"));
285 cnView.setVisibility(View.VISIBLE);
286 } else {
287 cnView.setVisibility(View.GONE);
288 }
289 if (s.get("O") != null) {
290 oView.setText(s.get("O"));
291 oView.setVisibility(View.VISIBLE);
292 } else {
293 oView.setVisibility(View.GONE);
294 }
295 if (s.get("OU") != null) {
296 ouView.setText(s.get("OU"));
297 ouView.setVisibility(View.VISIBLE);
298 } else {
299 ouView.setVisibility(View.GONE);
300 }
301 if (s.get("C") != null) {
302 cView.setText(s.get("C"));
303 cView.setVisibility(View.VISIBLE);
304 } else {
305 cView.setVisibility(View.GONE);
306 }
307 if (s.get("ST") != null) {
308 stView.setText(s.get("ST"));
309 stView.setVisibility(View.VISIBLE);
310 } else {
311 stView.setVisibility(View.GONE);
312 }
313 if (s.get("L") != null) {
314 lView.setText(s.get("L"));
315 lView.setVisibility(View.VISIBLE);
316 } else {
317 lView.setVisibility(View.GONE);
318 }
319 }
320
321
322 private Map<String, String> parsePrincipal(X500Principal principal) {
323 Map<String, String> result = new HashMap<String, String>();
324 String toParse = principal.getName();
325 String[] pieces = toParse.split(",");
326 String[] tokens = {"CN", "O", "OU", "C", "ST", "L"};
327 for (int i=0; i < pieces.length ; i++) {
328 for (int j=0; j<tokens.length; j++) {
329 if (pieces[i].startsWith(tokens[j] + "=")) {
330 result.put(tokens[j], pieces[i].substring(tokens[j].length()+1));
331 }
332 }
333 }
334 return result;
335 }
336
337 private void saveServerCert() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
338 if (mException.getServerCertificate() != null) {
339 // TODO make this asynchronously, it can take some time
340 OwnCloudClientUtils.addCertToKnownServersStore(mException.getServerCertificate(), getContext());
341 }
342 }
343
344
345 public interface OnSslValidatorListener {
346 public void onSavedCertificate();
347 public void onFailedSavingCertificate();
348 }
349 }
350