Skeleton for full support of all key types

This commit is contained in:
Max Dor
2019-02-12 15:39:05 +01:00
parent 249cc0ea92
commit acd8c7d7c5
28 changed files with 1191 additions and 41 deletions

View File

@@ -101,7 +101,7 @@ dependencies {
compile 'com.j256.ormlite:ormlite-jdbc:5.0' compile 'com.j256.ormlite:ormlite-jdbc:5.0'
// ed25519 handling // ed25519 handling
compile 'net.i2p.crypto:eddsa:0.1.0' compile 'net.i2p.crypto:eddsa:0.3.0'
// LDAP connector // LDAP connector
compile 'org.apache.directory.api:api-all:1.0.0' compile 'org.apache.directory.api:api-all:1.0.0'

View File

@@ -82,7 +82,7 @@ public class HttpMxisd {
// Identity endpoints // Identity endpoints
.get(HelloHandler.Path, helloHandler) .get(HelloHandler.Path, helloHandler)
.get(HelloHandler.Path + "/", helloHandler) // Be lax with possibly trailing slash .get(HelloHandler.Path + "/", helloHandler) // Be lax with possibly trailing slash
.get(SingleLookupHandler.Path, SaneHandler.around(new SingleLookupHandler(m.getIdentity(), m.getSign()))) .get(SingleLookupHandler.Path, SaneHandler.around(new SingleLookupHandler(m.getConfig(), m.getIdentity(), m.getSign())))
.post(BulkLookupHandler.Path, SaneHandler.around(new BulkLookupHandler(m.getIdentity()))) .post(BulkLookupHandler.Path, SaneHandler.around(new BulkLookupHandler(m.getIdentity())))
.post(StoreInviteHandler.Path, storeInvHandler) .post(StoreInviteHandler.Path, storeInvHandler)
.post(SessionStartHandler.Path, SaneHandler.around(new SessionStartHandler(m.getSession()))) .post(SessionStartHandler.Path, SaneHandler.around(new SessionStartHandler(m.getSession())))

View File

@@ -20,8 +20,6 @@
package io.kamax.mxisd; package io.kamax.mxisd;
import io.kamax.matrix.crypto.KeyManager;
import io.kamax.matrix.crypto.SignatureManager;
import io.kamax.mxisd.as.AppSvcManager; import io.kamax.mxisd.as.AppSvcManager;
import io.kamax.mxisd.auth.AuthManager; import io.kamax.mxisd.auth.AuthManager;
import io.kamax.mxisd.auth.AuthProviders; import io.kamax.mxisd.auth.AuthProviders;
@@ -48,6 +46,9 @@ import io.kamax.mxisd.profile.ProfileManager;
import io.kamax.mxisd.profile.ProfileProviders; import io.kamax.mxisd.profile.ProfileProviders;
import io.kamax.mxisd.session.SessionManager; import io.kamax.mxisd.session.SessionManager;
import io.kamax.mxisd.storage.IStorage; import io.kamax.mxisd.storage.IStorage;
import io.kamax.mxisd.storage.crypto.Ed25519KeyManager;
import io.kamax.mxisd.storage.crypto.KeyManager;
import io.kamax.mxisd.storage.crypto.SignatureManager;
import io.kamax.mxisd.storage.ormlite.OrmLiteSqlStorage; import io.kamax.mxisd.storage.ormlite.OrmLiteSqlStorage;
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;
@@ -63,7 +64,7 @@ public class Mxisd {
private IStorage store; private IStorage store;
private KeyManager keyMgr; private Ed25519KeyManager keyMgr;
private SignatureManager signMgr; private SignatureManager signMgr;
// Features // Features
@@ -92,7 +93,7 @@ public class Mxisd {
store = new OrmLiteSqlStorage(cfg); store = new OrmLiteSqlStorage(cfg);
keyMgr = CryptoFactory.getKeyManager(cfg.getKey()); keyMgr = CryptoFactory.getKeyManager(cfg.getKey());
signMgr = CryptoFactory.getSignatureManager(keyMgr, cfg.getServer()); signMgr = CryptoFactory.getSignatureManager(keyMgr);
ClientDnsOverwrite clientDns = new ClientDnsOverwrite(cfg.getDns().getOverwrite()); ClientDnsOverwrite clientDns = new ClientDnsOverwrite(cfg.getDns().getOverwrite());
FederationDnsOverwrite fedDns = new FederationDnsOverwrite(cfg.getDns().getOverwrite()); FederationDnsOverwrite fedDns = new FederationDnsOverwrite(cfg.getDns().getOverwrite());
Synapse synapse = new Synapse(cfg.getSynapseSql()); Synapse synapse = new Synapse(cfg.getSynapseSql());
@@ -105,7 +106,7 @@ public class Mxisd {
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.getSession(), cfg.getMatrix(), store, notifMgr, idStrategy, httpClient); sessMgr = new SessionManager(cfg.getSession(), cfg.getMatrix(), store, notifMgr, idStrategy, httpClient);
invMgr = new InvitationManager(cfg.getInvite(), store, idStrategy, signMgr, fedDns, notifMgr); invMgr = new InvitationManager(cfg, store, idStrategy, signMgr, fedDns, notifMgr);
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());
asHander = new AppSvcManager(cfg, store, pMgr, notifMgr, synapse); asHander = new AppSvcManager(cfg, store, pMgr, notifMgr, synapse);

View File

@@ -20,9 +20,8 @@
package io.kamax.mxisd.crypto; package io.kamax.mxisd.crypto;
import io.kamax.matrix.crypto.*;
import io.kamax.mxisd.config.KeyConfig; import io.kamax.mxisd.config.KeyConfig;
import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.storage.crypto.*;
import org.apache.commons.io.FileUtils; import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -31,10 +30,10 @@ import java.io.IOException;
public class CryptoFactory { public class CryptoFactory {
public static KeyManager getKeyManager(KeyConfig keyCfg) { public static Ed25519KeyManager getKeyManager(KeyConfig keyCfg) {
_KeyStore store; KeyStore store;
if (StringUtils.equals(":memory:", keyCfg.getPath())) { if (StringUtils.equals(":memory:", keyCfg.getPath())) {
store = new KeyMemoryStore(); store = new MemoryKeyStore();
} else { } else {
File keyStore = new File(keyCfg.getPath()); File keyStore = new File(keyCfg.getPath());
if (!keyStore.exists()) { if (!keyStore.exists()) {
@@ -45,14 +44,14 @@ public class CryptoFactory {
} }
} }
store = new KeyFileStore(keyCfg.getPath()); store = new FileKeyStore(keyCfg.getPath());
} }
return new KeyManager(store); return new Ed25519KeyManager(store);
} }
public static SignatureManager getSignatureManager(KeyManager keyMgr, ServerConfig cfg) { public static SignatureManager getSignatureManager(Ed25519KeyManager keyMgr) {
return new SignatureManager(keyMgr, cfg.getName()); return new Ed25519SignatureManager(keyMgr);
} }
} }

View File

@@ -21,10 +21,11 @@
package io.kamax.mxisd.http.undertow.handler.identity.v1; package io.kamax.mxisd.http.undertow.handler.identity.v1;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import io.kamax.matrix.crypto.KeyManager;
import io.kamax.mxisd.exception.BadRequestException;
import io.kamax.mxisd.http.IsAPIv1; import io.kamax.mxisd.http.IsAPIv1;
import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler; import io.kamax.mxisd.http.undertow.handler.BasicHttpHandler;
import io.kamax.mxisd.storage.crypto.GenericKeyIdentifier;
import io.kamax.mxisd.storage.crypto.KeyManager;
import io.kamax.mxisd.storage.crypto.KeyType;
import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpServerExchange;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -46,16 +47,12 @@ public class KeyGetHandler extends BasicHttpHandler {
public void handleRequest(HttpServerExchange exchange) { public void handleRequest(HttpServerExchange exchange) {
String key = getQueryParameter(exchange, Key); String key = getQueryParameter(exchange, Key);
String[] v = key.split(":", 2); String[] v = key.split(":", 2);
String keyType = v[0]; String keyAlgo = v[0];
int keyId = Integer.parseInt(v[1]); String keyId = v[1];
if (!"ed25519".contentEquals(keyType)) { log.info("Key {}:{} was requested", keyAlgo, keyId);
throw new BadRequestException("Invalid algorithm: " + keyType);
}
log.info("Key {}:{} was requested", keyType, keyId);
JsonObject obj = new JsonObject(); JsonObject obj = new JsonObject();
obj.addProperty("public_key", mgr.getPublicKeyBase64(keyId)); obj.addProperty("public_key", mgr.getPublicKeyBase64(new GenericKeyIdentifier(KeyType.Regular, keyAlgo, keyId)));
respond(exchange, obj); respond(exchange, obj);
} }

View File

@@ -20,10 +20,10 @@
package io.kamax.mxisd.http.undertow.handler.identity.v1; package io.kamax.mxisd.http.undertow.handler.identity.v1;
import io.kamax.matrix.crypto.KeyManager;
import io.kamax.mxisd.http.IsAPIv1; import io.kamax.mxisd.http.IsAPIv1;
import io.kamax.mxisd.storage.crypto.KeyManager;
import io.kamax.mxisd.storage.crypto.KeyType;
import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpServerExchange;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -44,9 +44,7 @@ public class RegularKeyIsValidHandler extends KeyIsValidHandler {
String pubKey = getQueryParameter(exchange, "public_key"); String pubKey = getQueryParameter(exchange, "public_key");
log.info("Validating public key {}", pubKey); log.info("Validating public key {}", pubKey);
// TODO do in manager respondJson(exchange, mgr.isValid(KeyType.Regular, pubKey) ? validKey : invalidKey);
boolean valid = StringUtils.equals(pubKey, mgr.getPublicKeyBase64(mgr.getCurrentIndex()));
respondJson(exchange, valid ? validKey : invalidKey);
} }
} }

View File

@@ -21,15 +21,17 @@
package io.kamax.mxisd.http.undertow.handler.identity.v1; package io.kamax.mxisd.http.undertow.handler.identity.v1;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import io.kamax.matrix.crypto.SignatureManager;
import io.kamax.matrix.event.EventKey; import io.kamax.matrix.event.EventKey;
import io.kamax.matrix.json.GsonUtil; import io.kamax.matrix.json.GsonUtil;
import io.kamax.matrix.json.MatrixJson; import io.kamax.matrix.json.MatrixJson;
import io.kamax.mxisd.config.MxisdConfig;
import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.http.IsAPIv1; import io.kamax.mxisd.http.IsAPIv1;
import io.kamax.mxisd.http.io.identity.SingeLookupReplyJson; import io.kamax.mxisd.http.io.identity.SingeLookupReplyJson;
import io.kamax.mxisd.lookup.SingleLookupReply; import io.kamax.mxisd.lookup.SingleLookupReply;
import io.kamax.mxisd.lookup.SingleLookupRequest; import io.kamax.mxisd.lookup.SingleLookupRequest;
import io.kamax.mxisd.lookup.strategy.LookupStrategy; import io.kamax.mxisd.lookup.strategy.LookupStrategy;
import io.kamax.mxisd.storage.crypto.SignatureManager;
import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpServerExchange;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -42,10 +44,12 @@ public class SingleLookupHandler extends LookupHandler {
private transient final Logger log = LoggerFactory.getLogger(SingleLookupHandler.class); private transient final Logger log = LoggerFactory.getLogger(SingleLookupHandler.class);
private ServerConfig cfg;
private LookupStrategy strategy; private LookupStrategy strategy;
private SignatureManager signMgr; private SignatureManager signMgr;
public SingleLookupHandler(LookupStrategy strategy, SignatureManager signMgr) { public SingleLookupHandler(MxisdConfig cfg, LookupStrategy strategy, SignatureManager signMgr) {
this.cfg = cfg.getServer();
this.strategy = strategy; this.strategy = strategy;
this.signMgr = signMgr; this.signMgr = signMgr;
} }
@@ -72,7 +76,7 @@ public class SingleLookupHandler extends LookupHandler {
// FIXME signing should be done in the business model, not in the controller // FIXME signing should be done in the business model, not in the controller
JsonObject obj = GsonUtil.makeObj(new SingeLookupReplyJson(lookup)); JsonObject obj = GsonUtil.makeObj(new SingeLookupReplyJson(lookup));
obj.add(EventKey.Signatures.get(), signMgr.signMessageGson(MatrixJson.encodeCanonical(obj))); obj.add(EventKey.Signatures.get(), signMgr.signMessageGson(cfg.getName(), MatrixJson.encodeCanonical(obj)));
respondJson(exchange, obj); respondJson(exchange, obj);
} }

View File

@@ -24,7 +24,6 @@ import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken; import com.google.gson.reflect.TypeToken;
import io.kamax.matrix.MatrixID; import io.kamax.matrix.MatrixID;
import io.kamax.matrix._MatrixID; import io.kamax.matrix._MatrixID;
import io.kamax.matrix.crypto.KeyManager;
import io.kamax.matrix.json.GsonUtil; import io.kamax.matrix.json.GsonUtil;
import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.config.ServerConfig;
import io.kamax.mxisd.exception.BadRequestException; import io.kamax.mxisd.exception.BadRequestException;
@@ -36,6 +35,7 @@ import io.kamax.mxisd.invitation.IThreePidInvite;
import io.kamax.mxisd.invitation.IThreePidInviteReply; import io.kamax.mxisd.invitation.IThreePidInviteReply;
import io.kamax.mxisd.invitation.InvitationManager; import io.kamax.mxisd.invitation.InvitationManager;
import io.kamax.mxisd.invitation.ThreePidInvite; import io.kamax.mxisd.invitation.ThreePidInvite;
import io.kamax.mxisd.storage.crypto.KeyManager;
import io.undertow.server.HttpServerExchange; import io.undertow.server.HttpServerExchange;
import io.undertow.util.QueryParameterUtils; import io.undertow.util.QueryParameterUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@@ -96,7 +96,8 @@ public class StoreInviteHandler extends BasicHttpHandler {
IThreePidInvite invite = new ThreePidInvite(sender, inv.getMedium(), inv.getAddress(), inv.getRoomId(), parameters); IThreePidInvite invite = new ThreePidInvite(sender, inv.getMedium(), inv.getAddress(), inv.getRoomId(), parameters);
IThreePidInviteReply reply = invMgr.storeInvite(invite); IThreePidInviteReply reply = invMgr.storeInvite(invite);
respondJson(exchange, new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getCurrentIndex()), cfg.getPublicUrl())); // FIXME the key info must be set by the invitation manager in the reply object!
respondJson(exchange, new ThreePidInviteReplyIO(reply, keyMgr.getPublicKeyBase64(keyMgr.getServerSigningKey().getId()), cfg.getPublicUrl()));
} }
} }

View File

@@ -23,9 +23,10 @@ package io.kamax.mxisd.invitation;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import io.kamax.matrix.MatrixID; import io.kamax.matrix.MatrixID;
import io.kamax.matrix.crypto.SignatureManager;
import io.kamax.matrix.json.GsonUtil; 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.ServerConfig;
import io.kamax.mxisd.dns.FederationDnsOverwrite; import io.kamax.mxisd.dns.FederationDnsOverwrite;
import io.kamax.mxisd.exception.BadRequestException; import io.kamax.mxisd.exception.BadRequestException;
import io.kamax.mxisd.exception.MappingAlreadyExistsException; import io.kamax.mxisd.exception.MappingAlreadyExistsException;
@@ -34,6 +35,7 @@ import io.kamax.mxisd.lookup.ThreePidMapping;
import io.kamax.mxisd.lookup.strategy.LookupStrategy; import io.kamax.mxisd.lookup.strategy.LookupStrategy;
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.crypto.SignatureManager;
import io.kamax.mxisd.storage.ormlite.dao.ThreePidInviteIO; import io.kamax.mxisd.storage.ormlite.dao.ThreePidInviteIO;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.RandomStringUtils; import org.apache.commons.lang.RandomStringUtils;
@@ -67,6 +69,7 @@ public class InvitationManager {
private transient final Logger log = LoggerFactory.getLogger(InvitationManager.class); private transient final Logger log = LoggerFactory.getLogger(InvitationManager.class);
private InvitationConfig cfg; private InvitationConfig cfg;
private ServerConfig srvCfg;
private IStorage storage; private IStorage storage;
private LookupStrategy lookupMgr; private LookupStrategy lookupMgr;
private SignatureManager signMgr; private SignatureManager signMgr;
@@ -79,14 +82,15 @@ public class InvitationManager {
private Map<String, IThreePidInviteReply> invitations = new ConcurrentHashMap<>(); private Map<String, IThreePidInviteReply> invitations = new ConcurrentHashMap<>();
public InvitationManager( public InvitationManager(
InvitationConfig cfg, MxisdConfig mxisdCfg,
IStorage storage, IStorage storage,
LookupStrategy lookupMgr, LookupStrategy lookupMgr,
SignatureManager signMgr, SignatureManager signMgr,
FederationDnsOverwrite dns, FederationDnsOverwrite dns,
NotificationManager notifMgr NotificationManager notifMgr
) { ) {
this.cfg = cfg; this.cfg = mxisdCfg.getInvite();
this.srvCfg = mxisdCfg.getServer();
this.storage = storage; this.storage = storage;
this.lookupMgr = lookupMgr; this.lookupMgr = lookupMgr;
this.signMgr = signMgr; this.signMgr = signMgr;
@@ -280,7 +284,7 @@ public class InvitationManager {
JsonObject obj = new JsonObject(); JsonObject obj = new JsonObject();
obj.addProperty("mxid", mxid); obj.addProperty("mxid", mxid);
obj.addProperty("token", reply.getToken()); obj.addProperty("token", reply.getToken());
obj.add("signatures", signMgr.signMessageGson(obj.toString())); obj.add("signatures", signMgr.signMessageGson(srvCfg.getName(), obj.toString()));
JsonObject objUp = new JsonObject(); JsonObject objUp = new JsonObject();
objUp.addProperty("mxid", mxid); objUp.addProperty("mxid", mxid);
@@ -298,7 +302,7 @@ public class InvitationManager {
content.addProperty("address", address); content.addProperty("address", address);
content.addProperty("mxid", mxid); content.addProperty("mxid", mxid);
content.add("signatures", signMgr.signMessageGson(content.toString())); content.add("signatures", signMgr.signMessageGson(srvCfg.getName(), content.toString()));
StringEntity entity = new StringEntity(content.toString(), StandardCharsets.UTF_8); StringEntity entity = new StringEntity(content.toString(), StandardCharsets.UTF_8);
entity.setContentType("application/json"); entity.setContentType("application/json");

View File

@@ -0,0 +1,29 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
public class Ed2219RegularKeyIdentifier extends RegularKeyIdentifier {
public Ed2219RegularKeyIdentifier(String serial) {
super(KeyAlgorithm.Ed25519, serial);
}
}

View File

@@ -0,0 +1,53 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
public class Ed25519Key implements Key {
private KeyIdentifier id;
private String privKey;
public Ed25519Key(KeyIdentifier id, String privKey) {
if (!KeyAlgorithm.Ed25519.equals(id.getAlgorithm())) {
throw new IllegalArgumentException();
}
this.id = new GenericKeyIdentifier(id);
this.privKey = privKey;
}
@Override
public KeyIdentifier getId() {
return id;
}
@Override
public boolean isValid() {
return true;
}
@Override
public String getPrivateKeyBase64() {
return privKey;
}
}

View File

@@ -0,0 +1,140 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
import io.kamax.matrix.codec.MxBase64;
import net.i2p.crypto.eddsa.EdDSAPrivateKey;
import net.i2p.crypto.eddsa.EdDSAPublicKey;
import net.i2p.crypto.eddsa.KeyPairGenerator;
import net.i2p.crypto.eddsa.spec.EdDSANamedCurveTable;
import net.i2p.crypto.eddsa.spec.EdDSAParameterSpec;
import net.i2p.crypto.eddsa.spec.EdDSAPrivateKeySpec;
import net.i2p.crypto.eddsa.spec.EdDSAPublicKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.nio.ByteBuffer;
import java.security.KeyPair;
import java.time.Instant;
import java.util.List;
public class Ed25519KeyManager implements KeyManager {
private static final Logger log = LoggerFactory.getLogger(Ed25519KeyManager.class);
private final EdDSAParameterSpec keySpecs;
private final KeyStore store;
public Ed25519KeyManager(KeyStore store) {
this.keySpecs = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.ED_25519);
this.store = store;
if (!store.getCurrentKey().isPresent()) {
List<KeyIdentifier> keys = store.list(KeyType.Regular);
if (keys.isEmpty()) {
keys.add(generateKey(KeyType.Regular));
}
store.setCurrentKey(keys.get(0));
}
}
protected String generateId() {
ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
buffer.putLong(Instant.now().toEpochMilli() - 1546297200000L); // TS since 2019-01-01T00:00:00Z to keep IDs short
return Base64.encodeBase64URLSafeString(buffer.array()) + RandomStringUtils.randomAlphanumeric(1);
}
protected String getPrivateKeyBase64(EdDSAPrivateKey key) {
return MxBase64.encode(key.getSeed());
}
public EdDSAParameterSpec getKeySpecs() {
return keySpecs;
}
@Override
public KeyIdentifier generateKey(KeyType type) {
KeyIdentifier id;
do {
id = new GenericKeyIdentifier(type, KeyAlgorithm.Ed25519, generateId());
} while (store.has(id));
KeyPair pair = (new KeyPairGenerator()).generateKeyPair();
String keyEncoded = getPrivateKeyBase64((EdDSAPrivateKey) pair.getPrivate());
Key key = new GenericKey(id, true, keyEncoded);
store.add(key);
return id;
}
@Override
public List<KeyIdentifier> getKeys(KeyType type) {
return store.list(type);
}
@Override
public Key getServerSigningKey() {
return store.get(store.getCurrentKey().orElseThrow(IllegalStateException::new));
}
@Override
public Key getKey(KeyIdentifier id) {
return store.get(id);
}
public EdDSAPrivateKeySpec getPrivateKeySpecs(KeyIdentifier id) {
return new EdDSAPrivateKeySpec(java.util.Base64.getDecoder().decode(getKey(id).getPrivateKeyBase64()), keySpecs);
}
public EdDSAPrivateKey getPrivateKey(KeyIdentifier id) {
return new EdDSAPrivateKey(getPrivateKeySpecs(id));
}
public EdDSAPublicKey getPublicKey(KeyIdentifier id) {
EdDSAPrivateKeySpec privKeySpec = getPrivateKeySpecs(id);
EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKeySpec.getA(), keySpecs);
return new EdDSAPublicKey(pubKeySpec);
}
@Override
public void disableKey(KeyIdentifier id) {
Key key = store.get(id);
key = new GenericKey(id, false, key.getPrivateKeyBase64());
store.update(key);
}
@Override
public String getPublicKeyBase64(KeyIdentifier id) {
return MxBase64.encode(getPublicKey(id).getAbyte());
}
@Override
public boolean isValid(KeyType type, String publicKeyBase64) {
// TODO caching?
return getKeys(type).stream().anyMatch(id -> StringUtils.equals(getPublicKeyBase64(id), publicKeyBase64));
}
}

View File

@@ -0,0 +1,85 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
import com.google.gson.JsonObject;
import io.kamax.matrix.codec.MxBase64;
import io.kamax.matrix.json.MatrixJson;
import net.i2p.crypto.eddsa.EdDSAEngine;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SignatureException;
public class Ed25519SignatureManager implements SignatureManager {
private final Ed25519KeyManager keyMgr;
public Ed25519SignatureManager(Ed25519KeyManager keyMgr) {
this.keyMgr = keyMgr;
}
@Override
public JsonObject signMessageGson(String domain, String message) {
Signature sign = sign(message);
JsonObject keySignature = new JsonObject();
// FIXME should create a signing key object what would give this ed and index values
keySignature.addProperty(sign.getKey().getAlgorithm() + ":" + sign.getKey().getSerial(), sign.getSignature());
JsonObject signature = new JsonObject();
signature.add(domain, keySignature);
return signature;
}
@Override
public Signature sign(JsonObject obj) {
return sign(MatrixJson.encodeCanonical(obj));
}
@Override
public Signature sign(byte[] data) {
try {
KeyIdentifier signingKeyId = keyMgr.getServerSigningKey().getId();
EdDSAEngine signEngine = new EdDSAEngine(MessageDigest.getInstance(keyMgr.getKeySpecs().getHashAlgorithm()));
signEngine.initSign(keyMgr.getPrivateKey(signingKeyId));
byte[] signRaw = signEngine.signOneShot(data);
String sign = MxBase64.encode(signRaw);
return new Signature() {
@Override
public KeyIdentifier getKey() {
return signingKeyId;
}
@Override
public String getSignature() {
return sign;
}
};
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
throw new RuntimeException(e);
}
}
}

View File

@@ -0,0 +1,78 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
import io.kamax.mxisd.exception.ObjectNotFoundException;
import java.util.List;
import java.util.Optional;
public class FileKeyStore implements KeyStore {
public FileKeyStore(String path) {
}
@Override
public boolean has(KeyIdentifier id) {
return false;
}
@Override
public List<KeyIdentifier> list() {
return null;
}
@Override
public List<KeyIdentifier> list(KeyType type) {
return null;
}
@Override
public Key get(KeyIdentifier id) throws ObjectNotFoundException {
return null;
}
@Override
public void add(Key key) throws IllegalStateException {
}
@Override
public void update(Key key) throws ObjectNotFoundException {
}
@Override
public void delete(KeyIdentifier id) throws ObjectNotFoundException {
}
@Override
public void setCurrentKey(KeyIdentifier id) throws IllegalArgumentException {
}
@Override
public Optional<KeyIdentifier> getCurrentKey() {
return Optional.empty();
}
}

View File

@@ -0,0 +1,51 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
public class GenericKey implements Key {
private final KeyIdentifier id;
private final boolean isValid;
private final String privKey;
public GenericKey(KeyIdentifier id, boolean isValid, String privKey) {
this.id = new GenericKeyIdentifier(id);
this.isValid = isValid;
this.privKey = privKey;
}
@Override
public KeyIdentifier getId() {
return id;
}
@Override
public boolean isValid() {
return isValid;
}
@Override
public String getPrivateKeyBase64() {
return privKey;
}
}

View File

@@ -0,0 +1,54 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
public class GenericKeyIdentifier implements KeyIdentifier {
private final KeyType type;
private final String algo;
private final String serial;
public GenericKeyIdentifier(KeyIdentifier id) {
this(id.getType(), id.getAlgorithm(), id.getSerial());
}
public GenericKeyIdentifier(KeyType type, String algo, String serial) {
this.type = type;
this.algo = algo;
this.serial = serial;
}
@Override
public KeyType getType() {
return type;
}
@Override
public String getAlgorithm() {
return algo;
}
@Override
public String getSerial() {
return serial;
}
}

View File

@@ -0,0 +1,44 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
/**
* A signing key
*/
public interface Key {
KeyIdentifier getId();
/**
* If the key is currently valid
*
* @return true if the key is valid, false if not
*/
boolean isValid();
/**
* Get the private key
*
* @return the private key encoded as Base64
*/
String getPrivateKeyBase64();
}

View File

@@ -0,0 +1,27 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
public interface KeyAlgorithm {
String Ed25519 = "ed25519";
}

View File

@@ -0,0 +1,50 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
/**
* Identifying data for a given Key.
*/
public interface KeyIdentifier {
/**
* Type of key.
*
* @return The type of the key
*/
KeyType getType();
/**
* Algorithm of the key. Typically <code>ed25519</code>.
*
* @return The algorithm of the key
*/
String getAlgorithm();
/**
* Serial of the key, unique for the algorithm.
* It is typically made of random alphanumerical characters.
*
* @return The serial of the key
*/
String getSerial();
}

View File

@@ -0,0 +1,41 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
import java.util.List;
public interface KeyManager {
KeyIdentifier generateKey(KeyType type);
List<KeyIdentifier> getKeys(KeyType type);
Key getServerSigningKey();
Key getKey(KeyIdentifier id);
void disableKey(KeyIdentifier id);
String getPublicKeyBase64(KeyIdentifier id);
boolean isValid(KeyType type, String publicKeyBase64);
}

View File

@@ -0,0 +1,98 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
import io.kamax.mxisd.exception.ObjectNotFoundException;
import java.util.List;
import java.util.Optional;
/**
* Store to persist signing keys and the identifier for the current long-term signing key
*/
public interface KeyStore {
/**
* If a given key is currently stored
*
* @param id The Identifier elements for the key
* @return true if the key is stored, false if not
*/
boolean has(KeyIdentifier id);
/**
* List all keys within the store
*
* @return The list of key identifiers
*/
List<KeyIdentifier> list();
/**
* List all keys of a given type within the store
*
* @param type The type to filter on
* @return The list of keys identifiers matching the given type
*/
List<KeyIdentifier> list(KeyType type);
/**
* Get the key that relates to the given identifier
*
* @param id The identifier of the key to get
* @return The key
* @throws ObjectNotFoundException If no key is found for that identifier
*/
Key get(KeyIdentifier id) throws ObjectNotFoundException;
/**
* Add a key to the store
*
* @param key The key to store
* @throws IllegalStateException If a key already exist for the given identifier data
*/
void add(Key key) throws IllegalStateException;
void update(Key key) throws ObjectNotFoundException;
/**
* Delete a key from the store
*
* @param id The key identifier of the key to delete
* @throws ObjectNotFoundException If no key is found for that identifier
*/
void delete(KeyIdentifier id) throws ObjectNotFoundException;
/**
* Store the information of which key is the current signing key
*
* @param id The key identifier
* @throws ObjectNotFoundException If the key is not known to the store
*/
void setCurrentKey(KeyIdentifier id) throws ObjectNotFoundException;
/**
* Retrieve the previously stored information of which key is the current signing key, if any
*
* @return The optional key identifier that was previously stored
*/
Optional<KeyIdentifier> getCurrentKey();
}

View File

@@ -0,0 +1,39 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
/**
* Types of keys used by an Identity server.
* See https://matrix.org/docs/spec/identity_service/r0.1.0.html#key-management
*/
public enum KeyType {
/**
* Ephemeral keys are related to 3PID invites and are only valid while the invite is pending.
*/
Ephemeral,
/**
* Regular keys are used by the Identity Server itself to sign requests/responses
*/
Regular
}

View File

@@ -0,0 +1,109 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
import io.kamax.mxisd.exception.ObjectNotFoundException;
import org.apache.commons.lang3.StringUtils;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class MemoryKeyStore implements KeyStore {
private Map<KeyType, Map<String, Map<String, String>>> keys = new ConcurrentHashMap<>();
private KeyIdentifier current;
private Map<String, String> getMap(KeyType type, String algo) {
return keys.computeIfAbsent(type, k -> new ConcurrentHashMap<>()).computeIfAbsent(algo, k -> new ConcurrentHashMap<>());
}
@Override
public boolean has(KeyIdentifier id) {
return getMap(id.getType(), id.getAlgorithm()).containsKey(id.getSerial());
}
@Override
public List<KeyIdentifier> list() {
List<KeyIdentifier> keyIds = new ArrayList<>();
keys.forEach((key, value) -> value.forEach((key1, value1) -> value1.forEach((key2, value2) -> keyIds.add(new GenericKeyIdentifier(key, key1, key2)))));
return keyIds;
}
@Override
public List<KeyIdentifier> list(KeyType type) {
List<KeyIdentifier> keyIds = new ArrayList<>();
keys.computeIfAbsent(type, t -> new ConcurrentHashMap<>()).forEach((key, value) -> value.forEach((key1, value1) -> keyIds.add(new GenericKeyIdentifier(type, key, key1))));
return keyIds;
}
@Override
public Key get(KeyIdentifier id) throws ObjectNotFoundException {
String data = getMap(id.getType(), id.getAlgorithm()).get(id.getSerial());
if (Objects.isNull(data)) {
throw new ObjectNotFoundException("Key", id.getType() + ":" + id.getAlgorithm() + ":" + id.getSerial());
}
return new GenericKey(new GenericKeyIdentifier(id), StringUtils.isEmpty(data), data);
}
private void set(Key key) {
String data = key.isValid() ? key.getPrivateKeyBase64() : "";
getMap(key.getId().getType(), key.getId().getAlgorithm()).put(key.getId().getSerial(), data);
}
@Override
public void add(Key key) throws IllegalStateException {
if (has(key.getId())) {
throw new IllegalStateException();
}
set(key);
}
@Override
public void update(Key key) throws ObjectNotFoundException {
if (!has(key.getId())) {
throw new ObjectNotFoundException("Key", key.getId().getType() + ":" + key.getId().getAlgorithm() + ":" + key.getId().getSerial());
}
set(key);
}
@Override
public void delete(KeyIdentifier id) throws ObjectNotFoundException {
keys.computeIfAbsent(id.getType(), k -> new ConcurrentHashMap<>()).computeIfAbsent(id.getAlgorithm(), k -> new ConcurrentHashMap<>()).remove(id.getSerial());
}
@Override
public void setCurrentKey(KeyIdentifier id) throws ObjectNotFoundException {
if (!has(id)) {
throw new ObjectNotFoundException("Key", id.getType() + ":" + id.getAlgorithm() + ":" + id.getSerial());
}
current = id;
}
@Override
public Optional<KeyIdentifier> getCurrentKey() {
return Optional.ofNullable(current);
}
}

View File

@@ -0,0 +1,29 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
public class RegularKeyIdentifier extends GenericKeyIdentifier {
public RegularKeyIdentifier(String algo, String serial) {
super(KeyType.Regular, algo, serial);
}
}

View File

@@ -0,0 +1,29 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
public interface Signature {
KeyIdentifier getKey();
String getSignature();
}

View File

@@ -0,0 +1,57 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.storage.crypto;
import com.google.gson.JsonObject;
import java.nio.charset.StandardCharsets;
public interface SignatureManager {
JsonObject signMessageGson(String domain, String message);
/**
* Sign the canonical form of a JSON object
*
* @param obj The JSON object to canonicalize and sign
* @return The signature
*/
Signature sign(JsonObject obj);
/**
* Sign the message, using UTF-8 as decoding character set
*
* @param message The UTF-8 encoded message
* @return
*/
default Signature sign(String message) {
return sign(message.getBytes(StandardCharsets.UTF_8));
}
/**
* Sign the data
*
* @param data The data to sign
* @return The signature
*/
Signature sign(byte[] data);
}

View File

@@ -0,0 +1,31 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.test.storage.crypto;
public class KeyTest {
// As per https://matrix.org/docs/spec/appendices.html#signing-key
public static final String Private = "YJDBA9Xnr2sVqXD9Vj7XVUnmFZcZrlw8Md7kMW+3XA1";
// The corresponding public key, not being documented in the spec
public static final String Public = "XGX0JRS2Af3be3knz2fBiRbApjm2Dh61gXDJA8kcJNI";
}

View File

@@ -0,0 +1,102 @@
/*
* mxisd - Matrix Identity Server Daemon
* Copyright (C) 2019 Kamax Sàrl
*
* 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 <http://www.gnu.org/licenses/>.
*/
package io.kamax.mxisd.test.storage.crypto;
import com.google.gson.JsonObject;
import io.kamax.matrix.json.GsonUtil;
import io.kamax.matrix.json.MatrixJson;
import io.kamax.mxisd.storage.crypto.*;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsEqual.equalTo;
import static org.junit.Assert.assertThat;
public class SignatureManagerTest {
private static SignatureManager signMgr;
private static SignatureManager build(String keySeed) {
Ed25519Key key = new Ed25519Key(new Ed2219RegularKeyIdentifier("0"), keySeed);
KeyStore store = new MemoryKeyStore();
store.add(key);
return new Ed25519SignatureManager(new Ed25519KeyManager(store));
}
@BeforeClass
public static void beforeClass() {
signMgr = build(KeyTest.Private);
}
private void testSign(String value, String sign) {
assertThat(signMgr.sign(value).getSignature(), is(equalTo(sign)));
}
// As per https://matrix.org/docs/spec/appendices.html#json-signing
@Test
public void onEmptyObject() {
String value = "{}";
String sign = "K8280/U9SSy9IVtjBuVeLr+HpOB4BQFWbg+UZaADMtTdGYI7Geitb76LTrr5QV/7Xg4ahLwYGYZzuHGZKM5ZAQ";
testSign(value, sign);
}
// As per https://matrix.org/docs/spec/appendices.html#json-signing
@Test
public void onSimpleObject() {
JsonObject data = new JsonObject();
data.addProperty("one", 1);
data.addProperty("two", "Two");
String value = GsonUtil.get().toJson(data);
String sign = "KqmLSbO39/Bzb0QIYE82zqLwsA+PDzYIpIRA2sRQ4sL53+sN6/fpNSoqE7BP7vBZhG6kYdD13EIMJpvhJI+6Bw";
testSign(value, sign);
}
@Test
public void onFederationHeader() {
SignatureManager mgr = build("1QblgjFeL3IxoY4DKOR7p5mL5sQTC0ChmeMJlqb4d5M");
JsonObject o = new JsonObject();
o.addProperty("method", "GET");
o.addProperty("uri", "/_matrix/federation/v1/query/directory?room_alias=%23a%3Amxhsd.local.kamax.io%3A8447");
o.addProperty("origin", "synapse.local.kamax.io");
o.addProperty("destination", "mxhsd.local.kamax.io:8447");
String signExpected = "SEMGSOJEsoalrBfHqPO2QrSlbLaUYLHLk4e3q4IJ2JbgvCynT1onp7QF1U4Sl3G3NzybrgdnVvpqcaEgV0WPCw";
Signature signProduced = mgr.sign(o);
assertThat(signProduced.getSignature(), is(equalTo(signExpected)));
}
@Test
public void onIdentityLookup() {
String value = MatrixJson.encodeCanonical("{\n" + " \"address\": \"mxisd-federation-test@kamax.io\",\n"
+ " \"medium\": \"email\",\n" + " \"mxid\": \"@mxisd-lookup-test:kamax.io\",\n"
+ " \"not_after\": 253402300799000,\n" + " \"not_before\": 0,\n" + " \"ts\": 1523482030147\n" + "}");
String sign = "ObKA4PNQh2g6c7Yo2QcTcuDgIwhknG7ZfqmNYzbhrbLBOqZomU22xX9raufN2Y3ke1FXsDqsGs7WBDodmzZJCg";
testSign(value, sign);
}
}