diff --git a/docs/threepids/notification/sendgrid-handler.md b/docs/threepids/notification/sendgrid-handler.md index db8b26d..c274440 100644 --- a/docs/threepids/notification/sendgrid-handler.md +++ b/docs/threepids/notification/sendgrid-handler.md @@ -31,8 +31,8 @@ notification: text: html: unbind: - fraudulent: - subject: + notification: + subject: body: text: html: diff --git a/docs/threepids/notification/template-generator.md b/docs/threepids/notification/template-generator.md index fffb040..64b7724 100644 --- a/docs/threepids/notification/template-generator.md +++ b/docs/threepids/notification/template-generator.md @@ -9,7 +9,7 @@ provide your own custom templates. Templates for the following events/actions are available: - [3PID invite](../../features/identity.md) - [3PID session: validation](../session/session.md) -- [3PID session: fraudulent unbind](https://github.com/kamax-matrix/ma1sd/wiki/ma1sd-and-your-privacy#improving-your-privacy-one-commit-at-the-time) +- [3PID session: unbind](https://github.com/kamax-matrix/ma1sd/wiki/ma1sd-and-your-privacy#improving-your-privacy-one-commit-at-the-time) - [Matrix ID invite](../../features/experimental/application-service.md#email-notification-about-room-invites-by-matrix-ids) ## Placeholders @@ -71,7 +71,7 @@ under the namespace `threepid.medium..generators.template`. Under such namespace, the following keys are available: - `invite`: Path to the 3PID invite notification template - `session.validation`: Path to the 3PID session validation notification template -- `session.unbind.fraudulent`: Path to the 3PID session fraudulent unbind notification template +- `session.unbind`: Path to the 3PID session unbind notification template - `generic.matrixId`: Path to the Matrix ID invite notification template - `placeholder`: Map of key/values to set static values for some placeholders. @@ -104,7 +104,7 @@ threepid: session: validation: '/path/to/validate-template.eml' unbind: - fraudulent: '/path/to/unbind-fraudulent-template.eml' + notification: '/path/to/unbind-notification-template.eml' generic: matrixId: '/path/to/mxid-invite-template.eml' placeholder: diff --git a/docs/threepids/session/session.md b/docs/threepids/session/session.md index c8696c9..022927c 100644 --- a/docs/threepids/session/session.md +++ b/docs/threepids/session/session.md @@ -103,8 +103,8 @@ session: validation: enabled: true unbind: - fraudulent: - sendWarning: true + notification: + enabled: true # DO NOT COPY/PASTE AS-IS IN YOUR CONFIGURATION # CONFIGURATION EXAMPLE @@ -115,11 +115,7 @@ are allowed to do in terms of 3PID sessions. The policy has a global on/off swit --- -`unbind.fraudulent` controls warning notifications if an illegal/fraudulent 3PID removal is attempted on the Identity server. -This is directly related to synapse disregard for privacy and new GDPR laws in Europe in an attempt to inform users about -potential privacy leaks. - -For more information, see the corresponding [synapse issue](https://github.com/matrix-org/synapse/issues/4540). +`unbind` controls warning notifications for 3PID removal. ### Web views Once a user click on a validation link, it is taken to the Identity Server validation page where the token is submitted. diff --git a/src/main/java/io/kamax/mxisd/Mxisd.java b/src/main/java/io/kamax/mxisd/Mxisd.java index d62e628..2ca454e 100644 --- a/src/main/java/io/kamax/mxisd/Mxisd.java +++ b/src/main/java/io/kamax/mxisd/Mxisd.java @@ -118,7 +118,7 @@ public class Mxisd { idStrategy = new RecursivePriorityLookupStrategy(cfg.getLookup(), ThreePidProviders.get(), bridgeFetcher); pMgr = new ProfileManager(ProfileProviders.get(), clientDns, httpClient); notifMgr = new NotificationManager(cfg.getNotification(), NotificationHandlers.get()); - sessMgr = new SessionManager(cfg.getSession(), cfg.getMatrix(), store, notifMgr); + sessMgr = new SessionManager(cfg.getSession(), cfg.getMatrix(), store, notifMgr, resolver, httpClient, signMgr); invMgr = new InvitationManager(cfg, store, idStrategy, keyMgr, signMgr, resolver, notifMgr, pMgr); authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient); dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get()); diff --git a/src/main/java/io/kamax/mxisd/config/MatrixConfig.java b/src/main/java/io/kamax/mxisd/config/MatrixConfig.java index c66751a..08edaf3 100644 --- a/src/main/java/io/kamax/mxisd/config/MatrixConfig.java +++ b/src/main/java/io/kamax/mxisd/config/MatrixConfig.java @@ -62,6 +62,7 @@ public class MatrixConfig { private transient final Logger log = LoggerFactory.getLogger(MatrixConfig.class); private String domain; + private String trustedIdServer; private Identity identity = new Identity(); public String getDomain() { @@ -72,6 +73,14 @@ public class MatrixConfig { this.domain = domain; } + public String getTrustedIdServer() { + return trustedIdServer; + } + + public void setTrustedIdServer(String trustedIdServer) { + this.trustedIdServer = trustedIdServer; + } + public Identity getIdentity() { return identity; } diff --git a/src/main/java/io/kamax/mxisd/config/SessionConfig.java b/src/main/java/io/kamax/mxisd/config/SessionConfig.java index ae9a0f7..674f67c 100644 --- a/src/main/java/io/kamax/mxisd/config/SessionConfig.java +++ b/src/main/java/io/kamax/mxisd/config/SessionConfig.java @@ -46,30 +46,15 @@ public class SessionConfig { public static class PolicyUnbind { - public static class PolicyUnbindFraudulent { + private boolean enabled = true; - private boolean sendWarning = true; - - public boolean getSendWarning() { - return sendWarning; - } - - public void setSendWarning(boolean sendWarning) { - this.sendWarning = sendWarning; - } + public boolean getEnabled() { + return enabled; } - - private PolicyUnbindFraudulent fraudulent = new PolicyUnbindFraudulent(); - - public PolicyUnbindFraudulent getFraudulent() { - return fraudulent; + public void setEnabled(boolean enabled) { + this.enabled = enabled; } - - public void setFraudulent(PolicyUnbindFraudulent fraudulent) { - this.fraudulent = fraudulent; - } - } public Policy() { diff --git a/src/main/java/io/kamax/mxisd/config/threepid/connector/EmailSendGridConfig.java b/src/main/java/io/kamax/mxisd/config/threepid/connector/EmailSendGridConfig.java index c9aeca3..ab97cf1 100644 --- a/src/main/java/io/kamax/mxisd/config/threepid/connector/EmailSendGridConfig.java +++ b/src/main/java/io/kamax/mxisd/config/threepid/connector/EmailSendGridConfig.java @@ -115,24 +115,10 @@ public class EmailSendGridConfig { public static class Templates { - public static class TemplateSessionUnbind { - - private EmailTemplate fraudulent = new EmailTemplate(); - - public EmailTemplate getFraudulent() { - return fraudulent; - } - - public void setFraudulent(EmailTemplate fraudulent) { - this.fraudulent = fraudulent; - } - - } - public static class TemplateSession { private EmailTemplate validation = new EmailTemplate(); - private TemplateSessionUnbind unbind = new TemplateSessionUnbind(); + private EmailTemplate unbind = new EmailTemplate(); public EmailTemplate getValidation() { return validation; @@ -142,11 +128,11 @@ public class EmailSendGridConfig { this.validation = validation; } - public TemplateSessionUnbind getUnbind() { + public EmailTemplate getUnbind() { return unbind; } - public void setUnbind(TemplateSessionUnbind unbind) { + public void setUnbind(EmailTemplate unbind) { this.unbind = unbind; } diff --git a/src/main/java/io/kamax/mxisd/config/threepid/medium/EmailTemplateConfig.java b/src/main/java/io/kamax/mxisd/config/threepid/medium/EmailTemplateConfig.java index 528ebed..2bc6977 100644 --- a/src/main/java/io/kamax/mxisd/config/threepid/medium/EmailTemplateConfig.java +++ b/src/main/java/io/kamax/mxisd/config/threepid/medium/EmailTemplateConfig.java @@ -31,7 +31,8 @@ public class EmailTemplateConfig extends GenericTemplateConfig { setInvite("classpath:/threepids/email/invite-template.eml"); getGeneric().put("matrixId", "classpath:/threepids/email/mxid-template.eml"); getSession().setValidation("classpath:/threepids/email/validate-template.eml"); - getSession().getUnbind().setFraudulent("classpath:/threepids/email/unbind-fraudulent.eml"); + getSession().getUnbind().setValidation("classpath:/threepids/email/unbind-template.eml"); + getSession().getUnbind().setNotification("classpath:/threepids/email/unbind-notification.eml"); } public EmailTemplateConfig build() { @@ -40,7 +41,8 @@ public class EmailTemplateConfig extends GenericTemplateConfig { log.info("Session:"); log.info(" Validation: {}", getSession().getValidation()); log.info(" Unbind:"); - log.info(" Fraudulent: {}", getSession().getUnbind().getFraudulent()); + log.info(" Validation: {}", getSession().getUnbind().getValidation()); + log.info(" Notification: {}", getSession().getUnbind().getNotification()); return this; } diff --git a/src/main/java/io/kamax/mxisd/config/threepid/medium/GenericTemplateConfig.java b/src/main/java/io/kamax/mxisd/config/threepid/medium/GenericTemplateConfig.java index 1c74d69..a612d08 100644 --- a/src/main/java/io/kamax/mxisd/config/threepid/medium/GenericTemplateConfig.java +++ b/src/main/java/io/kamax/mxisd/config/threepid/medium/GenericTemplateConfig.java @@ -41,16 +41,25 @@ public class GenericTemplateConfig { public static class SessionUnbind { - private String fraudulent; + private String validation; - public String getFraudulent() { - return fraudulent; + private String notification; + + public String getValidation() { + return validation; } - public void setFraudulent(String fraudulent) { - this.fraudulent = fraudulent; + public void setValidation(String validation) { + this.validation = validation; } + public String getNotification() { + return notification; + } + + public void setNotification(String notification) { + this.notification = notification; + } } private String validation; diff --git a/src/main/java/io/kamax/mxisd/config/threepid/medium/PhoneSmsTemplateConfig.java b/src/main/java/io/kamax/mxisd/config/threepid/medium/PhoneSmsTemplateConfig.java index 20b3aea..0a09fa3 100644 --- a/src/main/java/io/kamax/mxisd/config/threepid/medium/PhoneSmsTemplateConfig.java +++ b/src/main/java/io/kamax/mxisd/config/threepid/medium/PhoneSmsTemplateConfig.java @@ -30,7 +30,8 @@ public class PhoneSmsTemplateConfig extends GenericTemplateConfig { public PhoneSmsTemplateConfig() { setInvite("classpath:/threepids/sms/invite-template.txt"); getSession().setValidation("classpath:/threepids/sms/validate-template.txt"); - getSession().getUnbind().setFraudulent("classpath:/threepids/sms/unbind-fraudulent.txt"); + getSession().getUnbind().setValidation("classpath:/threepids/sms/unbind-validation.txt"); + getSession().getUnbind().setNotification("classpath:/threepids/sms/unbind-notification.txt"); } public PhoneSmsTemplateConfig build() { @@ -39,7 +40,8 @@ public class PhoneSmsTemplateConfig extends GenericTemplateConfig { log.info("Session:"); log.info(" Validation: {}", getSession().getValidation()); log.info(" Unbind:"); - log.info(" Fraudulent: {}", getSession().getUnbind().getFraudulent()); + log.info(" Validation: {}", getSession().getUnbind().getValidation()); + log.info(" Notification: {}", getSession().getUnbind().getNotification()); return this; } diff --git a/src/main/java/io/kamax/mxisd/crypto/CryptoFactory.java b/src/main/java/io/kamax/mxisd/crypto/CryptoFactory.java index 09712d3..b842cd9 100644 --- a/src/main/java/io/kamax/mxisd/crypto/CryptoFactory.java +++ b/src/main/java/io/kamax/mxisd/crypto/CryptoFactory.java @@ -58,5 +58,4 @@ public class CryptoFactory { public static SignatureManager getSignatureManager(MxisdConfig cfg, Ed25519KeyManager keyMgr) { return new Ed25519SignatureManager(cfg, keyMgr); } - } diff --git a/src/main/java/io/kamax/mxisd/crypto/SignatureManager.java b/src/main/java/io/kamax/mxisd/crypto/SignatureManager.java index 531047b..9f60623 100644 --- a/src/main/java/io/kamax/mxisd/crypto/SignatureManager.java +++ b/src/main/java/io/kamax/mxisd/crypto/SignatureManager.java @@ -26,6 +26,7 @@ import io.kamax.matrix.event.EventKey; import io.kamax.matrix.json.MatrixJson; import java.nio.charset.StandardCharsets; +import java.security.PublicKey; import java.util.Objects; public interface SignatureManager { @@ -106,4 +107,13 @@ public interface SignatureManager { */ Signature sign(byte[] data); + /** + * Verify the data. + * + * @param publicKey public key to verify + * @param signature signature to verify + * @param data the data to verify + * @return {@code true} if signature is valid, else {@code false} + */ + boolean verify(PublicKey publicKey, String signature, byte[] data); } diff --git a/src/main/java/io/kamax/mxisd/crypto/ed25519/Ed25519SignatureManager.java b/src/main/java/io/kamax/mxisd/crypto/ed25519/Ed25519SignatureManager.java index 46e23d0..455e1ec 100644 --- a/src/main/java/io/kamax/mxisd/crypto/ed25519/Ed25519SignatureManager.java +++ b/src/main/java/io/kamax/mxisd/crypto/ed25519/Ed25519SignatureManager.java @@ -33,7 +33,9 @@ import net.i2p.crypto.eddsa.EdDSAEngine; import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; import java.security.SignatureException; +import java.util.Base64; public class Ed25519SignatureManager implements SignatureManager { @@ -92,4 +94,15 @@ public class Ed25519SignatureManager implements SignatureManager { } } + @Override + public boolean verify(PublicKey publicKey, String signature, byte[] data) { + try { + EdDSAEngine signEngine = new EdDSAEngine(MessageDigest.getInstance(keyMgr.getKeySpecs().getHashAlgorithm())); + signEngine.initVerify(publicKey); + signEngine.update(data); + return signEngine.verify(Base64.getDecoder().decode(signature)); + } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { + throw new RuntimeException(e); + } + } } diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SessionTpidUnbindHandler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SessionTpidUnbindHandler.java index bccb9ec..e7d68aa 100644 --- a/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SessionTpidUnbindHandler.java +++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SessionTpidUnbindHandler.java @@ -21,13 +21,10 @@ package io.kamax.mxisd.http.undertow.handler.identity.v1; import com.google.gson.JsonObject; -import io.kamax.mxisd.exception.BadRequestException; -import io.kamax.mxisd.exception.NotAllowedException; import io.kamax.mxisd.http.IsAPIv1; import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler; import io.kamax.mxisd.session.SessionManager; import io.undertow.server.HttpServerExchange; -import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -46,20 +43,9 @@ public class SessionTpidUnbindHandler extends BasicHttpHandler { @Override public void handleRequest(HttpServerExchange exchange) { String auth = exchange.getRequestHeaders().getFirst("Authorization"); - if (StringUtils.isNotEmpty(auth)) { - // We have a auth header to process - if (StringUtils.startsWith(auth, "X-Matrix ")) { - log.warn("A remote host attempted to unbind without proper authorization. Request was denied"); - log.warn("See https://github.com/kamax-matrix/mxisd/wiki/mxisd-and-your-privacy for more info"); - throw new NotAllowedException("3PID can only be removed via 3PID sessions, not via Homeserver signature"); - } else { - throw new BadRequestException("Illegal authorization type"); - } - } JsonObject body = parseJsonObject(exchange); - sessionMgr.unbind(body); + sessionMgr.unbind(auth, body); writeBodyAsUtf8(exchange, "{}"); } - } diff --git a/src/main/java/io/kamax/mxisd/notification/NotificationHandler.java b/src/main/java/io/kamax/mxisd/notification/NotificationHandler.java index b6c5ba2..5060002 100644 --- a/src/main/java/io/kamax/mxisd/notification/NotificationHandler.java +++ b/src/main/java/io/kamax/mxisd/notification/NotificationHandler.java @@ -37,6 +37,6 @@ public interface NotificationHandler { void sendForValidation(IThreePidSession session); - void sendForFraudulentUnbind(ThreePid tpid); + void sendForUnbind(ThreePid tpid); } diff --git a/src/main/java/io/kamax/mxisd/notification/NotificationManager.java b/src/main/java/io/kamax/mxisd/notification/NotificationManager.java index 33eea8c..e30a736 100644 --- a/src/main/java/io/kamax/mxisd/notification/NotificationManager.java +++ b/src/main/java/io/kamax/mxisd/notification/NotificationManager.java @@ -78,8 +78,8 @@ public class NotificationManager { ensureMedium(session.getThreePid().getMedium()).sendForValidation(session); } - public void sendForFraudulentUnbind(ThreePid tpid) throws NotImplementedException { - ensureMedium(tpid.getMedium()).sendForFraudulentUnbind(tpid); + public void sendForUnbind(ThreePid tpid) throws NotImplementedException { + ensureMedium(tpid.getMedium()).sendForUnbind(tpid); } } diff --git a/src/main/java/io/kamax/mxisd/session/SessionManager.java b/src/main/java/io/kamax/mxisd/session/SessionManager.java index 8953d15..50bb0db 100644 --- a/src/main/java/io/kamax/mxisd/session/SessionManager.java +++ b/src/main/java/io/kamax/mxisd/session/SessionManager.java @@ -20,33 +20,49 @@ package io.kamax.mxisd.session; +import static io.kamax.mxisd.config.SessionConfig.Policy.PolicyTemplate; + import com.google.gson.JsonObject; import io.kamax.matrix.MatrixID; import io.kamax.matrix.ThreePid; import io.kamax.matrix._MatrixID; import io.kamax.matrix.json.GsonUtil; +import io.kamax.matrix.json.MatrixJson; import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.config.SessionConfig; +import io.kamax.mxisd.crypto.SignatureManager; import io.kamax.mxisd.exception.BadRequestException; import io.kamax.mxisd.exception.NotAllowedException; +import io.kamax.mxisd.exception.RemoteHomeServerException; import io.kamax.mxisd.exception.SessionNotValidatedException; import io.kamax.mxisd.exception.SessionUnknownException; import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.SingleLookupRequest; import io.kamax.mxisd.lookup.ThreePidValidation; +import io.kamax.mxisd.matrix.HomeserverFederationResolver; import io.kamax.mxisd.notification.NotificationManager; import io.kamax.mxisd.storage.IStorage; import io.kamax.mxisd.storage.dao.IThreePidSessionDao; import io.kamax.mxisd.threepid.session.ThreePidSession; +import net.i2p.crypto.eddsa.EdDSAPublicKey; +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveSpec; +import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable; +import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.CloseableHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Calendar; import java.util.Optional; -import static io.kamax.mxisd.config.SessionConfig.Policy.PolicyTemplate; - public class SessionManager { private static final Logger log = LoggerFactory.getLogger(SessionManager.class); @@ -55,17 +71,26 @@ public class SessionManager { private MatrixConfig mxCfg; private IStorage storage; private NotificationManager notifMgr; + private HomeserverFederationResolver resolver; + private CloseableHttpClient client; + private SignatureManager signatureManager; public SessionManager( - SessionConfig cfg, - MatrixConfig mxCfg, - IStorage storage, - NotificationManager notifMgr + SessionConfig cfg, + MatrixConfig mxCfg, + IStorage storage, + NotificationManager notifMgr, + HomeserverFederationResolver resolver, + CloseableHttpClient client, + SignatureManager signatureManager ) { this.cfg = cfg; this.mxCfg = mxCfg; this.storage = storage; this.notifMgr = notifMgr; + this.resolver = resolver; + this.client = client; + this.signatureManager = signatureManager; } private ThreePidSession getSession(String sid, String secret) { @@ -98,7 +123,8 @@ public class SessionManager { ThreePidSession session = new ThreePidSession(dao.get()); log.info("We already have a session for {}: {}", tpid, session.getId()); if (session.getAttempt() < attempt) { - log.info("Received attempt {} is greater than stored attempt {}, sending validation communication", attempt, session.getAttempt()); + log.info("Received attempt {} is greater than stored attempt {}, sending validation communication", attempt, + session.getAttempt()); notifMgr.sendForValidation(session); log.info("Sent validation notification to {}", tpid); session.increaseAttempt(); @@ -166,7 +192,7 @@ public class SessionManager { } log.info("Session {}: Binding of {}:{} to Matrix ID {} is accepted", - session.getId(), session.getThreePid().getMedium(), session.getThreePid().getAddress(), mxid.getId()); + session.getId(), session.getThreePid().getMedium(), session.getThreePid().getAddress(), mxid.getId()); SingleLookupRequest request = new SingleLookupRequest(); request.setType(session.getThreePid().getMedium()); @@ -174,7 +200,7 @@ public class SessionManager { return new SingleLookupReply(request, mxid); } - public void unbind(JsonObject reqData) { + public void unbind(String auth, JsonObject reqData) { _MatrixID mxid; try { mxid = MatrixID.asAcceptable(GsonUtil.getStringOrThrow(reqData, "mxid")); @@ -186,6 +212,128 @@ public class SessionManager { String secret = GsonUtil.getStringOrNull(reqData, "client_secret"); ThreePid tpid = GsonUtil.get().fromJson(GsonUtil.getObj(reqData, "threepid"), ThreePid.class); + if (StringUtils.isNotBlank(sid) && StringUtils.isNotBlank(secret)) { + checkSession(sid, secret, tpid, mxid); + } else if (StringUtils.isNotBlank(auth)) { + checkAuthorization(auth, reqData); + } else { + throw new NotAllowedException("Unable to validate request"); + } + + // TODO make invalid all 3PID with specified medium and address. + } + + private void checkAuthorization(String auth, JsonObject reqData) { + if (!auth.startsWith("X-Matrix ")) { + throw new NotAllowedException("Wrong authorization header"); + } + + if (StringUtils.isBlank(mxCfg.getTrustedIdServer())) { + throw new NotAllowedException("Unable to verify request, missing `matrix.trustedIdServer` variable"); + } + + String[] params = auth.substring("X-Matrix ".length()).split(","); + + String origin = null; + String key = null; + String sig = null; + for (String param : params) { + String[] paramItems = param.split("="); + String paramKey = paramItems[0]; + String paramValue = paramItems[1]; + switch (paramKey) { + case "origin": + origin = removeQuotes(paramValue); + break; + case "key": + key = removeQuotes(paramValue); + break; + case "sig": + sig = removeQuotes(paramValue); + break; + default: + log.error("Unknown parameter: {}", param); + throw new BadRequestException("Authorization with unknown parameter"); + } + } + + if (StringUtils.isBlank(origin) || StringUtils.isBlank(key) || StringUtils.isBlank(sig)) { + log.error("Missing required parameters"); + throw new BadRequestException("Missing required header parameters"); + } + + JsonObject jsonObject = new JsonObject(); + jsonObject.addProperty("method", "POST"); + jsonObject.addProperty("uri", "/_matrix/identity/api/v1/3pid/unbind"); + jsonObject.addProperty("origin", origin); + jsonObject.addProperty("destination_is", mxCfg.getTrustedIdServer()); + jsonObject.add("content", reqData); + + String canonical = MatrixJson.encodeCanonical(jsonObject); + + String originUrl = resolver.resolve(origin).toString(); + + validateServerKey(key, sig, canonical, originUrl); + } + + private String removeQuotes(String origin) { + return origin.startsWith("\"") && origin.endsWith("\"") ? origin.substring(1, origin.length() - 1) : origin; + } + + private void validateServerKey(String key, String signature, String canonical, String originUrl) { + HttpGet request = new HttpGet(originUrl + "/_matrix/key/v2/server"); + log.info("Get keys from the server {}", request.getURI()); + try (CloseableHttpResponse response = client.execute(request)) { + int statusCode = response.getStatusLine().getStatusCode(); + log.info("Answer code: {}", statusCode); + if (statusCode == 200) { + 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); + } + } + + private void verifyKey(String key, String signature, String canonical, CloseableHttpResponse response) throws IOException { + final String content = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + log.info("Answer body: {}", content); + final JsonObject responseObject = GsonUtil.parseObj(content); + final long validUntilTs = GsonUtil.getLong(responseObject, "valid_until_ts"); + + final Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(validUntilTs); + if (calendar.before(Calendar.getInstance())) { + final String msg = "Key is expired"; + log.error(msg); + throw new RemoteHomeServerException(msg); + } + + final JsonObject verifyKeys = GsonUtil.getObj(responseObject, "verify_keys"); + final JsonObject keyObject = GsonUtil.getObj(verifyKeys, key); + final String publicKey = GsonUtil.getStringOrNull(keyObject, "key"); + + if (StringUtils.isBlank(publicKey)) { + throw new RemoteHomeServerException("Missing server key."); + } + + EdDSANamedCurveSpec ed25519CurveSpec = EdDSANamedCurveTable.ED_25519_CURVE_SPEC; + EdDSAPublicKeySpec publicKeySpec = new EdDSAPublicKeySpec(Base64.getDecoder().decode(publicKey), ed25519CurveSpec); + EdDSAPublicKey dsaPublicKey = new EdDSAPublicKey(publicKeySpec); + + final boolean verificationResult = signatureManager.verify(dsaPublicKey, signature, canonical.getBytes(StandardCharsets.UTF_8)); + log.info("Verification result: {}", verificationResult); + if (!verificationResult) { + throw new RemoteHomeServerException("Unable to verify request."); + } + + log.info("Request was authorized."); + } + + private void checkSession(String sid, String secret, ThreePid tpid, _MatrixID mxid) { // We ensure the session was validated ThreePidSession session = getSessionIfValidated(sid, secret); @@ -199,8 +347,6 @@ public class SessionManager { throw new NotAllowedException("Only Matrix IDs from domain " + mxCfg.getDomain() + " can be unbound"); } - log.info("Session {}: Unbinding of {}:{} to Matrix ID {} is accepted", - session.getId(), session.getThreePid().getMedium(), session.getThreePid().getAddress(), mxid.getId()); + log.info("Request was authorized."); } - } diff --git a/src/main/java/io/kamax/mxisd/threepid/generator/GenericTemplateNotificationGenerator.java b/src/main/java/io/kamax/mxisd/threepid/generator/GenericTemplateNotificationGenerator.java index e513d4d..233781f 100644 --- a/src/main/java/io/kamax/mxisd/threepid/generator/GenericTemplateNotificationGenerator.java +++ b/src/main/java/io/kamax/mxisd/threepid/generator/GenericTemplateNotificationGenerator.java @@ -79,9 +79,9 @@ public abstract class GenericTemplateNotificationGenerator extends PlaceholderNo } @Override - public String getForFraudulentUnbind(ThreePid tpid) { - log.info("Generating notification content for fraudulent unbind"); - return populateForFraudulentUndind(tpid, getTemplateContent(cfg.getSession().getUnbind().getFraudulent())); + public String getForNotificationUnbind(ThreePid tpid) { + log.info("Generating notification content for unbind"); + return populateForNotificationUndind(tpid, getTemplateContent(cfg.getSession().getUnbind().getNotification())); } } diff --git a/src/main/java/io/kamax/mxisd/threepid/generator/NotificationGenerator.java b/src/main/java/io/kamax/mxisd/threepid/generator/NotificationGenerator.java index aeb85f1..ba4d3e6 100644 --- a/src/main/java/io/kamax/mxisd/threepid/generator/NotificationGenerator.java +++ b/src/main/java/io/kamax/mxisd/threepid/generator/NotificationGenerator.java @@ -37,6 +37,6 @@ public interface NotificationGenerator { String getForValidation(IThreePidSession session); - String getForFraudulentUnbind(ThreePid tpid); + String getForNotificationUnbind(ThreePid tpid); } diff --git a/src/main/java/io/kamax/mxisd/threepid/generator/PlaceholderNotificationGenerator.java b/src/main/java/io/kamax/mxisd/threepid/generator/PlaceholderNotificationGenerator.java index 14849c1..c8fa0ab 100644 --- a/src/main/java/io/kamax/mxisd/threepid/generator/PlaceholderNotificationGenerator.java +++ b/src/main/java/io/kamax/mxisd/threepid/generator/PlaceholderNotificationGenerator.java @@ -127,7 +127,7 @@ public abstract class PlaceholderNotificationGenerator { .replace("%NEXT_URL%", validationLink); } - protected String populateForFraudulentUndind(ThreePid tpid, String input) { + protected String populateForNotificationUndind(ThreePid tpid, String input) { return populateForCommon(tpid, input); } diff --git a/src/main/java/io/kamax/mxisd/threepid/notification/GenericNotificationHandler.java b/src/main/java/io/kamax/mxisd/threepid/notification/GenericNotificationHandler.java index cd77907..4956eed 100644 --- a/src/main/java/io/kamax/mxisd/threepid/notification/GenericNotificationHandler.java +++ b/src/main/java/io/kamax/mxisd/threepid/notification/GenericNotificationHandler.java @@ -73,8 +73,8 @@ public abstract class GenericNotificationHandler - - - - - - - - - - - -
-

Hi,

- -

THIS IS IMPORTANT, PLEASE READ CAREFULLY.
- If you are the system administrator of the Matrix installation, read the second section.

- -

This is a notification email that a possibly unauthorized entity has attempted to alter your - 3PIDs (email, phone numbers, etc.) settings. The request was denied and no change has been made.

- -

This is so you are aware of a possible failure in case you just tried to remove a 3PID from your account.

- -

If you do not understand this email, please forward it to your System administrator.

- -
- -

As the system administrator:

- -

If you are using synapse as a Homeserver, this is a known issue related to MSC1194 - and abuse of separation of concerns. As a privacy-centric product and to protect your privacy, the request was actively - blocked. We have written a more detailed explanation on our Privacy wiki page - (Direct link to section) - so you can fully grasp the impact for you and your users.

- -

We have open an issue on the synapse repos to reflect the related privacy concerns and GDPR violation(s) and would - appreciate if you could comment on it or simply adds a thumbs up so the concerns are finally dealt with by the synapse dev team.
- Issue: https://github.com/matrix-org/synapse/issues/4540

- -

If you are using another Homeserver or this came following no action from your own users, then you have been the target - of an unbind attack from a rogue entity which was blocked. You may want to check your logs to see the exact source of - the attack and take relevant actions following your policy.

- -

If you would like to disable these notifications, please see the - 3PID sessions configuration documentation.

- -

Thanks,

- -

%DOMAIN_PRETTY% Admins

-
- - ---M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR-- - ---7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ-- diff --git a/src/main/resources/threepids/email/unbind-notification.eml b/src/main/resources/threepids/email/unbind-notification.eml new file mode 100644 index 0000000..9ac3fd0 --- /dev/null +++ b/src/main/resources/threepids/email/unbind-notification.eml @@ -0,0 +1,87 @@ +Subject: Unbind 3PID +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ" + +--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ +Content-Type: text/plain; charset=UTF-8 +Content-Disposition: inline + +Hello there! + +You or a server on your behalf requested to unbind your email. + +If it was really you who made this request, you can click on the following link to +complete the unbinding your email address: + + %VALIDATION_LINK% + +If you didn't make this request, you can safely disregard this email. + +%DOMAIN_PRETTY% Admins + +--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ +Content-Type: multipart/related; + boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR"; + type="text/html" + +--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR +Content-Type: text/html; charset=UTF-8 +Content-Disposition: inline + + + + + + + + + + + + + +
+

Hello there!

+ +

You or a server on your behalf requested to unbind your email.

+ +

If it was really you who made this request, you can click on the following link to + complete the unbinding your email address:

+ +

Complete email unbinding

+ +

If you didn't make this request, you can safely disregard this email.

+ +

%DOMAIN_PRETTY% Admins

+
+ + +--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR-- + +--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ-- diff --git a/src/main/resources/threepids/email/unbind-template.eml b/src/main/resources/threepids/email/unbind-template.eml new file mode 100644 index 0000000..bd39906 --- /dev/null +++ b/src/main/resources/threepids/email/unbind-template.eml @@ -0,0 +1,87 @@ +Subject: Your Matrix Unbinding Token +MIME-Version: 1.0 +Content-Type: multipart/alternative; + boundary="7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ" + +--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ +Content-Type: text/plain; charset=UTF-8 +Content-Disposition: inline + +Hello there! + +You or a server on your behalf requested to unbind your email. + +If it was really you who made this request, you can click on the following link to +complete the unbinding your email address: + + %VALIDATION_LINK% + +If you didn't make this request, you can safely disregard this email. + +%DOMAIN_PRETTY% Admins + +--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ +Content-Type: multipart/related; + boundary="M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR"; + type="text/html" + +--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR +Content-Type: text/html; charset=UTF-8 +Content-Disposition: inline + + + + + + + + + + + + + +
+

Hello there!

+ +

You or a server on your behalf requested to unbind your email.

+ +

If it was really you who made this request, you can click on the following link to + complete the unbinding your email address:

+ +

Complete email verification

+ +

If you didn't make this request, you can safely disregard this email.

+ +

%DOMAIN_PRETTY% Admins

+
+ + +--M3yzHl5YZehm9v4bAM8sKEdcOoVnRnKR-- + +--7REaIwWQCioQ6NaBlAQlg8ztbUQj6PKJ-- diff --git a/src/main/resources/threepids/sms/unbind-fraudulent.txt b/src/main/resources/threepids/sms/unbind-fraudulent.txt deleted file mode 100644 index fd2f738..0000000 --- a/src/main/resources/threepids/sms/unbind-fraudulent.txt +++ /dev/null @@ -1 +0,0 @@ -INFORMATIONAL ONLY - Someone attempted to change your Matrix 3PIDs, with a potential data leak. Please contact your system administrator. \ No newline at end of file diff --git a/src/main/resources/threepids/sms/unbind-notification.txt b/src/main/resources/threepids/sms/unbind-notification.txt new file mode 100644 index 0000000..7eaad7e --- /dev/null +++ b/src/main/resources/threepids/sms/unbind-notification.txt @@ -0,0 +1 @@ +Your Matrix 3PID was unbinding. diff --git a/src/main/resources/threepids/sms/unbind-template.txt b/src/main/resources/threepids/sms/unbind-template.txt new file mode 100644 index 0000000..a66d6c5 --- /dev/null +++ b/src/main/resources/threepids/sms/unbind-template.txt @@ -0,0 +1 @@ +Your Matrix token is %VALIDATION_TOKEN% \ No newline at end of file