SslValidatorDialog.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. /* ownCloud Android client application
  2. * Copyright (C) 2012-2013 ownCloud Inc.
  3. *
  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.
  7. *
  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.
  12. *
  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/>.
  15. *
  16. */
  17. package com.owncloud.android.ui.dialog;
  18. import java.io.IOException;
  19. import java.security.GeneralSecurityException;
  20. import java.security.KeyStoreException;
  21. import java.security.NoSuchAlgorithmException;
  22. import java.security.cert.CertificateException;
  23. import java.security.cert.X509Certificate;
  24. import java.util.Date;
  25. import java.util.HashMap;
  26. import java.util.Map;
  27. import javax.security.auth.x500.X500Principal;
  28. import android.app.Dialog;
  29. import android.content.Context;
  30. import android.os.Bundle;
  31. import android.view.View;
  32. import android.view.Window;
  33. import android.widget.Button;
  34. import android.widget.TextView;
  35. import com.owncloud.android.Log_OC;
  36. import com.owncloud.android.R;
  37. import com.owncloud.android.network.CertificateCombinedException;
  38. import com.owncloud.android.network.OwnCloudClientUtils;
  39. import com.owncloud.android.operations.RemoteOperationResult;
  40. /**
  41. * Dialog to request the user about a certificate that could not be validated with the certificates store in the system.
  42. *
  43. * @author David A. Velasco
  44. */
  45. public class SslValidatorDialog extends Dialog {
  46. private final static String TAG = SslValidatorDialog.class.getSimpleName();
  47. private OnSslValidatorListener mListener;
  48. private CertificateCombinedException mException = null;
  49. private View mView;
  50. /**
  51. * Creates a new SslValidatorDialog to ask the user if an untrusted certificate from a server should
  52. * be trusted.
  53. *
  54. * @param context Android context where the dialog will live.
  55. * @param result Result of a failed remote operation.
  56. * @param listener Object to notice when the server certificate was added to the local certificates store.
  57. * @return A new SslValidatorDialog instance. NULL if the operation can not be recovered
  58. * by setting the certificate as reliable.
  59. */
  60. public static SslValidatorDialog newInstance(Context context, RemoteOperationResult result, OnSslValidatorListener listener) {
  61. if (result != null && result.isSslRecoverableException()) {
  62. SslValidatorDialog dialog = new SslValidatorDialog(context, listener);
  63. return dialog;
  64. } else {
  65. return null;
  66. }
  67. }
  68. /**
  69. * Private constructor.
  70. *
  71. * Instances have to be created through static {@link SslValidatorDialog#newInstance}.
  72. *
  73. * @param context Android context where the dialog will live
  74. * @param e Exception causing the need of prompt the user about the server certificate.
  75. * @param listener Object to notice when the server certificate was added to the local certificates store.
  76. */
  77. private SslValidatorDialog(Context context, OnSslValidatorListener listener) {
  78. super(context);
  79. mListener = listener;
  80. }
  81. /**
  82. * {@inheritDoc}
  83. */
  84. protected void onCreate(Bundle savedInstanceState) {
  85. super.onCreate(savedInstanceState);
  86. requestWindowFeature(Window.FEATURE_NO_TITLE);
  87. mView = getLayoutInflater().inflate(R.layout.ssl_validator_layout, null);
  88. setContentView(mView);
  89. mView.findViewById(R.id.ok).setOnClickListener(
  90. new View.OnClickListener() {
  91. @Override
  92. public void onClick(View v) {
  93. try {
  94. saveServerCert();
  95. dismiss();
  96. if (mListener != null)
  97. mListener.onSavedCertificate();
  98. else
  99. Log_OC.d(TAG, "Nobody there to notify the certificate was saved");
  100. } catch (GeneralSecurityException e) {
  101. dismiss();
  102. if (mListener != null)
  103. mListener.onFailedSavingCertificate();
  104. Log_OC.e(TAG, "Server certificate could not be saved in the known servers trust store ", e);
  105. } catch (IOException e) {
  106. dismiss();
  107. if (mListener != null)
  108. mListener.onFailedSavingCertificate();
  109. Log_OC.e(TAG, "Server certificate could not be saved in the known servers trust store ", e);
  110. }
  111. }
  112. });
  113. mView.findViewById(R.id.cancel).setOnClickListener(
  114. new View.OnClickListener() {
  115. @Override
  116. public void onClick(View v) {
  117. cancel();
  118. }
  119. });
  120. mView.findViewById(R.id.details_btn).setOnClickListener(
  121. new View.OnClickListener() {
  122. @Override
  123. public void onClick(View v) {
  124. View detailsScroll = findViewById(R.id.details_scroll);
  125. if (detailsScroll.getVisibility() == View.VISIBLE) {
  126. detailsScroll.setVisibility(View.GONE);
  127. ((Button)v).setText(R.string.ssl_validator_btn_details_see);
  128. } else {
  129. detailsScroll.setVisibility(View.VISIBLE);
  130. ((Button)v).setText(R.string.ssl_validator_btn_details_hide);
  131. }
  132. }
  133. });
  134. }
  135. public void updateResult(RemoteOperationResult result) {
  136. if (result.isSslRecoverableException()) {
  137. mException = (CertificateCombinedException) result.getException();
  138. /// clean
  139. mView.findViewById(R.id.reason_cert_not_trusted).setVisibility(View.GONE);
  140. mView.findViewById(R.id.reason_cert_expired).setVisibility(View.GONE);
  141. mView.findViewById(R.id.reason_cert_not_yet_valid).setVisibility(View.GONE);
  142. mView.findViewById(R.id.reason_hostname_not_verified).setVisibility(View.GONE);
  143. mView.findViewById(R.id.details_scroll).setVisibility(View.GONE);
  144. /// refresh
  145. if (mException.getCertPathValidatorException() != null) {
  146. ((TextView)mView.findViewById(R.id.reason_cert_not_trusted)).setVisibility(View.VISIBLE);
  147. }
  148. if (mException.getCertificateExpiredException() != null) {
  149. ((TextView)mView.findViewById(R.id.reason_cert_expired)).setVisibility(View.VISIBLE);
  150. }
  151. if (mException.getCertificateNotYetValidException() != null) {
  152. ((TextView)mView.findViewById(R.id.reason_cert_not_yet_valid)).setVisibility(View.VISIBLE);
  153. }
  154. if (mException.getSslPeerUnverifiedException() != null ) {
  155. ((TextView)mView.findViewById(R.id.reason_hostname_not_verified)).setVisibility(View.VISIBLE);
  156. }
  157. showCertificateData(mException.getServerCertificate());
  158. }
  159. }
  160. private void showCertificateData(X509Certificate cert) {
  161. if (cert != null) {
  162. showSubject(cert.getSubjectX500Principal());
  163. showIssuer(cert.getIssuerX500Principal());
  164. showValidity(cert.getNotBefore(), cert.getNotAfter());
  165. showSignature(cert);
  166. } else {
  167. // this should not happen
  168. // TODO
  169. }
  170. }
  171. private void showSignature(X509Certificate cert) {
  172. TextView sigView = ((TextView)mView.findViewById(R.id.value_signature));
  173. TextView algorithmView = ((TextView)mView.findViewById(R.id.value_signature_algorithm));
  174. sigView.setText(getHex(cert.getSignature()));
  175. algorithmView.setText(cert.getSigAlgName());
  176. }
  177. public String getHex(final byte [] raw) {
  178. if (raw == null) {
  179. return null;
  180. }
  181. final StringBuilder hex = new StringBuilder(2 * raw.length);
  182. for (final byte b : raw) {
  183. final int hiVal = (b & 0xF0) >> 4;
  184. final int loVal = b & 0x0F;
  185. hex.append((char) ('0' + (hiVal + (hiVal / 10 * 7))));
  186. hex.append((char) ('0' + (loVal + (loVal / 10 * 7))));
  187. }
  188. return hex.toString();
  189. }
  190. private void showValidity(Date notBefore, Date notAfter) {
  191. TextView fromView = ((TextView)mView.findViewById(R.id.value_validity_from));
  192. TextView toView = ((TextView)mView.findViewById(R.id.value_validity_to));
  193. fromView.setText(notBefore.toLocaleString());
  194. toView.setText(notAfter.toLocaleString());
  195. }
  196. private void showSubject(X500Principal subject) {
  197. Map<String, String> s = parsePrincipal(subject);
  198. TextView cnView = ((TextView)mView.findViewById(R.id.value_subject_CN));
  199. TextView oView = ((TextView)mView.findViewById(R.id.value_subject_O));
  200. TextView ouView = ((TextView)mView.findViewById(R.id.value_subject_OU));
  201. TextView cView = ((TextView)mView.findViewById(R.id.value_subject_C));
  202. TextView stView = ((TextView)mView.findViewById(R.id.value_subject_ST));
  203. TextView lView = ((TextView)mView.findViewById(R.id.value_subject_L));
  204. if (s.get("CN") != null) {
  205. cnView.setText(s.get("CN"));
  206. cnView.setVisibility(View.VISIBLE);
  207. } else {
  208. cnView.setVisibility(View.GONE);
  209. }
  210. if (s.get("O") != null) {
  211. oView.setText(s.get("O"));
  212. oView.setVisibility(View.VISIBLE);
  213. } else {
  214. oView.setVisibility(View.GONE);
  215. }
  216. if (s.get("OU") != null) {
  217. ouView.setText(s.get("OU"));
  218. ouView.setVisibility(View.VISIBLE);
  219. } else {
  220. ouView.setVisibility(View.GONE);
  221. }
  222. if (s.get("C") != null) {
  223. cView.setText(s.get("C"));
  224. cView.setVisibility(View.VISIBLE);
  225. } else {
  226. cView.setVisibility(View.GONE);
  227. }
  228. if (s.get("ST") != null) {
  229. stView.setText(s.get("ST"));
  230. stView.setVisibility(View.VISIBLE);
  231. } else {
  232. stView.setVisibility(View.GONE);
  233. }
  234. if (s.get("L") != null) {
  235. lView.setText(s.get("L"));
  236. lView.setVisibility(View.VISIBLE);
  237. } else {
  238. lView.setVisibility(View.GONE);
  239. }
  240. }
  241. private void showIssuer(X500Principal issuer) {
  242. Map<String, String> s = parsePrincipal(issuer);
  243. TextView cnView = ((TextView)mView.findViewById(R.id.value_issuer_CN));
  244. TextView oView = ((TextView)mView.findViewById(R.id.value_issuer_O));
  245. TextView ouView = ((TextView)mView.findViewById(R.id.value_issuer_OU));
  246. TextView cView = ((TextView)mView.findViewById(R.id.value_issuer_C));
  247. TextView stView = ((TextView)mView.findViewById(R.id.value_issuer_ST));
  248. TextView lView = ((TextView)mView.findViewById(R.id.value_issuer_L));
  249. if (s.get("CN") != null) {
  250. cnView.setText(s.get("CN"));
  251. cnView.setVisibility(View.VISIBLE);
  252. } else {
  253. cnView.setVisibility(View.GONE);
  254. }
  255. if (s.get("O") != null) {
  256. oView.setText(s.get("O"));
  257. oView.setVisibility(View.VISIBLE);
  258. } else {
  259. oView.setVisibility(View.GONE);
  260. }
  261. if (s.get("OU") != null) {
  262. ouView.setText(s.get("OU"));
  263. ouView.setVisibility(View.VISIBLE);
  264. } else {
  265. ouView.setVisibility(View.GONE);
  266. }
  267. if (s.get("C") != null) {
  268. cView.setText(s.get("C"));
  269. cView.setVisibility(View.VISIBLE);
  270. } else {
  271. cView.setVisibility(View.GONE);
  272. }
  273. if (s.get("ST") != null) {
  274. stView.setText(s.get("ST"));
  275. stView.setVisibility(View.VISIBLE);
  276. } else {
  277. stView.setVisibility(View.GONE);
  278. }
  279. if (s.get("L") != null) {
  280. lView.setText(s.get("L"));
  281. lView.setVisibility(View.VISIBLE);
  282. } else {
  283. lView.setVisibility(View.GONE);
  284. }
  285. }
  286. private Map<String, String> parsePrincipal(X500Principal principal) {
  287. Map<String, String> result = new HashMap<String, String>();
  288. String toParse = principal.getName();
  289. String[] pieces = toParse.split(",");
  290. String[] tokens = {"CN", "O", "OU", "C", "ST", "L"};
  291. for (int i=0; i < pieces.length ; i++) {
  292. for (int j=0; j<tokens.length; j++) {
  293. if (pieces[i].startsWith(tokens[j] + "=")) {
  294. result.put(tokens[j], pieces[i].substring(tokens[j].length()+1));
  295. }
  296. }
  297. }
  298. return result;
  299. }
  300. private void saveServerCert() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
  301. if (mException.getServerCertificate() != null) {
  302. // TODO make this asynchronously, it can take some time
  303. OwnCloudClientUtils.addCertToKnownServersStore(mException.getServerCertificate(), getContext());
  304. }
  305. }
  306. public interface OnSslValidatorListener {
  307. public void onSavedCertificate();
  308. public void onFailedSavingCertificate();
  309. }
  310. }