All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opensaml.ws.security.provider.ClientCertAuthRule Maven / Gradle / Ivy

Go to download

The OpenWS library provides a growing set of tools to work with web services at a low level. These tools include classes for creating and reading SOAP messages, transport-independent clients for connecting to web services, and various transports for use with those clients.

The newest version!
/*
 * 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: *

    *
  1. 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()}.
  2. *
  3. 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.
  4. *
  5. The first common name (CN) value appearing in the certificate subject DN.
  6. *
*

* *

* 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: *

    *
  1. 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()}.
  2. *
  3. 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.
  4. *
  5. The first common name (CN) value appearing in the certificate subject DN.
  6. *
*

* *

* 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; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy