View Javadoc

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      // Create the outer element.
83      Name securityName = envelope.createName("Security",
84                                              "wsse",
85                                              WsseHeaderElement.wsseNamespace);
86      SOAPHeaderElement security = header.addHeaderElement(securityName);
87      security.setActor(WsseHeaderElement.authenticatingActor);
88  
89      // Add the UsernameToken.
90      SOAPElement usernameToken
91          = WsseHeaderElement.addChildElement(envelope,
92                                              security,
93                                              "UsernameToken",
94                                              "wsse",
95                                              WsseHeaderElement.wsseNamespace);
96  
97      // Add the username elements: one per JAAS principal.
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     // Add the password elements.
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     // Add the nonce tokens. These are an AstroGrid extension to WSS.
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     // Find the header unit.
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     // Find the header elements at the first level of the header.
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       // Locate all the sub-elements.
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       // Extract values from the sub-elements.
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 }