1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.opensaml.ws.security.provider;
18
19 import java.security.cert.X509Certificate;
20 import java.util.ArrayList;
21 import java.util.List;
22
23 import org.opensaml.ws.message.MessageContext;
24 import org.opensaml.ws.security.SecurityPolicyException;
25 import org.opensaml.ws.transport.Transport;
26 import org.opensaml.xml.security.CriteriaSet;
27 import org.opensaml.xml.security.credential.Credential;
28 import org.opensaml.xml.security.credential.UsageType;
29 import org.opensaml.xml.security.criteria.EntityIDCriteria;
30 import org.opensaml.xml.security.criteria.UsageCriteria;
31 import org.opensaml.xml.security.trust.TrustEngine;
32 import org.opensaml.xml.security.x509.X509Credential;
33 import org.opensaml.xml.security.x509.X509Util;
34 import org.opensaml.xml.util.DatatypeHelper;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
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 public class ClientCertAuthRule extends BaseTrustEngineRule<X509Credential> {
72
73
74 private final Logger log = LoggerFactory.getLogger(ClientCertAuthRule.class);
75
76
77 private CertificateNameOptions certNameOptions;
78
79
80
81
82
83
84
85
86 public ClientCertAuthRule(TrustEngine<X509Credential> engine, CertificateNameOptions nameOptions) {
87 super(engine);
88 certNameOptions = nameOptions;
89 }
90
91
92 public void evaluate(MessageContext messageContext) throws SecurityPolicyException {
93
94 Credential peerCredential = messageContext.getInboundMessageTransport().getPeerCredential();
95
96 if (peerCredential == null) {
97 log.info("Inbound message transport did not contain a peer credential, "
98 + "skipping client certificate authentication");
99 return;
100 }
101 if (!(peerCredential instanceof X509Credential)) {
102 log.info("Inbound message transport did not contain an X509Credential, "
103 + "skipping client certificate authentication");
104 return;
105 }
106
107 X509Credential requestCredential = (X509Credential) peerCredential;
108
109 doEvaluate(requestCredential, messageContext);
110 }
111
112
113
114
115
116
117 protected CertificateNameOptions getCertificateNameOptions() {
118 return certNameOptions;
119 }
120
121
122
123
124
125
126
127
128
129 protected void doEvaluate(X509Credential requestCredential, MessageContext messageContext)
130 throws SecurityPolicyException {
131
132 String contextIssuer = messageContext.getInboundMessageIssuer();
133
134 if (contextIssuer != null) {
135 log.debug("Attempting client certificate authentication using context issuer: {}", contextIssuer);
136 if (evaluate(requestCredential, contextIssuer, messageContext)) {
137 log.info("Authentication via client certificate succeeded for context issuer entity ID: {}",
138 contextIssuer);
139 messageContext.getInboundMessageTransport().setAuthenticated(true);
140 } else {
141 log.error("Authentication via client certificate failed for context issuer entity ID {}",
142 contextIssuer);
143 throw new SecurityPolicyException(
144 "Client certificate authentication failed for context issuer entity ID");
145 }
146 return;
147 }
148
149 String derivedIssuer = evaluateCertificateNameDerivedIssuers(requestCredential, messageContext);
150 if (derivedIssuer != null) {
151 log.info("Authentication via client certificate succeeded for certificate-derived issuer entity ID {}",
152 derivedIssuer);
153 messageContext.setInboundMessageIssuer(derivedIssuer);
154 messageContext.getInboundMessageTransport().setAuthenticated(true);
155 return;
156 }
157
158 derivedIssuer = evaluateDerivedIssuers(requestCredential, messageContext);
159 if (derivedIssuer != null) {
160 log.info("Authentication via client certificate succeeded for derived issuer entity ID {}", derivedIssuer);
161 messageContext.setInboundMessageIssuer(derivedIssuer);
162 messageContext.getInboundMessageTransport().setAuthenticated(true);
163 return;
164 }
165 }
166
167
168 protected CriteriaSet buildCriteriaSet(String entityID, MessageContext messageContext)
169 throws SecurityPolicyException {
170
171 CriteriaSet criteriaSet = new CriteriaSet();
172 if (!DatatypeHelper.isEmpty(entityID)) {
173 criteriaSet.add(new EntityIDCriteria(entityID));
174 }
175
176 criteriaSet.add(new UsageCriteria(UsageType.SIGNING));
177
178 return criteriaSet;
179 }
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199 protected String evaluateDerivedIssuers(X509Credential requestCredential, MessageContext messageContext)
200 throws SecurityPolicyException {
201
202 return null;
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
231 protected String evaluateCertificateNameDerivedIssuers(X509Credential requestCredential,
232 MessageContext messageContext) throws SecurityPolicyException {
233
234 String candidateIssuer = null;
235
236 if (certNameOptions.evaluateSubjectDN()) {
237 candidateIssuer = evaluateSubjectDN(requestCredential, messageContext);
238 if (candidateIssuer != null) {
239 return candidateIssuer;
240 }
241 }
242
243 if (!certNameOptions.getSubjectAltNames().isEmpty()) {
244 candidateIssuer = evaluateSubjectAltNames(requestCredential, messageContext);
245 if (candidateIssuer != null) {
246 return candidateIssuer;
247 }
248 }
249
250 if (certNameOptions.evaluateSubjectCommonName()) {
251 candidateIssuer = evaluateSubjectCommonName(requestCredential, messageContext);
252 if (candidateIssuer != null) {
253 return candidateIssuer;
254 }
255 }
256
257 return null;
258 }
259
260
261
262
263
264
265
266
267
268
269
270 protected String evaluateSubjectCommonName(X509Credential requestCredential, MessageContext messageContext)
271 throws SecurityPolicyException {
272
273 log.debug("Evaluating client cert by deriving issuer as cert CN");
274 X509Certificate certificate = requestCredential.getEntityCertificate();
275 String candidateIssuer = getCommonName(certificate);
276 if (candidateIssuer != null) {
277 if (evaluate(requestCredential, candidateIssuer, messageContext)) {
278 log.info("Authentication succeeded for issuer derived from CN {}", candidateIssuer);
279 return candidateIssuer;
280 }
281 }
282 return null;
283 }
284
285
286
287
288
289
290
291
292
293 protected String evaluateSubjectDN(X509Credential requestCredential, MessageContext messageContext)
294 throws SecurityPolicyException {
295
296 log.debug("Evaluating client cert by deriving issuer as cert subject DN");
297 X509Certificate certificate = requestCredential.getEntityCertificate();
298 String candidateIssuer = getSubjectName(certificate);
299 if (candidateIssuer != null) {
300 if (evaluate(requestCredential, candidateIssuer, messageContext)) {
301 log.info("Authentication succeeded for issuer derived from subject DN {}", candidateIssuer);
302 return candidateIssuer;
303 }
304 }
305 return null;
306 }
307
308
309
310
311
312
313
314
315
316
317 protected String evaluateSubjectAltNames(X509Credential requestCredential, MessageContext messageContext)
318 throws SecurityPolicyException {
319
320 log.debug("Evaluating client cert by deriving issuer from subject alt names");
321 X509Certificate certificate = requestCredential.getEntityCertificate();
322 for (Integer altNameType : certNameOptions.getSubjectAltNames()) {
323 log.debug("Evaluating alt names of type: {}", altNameType.toString());
324 List<String> altNames = getAltNames(certificate, altNameType);
325 for (String altName : altNames) {
326 if (evaluate(requestCredential, altName, messageContext)) {
327 log.info("Authentication succeeded for issuer derived from subject alt name {}", altName);
328 return altName;
329 }
330 }
331 }
332 return null;
333 }
334
335
336
337
338
339
340
341 protected String getCommonName(X509Certificate cert) {
342 List<String> names = X509Util.getCommonNames(cert.getSubjectX500Principal());
343 if (names != null && !names.isEmpty()) {
344 String name = names.get(0);
345 log.debug("Extracted common name from certificate: {}", name);
346 return name;
347 }
348 return null;
349 }
350
351
352
353
354
355
356
357 protected String getSubjectName(X509Certificate cert) {
358 if (cert == null) {
359 return null;
360 }
361 String name = null;
362 if (!DatatypeHelper.isEmpty(certNameOptions.getX500SubjectDNFormat())) {
363 name = certNameOptions.getX500DNHandler().getName(cert.getSubjectX500Principal(),
364 certNameOptions.getX500SubjectDNFormat());
365 } else {
366 name = certNameOptions.getX500DNHandler().getName(cert.getSubjectX500Principal());
367 }
368 log.debug("Extracted subject name from certificate: {}", name);
369 return name;
370 }
371
372
373
374
375
376
377
378
379
380 protected List<String> getAltNames(X509Certificate cert, Integer altNameType) {
381 log.debug("Extracting alt names from certificate of type: {}", altNameType.toString());
382 Integer[] nameTypes = new Integer[] { altNameType };
383 List altNames = X509Util.getAltNames(cert, nameTypes);
384 List<String> names = new ArrayList<String>();
385 for (Object altNameValue : altNames) {
386 if (!(altNameValue instanceof String)) {
387 log.debug("Skipping non-String certificate alt name value");
388 } else {
389 names.add((String) altNameValue);
390 }
391 }
392 log.debug("Extracted alt names from certificate: {}", names.toString());
393 return names;
394 }
395
396 }