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