Skip to content

Commit

Permalink
Merge pull request #4 from methics/cleanup
Browse files Browse the repository at this point in the history
Cleanup
  • Loading branch information
Hemuu authored Apr 4, 2024
2 parents b3abd83 + 26d7417 commit 5bc5c97
Show file tree
Hide file tree
Showing 37 changed files with 644 additions and 222 deletions.
5 changes: 5 additions & 0 deletions conf/musaplink.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ CREATE TABLE transactions (
CREATE TABLE coupling_codes (
couplingcode TEXT,
linkid TEXT,
created_dt TIMESTAMP
PRIMARY KEY (couplingcode)
);

Expand Down Expand Up @@ -39,5 +40,9 @@ CREATE TABLE key_details (
musapid TEXT,
keyid TEXT,
keyname TEXT,
publickey BYTEA,
certificate BYTEA,
created_dt TIMESTAMP,
modified_dt TIMESTAMP,
PRIMARY KEY (musapid, keyid)
);
Binary file not shown.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
<artifactId>laverca-rest</artifactId>
<version>1.2.0</version>
<scope>system</scope>
<systemPath>${project.basedir}/libs/laverca-rest-1.2.0.jar</systemPath>
<systemPath>${project.basedir}/libs/laverca-rest-1.4.0.jar</systemPath>
</dependency>
<dependency>
<groupId>com.eatthepath</groupId>
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/fi/methics/webapp/musaplink/MusapLinkAccount.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import fi.methics.webapp.musaplink.coupling.json.ExternalSignatureResp;
import fi.methics.webapp.musaplink.link.json.MusapSignResp;
import fi.methics.webapp.musaplink.util.MusapTransportEncryption.TransportKeys;

/**
* Class representing a single account in MUSAP Link
Expand All @@ -28,6 +29,16 @@ public MusapLinkAccount() {

}

/**
* Get transport keys
* @return transport keys - or null if not available
*/
public TransportKeys getTransportKeys() {
if (this.aesKey == null) return null;
if (this.macKey == null) return null;
return new TransportKeys(this.musapid, this.aesKey, this.macKey);
}

@Override
public String toString() {
return musapid;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public <T extends CouplingApiPayload> T getRequestPayload() {
* @return Transport Encryption handler
*/
public MusapTransportEncryption getTransportEncryption() {
return null;
return new MusapTransportEncryption(getConfig());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,7 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.google.gson.Gson;

import fi.methics.webapp.musaplink.MusapLinkAccount;
import fi.methics.webapp.musaplink.coupling.cmd.CmdEnrollData;
import fi.methics.webapp.musaplink.coupling.cmd.CmdExternalSignature;
import fi.methics.webapp.musaplink.coupling.cmd.CmdGenerateKeyCallback;
Expand All @@ -20,6 +19,8 @@
import fi.methics.webapp.musaplink.link.json.MusapResp;
import fi.methics.webapp.musaplink.util.MusapException;
import fi.methics.webapp.musaplink.util.MusapLinkConf;
import fi.methics.webapp.musaplink.util.MusapTransportEncryption;
import fi.methics.webapp.musaplink.util.db.AccountStorage;

/**
* Servlet for communication between MUSAP and MUSAP Link.
Expand All @@ -30,16 +31,16 @@ public class MusapCouplingServlet {

private static final Log log = LogFactory.getLog(MusapCouplingServlet.class);

private static final Gson GSON = new Gson();

private static MusapLinkConf conf;
private static MusapTransportEncryption enc;

/**
* Initialize the servlet
*/
public static void init() {
// Read conf first
conf = MusapLinkConf.getInstance();
enc = new MusapTransportEncryption(conf);
if (conf == null || !conf.isInitialized()) {
log.fatal("Cannot read configuration file " + conf.getConfFilePath());
System.out.println("Cannot read configuration file " + conf.getConfFilePath());
Expand All @@ -52,16 +53,52 @@ public static void init() {
@Path("/musap")
public Response musapEndpoint(String body) {

CouplingApiMessage jReq = GSON.fromJson(body, CouplingApiMessage.class);
CouplingApiMessage jReq = CouplingApiMessage.fromJson(body);
CouplingApiMessage jResp = null;

if (jReq == null) {
log.debug("No request body");
return MusapResp.createErrorResponse(MusapResp.ERROR_WRONG_PARAM);
return MusapResp.createErrorResponse(MusapResp.ERROR_WRONG_PARAM, "Missing request body");
}

MusapLinkAccount account = null;

boolean isEncrypted = jReq.isEncrypted();
boolean shouldDecrypt = MusapTransportEncryption.shouldDecrypt(jReq);
boolean encryptRequired = conf.isTransportEncryptionRequired();

if (!isEncrypted && shouldDecrypt && encryptRequired) {
// Request is encrypted even when it should be
return MusapResp.createErrorResponse(MusapResp.ERROR_WRONG_PARAM, "Missing transport encryption");
}
log.debug("Request Payload: " + jReq.getPayloadJson());

try {
if (isEncrypted) {
// Fetch transport encryption keys and decrypt if needed
account = AccountStorage.findAccountByMusapId(jReq.musapid);

if (account == null) {
log.debug("Could not find account with MUSAP ID " + jReq.musapid);
return MusapResp.createErrorResponse(MusapResp.ERROR_WRONG_PARAM, "Failed to decrypt the request: Could not find account with MUSAP ID " + jReq.musapid);
}
if (account.getTransportKeys() == null) {
log.debug("Could not find transport encryption key");
return MusapResp.createErrorResponse(MusapResp.ERROR_WRONG_PARAM, "Failed to decrypt the request: Missing transport key");
}
try {
enc.decrypt(jReq, account.getTransportKeys());
} catch (Exception e) {
log.error("Failed to decrypt message", e);
return MusapResp.createErrorResponse(MusapResp.ERROR_WRONG_PARAM, "Failed to decrypt the request: " + e.getMessage());
}
if (!enc.isNonceValid(jReq)) {
log.error("NONCE check failed. Returning error.");
return MusapResp.createErrorResponse(MusapResp.ERROR_INTERNAL, "Replay-attack detection failed. Invalid NONCE.");
}
}

log.debug("Request Payload: " + jReq.getPayloadJson());

switch (jReq.type) {
case CouplingApiMessage.TYPE_ENROLLDATA: {
log.debug("Enrolling data");
Expand Down Expand Up @@ -104,23 +141,39 @@ public Response musapEndpoint(String body) {
}
default: {
log.debug("Unknown request type " + jReq.type);
return MusapResp.createErrorResponse(MusapResp.ERROR_WRONG_PARAM);
return MusapResp.createErrorResponse(MusapResp.ERROR_WRONG_PARAM, "Unknown request type " + jReq.type);
}
}
} catch (MusapException e) {
log.error(jReq.type + " failed", e);
throw e;
} catch (Exception e) {
log.error(jReq.type + " failed", e);
return MusapResp.createErrorResponse(MusapResp.ERROR_INTERNAL);
return MusapResp.createErrorResponse(MusapResp.ERROR_INTERNAL, e.getMessage());
}

if (jResp == null) {
log.debug("Returning empty response");
return Response.ok().build();
} else {
log.debug("Response Payload: " + jReq.getPayloadJson());
return Response.ok(GSON.toJson(jResp)).build();

try {
if (MusapTransportEncryption.shouldEncrypt(jResp)) {
if (account == null) {
account = AccountStorage.findAccountByLinkId(body);
}
if (account != null) {
enc.encrypt(jResp, account.getTransportKeys());
}
}
} catch (Exception e) {
log.error("Failed to encrypt the response", e);
return MusapResp.createErrorResponse(MusapResp.ERROR_WRONG_PARAM, "Failed to encrypt the response: " + e.getMessage());
}

return Response.ok(jResp.toJson()).build();
}
}

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
package fi.methics.webapp.musaplink.coupling.cmd;

import java.io.IOException;
import java.util.Base64;
import java.util.UUID;

import fi.methics.webapp.musaplink.AccountStorage;
import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.generators.HKDFBytesGenerator;
import org.bouncycastle.crypto.params.HKDFParameters;

import fi.methics.webapp.musaplink.MusapLinkAccount;
import fi.methics.webapp.musaplink.coupling.CouplingCommand;
import fi.methics.webapp.musaplink.coupling.json.CouplingApiMessage;
import fi.methics.webapp.musaplink.coupling.json.EnrollDataReq;
import fi.methics.webapp.musaplink.coupling.json.EnrollDataResp;
import fi.methics.webapp.musaplink.coupling.json.CouplingApiMessage;
import fi.methics.webapp.musaplink.link.json.MusapResp;
import fi.methics.webapp.musaplink.util.MusapException;
import fi.methics.webapp.musaplink.util.MusapTransportEncryption.TransportKeys;
import fi.methics.webapp.musaplink.util.db.AccountStorage;

/**
* Coupling API command for enrolling MUSAP to this MUSAP Link.
Expand All @@ -33,14 +41,57 @@ public CouplingApiMessage execute() throws IOException {
account.apnsToken = payload.apnstoken;
account.fcmToken = payload.fcmtoken;
account.musapid = UUID.randomUUID().toString();

String sharedSecret = payload.getSharedSecret();
if (sharedSecret != null && sharedSecret.length() > 0) {
byte[] ss = Base64.getDecoder().decode(sharedSecret);
byte[][] keypair_mac_aes = this.deriveKeys(ss);
account.macKey = keypair_mac_aes[0];
account.aesKey = keypair_mac_aes[1];
} else {
if (this.getConfig().isTransportEncryptionRequired()) {
log.error("Transport encryption is required, but client did not provide a secret");
throw new MusapException(MusapResp.ERROR_WRONG_PARAM, "Missing transport security secret");
} else {
log.info("Skipping transport encryption as client did not provide a secret");
}
}

log.debug("Storing account with MusapID " + account.musapid);
AccountStorage.storeAccount(account);

EnrollDataResp resp = new EnrollDataResp();
resp.musapid = account.musapid;
EnrollDataResp respPayload = new EnrollDataResp();
respPayload.musapid = account.musapid;

return req.createResponse(resp);
CouplingApiMessage resp = req.createResponse(respPayload);
TransportKeys keys = account.getTransportKeys();

resp.musapid = account.musapid;
if (keys != null) {
try {
this.getTransportEncryption().encrypt(resp, keys);
} catch (Exception e) {
log.warn("Failed to encrypt response", e);
}
}
return resp;
}

/**
* Calculates MAC and AES keys and returns them in a small array in that order (indexes 0, 1)
* @param secret Shared secret
*/
public byte[][] deriveKeys(final byte[] secret) {
HKDFBytesGenerator hkdf = new HKDFBytesGenerator(new SHA256Digest());
hkdf.init(new HKDFParameters(secret, null, null));
byte[] macKey = new byte[32];
byte[] aesKey = new byte[16];
byte[] output = new byte[macKey.length+aesKey.length];

hkdf.generateBytes(output, 0, output.length);
System.arraycopy(output, 0, macKey, 0, macKey.length);
System.arraycopy(output, macKey.length, aesKey, 0, aesKey.length);
return new byte[][] {macKey, aesKey};
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import java.util.Map;
import java.util.stream.Collectors;

import fi.methics.webapp.musaplink.AccountStorage;
import fi.methics.webapp.musaplink.MusapLinkAccount;
import fi.methics.webapp.musaplink.MusapLinkAccount.MusapKey;
import fi.methics.webapp.musaplink.coupling.CouplingCommand;
Expand All @@ -16,6 +15,7 @@
import fi.methics.webapp.musaplink.link.json.MusapResp;
import fi.methics.webapp.musaplink.util.IdGenerator;
import fi.methics.webapp.musaplink.util.MusapException;
import fi.methics.webapp.musaplink.util.db.AccountStorage;
import fi.methics.webapp.musaplink.util.etsi204.Etsi204Client;
import fi.methics.webapp.musaplink.util.etsi204.Etsi204Exception;
import fi.methics.webapp.musaplink.util.etsi204.Etsi204Response;
Expand All @@ -40,7 +40,7 @@ public CouplingApiMessage execute() throws Exception {
CouplingApiMessage req = this.getRequest();
String musapid = req.musapid;

MusapLinkAccount account = AccountStorage.findByMusapId(musapid);
MusapLinkAccount account = AccountStorage.findAccountByMusapId(musapid);
if (account == null) throw new MusapException(MusapResp.ERROR_UNKNOWN_USER);

ExternalSignatureReq sigReq = this.getRequestPayload();
Expand Down Expand Up @@ -126,6 +126,7 @@ private void sendRequest(MusapLinkAccount account,
try {
sigResp.publickey = resp.getPublicKeyB64();
sigResp.certificate = resp.getCertificateB64();
sigResp.certChain = resp.getCertificateChain();
} catch (Exception e) {
log.warn("Failed to parse certificate from response", e);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package fi.methics.webapp.musaplink.coupling.cmd;

import fi.methics.webapp.musaplink.AccountStorage;
import fi.methics.webapp.musaplink.MusapLinkAccount;
import fi.methics.webapp.musaplink.TxnStorage;
import fi.methics.webapp.musaplink.coupling.CouplingCommand;
import fi.methics.webapp.musaplink.coupling.json.CouplingApiMessage;
import fi.methics.webapp.musaplink.coupling.json.GenerateKeyCallbackResp;
import fi.methics.webapp.musaplink.link.json.MusapResp;
import fi.methics.webapp.musaplink.link.json.MusapSignResp;
import fi.methics.webapp.musaplink.util.MusapException;
import fi.methics.webapp.musaplink.util.db.AccountStorage;
import fi.methics.webapp.musaplink.util.db.TxnStorage;

/**
* Coupling API command for delivering a key generation response to MUSAP Link
Expand All @@ -27,7 +27,7 @@ public CouplingApiMessage execute() throws Exception {
String transid = req.transid;

log.info("Got generated key for MUSAP ID " + musapid);
MusapLinkAccount account = AccountStorage.findByMusapId(musapid);
MusapLinkAccount account = AccountStorage.findAccountByMusapId(musapid);
if (account == null) throw new MusapException(MusapResp.ERROR_UNKNOWN_USER);

GenerateKeyCallbackResp callback = req.getPayload(GenerateKeyCallbackResp.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package fi.methics.webapp.musaplink.coupling.cmd;

import fi.methics.webapp.musaplink.AccountStorage;
import fi.methics.webapp.musaplink.MusapLinkAccount;
import fi.methics.webapp.musaplink.TxnStorage;
import fi.methics.webapp.musaplink.coupling.CouplingCommand;
import fi.methics.webapp.musaplink.coupling.json.CouplingApiMessage;
import fi.methics.webapp.musaplink.coupling.json.SignatureReq;
import fi.methics.webapp.musaplink.link.json.MusapResp;
import fi.methics.webapp.musaplink.util.MusapException;
import fi.methics.webapp.musaplink.util.db.AccountStorage;
import fi.methics.webapp.musaplink.util.db.TxnStorage;

/**
* Coupling API command for checking for pending signature or key generation requests.
Expand All @@ -27,7 +27,7 @@ public CouplingApiMessage execute() throws Exception {
log.info("Getting data for MUSAP ID " + musapid);
log.debug("Total requests stored: " + TxnStorage.countTransactions());

MusapLinkAccount account = AccountStorage.findByMusapId(musapid);
MusapLinkAccount account = AccountStorage.findAccountByMusapId(musapid);
if (account == null) throw new MusapException(MusapResp.ERROR_UNKNOWN_USER);

for (String linkid : account.linkids) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,15 @@

import java.io.IOException;

import fi.methics.webapp.musaplink.AccountStorage;
import fi.methics.webapp.musaplink.coupling.CouplingCommand;
import fi.methics.webapp.musaplink.coupling.json.CouplingApiMessage;
import fi.methics.webapp.musaplink.coupling.json.LinkAccountReq;
import fi.methics.webapp.musaplink.coupling.json.LinkAccountResp;
import fi.methics.webapp.musaplink.link.json.MusapResp;
import fi.methics.webapp.musaplink.util.MusapException;
import fi.methics.webapp.musaplink.util.MusapRandom;
import fi.methics.webapp.musaplink.util.db.AccountStorage;
import fi.methics.webapp.musaplink.util.db.CouplingStorage;

/**
* Coupling API command for requesting MUSAP to link with an RP
Expand Down Expand Up @@ -44,7 +45,7 @@ public CouplingApiMessage execute() throws IOException {
return req.createResponse(new LinkAccountResp(linkid, null));
}

final String linkid = AccountStorage.findLinkId(couplingcode);
final String linkid = CouplingStorage.findLinkId(couplingcode);

if (linkid != null) {
AccountStorage.addLinkId(payload.musapid, linkid);
Expand Down
Loading

0 comments on commit 5bc5c97

Please sign in to comment.