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

org.opensaml.xmlsec.keyinfo.impl.provider.InlineX509DataProvider Maven / Gradle / Ivy

/*
 * 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.xmlsec.keyinfo.impl.provider;

import java.math.BigInteger;
import java.security.PublicKey;
import java.security.cert.CRLException;
import java.security.cert.CertificateException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.security.auth.x500.X500Principal;

import net.shibboleth.utilities.java.support.codec.Base64Support;
import net.shibboleth.utilities.java.support.collection.LazySet;
import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.resolver.CriteriaSet;

import org.opensaml.core.xml.XMLObject;
import org.opensaml.security.SecurityException;
import org.opensaml.security.credential.Credential;
import org.opensaml.security.credential.CredentialContext;
import org.opensaml.security.x509.BasicX509Credential;
import org.opensaml.security.x509.InternalX500DNHandler;
import org.opensaml.security.x509.X500DNHandler;
import org.opensaml.security.x509.X509Support;
import org.opensaml.xmlsec.algorithm.AlgorithmSupport;
import org.opensaml.xmlsec.keyinfo.KeyInfoCredentialResolver;
import org.opensaml.xmlsec.keyinfo.KeyInfoSupport;
import org.opensaml.xmlsec.keyinfo.impl.KeyInfoResolutionContext;
import org.opensaml.xmlsec.signature.X509Data;
import org.opensaml.xmlsec.signature.X509Digest;
import org.opensaml.xmlsec.signature.X509IssuerSerial;
import org.opensaml.xmlsec.signature.X509SKI;
import org.opensaml.xmlsec.signature.X509SubjectName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Strings;

/**
 * Implementation of {@link org.opensaml.xmlsec.keyinfo.impl.KeyInfoProvider} which provides basic support for
 * extracting a {@link org.opensaml.security.x509.X509Credential} from an {@link X509Data} child of KeyInfo.
 * 
 * This provider supports only inline {@link X509Certificate}'s and {@link org.opensaml.xmlsec.signature.X509CRL}s.
 * If only one certificate is present, it is assumed to be the end-entity certificate containing the public key
 * represented by this KeyInfo. If multiple certificates are present, and any instances of {@link X509SubjectName},
 * {@link X509IssuerSerial}, {@link X509SKI}, or {@link X509Digest} are also present, they will be used to identify
 * the end-entity certificate, in accordance with the XML Signature specification. If a public key from a previously
 * resolved {@link org.opensaml.xmlsec.signature.KeyValue} is available in the resolution context, it will also be
 * used to identify the end-entity certificate. If the end-entity certificate can not otherwise be identified, the
 * cert contained in the first X509Certificate element will be treated as the end-entity certificate.
 */
public class InlineX509DataProvider extends AbstractKeyInfoProvider {

    /** Class logger. */
    private final Logger log = LoggerFactory.getLogger(InlineX509DataProvider.class);

    /** Responsible for parsing and serializing X.500 names to/from {@link X500Principal} instances. */
    private X500DNHandler x500DNHandler;

    /**
     * Constructor.
     */
    public InlineX509DataProvider() {
        x500DNHandler = new InternalX500DNHandler();
    }

    /**
     * Get the handler which process X.500 distinguished names.
     * 
     * @return returns the X500DNHandler instance
     */
    @Nonnull public X500DNHandler getX500DNHandler() {
        return x500DNHandler;
    }

    /**
     * Set the handler which process X.500 distinguished names.
     * 
     * @param handler the new X500DNHandler instance
     */
    public void setX500DNHandler(@Nonnull final X500DNHandler handler) {
        x500DNHandler = Constraint.isNotNull(handler, "X500DNHandler cannot be null");
    }

    /** {@inheritDoc} */
    public boolean handles(@Nonnull final XMLObject keyInfoChild) {
        return keyInfoChild instanceof X509Data;
    }

    /** {@inheritDoc} */
    @Nullable public Collection process(@Nonnull final KeyInfoCredentialResolver resolver,
            @Nonnull final XMLObject keyInfoChild, @Nullable final CriteriaSet criteriaSet,
            @Nonnull final KeyInfoResolutionContext kiContext) throws SecurityException {

        if (!handles(keyInfoChild)) {
            return null;
        }

        final X509Data x509Data = (X509Data) keyInfoChild;

        log.debug("Attempting to extract credential from an X509Data");

        final List certs = extractCertificates(x509Data);
        if (certs.isEmpty()) {
            log.info("The X509Data contained no X509Certificate elements, skipping credential extraction");
            return null;
        }
        final List crls = extractCRLs(x509Data);

        PublicKey resolvedPublicKey = null;
        if (kiContext.getKey() != null && kiContext.getKey() instanceof PublicKey) {
            resolvedPublicKey = (PublicKey) kiContext.getKey();
        }
        final X509Certificate entityCert = findEntityCert(certs, x509Data, resolvedPublicKey);
        if (entityCert == null) {
            log.warn("The end-entity cert could not be identified, skipping credential extraction");
            return null;
        }

        final BasicX509Credential cred = new BasicX509Credential(entityCert);
        cred.setCRLs(crls);
        cred.setEntityCertificateChain(certs);

        cred.getKeyNames().addAll(kiContext.getKeyNames());

        final CredentialContext credContext = buildCredentialContext(kiContext);
        if (credContext != null) {
            cred.getCredentialContextSet().add(credContext);
        }

        final LazySet credentialSet = new LazySet<>();
        credentialSet.add(cred);
        return credentialSet;
    }

    /**
     * Extract CRLs from the X509Data.
     * 
     * @param x509Data the X509Data element
     * @return a list of X509CRLs
     * @throws SecurityException thrown if there is an error extracting CRLs
     */
    @Nonnull private List extractCRLs(@Nonnull final X509Data x509Data) throws SecurityException {
        List crls = null;
        try {
            crls = KeyInfoSupport.getCRLs(x509Data);
        } catch (final CRLException e) {
            log.error("Error extracting CRLs from X509Data", e);
            throw new SecurityException("Error extracting CRLs from X509Data", e);
        }

        log.debug("Found {} X509CRLs", crls.size());
        return crls;
    }

    /**
     * Extract certificates from the X509Data.
     * 
     * @param x509Data the X509Data element
     * @return a list of X509Certificates
     * @throws SecurityException thrown if there is an error extracting certificates
     */
    @Nonnull private List extractCertificates(@Nonnull final X509Data x509Data)
            throws SecurityException {
        List certs = null;
        try {
            certs = KeyInfoSupport.getCertificates(x509Data);
        } catch (final CertificateException e) {
            log.error("Error extracting certificates from X509Data", e);
            throw new SecurityException("Error extracting certificates from X509Data", e);
        }
        log.debug("Found {} X509Certificates", certs.size());
        return certs;
    }

    /**
     * Find the end-entity cert in the list of certs contained in the X509Data.
     * 
     * @param certs list of {@link java.security.cert.X509Certificate}
     * @param x509Data X509Data element which might contain other info helping to finding the end-entity cert
     * @param resolvedKey a key which might have previously been resolved from a KeyValue
     * @return the end-entity certificate, if found
     */
    @Nullable protected X509Certificate findEntityCert(@Nullable final List certs,
            @Nonnull final X509Data x509Data, @Nullable final PublicKey resolvedKey) {
        if (certs == null || certs.isEmpty()) {
            return null;
        }

        // If there is only 1 certificate, treat it as the end-entity certificate
        if (certs.size() == 1) {
            log.debug("Single certificate was present, treating as end-entity certificate");
            return certs.get(0);
        }

        X509Certificate cert = null;

        // Check against public key already resolved in resolution context
        cert = findCertFromKey(certs, resolvedKey);
        if (cert != null) {
            log.debug("End-entity certificate resolved by matching previously resolved public key");
            return cert;
        }

        // Check against any subject names
        cert = findCertFromSubjectNames(certs, x509Data.getX509SubjectNames());
        if (cert != null) {
            log.debug("End-entity certificate resolved by matching X509SubjectName");
            return cert;
        }

        // Check against issuer serial
        cert = findCertFromIssuerSerials(certs, x509Data.getX509IssuerSerials());
        if (cert != null) {
            log.debug("End-entity certificate resolved by matching X509IssuerSerial");
            return cert;
        }

        // Check against any subject key identifiers
        cert = findCertFromSubjectKeyIdentifier(certs, x509Data.getX509SKIs());
        if (cert != null) {
            log.debug("End-entity certificate resolved by matching X509SKI");
            return cert;
        }

        // Check against any subject X509 digests
        cert = findCertFromDigest(certs, x509Data.getX509Digests());
        if (cert != null) {
            log.debug("End-entity certificate resolved by matching X509Digest");
            return cert;
        }
        
        // TODO use some heuristic algorithm to try and figure it out based on the cert list alone.
        // This would be in X509Utils or somewhere else external to this class.

        // As a final fallback, treat the first cert in the X509Data element as the entity cert
        log.debug("Treating the first certificate in the X509Data as the end-entity certificate");
        return certs.get(0);
    }

    /**
     * Find the certificate from the chain that contains the specified key.
     * 
     * @param certs list of certificates to evaluate
     * @param key key to use as search criteria
     * @return the matching certificate, or null
     */
    @Nullable protected X509Certificate findCertFromKey(@Nonnull final List certs,
            @Nullable final PublicKey key) {
        if (key != null) {
            for (final X509Certificate cert : certs) {
                if (cert.getPublicKey().equals(key)) {
                    return cert;
                }
            }
        }
        return null;
    }

    /**
     * Find the certificate from the chain that contains one of the specified subject names.
     * 
     * @param certs list of certificates to evaluate
     * @param names X509 subject names to use as search criteria
     * @return the matching certificate, or null
     */
    @Nullable protected X509Certificate findCertFromSubjectNames(@Nonnull final List certs,
            @Nonnull final List names) {
        for (final X509SubjectName subjectName : names) {
            if (!Strings.isNullOrEmpty(subjectName.getValue())) {
                X500Principal subjectX500Principal = null;
                try {
                    subjectX500Principal = x500DNHandler.parse(subjectName.getValue());
                } catch (final IllegalArgumentException e) {
                    log.warn("X500 subject name '{}' could not be parsed by configured X500DNHandler '{}'",
                            subjectName.getValue(), x500DNHandler.getClass().getName());
                    return null;
                }
                for (final X509Certificate cert : certs) {
                    if (cert.getSubjectX500Principal().equals(subjectX500Principal)) {
                        return cert;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Find the certificate from the chain identified by one of the specified issuer serials.
     * 
     * @param certs list of certificates to evaluate
     * @param serials X509 issuer serials to use as search criteria
     * @return the matching certificate, or null
     */
    @Nullable protected X509Certificate findCertFromIssuerSerials(@Nonnull final List certs,
            @Nonnull final List serials) {
        for (final X509IssuerSerial issuerSerial : serials) {
            if (issuerSerial.getX509IssuerName() == null || issuerSerial.getX509SerialNumber() == null) {
                continue;
            }
            final String issuerNameValue = issuerSerial.getX509IssuerName().getValue();
            final BigInteger serialNumber = issuerSerial.getX509SerialNumber().getValue();
            if (!Strings.isNullOrEmpty(issuerNameValue)) {
                X500Principal issuerX500Principal = null;
                try {
                    issuerX500Principal = x500DNHandler.parse(issuerNameValue);
                } catch (final IllegalArgumentException e) {
                    log.warn("X500 issuer name '{}' could not be parsed by configured X500DNHandler '{}'",
                            issuerNameValue, x500DNHandler.getClass().getName());
                    return null;
                }
                for (final X509Certificate cert : certs) {
                    if (cert.getIssuerX500Principal().equals(issuerX500Principal)
                            && cert.getSerialNumber().equals(serialNumber)) {
                        return cert;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Find the certificate from the chain that contains one of the specified subject key identifiers.
     * 
     * @param certs list of certificates to evaluate
     * @param skis X509 subject key identifiers to use as search criteria
     * @return the matching certificate, or null
     */
    @Nullable protected X509Certificate findCertFromSubjectKeyIdentifier(@Nonnull final List certs,
            @Nonnull final List skis) {
        for (final X509SKI ski : skis) {
            if (!Strings.isNullOrEmpty(ski.getValue())) {
                final byte[] xmlValue = Base64Support.decode(ski.getValue());
                for (final X509Certificate cert : certs) {
                    final byte[] certValue = X509Support.getSubjectKeyIdentifier(cert);
                    if (certValue != null && Arrays.equals(xmlValue, certValue)) {
                        return cert;
                    }
                }
            }
        }
        return null;
    }

    /**
     * Find the certificate from the chain that matches one of the specified digests.
     * 
     * @param certs list of certificates to evaluate
     * @param digests X509 digests to use as search criteria
     * @return the matching certificate, or null
     */
    @Nullable protected X509Certificate findCertFromDigest(@Nonnull final List certs,
            @Nonnull final List digests) {
        
        for (final X509Digest digest : digests) {
            if (!Strings.isNullOrEmpty(digest.getValue()) && !Strings.isNullOrEmpty(digest.getAlgorithm())) {
                String alg = AlgorithmSupport.getAlgorithmID(digest.getAlgorithm());
                if (alg == null) {
                    log.warn("Algorithm {} not supported", digest.getAlgorithm());
                    continue;
                }
                final byte[] xmlValue = Base64Support.decode(digest.getValue());
                for (X509Certificate cert : certs) {
                    try {
                        final byte[] certValue = X509Support.getX509Digest(cert, alg);
                        if (certValue != null && Arrays.equals(xmlValue, certValue)) {
                            return cert;
                        }
                    } catch (final SecurityException e) {
                        // Ignore as no match.
                    }
                }
            }
        }
        return null;
    } 
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy