1 package org.astrogrid.security;
2
3 import java.security.Principal;
4 import java.util.Iterator;
5 import javax.security.auth.Subject;
6 import javax.xml.namespace.QName;
7 import javax.xml.soap.Name;
8 import javax.xml.soap.SOAPElement;
9 import javax.xml.soap.SOAPEnvelope;
10 import javax.xml.soap.SOAPException;
11 import javax.xml.soap.SOAPHeader;
12 import javax.xml.soap.SOAPHeaderElement;
13 import javax.xml.soap.SOAPMessage;
14 import javax.xml.soap.SOAPPart;
15
16 /***
17 * A SOAP-header element matching the WS-Security specification of OASIS.
18 *
19 * An object of this class associates credentials, carried in a
20 * {@link SecurityGuard} object, with a single SOAP message. The
21 * guard and message are set during construction. This class can
22 * write headers to the message or parse credentials from the message.
23 *
24 * This class uses SAAJ to access the message header but does not
25 * use JAX-RPC.
26 *
27 * @author Guy Rixon
28 */
29 public class WsseHeaderElement {
30
31 /***
32 * The URI for a SOAP actor that does authentication checks.
33 */
34 private static final String authenticatingActor
35 = "urn:astrogrid:soap-actors:authenticator";
36
37 /***
38 * The URI for the namespace of the WS-Security standard.
39 */
40 private static final String wsseNamespace
41 = "http://schemas.xmlsoap.org/ws/2002/07/secext";
42
43 /***
44 * The URI for the namespace for WSSE utility elements.
45 */
46 private static final String wssuNamespace
47 = "http://schemas.xmlsoap.org/ws/2002/07/utility";
48
49 /***
50 * The URI for the namespace for AG extensions to WSSE.
51 */
52 private static final String agNamespace
53 = "urn:astrogrid:security:wsse";
54
55
56 /***
57 * A formatter for using ISO8601 timestamps.
58 */
59 private static Iso8601DateFormat iso8601 = new Iso8601DateFormat();
60
61 /***
62 * Returns the qualified name for the header element.
63 */
64 public static QName getName() {
65 return new QName(WsseHeaderElement.wsseNamespace, "Security");
66 }
67
68
69
70 /***
71 * Adds a WSSE header to the SOAP message.
72 */
73 public static void write (Subject subject,
74 SOAPMessage sm) throws SOAPException {
75 SOAPPart sp = sm.getSOAPPart();
76 assert (sp != null);
77 SOAPEnvelope envelope = sp.getEnvelope();
78 assert (envelope != null);
79 SOAPHeader header = envelope.getHeader();
80 assert (header != null);
81
82
83 Name securityName = envelope.createName("Security",
84 "wsse",
85 WsseHeaderElement.wsseNamespace);
86 SOAPHeaderElement security = header.addHeaderElement(securityName);
87 security.setActor(WsseHeaderElement.authenticatingActor);
88
89
90 SOAPElement usernameToken
91 = WsseHeaderElement.addChildElement(envelope,
92 security,
93 "UsernameToken",
94 "wsse",
95 WsseHeaderElement.wsseNamespace);
96
97
98 Iterator principals = subject.getPrincipals().iterator();
99 while (principals.hasNext()) {
100 Principal p = (Principal) principals.next();
101 System.out.println("Principal: " + p.getName());
102 SOAPElement username
103 = WsseHeaderElement.addChildElement(envelope,
104 usernameToken,
105 "Username",
106 "wsse",
107 WsseHeaderElement.wsseNamespace);
108 username.addTextNode(p.getName());
109 }
110
111
112 Iterator passwords
113 = subject.getPrivateCredentials(Password.class).iterator();
114 while (passwords.hasNext()) {
115 Password p = (Password) passwords.next();
116 SOAPElement password
117 = WsseHeaderElement.addChildElement(envelope,
118 usernameToken,
119 "Password",
120 "wsse",
121 WsseHeaderElement.wsseNamespace);
122 if (p.isEncodable()) {
123 p.renewNonceAndTimestamp();
124 password.addTextNode(p.getEncodedPassword());
125 SOAPElement nonce
126 = WsseHeaderElement.addChildElement(envelope,
127 usernameToken,
128 "Nonce",
129 "wsse",
130 WsseHeaderElement.wsseNamespace);
131 nonce.addTextNode(p.getNonce());
132 SOAPElement created
133 = WsseHeaderElement.addChildElement(envelope,
134 usernameToken,
135 "Created",
136 "wssu",
137 WsseHeaderElement.wssuNamespace);
138 created.addTextNode(p.getTimestamp());
139 WsseHeaderElement.addAttribute(envelope,
140 password,
141 "Type",
142 "wsse",
143 WsseHeaderElement.wsseNamespace,
144 "wsse:PasswordDigest");
145 }
146 else {
147 password.addTextNode(p.getPlainPassword());
148 }
149 }
150
151
152 Iterator tokens
153 = subject.getPrivateCredentials(NonceToken.class).iterator();
154 while (tokens.hasNext()) {
155 NonceToken t = (NonceToken) tokens.next();
156 SOAPElement nonceToken
157 = WsseHeaderElement.addChildElement(envelope,
158 usernameToken,
159 "NonceToken",
160 "ag",
161 WsseHeaderElement.agNamespace);
162 nonceToken.addTextNode(t.toString());
163 }
164 }
165
166
167 /***
168 * Parses credentials from the SOAP message.
169 *
170 * @param sm the SOAP message with the credentials in the header
171 * @subject the JAAS Subject to which the credentials are added
172 * @throws NoCredentialsException if no credential header is present
173 * @throws SOAPException if the header is present but unparseable
174 */
175 public static void parse (SOAPMessage sm,
176 Subject subject) throws SOAPException,
177 NoCredentialsException {
178
179
180 SOAPPart sp = sm.getSOAPPart();
181 assert(sp != null);
182 SOAPEnvelope se = sp.getEnvelope();
183 assert(se != null);
184 SOAPHeader sh = se.getHeader();
185 assert(sh != null);
186
187
188 Iterator i
189 = sh.examineHeaderElements(WsseHeaderElement.authenticatingActor);
190 SOAPHeaderElement he = null;
191 while (i.hasNext() && he == null) {
192 Object o = i.next();
193 assert(o instanceof SOAPHeaderElement);
194 he = (SOAPHeaderElement) o;
195 QName q = new QName(he.getElementName().getURI(),
196 he.getElementName().getLocalName());
197 if (!WsseHeaderElement.getName().equals(q)) {
198 he = null;
199 }
200 }
201 if (he == null) {
202 throw new NoCredentialsException(
203 "No WSSE Security element was found in the SOAP header."
204 );
205 }
206
207
208 SOAPElement usernameToken
209 = WsseHeaderElement.getChildElement(se,
210 he,
211 "UsernameToken",
212 "wsse",
213 WsseHeaderElement.wsseNamespace);
214
215 if (usernameToken != null) {
216 System.out.println("Found /Security/UsernameToken");
217
218
219 SOAPElement username
220 = WsseHeaderElement.getChildElement(se,
221 usernameToken,
222 "Username",
223 "wsse",
224 WsseHeaderElement.wsseNamespace);
225
226 SOAPElement password
227 = WsseHeaderElement.getChildElement(se,
228 usernameToken,
229 "Password",
230 "wsse",
231 WsseHeaderElement.wsseNamespace);
232
233
234 SOAPElement nonce
235 = WsseHeaderElement.getChildElement(se,
236 usernameToken,
237 "Nonce",
238 "wsse",
239 WsseHeaderElement.wsseNamespace);
240
241 SOAPElement created
242 = WsseHeaderElement.getChildElement(se,
243 usernameToken,
244 "Created",
245 "wssu",
246 WsseHeaderElement.wssuNamespace);
247
248 SOAPElement nonceToken
249 = WsseHeaderElement.getChildElement(se,
250 usernameToken,
251 "NonceToken",
252 "ag",
253 WsseHeaderElement.agNamespace);
254
255
256 if (nonceToken != null) {
257 NonceToken t = new NonceToken(nonceToken.getValue());
258 subject.getPrivateCredentials().add(t);
259 subject.getPrincipals().add(new AccountName(t.getAccount()));
260 }
261
262 if (username != null) {
263 subject.getPrincipals().add(new AccountName(username.getValue()));
264 }
265
266 if (password != null) {
267 String passwordType = WsseHeaderElement.getAttribute(se,
268 password,
269 "Type",
270 "wsse",
271 WsseHeaderElement.wsseNamespace);
272 if (passwordType != null && passwordType.equals("wsse:PasswordDigest")) {
273 if (nonce == null || created == null) {
274 throw new SOAPException("Digested password is unreadable; " +
275 "need both nonce and timestamp");
276 }
277 else {
278 Password p = new Password(password.getValue(),
279 nonce.getValue(),
280 created.getValue(),
281 true);
282 subject.getPrivateCredentials().add(p);
283 }
284 }
285 else {
286 Password p = new Password(password.getValue(), false);
287 subject.getPrivateCredentials().add(p);
288 }
289 }
290 }
291 }
292
293
294 /***
295 * Locates a child SOAPElement within the current SOAPElement.
296 */
297 private static SOAPElement getChildElement (SOAPEnvelope envelope,
298 SOAPElement parent,
299 String child,
300 String prefix,
301 String uri) throws SOAPException {
302 Name name = envelope.createName(child, prefix, uri);
303 Iterator i = parent.getChildElements(name);
304 while (i.hasNext()) {
305 SOAPElement se = (SOAPElement) i.next();
306 return se;
307 }
308 return null;
309 }
310
311
312 /***
313 * Gets an attribute off an existing element.
314 * The SOAP envelope is used to derive the Name of the attribute.
315 *
316 * @param envelope SOAP envelope which generates the QNames
317 * @param parent existing element to bear the attribute
318 * @param name local name of the attribute
319 * @param prefix namespace prefix for the attribute
320 * @param uri namespace URI for the attribute
321 * @param value the value of the attribute
322 *
323 * @throws SOAPException if there was an error in constructing the
324 * QName for the child or in constructing the child element
325 */
326 private static String getAttribute (SOAPEnvelope envelope,
327 SOAPElement parent,
328 String name,
329 String prefix,
330 String uri)
331 throws SOAPException {
332 assert(envelope != null);
333 assert(parent != null);
334 assert(name != null);
335 assert(prefix != null);
336 assert(uri != null);
337 Name qName = envelope.createName(name, prefix, uri);
338 return parent.getAttributeValue(qName);
339 }
340
341
342
343 /***
344 * Adds a child element inside a SOAP header.
345 * This cannot be used to add children at the top level of
346 * a header as it returns SOAPElement instead of SOAPHeaderElement.
347 *
348 * @param envelope SOAP envelope which generates the QNames
349 * @param parent existing element to bear the child
350 * @param child local name of element to be added
351 * @param prefix namespace prefix for the child element
352 * @param uri namespace URI for the child element
353 *
354 * @return the child element
355 *
356 * @throws SOAPException if there was an error in constructing the
357 * QName for the child or in constructing the child element
358 */
359 private static SOAPElement addChildElement (SOAPEnvelope envelope,
360 SOAPElement parent,
361 String child,
362 String prefix,
363 String uri)
364 throws SOAPException {
365 assert(envelope != null);
366 assert(parent != null);
367 assert(child != null);
368 assert(prefix != null);
369 assert(uri != null);
370 Name name = envelope.createName(child, prefix, uri);
371 return parent.addChildElement(name);
372 }
373
374
375 /***
376 * Adds an attribute to an existing element.
377 * The SOAP element is used to derive the Name of the attribute.
378 *
379 * @param envelope SOAP envelope which generates the QNames
380 * @param parent existing element to bear the attribute
381 * @param name local name of the attribute
382 * @param prefix namespace prefix for the attribute
383 * @param uri namespace URI for the attribute
384 * @param value the value of the attribute
385 *
386 * @throws SOAPException if there was an error in constructing the
387 * QName for the child or in constructing the child element
388 */
389 private static void addAttribute (SOAPEnvelope envelope,
390 SOAPElement parent,
391 String name,
392 String prefix,
393 String uri,
394 String value)
395 throws SOAPException {
396 assert(envelope != null);
397 assert(parent != null);
398 assert(name != null);
399 assert(prefix != null);
400 assert(uri != null);
401 assert(value != null);
402 Name qName = envelope.createName(name, prefix, uri);
403 parent.addAttribute(qName, value);
404 }
405
406 }