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

org.wildfly.security.sasl.entity.EntityUtil Maven / Gradle / Ivy

The newest version!
/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2015 Red Hat, Inc., and individual contributors
 * as indicated by the @author tags.
 *
 * Licensed 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.wildfly.security.sasl.entity;

import static org.wildfly.security.asn1.ASN1.*;
import static org.wildfly.security.mechanism._private.ElytronMessages.saslEntity;
import static org.wildfly.security.sasl.entity.Entity.*;
import static org.wildfly.security.x500.GeneralName.*;
import static org.wildfly.security.x500.TrustedAuthority.*;

import java.io.ByteArrayInputStream;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.CertPath;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;

import javax.security.auth.x500.X500Principal;

import org.wildfly.security.asn1.ASN1Exception;
import org.wildfly.security.asn1.DERDecoder;
import org.wildfly.security.asn1.DEREncoder;
import org.wildfly.security.x500.GeneralName;
import org.wildfly.security.x500.TrustedAuthority;
import org.wildfly.security.x500.X500;
import org.wildfly.security.x500.util.X500PrincipalUtil;

/**
 * @author Farah Juma
 */
class EntityUtil {

    private static final byte[] randomCharDictionary;

    static {
        byte[] dict = new byte[93];
        int i = 0;
        for (byte c = '!'; c < ','; c ++) {
            dict[i ++] = c;
        }
        for (byte c = ',' + 1; c < 127; c ++) {
            dict[i ++] = c;
        }
        assert i == dict.length;
        randomCharDictionary = dict;
    }

    /* -- Methods used to encode ASN.1 data structures required for entity authentication -- */

    /**
     * Encode an ASN.1 set of certificates using the given DER encoder and the
     * given {@code X509Certificate} chain.
     *
     * @param encoder the DER encoder
     * @param certChain the X.509 certificate chain to encode
     * @throws ASN1Exception if an error occurs while encoding the given certificate chain
     */
    public static void encodeX509CertificateChain(final DEREncoder encoder, X509Certificate[] certChain) throws ASN1Exception {
        try {
            int chainSize = certChain.length;
            encoder.startSetOf();
            for (int i = 0; i < chainSize; i++) {
                encoder.writeEncoded(certChain[i].getEncoded());
            }
            encoder.endSetOf();
        } catch (CertificateEncodingException e) {
            throw new ASN1Exception(e);
        }
    }

    /**
     * 

* Encode an {@code AlgorithmIdentifier} without any parameters using the given * DER encoder and object identifier, where {@code AlgorithmIdentifier} is defined as: * *

     *      AlgorithmIdentifier  ::=  SEQUENCE  {
     *          algorithm      OBJECT IDENTIFIER,
     *          parameters     ANY DEFINED BY algorithm OPTIONAL
     *      }
     * 
*

* * @param encoder the DER encoder * @param objectIdentifier the object identifier for the algorithm * @param omitParametersField {@code true} if the parameters field should be ommitted in * the encoding and {@code false} otherwise * @throws ASN1Exception if the given object identifier is invalid */ public static void encodeAlgorithmIdentifier(final DEREncoder encoder, String objectIdentifier, boolean omitParametersField) throws ASN1Exception { encoder.startSequence(); encoder.encodeObjectIdentifier(objectIdentifier); if (!omitParametersField) { encoder.encodeNull(); } encoder.endSequence(); } /** *

* Encode an {@code AlgorithmIdentifier} using the given DER encoder, where * {@code AlgorithmIdentifier} is defined as: * *

     *      AlgorithmIdentifier  ::=  SEQUENCE  {
     *          algorithm      OBJECT IDENTIFIER,
     *          parameters     ANY DEFINED BY algorithm OPTIONAL
     *      }
     * 
*

* * @param encoder the DER encoder * @param algorithm the algorithm name * @throws ASN1Exception if the given algorithm name is unrecognised */ public static void encodeAlgorithmIdentifier(final DEREncoder encoder, String algorithm) throws ASN1Exception { // Determine whether or not the parameters field should be omitted in the encoding, // as specified in RFC 3279 (http://www.ietf.org/rfc/rfc3279) boolean omitParametersField; switch (algorithm) { case SHA1_WITH_RSA: { omitParametersField = false; break; } case SHA1_WITH_DSA: case SHA1_WITH_ECDSA: { omitParametersField = true; break; } default: throw saslEntity.asnUnrecognisedAlgorithm(algorithm); } encodeAlgorithmIdentifier(encoder, algorithmOid(algorithm), omitParametersField); } /** *

* Encode a {@code GeneralNames} element using the given DER encoder, where * {@code GeneralNames} is defined as: * *

     *      GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
     * 
*

* * @param encoder the DER encoder * @param generalNames the general names, as a {@code List} where each entry is a {@link GeneralName} * @throws ASN1Exception if any of the general names are invalid */ public static void encodeGeneralNames(final DEREncoder encoder, List generalNames) throws ASN1Exception { encoder.startSequence(); for (GeneralName generalName : generalNames) { generalName.encodeTo(encoder); } encoder.endSequence(); } /** * Encode a {@code GeneralNames} element consisting of one general name using * the given DER encoder. * * @param encoder the DER encoder * @param generalName the general name * @throws ASN1Exception if the general name is invalid */ public static void encodeGeneralNames(final DEREncoder encoder, GeneralName generalName) throws ASN1Exception { List generalNames = new ArrayList(1); generalNames.add(generalName); encodeGeneralNames(encoder, generalNames); } public static void encodeGeneralNames(final DEREncoder encoder, String subjectName, Collection> subjectAltNames) throws ASN1Exception { encoder.startSequence(); if (! subjectName.isEmpty()) { new DirectoryName(subjectName).encodeTo(encoder); } if (subjectAltNames != null) { for (List altName : subjectAltNames) { convertToGeneralName(altName).encodeTo(encoder); } } encoder.endSequence(); } /** *

* Encode a {@code RandomNumber} element using the given DER encoder, where * {@code RandomNumber} is defined as: * *

     *      RandomNumber ::= OCTET STRING (SIZE(8..MAX))
     * 
*

* * @param encoder the DER encoder * @param secureRandom the secure random to use (may be null) */ public static byte[] encodeRandomNumber(final DEREncoder encoder, SecureRandom secureRandom) { Random random = secureRandom != null ? secureRandom : ThreadLocalRandom.current(); byte[] randomA = generateRandomString(48, random); encoder.encodeOctetString(randomA); return randomA; } public static byte[] generateRandomString(int length, Random random) { final byte[] chars = new byte[length]; for (int i = 0; i < length; i ++) { chars[i] = randomCharDictionary[random.nextInt(93)]; } return chars; } /** * Encode an ASN.1 sequence of trusted authorities using the given DER encoder. * * @param encoder the DER encoder * @param trustedAuthorities the trusted authorities as a {@code List} where each entry must * be a {@link NameTrustedAuthority}, a {@link CertificateTrustedAuthority}, or a {@link HashTrustedAuthority} * @throws ASN1Exception if any of the trusted authorities are invalid */ public static void encodeTrustedAuthorities(final DEREncoder encoder, List trustedAuthorities) throws ASN1Exception { encoder.startSequence(); for (TrustedAuthority trustedAuthority : trustedAuthorities) { trustedAuthority.encodeTo(encoder); } encoder.endSequence(); } /* -- Methods used to decode ASN.1 data structures required for entity authentication -- */ /** * Decode the next element from the given DER decoder as a {@code GeneralNames} element. * * @param decoder the DER decoder * @return the general names * @throws ASN1Exception if the next element from the given decoder is not a general names element */ public static List decodeGeneralNames(final DERDecoder decoder) throws ASN1Exception { List generalNames = new ArrayList(); GeneralName generalName = null; decoder.startSequence(); while (decoder.hasNextElement()) { out: { for (int generalNameType = 0; generalNameType <= 8; generalNameType++) { switch (generalNameType) { case OTHER_NAME: if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, generalNameType, true)) { decoder.decodeImplicit(generalNameType); decoder.startSequence(); String typeId = decoder.decodeObjectIdentifier(); byte[] encodedValue = decoder.drainElement(); decoder.endSequence(); generalName = new OtherName(typeId, encodedValue); break out; } break; case RFC_822_NAME: if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, generalNameType, false)) { decoder.decodeImplicit(generalNameType); generalName = new RFC822Name(decoder.decodeIA5String()); break out; } break; case DNS_NAME: if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, generalNameType, false)) { decoder.decodeImplicit(generalNameType); generalName = new DNSName(decoder.decodeIA5String()); break out; } break; case X400_ADDRESS: if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, generalNameType, true)) { decoder.decodeImplicit(generalNameType); generalName = new X400Address(decoder.drainElementValue(), true); break out; } break; case DIRECTORY_NAME: if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, generalNameType, true)) { byte[] encodedName = decoder.drainElementValue(); generalName = new DirectoryName((new X500Principal(encodedName)).getName(X500Principal.CANONICAL)); break out; } break; case EDI_PARTY_NAME: if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, generalNameType, true)) { decoder.decodeImplicit(generalNameType); generalName = new EDIPartyName(decoder.drainElementValue(), true); break out; } break; case URI_NAME: if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, generalNameType, false)) { decoder.decodeImplicit(generalNameType); generalName = new URIName(decoder.decodeIA5String()); break out; } break; case IP_ADDRESS: if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, generalNameType, false)) { decoder.decodeImplicit(generalNameType); generalName = new IPAddress(decoder.decodeOctetString()); break out; } break; case REGISTERED_ID: if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, generalNameType, false)) { decoder.decodeImplicit(generalNameType); generalName = new RegisteredID(decoder.decodeObjectIdentifier()); break out; } break; default: throw saslEntity.asnInvalidGeneralNameType(); } } } generalNames.add(generalName); } decoder.endSequence(); return generalNames; } /** * Decode the next element from the given DER decoder as an X.509 certificate chain. * * @param decoder the DER decoder * @return the X.509 certificate chain * @throws ASN1Exception if the next element from the given decoder is not an X.509 * certificate chain or if an error occurs while decoding the X.509 certificate chain */ public static X509Certificate[] decodeX509CertificateChain(final DERDecoder decoder) throws ASN1Exception { if (decoder.peekType() != SET_TYPE) { throw saslEntity.asnUnexpectedTag(); } byte[] certChain = decoder.drainElement(); try { CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); certChain[0] = SEQUENCE_TYPE; // CertificateFactory#generateCertPath requires a DER SEQUE CertPath certPath = certFactory.generateCertPath(new ByteArrayInputStream(certChain)); List certs = certPath.getCertificates(); return certs.toArray(new X509Certificate[certs.size()]); } catch (CertificateException e) { throw new ASN1Exception(e); } } /** *

* Decode the next element from the given DER decoder as a {@code CertData} element, * which is defined as follows: * *

     *      CertData ::= CHOICE {
     *          certificateSet     SET SIZE (1..MAX) OF Certificate,
     *          certURL            IA5String (Note: No support for certificate URL)
     *      }
     * 
*

* * @param decoder the DER decoder * @return the X.509 certificate or certificate chain * @throws ASN1Exception if the next element from the given decoder is not a {@code CertData} * element or if an error occurs while decoding the certificate data */ public static X509Certificate[] decodeCertificateData(final DERDecoder decoder) throws ASN1Exception { X509Certificate[] peerCertChain; if (decoder.peekType() == SET_TYPE) { peerCertChain = decodeX509CertificateChain(decoder); } else { throw saslEntity.asnUnexpectedTag(); } return peerCertChain; } /** * Decode the next element from the given DER decoder as a trusted authorities element. * * @param decoder the DER decoder * @return the trusted authorities * @throws ASN1Exception if the next element from the given decoder is not a trusted authorities * element or if an error occurs while decoding the trusted authorities element */ public static List decodeTrustedAuthorities(final DERDecoder decoder) throws ASN1Exception { List trustedAuthorities = new ArrayList(); TrustedAuthority trustedAuthority = null; decoder.startSequence(); while (decoder.hasNextElement()) { out: { for (int trustedAuthorityType = 0; trustedAuthorityType <= 4; trustedAuthorityType++) { switch (trustedAuthorityType) { case AUTHORITY_NAME: if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, trustedAuthorityType, true)) { byte[] encodedName = decoder.drainElementValue(); trustedAuthority = new NameTrustedAuthority((new X500Principal(encodedName)).getName(X500Principal.CANONICAL)); break out; } break; case AUTHORITY_CERTIFICATE: if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, trustedAuthorityType, true)) { decoder.decodeImplicit(trustedAuthorityType); byte[] cert = decoder.drainElement(); cert[0] = SEQUENCE_TYPE; // Replace the trusted authority type tag with a DER SEQUENCE tag, as required by CertificateFactory#generateCertificate try { CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); trustedAuthority = new CertificateTrustedAuthority((X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream(cert))); } catch (CertificateException e) { throw new ASN1Exception(e); } break out; } break; case ISSUER_NAME_HASH: if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, trustedAuthorityType, false)) { decoder.decodeImplicit(trustedAuthorityType); trustedAuthority = new IssuerNameHashTrustedAuthority(decoder.decodeOctetString()); break out; } break; case ISSUER_KEY_HASH: if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, trustedAuthorityType, false)) { decoder.decodeImplicit(trustedAuthorityType); trustedAuthority = new IssuerKeyHashTrustedAuthority(decoder.decodeOctetString()); break out; } break; case PKCS_15_KEY_HASH: if (decoder.isNextType(CONTEXT_SPECIFIC_MASK, trustedAuthorityType, false)) { decoder.decodeImplicit(trustedAuthorityType); trustedAuthority = new PKCS15KeyHashTrustedAuthority(decoder.decodeOctetString()); break out; } break; default: throw saslEntity.asnInvalidGeneralNameType(); } } } trustedAuthorities.add(trustedAuthority); } decoder.endSequence(); return trustedAuthorities; } public static boolean matchGeneralNames(List generalNames, List actualGeneralNames) { if ((generalNames == null) || (actualGeneralNames == null)) { return false; } for (GeneralName generalName : generalNames) { for (GeneralName actualGeneralName : actualGeneralNames) { if (matchGeneralName(generalName, actualGeneralName)) { return true; } } } return false; } public static boolean matchGeneralName(GeneralName generalName, GeneralName actualGeneralName) { if ((generalName instanceof DNSName) && (actualGeneralName instanceof DirectoryName)) { // Check if the DNSName matches the DirectoryName's (most specific) Common Name field. // Although specifying a DNS name using the Common Name field has been deprecated, it is // still used in practice (e.g., see http://tools.ietf.org/html/rfc2818). String[] cnValues = X500PrincipalUtil.getAttributeValues(new X500Principal(((DirectoryName) actualGeneralName).getName()), X500.OID_AT_COMMON_NAME); String dnsName = ((DNSName) generalName).getName(); return dnsName.equalsIgnoreCase(cnValues[0]); } else { return generalName.equals(actualGeneralName); } } public static boolean matchGeneralNames(List generalNames, X509Certificate cert) { X500Principal certSubjectName = cert.getSubjectX500Principal(); try { if (matchGeneralNames(generalNames, convertToGeneralNames(cert.getSubjectAlternativeNames()))) { return true; } } catch (CertificateParsingException e) { // Ignore unless the subject name is empty if (certSubjectName == null) { throw saslEntity.unableToDetermineSubjectName(e); } } List certNames; if (certSubjectName != null) { certNames = new ArrayList(1); certNames.add(new DirectoryName(certSubjectName.getName(X500Principal.CANONICAL))); if (matchGeneralNames(generalNames, certNames)) { return true; } } return false; } public static String getDistinguishedNameFromGeneralNames(List generalNames) { for (GeneralName generalName : generalNames) { if (generalName instanceof DirectoryName) { return ((DirectoryName) generalName).getName(); } } return null; } private static GeneralName convertToGeneralName(List generalName) throws ASN1Exception { int type = ((Integer) generalName.get(0)).intValue(); Object name = generalName.get(1); switch (type) { case OTHER_NAME: return new OtherName((byte[]) name); case RFC_822_NAME: return new RFC822Name((String) name); case DNS_NAME: return new DNSName((String) name); case X400_ADDRESS: return new X400Address((byte[]) name); case DIRECTORY_NAME: return new DirectoryName((String) name); case EDI_PARTY_NAME: return new EDIPartyName((byte[]) name); case URI_NAME: return new URIName((String) name); case IP_ADDRESS: return new IPAddress((String) name); case REGISTERED_ID: return new RegisteredID((String) name); default: throw saslEntity.asnInvalidGeneralNameType(); } } private static List convertToGeneralNames(Collection> generalNames) throws ASN1Exception { if (generalNames == null) { return null; } List convertedGeneralNames = new ArrayList(); for (List generalName : generalNames) { convertedGeneralNames.add(convertToGeneralName(generalName)); } return convertedGeneralNames; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy