-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BFD-3723: Determine SAMHSA authorization based on certificate identity (
- Loading branch information
1 parent
a142293
commit 93e2728
Showing
29 changed files
with
928 additions
and
91 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
101 changes: 101 additions & 0 deletions
101
apps/bfd-server/bfd-server-war/src/main/java/gov/cms/bfd/server/war/AllowSamhsaFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
package gov.cms.bfd.server.war; | ||
|
||
import static gov.cms.bfd.server.war.SpringConfiguration.SSM_PATH_SAMHSA_ALLOWED_CERTIFICATE_ALIASES_JSON; | ||
import static gov.cms.bfd.server.war.commons.CommonTransformerUtils.SHOULD_FILTER_SAMHSA; | ||
|
||
import com.fasterxml.jackson.core.JsonProcessingException; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import gov.cms.bfd.server.war.commons.ClientCertificateUtils; | ||
import jakarta.servlet.FilterChain; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import java.io.IOException; | ||
import java.math.BigInteger; | ||
import java.security.KeyStore; | ||
import java.security.KeyStoreException; | ||
import java.security.cert.X509Certificate; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.stream.Collectors; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
import org.springframework.beans.factory.annotation.Qualifier; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.web.filter.OncePerRequestFilter; | ||
|
||
/** | ||
* Filter class to add metadata to the request that says whether clients can see SAMHSA data or not. | ||
*/ | ||
@Component("AllowSamhsaFilterBean") | ||
public class AllowSamhsaFilter extends OncePerRequestFilter { | ||
|
||
/** The logger. */ | ||
private static final Logger LOGGER = LoggerFactory.getLogger(AllowSamhsaFilter.class); | ||
|
||
/** List of allowed certificate serial numbers. */ | ||
private final List<BigInteger> samhsaAllowedSerialNumbers; | ||
|
||
/** | ||
* Creates a new {@link AllowSamhsaFilter}. | ||
* | ||
* @param samhsaAllowedCertificateAliasesJson list of certificate aliases to identify clients that | ||
* are allowed to see SAMHSA data | ||
* @param keyStore server key store | ||
*/ | ||
public AllowSamhsaFilter( | ||
@Value("${" + SSM_PATH_SAMHSA_ALLOWED_CERTIFICATE_ALIASES_JSON + "}") | ||
String samhsaAllowedCertificateAliasesJson, | ||
@Qualifier("serverTrustStore") KeyStore keyStore) | ||
throws JsonProcessingException { | ||
super(); | ||
ObjectMapper mapper = new ObjectMapper(); | ||
String[] samhsaAllowedCertAliases = | ||
mapper.readValue(samhsaAllowedCertificateAliasesJson, String[].class); | ||
this.samhsaAllowedSerialNumbers = | ||
Arrays.stream(samhsaAllowedCertAliases) | ||
.map(allowedCert -> getCertSerialNumber(keyStore, allowedCert)) | ||
.filter(Objects::nonNull) | ||
.collect(Collectors.toList()); | ||
} | ||
|
||
/** | ||
* Gets the serial number from the certificate alias. | ||
* | ||
* @param keyStore server key store | ||
* @param allowedCertAlias certificate alias | ||
* @return serial number | ||
*/ | ||
private static BigInteger getCertSerialNumber(KeyStore keyStore, String allowedCertAlias) { | ||
try { | ||
X509Certificate cert = ((X509Certificate) keyStore.getCertificate(allowedCertAlias)); | ||
if (cert == null) { | ||
LOGGER.error( | ||
"Certificate {} was configured to allow SAMHSA, but was not found", allowedCertAlias); | ||
return null; | ||
} | ||
return cert.getSerialNumber(); | ||
} catch (KeyStoreException e) { | ||
LOGGER.error("Error loading keystore", e); | ||
return null; | ||
} | ||
} | ||
|
||
/** {@inheritDoc} */ | ||
@Override | ||
protected void doFilterInternal( | ||
@NotNull HttpServletRequest request, | ||
@NotNull HttpServletResponse response, | ||
@NotNull FilterChain chain) | ||
throws ServletException, IOException { | ||
BigInteger serialNumber = ClientCertificateUtils.getClientSslSerialNumber(request); | ||
// Set the attribute on the request so the transformers can check for this property | ||
request.setAttribute( | ||
SHOULD_FILTER_SAMHSA, | ||
serialNumber == null || !samhsaAllowedSerialNumbers.contains(serialNumber)); | ||
chain.doFilter(request, response); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
69 changes: 69 additions & 0 deletions
69
...r/bfd-server-war/src/main/java/gov/cms/bfd/server/war/commons/ClientCertificateUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
package gov.cms.bfd.server.war.commons; | ||
|
||
import jakarta.servlet.http.HttpServletRequest; | ||
import java.math.BigInteger; | ||
import java.security.cert.X509Certificate; | ||
import javax.security.auth.x500.X500Principal; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** Utilities for parsing metadata from client certificates. */ | ||
public class ClientCertificateUtils { | ||
/** The logger for this filter. */ | ||
private static final Logger LOGGER = LoggerFactory.getLogger(ClientCertificateUtils.class); | ||
|
||
/** | ||
* Gets the {@link X500Principal#getName()} for the client certificate if available. | ||
* | ||
* @param request the {@link HttpServletRequest} to get the client principal DN (if any) for | ||
* @return the {@link X500Principal#getName()} for the client certificate, or <code>null</code> if | ||
* that's not available | ||
*/ | ||
public static String getClientSslPrincipalDistinguishedName(HttpServletRequest request) { | ||
/* | ||
* Note: Now that Wildfly/JBoss is properly configured with a security realm, | ||
* this method is equivalent to calling `request.getRemoteUser()`. | ||
*/ | ||
X509Certificate clientCert = getClientCertificate(request); | ||
if (clientCert == null || clientCert.getSubjectX500Principal() == null) { | ||
LOGGER.debug("No client SSL principal available: {}", clientCert); | ||
return null; | ||
} | ||
|
||
return clientCert.getSubjectX500Principal().getName(); | ||
} | ||
|
||
/** | ||
* Gets the serial number for the client certificate if available. | ||
* | ||
* @param request the {@link HttpServletRequest} containing the certificate | ||
* @return the serial number | ||
*/ | ||
public static BigInteger getClientSslSerialNumber(HttpServletRequest request) { | ||
X509Certificate clientCert = getClientCertificate(request); | ||
if (clientCert == null) { | ||
LOGGER.debug("No client cert available"); | ||
return null; | ||
} | ||
|
||
return clientCert.getSerialNumber(); | ||
} | ||
|
||
/** | ||
* Gets the {@link X509Certificate} for the {@link HttpServletRequest}'s client SSL certificate if | ||
* available. | ||
* | ||
* @param request the {@link HttpServletRequest} to get the client SSL certificate for | ||
* @return the {@link X509Certificate} for the {@link HttpServletRequest}'s client SSL | ||
* certificate, or <code>null</code> if that's not available | ||
*/ | ||
private static X509Certificate getClientCertificate(HttpServletRequest request) { | ||
X509Certificate[] certs = | ||
(X509Certificate[]) request.getAttribute("jakarta.servlet.request.X509Certificate"); | ||
if (certs == null || certs.length == 0) { | ||
LOGGER.debug("No client certificate found for request."); | ||
return null; | ||
} | ||
return certs[certs.length - 1]; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.