2 * ownCloud Android client application
4 * @author David A. Velasco
5 * Copyright (C) 2015 ownCloud Inc.
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.
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.
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/>.
21 package com
.owncloud
.android
.ui
.dialog
;
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
;
33 import javax
.security
.auth
.x500
.X500Principal
;
35 import com
.owncloud
.android
.R
;
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
;
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
;
51 * Dialog to request the user about a certificate that could not be validated with the certificates store in the system.
53 public class SslValidatorDialog
extends Dialog
{
55 private final static String TAG
= SslValidatorDialog
.class.getSimpleName();
57 private OnSslValidatorListener mListener
;
58 private CertificateCombinedException mException
= null
;
63 * Creates a new SslValidatorDialog to ask the user if an untrusted certificate from a server should
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.
72 public static SslValidatorDialog
newInstance(Context context
, RemoteOperationResult result
, OnSslValidatorListener listener
) {
73 if (result
!= null
&& result
.isSslRecoverableException()) {
74 SslValidatorDialog dialog
= new SslValidatorDialog(context
, listener
);
82 * Private constructor.
84 * Instances have to be created through static {@link SslValidatorDialog#newInstance}.
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.
90 private SslValidatorDialog(Context context
, OnSslValidatorListener listener
) {
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
);
105 mView
.findViewById(R
.id
.ok
).setOnClickListener(
106 new View
.OnClickListener() {
108 public void onClick(View v
) {
112 if (mListener
!= null
)
113 mListener
.onSavedCertificate();
115 Log_OC
.d(TAG
, "Nobody there to notify the certificate was saved");
117 } catch (GeneralSecurityException e
) {
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
);
123 } catch (IOException e
) {
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
);
132 mView
.findViewById(R
.id
.cancel
).setOnClickListener(
133 new View
.OnClickListener() {
135 public void onClick(View v
) {
140 mView
.findViewById(R
.id
.details_btn
).setOnClickListener(
141 new View
.OnClickListener() {
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
);
150 detailsScroll
.setVisibility(View
.VISIBLE
);
151 ((Button
) v
).setText(R
.string
.ssl_validator_btn_details_hide
);
158 public void updateResult(RemoteOperationResult result
) {
159 if (result
.isSslRecoverableException()) {
160 mException
= (CertificateCombinedException
) result
.getException();
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
);
170 if (mException
.getCertPathValidatorException() != null
) {
171 ((TextView
)mView
.findViewById(R
.id
.reason_cert_not_trusted
)).setVisibility(View
.VISIBLE
);
174 if (mException
.getCertificateExpiredException() != null
) {
175 ((TextView
)mView
.findViewById(R
.id
.reason_cert_expired
)).setVisibility(View
.VISIBLE
);
178 if (mException
.getCertificateNotYetValidException() != null
) {
179 ((TextView
)mView
.findViewById(R
.id
.reason_cert_not_yet_valid
)).setVisibility(View
.VISIBLE
);
182 if (mException
.getSslPeerUnverifiedException() != null
) {
183 ((TextView
)mView
.findViewById(R
.id
.reason_hostname_not_verified
)).setVisibility(View
.VISIBLE
);
187 showCertificateData(mException
.getServerCertificate());
192 private void showCertificateData(X509Certificate cert
) {
195 showSubject(cert
.getSubjectX500Principal());
196 showIssuer(cert
.getIssuerX500Principal());
197 showValidity(cert
.getNotBefore(), cert
.getNotAfter());
201 // this should not happen
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());
213 public String
getHex(final byte [] raw
) {
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))));
224 return hex
.toString();
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());
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
));
244 if (s
.get("CN") != null
) {
245 cnView
.setText(s
.get("CN"));
246 cnView
.setVisibility(View
.VISIBLE
);
248 cnView
.setVisibility(View
.GONE
);
250 if (s
.get("O") != null
) {
251 oView
.setText(s
.get("O"));
252 oView
.setVisibility(View
.VISIBLE
);
254 oView
.setVisibility(View
.GONE
);
256 if (s
.get("OU") != null
) {
257 ouView
.setText(s
.get("OU"));
258 ouView
.setVisibility(View
.VISIBLE
);
260 ouView
.setVisibility(View
.GONE
);
262 if (s
.get("C") != null
) {
263 cView
.setText(s
.get("C"));
264 cView
.setVisibility(View
.VISIBLE
);
266 cView
.setVisibility(View
.GONE
);
268 if (s
.get("ST") != null
) {
269 stView
.setText(s
.get("ST"));
270 stView
.setVisibility(View
.VISIBLE
);
272 stView
.setVisibility(View
.GONE
);
274 if (s
.get("L") != null
) {
275 lView
.setText(s
.get("L"));
276 lView
.setVisibility(View
.VISIBLE
);
278 lView
.setVisibility(View
.GONE
);
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
));
291 if (s
.get("CN") != null
) {
292 cnView
.setText(s
.get("CN"));
293 cnView
.setVisibility(View
.VISIBLE
);
295 cnView
.setVisibility(View
.GONE
);
297 if (s
.get("O") != null
) {
298 oView
.setText(s
.get("O"));
299 oView
.setVisibility(View
.VISIBLE
);
301 oView
.setVisibility(View
.GONE
);
303 if (s
.get("OU") != null
) {
304 ouView
.setText(s
.get("OU"));
305 ouView
.setVisibility(View
.VISIBLE
);
307 ouView
.setVisibility(View
.GONE
);
309 if (s
.get("C") != null
) {
310 cView
.setText(s
.get("C"));
311 cView
.setVisibility(View
.VISIBLE
);
313 cView
.setVisibility(View
.GONE
);
315 if (s
.get("ST") != null
) {
316 stView
.setText(s
.get("ST"));
317 stView
.setVisibility(View
.VISIBLE
);
319 stView
.setVisibility(View
.GONE
);
321 if (s
.get("L") != null
) {
322 lView
.setText(s
.get("L"));
323 lView
.setVisibility(View
.VISIBLE
);
325 lView
.setVisibility(View
.GONE
);
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));
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());
353 public interface OnSslValidatorListener
{
354 public void onSavedCertificate();
355 public void onFailedSavingCertificate();