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