Fix crypto

A recent change in synapse shown that the various classes handling crypto were broken.
All the crypto code has been refactored in the SDK and the local code has been adapted.
This commit is contained in:
Max Dor
2018-04-11 23:37:33 +02:00
parent a0f8af820e
commit 1e413af019
10 changed files with 75 additions and 226 deletions

View File

@@ -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'

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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)

View File

@@ -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<String, Map<String, String>> 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();
}
}

View File

@@ -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;

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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<KeyPair> 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());
}
}

View File

@@ -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;
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
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());
}
}