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();