1 /* ownCloud Android client application
2 * Copyright (C) 2011 Bartek Przybylski
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 package com
.owncloud
.android
.ui
.dialog
;
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
;
29 import javax
.security
.auth
.x500
.X500Principal
;
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
;
40 import com
.owncloud
.android
.R
;
41 import com
.owncloud
.android
.network
.CertificateCombinedException
;
42 import com
.owncloud
.android
.network
.OwnCloudClientUtils
;
43 import com
.owncloud
.android
.operations
.RemoteOperationResult
;
46 * Dialog to request the user about a certificate that could not be validated with the certificates store in the system.
48 * @author David A. Velasco
50 public class SslValidatorDialog
extends Dialog
{
52 private final static String TAG
= SslValidatorDialog
.class.getSimpleName();
54 private OnSslValidatorListener mListener
;
55 private CertificateCombinedException mException
= null
;
60 * Creates a new SslValidatorDialog to ask the user if an untrusted certificate from a server should
63 * @param context Android context where the dialog will live.
64 * @param result Result of a failed remote operation.
65 * @param listener Object to notice when the server certificate was added to the local certificates store.
66 * @return A new SslValidatorDialog instance, or NULL if the operation can not be recovered
67 * by setting the certificate as reliable.
69 public static SslValidatorDialog
newInstance(Context context
, RemoteOperationResult result
, OnSslValidatorListener listener
) {
70 if (result
!= null
&& result
.isSslRecoverableException()) {
71 SslValidatorDialog dialog
= new SslValidatorDialog(context
, listener
);
79 * Private constructor.
81 * Instances have to be created through static {@link SslValidatorDialog#newInstance}.
83 * @param context Android context where the dialog will live
84 * @param e Exception causing the need of prompt the user about the server certificate.
85 * @param listener Object to notice when the server certificate was added to the local certificates store.
87 private SslValidatorDialog(Context context
, OnSslValidatorListener listener
) {
96 protected void onCreate(Bundle savedInstanceState
) {
97 super.onCreate(savedInstanceState
);
98 requestWindowFeature(Window
.FEATURE_NO_TITLE
);
99 mView
= getLayoutInflater().inflate(R
.layout
.ssl_validator_layout
, null
);
100 setContentView(mView
);
102 mView
.findViewById(R
.id
.ok
).setOnClickListener(
103 new View
.OnClickListener() {
105 public void onClick(View v
) {
109 if (mListener
!= null
)
110 mListener
.onSavedCertificate();
112 Log
.d(TAG
, "Nobody there to notify the certificate was saved");
114 } catch (Exception e
) {
116 if (mListener
!= null
)
117 mListener
.onFailedSavingCertificate();
118 Log
.e(TAG
, "Server certificate could not be saved in the known servers trust store ", e
);
123 mView
.findViewById(R
.id
.cancel
).setOnClickListener(
124 new View
.OnClickListener() {
126 public void onClick(View v
) {
131 mView
.findViewById(R
.id
.details_btn
).setOnClickListener(
132 new View
.OnClickListener() {
134 public void onClick(View v
) {
135 View detailsScroll
= findViewById(R
.id
.details_scroll
);
136 if (detailsScroll
.getVisibility() == View
.VISIBLE
) {
137 detailsScroll
.setVisibility(View
.GONE
);
138 ((Button
)v
).setText(R
.string
.ssl_validator_btn_details_see
);
141 detailsScroll
.setVisibility(View
.VISIBLE
);
142 ((Button
)v
).setText(R
.string
.ssl_validator_btn_details_hide
);
149 public void updateResult(RemoteOperationResult result
) {
150 if (result
.isSslRecoverableException()) {
151 mException
= (CertificateCombinedException
) result
.getException();
154 mView
.findViewById(R
.id
.reason_cert_not_trusted
).setVisibility(View
.GONE
);
155 mView
.findViewById(R
.id
.reason_cert_expired
).setVisibility(View
.GONE
);
156 mView
.findViewById(R
.id
.reason_cert_not_yet_valid
).setVisibility(View
.GONE
);
157 mView
.findViewById(R
.id
.reason_hostname_not_verified
).setVisibility(View
.GONE
);
158 mView
.findViewById(R
.id
.details_scroll
).setVisibility(View
.GONE
);
161 if (mException
.getCertPathValidatorException() != null
) {
162 ((TextView
)mView
.findViewById(R
.id
.reason_cert_not_trusted
)).setVisibility(View
.VISIBLE
);
165 if (mException
.getCertificateExpiredException() != null
) {
166 ((TextView
)mView
.findViewById(R
.id
.reason_cert_expired
)).setVisibility(View
.VISIBLE
);
169 if (mException
.getCertificateNotYetValidException() != null
) {
170 ((TextView
)mView
.findViewById(R
.id
.reason_cert_not_yet_valid
)).setVisibility(View
.VISIBLE
);
173 if (mException
.getSslPeerUnverifiedException() != null
) {
174 ((TextView
)mView
.findViewById(R
.id
.reason_hostname_not_verified
)).setVisibility(View
.VISIBLE
);
178 showCertificateData(mException
.getServerCertificate());
183 private void showCertificateData(X509Certificate cert
) {
186 showSubject(cert
.getSubjectX500Principal());
187 showIssuer(cert
.getIssuerX500Principal());
188 showValidity(cert
.getNotBefore(), cert
.getNotAfter());
192 // this should not happen
197 private void showSignature(X509Certificate cert
) {
198 TextView sigView
= ((TextView
)mView
.findViewById(R
.id
.value_signature
));
199 TextView algorithmView
= ((TextView
)mView
.findViewById(R
.id
.value_signature_algorithm
));
200 sigView
.setText(getHex(cert
.getSignature()));
201 algorithmView
.setText(cert
.getSigAlgName());
204 public String
getHex(final byte [] raw
) {
208 final StringBuilder hex
= new StringBuilder(2 * raw
.length
);
209 for (final byte b
: raw
) {
210 final int hiVal
= (b
& 0xF0) >> 4;
211 final int loVal
= b
& 0x0F;
212 hex
.append((char) ('0' + (hiVal
+ (hiVal
/ 10 * 7))));
213 hex
.append((char) ('0' + (loVal
+ (loVal
/ 10 * 7))));
215 return hex
.toString();
218 private void showValidity(Date notBefore
, Date notAfter
) {
219 TextView fromView
= ((TextView
)mView
.findViewById(R
.id
.value_validity_from
));
220 TextView toView
= ((TextView
)mView
.findViewById(R
.id
.value_validity_to
));
221 fromView
.setText(notBefore
.toLocaleString());
222 toView
.setText(notAfter
.toLocaleString());
225 private void showSubject(X500Principal subject
) {
226 Map
<String
, String
> s
= parsePrincipal(subject
);
227 TextView cnView
= ((TextView
)mView
.findViewById(R
.id
.value_subject_CN
));
228 TextView oView
= ((TextView
)mView
.findViewById(R
.id
.value_subject_O
));
229 TextView ouView
= ((TextView
)mView
.findViewById(R
.id
.value_subject_OU
));
230 TextView cView
= ((TextView
)mView
.findViewById(R
.id
.value_subject_C
));
231 TextView stView
= ((TextView
)mView
.findViewById(R
.id
.value_subject_ST
));
232 TextView lView
= ((TextView
)mView
.findViewById(R
.id
.value_subject_L
));
234 if (s
.get("CN") != null
) {
235 cnView
.setText(s
.get("CN"));
236 cnView
.setVisibility(View
.VISIBLE
);
238 cnView
.setVisibility(View
.GONE
);
240 if (s
.get("O") != null
) {
241 oView
.setText(s
.get("O"));
242 oView
.setVisibility(View
.VISIBLE
);
244 oView
.setVisibility(View
.GONE
);
246 if (s
.get("OU") != null
) {
247 ouView
.setText(s
.get("OU"));
248 ouView
.setVisibility(View
.VISIBLE
);
250 ouView
.setVisibility(View
.GONE
);
252 if (s
.get("C") != null
) {
253 cView
.setText(s
.get("C"));
254 cView
.setVisibility(View
.VISIBLE
);
256 cView
.setVisibility(View
.GONE
);
258 if (s
.get("ST") != null
) {
259 stView
.setText(s
.get("ST"));
260 stView
.setVisibility(View
.VISIBLE
);
262 stView
.setVisibility(View
.GONE
);
264 if (s
.get("L") != null
) {
265 lView
.setText(s
.get("L"));
266 lView
.setVisibility(View
.VISIBLE
);
268 lView
.setVisibility(View
.GONE
);
272 private void showIssuer(X500Principal issuer
) {
273 Map
<String
, String
> s
= parsePrincipal(issuer
);
274 TextView cnView
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_CN
));
275 TextView oView
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_O
));
276 TextView ouView
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_OU
));
277 TextView cView
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_C
));
278 TextView stView
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_ST
));
279 TextView lView
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_L
));
281 if (s
.get("CN") != null
) {
282 cnView
.setText(s
.get("CN"));
283 cnView
.setVisibility(View
.VISIBLE
);
285 cnView
.setVisibility(View
.GONE
);
287 if (s
.get("O") != null
) {
288 oView
.setText(s
.get("O"));
289 oView
.setVisibility(View
.VISIBLE
);
291 oView
.setVisibility(View
.GONE
);
293 if (s
.get("OU") != null
) {
294 ouView
.setText(s
.get("OU"));
295 ouView
.setVisibility(View
.VISIBLE
);
297 ouView
.setVisibility(View
.GONE
);
299 if (s
.get("C") != null
) {
300 cView
.setText(s
.get("C"));
301 cView
.setVisibility(View
.VISIBLE
);
303 cView
.setVisibility(View
.GONE
);
305 if (s
.get("ST") != null
) {
306 stView
.setText(s
.get("ST"));
307 stView
.setVisibility(View
.VISIBLE
);
309 stView
.setVisibility(View
.GONE
);
311 if (s
.get("L") != null
) {
312 lView
.setText(s
.get("L"));
313 lView
.setVisibility(View
.VISIBLE
);
315 lView
.setVisibility(View
.GONE
);
320 private Map
<String
, String
> parsePrincipal(X500Principal principal
) {
321 Map
<String
, String
> result
= new HashMap
<String
, String
>();
322 String toParse
= principal
.getName();
323 String
[] pieces
= toParse
.split(",");
324 String
[] tokens
= {"CN", "O", "OU", "C", "ST", "L"};
325 for (int i
=0; i
< pieces
.length
; i
++) {
326 for (int j
=0; j
<tokens
.length
; j
++) {
327 if (pieces
[i
].startsWith(tokens
[j
] + "=")) {
328 result
.put(tokens
[j
], pieces
[i
].substring(tokens
[j
].length()+1));
335 private void saveServerCert() throws KeyStoreException
, NoSuchAlgorithmException
, CertificateException
, IOException
{
336 if (mException
.getServerCertificate() != null
) {
337 // TODO make this asynchronously, it can take some time
338 OwnCloudClientUtils
.addCertToKnownServersStore(mException
.getServerCertificate(), getContext());
343 public interface OnSslValidatorListener
{
344 public void onSavedCertificate();
345 public void onFailedSavingCertificate();