Library updated with final names (project and packages)
[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.network.CertificateCombinedException;
43 import com.owncloud.android.lib.network.NetworkUtils;
44 import com.owncloud.android.lib.operations.common.RemoteOperationResult;
45 import com.owncloud.android.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 private void showValidity(Date notBefore, Date notAfter) {
227 TextView fromView = ((TextView)mView.findViewById(R.id.value_validity_from));
228 TextView toView = ((TextView)mView.findViewById(R.id.value_validity_to));
229 fromView.setText(notBefore.toLocaleString());
230 toView.setText(notAfter.toLocaleString());
231 }
232
233 private void showSubject(X500Principal subject) {
234 Map<String, String> s = parsePrincipal(subject);
235 TextView cnView = ((TextView)mView.findViewById(R.id.value_subject_CN));
236 TextView oView = ((TextView)mView.findViewById(R.id.value_subject_O));
237 TextView ouView = ((TextView)mView.findViewById(R.id.value_subject_OU));
238 TextView cView = ((TextView)mView.findViewById(R.id.value_subject_C));
239 TextView stView = ((TextView)mView.findViewById(R.id.value_subject_ST));
240 TextView lView = ((TextView)mView.findViewById(R.id.value_subject_L));
241
242 if (s.get("CN") != null) {
243 cnView.setText(s.get("CN"));
244 cnView.setVisibility(View.VISIBLE);
245 } else {
246 cnView.setVisibility(View.GONE);
247 }
248 if (s.get("O") != null) {
249 oView.setText(s.get("O"));
250 oView.setVisibility(View.VISIBLE);
251 } else {
252 oView.setVisibility(View.GONE);
253 }
254 if (s.get("OU") != null) {
255 ouView.setText(s.get("OU"));
256 ouView.setVisibility(View.VISIBLE);
257 } else {
258 ouView.setVisibility(View.GONE);
259 }
260 if (s.get("C") != null) {
261 cView.setText(s.get("C"));
262 cView.setVisibility(View.VISIBLE);
263 } else {
264 cView.setVisibility(View.GONE);
265 }
266 if (s.get("ST") != null) {
267 stView.setText(s.get("ST"));
268 stView.setVisibility(View.VISIBLE);
269 } else {
270 stView.setVisibility(View.GONE);
271 }
272 if (s.get("L") != null) {
273 lView.setText(s.get("L"));
274 lView.setVisibility(View.VISIBLE);
275 } else {
276 lView.setVisibility(View.GONE);
277 }
278 }
279
280 private void showIssuer(X500Principal issuer) {
281 Map<String, String> s = parsePrincipal(issuer);
282 TextView cnView = ((TextView)mView.findViewById(R.id.value_issuer_CN));
283 TextView oView = ((TextView)mView.findViewById(R.id.value_issuer_O));
284 TextView ouView = ((TextView)mView.findViewById(R.id.value_issuer_OU));
285 TextView cView = ((TextView)mView.findViewById(R.id.value_issuer_C));
286 TextView stView = ((TextView)mView.findViewById(R.id.value_issuer_ST));
287 TextView lView = ((TextView)mView.findViewById(R.id.value_issuer_L));
288
289 if (s.get("CN") != null) {
290 cnView.setText(s.get("CN"));
291 cnView.setVisibility(View.VISIBLE);
292 } else {
293 cnView.setVisibility(View.GONE);
294 }
295 if (s.get("O") != null) {
296 oView.setText(s.get("O"));
297 oView.setVisibility(View.VISIBLE);
298 } else {
299 oView.setVisibility(View.GONE);
300 }
301 if (s.get("OU") != null) {
302 ouView.setText(s.get("OU"));
303 ouView.setVisibility(View.VISIBLE);
304 } else {
305 ouView.setVisibility(View.GONE);
306 }
307 if (s.get("C") != null) {
308 cView.setText(s.get("C"));
309 cView.setVisibility(View.VISIBLE);
310 } else {
311 cView.setVisibility(View.GONE);
312 }
313 if (s.get("ST") != null) {
314 stView.setText(s.get("ST"));
315 stView.setVisibility(View.VISIBLE);
316 } else {
317 stView.setVisibility(View.GONE);
318 }
319 if (s.get("L") != null) {
320 lView.setText(s.get("L"));
321 lView.setVisibility(View.VISIBLE);
322 } else {
323 lView.setVisibility(View.GONE);
324 }
325 }
326
327
328 private Map<String, String> parsePrincipal(X500Principal principal) {
329 Map<String, String> result = new HashMap<String, String>();
330 String toParse = principal.getName();
331 String[] pieces = toParse.split(",");
332 String[] tokens = {"CN", "O", "OU", "C", "ST", "L"};
333 for (int i=0; i < pieces.length ; i++) {
334 for (int j=0; j<tokens.length; j++) {
335 if (pieces[i].startsWith(tokens[j] + "=")) {
336 result.put(tokens[j], pieces[i].substring(tokens[j].length()+1));
337 }
338 }
339 }
340 return result;
341 }
342
343 private void saveServerCert() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
344 if (mException.getServerCertificate() != null) {
345 // TODO make this asynchronously, it can take some time
346 NetworkUtils.addCertToKnownServersStore(mException.getServerCertificate(), getContext());
347 }
348 }
349
350
351 public interface OnSslValidatorListener {
352 public void onSavedCertificate();
353 public void onFailedSavingCertificate();
354 }
355 }
356