First implementation

This commit is contained in:
Maxime Dor
2017-01-23 04:10:03 +01:00
commit 4c5fe95e8e
22 changed files with 1760 additions and 0 deletions

View File

@@ -0,0 +1,33 @@
/*
* 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
import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
@SpringBootApplication
class MatrixIdentityServerApplication {
static void main(String[] args) throws Exception {
SpringApplication.run(MatrixIdentityServerApplication.class, args)
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.config
import org.apache.commons.lang.StringUtils
import org.springframework.beans.factory.InitializingBean
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
@Configuration
@ConfigurationProperties(prefix = "key")
class KeyConfig implements InitializingBean {
private String path
void setPath(String path) {
this.path = path
}
String getPath() {
return path
}
@Override
void afterPropertiesSet() throws Exception {
if (StringUtils.isBlank(getPath())) {
throw new RuntimeException("Key path must be configured!")
}
}
}

View File

@@ -0,0 +1,94 @@
/*
* 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.config
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
@Configuration
@ConfigurationProperties(prefix = "ldap")
class LdapConfig {
private String host
private int port
private String baseDn
private String query
private String attribute
private String bindDn
private String bindPassword
String getHost() {
return host
}
void setHost(String host) {
this.host = host
}
int getPort() {
return port
}
void setPort(int port) {
this.port = port
}
String getBaseDn() {
return baseDn
}
void setBaseDn(String baseDn) {
this.baseDn = baseDn
}
String getQuery() {
return query
}
void setQuery(String query) {
this.query = query
}
String getAttribute() {
return attribute
}
void setAttribute(String attribute) {
this.attribute = attribute
}
String getBindDn() {
return bindDn
}
void setBindDn(String bindDn) {
this.bindDn = bindDn
}
String getBindPassword() {
return bindPassword
}
void setBindPassword(String bindPassword) {
this.bindPassword = bindPassword
}
}

View File

@@ -0,0 +1,49 @@
/*
* 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.config
import org.apache.commons.lang.StringUtils
import org.springframework.beans.factory.InitializingBean
import org.springframework.boot.context.properties.ConfigurationProperties
import org.springframework.context.annotation.Configuration
@Configuration
@ConfigurationProperties(prefix = "server")
class ServerConfig implements InitializingBean {
private String name
String getName() {
return name
}
void setName(String name) {
this.name = name
}
@Override
void afterPropertiesSet() throws Exception {
if (StringUtils.isBlank(getName())) {
throw new RuntimeException("Server name must be configured!")
}
}
}

View File

@@ -0,0 +1,37 @@
/*
* 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.controller
import io.kamax.mxisd.exception.NotImplementedException
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import static org.springframework.web.bind.annotation.RequestMethod.POST
@RestController
class InvitationController {
@RequestMapping(value = "/_matrix/identity/api/v1/store-invite", method = POST)
String store() {
throw new NotImplementedException()
}
}

View File

@@ -0,0 +1,61 @@
/*
* 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.controller
import groovy.json.JsonOutput
import io.kamax.mxisd.exception.BadRequestException
import io.kamax.mxisd.exception.NotImplementedException
import io.kamax.mxisd.key.KeyManager
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.PathVariable
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import static org.springframework.web.bind.annotation.RequestMethod.GET
@RestController
class KeyController {
@Autowired
private KeyManager keyMgr
@RequestMapping(value = "/_matrix/identity/api/v1/pubkey/{keyType}:{keyId}", method = GET)
String getKey(@PathVariable String keyType, @PathVariable int keyId) {
if (!"ed25519".contentEquals(keyType)) {
throw new BadRequestException("Invalid algorithm: " + keyType)
}
return JsonOutput.toJson([
public_key: keyMgr.getPublicKeyBase64(keyId)
])
}
@RequestMapping(value = "/_matrix/identity/api/v1/pubkey/ephemeral/isvalid", method = GET)
String checkEphemeralKeyValidity() {
throw new NotImplementedException()
}
@RequestMapping(value = "/_matrix/identity/api/v1/pubkey/isvalid", method = GET)
String checkKeyValidity() {
throw new NotImplementedException()
}
}

View File

@@ -0,0 +1,89 @@
/*
* 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.controller
import groovy.json.JsonOutput
import io.kamax.mxisd.config.LdapConfig
import io.kamax.mxisd.exception.BadRequestException
import io.kamax.mxisd.signature.SignatureManager
import org.apache.directory.api.ldap.model.cursor.EntryCursor
import org.apache.directory.api.ldap.model.entry.Attribute
import org.apache.directory.api.ldap.model.message.SearchScope
import org.apache.directory.ldap.client.api.LdapConnection
import org.apache.directory.ldap.client.api.LdapNetworkConnection
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.bind.annotation.RestController
import static org.springframework.web.bind.annotation.RequestMethod.GET
@RestController
class MappingController {
@Autowired
private LdapConfig ldapCfg
@Autowired
private SignatureManager signMgr
@RequestMapping(value = "/_matrix/identity/api/v1/lookup", method = GET)
String lookup(@RequestParam String medium, @RequestParam String address) {
if (!"email".contentEquals(medium)) {
throw new BadRequestException("Invalid medium type")
}
LdapConnection conn = new LdapNetworkConnection(ldapCfg.getHost(), ldapCfg.getPort())
try {
conn.bind(ldapCfg.getBindDn(), ldapCfg.getBindPassword())
String searchQuery = ldapCfg.getQuery().replaceAll("%3pid", address)
EntryCursor cursor = conn.search(ldapCfg.getBaseDn(), searchQuery, SearchScope.SUBTREE, ldapCfg.getAttribute())
try {
if (!cursor.next()) {
return JsonOutput.toJson([])
}
Attribute attribute = cursor.get().get(ldapCfg.getAttribute())
if (attribute == null) {
return JsonOutput.toJson([])
}
def data = new LinkedHashMap([
address : address,
medium : medium,
mxid : attribute.get().toString(),
not_before: 0,
not_after : 9223372036854775807,
ts : 0
])
data['signatures'] = signMgr.signMessage(JsonOutput.toJson(data))
return JsonOutput.toJson(data)
} finally {
cursor.close()
}
} finally {
conn.close()
}
}
}

View File

@@ -0,0 +1,53 @@
/*
* 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.controller
import io.kamax.mxisd.exception.NotImplementedException
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import static org.springframework.web.bind.annotation.RequestMethod.GET
import static org.springframework.web.bind.annotation.RequestMethod.POST
@RestController
class SessionController {
@RequestMapping(value = "/_matrix/identity/api/v1/validate/email/requestToken", method = POST)
String init() {
throw new NotImplementedException()
}
@RequestMapping(value = "/_matrix/identity/api/v1/validate/email/submitToken", method = [GET, POST])
String validate() {
throw new NotImplementedException()
}
@RequestMapping(value = "/_matrix/identity/api/v1/3pid/getValidated3pid", method = POST)
String check() {
throw new NotImplementedException()
}
@RequestMapping(value = "/_matrix/identity/api/v1/3pid/bind", method = POST)
String bind() {
throw new NotImplementedException()
}
}

View File

@@ -0,0 +1,33 @@
/*
* 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.exception
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus
@ResponseStatus(value = HttpStatus.BAD_REQUEST)
class BadRequestException extends RuntimeException {
BadRequestException(String s) {
super(s)
}
}

View File

@@ -0,0 +1,28 @@
/*
* 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.exception
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ResponseStatus
@ResponseStatus(value = HttpStatus.NOT_IMPLEMENTED)
class NotImplementedException extends RuntimeException {
}

View File

@@ -0,0 +1,107 @@
/*
* 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.InitializingBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
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.PrivateKey
import java.security.PublicKey
@Component
class KeyManager implements InitializingBean {
@Autowired
private KeyConfig keyCfg
private EdDSAParameterSpec keySpecs
private EdDSAEngine signEngine
private List<KeyPair> keys
@Override
void afterPropertiesSet() throws Exception {
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)))
}
}
}
int getCurrentIndex() {
return 0
}
KeyPair getKeys(int index) {
return keys.get(index)
}
PrivateKey getPrivateKey(int index) {
return getKeys(index).getPrivate()
}
PublicKey getPublicKey(int index) {
return getKeys(index).getPublic()
}
EdDSAParameterSpec getSpecs() {
return keySpecs
}
String getPublicKeyBase64(int index) {
return Base64.getEncoder().encodeToString(getPublicKey(index).getEncoded())
}
}

View File

@@ -0,0 +1,59 @@
/*
* 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 io.kamax.mxisd.config.ServerConfig
import io.kamax.mxisd.key.KeyManager
import net.i2p.crypto.eddsa.EdDSAEngine
import org.springframework.beans.factory.InitializingBean
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import java.security.MessageDigest
@Component
class SignatureManager implements InitializingBean {
@Autowired
private KeyManager keyMgr
@Autowired
private ServerConfig srvCfg
private EdDSAEngine signEngine
Map<?, ?> signMessage(String message) {
byte[] signRaw = signEngine.signOneShot(message.getBytes())
String sign = Base64.getEncoder().encodeToString(signRaw)
return [
"${srvCfg.getName()}": [
"ed25519:${keyMgr.getCurrentIndex()}": sign
]
]
}
@Override
void afterPropertiesSet() throws Exception {
signEngine = new EdDSAEngine(MessageDigest.getInstance(keyMgr.getSpecs().getHashAlgorithm()))
signEngine.initSign(keyMgr.getPrivateKey(keyMgr.getCurrentIndex()))
}
}

View File

@@ -0,0 +1,11 @@
[Unit]
Description=mxisd
After=syslog.target
[Service]
User=mxisd
ExecStart=/usr/bin/mxisd --spring.config.location=/etc/mxis/ --spring.config.name=mxisd
SuccessExitStatus=143
[Install]
WantedBy=multi-user.target