Fix homeserver verification with wildcards certificates.
Disable v2 by default. Add migration to fix the accepted table (due to sqlite unable to change constraint, drop table and create again). Fix displaying the expiration period of the new token. Remove duplicated code. Use v1 single lookup when receive the request with `none` algorithm and the only one argument. Hide v2 endpoint if v2 API disabled.
This commit is contained in:
@@ -189,23 +189,32 @@ public class HttpMxisd {
|
||||
}
|
||||
|
||||
private void accountEndpoints(RoutingHandler routingHandler) {
|
||||
routingHandler.post(AccountRegisterHandler.Path, SaneHandler.around(new AccountRegisterHandler(m.getAccMgr())));
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, sane(new AccountGetUserInfoHandler(m.getAccMgr())),
|
||||
AccountGetUserInfoHandler.Path, true);
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, sane(new AccountLogoutHandler(m.getAccMgr())),
|
||||
AccountLogoutHandler.Path, true);
|
||||
MatrixConfig matrixConfig = m.getConfig().getMatrix();
|
||||
if (matrixConfig.isV2()) {
|
||||
routingHandler.post(AccountRegisterHandler.Path, SaneHandler.around(new AccountRegisterHandler(m.getAccMgr())));
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, sane(new AccountGetUserInfoHandler(m.getAccMgr())),
|
||||
AccountGetUserInfoHandler.Path, true);
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, sane(new AccountLogoutHandler(m.getAccMgr())),
|
||||
AccountLogoutHandler.Path, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void termsEndpoints(RoutingHandler routingHandler) {
|
||||
routingHandler.get(GetTermsHandler.PATH, new GetTermsHandler(m.getConfig().getPolicy()));
|
||||
routingHandler.post(AcceptTermsHandler.PATH, sane(new AcceptTermsHandler(m.getAccMgr())));
|
||||
MatrixConfig matrixConfig = m.getConfig().getMatrix();
|
||||
if (matrixConfig.isV2()) {
|
||||
routingHandler.get(GetTermsHandler.PATH, new GetTermsHandler(m.getConfig().getPolicy()));
|
||||
routingHandler.post(AcceptTermsHandler.PATH, sane(new AcceptTermsHandler(m.getAccMgr())));
|
||||
}
|
||||
}
|
||||
|
||||
private void hashEndpoints(RoutingHandler routingHandler) {
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, sane(new HashDetailsHandler(m.getHashManager())),
|
||||
HashDetailsHandler.PATH, true);
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.POST,
|
||||
sane(new HashLookupHandler(m.getIdentity(), m.getHashManager())), HashLookupHandler.Path, true);
|
||||
MatrixConfig matrixConfig = m.getConfig().getMatrix();
|
||||
if (matrixConfig.isV2()) {
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.GET, sane(new HashDetailsHandler(m.getHashManager())),
|
||||
HashDetailsHandler.PATH, true);
|
||||
wrapWithTokenAndAuthorizationHandlers(routingHandler, Methods.POST,
|
||||
sane(new HashLookupHandler(m.getIdentity(), m.getHashManager())), HashLookupHandler.Path, true);
|
||||
}
|
||||
}
|
||||
|
||||
private void addEndpoints(RoutingHandler routingHandler, HttpString method, boolean useAuthorization, ApiHandler... handlers) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import io.kamax.mxisd.exception.BadRequestException;
|
||||
import io.kamax.mxisd.exception.InvalidCredentialsException;
|
||||
import io.kamax.mxisd.exception.NotFoundException;
|
||||
import io.kamax.mxisd.matrix.HomeserverFederationResolver;
|
||||
import io.kamax.mxisd.matrix.HomeserverVerifier;
|
||||
import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AccountDao;
|
||||
import org.apache.http.HttpStatus;
|
||||
@@ -22,18 +23,10 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLPeerUnverifiedException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
public class AccountManager {
|
||||
|
||||
@@ -80,7 +73,7 @@ public class AccountManager {
|
||||
homeserverURL + "/_matrix/federation/v1/openid/userinfo?access_token=" + openIdToken.getAccessToken());
|
||||
String userId;
|
||||
try (CloseableHttpClient httpClient = HttpClients.custom()
|
||||
.setSSLHostnameVerifier(new MatrixHostnameVerifier(homeserverTarget.getDomain())).build()) {
|
||||
.setSSLHostnameVerifier(new HomeserverVerifier(homeserverTarget.getDomain())).build()) {
|
||||
try (CloseableHttpResponse response = httpClient.execute(getUserInfo)) {
|
||||
int statusCode = response.getStatusLine().getStatusCode();
|
||||
if (statusCode == HttpStatus.SC_OK) {
|
||||
@@ -170,74 +163,4 @@ public class AccountManager {
|
||||
public MatrixConfig getMatrixConfig() {
|
||||
return matrixConfig;
|
||||
}
|
||||
|
||||
public static class MatrixHostnameVerifier implements HostnameVerifier {
|
||||
|
||||
private static final String ALT_DNS_NAME_TYPE = "2";
|
||||
private static final String ALT_IP_ADDRESS_TYPE = "7";
|
||||
|
||||
private final String matrixHostname;
|
||||
|
||||
public MatrixHostnameVerifier(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ public class MatrixConfig {
|
||||
private String domain;
|
||||
private Identity identity = new Identity();
|
||||
private boolean v1 = true;
|
||||
private boolean v2 = true;
|
||||
private boolean v2 = false;
|
||||
|
||||
public String getDomain() {
|
||||
return domain;
|
||||
|
||||
@@ -48,7 +48,7 @@ public class AccountRegisterHandler extends BasicHttpHandler {
|
||||
|
||||
if (LOGGER.isInfoEnabled()) {
|
||||
LOGGER.info("Registration from domain: {}, expired at {}", openIdToken.getMatrixServerName(),
|
||||
new Date(openIdToken.getExpiresIn()));
|
||||
new Date(System.currentTimeMillis() + openIdToken.getExpiresIn()));
|
||||
}
|
||||
|
||||
String token = accountManager.register(openIdToken);
|
||||
|
||||
@@ -31,6 +31,8 @@ import io.kamax.mxisd.http.undertow.handler.ApiHandler;
|
||||
import io.kamax.mxisd.http.undertow.handler.identity.share.LookupHandler;
|
||||
import io.kamax.mxisd.lookup.BulkLookupRequest;
|
||||
import io.kamax.mxisd.lookup.HashLookupRequest;
|
||||
import io.kamax.mxisd.lookup.SingleLookupReply;
|
||||
import io.kamax.mxisd.lookup.SingleLookupRequest;
|
||||
import io.kamax.mxisd.lookup.ThreePidMapping;
|
||||
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
|
||||
import io.undertow.server.HttpServerExchange;
|
||||
@@ -40,6 +42,7 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public class HashLookupHandler extends LookupHandler implements ApiHandler {
|
||||
|
||||
@@ -89,6 +92,18 @@ public class HashLookupHandler extends LookupHandler implements ApiHandler {
|
||||
throw new InvalidParamException();
|
||||
}
|
||||
|
||||
ClientHashLookupAnswer answer = null;
|
||||
if (input.getAddresses() != null && input.getAddresses().size() > 0) {
|
||||
if (input.getAddresses().size() == 1) {
|
||||
answer = noneSingleLookup(request, input);
|
||||
} else {
|
||||
answer = noneBulkLookup(request, input);
|
||||
}
|
||||
}
|
||||
respondJson(exchange, answer != null ? answer : new ClientHashLookupAnswer());
|
||||
}
|
||||
|
||||
private ClientHashLookupAnswer noneBulkLookup(HashLookupRequest request, ClientHashLookupRequest input) throws Exception {
|
||||
BulkLookupRequest bulkLookupRequest = new BulkLookupRequest();
|
||||
List<ThreePidMapping> mappings = new ArrayList<>();
|
||||
for (String address : input.getAddresses()) {
|
||||
@@ -107,7 +122,26 @@ public class HashLookupHandler extends LookupHandler implements ApiHandler {
|
||||
}
|
||||
log.info("Finished bulk lookup request from {}", request.getRequester());
|
||||
|
||||
respondJson(exchange, answer);
|
||||
return answer;
|
||||
}
|
||||
|
||||
private ClientHashLookupAnswer noneSingleLookup(HashLookupRequest request, ClientHashLookupRequest input) {
|
||||
SingleLookupRequest singleLookupRequest = new SingleLookupRequest();
|
||||
String address = input.getAddresses().get(0);
|
||||
String[] parts = address.split(" ");
|
||||
singleLookupRequest.setThreePid(parts[0]);
|
||||
singleLookupRequest.setType(parts[1]);
|
||||
|
||||
ClientHashLookupAnswer answer = new ClientHashLookupAnswer();
|
||||
|
||||
Optional<SingleLookupReply> singleLookupReply = strategy.find(singleLookupRequest);
|
||||
if (singleLookupReply.isPresent()) {
|
||||
SingleLookupReply reply = singleLookupReply.get();
|
||||
answer.getMappings().put(address, reply.getMxid().toString());
|
||||
}
|
||||
log.info("Finished single lookup request from {}", request.getRequester());
|
||||
|
||||
return answer;
|
||||
}
|
||||
|
||||
private void sha256Algorithm(HttpServerExchange exchange, HashLookupRequest request, ClientHashLookupRequest input) {
|
||||
|
||||
@@ -77,7 +77,8 @@ public class HomeserverVerifier implements HostnameVerifier {
|
||||
|
||||
private boolean match(String altSubjectName) {
|
||||
if (altSubjectName.startsWith("*.")) {
|
||||
return altSubjectName.toLowerCase().endsWith(matrixHostname.toLowerCase());
|
||||
String subjectNameWithoutMask = altSubjectName.substring(1); // remove wildcard
|
||||
return matrixHostname.toLowerCase().endsWith(subjectNameWithoutMask.toLowerCase());
|
||||
} else {
|
||||
return matrixHostname.equalsIgnoreCase(altSubjectName);
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import io.kamax.mxisd.storage.IStorage;
|
||||
import io.kamax.mxisd.storage.dao.IThreePidSessionDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.ASTransactionDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AccountDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.ChangelogDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.HashDao;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.HistoricalThreePidInviteIO;
|
||||
import io.kamax.mxisd.storage.ormlite.dao.AcceptedDao;
|
||||
@@ -52,6 +53,7 @@ import java.sql.SQLException;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
@@ -73,13 +75,18 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
|
||||
}
|
||||
|
||||
public static class Migrations {
|
||||
public static final String FIX_ACCEPTED_DAO = "2019_12_09__2254__fix_accepted_dao";
|
||||
}
|
||||
|
||||
private Dao<ThreePidInviteIO, String> invDao;
|
||||
private Dao<HistoricalThreePidInviteIO, String> expInvDao;
|
||||
private Dao<ThreePidSessionDao, String> sessionDao;
|
||||
private Dao<ASTransactionDao, String> asTxnDao;
|
||||
private Dao<AccountDao, String> accountDao;
|
||||
private Dao<AcceptedDao, String> acceptedDao;
|
||||
private Dao<AcceptedDao, Long> acceptedDao;
|
||||
private Dao<HashDao, String> hashDao;
|
||||
private Dao<ChangelogDao, String> changelogDao;
|
||||
|
||||
public OrmLiteSqlStorage(MxisdConfig cfg) {
|
||||
this(cfg.getStorage().getBackend(), cfg.getStorage().getProvider().getSqlite().getDatabase());
|
||||
@@ -96,6 +103,7 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
|
||||
withCatcher(() -> {
|
||||
ConnectionSource connPool = new JdbcConnectionSource("jdbc:" + backend + ":" + path);
|
||||
changelogDao = createDaoAndTable(connPool, ChangelogDao.class);
|
||||
invDao = createDaoAndTable(connPool, ThreePidInviteIO.class);
|
||||
expInvDao = createDaoAndTable(connPool, HistoricalThreePidInviteIO.class);
|
||||
sessionDao = createDaoAndTable(connPool, ThreePidSessionDao.class);
|
||||
@@ -103,9 +111,23 @@ public class OrmLiteSqlStorage implements IStorage {
|
||||
accountDao = createDaoAndTable(connPool, AccountDao.class);
|
||||
acceptedDao = createDaoAndTable(connPool, AcceptedDao.class);
|
||||
hashDao = createDaoAndTable(connPool, HashDao.class);
|
||||
runMigration(connPool);
|
||||
});
|
||||
}
|
||||
|
||||
private void runMigration(ConnectionSource connPol) throws SQLException {
|
||||
ChangelogDao fixAcceptedDao = changelogDao.queryForId(Migrations.FIX_ACCEPTED_DAO);
|
||||
if (fixAcceptedDao == null) {
|
||||
fixAcceptedDao(connPol);
|
||||
changelogDao.create(new ChangelogDao(Migrations.FIX_ACCEPTED_DAO, new Date(), "Recreate the accepted table."));
|
||||
}
|
||||
}
|
||||
|
||||
private void fixAcceptedDao(ConnectionSource connPool) throws SQLException {
|
||||
TableUtils.dropTable(acceptedDao, true);
|
||||
TableUtils.createTableIfNotExists(connPool, AcceptedDao.class);
|
||||
}
|
||||
|
||||
private <V, K> Dao<V, K> createDaoAndTable(ConnectionSource connPool, Class<V> c) throws SQLException {
|
||||
Dao<V, K> dao = DaoManager.createDao(connPool, c);
|
||||
TableUtils.createTableIfNotExists(connPool, c);
|
||||
|
||||
@@ -26,7 +26,7 @@ import com.j256.ormlite.table.DatabaseTable;
|
||||
@DatabaseTable(tableName = "accepted")
|
||||
public class AcceptedDao {
|
||||
|
||||
@DatabaseField(canBeNull = false, id = true)
|
||||
@DatabaseField(canBeNull = false)
|
||||
private String url;
|
||||
|
||||
@DatabaseField(canBeNull = false)
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
package io.kamax.mxisd.storage.ormlite.dao;
|
||||
|
||||
import com.j256.ormlite.field.DatabaseField;
|
||||
import com.j256.ormlite.table.DatabaseTable;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
@DatabaseTable(tableName = "changelog")
|
||||
public class ChangelogDao {
|
||||
|
||||
@DatabaseField(id = true)
|
||||
private String id;
|
||||
|
||||
@DatabaseField
|
||||
private Date createdAt;
|
||||
|
||||
@DatabaseField
|
||||
private String comment;
|
||||
|
||||
public ChangelogDao() {
|
||||
}
|
||||
|
||||
public ChangelogDao(String id, Date createdAt, String comment) {
|
||||
this.id = id;
|
||||
this.createdAt = createdAt;
|
||||
this.comment = comment;
|
||||
}
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public Date getCreatedAt() {
|
||||
return createdAt;
|
||||
}
|
||||
|
||||
public void setCreatedAt(Date createdAt) {
|
||||
this.createdAt = createdAt;
|
||||
}
|
||||
|
||||
public String getComment() {
|
||||
return comment;
|
||||
}
|
||||
|
||||
public void setComment(String comment) {
|
||||
this.comment = comment;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user