Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion mid-java-client-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>ch.mobileid.mid-java-client</groupId>
<artifactId>mid-java-client-parent</artifactId>
<version>1.5.6</version>
<version>1.5.7</version>
</parent>

<artifactId>mid-java-client-core</artifactId>
Expand All @@ -31,6 +31,15 @@
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.12.0</version>
</dependency>
</dependencies>

</project>
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@
import ch.swisscom.mid.client.SignatureValidator;
import ch.swisscom.mid.client.config.ConfigurationException;
import ch.swisscom.mid.client.config.SignatureValidationConfiguration;
import ch.swisscom.mid.client.model.DataToBeSignedTXNResponseType;
import ch.swisscom.mid.client.model.SignatureValidationFailureReason;
import ch.swisscom.mid.client.model.SignatureValidationResult;
import ch.swisscom.mid.client.model.Traceable;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cms.CMSException;
Expand All @@ -32,6 +36,7 @@
import java.util.regex.Pattern;

import static ch.swisscom.mid.client.utils.Utils.*;
import static org.apache.commons.text.StringEscapeUtils.unescapeJava;

/**
* Default implementation of {@link SignatureValidator}.
Expand All @@ -43,15 +48,22 @@ public class SignatureValidatorImpl implements SignatureValidator {
private static final Logger log = LoggerFactory.getLogger(Loggers.SIGNATURE_VALIDATOR);

private final KeyStore validationTrustStore;
private ObjectMapper jacksonMapper;

public SignatureValidatorImpl(SignatureValidationConfiguration config) {
Security.addProvider(new BouncyCastleProvider());
this.validationTrustStore = loadValidationTruststore(config);

jacksonMapper = new ObjectMapper();
jacksonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}

public SignatureValidatorImpl(KeyStore validationTrustStore) {
Security.addProvider(new BouncyCastleProvider());
this.validationTrustStore = validationTrustStore;

jacksonMapper = new ObjectMapper();
jacksonMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}

@Override
Expand Down Expand Up @@ -143,14 +155,45 @@ public SignatureValidationResult validateSignature(String base64SignatureContent
}
} catch (OperatorCreationException | CMSException e) {
log.warn("Failed to validate the signature against the signer info " +
"during the signature CMS content validation{}", printTrace(trace), e);
"during the signature CMS content validation{}", printTrace(trace), e);
result.setValidationException(e);
result.setValidationFailureReason(SignatureValidationFailureReason.SIGNATURE_VALIDATION_FAILED);
return result;
}

// verify the DTBS from the request vs the one from the response
if (requestedDtbs.equals(result.getSignedDtbs())) {
if (result.getSignedDtbs() == null) {
log.info("Failed to match the DTBS texts, requested=[{}] vs signed=[{}]{}", requestedDtbs, result.getSignedDtbs(), printTrace(trace));
result.setValidationFailureReason(SignatureValidationFailureReason.DATA_TO_BE_SIGNED_NOT_MATCHING);
return result;
}
if (requestedDtbs.startsWith("{")) {
result.setDtbsMatching(false);
try {
// parse item
String[] dtbsArray = requestedDtbs.split("\"dtbd\":");
String reqDtbsValueStr = "";
if (dtbsArray.length > 0) {
String reqDtbsValueRaw = dtbsArray[1];
reqDtbsValueStr = reqDtbsValueRaw.substring(0, reqDtbsValueRaw.length() - 1);
}
// fix response DTBS string
String escResultDtbs = unescapeJava(result.getSignedDtbs()
.replace("\"format_version\"", "\\\"format_version\\\"")
.replace("\"content_string\"", "\\\"content_string\\\"")
.replace("\"[", "[")
.replace("]\"", "]"));

DataToBeSignedTXNResponseType resDtbs = jacksonMapper.readValue(escResultDtbs, DataToBeSignedTXNResponseType.class);
String finalResDtbs = jacksonMapper.writeValueAsString(resDtbs.getDtbd());
result.setDtbsMatching(reqDtbsValueStr.equals(finalResDtbs));
} catch (JsonProcessingException e) {
log.info("Failed to match the DTBS texts, requested=[{}] vs signed=[{}]{}", requestedDtbs, result.getSignedDtbs(), printTrace(trace));
result.setValidationFailureReason(SignatureValidationFailureReason.DATA_TO_BE_SIGNED_NOT_MATCHING);
}
return result;

} else if (requestedDtbs.equals(result.getSignedDtbs())) {
result.setDtbsMatching(true);
} else {
log.info("Failed to match the DTBS texts, requested=[{}] vs signed=[{}]{}", requestedDtbs, result.getSignedDtbs(), printTrace(trace));
Expand Down Expand Up @@ -225,23 +268,23 @@ private KeyStore loadValidationTruststore(SignatureValidationConfiguration confi
if (config.getTrustStoreFile() != null) {
try (InputStream is = new FileInputStream(config.getTrustStoreFile())) {
trustStore.load(is, config.getTrustStorePassword() == null ?
null : config.getTrustStorePassword().toCharArray());
null : config.getTrustStorePassword().toCharArray());
}
} else if (config.getTrustStoreClasspathFile() != null) {
try (InputStream is = this.getClass().getResourceAsStream(config.getTrustStoreClasspathFile())) {
trustStore.load(is, config.getTrustStorePassword() == null ?
null : config.getTrustStorePassword().toCharArray());
null : config.getTrustStorePassword().toCharArray());
}
} else {
try (InputStream is = new ByteArrayInputStream(config.getTrustStoreBytes())) {
trustStore.load(is, config.getTrustStorePassword() == null ?
null : config.getTrustStorePassword().toCharArray());
null : config.getTrustStorePassword().toCharArray());
}
}
return trustStore;
} catch (Exception e) {
throw new ConfigurationException("Failed to initialize the digital signature validation truststore " +
"(Mobile ID CMS signature validator)", e);
"(Mobile ID CMS signature validator)", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package ch.swisscom.mid.client.model;

import static ch.swisscom.mid.client.utils.Utils.dataIsTXNApprovalRequestType;
import static ch.swisscom.mid.client.utils.Utils.dataNotEmpty;

public class DataToBeSigned {
Expand Down Expand Up @@ -61,14 +62,17 @@ public void validateYourself() {
dataNotEmpty(data, "The data in the DataToBeSigned cannot be null or empty");
dataNotEmpty(encoding, "The encoding in the DataToBeSigned cannot be null or empty (set it to \"UTF-8\", for example)");
dataNotEmpty(mimeType, "The mime type in the DataToBeSigned cannot be null or empty (set it to \"text/plain\", for example)");
if (mimeType.equalsIgnoreCase("application/vnd.mobileid.txn-approval")) {
dataIsTXNApprovalRequestType(data, "The DataToBeSigned format is not valid for mime type 'application/vnd.mobileid.txn-approval'");
}
}

@Override
public String toString() {
return "DataToBeSigned{" +
"data='" + data + '\'' +
", encoding='" + encoding + '\'' +
", mimeType='" + mimeType + '\'' +
'}';
"data='" + data + '\'' +
", encoding='" + encoding + '\'' +
", mimeType='" + mimeType + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ch.swisscom.mid.client.model;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;


public class DataToBeSignedTXNResponseType {
@JsonProperty("format_version")
private String formatVersion;

@JsonProperty("content_string")
private List<Map<String, String>> dtbd = new ArrayList<>();

public DataToBeSignedTXNResponseType() {
}

public DataToBeSignedTXNResponseType(String formatVersion, List<Map<String, String>> dtbd) {
this.formatVersion = formatVersion;
this.dtbd = dtbd;
}

public String getFormatVersion() {
return formatVersion;
}

public void setFormatVersion(String formatVersion) {
this.formatVersion = formatVersion;
}

public List<Map<String, String>> getDtbd() {
return dtbd;
}

public void setDtbd(List<Map<String, String>> dtbd) {
this.dtbd = dtbd;
}

@Override
public String toString() {
return "DataToBeSignedTXNResponseType{" +
"formatVersion='" + formatVersion + '\'' +
", content_string=" + dtbd +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Swisscom (Schweiz) AG
* Copyright 2021-2025 Swisscom (Schweiz) AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -88,10 +88,12 @@ public void setMaxAccuracyMeters(String maxAccuracyMeters) {


public boolean isDefined() {
if (countryWhiteList != null && !countryWhiteList.isEmpty()) return true;
if(countryBlackList!=null && !countryBlackList.isEmpty()) return true;
if (minDeviceConfidence != null && !minDeviceConfidence.isEmpty() && !minDeviceConfidence.equalsIgnoreCase("0")) return true;
if (minLocationConfidence != null && !minLocationConfidence.isEmpty() && !minLocationConfidence.equalsIgnoreCase("0")) return true;
if (countryWhiteList != null && !countryWhiteList.isEmpty()) return true;
if (countryBlackList != null && !countryBlackList.isEmpty()) return true;
if (minDeviceConfidence != null && !minDeviceConfidence.isEmpty() && !minDeviceConfidence.equalsIgnoreCase("0"))
return true;
if (minLocationConfidence != null && !minLocationConfidence.isEmpty() && !minLocationConfidence.equalsIgnoreCase("0"))
return true;
if (maxAccuracyMeters != null && !maxAccuracyMeters.isEmpty()) return true;
if (maxTimestampMinutes != null && !maxTimestampMinutes.isEmpty()) return true;
return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2021 Swisscom (Schweiz) AG
* Copyright 2021-2025 Swisscom (Schweiz) AG
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -15,57 +15,58 @@
*/
package ch.swisscom.mid.client.model;

import ch.swisscom.mid.client.impl.Loggers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import ch.swisscom.mid.client.impl.Loggers;

public enum StatusCode implements DocumentedEnum {

REQUEST_OK(100, false, "The request from the AP has been accepted."),
WRONG_PARAM(101, true, "The AP’s request contains wrong parameters."),
MISSING_PARAM(102, true, "The AP’s request has missing parameters."),
WRONG_DATA_LENGTH(103, true, "The AP’s request contains a DTBD message "
+ "that is exceeding the max. allowed length."),
+ "that is exceeding the max. allowed length."),
UNAUTHORIZED_ACCESS(104, true, "AP is not authorized to access the Mobile ID API. "
+ "This is typically due to a wrong AP_ID value or missing X509 client certificate."),
+ "This is typically due to a wrong AP_ID value or missing X509 client certificate."),
UNKNOWN_CLIENT(105, true, "The MSISDN value is unknown to the MID service. "
+ "There is no Mobile ID user with that MSISDN value."),
+ "There is no Mobile ID user with that MSISDN value."),
INAPPROPRIATE_DATA(107, true, "The AP’s request was not accepted due to inappropriate data. "
+ "Typically, the DTBD message does not contain the mandatory prefix string "
+ "(see section 2.19 of the Reference Guide) that is a unique string for each AP."),
+ "Typically, the DTBD message does not contain the mandatory prefix string "
+ "(see section 2.19 of the Reference Guide) that is a unique string for each AP."),
INCOMPATIBLE_INTERFACE(108, true, "The AP’s request contains bad data. "
+ "Typically, a wrong MajorVersion or MinorVersion value has been set in the request."),
+ "Typically, a wrong MajorVersion or MinorVersion value has been set in the request."),
UNSUPPORTED_PROFILE(109, true, "Either the AP has specified an MSS signature profile value "
+ "that the MSSP does not support or the AP is not authorized to use the "
+ "Signature Profile. See section 3.2.1 of the Reference Guide."),
+ "that the MSSP does not support or the AP is not authorized to use the "
+ "Signature Profile. See section 3.2.1 of the Reference Guide."),
EXPIRED_TRANSACTION(208, true, "The transaction timed out. The AP may try again."),
OTA_ERROR(209, true, "A Problem related to the MSSP internal Over-The-Air (OTA) communication "
+ "with the Mobile ID user’s SIM. "
+ "Typically, there is a temporary problem with SMS communication."),
+ "with the Mobile ID user’s SIM. "
+ "Typically, there is a temporary problem with SMS communication."),
USER_CANCEL(401, true, "The user cancelled the request at the mobile phone."),
PIN_NR_BLOCKED(402, true, "The Mobile ID PIN of the SIM method is blocked. "
+ "The user must reactivate the Mobile ID SIM card on the Mobile ID selfcare portal."),
+ "The user must reactivate the Mobile ID SIM card on the Mobile ID selfcare portal."),
CARD_BLOCKED(403, true, "The Mobile ID user is currently suspended. "
+ "Please contact Swisscom Support."),
+ "Please contact Swisscom Support."),
NO_KEY_FOUND(404, true, "The Mobile ID user exists but is not in an active state. "
+ "The user must activate the account on the Mobile ID selfcare portal."),
+ "The user must activate the account on the Mobile ID selfcare portal."),
PB_SIGNATURE_PROCESS(406, true, "A signature transaction is already on-going. "
+ "Please try again later."),
+ "Please try again later."),
NO_CERT_FOUND(422, true, "The Mobile ID user exists but is not in an active state. "
+ "The user must activate the account on the Mobile ID selfcare portal."),
+ "The user must activate the account on the Mobile ID selfcare portal."),
GEOFENCING_POLICY_VIOLATION(450, true, "Geo policy for referenced AP ID was violated. "
+ "Please try again later or contact Swisscom Support, if the problem persists."),
SIGNATURE(500, false, "The MSS Signature transaction was successful."),
REVOKED_CERTIFICATE(501, false, "The Mobile ID user’s509 certificate has been revoked. "
+ "The user must re-activate the account on the Mobile ID selfcare portal."),
+ "The user must re-activate the account on the Mobile ID selfcare portal."),
VALID_SIGNATURE(502, false, "The MSS Signature transaction was successful."),
INVALID_SIGNATURE(503, false, "The MSS Signature transaction failed due to invalid signature data. "
+ "The user must re-activate the account on the Mobile ID selfcare portal."),
+ "The user must re-activate the account on the Mobile ID selfcare portal."),
OUTSTANDING_TRANSACTION(504, false, "The MSS Signature transaction is outstanding. "
+ "The AP must try again later."),
+ "The AP must try again later."),
CONNECTION_REFUSED(780, true, "The connection to the service was refused. "
+ "The client did not present a valid TLS certificate"),
+ "The client did not present a valid TLS certificate"),
INTERNAL_ERROR(900, true, "An internal error on MSSP has occurred. "
+ "Please try again later or contact Swisscom Support, if the problem persists.");
+ "Please try again later or contact Swisscom Support, if the problem persists.");

private static final Logger log = LoggerFactory.getLogger(Loggers.CLIENT);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package ch.swisscom.mid.client.model;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class TXNApprovalReqType {

@JsonProperty("type")
private String type;

@JsonProperty("dtbd")
private List<Map<String, String>> dtbd = new ArrayList<>();

public TXNApprovalReqType() {
}

public TXNApprovalReqType(String type, List<Map<String, String>> dtbd) {
this.type = type;
this.dtbd = dtbd;
}

@Override
public String toString() {
return "TXNApprovalReqType{" +
"type='" + type + '\'' +
", dtbd=" + dtbd +
'}';
}

public void setType(String type) {
this.type = type;
}

public void setDtbd(List<Map<String, String>> dtbd) {
this.dtbd = dtbd;
}

public String getType() {
return type;
}

public List<Map<String, String>> getDtbd() {
return dtbd;
}
}
Loading
Loading