Skip to content

NFC-46 Add NFC token support for validation #82

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: web-eid-mobile
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion example/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
version: '2'
services:
web-eid-springboot-example:
image: web-eid-springboot-example:3.1.1
image: web-eid-springboot-example:3.2.0
restart: always
environment:
JAVA_TOOL_OPTIONS: '-Dspring.profiles.active=prod'
Expand Down
14 changes: 7 additions & 7 deletions example/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.4</version>
<version>3.5.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>eu.webeid.example</groupId>
<artifactId>web-eid-springboot-example</artifactId>
<version>3.1.1</version>
<version>3.2.0</version>
<name>web-eid-springboot-example</name>
<description>Example Spring Boot application that demonstrates how to use Web eID for authentication and digital
signing
</description>

<properties>
<java.version>17</java.version>
<maven-surefire-plugin.version>3.5.2</maven-surefire-plugin.version>
<webeid.version>3.1.1</webeid.version>
<digidoc4j.version>6.0.0</digidoc4j.version>
<jmockit.version>1.44</jmockit.version>
<jib.version>3.4.5</jib.version>
<maven-surefire-plugin.version>3.5.3</maven-surefire-plugin.version>
<webeid.version>3.2.0</webeid.version>
<digidoc4j.version>6.0.1</digidoc4j.version>
<jmockit.version>1.44</jmockit.version> <!-- Keep version 1.44, otherwise mocking will fail. -->
<jib.version>3.4.6</jib.version>
</properties>

<dependencies>
Expand Down
12 changes: 6 additions & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,24 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>authtoken-validation</artifactId>
<groupId>eu.webeid.security</groupId>
<version>3.1.1</version>
<version>3.2.0</version>
<packaging>jar</packaging>
<name>authtoken-validation</name>
<description>Web eID authentication token validation library for Java</description>

<properties>
<java.version>11</java.version>
<jjwt.version>0.12.6</jjwt.version>
<bouncycastle.version>1.80</bouncycastle.version>
<jackson.version>2.18.3</jackson.version>
<bouncycastle.version>1.81</bouncycastle.version>
<jackson.version>2.19.1</jackson.version>
<slf4j.version>2.0.17</slf4j.version>
<junit-jupiter.version>5.12.1</junit-jupiter.version>
<junit-jupiter.version>5.13.3</junit-jupiter.version>
<assertj.version>3.27.3</assertj.version>
<mockito.version>5.17.0</mockito.version>
<mockito.version>5.18.0</mockito.version>
<maven-surefire-plugin.version>3.5.2</maven-surefire-plugin.version>
<maven-source-plugin.version>3.3.1</maven-source-plugin.version>
<maven-javadoc-plugin.version>3.11.2</maven-javadoc-plugin.version>
<maven-enforcer-plugin.version>3.5.0</maven-enforcer-plugin.version>
<maven-enforcer-plugin.version>3.6.0</maven-enforcer-plugin.version>
<jacoco.version>0.8.12</jacoco.version>
<sonar.coverage.jacoco.xmlReportPaths>
${project.basedir}/../jacoco-coverage-report/target/site/jacoco-aggregate/jacoco.xml
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package eu.webeid.security.authtoken;

public class SupportedSignatureAlgorithm {
private String cryptoAlgorithm;
private String hashFunction;
private String paddingScheme;

public String getCryptoAlgorithm() {
return cryptoAlgorithm;
}

public void setCryptoAlgorithm(String cryptoAlgorithm) {
this.cryptoAlgorithm = cryptoAlgorithm;
}

public String getHashFunction() {
return hashFunction;
}

public void setHashFunction(String hashFunction) {
this.hashFunction = hashFunction;
}

public String getPaddingScheme() {
return paddingScheme;
}

public void setPaddingScheme(String paddingScheme) {
this.paddingScheme = paddingScheme;
}
}
20 changes: 20 additions & 0 deletions src/main/java/eu/webeid/security/authtoken/WebEidAuthToken.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;

import java.util.List;

@JsonIgnoreProperties(ignoreUnknown = true)
public class WebEidAuthToken {

Expand All @@ -32,6 +34,9 @@ public class WebEidAuthToken {
private String algorithm;
private String format;

private String unverifiedSigningCertificate;
private List<SupportedSignatureAlgorithm> supportedSignatureAlgorithms;

public String getUnverifiedCertificate() {
return unverifiedCertificate;
}
Expand Down Expand Up @@ -64,4 +69,19 @@ public void setFormat(String format) {
this.format = format;
}

public String getUnverifiedSigningCertificate() {
return unverifiedSigningCertificate;
}

public void setUnverifiedSigningCertificate(String unverifiedSigningCertificate) {
this.unverifiedSigningCertificate = unverifiedSigningCertificate;
}

public List<SupportedSignatureAlgorithm> getSupportedSignatureAlgorithms() {
return supportedSignatureAlgorithms;
}

public void setSupportedSignatureAlgorithms(List<SupportedSignatureAlgorithm> supportedSignatureAlgorithms) {
this.supportedSignatureAlgorithms = supportedSignatureAlgorithms;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package eu.webeid.security.validator;

import eu.webeid.security.authtoken.SupportedSignatureAlgorithm;
import eu.webeid.security.authtoken.WebEidAuthToken;
import eu.webeid.security.certificate.CertificateLoader;
import eu.webeid.security.exceptions.AuthTokenException;
import eu.webeid.security.exceptions.AuthTokenParseException;
import eu.webeid.security.validator.certvalidators.SubjectCertificateValidatorBatch;

import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Set;
import java.util.function.Supplier;

import static eu.webeid.security.util.Strings.isNullOrEmpty;

public class AuthTokenV11Validator {

private final SubjectCertificateValidatorBatch simpleSubjectCertificateValidators;
private final Supplier<SubjectCertificateValidatorBatch> certTrustValidatorsSupplier;

public AuthTokenV11Validator(
SubjectCertificateValidatorBatch simpleSubjectCertificateValidators,
Supplier<SubjectCertificateValidatorBatch> certTrustValidatorsSupplier
) {
this.simpleSubjectCertificateValidators = simpleSubjectCertificateValidators;
this.certTrustValidatorsSupplier = certTrustValidatorsSupplier;
}

public void validate(WebEidAuthToken token, X509Certificate subjectCertificate) throws AuthTokenException {
if (isNullOrEmpty(token.getUnverifiedSigningCertificate())) {
throw new AuthTokenParseException("'unverifiedSigningCertificate' field is missing, null or empty for format 'web-eid:1.1'");
}

if (token.getSupportedSignatureAlgorithms() == null || token.getSupportedSignatureAlgorithms().isEmpty()) {
throw new AuthTokenParseException("'supportedSignatureAlgorithms' field is missing");
}

validateSupportedSignatureAlgorithms(token.getSupportedSignatureAlgorithms());

final X509Certificate signingCertificate =
CertificateLoader.decodeCertificateFromBase64(token.getUnverifiedSigningCertificate());

if (!subjectCertificate.getSubjectX500Principal().equals(signingCertificate.getSubjectX500Principal())) {
throw new AuthTokenParseException("Signing certificate subject does not match authentication certificate subject");
}

simpleSubjectCertificateValidators.executeFor(signingCertificate);
certTrustValidatorsSupplier.get().executeFor(signingCertificate);
}

private static void validateSupportedSignatureAlgorithms(List<SupportedSignatureAlgorithm> algorithms) throws AuthTokenParseException {
boolean hasInvalid = algorithms.stream().anyMatch(supportedSignatureAlgorithm ->
!isValidCryptoAlgorithm(supportedSignatureAlgorithm.getCryptoAlgorithm())
|| !isValidHashFunction(supportedSignatureAlgorithm.getHashFunction())
|| !isValidPaddingScheme(supportedSignatureAlgorithm.getPaddingScheme())
);

if (hasInvalid) {
throw new AuthTokenParseException("Unsupported signature algorithm");
}
}

private static boolean isValidCryptoAlgorithm(String value) {
return "RSA".equals(value) || "ECC".equals(value);
}

private static boolean isValidHashFunction(String value) {
return Set.of(
"SHA-224", "SHA-256", "SHA-384", "SHA-512",
"SHA3-224", "SHA3-256", "SHA3-384", "SHA3-512"
).contains(value);
}

private static boolean isValidPaddingScheme(String value) {
return "NONE".equals(value) || "PKCS1.5".equals(value) || "PSS".equals(value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,6 @@
*/
public interface AuthTokenValidator {

String CURRENT_TOKEN_FORMAT_VERSION = "web-eid:1";

/**
* Parses the Web eID authentication token signed by the subject.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
import eu.webeid.security.validator.certvalidators.SubjectCertificatePurposeValidator;
import eu.webeid.security.validator.certvalidators.SubjectCertificateTrustedValidator;
import eu.webeid.security.validator.certvalidators.SubjectCertificateValidatorBatch;
import eu.webeid.security.validator.format.AuthTokenV11FormatValidator;
import eu.webeid.security.validator.format.AuthTokenV1FormatValidator;
import eu.webeid.security.validator.format.TokenFormatValidator;
import eu.webeid.security.validator.format.TokenFormatValidatorFactory;
import eu.webeid.security.validator.ocsp.OcspClient;
import eu.webeid.security.validator.ocsp.OcspServiceProvider;
import eu.webeid.security.validator.ocsp.service.AiaOcspServiceConfiguration;
Expand All @@ -45,6 +49,7 @@
import java.security.cert.CertStore;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.List;
import java.util.Objects;
import java.util.Set;

Expand All @@ -68,6 +73,8 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
private OcspClient ocspClient;
private OcspServiceProvider ocspServiceProvider;
private final AuthTokenSignatureValidator authTokenSignatureValidator;
private final TokenFormatValidatorFactory tokenFormatValidatorFactory;


/**
* @param configuration configuration parameters for the token validator
Expand Down Expand Up @@ -96,6 +103,11 @@ final class AuthTokenValidatorImpl implements AuthTokenValidator {
trustedCACertificateCertStore));
}

this.tokenFormatValidatorFactory = new TokenFormatValidatorFactory(List.of(
new AuthTokenV11FormatValidator(simpleSubjectCertificateValidators, this::getCertTrustValidators),
new AuthTokenV1FormatValidator()
));

authTokenSignatureValidator = new AuthTokenSignatureValidator(configuration.getSiteOrigin());
}

Expand Down Expand Up @@ -146,15 +158,15 @@ private WebEidAuthToken parseToken(String authToken) throws AuthTokenParseExcept
}

private X509Certificate validateToken(WebEidAuthToken token, String currentChallengeNonce) throws AuthTokenException {
if (token.getFormat() == null || !token.getFormat().startsWith(CURRENT_TOKEN_FORMAT_VERSION)) {
throw new AuthTokenParseException("Only token format version '" + CURRENT_TOKEN_FORMAT_VERSION +
"' is currently supported");
}
TokenFormatValidator formatValidator = tokenFormatValidatorFactory.requireFor(token.getFormat());

if (token.getUnverifiedCertificate() == null || token.getUnverifiedCertificate().isEmpty()) {
throw new AuthTokenParseException("'unverifiedCertificate' field is missing, null or empty");
}
final X509Certificate subjectCertificate = CertificateLoader.decodeCertificateFromBase64(token.getUnverifiedCertificate());

formatValidator.validate(token, subjectCertificate);

simpleSubjectCertificateValidators.executeFor(subjectCertificate);
getCertTrustValidators().executeFor(subjectCertificate);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package eu.webeid.security.validator.format;

import eu.webeid.security.authtoken.WebEidAuthToken;
import eu.webeid.security.exceptions.AuthTokenException;
import eu.webeid.security.validator.AuthTokenV11Validator;
import eu.webeid.security.validator.certvalidators.SubjectCertificateValidatorBatch;

import java.security.cert.X509Certificate;
import java.util.function.Supplier;

public final class AuthTokenV11FormatValidator implements TokenFormatValidator {

private static final String SUPPORTED_PREFIX = "web-eid:1.1";

private final AuthTokenV11Validator delegate;

public AuthTokenV11FormatValidator(SubjectCertificateValidatorBatch simpleSubjectCertificateValidators,
Supplier<SubjectCertificateValidatorBatch> certTrustValidatorsSupplier) {
this.delegate = new AuthTokenV11Validator(simpleSubjectCertificateValidators, certTrustValidatorsSupplier);
}

@Override
public boolean supports(String format) {
return format != null && format.startsWith(SUPPORTED_PREFIX);
}

@Override
public void validate(WebEidAuthToken token, X509Certificate subjectCertificate) throws AuthTokenException {
delegate.validate(token, subjectCertificate);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package eu.webeid.security.validator.format;

import eu.webeid.security.authtoken.WebEidAuthToken;

import java.security.cert.X509Certificate;

public final class AuthTokenV1FormatValidator implements TokenFormatValidator {
private static final String SUPPORTED_TOKEN_FORMAT_VERSION = "web-eid:1";

@Override
public boolean supports(String format) {
return format != null && format.startsWith(SUPPORTED_TOKEN_FORMAT_VERSION);
}

@Override
@SuppressWarnings("java:S1186")
public void validate(WebEidAuthToken token, X509Certificate subjectCertificate) {
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package eu.webeid.security.validator.format;

import eu.webeid.security.authtoken.WebEidAuthToken;
import eu.webeid.security.exceptions.AuthTokenException;

import java.security.cert.X509Certificate;

public interface TokenFormatValidator {
boolean supports(String format);
void validate(WebEidAuthToken token, X509Certificate subjectCertificate) throws AuthTokenException;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package eu.webeid.security.validator.format;

import eu.webeid.security.exceptions.AuthTokenParseException;

import java.util.List;

public final class TokenFormatValidatorFactory {
public static final String CURRENT_TOKEN_FORMAT_VERSION = "web-eid:1";
private final List<TokenFormatValidator> validators;

public TokenFormatValidatorFactory(List<TokenFormatValidator> validators) {
this.validators = List.copyOf(validators);
}

public TokenFormatValidator requireFor(String format) throws AuthTokenParseException {
if (format == null || format.isBlank()) {
throw new AuthTokenParseException("'format' field is missing");
}

if (!format.startsWith(CURRENT_TOKEN_FORMAT_VERSION)) {
throw new AuthTokenParseException("Only token format version '" + CURRENT_TOKEN_FORMAT_VERSION + "' is currently supported");
}
return validators.stream()
.filter(v -> v.supports(format))
.findFirst()
.orElseThrow(() -> new AuthTokenParseException("Unsupported token format: " + format));
}
}

Loading
Loading