1 /* ownCloud Android client application
2 * Copyright (C) 2012-2014 ownCloud Inc.
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.
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.
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/>.
17 package com
.owncloud
.android
.ui
.dialog
;
19 import java
.io
.IOException
;
20 import java
.security
.GeneralSecurityException
;
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
;
29 import javax
.security
.auth
.x500
.X500Principal
;
31 import com
.actionbarsherlock
.app
.SherlockActivity
;
32 import com
.owncloud
.android
.R
;
33 import com
.owncloud
.android
.lib
.common
.network
.CertificateCombinedException
;
34 import com
.owncloud
.android
.lib
.common
.network
.NetworkUtils
;
35 import com
.owncloud
.android
.lib
.common
.operations
.RemoteOperationResult
;
36 import com
.owncloud
.android
.utils
.Log_OC
;
38 import android
.app
.Activity
;
39 import android
.app
.Dialog
;
40 import android
.net
.http
.SslError
;
41 import android
.os
.Bundle
;
42 import android
.view
.LayoutInflater
;
43 import android
.view
.View
;
44 import android
.view
.View
.OnClickListener
;
45 import android
.view
.ViewGroup
;
46 import android
.view
.Window
;
47 import android
.widget
.Button
;
48 import android
.widget
.TextView
;
51 * Dialog to show an Untrusted Certificate
54 * @author David A. Velasco
57 public class SslUntrustedCertDialog
extends SslUntrustedCertDialogABSTRACT
{
59 private final static String TAG
= SslUntrustedCertDialog
.class.getSimpleName();
61 private X509Certificate mCertificate
;
63 private OnSslUntrustedCertListener mListener
;
64 private SslError mError
;
65 private CertificateCombinedException mException
= null
;
67 public SslUntrustedCertDialog() {
70 public SslUntrustedCertDialog(X509Certificate cert
, SslError error
) {
76 * Private constructor.
78 * Instances have to be created through static {@link SslUntrustedCertDialog#newInstance}.
80 * @param context Android context where the dialog will live
81 * @param e Exception causing the need of prompt the user about the server certificate.
82 * @param listener Object to notice when the server certificate was added to the local certificates store.
84 private SslUntrustedCertDialog(RemoteOperationResult result
, OnSslUntrustedCertListener listener
) {
86 if (result
.isSslRecoverableException()) {
87 mException
= (CertificateCombinedException
) result
.getException();
88 mCertificate
= mException
.getServerCertificate();
93 public static SslUntrustedCertDialog
newInstance(X509Certificate cert
, SslError error
) {
95 SslUntrustedCertDialog dialog
= new SslUntrustedCertDialog(cert
, error
);
97 } else { // TODO Review this case
98 SslUntrustedCertDialog dialog
= new SslUntrustedCertDialog();
106 * Creates a new SslUntrustedCertDialog to ask the user if an untrusted certificate from a server should
109 * @param context Android context where the dialog will live.
110 * @param result Result of a failed remote operation.
111 * @param listener Object to notice when the server certificate was added to the local certificates store.
112 * @return A new SslUntrustedCertDialog instance. NULL if the operation can not be recovered
113 * by setting the certificate as reliable.
115 public static SslUntrustedCertDialog
newInstance(RemoteOperationResult result
, OnSslUntrustedCertListener listener
) {
116 if (result
!= null
&& result
.isSslRecoverableException()) {
117 SslUntrustedCertDialog dialog
= new SslUntrustedCertDialog(result
, listener
);
125 public void onCreate(Bundle savedInstanceState
) {
126 super.onCreate(savedInstanceState
);
127 setRetainInstance(true
);
132 public void onAttach(Activity activity
) {
133 super.onAttach(activity
);
134 if (activity
instanceof SherlockActivity
) {
135 mListener
= (OnSslUntrustedCertListener
) activity
;
140 public View
onCreateView(LayoutInflater inflater
, ViewGroup container
, Bundle savedInstanceState
) {
141 // Create a view by inflating desired layout
142 mView
= inflater
.inflate(R
.layout
.ssl_untrusted_cert_layout
, container
, false
);
144 updateMessageException(mException
, mError
);
146 Button ok
= (Button
) mView
.findViewById(R
.id
.ok
);
147 ok
.setOnClickListener(new OnClickListener() {
150 public void onClick(View v
) {
154 if (mListener
!= null
) {
155 mListener
.onSavedCertificate();
158 Log_OC
.d(TAG
, "Nobody there to notify the certificate was saved");
160 } catch (GeneralSecurityException e
) {
162 if (mListener
!= null
) {
163 mListener
.onFailedSavingCertificate();
165 Log_OC
.e(TAG
, "Server certificate could not be saved in the known servers trust store ", e
);
167 } catch (IOException e
) {
169 if (mListener
!= null
) {
170 mListener
.onFailedSavingCertificate();
172 Log_OC
.e(TAG
, "Server certificate could not be saved in the known servers trust store ", e
);
178 Button cancel
= (Button
) mView
.findViewById(R
.id
.cancel
);
179 cancel
.setOnClickListener(new OnClickListener() {
182 public void onClick(View v
) {
183 getDialog().cancel();
184 mListener
.onCancelCertificate();
188 Button details
= (Button
) mView
.findViewById(R
.id
.details_btn
);
189 details
.setOnClickListener(new OnClickListener() {
192 public void onClick(View v
) {
193 View detailsScroll
= mView
.findViewById(R
.id
.details_scroll
);
194 if (detailsScroll
.getVisibility() == View
.VISIBLE
) {
195 detailsScroll
.setVisibility(View
.GONE
);
196 ((Button
) v
).setText(R
.string
.ssl_validator_btn_details_see
);
199 detailsScroll
.setVisibility(View
.VISIBLE
);
200 ((Button
) v
).setText(R
.string
.ssl_validator_btn_details_hide
);
202 showCertificateData(mCertificate
);
212 public Dialog
onCreateDialog(Bundle savedInstanceState
) {
213 final Dialog dialog
= super.onCreateDialog(savedInstanceState
);
214 dialog
.requestWindowFeature(Window
.FEATURE_NO_TITLE
);
220 public void onDestroyView() {
221 if (getDialog() != null
&& getRetainInstance())
222 getDialog().setDismissMessage(null
);
223 super.onDestroyView();
227 private void updateMessageException(CertificateCombinedException exception
, SslError error
) {
230 mView
.findViewById(R
.id
.reason_cert_not_trusted
).setVisibility(View
.GONE
);
231 mView
.findViewById(R
.id
.reason_cert_expired
).setVisibility(View
.GONE
);
232 mView
.findViewById(R
.id
.reason_cert_not_yet_valid
).setVisibility(View
.GONE
);
233 mView
.findViewById(R
.id
.reason_hostname_not_verified
).setVisibility(View
.GONE
);
234 mView
.findViewById(R
.id
.reason_no_info_about_error
).setVisibility(View
.GONE
);
235 mView
.findViewById(R
.id
.details_scroll
).setVisibility(View
.GONE
);
238 if (exception
!= null
) {
241 if (exception
.getCertPathValidatorException() != null
) {
242 ((TextView
)mView
.findViewById(R
.id
.reason_cert_not_trusted
)).setVisibility(View
.VISIBLE
);
245 if (exception
.getCertificateExpiredException() != null
) {
246 ((TextView
)mView
.findViewById(R
.id
.reason_cert_expired
)).setVisibility(View
.VISIBLE
);
249 if (exception
.getCertificateNotYetValidException() != null
) {
250 ((TextView
)mView
.findViewById(R
.id
.reason_cert_not_yet_valid
)).setVisibility(View
.VISIBLE
);
253 if (exception
.getSslPeerUnverifiedException() != null
) {
254 ((TextView
)mView
.findViewById(R
.id
.reason_hostname_not_verified
)).setVisibility(View
.VISIBLE
);
257 } else if ( error
!= null
) {
259 if (error
.getPrimaryError() == SslError
.SSL_UNTRUSTED
) {
260 ((TextView
)mView
.findViewById(R
.id
.reason_cert_not_trusted
)).setVisibility(View
.VISIBLE
);
262 } else if (error
.getPrimaryError() == SslError
.SSL_EXPIRED
) {
263 ((TextView
)mView
.findViewById(R
.id
.reason_cert_expired
)).setVisibility(View
.VISIBLE
);
265 } else if (error
.getPrimaryError() == SslError
.SSL_NOTYETVALID
) {
266 ((TextView
)mView
.findViewById(R
.id
.reason_cert_not_yet_valid
)).setVisibility(View
.VISIBLE
);
268 } else if (error
.getPrimaryError() == SslError
.SSL_IDMISMATCH
) {
269 ((TextView
)mView
.findViewById(R
.id
.reason_hostname_not_verified
)).setVisibility(View
.VISIBLE
);
275 private void showCertificateData(X509Certificate cert
) {
277 TextView nullCerView
= (TextView
) mView
.findViewById(R
.id
.null_cert
);
280 nullCerView
.setVisibility(View
.GONE
);
281 showSubject(cert
.getSubjectX500Principal());
282 showIssuer(cert
.getIssuerX500Principal());
283 showValidity(cert
.getNotBefore(), cert
.getNotAfter());
287 nullCerView
.setVisibility(View
.VISIBLE
);
291 private void showSignature(X509Certificate cert
) {
292 TextView sigView
= ((TextView
)mView
.findViewById(R
.id
.value_signature
));
293 TextView algorithmView
= ((TextView
)mView
.findViewById(R
.id
.value_signature_algorithm
));
294 sigView
.setText(getHex(cert
.getSignature()));
295 algorithmView
.setText(cert
.getSigAlgName());
298 public String
getHex(final byte [] raw
) {
302 final StringBuilder hex
= new StringBuilder(2 * raw
.length
);
303 for (final byte b
: raw
) {
304 final int hiVal
= (b
& 0xF0) >> 4;
305 final int loVal
= b
& 0x0F;
306 hex
.append((char) ('0' + (hiVal
+ (hiVal
/ 10 * 7))));
307 hex
.append((char) ('0' + (loVal
+ (loVal
/ 10 * 7))));
309 return hex
.toString();
312 @SuppressWarnings("deprecation")
313 private void showValidity(Date notBefore
, Date notAfter
) {
314 TextView fromView
= ((TextView
)mView
.findViewById(R
.id
.value_validity_from
));
315 TextView toView
= ((TextView
)mView
.findViewById(R
.id
.value_validity_to
));
316 fromView
.setText(notBefore
.toLocaleString());
317 toView
.setText(notAfter
.toLocaleString());
320 private void showSubject(X500Principal subject
) {
321 Map
<String
, String
> s
= parsePrincipal(subject
);
322 TextView cnView
= ((TextView
)mView
.findViewById(R
.id
.value_subject_CN
));
323 TextView oView
= ((TextView
)mView
.findViewById(R
.id
.value_subject_O
));
324 TextView ouView
= ((TextView
)mView
.findViewById(R
.id
.value_subject_OU
));
325 TextView cView
= ((TextView
)mView
.findViewById(R
.id
.value_subject_C
));
326 TextView stView
= ((TextView
)mView
.findViewById(R
.id
.value_subject_ST
));
327 TextView lView
= ((TextView
)mView
.findViewById(R
.id
.value_subject_L
));
329 if (s
.get("CN") != null
) {
330 cnView
.setText(s
.get("CN"));
331 cnView
.setVisibility(View
.VISIBLE
);
333 cnView
.setVisibility(View
.GONE
);
335 if (s
.get("O") != null
) {
336 oView
.setText(s
.get("O"));
337 oView
.setVisibility(View
.VISIBLE
);
339 oView
.setVisibility(View
.GONE
);
341 if (s
.get("OU") != null
) {
342 ouView
.setText(s
.get("OU"));
343 ouView
.setVisibility(View
.VISIBLE
);
345 ouView
.setVisibility(View
.GONE
);
347 if (s
.get("C") != null
) {
348 cView
.setText(s
.get("C"));
349 cView
.setVisibility(View
.VISIBLE
);
351 cView
.setVisibility(View
.GONE
);
353 if (s
.get("ST") != null
) {
354 stView
.setText(s
.get("ST"));
355 stView
.setVisibility(View
.VISIBLE
);
357 stView
.setVisibility(View
.GONE
);
359 if (s
.get("L") != null
) {
360 lView
.setText(s
.get("L"));
361 lView
.setVisibility(View
.VISIBLE
);
363 lView
.setVisibility(View
.GONE
);
367 private void showIssuer(X500Principal issuer
) {
368 Map
<String
, String
> s
= parsePrincipal(issuer
);
369 TextView cnView
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_CN
));
370 TextView oView
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_O
));
371 TextView ouView
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_OU
));
372 TextView cView
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_C
));
373 TextView stView
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_ST
));
374 TextView lView
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_L
));
376 if (s
.get("CN") != null
) {
377 cnView
.setText(s
.get("CN"));
378 cnView
.setVisibility(View
.VISIBLE
);
380 cnView
.setVisibility(View
.GONE
);
382 if (s
.get("O") != null
) {
383 oView
.setText(s
.get("O"));
384 oView
.setVisibility(View
.VISIBLE
);
386 oView
.setVisibility(View
.GONE
);
388 if (s
.get("OU") != null
) {
389 ouView
.setText(s
.get("OU"));
390 ouView
.setVisibility(View
.VISIBLE
);
392 ouView
.setVisibility(View
.GONE
);
394 if (s
.get("C") != null
) {
395 cView
.setText(s
.get("C"));
396 cView
.setVisibility(View
.VISIBLE
);
398 cView
.setVisibility(View
.GONE
);
400 if (s
.get("ST") != null
) {
401 stView
.setText(s
.get("ST"));
402 stView
.setVisibility(View
.VISIBLE
);
404 stView
.setVisibility(View
.GONE
);
406 if (s
.get("L") != null
) {
407 lView
.setText(s
.get("L"));
408 lView
.setVisibility(View
.VISIBLE
);
410 lView
.setVisibility(View
.GONE
);
415 private Map
<String
, String
> parsePrincipal(X500Principal principal
) {
416 Map
<String
, String
> result
= new HashMap
<String
, String
>();
417 String toParse
= principal
.getName();
418 String
[] pieces
= toParse
.split(",");
419 String
[] tokens
= {"CN", "O", "OU", "C", "ST", "L"};
420 for (int i
=0; i
< pieces
.length
; i
++) {
421 for (int j
=0; j
<tokens
.length
; j
++) {
422 if (pieces
[i
].startsWith(tokens
[j
] + "=")) {
423 result
.put(tokens
[j
], pieces
[i
].substring(tokens
[j
].length()+1));
430 private void saveServerCert() throws KeyStoreException
, NoSuchAlgorithmException
, CertificateException
, IOException
{
431 if (mCertificate
!= null
) {
432 // TODO make this asynchronously, it can take some time
433 NetworkUtils
.addCertToKnownServersStore(mCertificate
, getSherlockActivity());
437 public interface OnSslUntrustedCertListener
{
438 public void onSavedCertificate();
439 public void onCancelCertificate();
440 public void onFailedSavingCertificate();