org.wildfly.security.x500.util.X500PrincipalUtil 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.x500.util;
import static org.wildfly.security.x500._private.ElytronMessages.log;
import static org.wildfly.security.asn1.ASN1.BMP_STRING_TYPE;
import static org.wildfly.security.asn1.ASN1.IA5_STRING_TYPE;
import static org.wildfly.security.asn1.ASN1.PRINTABLE_STRING_TYPE;
import static org.wildfly.security.asn1.ASN1.UNIVERSAL_STRING_TYPE;
import static org.wildfly.security.asn1.ASN1.UTF8_STRING_TYPE;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.Principal;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import javax.security.auth.x500.X500Principal;
import org.wildfly.common.Assert;
import org.wildfly.security.asn1.ASN1Decoder;
import org.wildfly.security.asn1.DERDecoder;
/**
* A utility class for easily accessing details of an {@link X500Principal}.
*
* @author David M. Lloyd
*/
public final class X500PrincipalUtil {
private static final String[] NO_STRINGS = new String[0];
private static final Class> X500_NAME_CLASS;
private static final MethodHandle AS_X500_PRINCIPAL_HANDLE;
static {
Class> x500Name = null;
MethodHandle asX500PrincipalHandle = null;
try {
x500Name = Class.forName("sun.security.x509.X500Name", true, X500PrincipalUtil.class.getClassLoader());
asX500PrincipalHandle = MethodHandles.publicLookup().unreflect(x500Name.getDeclaredMethod("asX500Principal"));
} catch (Throwable t) {
/*
* This is intended to be a best efforts optimisation, if it fails for ANY reason we don't support the optimisation
* and resort to default behaviour.
*
* Throwing any Exception or Error from this static block results in a NoClassDefFoundError for any access to the
* class and subsequently even the non-optimised scenario is unavailable.
*/
log.trace("X550Name.asX500Principal() is not available.", t);
}
X500_NAME_CLASS = x500Name;
AS_X500_PRINCIPAL_HANDLE = asX500PrincipalHandle;
}
private X500PrincipalUtil() {
}
/**
* Get all the values of the attribute with the given OID in the given principal. This includes occurrences within
* multi-valued RDNs.
*
* @param principal the principal to examine
* @param oid the OID whose values are to be returned
* @return the list of values associated with the OID
*/
public static String[] getAttributeValues(X500Principal principal, String oid) {
return getAttributeValues(principal, oid, false);
}
/**
* Get all the values of the attribute with the given OID in the given principal. This includes occurrences within
* multi-valued RDNs.
*
* @param principal the principal to examine
* @param oid the OID whose values are to be returned
* @param reverse {@code true} if the values in the returned list should be in reverse order
* @return the list of values associated with the OID
*/
public static String[] getAttributeValues(X500Principal principal, String oid, boolean reverse) {
final ASN1Decoder decoder = new DERDecoder(principal.getEncoded());
String[] strings = NO_STRINGS;
int len = 0;
decoder.startSequence();
while (decoder.hasNextElement()) {
decoder.startSet();
while (decoder.hasNextElement()) {
decoder.startSequence();
// first item is the attribute
String testOid = decoder.decodeObjectIdentifier();
if (oid.equals(testOid)) {
// second item is the value
switch (decoder.peekType()) {
case IA5_STRING_TYPE: {
if (strings.length == len) {
strings = Arrays.copyOf(strings, Math.max(2, strings.length) * 2);
}
strings[len++] = decoder.decodeIA5String();
break;
}
case PRINTABLE_STRING_TYPE: {
if (strings.length == len) {
strings = Arrays.copyOf(strings, Math.max(2, strings.length) * 2);
}
strings[len++] = decoder.decodePrintableString();
break;
}
case UNIVERSAL_STRING_TYPE: {
if (strings.length == len) {
strings = Arrays.copyOf(strings, Math.max(2, strings.length) * 2);
}
strings[len++] = decoder.decodeUniversalString();
break;
}
case UTF8_STRING_TYPE: {
if (strings.length == len) {
strings = Arrays.copyOf(strings, Math.max(2, strings.length) * 2);
}
strings[len++] = decoder.decodeUtf8String();
break;
}
case BMP_STRING_TYPE: {
if (strings.length == len) {
strings = Arrays.copyOf(strings, Math.max(2, strings.length) * 2);
}
strings[len++] = decoder.decodeBMPString();
break;
}
default: {
decoder.skipElement();
break;
}
}
} else {
decoder.skipElement();
}
decoder.endSequence();
}
decoder.endSet();
}
decoder.endSequence();
if (decoder.hasNextElement()) {
throw log.unexpectedTrailingGarbageInX500principal();
}
String[] result = len == 0 ? NO_STRINGS : new String[len];
if (! reverse) {
// The attribute values will be in the same order they appear in the string representation of the X.500 principal
for (int i = 0; i < len; i++) {
result[len - i - 1] = strings[i];
}
} else {
// The attribute values will be in reverse order
System.arraycopy(strings, 0, result, 0, len);
}
return result;
}
/**
* Determine if the given principal contains all of the attributes specified by the given OIDs.
* This includes occurrences within multi-valued RDNs.
*
* @param principal the principal to examine
* @param oids the OIDs of the attributes that must be present in the given principal (must not be {@code null},
* cannot have {@code null} elements)
* @return {@code true} if the given principal contains all of the attributes specified by the given OIDs,
* {@code false} otherwise
*/
public static boolean containsAllAttributes(X500Principal principal, String... oids) {
Assert.checkNotNullParam("principal", principal);
Assert.checkNotNullParam("oids", oids);
final Set requiredAttributes = new HashSet<>(Arrays.asList(oids));
final ASN1Decoder decoder = new DERDecoder(principal.getEncoded());
decoder.startSequence();
while (decoder.hasNextElement() && ! requiredAttributes.isEmpty()) {
decoder.startSet();
while (decoder.hasNextElement() && ! requiredAttributes.isEmpty()) {
decoder.startSequence();
// first item is the attribute
String testOid = decoder.decodeObjectIdentifier();
requiredAttributes.remove(testOid);
// skip over the attribute value
decoder.skipElement();
decoder.endSequence();
}
decoder.endSet();
}
decoder.endSequence();
if (decoder.hasNextElement()) {
throw log.unexpectedTrailingGarbageInX500principal();
}
return requiredAttributes.isEmpty();
}
/**
* Attempt to convert the given principal to an X.500 principal.
*
* @param principal the original principal
* @return the X.500 principal or {@code null} if the principal can not be converted.
*/
public static X500Principal asX500Principal(Principal principal) {
return asX500Principal(principal, false);
}
/**
* Attempt to convert the given principal to an X.500 principal.
*
* @param principal the original principal
* @param convert {@code true} if the principal should be converted to a {@link X500Principal} if not one already.
* @return the X.500 principal or {@code null} if the principal can not be converted.
*/
public static X500Principal asX500Principal(Principal principal, boolean convert) {
if (principal instanceof X500Principal) {
return (X500Principal) principal;
}
if (X500_NAME_CLASS != null && X500_NAME_CLASS.isAssignableFrom(principal.getClass())) {
try {
return (X500Principal) AS_X500_PRINCIPAL_HANDLE.invoke(principal);
} catch (RuntimeException | Error ex) {
throw ex;
} catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
if (convert) {
try {
return new X500Principal(principal.getName());
} catch (IllegalArgumentException ignored) {
log.trace("Unable to convert to X500Principal", ignored);
}
}
return null;
}
}