diff --git a/src/main/java/io/kamax/mxisd/HttpMxisd.java b/src/main/java/io/kamax/mxisd/HttpMxisd.java
index 41eb181..a17cd87 100644
--- a/src/main/java/io/kamax/mxisd/HttpMxisd.java
+++ b/src/main/java/io/kamax/mxisd/HttpMxisd.java
@@ -91,6 +91,7 @@ public class HttpMxisd {
.get(SessionTpidGetValidatedHandler.Path, SaneHandler.around(new SessionTpidGetValidatedHandler(m.getSession())))
.post(SessionTpidBindHandler.Path, SaneHandler.around(new SessionTpidBindHandler(m.getSession(), m.getInvitationManager())))
.post(SessionTpidUnbindHandler.Path, SaneHandler.around(new SessionTpidUnbindHandler(m.getSession())))
+ .post(SignEd25519Handler.Path, SaneHandler.around(new SignEd25519Handler(m.getConfig(), m.getInvitationManager(), m.getSign())))
// Profile endpoints
.get(ProfileHandler.Path, SaneHandler.around(new ProfileHandler(m.getProfile())))
diff --git a/src/main/java/io/kamax/mxisd/Mxisd.java b/src/main/java/io/kamax/mxisd/Mxisd.java
index bdaceca..31f37f9 100644
--- a/src/main/java/io/kamax/mxisd/Mxisd.java
+++ b/src/main/java/io/kamax/mxisd/Mxisd.java
@@ -106,7 +106,7 @@ public class Mxisd {
pMgr = new ProfileManager(ProfileProviders.get(), clientDns, httpClient);
notifMgr = new NotificationManager(cfg.getNotification(), NotificationHandlers.get());
sessMgr = new SessionManager(cfg.getSession(), cfg.getMatrix(), store, notifMgr, idStrategy, httpClient);
- invMgr = new InvitationManager(cfg, store, idStrategy, signMgr, fedDns, notifMgr);
+ invMgr = new InvitationManager(cfg, store, idStrategy, keyMgr, signMgr, fedDns, notifMgr);
authMgr = new AuthManager(cfg, AuthProviders.get(), idStrategy, invMgr, clientDns, httpClient);
dirMgr = new DirectoryManager(cfg.getDirectory(), clientDns, httpClient, DirectoryProviders.get());
asHander = new AppSvcManager(cfg, store, pMgr, notifMgr, synapse);
diff --git a/src/main/java/io/kamax/mxisd/exception/ObjectNotFoundException.java b/src/main/java/io/kamax/mxisd/exception/ObjectNotFoundException.java
index 81f79f0..3111470 100644
--- a/src/main/java/io/kamax/mxisd/exception/ObjectNotFoundException.java
+++ b/src/main/java/io/kamax/mxisd/exception/ObjectNotFoundException.java
@@ -22,8 +22,12 @@ package io.kamax.mxisd.exception;
public class ObjectNotFoundException extends RuntimeException {
+ public ObjectNotFoundException(String message) {
+ super(message);
+ }
+
public ObjectNotFoundException(String type, String id) {
- super(type + " with ID " + id + " does not exist");
+ this(type + " with ID " + id + " does not exist");
}
}
diff --git a/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SignEd25519Handler.java b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SignEd25519Handler.java
new file mode 100644
index 0000000..f76e7f5
--- /dev/null
+++ b/src/main/java/io/kamax/mxisd/http/undertow/handler/identity/v1/SignEd25519Handler.java
@@ -0,0 +1,75 @@
+/*
+ * mxisd - Matrix Identity Server Daemon
+ * Copyright (C) 2019 Kamax Sarl
+ *
+ * https://www.kamax.io/
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program. If not, see .
+ */
+
+package io.kamax.mxisd.http.undertow.handler.identity.v1;
+
+import com.google.gson.JsonObject;
+import io.kamax.matrix.MatrixID;
+import io.kamax.matrix._MatrixID;
+import io.kamax.matrix.json.GsonUtil;
+import io.kamax.matrix.json.MatrixJson;
+import io.kamax.mxisd.config.MxisdConfig;
+import io.kamax.mxisd.http.IsAPIv1;
+import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
+import io.kamax.mxisd.invitation.IThreePidInviteReply;
+import io.kamax.mxisd.invitation.InvitationManager;
+import io.kamax.mxisd.storage.crypto.SignatureManager;
+import io.undertow.server.HttpServerExchange;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class SignEd25519Handler extends BasicHttpHandler {
+
+ public static final String Path = IsAPIv1.Base + "/sign-ed25519";
+
+ private static final Logger log = LoggerFactory.getLogger(SignEd25519Handler.class);
+
+ private final MxisdConfig cfg;
+ private final InvitationManager invMgr;
+ private final SignatureManager signMgr;
+
+ public SignEd25519Handler(MxisdConfig cfg, InvitationManager invMgr, SignatureManager signMgr) {
+ this.cfg = cfg;
+ this.invMgr = invMgr;
+ this.signMgr = signMgr;
+ }
+
+ @Override
+ public void handleRequest(HttpServerExchange exchange) {
+ JsonObject body = parseJsonObject(exchange);
+
+ _MatrixID mxid = MatrixID.asAcceptable(GsonUtil.getStringOrThrow(body, "mxid"));
+ String token = GsonUtil.getStringOrThrow(body, "token");
+ String privKey = GsonUtil.getStringOrThrow(body, "private_key");
+
+ IThreePidInviteReply reply = invMgr.getInvite(token, privKey);
+ _MatrixID sender = reply.getInvite().getSender();
+
+ JsonObject res = new JsonObject();
+ res.addProperty("token", token);
+ res.addProperty("sender", sender.getId());
+ res.addProperty("mxid", mxid.getId());
+ res.add("signatures", signMgr.signMessageGson(cfg.getServer().getName(), MatrixJson.encodeCanonical(res)));
+
+ log.info("Signed data for invite using token {}", token);
+ respondJson(exchange, res);
+ }
+
+}
diff --git a/src/main/java/io/kamax/mxisd/invitation/IThreePidInviteReply.java b/src/main/java/io/kamax/mxisd/invitation/IThreePidInviteReply.java
index b1b561b..00057e6 100644
--- a/src/main/java/io/kamax/mxisd/invitation/IThreePidInviteReply.java
+++ b/src/main/java/io/kamax/mxisd/invitation/IThreePidInviteReply.java
@@ -20,6 +20,8 @@
package io.kamax.mxisd.invitation;
+import java.util.List;
+
public interface IThreePidInviteReply {
String getId();
@@ -30,4 +32,6 @@ public interface IThreePidInviteReply {
String getDisplayName();
+ List getPublicKeys();
+
}
diff --git a/src/main/java/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/java/io/kamax/mxisd/invitation/InvitationManager.java
index 4aa5150..7dca267 100644
--- a/src/main/java/io/kamax/mxisd/invitation/InvitationManager.java
+++ b/src/main/java/io/kamax/mxisd/invitation/InvitationManager.java
@@ -30,16 +30,17 @@ import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.dns.FederationDnsOverwrite;
import io.kamax.mxisd.exception.BadRequestException;
import io.kamax.mxisd.exception.MappingAlreadyExistsException;
+import io.kamax.mxisd.exception.ObjectNotFoundException;
import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.strategy.LookupStrategy;
import io.kamax.mxisd.notification.NotificationManager;
import io.kamax.mxisd.storage.IStorage;
-import io.kamax.mxisd.storage.crypto.SignatureManager;
+import io.kamax.mxisd.storage.crypto.*;
import io.kamax.mxisd.storage.ormlite.dao.ThreePidInviteIO;
import org.apache.commons.io.IOUtils;
-import org.apache.commons.lang.RandomStringUtils;
-import org.apache.commons.lang.StringUtils;
+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.HttpPost;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
@@ -72,6 +73,7 @@ public class InvitationManager {
private ServerConfig srvCfg;
private IStorage storage;
private LookupStrategy lookupMgr;
+ private KeyManager keyMgr;
private SignatureManager signMgr;
private FederationDnsOverwrite dns;
private NotificationManager notifMgr;
@@ -85,6 +87,7 @@ public class InvitationManager {
MxisdConfig mxisdCfg,
IStorage storage,
LookupStrategy lookupMgr,
+ KeyManager keyMgr,
SignatureManager signMgr,
FederationDnsOverwrite dns,
NotificationManager notifMgr
@@ -93,6 +96,7 @@ public class InvitationManager {
this.srvCfg = mxisdCfg.getServer();
this.storage = storage;
this.lookupMgr = lookupMgr;
+ this.keyMgr = keyMgr;
this.signMgr = signMgr;
this.dns = dns;
this.notifMgr = notifMgr;
@@ -109,7 +113,7 @@ public class InvitationManager {
io.getProperties()
);
- ThreePidInviteReply reply = new ThreePidInviteReply(getId(invite), invite, io.getToken(), "");
+ ThreePidInviteReply reply = new ThreePidInviteReply(getId(invite), invite, io.getToken(), "", Collections.emptyList());
invitations.put(reply.getId(), reply);
});
@@ -221,7 +225,7 @@ public class InvitationManager {
log.info("Invite is already pending for {}:{}, returning data", invitation.getMedium(), invitation.getAddress());
if (!StringUtils.equals(invitation.getRoomId(), reply.getInvite().getRoomId())) {
log.info("Sending new notification as new invite room {} is different from the original {}", invitation.getRoomId(), reply.getInvite().getRoomId());
- notifMgr.sendForReply(new ThreePidInviteReply(reply.getId(), invitation, reply.getToken(), reply.getDisplayName()));
+ notifMgr.sendForReply(new ThreePidInviteReply(reply.getId(), invitation, reply.getToken(), reply.getDisplayName(), reply.getPublicKeys()));
} else {
// FIXME we should check attempt and send if bigger
}
@@ -236,8 +240,20 @@ public class InvitationManager {
String token = RandomStringUtils.randomAlphanumeric(64);
String displayName = invitation.getAddress().substring(0, 3) + "...";
+ KeyIdentifier pKeyId = keyMgr.getServerSigningKey().getId();
+ KeyIdentifier eKeyId = keyMgr.generateKey(KeyType.Ephemeral);
- reply = new ThreePidInviteReply(invId, invitation, token, displayName);
+ String pPubKey = keyMgr.getPublicKeyBase64(pKeyId);
+ String ePubKey = keyMgr.getPublicKeyBase64(eKeyId);
+
+ invitation.getProperties().put("p_key_algo", pKeyId.getAlgorithm());
+ invitation.getProperties().put("p_key_serial", pKeyId.getSerial());
+ invitation.getProperties().put("p_key_public", pPubKey);
+ invitation.getProperties().put("e_key_algo", eKeyId.getAlgorithm());
+ invitation.getProperties().put("e_key_serial", eKeyId.getSerial());
+ invitation.getProperties().put("e_key_public", ePubKey);
+
+ reply = new ThreePidInviteReply(invId, invitation, token, displayName, Arrays.asList(pPubKey, ePubKey));
log.info("Performing invite to {}:{}", invitation.getMedium(), invitation.getAddress());
notifMgr.sendForReply(reply);
@@ -270,6 +286,28 @@ public class InvitationManager {
}
}
+ public IThreePidInviteReply getInvite(String token, String privKey) {
+ for (IThreePidInviteReply reply : invitations.values()) {
+ if (StringUtils.equals(reply.getToken(), token)) {
+ String algo = reply.getInvite().getProperties().get("e_key_algo");
+ String serial = reply.getInvite().getProperties().get("e_key_serial");
+
+ if (StringUtils.isAnyBlank(algo, serial)) {
+ continue;
+ }
+
+ String storedPrivKey = keyMgr.getKey(new GenericKeyIdentifier(KeyType.Ephemeral, algo, serial)).getPrivateKeyBase64();
+ if (!StringUtils.equals(storedPrivKey, privKey)) {
+ continue;
+ }
+
+ return reply;
+ }
+ }
+
+ throw new ObjectNotFoundException("No invite with such token and/or private key");
+ }
+
private void publishMapping(IThreePidInviteReply reply, String mxid) {
String medium = reply.getInvite().getMedium();
String address = reply.getInvite().getAddress();
diff --git a/src/main/java/io/kamax/mxisd/invitation/ThreePidInviteReply.java b/src/main/java/io/kamax/mxisd/invitation/ThreePidInviteReply.java
index 04ce9e6..4def1ca 100644
--- a/src/main/java/io/kamax/mxisd/invitation/ThreePidInviteReply.java
+++ b/src/main/java/io/kamax/mxisd/invitation/ThreePidInviteReply.java
@@ -20,18 +20,24 @@
package io.kamax.mxisd.invitation;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
public class ThreePidInviteReply implements IThreePidInviteReply {
private String id;
private IThreePidInvite invite;
private String token;
private String displayName;
+ private List publicKeys;
- public ThreePidInviteReply(String id, IThreePidInvite invite, String token, String displayName) {
+ public ThreePidInviteReply(String id, IThreePidInvite invite, String token, String displayName, List publicKeys) {
this.id = id;
this.invite = invite;
this.token = token;
this.displayName = displayName;
+ this.publicKeys = Collections.unmodifiableList(new ArrayList<>(publicKeys));
}
@Override
@@ -54,4 +60,9 @@ public class ThreePidInviteReply implements IThreePidInviteReply {
return displayName;
}
+ @Override
+ public List getPublicKeys() {
+ return publicKeys;
+ }
+
}