Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

SSLCertificateDialogModel.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. /*
  2. * Copyright (c) 2006-2017 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
  5. * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
  6. * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
  7. * permit persons to whom the Software is furnished to do so, subject to the following conditions:
  8. *
  9. * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
  10. * Software.
  11. *
  12. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
  13. * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
  14. * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
  15. * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  16. */
  17. package com.dmdirc.ui.core.dialogs.sslcertificate;
  18. import com.dmdirc.tls.CertificateAction;
  19. import com.dmdirc.tls.CertificateDoesntMatchHostException;
  20. import com.dmdirc.tls.CertificateHostChecker;
  21. import com.dmdirc.tls.CertificateManager;
  22. import com.dmdirc.tls.CertificateNotTrustedException;
  23. import java.security.cert.CertificateException;
  24. import java.security.cert.CertificateExpiredException;
  25. import java.security.cert.CertificateNotYetValidException;
  26. import java.security.cert.CertificateParsingException;
  27. import java.security.cert.X509Certificate;
  28. import java.util.ArrayList;
  29. import java.util.Collection;
  30. import java.util.List;
  31. import java.util.Map;
  32. /**
  33. * Model for SSL certificate dialogs.
  34. *
  35. * @since 0.6.3m1
  36. */
  37. public class SSLCertificateDialogModel {
  38. /** The text to use if a field isn't present on the certificate. */
  39. private static final String NOTPRESENT = "(not present on certificate)";
  40. /** The certificate chain that we're displaying information about. */
  41. private final List<X509Certificate> chain;
  42. /** The certificate manager for the connection attempt. */
  43. private final CertificateManager manager;
  44. /** The list of problems found with the certs, if any. */
  45. private final Collection<CertificateException> problems;
  46. /** Checker to use for hostnames. */
  47. private final CertificateHostChecker hostChecker;
  48. /**
  49. * Creates a new SSLCertificateDialogModel for the specified chain.
  50. *
  51. * @param chain The chain of certificates to display info on
  52. * @param problems A list of problems with the certificates, if any
  53. * @param manager The certificate manager responsible for the certs
  54. */
  55. public SSLCertificateDialogModel(final List<X509Certificate> chain,
  56. final Collection<CertificateException> problems,
  57. final CertificateManager manager) {
  58. this.chain = chain;
  59. this.problems = problems;
  60. this.manager = manager;
  61. this.hostChecker = new CertificateHostChecker();
  62. }
  63. /**
  64. * Retrieves the certificate chain that's under question.
  65. *
  66. * @return A list of {@link CertificateChainEntry}s corresponding to the certificate chain being
  67. * questioned.
  68. */
  69. public List<CertificateChainEntry> getCertificateChain() {
  70. final List<CertificateChainEntry> res = new ArrayList<>();
  71. boolean first = true;
  72. for (X509Certificate cert : chain) {
  73. boolean invalid = first && !hostChecker.isValidFor(cert, manager.getServerName());
  74. first = false;
  75. try {
  76. cert.checkValidity();
  77. } catch (CertificateException ex) {
  78. invalid |= true;
  79. }
  80. res.add(new CertificateChainEntry(CertificateManager
  81. .getDNFieldsFromCert(cert).get("CN"),
  82. manager.isTrusted(cert).isTrusted(), invalid));
  83. }
  84. return res;
  85. }
  86. /**
  87. * Retrieves displayable information about the certificate with the specified index in the
  88. * chain.
  89. *
  90. * @param index The index of the certificate to request information on
  91. *
  92. * @return A list of lists of {@link CertificateInformationEntry}s.
  93. */
  94. public List<List<CertificateInformationEntry>> getCertificateInfo(final int index) {
  95. final List<List<CertificateInformationEntry>> res = new ArrayList<>();
  96. final X509Certificate cert = chain.get(index);
  97. List<CertificateInformationEntry> group;
  98. boolean tooOld = false;
  99. boolean tooNew = false;
  100. try {
  101. cert.checkValidity();
  102. } catch (CertificateExpiredException ex) {
  103. tooOld = true;
  104. } catch (CertificateNotYetValidException ex) {
  105. tooNew = true;
  106. }
  107. group = new ArrayList<>();
  108. group.add(new CertificateInformationEntry("Valid from",
  109. cert.getNotBefore().toString(), tooNew, false));
  110. group.add(new CertificateInformationEntry("Valid to",
  111. cert.getNotAfter().toString(), tooOld, false));
  112. res.add(group);
  113. final boolean wrongName = index == 0 && !hostChecker.isValidFor(cert, manager.getServerName());
  114. final String names = getAlternateNames(cert);
  115. final Map<String, String> fields = CertificateManager.getDNFieldsFromCert(cert);
  116. group = new ArrayList<>();
  117. addCertField(fields, group, "Common name", "CN", wrongName);
  118. group.add(new CertificateInformationEntry("Alternate names",
  119. names == null ? NOTPRESENT : names, wrongName, names == null));
  120. addCertField(fields, group, "Organisation", "O", false);
  121. addCertField(fields, group, "Unit", "OU", false);
  122. addCertField(fields, group, "Locality", "L", false);
  123. addCertField(fields, group, "State", "ST", false);
  124. addCertField(fields, group, "Country", "C", false);
  125. res.add(group);
  126. group = new ArrayList<>();
  127. group.add(new CertificateInformationEntry("Serial number",
  128. cert.getSerialNumber().toString(), false, false));
  129. group.add(new CertificateInformationEntry("Algorithm",
  130. cert.getSigAlgName(), false, false));
  131. group.add(new CertificateInformationEntry("SSL version",
  132. String.valueOf(cert.getVersion()), false, false));
  133. res.add(group);
  134. return res;
  135. }
  136. /**
  137. * Retrieves a list of all the alternate names of the specified certificates as a
  138. * comma-separated string.
  139. *
  140. * @param cert The certificate to retrieve alternate names for
  141. *
  142. * @return A comma-separated list of alternate names
  143. */
  144. private String getAlternateNames(final X509Certificate cert) {
  145. final StringBuilder res = new StringBuilder();
  146. try {
  147. if (cert.getSubjectAlternativeNames() == null) {
  148. return null;
  149. }
  150. for (List<?> entry : cert.getSubjectAlternativeNames()) {
  151. final int type = (Integer) entry.get(0);
  152. // DNS or IP
  153. if (type == 2 || type == 7) {
  154. if (res.length() > 0) {
  155. res.append(", ");
  156. }
  157. res.append(entry.get(1));
  158. }
  159. }
  160. } catch (CertificateParsingException ex) {
  161. // Do nothing
  162. }
  163. return res.toString();
  164. }
  165. /**
  166. * Adds a field to the specified group.
  167. *
  168. * @param fields The fields extracted from the certificate
  169. * @param group The group to add an entry to
  170. * @param title The user-friendly title of the field
  171. * @param field The name of the field to look for
  172. * @param invalid Whether or not the field is a cause for concern
  173. */
  174. private void addCertField(
  175. final Map<String, String> fields,
  176. final List<CertificateInformationEntry> group,
  177. final String title,
  178. final String field,
  179. final boolean invalid) {
  180. group.add(new CertificateInformationEntry(title, fields.getOrDefault(field, NOTPRESENT), invalid,
  181. !fields.containsKey(field)));
  182. }
  183. /**
  184. * Retrieves a list of summary elements to describe the overall status of the certificate chain.
  185. *
  186. * @return A list of summary entries
  187. */
  188. public List<CertificateSummaryEntry> getSummary() {
  189. final List<CertificateSummaryEntry> res = new ArrayList<>();
  190. boolean outOfDate = false;
  191. boolean wrongHost = false;
  192. boolean notTrusted = false;
  193. for (CertificateException ex : problems) {
  194. if (ex instanceof CertificateExpiredException
  195. || ex instanceof CertificateNotYetValidException) {
  196. outOfDate = true;
  197. } else if (ex instanceof CertificateDoesntMatchHostException) {
  198. wrongHost = true;
  199. } else if (ex instanceof CertificateNotTrustedException) {
  200. notTrusted = true;
  201. }
  202. }
  203. if (outOfDate) {
  204. res.add(new CertificateSummaryEntry("One or more certificates are "
  205. + "not within their validity period", false));
  206. } else {
  207. res.add(new CertificateSummaryEntry("All certificates are "
  208. + "within their validity period", true));
  209. }
  210. if (notTrusted) {
  211. res.add(new CertificateSummaryEntry("The certificate is not issued "
  212. + "by a trusted authority", false));
  213. } else {
  214. res.add(new CertificateSummaryEntry("The certificate chain is "
  215. + "trusted", true));
  216. }
  217. if (wrongHost) {
  218. res.add(new CertificateSummaryEntry("The certificate is not issued "
  219. + "to the host you are connecting to", false));
  220. } else {
  221. res.add(new CertificateSummaryEntry("The certificate is issued "
  222. + "to the host you are connecting to", true));
  223. }
  224. return res;
  225. }
  226. /**
  227. * Determines whether or not a response is required from the user about this certificate chain.
  228. *
  229. * @return True if a response is required, false otherwise
  230. */
  231. public boolean needsResponse() {
  232. return !problems.isEmpty();
  233. }
  234. /**
  235. * Retrieves the name of the server to which the user is trying to connect.
  236. *
  237. * @return The name of the server that the user is trying to connect to
  238. */
  239. public String getServerName() {
  240. return manager.getServerName();
  241. }
  242. /**
  243. * Performs the specified action on the certificate chain/connection. Should only be called once
  244. * per instance, and only if {@link #needsResponse()} returns true.
  245. *
  246. * @param action The action to be performed
  247. */
  248. public void performAction(final CertificateAction action) {
  249. if (!needsResponse()) {
  250. throw new IllegalStateException("Can't perform action when "
  251. + "no action is needed");
  252. }
  253. manager.setAction(action);
  254. }
  255. }