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

org.cesecore.authentication.tokens.X509CertificateAuthenticationToken Maven / Gradle / Ivy

/*************************************************************************
 *                                                                       *
 *  CESeCore: CE Security Core                                           *
 *                                                                       *
 *  This software is free software; you can redistribute it and/or       *
 *  modify it under the terms of the GNU Lesser General Public           *
 *  License as published by the Free Software Foundation; either         *
 *  version 2.1 of the License, or any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/
package org.cesecore.authentication.tokens;

import java.io.ByteArrayInputStream;
import java.math.BigInteger;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

import javax.security.auth.x500.X500Principal;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.cesecore.authorization.user.AccessUserAspect;
import org.cesecore.authorization.user.matchvalues.X500PrincipalAccessMatchValue;
import org.cesecore.certificates.util.DNFieldExtractor;
import org.cesecore.util.CertTools;

/**
 * This is an implementation of the AuthenticationToken concept, based on using an {@link X509Certificate} as it's single credential, and that
 * certificate's {@link X500Principal} as its principle, but as the X500Principle is contained in the X509Certificate, this remains little more than a
 * formality. This AuthenticationToken is the default used in EJBCA.
 * 
 * The implementation of the matches(...) method is based on AdminEntity.java 10832 2010-12-13 13:54:25Z anatom from EJBCA.
 * 
 * 
 * @version $Id: X509CertificateAuthenticationToken.java 31392 2019-02-05 10:44:16Z samuellb $
 * 
 */
public class X509CertificateAuthenticationToken extends NestableAuthenticationToken {

    public static final X509CertificateAuthenticationTokenMetaData metaData = new X509CertificateAuthenticationTokenMetaData();
    
    private static final Logger log = Logger.getLogger(X509CertificateAuthenticationToken.class);
    private static final long serialVersionUID = 1097165653913865515L;

    private static final Pattern serialPattern = Pattern.compile("\\bSERIALNUMBER=", Pattern.CASE_INSENSITIVE);

    private final X509Certificate certificate;
    // get the subjectDN from the certificate and keep it for caching (speed optimization)
    private transient String adminSubjectDN;

    private final int adminCaId;
    private final DNFieldExtractor dnExtractor;
    private final DNFieldExtractor anExtractor;

    /**
     * Standard constructor for X509CertificateAuthenticationToken
     * 
     * @param principals
     *            A set of X500Principals. Should contain one and only one value.
     * @param credentials
     *            A set of X509Certificates. As with the principals, this set should contain one and only one value, anything else will result in a
     *            {@link InvalidAuthenticationTokenException} being thrown.
     */
    public X509CertificateAuthenticationToken(final Set principals, final Set credentials) {
        super(principals, credentials);
        /*
         * In order to save having to verify the credentials set every time the matches(...) method is called, it's checked here, and the
         * resulting credential is stored locally.
         */
        final X509Certificate[] certificateArray = getCredentials().toArray(new X509Certificate[0]);
        if (certificateArray.length != 1) {
            throw new InvalidAuthenticationTokenException("X509CertificateAuthenticationToken was containing " + certificateArray.length
                    + " credentials instead of 1.");
        } else {
            // Speed optimization, make it into a BC class, since we will want that many times later on
            final String clazz = certificateArray[0].getClass().getName();
            if (clazz.contains("org.bouncycastle")) {
                certificate = certificateArray[0];
            } else {
                final CertificateFactory cf = CertTools.getCertificateFactory();
                X509Certificate cert;
                try {
                    cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(certificateArray[0].getEncoded()));
                } catch (CertificateException e) {
                    log.warn("Error encoding/decoding client TLS certificate in BC, just passing instead of optimizing: ", e);
                    cert = certificateArray[0];
                }
                certificate = cert;
            } 
        }
        String certstring = CertTools.getSubjectDN(certificate).toString();
        adminCaId = CertTools.getIssuerDN(certificate).hashCode();
        adminSubjectDN = CertTools.getSubjectDN(certificate);
        certstring = serialPattern.matcher(certstring).replaceAll("SN=");
        final String altNameString = CertTools.getSubjectAlternativeName(certificate);
        dnExtractor = new DNFieldExtractor(certstring, DNFieldExtractor.TYPE_SUBJECTDN);
        anExtractor = new DNFieldExtractor(altNameString, DNFieldExtractor.TYPE_SUBJECTALTNAME);
    }

    /**
     * Standard simplified constructor for X509CertificateAuthenticationToken
     * 
     * @param certificate A X509Certificate that will be used as principal and credential.
     * @throws NullPointerException if the provided certificate is null
     */
    public X509CertificateAuthenticationToken(final X509Certificate certificate) {
        this(new HashSet<>(Arrays.asList(certificate.getSubjectX500Principal())), new HashSet<>(Arrays.asList(certificate)));
    }

    @Override
    public boolean matches(AccessUserAspect accessUser) {
        // Protect against spoofing by checking if this token was created locally
        if (!super.isCreatedInThisJvm()) {
            return false;
        } 
        boolean returnvalue = false;
        int parameter;
        int size = 0;
        String[] clientstrings = null;
        if (StringUtils.equals(getMetaData().getTokenType(), accessUser.getTokenType())) {
            // First check that issuers match.
            if (accessUser.getCaId() == adminCaId) {
                // Check if we actually have some value to match against, null is not an allowed match value
                if (accessUser.getMatchValue() != null) {                    
                    // Determine part of certificate to match with.
                    DNFieldExtractor usedExtractor = dnExtractor;
                    X500PrincipalAccessMatchValue matchValue = (X500PrincipalAccessMatchValue) getMatchValueFromDatabaseValue(accessUser.getMatchWith());
                    if (matchValue == X500PrincipalAccessMatchValue.WITH_SERIALNUMBER) {
                        try {
                            BigInteger matchValueAsBigInteger = new BigInteger(accessUser.getMatchValue(), 16);
                            switch (accessUser.getMatchTypeAsType()) {
                            case TYPE_EQUALCASE:
                            case TYPE_EQUALCASEINS:
                                returnvalue = matchValueAsBigInteger.equals(certificate.getSerialNumber());
                                break;
                            case TYPE_NOT_EQUALCASE:
                            case TYPE_NOT_EQUALCASEINS:
                                returnvalue = !matchValueAsBigInteger.equals(certificate.getSerialNumber());
                                break;
                            default:
                            }
                        } catch (NumberFormatException nfe) {
                            log.info("Invalid matchValue for accessUser when expecting a hex serialNumber: "+accessUser.getMatchValue());
                        }
                    } else if (matchValue == X500PrincipalAccessMatchValue.WITH_FULLDN) {
                        String value = accessUser.getMatchValue();
                        switch (accessUser.getMatchTypeAsType()) {
                        case TYPE_EQUALCASE:
                            returnvalue = value.equals(CertTools.getSubjectDN(certificate));
                            break;
                        case TYPE_EQUALCASEINS:
                            returnvalue = value.equalsIgnoreCase(CertTools.getSubjectDN(certificate));
                            break;
                        case TYPE_NOT_EQUALCASE:
                            returnvalue = !value.equals(CertTools.getSubjectDN(certificate));
                        case TYPE_NOT_EQUALCASEINS:
                            returnvalue = !value.equalsIgnoreCase(CertTools.getSubjectDN(certificate));
                            break;
                        default:
                        }
                    } else {
                        parameter = DNFieldExtractor.CN;
                        switch (matchValue) {
                        case WITH_COUNTRY:
                            parameter = DNFieldExtractor.C;
                            break;
                        case WITH_DOMAINCOMPONENT:
                            parameter = DNFieldExtractor.DC;
                            break;
                        case WITH_STATEORPROVINCE:
                            parameter = DNFieldExtractor.ST;
                            break;
                        case WITH_LOCALITY:
                            parameter = DNFieldExtractor.L;
                            break;
                        case WITH_ORGANIZATION:
                            parameter = DNFieldExtractor.O;
                            break;
                        case WITH_ORGANIZATIONALUNIT:
                            parameter = DNFieldExtractor.OU;
                            break;
                        case WITH_TITLE:
                            parameter = DNFieldExtractor.T;
                            break;
                        case WITH_DNSERIALNUMBER:
                            parameter = DNFieldExtractor.SN;
                            break;
                        case WITH_COMMONNAME:
                            parameter = DNFieldExtractor.CN;
                            break;
                        case WITH_UID:
                            parameter = DNFieldExtractor.UID;
                            break;
                        case WITH_DNEMAILADDRESS:
                            parameter = DNFieldExtractor.E;
                            break;
                        case WITH_RFC822NAME:
                            parameter = DNFieldExtractor.RFC822NAME;
                            usedExtractor = anExtractor;
                            break;
                        case WITH_UPN:
                            parameter = DNFieldExtractor.UPN;
                            usedExtractor = anExtractor;
                            break;
                        default:
                        }
                        size = usedExtractor.getNumberOfFields(parameter);
                        clientstrings = new String[size];
                        for (int i = 0; i < size; i++) {
                            clientstrings[i] = usedExtractor.getField(parameter, i);
                        }

                        // Determine how to match.
                        if (clientstrings != null) {
                            switch (accessUser.getMatchTypeAsType()) {
                            case TYPE_EQUALCASE:
                                String accessUserMatchValue = accessUser.getMatchValue();
                                for (int i = 0; i < size; i++) {
                                    returnvalue = clientstrings[i].equals(accessUserMatchValue);
                                    if (returnvalue) {
                                        break;
                                    }
                                }
                                break;
                            case TYPE_EQUALCASEINS:
                                for (int i = 0; i < size; i++) {
                                    returnvalue = clientstrings[i].equalsIgnoreCase(accessUser.getMatchValue());
                                    if (returnvalue) {
                                        break;
                                    }
                                }
                                break;
                            case TYPE_NOT_EQUALCASE:
                                for (int i = 0; i < size; i++) {
                                    returnvalue = !clientstrings[i].equals(accessUser.getMatchValue());
                                    if (returnvalue) {
                                        break;
                                    }
                                }
                                break;
                            case TYPE_NOT_EQUALCASEINS:
                                for (int i = 0; i < size; i++) {
                                    returnvalue = !clientstrings[i].equalsIgnoreCase(accessUser.getMatchValue());
                                    if (returnvalue) {
                                        break;
                                    }
                                }
                                break;
                            default:
                            }
                        }
                    }
                } else {
                    if (log.isTraceEnabled()) {
                        log.trace("Match value is null and could not be matched. A value is required.");
                    }
                }
            } else {
                if (log.isTraceEnabled()) {
                    log.trace("Caid does not match. Required="+adminCaId+", actual was "+accessUser.getCaId());
                }
            }
        } else {
            if (log.isTraceEnabled()) {
                log.trace("Token type does not match. Required="+getMetaData().getTokenType()+", actual was "+accessUser.getTokenType());
            }            
        }

        return returnvalue;
    }
    
    @Override
    public int getPreferredMatchKey() {
        return X500PrincipalAccessMatchValue.WITH_SERIALNUMBER.getNumericValue();
    }
    
    /** Returns the serial number as a decimal string */
    @Override
    public String getPreferredMatchValue() {
        return CertTools.getSerialNumberAsString(certificate);
    }

    /** Returns user information of the user this authentication token belongs to. */
    @Override
    public String toString() {
    	return super.toString();
    }

    /** Override the default X500Principal.getName() when doing toString on this object. */
    @Override
    protected String toStringOverride() {
        // Return cached value to optimize, because this can be called multiple times during the tokens lifetime
        if (adminSubjectDN == null) {
            adminSubjectDN = CertTools.getSubjectDN(certificate);
        }
        return adminSubjectDN;
    }

    @Override
    public int hashCode() {
        final int prime = 4711;
        int result = 1;
        result = prime * result + ((certificate == null) ? 0 : certificate.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        X509CertificateAuthenticationToken other = (X509CertificateAuthenticationToken) obj;
        if (certificate == null) {
            if (other.certificate != null) {
                return false;
            }
        } else if (!certificate.equals(other.certificate)) {
            return false;
        }
        return true;
    }

    /**
     * @return the certificate
     */
    public X509Certificate getCertificate() {
        return certificate;
    }
    
    @Override
    protected String generateUniqueId() {
        byte[] encodedCertificate = null;
        try {
            encodedCertificate = certificate.getEncoded();
        } catch (CertificateEncodingException e) {
            throw new IllegalStateException(e);
        }
        return generateUniqueId(super.isCreatedInThisJvm(), encodedCertificate) + ";" + super.generateUniqueId();
    }

    @Override
    public X509CertificateAuthenticationTokenMetaData getMetaData() {
        return metaData;
    }
}





© 2015 - 2024 Weber Informatics LLC | Privacy Policy