Improve crypto
- Re-organize packages to be consistent - Add Key store tests
This commit is contained in:
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* 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.CURVE_ED25519_SHA512);
|
||||
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(Base64.decodeBase64(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));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,6 +20,8 @@
|
||||
|
||||
package io.kamax.mxisd.storage.crypto;
|
||||
|
||||
import io.kamax.mxisd.crypto.Key;
|
||||
|
||||
public class FileKeyJson {
|
||||
|
||||
public static FileKeyJson get(Key key) {
|
||||
|
||||
@@ -23,6 +23,7 @@ package io.kamax.mxisd.storage.crypto;
|
||||
import com.google.gson.JsonObject;
|
||||
import io.kamax.matrix.crypto.KeyFileStore;
|
||||
import io.kamax.matrix.json.GsonUtil;
|
||||
import io.kamax.mxisd.crypto.*;
|
||||
import io.kamax.mxisd.exception.ObjectNotFoundException;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
@@ -54,31 +55,39 @@ public class FileKeyStore implements KeyStore {
|
||||
base = new File(path).getAbsoluteFile().toString();
|
||||
File f = new File(base);
|
||||
|
||||
if (f.exists() && f.isFile()) {
|
||||
if (!f.exists()) {
|
||||
try {
|
||||
log.info("Found old key store format at {}, migrating...", base);
|
||||
File oldStorePath = new File(f.toString() + ".backup-before-migration");
|
||||
FileUtils.moveFile(f, oldStorePath);
|
||||
FileUtils.forceMkdir(f);
|
||||
|
||||
|
||||
String privKey = new KeyFileStore(oldStorePath.toString()).load().orElse("");
|
||||
if (StringUtils.isBlank(privKey)) {
|
||||
log.info("Empty file, nothing to migrate");
|
||||
} else {
|
||||
// We ensure this is valid Base64 data before migrating
|
||||
Base64.decodeBase64(privKey);
|
||||
|
||||
// We store the new key
|
||||
add(new GenericKey(new GenericKeyIdentifier(KeyType.Regular, KeyAlgorithm.Ed25519, "0"), true, privKey));
|
||||
|
||||
log.info("Store migrated to new directory format");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to migrate store from old single file format to new directory format", e);
|
||||
throw new RuntimeException("Unable to create key store");
|
||||
}
|
||||
} else {
|
||||
log.info("Key store is already in directory format");
|
||||
if (f.isFile()) {
|
||||
try {
|
||||
log.info("Found old key store format at {}, migrating...", base);
|
||||
File oldStorePath = new File(f.toString() + ".backup-before-migration");
|
||||
FileUtils.moveFile(f, oldStorePath);
|
||||
FileUtils.forceMkdir(f);
|
||||
|
||||
|
||||
String privKey = new KeyFileStore(oldStorePath.toString()).load().orElse("");
|
||||
if (StringUtils.isBlank(privKey)) {
|
||||
log.info("Empty file, nothing to migrate");
|
||||
} else {
|
||||
// We ensure this is valid Base64 data before migrating
|
||||
Base64.decodeBase64(privKey);
|
||||
|
||||
// We store the new key
|
||||
add(new GenericKey(new GenericKeyIdentifier(KeyType.Regular, KeyAlgorithm.Ed25519, "0"), true, privKey));
|
||||
|
||||
log.info("Store migrated to new directory format");
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to migrate store from old single file format to new directory format", e);
|
||||
}
|
||||
} else {
|
||||
log.info("Key store is already in directory format");
|
||||
}
|
||||
}
|
||||
|
||||
if (!f.isDirectory()) {
|
||||
@@ -207,6 +216,10 @@ public class FileKeyStore implements KeyStore {
|
||||
|
||||
@Override
|
||||
public void setCurrentKey(KeyIdentifier id) throws IllegalArgumentException {
|
||||
if (!has(id)) {
|
||||
throw new IllegalArgumentException("Key " + id.getType() + ":" + id.getAlgorithm() + ":" + id.getSerial() + " is not known to the store");
|
||||
}
|
||||
|
||||
JsonObject json = new JsonObject();
|
||||
json.addProperty("type", id.getType().name());
|
||||
json.addProperty("algo", id.getAlgorithm());
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
/*
|
||||
* 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";
|
||||
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
|
||||
default String getId() {
|
||||
return getAlgorithm().toLowerCase() + ":" + getSerial();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
@@ -20,6 +20,9 @@
|
||||
|
||||
package io.kamax.mxisd.storage.crypto;
|
||||
|
||||
import io.kamax.mxisd.crypto.Key;
|
||||
import io.kamax.mxisd.crypto.KeyIdentifier;
|
||||
import io.kamax.mxisd.crypto.KeyType;
|
||||
import io.kamax.mxisd.exception.ObjectNotFoundException;
|
||||
|
||||
import java.util.List;
|
||||
@@ -84,9 +87,9 @@ public interface KeyStore {
|
||||
* 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
|
||||
* @throws IllegalArgumentException If the key is not known to the store
|
||||
*/
|
||||
void setCurrentKey(KeyIdentifier id) throws ObjectNotFoundException;
|
||||
void setCurrentKey(KeyIdentifier id) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Retrieve the previously stored information of which key is the current signing key, if any
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
}
|
||||
@@ -20,18 +20,18 @@
|
||||
|
||||
package io.kamax.mxisd.storage.crypto;
|
||||
|
||||
import io.kamax.mxisd.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 Map<KeyType, Map<String, Map<String, FileKeyJson>>> keys = new ConcurrentHashMap<>();
|
||||
private KeyIdentifier current;
|
||||
|
||||
private Map<String, String> getMap(KeyType type, String algo) {
|
||||
private Map<String, FileKeyJson> getMap(KeyType type, String algo) {
|
||||
return keys.computeIfAbsent(type, k -> new ConcurrentHashMap<>()).computeIfAbsent(algo, k -> new ConcurrentHashMap<>());
|
||||
}
|
||||
|
||||
@@ -56,23 +56,23 @@ public class MemoryKeyStore implements KeyStore {
|
||||
|
||||
@Override
|
||||
public Key get(KeyIdentifier id) throws ObjectNotFoundException {
|
||||
String data = getMap(id.getType(), id.getAlgorithm()).get(id.getSerial());
|
||||
FileKeyJson 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);
|
||||
return new GenericKey(new GenericKeyIdentifier(id), data.isValid(), data.getKey());
|
||||
}
|
||||
|
||||
private void set(Key key) {
|
||||
String data = key.isValid() ? key.getPrivateKeyBase64() : "";
|
||||
FileKeyJson data = FileKeyJson.get(key);
|
||||
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();
|
||||
throw new IllegalStateException("Key " + key.getId().getId() + " already exists");
|
||||
}
|
||||
|
||||
set(key);
|
||||
@@ -89,13 +89,17 @@ public class MemoryKeyStore implements KeyStore {
|
||||
|
||||
@Override
|
||||
public void delete(KeyIdentifier id) throws ObjectNotFoundException {
|
||||
if (!has(id)) {
|
||||
throw new ObjectNotFoundException("Key", id.getType() + ":" + id.getAlgorithm() + ":" + id.getSerial());
|
||||
}
|
||||
|
||||
keys.computeIfAbsent(id.getType(), k -> new ConcurrentHashMap<>()).computeIfAbsent(id.getAlgorithm(), k -> new ConcurrentHashMap<>()).remove(id.getSerial());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setCurrentKey(KeyIdentifier id) throws ObjectNotFoundException {
|
||||
public void setCurrentKey(KeyIdentifier id) throws IllegalArgumentException {
|
||||
if (!has(id)) {
|
||||
throw new ObjectNotFoundException("Key", id.getType() + ":" + id.getAlgorithm() + ":" + id.getSerial());
|
||||
throw new IllegalArgumentException("Key " + id.getType() + ":" + id.getAlgorithm() + ":" + id.getSerial() + " is not known to the store");
|
||||
}
|
||||
|
||||
current = id;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
/*
|
||||
* 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();
|
||||
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user