diff --git a/build.gradle b/build.gradle index 428719d..5b7b2f6 100644 --- a/build.gradle +++ b/build.gradle @@ -66,6 +66,7 @@ buildscript { repositories { maven { url "https://kamax.io/maven/releases/" } + maven { url "https://kamax.io/maven/snapshots/" } mavenCentral() } @@ -80,7 +81,7 @@ dependencies { compile "org.springframework.boot:spring-boot-starter-thymeleaf:1.5.10.RELEASE" // Matrix Java SDK - compile 'io.kamax:matrix-java-sdk:0.0.8' + compile 'io.kamax:matrix-java-sdk:0.0.11' // ed25519 handling compile 'net.i2p.crypto:eddsa:0.1.0' @@ -94,9 +95,6 @@ dependencies { // HTTP connections compile 'org.apache.httpcomponents:httpclient:4.5.3' - // JSON - compile 'com.google.code.gson:gson:2.8.1' - // Phone numbers validation compile 'com.googlecode.libphonenumber:libphonenumber:8.7.1' diff --git a/src/main/java/io/kamax/mxisd/controller/identity/v1/InvitationController.java b/src/main/java/io/kamax/mxisd/controller/identity/v1/InvitationController.java index 697f805..a6fd904 100644 --- a/src/main/java/io/kamax/mxisd/controller/identity/v1/InvitationController.java +++ b/src/main/java/io/kamax/mxisd/controller/identity/v1/InvitationController.java @@ -22,13 +22,13 @@ package io.kamax.mxisd.controller.identity.v1; import com.google.gson.Gson; import io.kamax.matrix.MatrixID; +import io.kamax.matrix.crypto.KeyManager; import io.kamax.mxisd.config.ServerConfig; import io.kamax.mxisd.controller.identity.v1.io.ThreePidInviteReplyIO; import io.kamax.mxisd.invitation.IThreePidInvite; import io.kamax.mxisd.invitation.IThreePidInviteReply; import io.kamax.mxisd.invitation.InvitationManager; import io.kamax.mxisd.invitation.ThreePidInvite; -import io.kamax.mxisd.key.KeyManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; diff --git a/src/main/java/io/kamax/mxisd/controller/identity/v1/KeyController.java b/src/main/java/io/kamax/mxisd/controller/identity/v1/KeyController.java index cd6575f..49be3ef 100644 --- a/src/main/java/io/kamax/mxisd/controller/identity/v1/KeyController.java +++ b/src/main/java/io/kamax/mxisd/controller/identity/v1/KeyController.java @@ -22,9 +22,9 @@ package io.kamax.mxisd.controller.identity.v1; import com.google.gson.Gson; import com.google.gson.JsonObject; +import io.kamax.matrix.crypto.KeyManager; import io.kamax.mxisd.controller.identity.v1.io.KeyValidityJson; import io.kamax.mxisd.exception.BadRequestException; -import io.kamax.mxisd.key.KeyManager; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -64,7 +64,7 @@ public class KeyController { @RequestMapping(value = "/pubkey/ephemeral/isvalid", method = GET) public String checkEphemeralKeyValidity(HttpServletRequest request) { - log.warn("Ephemeral key was request but no ephemeral key are generated, replying not valid"); + log.warn("Ephemeral key was requested but no ephemeral key are generated, replying not valid"); return invalidKey; } diff --git a/src/main/java/io/kamax/mxisd/controller/identity/v1/MappingController.java b/src/main/java/io/kamax/mxisd/controller/identity/v1/MappingController.java index 32b7c6a..ce019b5 100644 --- a/src/main/java/io/kamax/mxisd/controller/identity/v1/MappingController.java +++ b/src/main/java/io/kamax/mxisd/controller/identity/v1/MappingController.java @@ -22,11 +22,14 @@ package io.kamax.mxisd.controller.identity.v1; import com.google.gson.Gson; import com.google.gson.JsonObject; +import io.kamax.matrix.crypto.SignatureManager; +import io.kamax.matrix.event.EventKey; +import io.kamax.matrix.json.MatrixJson; +import io.kamax.mxisd.config.MatrixConfig; import io.kamax.mxisd.controller.identity.v1.io.SingeLookupReplyJson; import io.kamax.mxisd.exception.InternalServerError; import io.kamax.mxisd.lookup.*; import io.kamax.mxisd.lookup.strategy.LookupStrategy; -import io.kamax.mxisd.signature.SignatureManager; import io.kamax.mxisd.util.GsonParser; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; @@ -57,6 +60,9 @@ public class MappingController { private Gson gson = new Gson(); private GsonParser parser = new GsonParser(gson); + @Autowired + private MatrixConfig mxCfg; + @Autowired private LookupStrategy strategy; @@ -92,16 +98,12 @@ public class MappingController { } SingleLookupReply lookup = lookupOpt.get(); - if (lookup.isSigned()) { - log.info("Lookup is already signed, sending as-is"); - return lookup.getBody(); - } else { - log.info("Lookup is not signed, signing"); - JsonObject obj = gson.toJsonTree(new SingeLookupReplyJson(lookup)).getAsJsonObject(); - obj.add("signatures", signMgr.signMessageGson(gson.toJson(obj))); - return gson.toJson(obj); - } + // FIXME signing should be done in the business model, not in the controller + JsonObject obj = gson.toJsonTree(new SingeLookupReplyJson(lookup)).getAsJsonObject(); + obj.add(EventKey.Signatures.get(), signMgr.signMessageGson(MatrixJson.encodeCanonical(obj))); + + return gson.toJson(obj); } @RequestMapping(value = "/bulk_lookup", method = POST) diff --git a/src/main/java/io/kamax/mxisd/controller/identity/v1/io/SingeLookupReplyJson.java b/src/main/java/io/kamax/mxisd/controller/identity/v1/io/SingeLookupReplyJson.java index d06528a..4418c45 100644 --- a/src/main/java/io/kamax/mxisd/controller/identity/v1/io/SingeLookupReplyJson.java +++ b/src/main/java/io/kamax/mxisd/controller/identity/v1/io/SingeLookupReplyJson.java @@ -22,9 +22,6 @@ package io.kamax.mxisd.controller.identity.v1.io; import io.kamax.mxisd.lookup.SingleLookupReply; -import java.util.HashMap; -import java.util.Map; - public class SingeLookupReplyJson { private String address; @@ -33,7 +30,6 @@ public class SingeLookupReplyJson { private long not_after; private long not_before; private long ts; - private Map> signatures = new HashMap<>(); public SingeLookupReplyJson(SingleLookupReply reply) { this.address = reply.getRequest().getThreePid(); @@ -68,8 +64,4 @@ public class SingeLookupReplyJson { return ts; } - public boolean isSigned() { - return signatures != null && !signatures.isEmpty(); - } - } diff --git a/src/main/java/io/kamax/mxisd/invitation/InvitationManager.java b/src/main/java/io/kamax/mxisd/invitation/InvitationManager.java index b3d6412..6757c90 100644 --- a/src/main/java/io/kamax/mxisd/invitation/InvitationManager.java +++ b/src/main/java/io/kamax/mxisd/invitation/InvitationManager.java @@ -24,6 +24,7 @@ import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import io.kamax.matrix.MatrixID; +import io.kamax.matrix.crypto.SignatureManager; import io.kamax.mxisd.config.InvitationConfig; import io.kamax.mxisd.dns.FederationDnsOverwrite; import io.kamax.mxisd.exception.BadRequestException; @@ -32,7 +33,6 @@ 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.signature.SignatureManager; import io.kamax.mxisd.storage.IStorage; import io.kamax.mxisd.storage.ormlite.ThreePidInviteIO; import org.apache.commons.io.IOUtils; diff --git a/src/main/java/io/kamax/mxisd/key/KeyManager.java b/src/main/java/io/kamax/mxisd/key/KeyManager.java deleted file mode 100644 index bb72a46..0000000 --- a/src/main/java/io/kamax/mxisd/key/KeyManager.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * mxisd - Matrix Identity Server Daemon - * Copyright (C) 2017 Maxime Dor - * - * https://max.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.key; - -import io.kamax.mxisd.config.KeyConfig; -import net.i2p.crypto.eddsa.EdDSAEngine; -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.io.FileUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.KeyPair; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.util.ArrayList; -import java.util.Base64; -import java.util.List; - -@Component -public class KeyManager { - - @Autowired - private KeyConfig keyCfg; - - private EdDSAParameterSpec keySpecs; - private EdDSAEngine signEngine; - private List keys; - - @PostConstruct - public void build() { - try { - keySpecs = EdDSANamedCurveTable.getByName(EdDSANamedCurveTable.CURVE_ED25519_SHA512); - signEngine = new EdDSAEngine(MessageDigest.getInstance(keySpecs.getHashAlgorithm())); - keys = new ArrayList<>(); - - Path privKey = Paths.get(keyCfg.getPath()); - - if (!Files.exists(privKey)) { - KeyPair pair = (new KeyPairGenerator()).generateKeyPair(); - String keyEncoded = Base64.getEncoder().encodeToString(pair.getPrivate().getEncoded()); - FileUtils.writeStringToFile(privKey.toFile(), keyEncoded, StandardCharsets.ISO_8859_1); - keys.add(pair); - } else { - if (Files.isDirectory(privKey)) { - throw new RuntimeException("Invalid path for private key: " + privKey.toString()); - } - - if (Files.isReadable(privKey)) { - byte[] seed = Base64.getDecoder().decode(FileUtils.readFileToString(privKey.toFile(), StandardCharsets.ISO_8859_1)); - EdDSAPrivateKeySpec privKeySpec = new EdDSAPrivateKeySpec(seed, keySpecs); - EdDSAPublicKeySpec pubKeySpec = new EdDSAPublicKeySpec(privKeySpec.getA(), keySpecs); - keys.add(new KeyPair(new EdDSAPublicKey(pubKeySpec), new EdDSAPrivateKey(privKeySpec))); - } - } - } catch (NoSuchAlgorithmException | IOException e) { - throw new RuntimeException(e); - } - } - - public int getCurrentIndex() { - return 0; - } - - public KeyPair getKeys(int index) { - return keys.get(index); - } - - public PrivateKey getPrivateKey(int index) { - return getKeys(index).getPrivate(); - } - - public EdDSAPublicKey getPublicKey(int index) { - return (EdDSAPublicKey) getKeys(index).getPublic(); - } - - public EdDSAParameterSpec getSpecs() { - return keySpecs; - } - - public String getPublicKeyBase64(int index) { - return Base64.getEncoder().encodeToString(getPublicKey(index).getAbyte()); - } - -} diff --git a/src/main/java/io/kamax/mxisd/lookup/SingleLookupReply.java b/src/main/java/io/kamax/mxisd/lookup/SingleLookupReply.java index 710c236..c1e15ac 100644 --- a/src/main/java/io/kamax/mxisd/lookup/SingleLookupReply.java +++ b/src/main/java/io/kamax/mxisd/lookup/SingleLookupReply.java @@ -33,7 +33,6 @@ public class SingleLookupReply { private static Gson gson = new Gson(); private boolean isRecursive; - private boolean isSigned; private String body; private SingleLookupRequest request; private _MatrixID mxid; @@ -53,7 +52,6 @@ public class SingleLookupReply { reply.notAfter = Instant.ofEpochMilli(json.getNot_after()); reply.notBefore = Instant.ofEpochMilli(json.getNot_before()); reply.timestamp = Instant.ofEpochMilli(json.getTs()); - reply.isSigned = json.isSigned(); } catch (JsonSyntaxException e) { // stub - we only want to try, nothing more } @@ -85,10 +83,6 @@ public class SingleLookupReply { return isRecursive; } - public boolean isSigned() { - return isSigned; - } - public String getBody() { return body; } diff --git a/src/main/java/io/kamax/mxisd/signature/SignatureManager.java b/src/main/java/io/kamax/mxisd/signature/SignatureManager.java deleted file mode 100644 index 6c45803..0000000 --- a/src/main/java/io/kamax/mxisd/signature/SignatureManager.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * mxisd - Matrix Identity Server Daemon - * Copyright (C) 2017 Maxime Dor - * - * https://max.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.signature; - -import com.google.gson.JsonObject; -import io.kamax.mxisd.config.ServerConfig; -import io.kamax.mxisd.exception.InternalServerError; -import io.kamax.mxisd.key.KeyManager; -import net.i2p.crypto.eddsa.EdDSAEngine; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.SignatureException; -import java.util.Base64; - -@Component -public class SignatureManager { - - @Autowired - private KeyManager keyMgr; - - @Autowired - private ServerConfig srvCfg; - - private EdDSAEngine signEngine; - - private String sign(String message) { - try { - byte[] signRaw = signEngine.signOneShot(message.getBytes()); - return Base64.getEncoder().encodeToString(signRaw); - } catch (SignatureException e) { - throw new InternalServerError(e); - } - } - - public JsonObject signMessageGson(String message) { - String sign = sign(message); - - JsonObject keySignature = new JsonObject(); - keySignature.addProperty("ed25519:" + keyMgr.getCurrentIndex(), sign); - JsonObject signature = new JsonObject(); - signature.add(srvCfg.getName(), keySignature); - - return signature; - } - - @PostConstruct - public void build() { - try { - signEngine = new EdDSAEngine(MessageDigest.getInstance(keyMgr.getSpecs().getHashAlgorithm())); - signEngine.initSign(keyMgr.getPrivateKey(keyMgr.getCurrentIndex())); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/src/main/java/io/kamax/mxisd/spring/CryptoFactory.java b/src/main/java/io/kamax/mxisd/spring/CryptoFactory.java new file mode 100644 index 0000000..5d408dc --- /dev/null +++ b/src/main/java/io/kamax/mxisd/spring/CryptoFactory.java @@ -0,0 +1,57 @@ +/* + * mxisd - Matrix Identity Server Daemon + * Copyright (C) 2018 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.spring; + +import io.kamax.matrix.crypto.KeyFileStore; +import io.kamax.matrix.crypto.KeyManager; +import io.kamax.matrix.crypto.SignatureManager; +import io.kamax.mxisd.config.KeyConfig; +import io.kamax.mxisd.config.MatrixConfig; +import org.apache.commons.io.FileUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.io.File; +import java.io.IOException; + +@Configuration +public class CryptoFactory { + + @Bean + public KeyManager getKeyManager(KeyConfig keyCfg) { + File keyStore = new File(keyCfg.getPath()); + if (!keyStore.exists()) { + try { + FileUtils.touch(keyStore); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + return new KeyManager(new KeyFileStore(keyCfg.getPath())); + } + + @Bean + public SignatureManager getSignatureManager(KeyManager keyMgr, MatrixConfig mxCfg) { + return new SignatureManager(keyMgr, mxCfg.getDomain()); + } + +}