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

io.quarkus.vertx.http.runtime.security.CertificateRoleAttribute Maven / Gradle / Ivy

The newest version!
package io.quarkus.vertx.http.runtime.security;

import static io.quarkus.vertx.http.runtime.security.HttpSecurityUtils.COMMON_NAME;
import static io.quarkus.vertx.http.runtime.security.HttpSecurityUtils.getRdnValue;

import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import javax.security.auth.x500.X500Principal;

import org.jboss.logging.Logger;

import io.vertx.ext.auth.impl.asn.ASN1;

public record CertificateRoleAttribute(Function> rolesMapper) {

    private static final Logger log = Logger.getLogger(CertificateRoleAttribute.class);
    private static final String SAN_PREFIX = "SAN_";

    CertificateRoleAttribute(String configValue, Map> roles) {
        this(of(configValue.toUpperCase(), Map.copyOf(roles)));
    }

    private static Function> of(String configValue, Map> roles) {
        if (configValue.contains(SAN_PREFIX)) {

            return new Function>() {
                @Override
                public Set apply(X509Certificate certificate) {
                    return extractRolesFromCertSan(certificate, SAN.valueOf(configValue).generalNameType, roles);
                }
            };
        } else {

            return new Function>() {
                @Override
                public Set apply(X509Certificate certificate) {
                    return extractRolesFromCertRdn(certificate, roles, configValue);
                }
            };
        }
    }

    private static Set extractRolesFromCertRdn(X509Certificate certificate, Map> roles,
            String rdnType) {
        X500Principal principal = certificate.getSubjectX500Principal();
        if (principal == null || principal.getName() == null) {
            return Set.of();
        }
        Set matchedRoles;
        if (COMMON_NAME.equals(rdnType)) {
            matchedRoles = roles.get(principal.getName());
            if (matchedRoles != null) {
                return matchedRoles;
            }
        }
        String rdnValue = getRdnValue(principal, rdnType);
        if (rdnValue != null) {
            matchedRoles = roles.get(rdnValue);
            if (matchedRoles != null) {
                return matchedRoles;
            }
        }
        return Set.of();
    }

    private enum SAN {
        /**
         * Subject Alternative Name field Other Name.
         * Please note that only simple case of UTF8 identifier mapping is support.
         * For example, you can map 'other-identifier' to the SecurityIdentity roles.
         * If you use 'openssl' tool, supported Other name definition would look like this:
         * subjectAltName=otherName:1.2.3.4;UTF8:other-identifier
         */
        SAN_ANY(0),
        /**
         * Subject Alternative Name field RFC 822 Name.
         */
        SAN_RFC822(1),
        /**
         * Subject Alternative Name field Uniform Resource Identifier (URI).
         */
        SAN_URI(6);

        private final int generalNameType;

        SAN(int generalNameType) {
            this.generalNameType = generalNameType;
        }
    }

    private static Set extractRolesFromCertSan(X509Certificate certificate, int generalNameType,
            Map> roles) {
        final Set result = new HashSet<>();
        try {
            var sanList = certificate.getSubjectAlternativeNames();
            if (sanList != null && !sanList.isEmpty()) {
                for (List objects : sanList) {
                    if (objects != null && objects.size() >= 2) {
                        if (objects.get(0) instanceof Integer thatGeneralNameType) {
                            if (thatGeneralNameType == generalNameType) {

                                // special handling for Other name
                                if (thatGeneralNameType == 0 && objects.get(1) instanceof byte[] byteArr) {
                                    var asn1 = ASN1.parseASN1(byteArr);
                                    if (asn1.is(ASN1.SEQUENCE) && asn1.length() == 2) {

                                        var otherIdentifier = asn1.object(1);
                                        while (otherIdentifier.length() == 1
                                                && otherIdentifier.is(ASN1.CONTEXT_SPECIFIC)) {
                                            // there can be one extra context specific ASN with OpenJDK 17, hence loop
                                            otherIdentifier = otherIdentifier.object(0);
                                        }

                                        if (otherIdentifier.is(ASN1.UTF8_STRING)) {
                                            var value = new String(otherIdentifier.binary(0), StandardCharsets.UTF_8);
                                            if (roles.containsKey(value)) {
                                                result.addAll(roles.get(value));
                                                break;
                                            }
                                        }
                                    }
                                }

                                for (int i = 1; i < objects.size(); i++) {
                                    if (objects.get(i) instanceof String name) {
                                        if (roles.containsKey(name)) {
                                            result.addAll(roles.get(name));
                                        }
                                    }
                                }
                            }
                            continue;
                        }
                    }
                    log.tracef("Cannot map SecurityIdentity roles from '%s' due to unsupported format", objects);
                    break;
                }
            }
        } catch (CertificateParsingException e) {
            log.tracef("Cannot map SecurityIdentity roles as certificate parsing failed");
        }
        return Set.copyOf(result);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy