Make the federation homeserver resolve more accurate (on resolve via DNS record check that the certificate present for the original host).
This commit is contained in:
		| @@ -125,7 +125,7 @@ public class Mxisd { | |||||||
|         idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher, hashManager); |         idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher, hashManager); | ||||||
|         pMgr = new ProfileManager(ProfileProviders.get(), clientDns, httpClient); |         pMgr = new ProfileManager(ProfileProviders.get(), clientDns, httpClient); | ||||||
|         notifMgr = new NotificationManager(cfg.getNotification(), NotificationHandlers.get()); |         notifMgr = new NotificationManager(cfg.getNotification(), NotificationHandlers.get()); | ||||||
|         sessMgr = new SessionManager(cfg, store, notifMgr, resolver, httpClient, signMgr); |         sessMgr = new SessionManager(cfg, store, notifMgr, resolver, signMgr); | ||||||
|         invMgr = new InvitationManager(cfg, store, idStrategy, keyMgr, signMgr, resolver, notifMgr, pMgr); |         invMgr = new InvitationManager(cfg, store, idStrategy, keyMgr, signMgr, resolver, notifMgr, pMgr); | ||||||
|         authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient); |         authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient); | ||||||
|         dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get()); |         dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get()); | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ import org.apache.http.HttpStatus; | |||||||
| import org.apache.http.client.methods.CloseableHttpResponse; | import org.apache.http.client.methods.CloseableHttpResponse; | ||||||
| import org.apache.http.client.methods.HttpGet; | import org.apache.http.client.methods.HttpGet; | ||||||
| import org.apache.http.impl.client.CloseableHttpClient; | import org.apache.http.impl.client.CloseableHttpClient; | ||||||
| import org.apache.http.impl.client.HttpClientBuilder; | import org.apache.http.impl.client.HttpClients; | ||||||
| import org.apache.http.util.EntityUtils; | import org.apache.http.util.EntityUtils; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
| @@ -73,13 +73,14 @@ public class AccountManager { | |||||||
|  |  | ||||||
|     private String getUserId(OpenIdToken openIdToken) { |     private String getUserId(OpenIdToken openIdToken) { | ||||||
|         String matrixServerName = openIdToken.getMatrixServerName(); |         String matrixServerName = openIdToken.getMatrixServerName(); | ||||||
|         String homeserverURL = resolver.resolve(matrixServerName).toString(); |         HomeserverFederationResolver.HomeserverTarget homeserverTarget = resolver.resolve(matrixServerName); | ||||||
|  |         String homeserverURL = homeserverTarget.getUrl().toString(); | ||||||
|         LOGGER.info("Domain resolved: {} => {}", matrixServerName, homeserverURL); |         LOGGER.info("Domain resolved: {} => {}", matrixServerName, homeserverURL); | ||||||
|         HttpGet getUserInfo = new HttpGet( |         HttpGet getUserInfo = new HttpGet( | ||||||
|             homeserverURL + "/_matrix/federation/v1/openid/userinfo?access_token=" + openIdToken.getAccessToken()); |             homeserverURL + "/_matrix/federation/v1/openid/userinfo?access_token=" + openIdToken.getAccessToken()); | ||||||
|         String userId; |         String userId; | ||||||
|         try (CloseableHttpClient httpClient = HttpClientBuilder.create() |         try (CloseableHttpClient httpClient = HttpClients.custom() | ||||||
|             .setSSLHostnameVerifier(new MatrixHostnameVerifier(matrixServerName)).build()) { |             .setSSLHostnameVerifier(new MatrixHostnameVerifier(homeserverTarget.getDomain())).build()) { | ||||||
|             try (CloseableHttpResponse response = httpClient.execute(getUserInfo)) { |             try (CloseableHttpResponse response = httpClient.execute(getUserInfo)) { | ||||||
|                 int statusCode = response.getStatusLine().getStatusCode(); |                 int statusCode = response.getStatusLine().getStatusCode(); | ||||||
|                 if (statusCode == HttpStatus.SC_OK) { |                 if (statusCode == HttpStatus.SC_OK) { | ||||||
|   | |||||||
| @@ -29,7 +29,11 @@ import io.kamax.matrix.json.GsonUtil; | |||||||
| import io.kamax.mxisd.config.InvitationConfig; | import io.kamax.mxisd.config.InvitationConfig; | ||||||
| import io.kamax.mxisd.config.MxisdConfig; | import io.kamax.mxisd.config.MxisdConfig; | ||||||
| import io.kamax.mxisd.config.ServerConfig; | import io.kamax.mxisd.config.ServerConfig; | ||||||
| import io.kamax.mxisd.crypto.*; | import io.kamax.mxisd.crypto.GenericKeyIdentifier; | ||||||
|  | import io.kamax.mxisd.crypto.KeyIdentifier; | ||||||
|  | import io.kamax.mxisd.crypto.KeyManager; | ||||||
|  | import io.kamax.mxisd.crypto.KeyType; | ||||||
|  | import io.kamax.mxisd.crypto.SignatureManager; | ||||||
| import io.kamax.mxisd.exception.BadRequestException; | import io.kamax.mxisd.exception.BadRequestException; | ||||||
| import io.kamax.mxisd.exception.ConfigurationException; | import io.kamax.mxisd.exception.ConfigurationException; | ||||||
| import io.kamax.mxisd.exception.MappingAlreadyExistsException; | import io.kamax.mxisd.exception.MappingAlreadyExistsException; | ||||||
| @@ -38,6 +42,7 @@ import io.kamax.mxisd.lookup.SingleLookupReply; | |||||||
| import io.kamax.mxisd.lookup.ThreePidMapping; | import io.kamax.mxisd.lookup.ThreePidMapping; | ||||||
| import io.kamax.mxisd.lookup.strategy.LookupStrategy; | import io.kamax.mxisd.lookup.strategy.LookupStrategy; | ||||||
| import io.kamax.mxisd.matrix.HomeserverFederationResolver; | import io.kamax.mxisd.matrix.HomeserverFederationResolver; | ||||||
|  | import io.kamax.mxisd.matrix.HomeserverVerifier; | ||||||
| import io.kamax.mxisd.notification.NotificationManager; | import io.kamax.mxisd.notification.NotificationManager; | ||||||
| import io.kamax.mxisd.profile.ProfileManager; | import io.kamax.mxisd.profile.ProfileManager; | ||||||
| import io.kamax.mxisd.storage.IStorage; | import io.kamax.mxisd.storage.IStorage; | ||||||
| @@ -48,23 +53,26 @@ import org.apache.commons.lang3.RandomStringUtils; | |||||||
| import org.apache.commons.lang3.StringUtils; | import org.apache.commons.lang3.StringUtils; | ||||||
| import org.apache.http.client.methods.CloseableHttpResponse; | import org.apache.http.client.methods.CloseableHttpResponse; | ||||||
| import org.apache.http.client.methods.HttpPost; | import org.apache.http.client.methods.HttpPost; | ||||||
| import org.apache.http.conn.ssl.NoopHostnameVerifier; |  | ||||||
| import org.apache.http.conn.ssl.SSLConnectionSocketFactory; |  | ||||||
| import org.apache.http.conn.ssl.TrustSelfSignedStrategy; |  | ||||||
| import org.apache.http.entity.StringEntity; | import org.apache.http.entity.StringEntity; | ||||||
| import org.apache.http.impl.client.CloseableHttpClient; | import org.apache.http.impl.client.CloseableHttpClient; | ||||||
| import org.apache.http.impl.client.HttpClients; | import org.apache.http.impl.client.HttpClients; | ||||||
| import org.apache.http.ssl.SSLContextBuilder; |  | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
| import javax.net.ssl.HostnameVerifier; |  | ||||||
| import javax.net.ssl.SSLContext; |  | ||||||
| import java.io.IOException; | import java.io.IOException; | ||||||
| import java.nio.charset.StandardCharsets; | import java.nio.charset.StandardCharsets; | ||||||
| import java.time.DateTimeException; | import java.time.DateTimeException; | ||||||
| import java.time.Instant; | import java.time.Instant; | ||||||
| import java.util.*; | import java.util.ArrayList; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.Collection; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.List; | ||||||
|  | import java.util.Map; | ||||||
|  | import java.util.Objects; | ||||||
|  | import java.util.Optional; | ||||||
|  | import java.util.Timer; | ||||||
|  | import java.util.TimerTask; | ||||||
| import java.util.concurrent.ConcurrentHashMap; | import java.util.concurrent.ConcurrentHashMap; | ||||||
| import java.util.concurrent.ForkJoinPool; | import java.util.concurrent.ForkJoinPool; | ||||||
| import java.util.concurrent.TimeUnit; | import java.util.concurrent.TimeUnit; | ||||||
| @@ -86,7 +94,6 @@ public class InvitationManager { | |||||||
|     private NotificationManager notifMgr; |     private NotificationManager notifMgr; | ||||||
|     private ProfileManager profileMgr; |     private ProfileManager profileMgr; | ||||||
|  |  | ||||||
|     private CloseableHttpClient client; |  | ||||||
|     private Timer refreshTimer; |     private Timer refreshTimer; | ||||||
|  |  | ||||||
|     private Map<String, IThreePidInviteReply> invitations = new ConcurrentHashMap<>(); |     private Map<String, IThreePidInviteReply> invitations = new ConcurrentHashMap<>(); | ||||||
| @@ -129,17 +136,6 @@ public class InvitationManager { | |||||||
|         }); |         }); | ||||||
|         log.info("Loaded saved invites"); |         log.info("Loaded saved invites"); | ||||||
|  |  | ||||||
|         // FIXME export such madness into matrix-java-sdk with a nice wrapper to talk to a homeserver |  | ||||||
|         try { |  | ||||||
|             SSLContext sslContext = SSLContextBuilder.create().loadTrustMaterial(new TrustSelfSignedStrategy()).build(); |  | ||||||
|             HostnameVerifier hostnameVerifier = new NoopHostnameVerifier(); |  | ||||||
|             SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); |  | ||||||
|             client = HttpClients.custom().setSSLSocketFactory(sslSocketFactory).build(); |  | ||||||
|         } catch (Exception e) { |  | ||||||
|             // FIXME do better... |  | ||||||
|             throw new RuntimeException(e); |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         log.info("Setting up invitation mapping refresh timer"); |         log.info("Setting up invitation mapping refresh timer"); | ||||||
|         refreshTimer = new Timer(); |         refreshTimer = new Timer(); | ||||||
|  |  | ||||||
| @@ -423,11 +419,11 @@ public class InvitationManager { | |||||||
|         String address = reply.getInvite().getAddress(); |         String address = reply.getInvite().getAddress(); | ||||||
|         String domain = reply.getInvite().getSender().getDomain(); |         String domain = reply.getInvite().getSender().getDomain(); | ||||||
|         log.info("Discovering HS for domain {}", domain); |         log.info("Discovering HS for domain {}", domain); | ||||||
|         String hsUrlOpt = resolver.resolve(domain).toString(); |         HomeserverFederationResolver.HomeserverTarget hsUrlOpt = resolver.resolve(domain); | ||||||
|  |  | ||||||
|         // TODO this is needed as this will block if called during authentication cycle due to synapse implementation |         // TODO this is needed as this will block if called during authentication cycle due to synapse implementation | ||||||
|         new Thread(() -> { // FIXME need to make this retry-able and within a general background working pool |         new Thread(() -> { // FIXME need to make this retry-able and within a general background working pool | ||||||
|             HttpPost req = new HttpPost(hsUrlOpt + "/_matrix/federation/v1/3pid/onbind"); |             HttpPost req = new HttpPost(hsUrlOpt.getUrl().toString() + "/_matrix/federation/v1/3pid/onbind"); | ||||||
|             // Expected body: https://matrix.to/#/!HUeDbmFUsWAhxHHvFG:matrix.org/$150469846739DCLWc:matrix.trancendances.fr |             // Expected body: https://matrix.to/#/!HUeDbmFUsWAhxHHvFG:matrix.org/$150469846739DCLWc:matrix.trancendances.fr | ||||||
|             JsonObject obj = new JsonObject(); |             JsonObject obj = new JsonObject(); | ||||||
|             obj.addProperty("mxid", mxid); |             obj.addProperty("mxid", mxid); | ||||||
| @@ -459,36 +455,41 @@ public class InvitationManager { | |||||||
|             Instant resolvedAt = Instant.now(); |             Instant resolvedAt = Instant.now(); | ||||||
|             boolean couldPublish = false; |             boolean couldPublish = false; | ||||||
|             boolean shouldArchive = true; |             boolean shouldArchive = true; | ||||||
|             try { |             try (CloseableHttpClient httpClient = HttpClients.custom().setSSLHostnameVerifier(new HomeserverVerifier(hsUrlOpt.getDomain())) | ||||||
|                 log.info("Posting onBind event to {}", req.getURI()); |                 .build()) { | ||||||
|                 CloseableHttpResponse response = client.execute(req); |                 try { | ||||||
|                 int statusCode = response.getStatusLine().getStatusCode(); |                     log.info("Posting onBind event to {}", req.getURI()); | ||||||
|                 log.info("Answer code: {}", statusCode); |                     CloseableHttpResponse response = httpClient.execute(req); | ||||||
|                 if (statusCode >= 300 && statusCode != 403) { |                     int statusCode = response.getStatusLine().getStatusCode(); | ||||||
|                     log.info("Answer body: {}", IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8)); |                     log.info("Answer code: {}", statusCode); | ||||||
|                     log.warn("HS returned an error."); |                     if (statusCode >= 300 && statusCode != 403) { | ||||||
|  |                         log.info("Answer body: {}", IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8)); | ||||||
|  |                         log.warn("HS returned an error."); | ||||||
|  |  | ||||||
|                     shouldArchive = statusCode != 502; |                         shouldArchive = statusCode != 502; | ||||||
|  |                         if (shouldArchive) { | ||||||
|  |                             log.info("Invite can be found in historical storage for manual re-processing"); | ||||||
|  |                         } | ||||||
|  |                     } else { | ||||||
|  |                         couldPublish = true; | ||||||
|  |                         if (statusCode == 403) { | ||||||
|  |                             log.info("Invite is obsolete or no longer under our control"); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                     response.close(); | ||||||
|  |                 } catch (IOException e) { | ||||||
|  |                     log.warn("Unable to tell HS {} about invite being mapped", domain, e); | ||||||
|  |                 } finally { | ||||||
|                     if (shouldArchive) { |                     if (shouldArchive) { | ||||||
|                         log.info("Invite can be found in historical storage for manual re-processing"); |                         synchronized (this) { | ||||||
|                     } |                             storage.insertHistoricalInvite(reply, mxid, resolvedAt, couldPublish); | ||||||
|                 } else { |                             removeInvite(reply); | ||||||
|                     couldPublish = true; |                             log.info("Moved invite {} to historical table", reply.getId()); | ||||||
|                     if (statusCode == 403) { |                         } | ||||||
|                         log.info("Invite is obsolete or no longer under our control"); |  | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 response.close(); |  | ||||||
|             } catch (IOException e) { |             } catch (IOException e) { | ||||||
|                 log.warn("Unable to tell HS {} about invite being mapped", domain, e); |                 log.error("Unable to create client to the " + hsUrlOpt.getUrl().toString(), e); | ||||||
|             } finally { |  | ||||||
|                 if (shouldArchive) { |  | ||||||
|                     synchronized (this) { |  | ||||||
|                         storage.insertHistoricalInvite(reply, mxid, resolvedAt, couldPublish); |  | ||||||
|                         removeInvite(reply); |  | ||||||
|                         log.info("Moved invite {} to historical table", reply.getId()); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |             } | ||||||
|         }).start(); |         }).start(); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -178,26 +178,26 @@ public class HomeserverFederationResolver { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     public URL resolve(String domain) { |     public HomeserverTarget resolve(String domain) { | ||||||
|         Optional<URL> s1 = resolveOverwrite(domain); |         Optional<URL> s1 = resolveOverwrite(domain); | ||||||
|         if (s1.isPresent()) { |         if (s1.isPresent()) { | ||||||
|             URL dest = s1.get(); |             URL dest = s1.get(); | ||||||
|             log.info("Resolution of {} via DNS overwrite to {}", domain, dest); |             log.info("Resolution of {} via DNS overwrite to {}", domain, dest); | ||||||
|             return dest; |             return new HomeserverTarget(dest.getHost(), dest); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         Optional<URL> s2 = resolveLiteral(domain); |         Optional<URL> s2 = resolveLiteral(domain); | ||||||
|         if (s2.isPresent()) { |         if (s2.isPresent()) { | ||||||
|             URL dest = s2.get(); |             URL dest = s2.get(); | ||||||
|             log.info("Resolution of {} as IP literal or IP/hostname with explicit port to {}", domain, dest); |             log.info("Resolution of {} as IP literal or IP/hostname with explicit port to {}", domain, dest); | ||||||
|             return dest; |             return new HomeserverTarget(dest.getHost(), dest); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         Optional<URL> s3 = resolveWellKnown(domain); |         Optional<URL> s3 = resolveWellKnown(domain); | ||||||
|         if (s3.isPresent()) { |         if (s3.isPresent()) { | ||||||
|             URL dest = s3.get(); |             URL dest = s3.get(); | ||||||
|             log.info("Resolution of {} via well-known to {}", domain, dest); |             log.info("Resolution of {} via well-known to {}", domain, dest); | ||||||
|             return dest; |             return new HomeserverTarget(dest.getHost(), dest); | ||||||
|         } |         } | ||||||
|         // The domain needs to be resolved |         // The domain needs to be resolved | ||||||
|  |  | ||||||
| @@ -205,12 +205,30 @@ public class HomeserverFederationResolver { | |||||||
|         if (s4.isPresent()) { |         if (s4.isPresent()) { | ||||||
|             URL dest = s4.get(); |             URL dest = s4.get(); | ||||||
|             log.info("Resolution of {} via DNS SRV record to {}", domain, dest); |             log.info("Resolution of {} via DNS SRV record to {}", domain, dest); | ||||||
|             return dest; |             return new HomeserverTarget(domain, dest); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         URL dest = build(domain + ":" + getDefaultPort()); |         URL dest = build(domain + ":" + getDefaultPort()); | ||||||
|         log.info("Resolution of {} to {}", domain, dest); |         log.info("Resolution of {} to {}", domain, dest); | ||||||
|         return dest; |         return new HomeserverTarget(dest.getHost(), dest); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     public static class HomeserverTarget { | ||||||
|  |  | ||||||
|  |         private final String domain; | ||||||
|  |         private final URL url; | ||||||
|  |  | ||||||
|  |         HomeserverTarget(String domain, URL url) { | ||||||
|  |             this.domain = domain; | ||||||
|  |             this.url = url; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public String getDomain() { | ||||||
|  |             return domain; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         public URL getUrl() { | ||||||
|  |             return url; | ||||||
|  |         } | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										85
									
								
								src/main/java/io/kamax/mxisd/matrix/HomeserverVerifier.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/main/java/io/kamax/mxisd/matrix/HomeserverVerifier.java
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,85 @@ | |||||||
|  | package io.kamax.mxisd.matrix; | ||||||
|  |  | ||||||
|  | import org.slf4j.Logger; | ||||||
|  | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
|  | import java.security.cert.Certificate; | ||||||
|  | import java.security.cert.CertificateParsingException; | ||||||
|  | import java.security.cert.X509Certificate; | ||||||
|  | import java.util.ArrayList; | ||||||
|  | import java.util.Collections; | ||||||
|  | import java.util.List; | ||||||
|  | import javax.net.ssl.HostnameVerifier; | ||||||
|  | import javax.net.ssl.SSLPeerUnverifiedException; | ||||||
|  | import javax.net.ssl.SSLSession; | ||||||
|  |  | ||||||
|  | public class HomeserverVerifier implements HostnameVerifier { | ||||||
|  |  | ||||||
|  |     private static final Logger LOGGER = LoggerFactory.getLogger(HomeserverVerifier.class); | ||||||
|  |     private static final String ALT_DNS_NAME_TYPE = "2"; | ||||||
|  |     private static final String ALT_IP_ADDRESS_TYPE = "7"; | ||||||
|  |  | ||||||
|  |     private final String matrixHostname; | ||||||
|  |  | ||||||
|  |     public HomeserverVerifier(String matrixHostname) { | ||||||
|  |         this.matrixHostname = matrixHostname; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     @Override | ||||||
|  |     public boolean verify(String hostname, SSLSession session) { | ||||||
|  |         try { | ||||||
|  |             Certificate peerCertificate = session.getPeerCertificates()[0]; | ||||||
|  |             if (peerCertificate instanceof X509Certificate) { | ||||||
|  |                 X509Certificate x509Certificate = (X509Certificate) peerCertificate; | ||||||
|  |                 if (x509Certificate.getSubjectAlternativeNames() == null) { | ||||||
|  |                     return false; | ||||||
|  |                 } | ||||||
|  |                 for (String altSubjectName : getAltSubjectNames(x509Certificate)) { | ||||||
|  |                     if (match(altSubjectName)) { | ||||||
|  |                         return true; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (SSLPeerUnverifiedException | CertificateParsingException e) { | ||||||
|  |             LOGGER.error("Unable to check remote host", e); | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private List<String> getAltSubjectNames(X509Certificate x509Certificate) { | ||||||
|  |         List<String> subjectNames = new ArrayList<>(); | ||||||
|  |         try { | ||||||
|  |             for (List<?> subjectAlternativeNames : x509Certificate.getSubjectAlternativeNames()) { | ||||||
|  |                 if (subjectAlternativeNames == null | ||||||
|  |                     || subjectAlternativeNames.size() < 2 | ||||||
|  |                     || subjectAlternativeNames.get(0) == null | ||||||
|  |                     || subjectAlternativeNames.get(1) == null) { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 String subjectType = subjectAlternativeNames.get(0).toString(); | ||||||
|  |                 switch (subjectType) { | ||||||
|  |                     case ALT_DNS_NAME_TYPE: | ||||||
|  |                     case ALT_IP_ADDRESS_TYPE: | ||||||
|  |                         subjectNames.add(subjectAlternativeNames.get(1).toString()); | ||||||
|  |                         break; | ||||||
|  |                     default: | ||||||
|  |                         LOGGER.trace("Unusable subject type: " + subjectType); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         } catch (CertificateParsingException e) { | ||||||
|  |             LOGGER.error("Unable to parse the certificate", e); | ||||||
|  |             return Collections.emptyList(); | ||||||
|  |         } | ||||||
|  |         return subjectNames; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     private boolean match(String altSubjectName) { | ||||||
|  |         if (altSubjectName.startsWith("*.")) { | ||||||
|  |             return altSubjectName.toLowerCase().endsWith(matrixHostname.toLowerCase()); | ||||||
|  |         } else { | ||||||
|  |             return matrixHostname.equalsIgnoreCase(altSubjectName); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -39,6 +39,7 @@ import io.kamax.mxisd.lookup.SingleLookupReply; | |||||||
| import io.kamax.mxisd.lookup.SingleLookupRequest; | import io.kamax.mxisd.lookup.SingleLookupRequest; | ||||||
| import io.kamax.mxisd.lookup.ThreePidValidation; | import io.kamax.mxisd.lookup.ThreePidValidation; | ||||||
| import io.kamax.mxisd.matrix.HomeserverFederationResolver; | import io.kamax.mxisd.matrix.HomeserverFederationResolver; | ||||||
|  | import io.kamax.mxisd.matrix.HomeserverVerifier; | ||||||
| import io.kamax.mxisd.notification.NotificationManager; | import io.kamax.mxisd.notification.NotificationManager; | ||||||
| import io.kamax.mxisd.storage.IStorage; | import io.kamax.mxisd.storage.IStorage; | ||||||
| import io.kamax.mxisd.storage.dao.IThreePidSessionDao; | import io.kamax.mxisd.storage.dao.IThreePidSessionDao; | ||||||
| @@ -53,6 +54,7 @@ import org.apache.commons.lang3.StringUtils; | |||||||
| import org.apache.http.client.methods.CloseableHttpResponse; | import org.apache.http.client.methods.CloseableHttpResponse; | ||||||
| import org.apache.http.client.methods.HttpGet; | import org.apache.http.client.methods.HttpGet; | ||||||
| import org.apache.http.impl.client.CloseableHttpClient; | import org.apache.http.impl.client.CloseableHttpClient; | ||||||
|  | import org.apache.http.impl.client.HttpClients; | ||||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||||
|  |  | ||||||
| @@ -73,7 +75,6 @@ public class SessionManager { | |||||||
|     private IStorage storage; |     private IStorage storage; | ||||||
|     private NotificationManager notifMgr; |     private NotificationManager notifMgr; | ||||||
|     private HomeserverFederationResolver resolver; |     private HomeserverFederationResolver resolver; | ||||||
|     private CloseableHttpClient client; |  | ||||||
|     private SignatureManager signatureManager; |     private SignatureManager signatureManager; | ||||||
|  |  | ||||||
|     public SessionManager( |     public SessionManager( | ||||||
| @@ -81,14 +82,12 @@ public class SessionManager { | |||||||
|         IStorage storage, |         IStorage storage, | ||||||
|         NotificationManager notifMgr, |         NotificationManager notifMgr, | ||||||
|         HomeserverFederationResolver resolver, |         HomeserverFederationResolver resolver, | ||||||
|         CloseableHttpClient client, |  | ||||||
|         SignatureManager signatureManager |         SignatureManager signatureManager | ||||||
|     ) { |     ) { | ||||||
|         this.cfg = cfg; |         this.cfg = cfg; | ||||||
|         this.storage = storage; |         this.storage = storage; | ||||||
|         this.notifMgr = notifMgr; |         this.notifMgr = notifMgr; | ||||||
|         this.resolver = resolver; |         this.resolver = resolver; | ||||||
|         this.client = client; |  | ||||||
|         this.signatureManager = signatureManager; |         this.signatureManager = signatureManager; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -308,25 +307,34 @@ public class SessionManager { | |||||||
|  |  | ||||||
|         String canonical = MatrixJson.encodeCanonical(jsonObject); |         String canonical = MatrixJson.encodeCanonical(jsonObject); | ||||||
|  |  | ||||||
|         String originUrl = resolver.resolve(origin).toString(); |         HomeserverFederationResolver.HomeserverTarget homeserverTarget = resolver.resolve(origin); | ||||||
|  |  | ||||||
|         validateServerKey(key, sig, canonical, originUrl); |         validateServerKey(key, sig, canonical, homeserverTarget); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private String removeQuotes(String origin) { |     private String removeQuotes(String origin) { | ||||||
|         return origin.startsWith("\"") && origin.endsWith("\"") ? origin.substring(1, origin.length() - 1) : origin; |         return origin.startsWith("\"") && origin.endsWith("\"") ? origin.substring(1, origin.length() - 1) : origin; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private void validateServerKey(String key, String signature, String canonical, String originUrl) { |     private void validateServerKey(String key, String signature, String canonical, | ||||||
|  |                                    HomeserverFederationResolver.HomeserverTarget homeserverTarget) { | ||||||
|  |         String originUrl = homeserverTarget.getUrl().toString(); | ||||||
|         HttpGet request = new HttpGet(originUrl + "/_matrix/key/v2/server"); |         HttpGet request = new HttpGet(originUrl + "/_matrix/key/v2/server"); | ||||||
|         log.info("Get keys from the server {}", request.getURI()); |         log.info("Get keys from the server {}", request.getURI()); | ||||||
|         try (CloseableHttpResponse response = client.execute(request)) { |         try (CloseableHttpClient httpClient = HttpClients.custom() | ||||||
|             int statusCode = response.getStatusLine().getStatusCode(); |             .setSSLHostnameVerifier(new HomeserverVerifier(homeserverTarget.getDomain())).build()) { | ||||||
|             log.info("Answer code: {}", statusCode); |             try (CloseableHttpResponse response = httpClient.execute(request)) { | ||||||
|             if (statusCode == 200) { |                 int statusCode = response.getStatusLine().getStatusCode(); | ||||||
|                 verifyKey(key, signature, canonical, response); |                 log.info("Answer code: {}", statusCode); | ||||||
|             } else { |                 if (statusCode == 200) { | ||||||
|                 throw new RemoteHomeServerException("Unable to fetch server keys."); |                     verifyKey(key, signature, canonical, response); | ||||||
|  |                 } else { | ||||||
|  |                     throw new RemoteHomeServerException("Unable to fetch server keys."); | ||||||
|  |                 } | ||||||
|  |             } catch (IOException e) { | ||||||
|  |                 String message = "Unable to get server keys: " + originUrl; | ||||||
|  |                 log.error(message, e); | ||||||
|  |                 throw new IllegalArgumentException(message); | ||||||
|             } |             } | ||||||
|         } catch (IOException e) { |         } catch (IOException e) { | ||||||
|             String message = "Unable to get server keys: " + originUrl; |             String message = "Unable to get server keys: " + originUrl; | ||||||
|   | |||||||
| @@ -51,13 +51,13 @@ public class HomeserverFederationResolverTest { | |||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     public void hostnameWithoutPort() { |     public void hostnameWithoutPort() { | ||||||
|         URL url = resolver.resolve("example.org"); |         URL url = resolver.resolve("example.org").getUrl(); | ||||||
|         assertEquals("https://example.org:8448", url.toString()); |         assertEquals("https://example.org:8448", url.toString()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     @Test |     @Test | ||||||
|     public void hostnameWithPort() { |     public void hostnameWithPort() { | ||||||
|         URL url = resolver.resolve("example.org:443"); |         URL url = resolver.resolve("example.org:443").getUrl(); | ||||||
|         assertEquals("https://example.org:443", url.toString()); |         assertEquals("https://example.org:443", url.toString()); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user