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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ The `validate()` method returns the validated user certificate object if validat

```java
import eu.webeid.security.certificate;
import static eu.webeid.security.util.TitleCase.toTitleCase;
import static eu.webeid.security.util.Strings.toTitleCase;

...

Expand Down
7 changes: 1 addition & 6 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<maven-surefire-plugin.version>2.22.2</maven-surefire-plugin.version>
<java.version>1.8</java.version>
<jjwt.version>0.11.5</jjwt.version>
<jackson.version>2.13.3</jackson.version>
<jackson.version>2.13.4</jackson.version>
<slf4j.version>1.7.36</slf4j.version>
<bouncycastle.version>1.70</bouncycastle.version>
<guava.version>31.1-jre</guava.version>
Expand Down Expand Up @@ -55,11 +55,6 @@
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
Expand Down
48 changes: 48 additions & 0 deletions src/main/java/eu/webeid/security/util/Collections.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2022 Estonian Information System Authority
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package eu.webeid.security.util;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public final class Collections {

@SafeVarargs
public static <T> Set<T> newHashSet(T... elements) {
final Set<T> set = new HashSet<>();
java.util.Collections.addAll(set, elements);
return set;
}

public static byte[] concat(byte[] first, byte[] second) {
byte[] result = Arrays.copyOf(first, first.length + second.length);
System.arraycopy(second, 0, result, first.length, second.length);
return result;
}

private Collections() {
throw new IllegalStateException("Utility class");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

package eu.webeid.security.util;

public final class TitleCase {
public final class Strings {

public static String toTitleCase(String input) {
final StringBuilder titleCase = new StringBuilder(input.length());
Expand All @@ -41,7 +41,11 @@ public static String toTitleCase(String input) {
return titleCase.toString();
}

private TitleCase() {
public static boolean isNullOrEmpty(String argument) {
return argument == null || argument.isEmpty();
}

private Strings() {
throw new IllegalStateException("Utility class");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
*/
package eu.webeid.security.validator;

import com.google.common.base.Strings;
import com.google.common.primitives.Bytes;
import eu.webeid.security.exceptions.AuthTokenException;
import eu.webeid.security.exceptions.AuthTokenParseException;
import eu.webeid.security.exceptions.AuthTokenSignatureValidationException;
Expand All @@ -43,6 +41,8 @@
import java.util.Set;

import static eu.webeid.security.util.Base64Decoder.decodeBase64;
import static eu.webeid.security.util.Collections.concat;
import static eu.webeid.security.util.Strings.isNullOrEmpty;

public class AuthTokenSignatureValidator {

Expand All @@ -65,7 +65,7 @@ public void validate(String algorithm, String signature, PublicKey publicKey, St
requireNotEmpty(algorithm, "algorithm");
requireNotEmpty(signature, "signature");
Objects.requireNonNull(publicKey);
if (Strings.isNullOrEmpty(currentChallengeNonce)) {
if (isNullOrEmpty(currentChallengeNonce)) {
throw new ChallengeNullOrEmptyException();
}

Expand Down Expand Up @@ -96,7 +96,7 @@ public void validate(String algorithm, String signature, PublicKey publicKey, St

final byte[] originHash = hashAlgorithm.digest(originBytes);
final byte[] nonceHash = hashAlgorithm.digest(currentChallengeNonce.getBytes(StandardCharsets.UTF_8));
final byte[] concatSignedFields = Bytes.concat(originHash, nonceHash);
final byte[] concatSignedFields = concat(originHash, nonceHash);

// Note that in case of ECDSA, the eID card outputs raw R||S, but JCA's SHA384withECDSA signature
// validation implementation requires the signature in DER encoding.
Expand All @@ -112,7 +112,7 @@ private MessageDigest hashAlgorithmForName(String algorithm) throws NoSuchAlgori
}

private void requireNotEmpty(String argument, String fieldName) throws AuthTokenParseException {
if (Strings.isNullOrEmpty(argument)) {
if (isNullOrEmpty(argument)) {
throw new AuthTokenParseException("'" + fieldName + "' is null or empty");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

package eu.webeid.security.validator;

import com.google.common.collect.Sets;
import eu.webeid.security.certificate.SubjectCertificatePolicies;
import eu.webeid.security.validator.ocsp.service.DesignatedOcspServiceConfiguration;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
Expand All @@ -37,6 +36,7 @@
import java.util.HashSet;
import java.util.Objects;

import static eu.webeid.security.util.Collections.newHashSet;
import static eu.webeid.security.util.DateAndTime.requirePositiveDuration;
import static eu.webeid.security.validator.ocsp.OcspUrl.AIA_ESTEID_2015;

Expand All @@ -51,14 +51,14 @@ public final class AuthTokenValidationConfiguration {
private Duration ocspRequestTimeout = Duration.ofSeconds(5);
private DesignatedOcspServiceConfiguration designatedOcspServiceConfiguration;
// Don't allow Estonian Mobile-ID policy by default.
private Collection<ASN1ObjectIdentifier> disallowedSubjectCertificatePolicies = Sets.newHashSet(
private Collection<ASN1ObjectIdentifier> disallowedSubjectCertificatePolicies = newHashSet(
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V1,
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V2,
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY_V3,
SubjectCertificatePolicies.ESTEID_SK_2015_MOBILE_ID_POLICY
);
// Disable OCSP nonce extension for EstEID 2015 cards by default.
private Collection<URI> nonceDisabledOcspUrls = Sets.newHashSet(AIA_ESTEID_2015);
private Collection<URI> nonceDisabledOcspUrls = newHashSet(AIA_ESTEID_2015);

AuthTokenValidationConfiguration() {
}
Expand Down Expand Up @@ -158,4 +158,5 @@ public static void validateIsOriginURL(URI uri) throws IllegalArgumentException
throw new IllegalArgumentException("An URI syntax exception occurred");
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import eu.webeid.security.authtoken.WebEidAuthToken;
import eu.webeid.security.certificate.CertificateData;
import eu.webeid.security.exceptions.AuthTokenException;
import eu.webeid.security.util.TitleCase;
import eu.webeid.security.util.Strings;

import java.security.cert.X509Certificate;

Expand All @@ -49,7 +49,7 @@ public interface AuthTokenValidator {
* Validates the Web eID authentication token signed by the subject and returns
* the subject certificate that can be used for retrieving information about the subject.
* <p>
* See {@link CertificateData} and {@link TitleCase} for convenience methods for retrieving user
* See {@link CertificateData} and {@link Strings} for convenience methods for retrieving user
* information from the certificate.
*
* @param authToken the Web eID authentication token
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,18 +22,21 @@

package eu.webeid.security.validator.certvalidators;

import com.google.common.collect.Lists;
import eu.webeid.security.exceptions.AuthTokenException;

import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public final class SubjectCertificateValidatorBatch {

private final List<SubjectCertificateValidator> validatorList;

public static SubjectCertificateValidatorBatch createFrom(SubjectCertificateValidator... validatorList) {
return new SubjectCertificateValidatorBatch(Lists.newArrayList(validatorList));
final List<SubjectCertificateValidator> list = new ArrayList<>();
Collections.addAll(list, validatorList);
return new SubjectCertificateValidatorBatch(list);
}

public void executeFor(X509Certificate subjectCertificate) throws AuthTokenException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

package eu.webeid.security.testutil;

import com.google.common.collect.Sets;
import eu.webeid.security.certificate.CertificateValidator;
import eu.webeid.security.exceptions.JceException;
import eu.webeid.security.exceptions.OCSPCertificateException;
Expand All @@ -39,6 +38,7 @@
import java.util.List;

import static eu.webeid.security.testutil.Certificates.*;
import static eu.webeid.security.util.Collections.newHashSet;
import static eu.webeid.security.validator.ocsp.OcspUrl.AIA_ESTEID_2015;

public class OcspServiceMaker {
Expand Down Expand Up @@ -77,7 +77,7 @@ public static OcspServiceProvider getDesignatedOcspServiceProvider(String ocspSe

private static AiaOcspServiceConfiguration getAiaOcspServiceConfiguration() throws JceException {
return new AiaOcspServiceConfiguration(
Sets.newHashSet(AIA_ESTEID_2015, TEST_ESTEID_2015),
newHashSet(AIA_ESTEID_2015, TEST_ESTEID_2015),
CertificateValidator.buildTrustAnchorsFromCertificates(TRUSTED_CA_CERTIFICATES),
CertificateValidator.buildCertStoreFromCertificates(TRUSTED_CA_CERTIFICATES));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,19 @@

package eu.webeid.security.validator;

import eu.webeid.security.authtoken.WebEidAuthToken;
import eu.webeid.security.certificate.CertificateData;
import eu.webeid.security.exceptions.AuthTokenSignatureValidationException;
import eu.webeid.security.testutil.AbstractTestWithValidator;
import eu.webeid.security.testutil.AuthTokenValidators;
import eu.webeid.security.util.TitleCase;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
import eu.webeid.security.authtoken.WebEidAuthToken;
import eu.webeid.security.exceptions.AuthTokenSignatureValidationException;
import eu.webeid.security.testutil.AbstractTestWithValidator;

import java.security.cert.X509Certificate;

import static eu.webeid.security.util.Strings.toTitleCase;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static eu.webeid.security.testutil.AuthTokenValidators.getAuthTokenValidator;

class AuthTokenSignatureTest extends AbstractTestWithValidator {

Expand All @@ -51,9 +50,9 @@ void whenValidTokenAndNonce_thenValidationSucceeds() throws Exception {

Assertions.assertThat(CertificateData.getSubjectCN(result))
.isEqualTo("JÕEORG\\,JAAK-KRISTJAN\\,38001085718");
Assertions.assertThat(TitleCase.toTitleCase(CertificateData.getSubjectGivenName(result)))
Assertions.assertThat(toTitleCase(CertificateData.getSubjectGivenName(result)))
.isEqualTo("Jaak-Kristjan");
Assertions.assertThat(TitleCase.toTitleCase(CertificateData.getSubjectSurname(result)))
Assertions.assertThat(toTitleCase(CertificateData.getSubjectSurname(result)))
.isEqualTo("Jõeorg");
assertThat(CertificateData.getSubjectIdCode(result))
.isEqualTo("PNOEE-38001085718");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@

package eu.webeid.security.validator.certvalidators;

import com.google.common.io.ByteStreams;
import eu.webeid.security.exceptions.JceException;
import eu.webeid.security.validator.ocsp.OcspClient;
import eu.webeid.security.validator.ocsp.OkHttpOcspClient;
Expand Down Expand Up @@ -241,15 +240,16 @@ void whenOcspResponseRevoked_thenThrows() throws Exception {
@Test
void whenOcspResponseUnknown_thenThrows() throws Exception {
final OcspServiceProvider ocspServiceProvider = getDesignatedOcspServiceProvider("https://web-eid-test.free.beeceptor.com");
final Response response = getResponseBuilder()
try (final Response response = getResponseBuilder()
.body(ResponseBody.create(getOcspResponseBytesFromResources("ocsp_response_unknown.der"), OCSP_RESPONSE))
.build();
final OcspClient client = (url, request) -> new OCSPResp(Objects.requireNonNull(response.body()).bytes());
final SubjectCertificateNotRevokedValidator validator = new SubjectCertificateNotRevokedValidator(trustedValidator, client, ocspServiceProvider);
assertThatExceptionOfType(UserCertificateRevokedException.class)
.isThrownBy(() ->
validator.validateCertificateNotRevoked(estEid2018Cert))
.withMessage("User certificate has been revoked: Unknown status");
.build()) {
final OcspClient client = (url, request) -> new OCSPResp(Objects.requireNonNull(response.body()).bytes());
final SubjectCertificateNotRevokedValidator validator = new SubjectCertificateNotRevokedValidator(trustedValidator, client, ocspServiceProvider);
assertThatExceptionOfType(UserCertificateRevokedException.class)
.isThrownBy(() ->
validator.validateCertificateNotRevoked(estEid2018Cert))
.withMessage("User certificate has been revoked: Unknown status");
}
}

@Test
Expand Down Expand Up @@ -320,7 +320,7 @@ private static byte[] getOcspResponseBytesFromResources() throws IOException {

private static byte[] getOcspResponseBytesFromResources(String resource) throws IOException {
try (final InputStream resourceAsStream = ClassLoader.getSystemResourceAsStream(resource)) {
return ByteStreams.toByteArray(Objects.requireNonNull(resourceAsStream));
return toByteArray(resourceAsStream);
}
}

Expand Down Expand Up @@ -350,4 +350,15 @@ private static Response.Builder getResponseBuilder() {
.code(200);
}

private static byte[] toByteArray(InputStream resourceAsStream) throws IOException {
Objects.requireNonNull(resourceAsStream);
int bytesAvailable = resourceAsStream.available();
byte[] result = new byte[bytesAvailable];
int bytesRead = resourceAsStream.read(result);
if (bytesRead != bytesAvailable) {
throw new RuntimeException("Short read while loading resources");
}
return result;
}

}