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