org.opensaml.ws.security.provider.ClientCertAuthRule Maven / Gradle / Ivy
Show all versions of openws Show documentation
/*
* Licensed to the University Corporation for Advanced Internet Development,
* Inc. (UCAID) under one or more contributor license agreements. See the
* NOTICE file distributed with this work for additional information regarding
* copyright ownership. The UCAID licenses 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 org.opensaml.ws.security.provider;
import java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import org.opensaml.ws.message.MessageContext;
import org.opensaml.ws.security.SecurityPolicyException;
import org.opensaml.xml.security.CriteriaSet;
import org.opensaml.xml.security.credential.Credential;
import org.opensaml.xml.security.credential.UsageType;
import org.opensaml.xml.security.criteria.EntityIDCriteria;
import org.opensaml.xml.security.criteria.UsageCriteria;
import org.opensaml.xml.security.trust.TrustEngine;
import org.opensaml.xml.security.x509.X509Credential;
import org.opensaml.xml.security.x509.X509Util;
import org.opensaml.xml.util.Base64;
import org.opensaml.xml.util.DatatypeHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Policy rule that checks if the client cert used to authenticate the request is valid and trusted.
*
*
* This rule is only evaluated if the message context contains a peer {@link X509Credential} as returned from the
* inbound message context's inbound message transport {@link org.opensaml.ws.transport.Transport#getPeerCredential()}.
*
*
*
* The entity ID used to perform trust evaluation of the X509 credential is first retrieved via
* {@link #getCertificatePresenterEntityID(MessageContext)}. If this value is non-null, trust evaluation proceeds on
* that basis. If trust evaluation using this entity ID is successful, the message context's inbound transport
* authentication state will be set to true
and processing is terminated. If unsuccessful, a
* {@link SecurityPolicyException} is thrown.
*
*
*
* If a non-null value was available from {@link #getCertificatePresenterEntityID(MessageContext)}, then rule evaluation
* will be attempted as described in {@link #evaluateCertificateNameDerivedPresenters(X509Credential, MessageContext)},
* based on the currently configured certificate name evaluation options. If this method returns a non-null certificate
* presenter entity ID, it will be set on the message context by calling
* {@link #setAuthenticatedCertificatePresenterEntityID(MessageContext, String)} The message context's inbound transport
* authentication state will be set to true
via
* {@link org.opensaml.ws.transport.InTransport#setAuthenticated(boolean)}. Rule processing is then terminated. If the
* method returns null, the client certificate presenter entity ID and inbound transport authentication state will
* remain unmodified and rule processing continues.
*
*
*
* Finally rule evaluation will proceed as described in
* {@link #evaluateDerivedPresenters(X509Credential, MessageContext)}. This is primarily an extension point by which
* subclasses may implement specific custom logic. If this method returns a non-null client certificate presenter entity
* ID, it will be set via {@link #setAuthenticatedCertificatePresenterEntityID(MessageContext, String)}, the message
* context's inbound transport authentication state will be set to true
and rule processing is terminated.
* If the method returns null, the client certificate presenter entity ID and transport authentication state will remain
* unmodified.
*
*/
public class ClientCertAuthRule extends BaseTrustEngineRule {
/** Logger. */
private final Logger log = LoggerFactory.getLogger(ClientCertAuthRule.class);
/** Options for derving client cert presenter entity ID's from an X.509 certificate. */
private CertificateNameOptions certNameOptions;
/**
* Constructor.
*
* @param engine Trust engine used to verify the request X509Credential
* @param nameOptions options for deriving certificate presenter entity ID's from an X.509 certificate
*
*/
public ClientCertAuthRule(TrustEngine engine, CertificateNameOptions nameOptions) {
super(engine);
certNameOptions = nameOptions;
}
/** {@inheritDoc} */
public void evaluate(MessageContext messageContext) throws SecurityPolicyException {
Credential peerCredential = messageContext.getInboundMessageTransport().getPeerCredential();
if (peerCredential == null) {
log.info("Inbound message transport did not contain a peer credential, "
+ "skipping client certificate authentication");
return;
}
if (!(peerCredential instanceof X509Credential)) {
log.info("Inbound message transport did not contain an X509Credential, "
+ "skipping client certificate authentication");
return;
}
X509Credential requestCredential = (X509Credential) peerCredential;
if (log.isDebugEnabled()) {
try {
log.debug("Attempting to authenticate inbound connection that presented the certificate:");
log.debug(Base64.encodeBytes(requestCredential.getEntityCertificate().getEncoded()));
} catch (CertificateEncodingException e) {
// do nothing
}
}
doEvaluate(requestCredential, messageContext);
}
/**
* Get the currently configured certificate name options.
*
* @return the certificate name options
*/
protected CertificateNameOptions getCertificateNameOptions() {
return certNameOptions;
}
/**
* Evaluate the request credential.
*
* @param requestCredential the X509Credential derived from the request
* @param messageContext the message context being evaluated
* @throws SecurityPolicyException thrown if a certificate presenter entity ID available from the message context
* and the client certificate token can not be establishd as trusted on that basis, or if there is error
* during evaluation processing
*/
protected void doEvaluate(X509Credential requestCredential, MessageContext messageContext)
throws SecurityPolicyException {
String presenterEntityID = getCertificatePresenterEntityID(messageContext);
if (presenterEntityID != null) {
log.debug("Attempting client certificate authentication using context presenter entity ID: {}",
presenterEntityID);
if (evaluate(requestCredential, presenterEntityID, messageContext)) {
log.info("Authentication via client certificate succeeded for context presenter entity ID: {}",
presenterEntityID);
messageContext.getInboundMessageTransport().setAuthenticated(true);
} else {
log.error("Authentication via client certificate failed for context presenter entity ID {}",
presenterEntityID);
throw new SecurityPolicyException(
"Client certificate authentication failed for context presenter entity ID");
}
return;
}
String derivedPresenter = evaluateCertificateNameDerivedPresenters(requestCredential, messageContext);
if (derivedPresenter != null) {
log.info("Authentication via client certificate succeeded for certificate-derived presenter entity ID {}",
derivedPresenter);
setAuthenticatedCertificatePresenterEntityID(messageContext, derivedPresenter);
messageContext.getInboundMessageTransport().setAuthenticated(true);
return;
}
derivedPresenter = evaluateDerivedPresenters(requestCredential, messageContext);
if (derivedPresenter != null) {
log.info("Authentication via client certificate succeeded for derived presenter entity ID {}",
derivedPresenter);
setAuthenticatedCertificatePresenterEntityID(messageContext, derivedPresenter);
messageContext.getInboundMessageTransport().setAuthenticated(true);
return;
}
}
/**
* Get the entity ID of the presenter of the client TLS certificate, as will be used for trust evaluation purposes.
*
*
* The default behavior is to return the value of {@link MessageContext#getInboundMessageIssuer()}. Subclasses may
* override to implement different logic.
*
*
* @param messageContext the current message context
* @return the entity ID of the client TLS certificate presenter
*/
protected String getCertificatePresenterEntityID(MessageContext messageContext) {
return messageContext.getInboundMessageIssuer();
}
/**
* Store the sucessfully authenticated derived entity ID of the certificate presenter in the message context.
*
*
* The default behavior is to set the value by calling {@link MessageContext#setInboundMessageIssuer(String)}.
* Subclasses may override to implement different logic.
*
*
* @param messageContext the current message context
* @param entityID the successfully authenticated derived entity ID of the client TLS certificate presenter
*/
protected void setAuthenticatedCertificatePresenterEntityID(MessageContext messageContext, String entityID) {
messageContext.setInboundMessageIssuer(entityID);
}
/** {@inheritDoc} */
protected CriteriaSet buildCriteriaSet(String entityID, MessageContext messageContext)
throws SecurityPolicyException {
CriteriaSet criteriaSet = new CriteriaSet();
if (!DatatypeHelper.isEmpty(entityID)) {
criteriaSet.add(new EntityIDCriteria(entityID));
}
criteriaSet.add(new UsageCriteria(UsageType.SIGNING));
return criteriaSet;
}
/**
* Evaluate any candidate presenter entity ID's which may be derived from the credential or other message context
* information.
*
*
* This serves primarily as an extension point for subclasses to implement application-specific logic.
*
*
*
* If multiple derived candidate entity ID's would satisfy the trust engine criteria, the choice of which one to
* return as the canonical presenter entity ID value is implementation-specific.
*
*
* @param requestCredential the X509Credential derived from the request
* @param messageContext the message context being evaluated
* @return a presenter entity ID which was successfully evaluated by the trust engine
* @throws SecurityPolicyException thrown if there is error during processing
* @deprecated Use {@link #evaluateDerivedPresenters(X509Credential,MessageContext)} instead
*/
protected String evaluateDerivedIssuers(X509Credential requestCredential, MessageContext messageContext)
throws SecurityPolicyException {
return evaluateDerivedPresenters(requestCredential, messageContext);
}
/**
* Evaluate any candidate presenter entity ID's which may be derived from the credential or other message context
* information.
*
*
* This serves primarily as an extension point for subclasses to implement application-specific logic.
*
*
*
* If multiple derived candidate entity ID's would satisfy the trust engine criteria, the choice of which one to
* return as the canonical presenter entity ID value is implementation-specific.
*
*
* @param requestCredential the X509Credential derived from the request
* @param messageContext the message context being evaluated
* @return a presenter entity ID which was successfully evaluated by the trust engine
* @throws SecurityPolicyException thrown if there is error during processing
*/
protected String evaluateDerivedPresenters(X509Credential requestCredential, MessageContext messageContext)
throws SecurityPolicyException {
return null;
}
/**
* Evaluate candidate presenter entity ID's which may be derived from the request credential's entity certificate
* according to the options supplied via {@link CertificateNameOptions}.
*
*
* Configured certificate name types are derived as candidate presenter entity ID's and processed in the following
* order:
*
* - The certificate subject DN string as serialized by the X500DNHandler obtained via
* {@link CertificateNameOptions#getX500DNHandler()} and using the output format indicated by
* {@link CertificateNameOptions#getX500SubjectDNFormat()}.
* - Subject alternative names of the types configured via {@link CertificateNameOptions#getSubjectAltNames()}.
* Note that this is a LinkedHashSet, so the order of evaluation is the order of insertion.
* - The first common name (CN) value appearing in the certificate subject DN.
*
*
*
*
* The first one of the above which is successfully evaluated by the trust engine using criteria built from
* {@link BaseTrustEngineRule#buildCriteriaSet(String, MessageContext)} will be returned.
*
*
* @param requestCredential the X509Credential derived from the request
* @param messageContext the message context being evaluated
* @return a certificate presenter entity ID which was successfully evaluated by the trust engine
* @throws SecurityPolicyException thrown if there is error during processing
* @deprecated Use {@link #evaluateCertificateNameDerivedPresenters(X509Credential,MessageContext)} instead
*/
protected String evaluateCertificateNameDerivedIssuers(X509Credential requestCredential,
MessageContext messageContext) throws SecurityPolicyException {
return evaluateCertificateNameDerivedPresenters(requestCredential, messageContext);
}
/**
* Evaluate candidate presenter entity ID's which may be derived from the request credential's entity certificate
* according to the options supplied via {@link CertificateNameOptions}.
*
*
* Configured certificate name types are derived as candidate presenter entity ID's and processed in the following
* order:
*
* - The certificate subject DN string as serialized by the X500DNHandler obtained via
* {@link CertificateNameOptions#getX500DNHandler()} and using the output format indicated by
* {@link CertificateNameOptions#getX500SubjectDNFormat()}.
* - Subject alternative names of the types configured via {@link CertificateNameOptions#getSubjectAltNames()}.
* Note that this is a LinkedHashSet, so the order of evaluation is the order of insertion.
* - The first common name (CN) value appearing in the certificate subject DN.
*
*
*
*
* The first one of the above which is successfully evaluated by the trust engine using criteria built from
* {@link BaseTrustEngineRule#buildCriteriaSet(String, MessageContext)} will be returned.
*
*
* @param requestCredential the X509Credential derived from the request
* @param messageContext the message context being evaluated
* @return a certificate presenter entity ID which was successfully evaluated by the trust engine
* @throws SecurityPolicyException thrown if there is error during processing
*/
protected String evaluateCertificateNameDerivedPresenters(X509Credential requestCredential,
MessageContext messageContext) throws SecurityPolicyException {
String candidatePresenter = null;
if (certNameOptions.evaluateSubjectDN()) {
candidatePresenter = evaluateSubjectDN(requestCredential, messageContext);
if (candidatePresenter != null) {
return candidatePresenter;
}
}
if (!certNameOptions.getSubjectAltNames().isEmpty()) {
candidatePresenter = evaluateSubjectAltNames(requestCredential, messageContext);
if (candidatePresenter != null) {
return candidatePresenter;
}
}
if (certNameOptions.evaluateSubjectCommonName()) {
candidatePresenter = evaluateSubjectCommonName(requestCredential, messageContext);
if (candidatePresenter != null) {
return candidatePresenter;
}
}
return null;
}
/**
* Evaluate the presenter entity ID as derived from the cert subject common name (CN).
*
* Only the first CN value from the subject DN is evaluated.
*
* @param requestCredential the X509Credential derived from the request
* @param messageContext the message context being evaluated
* @return a presenter entity ID which was successfully evaluated by the trust engine
* @throws SecurityPolicyException thrown if there is error during processing
*/
protected String evaluateSubjectCommonName(X509Credential requestCredential, MessageContext messageContext)
throws SecurityPolicyException {
log.debug("Evaluating client cert by deriving presenter as cert CN");
X509Certificate certificate = requestCredential.getEntityCertificate();
String candidatePresenter = getCommonName(certificate);
if (candidatePresenter != null) {
if (evaluate(requestCredential, candidatePresenter, messageContext)) {
log.info("Authentication succeeded for presenter entity ID derived from CN {}", candidatePresenter);
return candidatePresenter;
}
}
return null;
}
/**
* Evaluate the presenter entity ID as derived from the cert subject DN.
*
* @param requestCredential the X509Credential derived from the request
* @param messageContext the message context being evaluated
* @return a presenter entity ID which was successfully evaluated by the trust engine
* @throws SecurityPolicyException thrown if there is error during processing
*/
protected String evaluateSubjectDN(X509Credential requestCredential, MessageContext messageContext)
throws SecurityPolicyException {
log.debug("Evaluating client cert by deriving presenter as cert subject DN");
X509Certificate certificate = requestCredential.getEntityCertificate();
String candidatePresenter = getSubjectName(certificate);
if (candidatePresenter != null) {
if (evaluate(requestCredential, candidatePresenter, messageContext)) {
log.info("Authentication succeeded for presenter entity ID derived from subject DN {}",
candidatePresenter);
return candidatePresenter;
}
}
return null;
}
/**
* Evaluate the presenter entity ID as derived from the cert subject alternative names specified by types enumerated
* in {@link CertificateNameOptions#getSubjectAltNames()}.
*
* @param requestCredential the X509Credential derived from the request
* @param messageContext the message context being evaluated
* @return a presenter entity ID which was successfully evaluated by the trust engine
* @throws SecurityPolicyException thrown if there is error during processing
*/
protected String evaluateSubjectAltNames(X509Credential requestCredential, MessageContext messageContext)
throws SecurityPolicyException {
log.debug("Evaluating client cert by deriving presenter from subject alt names");
X509Certificate certificate = requestCredential.getEntityCertificate();
for (Integer altNameType : certNameOptions.getSubjectAltNames()) {
log.debug("Evaluating alt names of type: {}", altNameType.toString());
List altNames = getAltNames(certificate, altNameType);
for (String altName : altNames) {
if (evaluate(requestCredential, altName, messageContext)) {
log.info("Authentication succeeded for presenter entity ID derived from subject alt name {}",
altName);
return altName;
}
}
}
return null;
}
/**
* Get the first common name (CN) value from the subject DN of the specified certificate.
*
* @param cert the certificate being processed
* @return the first CN value, or null if there are none
*/
protected String getCommonName(X509Certificate cert) {
List names = X509Util.getCommonNames(cert.getSubjectX500Principal());
if (names != null && !names.isEmpty()) {
String name = names.get(0);
log.debug("Extracted common name from certificate: {}", name);
return name;
}
return null;
}
/**
* Get subject name from a certificate, using the currently configured X500DNHandler and subject DN output format.
*
* @param cert the certificate being processed
* @return the subject name
*/
protected String getSubjectName(X509Certificate cert) {
if (cert == null) {
return null;
}
String name = null;
if (!DatatypeHelper.isEmpty(certNameOptions.getX500SubjectDNFormat())) {
name = certNameOptions.getX500DNHandler().getName(cert.getSubjectX500Principal(),
certNameOptions.getX500SubjectDNFormat());
} else {
name = certNameOptions.getX500DNHandler().getName(cert.getSubjectX500Principal());
}
log.debug("Extracted subject name from certificate: {}", name);
return name;
}
/**
* Get the list of subject alt name values from the certificate which are of the specified alt name type.
*
* @param cert the certificate from which to extract alt names
* @param altNameType the type of alt name to extract
*
* @return the list of certificate subject alt names
*/
protected List getAltNames(X509Certificate cert, Integer altNameType) {
log.debug("Extracting alt names from certificate of type: {}", altNameType.toString());
Integer[] nameTypes = new Integer[] { altNameType };
List altNames = X509Util.getAltNames(cert, nameTypes);
List names = new ArrayList();
for (Object altNameValue : altNames) {
if (!(altNameValue instanceof String)) {
log.debug("Skipping non-String certificate alt name value");
} else {
names.add((String) altNameValue);
}
}
log.debug("Extracted alt names from certificate: {}", names.toString());
return names;
}
}