/
X500PrincipalUtil.java
230 lines (214 loc) · 9.24 KB
/
X500PrincipalUtil.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
/*
* 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._private.ElytronMessages.log;
import static org.wildfly.security.asn1.util.ASN1.IA5_STRING_TYPE;
import static org.wildfly.security.asn1.util.ASN1.PRINTABLE_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 <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
*/
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;
}
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
for (int i = 0; i < len; i++) {
result[i] = strings[i];
}
}
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<String> 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;
}
}