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