aboutsummaryrefslogtreecommitdiff
path: root/src/org/jivesoftware/smack/ServerTrustManager.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/org/jivesoftware/smack/ServerTrustManager.java')
-rw-r--r--src/org/jivesoftware/smack/ServerTrustManager.java331
1 files changed, 331 insertions, 0 deletions
diff --git a/src/org/jivesoftware/smack/ServerTrustManager.java b/src/org/jivesoftware/smack/ServerTrustManager.java
new file mode 100644
index 0000000..63da3e7
--- /dev/null
+++ b/src/org/jivesoftware/smack/ServerTrustManager.java
@@ -0,0 +1,331 @@
+/**
+ * $RCSfile$
+ * $Revision$
+ * $Date$
+ *
+ * Copyright 2003-2005 Jive Software.
+ *
+ * All rights reserved. Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.jivesoftware.smack;
+
+import javax.net.ssl.X509TrustManager;
+
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.security.*;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateParsingException;
+import java.security.cert.X509Certificate;
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Trust manager that checks all certificates presented by the server. This class
+ * is used during TLS negotiation. It is possible to disable/enable some or all checkings
+ * by configuring the {@link ConnectionConfiguration}. The truststore file that contains
+ * knows and trusted CA root certificates can also be configure in {@link ConnectionConfiguration}.
+ *
+ * @author Gaston Dombiak
+ */
+class ServerTrustManager implements X509TrustManager {
+
+ private static Pattern cnPattern = Pattern.compile("(?i)(cn=)([^,]*)");
+
+ private ConnectionConfiguration configuration;
+
+ /**
+ * Holds the domain of the remote server we are trying to connect
+ */
+ private String server;
+ private KeyStore trustStore;
+
+ private static Map<KeyStoreOptions, KeyStore> stores = new HashMap<KeyStoreOptions, KeyStore>();
+
+ public ServerTrustManager(String server, ConnectionConfiguration configuration) {
+ this.configuration = configuration;
+ this.server = server;
+
+ InputStream in = null;
+ synchronized (stores) {
+ KeyStoreOptions options = new KeyStoreOptions(configuration.getTruststoreType(),
+ configuration.getTruststorePath(), configuration.getTruststorePassword());
+ if (stores.containsKey(options)) {
+ trustStore = stores.get(options);
+ } else {
+ try {
+ trustStore = KeyStore.getInstance(options.getType());
+ in = new FileInputStream(options.getPath());
+ trustStore.load(in, options.getPassword().toCharArray());
+ } catch (Exception e) {
+ trustStore = null;
+ e.printStackTrace();
+ } finally {
+ if (in != null) {
+ try {
+ in.close();
+ } catch (IOException ioe) {
+ // Ignore.
+ }
+ }
+ }
+ stores.put(options, trustStore);
+ }
+ if (trustStore == null)
+ // Disable root CA checking
+ configuration.setVerifyRootCAEnabled(false);
+ }
+ }
+
+ public X509Certificate[] getAcceptedIssuers() {
+ return new X509Certificate[0];
+ }
+
+ public void checkClientTrusted(X509Certificate[] arg0, String arg1)
+ throws CertificateException {
+ }
+
+ public void checkServerTrusted(X509Certificate[] x509Certificates, String arg1)
+ throws CertificateException {
+
+ int nSize = x509Certificates.length;
+
+ List<String> peerIdentities = getPeerIdentity(x509Certificates[0]);
+
+ if (configuration.isVerifyChainEnabled()) {
+ // Working down the chain, for every certificate in the chain,
+ // verify that the subject of the certificate is the issuer of the
+ // next certificate in the chain.
+ Principal principalLast = null;
+ for (int i = nSize -1; i >= 0 ; i--) {
+ X509Certificate x509certificate = x509Certificates[i];
+ Principal principalIssuer = x509certificate.getIssuerDN();
+ Principal principalSubject = x509certificate.getSubjectDN();
+ if (principalLast != null) {
+ if (principalIssuer.equals(principalLast)) {
+ try {
+ PublicKey publickey =
+ x509Certificates[i + 1].getPublicKey();
+ x509Certificates[i].verify(publickey);
+ }
+ catch (GeneralSecurityException generalsecurityexception) {
+ throw new CertificateException(
+ "signature verification failed of " + peerIdentities);
+ }
+ }
+ else {
+ throw new CertificateException(
+ "subject/issuer verification failed of " + peerIdentities);
+ }
+ }
+ principalLast = principalSubject;
+ }
+ }
+
+ if (configuration.isVerifyRootCAEnabled()) {
+ // Verify that the the last certificate in the chain was issued
+ // by a third-party that the client trusts.
+ boolean trusted = false;
+ try {
+ trusted = trustStore.getCertificateAlias(x509Certificates[nSize - 1]) != null;
+ if (!trusted && nSize == 1 && configuration.isSelfSignedCertificateEnabled())
+ {
+ System.out.println("Accepting self-signed certificate of remote server: " +
+ peerIdentities);
+ trusted = true;
+ }
+ }
+ catch (KeyStoreException e) {
+ e.printStackTrace();
+ }
+ if (!trusted) {
+ throw new CertificateException("root certificate not trusted of " + peerIdentities);
+ }
+ }
+
+ if (configuration.isNotMatchingDomainCheckEnabled()) {
+ // Verify that the first certificate in the chain corresponds to
+ // the server we desire to authenticate.
+ // Check if the certificate uses a wildcard indicating that subdomains are valid
+ if (peerIdentities.size() == 1 && peerIdentities.get(0).startsWith("*.")) {
+ // Remove the wildcard
+ String peerIdentity = peerIdentities.get(0).replace("*.", "");
+ // Check if the requested subdomain matches the certified domain
+ if (!server.endsWith(peerIdentity)) {
+ throw new CertificateException("target verification failed of " + peerIdentities);
+ }
+ }
+ else if (!peerIdentities.contains(server)) {
+ throw new CertificateException("target verification failed of " + peerIdentities);
+ }
+ }
+
+ if (configuration.isExpiredCertificatesCheckEnabled()) {
+ // For every certificate in the chain, verify that the certificate
+ // is valid at the current time.
+ Date date = new Date();
+ for (int i = 0; i < nSize; i++) {
+ try {
+ x509Certificates[i].checkValidity(date);
+ }
+ catch (GeneralSecurityException generalsecurityexception) {
+ throw new CertificateException("invalid date of " + server);
+ }
+ }
+ }
+
+ }
+
+ /**
+ * Returns the identity of the remote server as defined in the specified certificate. The
+ * identity is defined in the subjectDN of the certificate and it can also be defined in
+ * the subjectAltName extension of type "xmpp". When the extension is being used then the
+ * identity defined in the extension in going to be returned. Otherwise, the value stored in
+ * the subjectDN is returned.
+ *
+ * @param x509Certificate the certificate the holds the identity of the remote server.
+ * @return the identity of the remote server as defined in the specified certificate.
+ */
+ public static List<String> getPeerIdentity(X509Certificate x509Certificate) {
+ // Look the identity in the subjectAltName extension if available
+ List<String> names = getSubjectAlternativeNames(x509Certificate);
+ if (names.isEmpty()) {
+ String name = x509Certificate.getSubjectDN().getName();
+ Matcher matcher = cnPattern.matcher(name);
+ if (matcher.find()) {
+ name = matcher.group(2);
+ }
+ // Create an array with the unique identity
+ names = new ArrayList<String>();
+ names.add(name);
+ }
+ return names;
+ }
+
+ /**
+ * Returns the JID representation of an XMPP entity contained as a SubjectAltName extension
+ * in the certificate. If none was found then return <tt>null</tt>.
+ *
+ * @param certificate the certificate presented by the remote entity.
+ * @return the JID representation of an XMPP entity contained as a SubjectAltName extension
+ * in the certificate. If none was found then return <tt>null</tt>.
+ */
+ private static List<String> getSubjectAlternativeNames(X509Certificate certificate) {
+ List<String> identities = new ArrayList<String>();
+ try {
+ Collection<List<?>> altNames = certificate.getSubjectAlternativeNames();
+ // Check that the certificate includes the SubjectAltName extension
+ if (altNames == null) {
+ return Collections.emptyList();
+ }
+ // Use the type OtherName to search for the certified server name
+ /*for (List item : altNames) {
+ Integer type = (Integer) item.get(0);
+ if (type == 0) {
+ // Type OtherName found so return the associated value
+ try {
+ // Value is encoded using ASN.1 so decode it to get the server's identity
+ ASN1InputStream decoder = new ASN1InputStream((byte[]) item.toArray()[1]);
+ DEREncodable encoded = decoder.readObject();
+ encoded = ((DERSequence) encoded).getObjectAt(1);
+ encoded = ((DERTaggedObject) encoded).getObject();
+ encoded = ((DERTaggedObject) encoded).getObject();
+ String identity = ((DERUTF8String) encoded).getString();
+ // Add the decoded server name to the list of identities
+ identities.add(identity);
+ }
+ catch (UnsupportedEncodingException e) {
+ // Ignore
+ }
+ catch (IOException e) {
+ // Ignore
+ }
+ catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+ // Other types are not good for XMPP so ignore them
+ System.out.println("SubjectAltName of invalid type found: " + certificate);
+ }*/
+ }
+ catch (CertificateParsingException e) {
+ e.printStackTrace();
+ }
+ return identities;
+ }
+
+ private static class KeyStoreOptions {
+ private final String type;
+ private final String path;
+ private final String password;
+
+ public KeyStoreOptions(String type, String path, String password) {
+ super();
+ this.type = type;
+ this.path = path;
+ this.password = password;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getPath() {
+ return path;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((password == null) ? 0 : password.hashCode());
+ result = prime * result + ((path == null) ? 0 : path.hashCode());
+ result = prime * result + ((type == null) ? 0 : type.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ KeyStoreOptions other = (KeyStoreOptions) obj;
+ if (password == null) {
+ if (other.password != null)
+ return false;
+ } else if (!password.equals(other.password))
+ return false;
+ if (path == null) {
+ if (other.path != null)
+ return false;
+ } else if (!path.equals(other.path))
+ return false;
+ if (type == null) {
+ if (other.type != null)
+ return false;
+ } else if (!type.equals(other.type))
+ return false;
+ return true;
+ }
+ }
+}