1 /* ownCloud Android client application 
   2  *   Copyright (C) 2012-2013 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/>. 
  18 package com
.owncloud
.android
.ui
.dialog
; 
  20 import java
.io
.IOException
; 
  21 import java
.security
.GeneralSecurityException
; 
  22 import java
.security
.KeyStoreException
; 
  23 import java
.security
.NoSuchAlgorithmException
; 
  24 import java
.security
.cert
.CertificateException
; 
  25 import java
.security
.cert
.X509Certificate
; 
  26 import java
.util
.Date
; 
  27 import java
.util
.HashMap
; 
  30 import javax
.security
.auth
.x500
.X500Principal
; 
  32 import com
.owncloud
.android
.R
; 
  34 import android
.app
.Dialog
; 
  35 import android
.content
.Context
; 
  36 import android
.os
.Bundle
; 
  37 import android
.view
.View
; 
  38 import android
.view
.Window
; 
  39 import android
.widget
.Button
; 
  40 import android
.widget
.TextView
; 
  42 import com
.owncloud
.android
.lib
.network
.CertificateCombinedException
; 
  43 import com
.owncloud
.android
.lib
.network
.NetworkUtils
; 
  44 import com
.owncloud
.android
.lib
.operations
.common
.RemoteOperationResult
; 
  45 import com
.owncloud
.android
.utils
.Log_OC
; 
  48  * Dialog to request the user about a certificate that could not be validated with the certificates store in the system. 
  50  * @author David A. Velasco 
  52 public class SslValidatorDialog 
extends Dialog 
{ 
  54     private final static String TAG 
= SslValidatorDialog
.class.getSimpleName(); 
  56     private OnSslValidatorListener mListener
; 
  57     private CertificateCombinedException mException 
= null
; 
  62      * Creates a new SslValidatorDialog to ask the user if an untrusted certificate from a server should 
  65      * @param context       Android context where the dialog will live. 
  66      * @param result        Result of a failed remote operation. 
  67      * @param listener      Object to notice when the server certificate was added to the local certificates store. 
  68      * @return              A new SslValidatorDialog instance. NULL if the operation can not be recovered 
  69      *                      by setting the certificate as reliable. 
  71     public static SslValidatorDialog 
newInstance(Context context
, RemoteOperationResult result
, OnSslValidatorListener listener
) { 
  72         if (result 
!= null 
&& result
.isSslRecoverableException()) { 
  73             SslValidatorDialog dialog 
= new SslValidatorDialog(context
, listener
); 
  81      * Private constructor.  
  83      * Instances have to be created through static {@link SslValidatorDialog#newInstance}. 
  85      * @param context       Android context where the dialog will live 
  86      * @param e             Exception causing the need of prompt the user about the server certificate. 
  87      * @param listener      Object to notice when the server certificate was added to the local certificates store. 
  89     private SslValidatorDialog(Context context
, OnSslValidatorListener listener
) { 
  98     protected void onCreate(Bundle savedInstanceState
) { 
  99         super.onCreate(savedInstanceState
); 
 100         requestWindowFeature(Window
.FEATURE_NO_TITLE
); 
 101         mView 
= getLayoutInflater().inflate(R
.layout
.ssl_validator_layout
, null
); 
 102         setContentView(mView
);  
 104         mView
.findViewById(R
.id
.ok
).setOnClickListener(  
 105                 new View
.OnClickListener() { 
 107                     public void onClick(View v
) { 
 111                             if (mListener 
!= null
) 
 112                                 mListener
.onSavedCertificate(); 
 114                                 Log_OC
.d(TAG
, "Nobody there to notify the certificate was saved"); 
 116                         } catch (GeneralSecurityException e
) { 
 118                             if (mListener 
!= null
) 
 119                                 mListener
.onFailedSavingCertificate(); 
 120                             Log_OC
.e(TAG
, "Server certificate could not be saved in the known servers trust store ", e
); 
 122                         } catch (IOException e
) { 
 124                             if (mListener 
!= null
) 
 125                                 mListener
.onFailedSavingCertificate(); 
 126                             Log_OC
.e(TAG
, "Server certificate could not be saved in the known servers trust store ", e
); 
 131         mView
.findViewById(R
.id
.cancel
).setOnClickListener( 
 132                 new View
.OnClickListener() { 
 134                     public void onClick(View v
) { 
 139         mView
.findViewById(R
.id
.details_btn
).setOnClickListener( 
 140                 new View
.OnClickListener() { 
 142                     public void onClick(View v
) { 
 143                        View detailsScroll 
= findViewById(R
.id
.details_scroll
); 
 144                        if (detailsScroll
.getVisibility() == View
.VISIBLE
) { 
 145                            detailsScroll
.setVisibility(View
.GONE
); 
 146                            ((Button
) v
).setText(R
.string
.ssl_validator_btn_details_see
); 
 149                            detailsScroll
.setVisibility(View
.VISIBLE
); 
 150                            ((Button
) v
).setText(R
.string
.ssl_validator_btn_details_hide
); 
 157     public void updateResult(RemoteOperationResult result
) { 
 158         if (result
.isSslRecoverableException()) { 
 159             mException 
= (CertificateCombinedException
) result
.getException(); 
 162             mView
.findViewById(R
.id
.reason_cert_not_trusted
).setVisibility(View
.GONE
); 
 163             mView
.findViewById(R
.id
.reason_cert_expired
).setVisibility(View
.GONE
); 
 164             mView
.findViewById(R
.id
.reason_cert_not_yet_valid
).setVisibility(View
.GONE
); 
 165             mView
.findViewById(R
.id
.reason_hostname_not_verified
).setVisibility(View
.GONE
); 
 166             mView
.findViewById(R
.id
.details_scroll
).setVisibility(View
.GONE
); 
 169             if (mException
.getCertPathValidatorException() != null
) { 
 170                 ((TextView
)mView
.findViewById(R
.id
.reason_cert_not_trusted
)).setVisibility(View
.VISIBLE
); 
 173             if (mException
.getCertificateExpiredException() != null
) { 
 174                 ((TextView
)mView
.findViewById(R
.id
.reason_cert_expired
)).setVisibility(View
.VISIBLE
); 
 177             if (mException
.getCertificateNotYetValidException() != null
) { 
 178                 ((TextView
)mView
.findViewById(R
.id
.reason_cert_not_yet_valid
)).setVisibility(View
.VISIBLE
); 
 181             if (mException
.getSslPeerUnverifiedException() != null 
) { 
 182                 ((TextView
)mView
.findViewById(R
.id
.reason_hostname_not_verified
)).setVisibility(View
.VISIBLE
); 
 186             showCertificateData(mException
.getServerCertificate()); 
 191     private void showCertificateData(X509Certificate cert
) { 
 194             showSubject(cert
.getSubjectX500Principal()); 
 195             showIssuer(cert
.getIssuerX500Principal()); 
 196             showValidity(cert
.getNotBefore(), cert
.getNotAfter()); 
 200             // this should not happen 
 205     private void showSignature(X509Certificate cert
) { 
 206         TextView sigView 
= ((TextView
)mView
.findViewById(R
.id
.value_signature
)); 
 207         TextView algorithmView 
= ((TextView
)mView
.findViewById(R
.id
.value_signature_algorithm
)); 
 208         sigView
.setText(getHex(cert
.getSignature())); 
 209         algorithmView
.setText(cert
.getSigAlgName()); 
 212     public String 
getHex(final byte [] raw
) { 
 216         final StringBuilder hex 
= new StringBuilder(2 * raw
.length
); 
 217         for (final byte b 
: raw
) { 
 218            final int hiVal 
= (b 
& 0xF0) >> 4; 
 219            final int loVal 
= b 
& 0x0F; 
 220            hex
.append((char) ('0' + (hiVal 
+ (hiVal 
/ 10 * 7)))); 
 221            hex
.append((char) ('0' + (loVal 
+ (loVal 
/ 10 * 7)))); 
 223         return hex
.toString(); 
 226     private void showValidity(Date notBefore
, Date notAfter
) { 
 227         TextView fromView 
= ((TextView
)mView
.findViewById(R
.id
.value_validity_from
)); 
 228         TextView toView 
= ((TextView
)mView
.findViewById(R
.id
.value_validity_to
)); 
 229         fromView
.setText(notBefore
.toLocaleString()); 
 230         toView
.setText(notAfter
.toLocaleString()); 
 233     private void showSubject(X500Principal subject
) { 
 234         Map
<String
, String
> s 
= parsePrincipal(subject
); 
 235         TextView cnView 
= ((TextView
)mView
.findViewById(R
.id
.value_subject_CN
)); 
 236         TextView oView 
= ((TextView
)mView
.findViewById(R
.id
.value_subject_O
)); 
 237         TextView ouView 
= ((TextView
)mView
.findViewById(R
.id
.value_subject_OU
)); 
 238         TextView cView 
= ((TextView
)mView
.findViewById(R
.id
.value_subject_C
)); 
 239         TextView stView 
= ((TextView
)mView
.findViewById(R
.id
.value_subject_ST
)); 
 240         TextView lView 
= ((TextView
)mView
.findViewById(R
.id
.value_subject_L
)); 
 242         if (s
.get("CN") != null
) { 
 243             cnView
.setText(s
.get("CN")); 
 244             cnView
.setVisibility(View
.VISIBLE
); 
 246             cnView
.setVisibility(View
.GONE
); 
 248         if (s
.get("O") != null
) { 
 249             oView
.setText(s
.get("O")); 
 250             oView
.setVisibility(View
.VISIBLE
); 
 252             oView
.setVisibility(View
.GONE
); 
 254         if (s
.get("OU") != null
) { 
 255             ouView
.setText(s
.get("OU")); 
 256             ouView
.setVisibility(View
.VISIBLE
); 
 258             ouView
.setVisibility(View
.GONE
); 
 260         if (s
.get("C") != null
) { 
 261             cView
.setText(s
.get("C")); 
 262             cView
.setVisibility(View
.VISIBLE
); 
 264             cView
.setVisibility(View
.GONE
); 
 266         if (s
.get("ST") != null
) { 
 267             stView
.setText(s
.get("ST")); 
 268             stView
.setVisibility(View
.VISIBLE
); 
 270             stView
.setVisibility(View
.GONE
); 
 272         if (s
.get("L") != null
) { 
 273             lView
.setText(s
.get("L")); 
 274             lView
.setVisibility(View
.VISIBLE
); 
 276             lView
.setVisibility(View
.GONE
); 
 280     private void showIssuer(X500Principal issuer
) { 
 281         Map
<String
, String
> s 
= parsePrincipal(issuer
); 
 282         TextView cnView 
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_CN
)); 
 283         TextView oView 
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_O
)); 
 284         TextView ouView 
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_OU
)); 
 285         TextView cView 
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_C
)); 
 286         TextView stView 
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_ST
)); 
 287         TextView lView 
= ((TextView
)mView
.findViewById(R
.id
.value_issuer_L
)); 
 289         if (s
.get("CN") != null
) { 
 290             cnView
.setText(s
.get("CN")); 
 291             cnView
.setVisibility(View
.VISIBLE
); 
 293             cnView
.setVisibility(View
.GONE
); 
 295         if (s
.get("O") != null
) { 
 296             oView
.setText(s
.get("O")); 
 297             oView
.setVisibility(View
.VISIBLE
); 
 299             oView
.setVisibility(View
.GONE
); 
 301         if (s
.get("OU") != null
) { 
 302             ouView
.setText(s
.get("OU")); 
 303             ouView
.setVisibility(View
.VISIBLE
); 
 305             ouView
.setVisibility(View
.GONE
); 
 307         if (s
.get("C") != null
) { 
 308             cView
.setText(s
.get("C")); 
 309             cView
.setVisibility(View
.VISIBLE
); 
 311             cView
.setVisibility(View
.GONE
); 
 313         if (s
.get("ST") != null
) { 
 314             stView
.setText(s
.get("ST")); 
 315             stView
.setVisibility(View
.VISIBLE
); 
 317             stView
.setVisibility(View
.GONE
); 
 319         if (s
.get("L") != null
) { 
 320             lView
.setText(s
.get("L")); 
 321             lView
.setVisibility(View
.VISIBLE
); 
 323             lView
.setVisibility(View
.GONE
); 
 328     private Map
<String
, String
> parsePrincipal(X500Principal principal
) { 
 329         Map
<String
, String
> result 
= new HashMap
<String
, String
>(); 
 330         String toParse 
= principal
.getName(); 
 331         String
[] pieces 
= toParse
.split(","); 
 332         String
[] tokens 
= {"CN", "O", "OU", "C", "ST", "L"};  
 333         for (int i
=0; i 
< pieces
.length 
; i
++) { 
 334             for (int j
=0; j
<tokens
.length
; j
++) { 
 335                 if (pieces
[i
].startsWith(tokens
[j
] + "=")) { 
 336                     result
.put(tokens
[j
], pieces
[i
].substring(tokens
[j
].length()+1)); 
 343     private void saveServerCert() throws KeyStoreException
, NoSuchAlgorithmException
, CertificateException
, IOException 
{ 
 344         if (mException
.getServerCertificate() != null
) { 
 345             // TODO make this asynchronously, it can take some time 
 346             NetworkUtils
.addCertToKnownServersStore(mException
.getServerCertificate(), getContext()); 
 351     public interface OnSslValidatorListener 
{ 
 352         public void onSavedCertificate(); 
 353         public void onFailedSavingCertificate();