diff --git a/src/main/java/io/kamax/mxisd/HttpMxisd.java b/src/main/java/io/kamax/mxisd/HttpMxisd.java index a4d236a..6197815 100644 --- a/src/main/java/io/kamax/mxisd/HttpMxisd.java +++ b/src/main/java/io/kamax/mxisd/HttpMxisd.java @@ -53,6 +53,7 @@ import io.kamax.mxisd.http.undertow.handler.identity.share.SessionValidationPost import io.kamax.mxisd.http.undertow.handler.identity.share.SignEd25519Handler; import io.kamax.mxisd.http.undertow.handler.identity.share.StoreInviteHandler; import io.kamax.mxisd.http.undertow.handler.identity.v1.*; +import io.kamax.mxisd.http.undertow.handler.identity.v2.HashDetailsHandler; import io.kamax.mxisd.http.undertow.handler.invite.v1.RoomInviteHandler; import io.kamax.mxisd.http.undertow.handler.profile.v1.InternalProfileHandler; import io.kamax.mxisd.http.undertow.handler.profile.v1.ProfileHandler; @@ -147,6 +148,7 @@ public class HttpMxisd { keyEndpoints(handler); identityEndpoints(handler); termsEndpoints(handler); + hashEndpoints(handler); httpSrv = Undertow.builder().addHttpListener(m.getConfig().getServer().getPort(), "0.0.0.0").setHandler(handler).build(); httpSrv.start(); @@ -196,6 +198,10 @@ public class HttpMxisd { .post(AcceptTermsHandler.PATH, AuthorizationHandler.around(m.getAccMgr(), sane(new AcceptTermsHandler(m.getAccMgr())))); } + private void hashEndpoints(RoutingHandler routingHandler) { + routingHandler.get(HashDetailsHandler.PATH, AuthorizationHandler.around(m.getAccMgr(), sane(new HashDetailsHandler(m.getHashManager())))); + } + private void addEndpoints(RoutingHandler routingHandler, HttpString method, boolean useAuthorization, ApiHandler... handlers) { for (ApiHandler handler : handlers) { attachHandler(routingHandler, method, handler, useAuthorization, sane(handler)); diff --git a/src/main/java/io/kamax/mxisd/Mxisd.java b/src/main/java/io/kamax/mxisd/Mxisd.java index 730b787..fa09208 100644 --- a/src/main/java/io/kamax/mxisd/Mxisd.java +++ b/src/main/java/io/kamax/mxisd/Mxisd.java @@ -35,6 +35,7 @@ import io.kamax.mxisd.directory.DirectoryManager; import io.kamax.mxisd.directory.DirectoryProviders; import io.kamax.mxisd.dns.ClientDnsOverwrite; import io.kamax.mxisd.dns.FederationDnsOverwrite; +import io.kamax.mxisd.hash.HashManager; import io.kamax.mxisd.invitation.InvitationManager; import io.kamax.mxisd.lookup.ThreePidProviders; import io.kamax.mxisd.lookup.fetcher.IRemoteIdentityServerFetcher; @@ -87,6 +88,7 @@ public class Mxisd { private NotificationManager notifMgr; private RegistrationManager regMgr; private AccountManager accMgr; + private HashManager hashManager; // HS-specific classes private Synapse synapse; @@ -127,6 +129,9 @@ public class Mxisd { regMgr = new RegistrationManager(cfg.getRegister(), httpClient, clientDns, invMgr); asHander = new AppSvcManager(this); accMgr = new AccountManager(store, resolver, getHttpClient(), cfg.getAccountConfig(), cfg.getMatrix()); + + hashManager = new HashManager(); + hashManager.init(cfg.getHashing(), ThreePidProviders.get()); } public MxisdConfig getConfig() { @@ -201,6 +206,10 @@ public class Mxisd { return accMgr; } + public HashManager getHashManager() { + return hashManager; + } + public void start() { build(); } diff --git a/src/main/java/io/kamax/mxisd/config/HashingConfig.java b/src/main/java/io/kamax/mxisd/config/HashingConfig.java new file mode 100644 index 0000000..ae1882c --- /dev/null +++ b/src/main/java/io/kamax/mxisd/config/HashingConfig.java @@ -0,0 +1,65 @@ +package io.kamax.mxisd.config; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HashingConfig { + + private static final Logger LOGGER = LoggerFactory.getLogger(HashingConfig.class); + + private boolean enabled = false; + private int pepperLength = 10; + private RotationPolicyEnum rotationPolicy; + private HashStorageEnum hashStorageType; + + public void build() { + if (isEnabled()) { + LOGGER.info("--- Hash configuration ---"); + LOGGER.info(" Pepper length: {}", getPepperLength()); + LOGGER.info(" Rotation policy: {}", getRotationPolicy()); + LOGGER.info(" Hash storage type: {}", getHashStorageType()); + } else { + LOGGER.info("Hash configuration disabled, used only `none` pepper."); + } + } + + public enum RotationPolicyEnum { + PER_REQUESTS + } + + public enum HashStorageEnum { + IN_MEMORY + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public int getPepperLength() { + return pepperLength; + } + + public void setPepperLength(int pepperLength) { + this.pepperLength = pepperLength; + } + + public RotationPolicyEnum getRotationPolicy() { + return rotationPolicy; + } + + public void setRotationPolicy(RotationPolicyEnum rotationPolicy) { + this.rotationPolicy = rotationPolicy; + } + + public HashStorageEnum getHashStorageType() { + return hashStorageType; + } + + public void setHashStorageType(HashStorageEnum hashStorageType) { + this.hashStorageType = hashStorageType; + } +} diff --git a/src/main/java/io/kamax/mxisd/config/MxisdConfig.java b/src/main/java/io/kamax/mxisd/config/MxisdConfig.java index 5baeec8..f0d51ed 100644 --- a/src/main/java/io/kamax/mxisd/config/MxisdConfig.java +++ b/src/main/java/io/kamax/mxisd/config/MxisdConfig.java @@ -116,6 +116,7 @@ public class MxisdConfig { private ViewConfig view = new ViewConfig(); private WordpressConfig wordpress = new WordpressConfig(); private PolicyConfig policy = new PolicyConfig(); + private HashingConfig hashing = new HashingConfig(); public AppServiceConfig getAppsvc() { return appsvc; @@ -333,6 +334,14 @@ public class MxisdConfig { this.policy = policy; } + public HashingConfig getHashing() { + return hashing; + } + + public void setHashing(HashingConfig hashing) { + this.hashing = hashing; + } + public MxisdConfig inMemory() { getKey().setPath(":memory:"); getStorage().getProvider().getSqlite().setDatabase(":memory:"); @@ -372,6 +381,7 @@ public class MxisdConfig { getView().build(); getWordpress().build(); getPolicy().build(); + getHashing().build(); return this; } diff --git a/src/main/java/io/kamax/mxisd/config/PolicyConfig.java b/src/main/java/io/kamax/mxisd/config/PolicyConfig.java index a0bba22..aab17c6 100644 --- a/src/main/java/io/kamax/mxisd/config/PolicyConfig.java +++ b/src/main/java/io/kamax/mxisd/config/PolicyConfig.java @@ -15,8 +15,6 @@ public class PolicyConfig { private Map policies = new HashMap<>(); - private AcceptingPolicy acceptingPolicy = AcceptingPolicy.ANY; - public static class TermObject { private String name; @@ -87,14 +85,6 @@ public class PolicyConfig { this.policies = policies; } - public AcceptingPolicy getAcceptingPolicy() { - return acceptingPolicy; - } - - public void setAcceptingPolicy(AcceptingPolicy acceptingPolicy) { - this.acceptingPolicy = acceptingPolicy; - } - public void build() { LOGGER.info("--- Policy Config ---"); if (getPolicies().isEmpty()) { diff --git a/src/main/java/io/kamax/mxisd/hash/HashEngine.java b/src/main/java/io/kamax/mxisd/hash/HashEngine.java new file mode 100644 index 0000000..741f46a --- /dev/null +++ b/src/main/java/io/kamax/mxisd/hash/HashEngine.java @@ -0,0 +1,50 @@ +package io.kamax.mxisd.hash; + +import io.kamax.matrix.codec.MxSha256; +import io.kamax.mxisd.config.HashingConfig; +import io.kamax.mxisd.lookup.ThreePidMapping; +import io.kamax.mxisd.lookup.provider.IThreePidProvider; +import org.apache.commons.lang3.RandomStringUtils; + +import java.util.List; + +public class HashEngine { + + private final List providers; + private final HashStorage hashStorage; + private final MxSha256 sha256 = new MxSha256(); + private final HashingConfig config; + private String pepper; + + public HashEngine(List providers, HashStorage hashStorage, HashingConfig config) { + this.providers = providers; + this.hashStorage = hashStorage; + this.config = config; + } + + public void updateHashes() { + synchronized (hashStorage) { + this.pepper = newPepper(); + hashStorage.clear(); + for (IThreePidProvider provider : providers) { + for (ThreePidMapping pidMapping : provider.populateHashes()) { + hashStorage.add(pidMapping, hash(pidMapping)); + } + } + } + } + + public String getPepper() { + synchronized (hashStorage) { + return pepper; + } + } + + protected String hash(ThreePidMapping pidMapping) { + return sha256.hash(pidMapping.getMedium() + " " + pidMapping.getValue() + " " + getPepper()); + } + + protected String newPepper() { + return RandomStringUtils.random(config.getPepperLength()); + } +} diff --git a/src/main/java/io/kamax/mxisd/hash/HashManager.java b/src/main/java/io/kamax/mxisd/hash/HashManager.java new file mode 100644 index 0000000..8ea57c2 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/hash/HashManager.java @@ -0,0 +1,62 @@ +package io.kamax.mxisd.hash; + +import io.kamax.mxisd.config.HashingConfig; +import io.kamax.mxisd.lookup.provider.IThreePidProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +public class HashManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(HashManager.class); + + private HashEngine hashEngine; + private HashRotationStrategy rotationStrategy; + private HashStorage hashStorage; + private HashingConfig config; + + public void init(HashingConfig config, List providers) { + this.config = config; + initStorage(); + hashEngine = new HashEngine(providers, getHashStorage(), config); + initRotationStrategy(); + } + + private void initStorage() { + switch (config.getHashStorageType()) { + case IN_MEMORY: + this.hashStorage = new InMemoryHashStorage(); + break; + default: + throw new IllegalArgumentException("Unknown storage type: " + config.getHashStorageType()); + } + } + + private void initRotationStrategy() { + switch (config.getRotationPolicy()) { + case PER_REQUESTS: + this.rotationStrategy = new RotationPerRequests(); + break; + default: + throw new IllegalArgumentException("Unknown rotation type: " + config.getHashStorageType()); + } + this.rotationStrategy.register(getHashEngine()); + } + + public HashEngine getHashEngine() { + return hashEngine; + } + + public HashRotationStrategy getRotationStrategy() { + return rotationStrategy; + } + + public HashStorage getHashStorage() { + return hashStorage; + } + + public HashingConfig getConfig() { + return config; + } +} diff --git a/src/main/java/io/kamax/mxisd/hash/HashRotationStrategy.java b/src/main/java/io/kamax/mxisd/hash/HashRotationStrategy.java new file mode 100644 index 0000000..d1e3e5a --- /dev/null +++ b/src/main/java/io/kamax/mxisd/hash/HashRotationStrategy.java @@ -0,0 +1,14 @@ +package io.kamax.mxisd.hash; + +public interface HashRotationStrategy { + + void register(HashEngine hashEngine); + + HashEngine getHashEngine(); + + void newRequest(); + + default void trigger() { + getHashEngine().updateHashes(); + } +} diff --git a/src/main/java/io/kamax/mxisd/hash/HashStorage.java b/src/main/java/io/kamax/mxisd/hash/HashStorage.java new file mode 100644 index 0000000..e75d703 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/hash/HashStorage.java @@ -0,0 +1,13 @@ +package io.kamax.mxisd.hash; + +import io.kamax.matrix.ThreePid; +import io.kamax.mxisd.lookup.ThreePidMapping; + +public interface HashStorage { + + Iterable find(Iterable hashes); + + void add(ThreePidMapping pidMapping, String hash); + + void clear(); +} diff --git a/src/main/java/io/kamax/mxisd/hash/InMemoryHashStorage.java b/src/main/java/io/kamax/mxisd/hash/InMemoryHashStorage.java new file mode 100644 index 0000000..c56d272 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/hash/InMemoryHashStorage.java @@ -0,0 +1,35 @@ +package io.kamax.mxisd.hash; + +import io.kamax.mxisd.lookup.ThreePidMapping; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class InMemoryHashStorage implements HashStorage { + + private final Map mapping = new ConcurrentHashMap<>(); + + @Override + public Iterable find(Iterable hashes) { + List result = new ArrayList<>(); + for (String hash : hashes) { + ThreePidMapping pidMapping = mapping.get(hash); + if (pidMapping != null) { + result.add(pidMapping); + } + } + return result; + } + + @Override + public void add(ThreePidMapping pidMapping, String hash) { + mapping.put(hash, pidMapping); + } + + @Override + public void clear() { + mapping.clear(); + } +} diff --git a/src/main/java/io/kamax/mxisd/hash/RotationPerRequests.java b/src/main/java/io/kamax/mxisd/hash/RotationPerRequests.java new file mode 100644 index 0000000..5452773 --- /dev/null +++ b/src/main/java/io/kamax/mxisd/hash/RotationPerRequests.java @@ -0,0 +1,28 @@ +package io.kamax.mxisd.hash; + +import java.util.concurrent.atomic.AtomicInteger; + +public class RotationPerRequests implements HashRotationStrategy { + + private HashEngine hashEngine; + private final AtomicInteger counter = new AtomicInteger(0); + + @Override + public void register(HashEngine hashEngine) { + this.hashEngine = hashEngine; + } + + @Override + public HashEngine getHashEngine() { + return hashEngine; + } + + @Override + public synchronized void newRequest() { + int newValue = counter.incrementAndGet(); + if (newValue >= 10) { + counter.set(0); + trigger(); + } + } +} diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v2/HashDetailsHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v2/HashDetailsHandler.java new file mode 100644 index 0000000..9813a9a --- /dev/null +++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v2/HashDetailsHandler.java @@ -0,0 +1,42 @@ +package io.kamax.mxisd.http.undertow.handler.identity.v2; + +import com.google.gson.JsonArray; +import com.google.gson.JsonObject; +import io.kamax.mxisd.hash.HashManager; +import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler; +import io.undertow.server.HttpServerExchange; + +public class HashDetailsHandler extends BasicHttpHandler { + + public static final String PATH = "/_matrix/identity/v2/hash_details"; + + private final HashManager hashManager; + private volatile JsonObject response = null; + + public HashDetailsHandler(HashManager hashManager) { + this.hashManager = hashManager; + } + + @Override + public void handleRequest(HttpServerExchange exchange) throws Exception { + respond(exchange, getResponse()); + } + + private JsonObject getResponse() { + if (response == null) { + synchronized (this) { + if (response == null) { + response = new JsonObject(); + response.addProperty("lookup_pepper", hashManager.getHashEngine().getPepper()); + JsonArray algorithms = new JsonArray(); + algorithms.add("none"); + if (hashManager.getConfig().isEnabled()) { + algorithms.add("sha256"); + } + response.add("algorithms", algorithms); + } + } + } + return response; + } +} diff --git a/src/main/java/io/kamax/mxisd/lookup/provider/IThreePidProvider.java b/src/main/java/io/kamax/mxisd/lookup/provider/IThreePidProvider.java index 8ec8760..0f802f6 100644 --- a/src/main/java/io/kamax/mxisd/lookup/provider/IThreePidProvider.java +++ b/src/main/java/io/kamax/mxisd/lookup/provider/IThreePidProvider.java @@ -24,6 +24,7 @@ import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.SingleLookupRequest; import io.kamax.mxisd.lookup.ThreePidMapping; +import java.util.Collections; import java.util.List; import java.util.Optional; @@ -40,4 +41,7 @@ public interface IThreePidProvider { List populate(List mappings); + default Iterable populateHashes() { + return Collections.emptyList(); + } }