Skip to content
This repository has been archived by the owner on May 16, 2023. It is now read-only.

Commit

Permalink
Feat: Allow-List for Certificate Hashes (#108)
Browse files Browse the repository at this point in the history
* Add Allow-List-Check for mTLS Authentication

* Add Allow-List-Check for mTLS Authentication

* Change Allow-List to Comma seperated
  • Loading branch information
f11h authored Oct 22, 2021
1 parent 275ef3e commit 897fbc8
Show file tree
Hide file tree
Showing 5 changed files with 126 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,35 +21,20 @@

package app.coronawarn.testresult.config;

import java.util.Arrays;
import org.springframework.context.annotation.Bean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

@Bean
protected HttpFirewall strictFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowedHttpMethods(Arrays.asList(
HttpMethod.GET.name(),
HttpMethod.POST.name()
));
return firewall;
}
@ConditionalOnProperty(name = "server.ssl.client-auth", havingValue = "none", matchIfMissing = true)
public class LocalSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.mvcMatchers("/api/**").permitAll()
.mvcMatchers("/actuator/**").permitAll()
.anyRequest().denyAll()
http
.authorizeRequests()
.anyRequest().permitAll()
.and().csrf().disable();
}

}
115 changes: 115 additions & 0 deletions src/main/java/app/coronawarn/testresult/config/MtlsSecurityConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Corona-Warn-App / cwa-testresult-server
*
* (C) 2020, T-Systems International GmbH
*
* Deutsche Telekom AG and all other contributors /
* copyright owners license this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this
* file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package app.coronawarn.testresult.config;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.stream.Stream;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.codec.Hex;
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
import org.springframework.security.web.firewall.HttpFirewall;
import org.springframework.security.web.firewall.StrictHttpFirewall;
import org.springframework.web.server.ResponseStatusException;

@Configuration
@Slf4j
@RequiredArgsConstructor
@ConditionalOnProperty(name = "server.ssl.client-auth", havingValue = "need")
public class MtlsSecurityConfig extends WebSecurityConfigurerAdapter {

private final TestResultConfig testResultConfig;

@Bean
protected HttpFirewall strictFirewall() {
StrictHttpFirewall firewall = new StrictHttpFirewall();
firewall.setAllowedHttpMethods(Arrays.asList(
HttpMethod.GET.name(),
HttpMethod.POST.name()
));
return firewall;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.mvcMatchers("/api/**").authenticated().and()
.x509().x509PrincipalExtractor(new ThumbprintX509PrincipalExtractor()).userDetailsService(userDetailsService())
.and().authorizeRequests()
.mvcMatchers("/actuator/**").permitAll()
.anyRequest().denyAll()
.and().csrf().disable();
}

@Override
public UserDetailsService userDetailsService() {
return hash -> {

boolean allowed = Stream.of(testResultConfig.getAllowedClientCertificates()
.split(","))
.map(String::trim)
.anyMatch(entry -> entry.equalsIgnoreCase(hash));

if (allowed) {
return new User(hash, "", Collections.emptyList());
} else {
log.error("Failed to authenticate cert with hash {}", hash);
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}
};
}

private static class ThumbprintX509PrincipalExtractor implements X509PrincipalExtractor {

MessageDigest messageDigest;

private ThumbprintX509PrincipalExtractor() throws NoSuchAlgorithmException {
messageDigest = MessageDigest.getInstance("SHA-256");
}

@Override
public Object extractPrincipal(X509Certificate x509Certificate) {
try {
return String.valueOf(Hex.encode(messageDigest.digest(x509Certificate.getEncoded())));
} catch (CertificateEncodingException e) {
log.error("Failed to extract bytes from certificate");
return null;
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
@ConfigurationProperties(prefix = "testresult")
public class TestResultConfig {

private String allowedClientCertificates;

private Cleanup cleanup;

@Getter
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/application-cloud.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,5 @@ server:
enabled: true
key-store-password: ${SSL_TESTRESULT_KEYSTORE_PASSWORD}
trust-store-password: ${SSL_TESTRESULT_TRUSTSTORE_PASSWORD}
testresult:
allowed-client-certificates: ${TESTRESULT_ALLOWEDCLIENTCERTIFICATES}
1 change: 1 addition & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,4 @@ testresult:
delete:
days: 60
rate: 3600000
allowed-client-certificates:

0 comments on commit 897fbc8

Please sign in to comment.