View Javadoc

1   /*
2    * Copyright  2003-2004 The Apache Software Foundation.
3    *
4    *  Licensed under the Apache License, Version 2.0 (the "License");
5    *  you may not use this file except in compliance with the License.
6    *  You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   *  Unless required by applicable law or agreed to in writing, software
11   *  distributed under the License is distributed on an "AS IS" BASIS,
12   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *  See the License for the specific language governing permissions and
14   *  limitations under the License.
15   *
16   */
17  
18  package org.apache.ws.security.message.token;
19  
20  import org.apache.commons.logging.Log;
21  import org.apache.commons.logging.LogFactory;
22  import org.apache.ws.security.WSConstants;
23  import org.apache.ws.security.WSDocInfo;
24  import org.apache.ws.security.WSSConfig;
25  import org.apache.ws.security.WSSecurityException;
26  import org.apache.ws.security.components.crypto.Crypto;
27  import org.apache.ws.security.util.DOM2Writer;
28  import org.apache.ws.security.util.WSSecurityUtil;
29  import org.apache.xml.security.exceptions.XMLSecurityException;
30  import org.apache.xml.security.keys.content.x509.XMLX509IssuerSerial;
31  import org.apache.xml.security.utils.Base64;
32  import org.w3c.dom.*;
33  
34  import java.security.cert.CertificateEncodingException;
35  import java.security.cert.X509Certificate;
36  
37  /***
38   * Security Token Reference.
39   * <p/>
40   *
41   * @author Davanum Srinivas (dims@yahoo.com).
42   */
43  public class SecurityTokenReference {
44      private static Log log =
45              LogFactory.getLog(SecurityTokenReference.class.getName());
46      private static Log tlog = LogFactory.getLog("org.apache.ws.security.TIME");
47      public static final String SECURITY_TOKEN_REFERENCE = "SecurityTokenReference";
48      public static final String KEY_NAME = "KeyName";
49      public static final String SKI_URI =
50              WSConstants.X509TOKEN_NS + "#X509SubjectKeyIdentifier";
51      protected Element element = null;
52      private XMLX509IssuerSerial issuerSerial = null;
53      private byte[] skiBytes = null;
54      protected WSSConfig wssConfig = WSSConfig.getDefaultWSConfig();
55  
56      private static boolean doDebug = false;
57  
58      /***
59       * Constructor.
60       * <p/>
61       *
62       * @param wssConfig
63       * @param elem
64       * @throws WSSecurityException
65       */
66      public SecurityTokenReference(WSSConfig wssConfig, Element elem) throws WSSecurityException {
67          doDebug = log.isDebugEnabled();
68          this.element = elem;
69          this.wssConfig = wssConfig;
70          boolean goodElement = false;
71          if (SECURITY_TOKEN_REFERENCE.equals(element.getLocalName())) {
72              if (wssConfig.getProcessNonCompliantMessages()) {
73                  for (int i = 0; !goodElement && i < WSConstants.WSSE_NS_ARRAY.length; ++i) {
74                      goodElement = WSConstants.WSSE_NS_ARRAY[i].equals(element.getNamespaceURI());
75                  }
76              } else {
77                  goodElement = wssConfig.getWsseNS().equals(element.getNamespaceURI());
78              }
79          } else if (KEY_NAME.equals(element.getLocalName())) {
80              goodElement = WSConstants.SIG_NS.equals(element.getNamespaceURI());
81          }
82          if (!goodElement) {
83              throw new WSSecurityException(WSSecurityException.FAILURE,
84                      "badElement",
85                      null);
86          }
87      }
88  
89      /***
90       * Constructor.
91       * <p/>
92       *
93       * @param wssConfig
94       * @param doc
95       */
96      public SecurityTokenReference(WSSConfig wssConfig, Document doc) {
97          doDebug = log.isDebugEnabled();
98          this.wssConfig = wssConfig;
99          this.element =
100                 doc.createElementNS(wssConfig.getWsseNS(),
101                         "wsse:SecurityTokenReference");
102     }
103 
104     /*
105      * Here the methods that handle the direct reference inside
106      * a SecurityTokenReference
107      */
108 
109     /***
110      * set the reference.
111      * <p/>
112      *
113      * @param ref
114      */
115     public void setReference(Reference ref) {
116         Element elem = getFirstElement();
117         if (elem != null) {
118             this.element.replaceChild(ref.getElement(), elem);
119         } else {
120             this.element.appendChild(ref.getElement());
121         }
122     }
123 
124     /***
125      * Gets the Reference.
126      *
127      * @return the <code>Reference</code> element contained in this
128      *         SecurityTokeneReference
129      * @throws WSSecurityException
130      */
131     public Reference getReference() throws WSSecurityException {
132         Element elem = getFirstElement();
133         return new Reference(wssConfig, elem);
134     }
135 
136     /***
137      * Gets the signing token element, which maybe a <code>BinarySecurityToken
138      * </code> or a SAML token        .
139      * The method gets the URI attribute of the {@link Reference} contained in
140      * the {@link SecurityTokenReference} and tries to find the referenced
141      * Element in the document.
142      *
143      * @param doc the document that contains the binary security token
144      *            element. This could be different from the document
145      *            that contains the SecurityTokenReference (STR). See
146      *            STRTransform.derefenceBST() method
147      * @return Element     containing the signing token, must be a BinarySecurityToken
148      * @throws WSSecurityException When either no <code>Reference</code> element, or the found
149      *                   reference contains no URI, or the referenced signing not found.
150      */
151     public Element getTokenElement(Document doc, WSDocInfo docInfo)
152             throws WSSecurityException {
153         Reference ref = getReference();
154         String uri = ref.getURI();
155         if (doDebug) {
156             log.debug("Token reference uri: " + uri);
157         }
158         if (uri == null) {
159             throw new WSSecurityException(WSSecurityException.INVALID_SECURITY,
160                     "badReferenceURI");
161         }
162         Element tokElement = null;
163         String tmpS = WSConstants.WSS_SAML_NS + WSConstants.WSS_SAML_ASSERTION;
164         if (tmpS.equals(ref.getValueType())) {
165             Element sa = docInfo.getAssertion();
166             String saID = null;
167             if (sa != null) {
168                 saID = sa.getAttribute("AssertionID");
169             }
170             if (doDebug) {
171                 log.debug("SAML token ID: " + saID);
172             }
173             String id = uri.substring(1);
174             if (saID == null || !saID.equals(id)) {
175                 throw new WSSecurityException(WSSecurityException.INVALID_SECURITY,
176                         "badReferenceURI",
177                         new Object[]{"uri:" + uri + ", saID: " + saID});
178             }
179             tokElement = sa;
180         } else {
181             tokElement = WSSecurityUtil.getElementByWsuId(wssConfig, doc, uri);
182         }
183         if (tokElement == null) {
184             throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
185                     "noToken",
186                     new Object[]{uri});
187         }
188         return tokElement;
189     }
190 
191     /*
192      * Here the methods that handle the various key identifer types
193      * such as KeyIdentifier, SubjectKeyIdentifier (SKI)
194      */
195 
196     /***
197      * Sets the KeyIdentifer Element as a X509 certificate.
198      * Takes a X509 certificate, converts its data into base 64 and inserts
199      * it into a <code>wsse:KeyIdentifier</code> element, which is placed
200      * in the <code>wsse:SecurityTokenReference</code> element.
201      *
202      * @param cert is the X509 certficate to be inserted as key identifier
203      */
204     public void setKeyIdentifier(X509Certificate cert)
205             throws WSSecurityException {
206         Document doc = this.element.getOwnerDocument();
207         byte data[] = null;
208         try {
209             data = cert.getEncoded();
210         } catch (CertificateEncodingException e) {
211             throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
212                     "encodeError");
213         }
214         Text certText = doc.createTextNode(Base64.encode(data));
215         Element keyId =
216                 doc.createElementNS(wssConfig.getWsseNS(), "wsse:KeyIdentifier");
217         if (wssConfig.isBSTAttributesQualified()) {
218             keyId.setAttributeNS(wssConfig.getWsseNS(),
219                     WSConstants.WSSE_PREFIX + ":ValueType",
220                     X509Security.getType(wssConfig));
221             keyId.setAttributeNS(wssConfig.getWsseNS(),
222                     WSConstants.WSSE_PREFIX + ":EncodingType",
223                     BinarySecurity.getBase64EncodingValue(wssConfig));
224         } else {
225             keyId.setAttributeNS(null, "ValueType", X509Security.getType(wssConfig));
226             keyId.setAttributeNS(null,
227                     "EncodingType",
228                     BinarySecurity.getBase64EncodingValue(wssConfig));
229         }
230         keyId.appendChild(certText);
231         Element elem = getFirstElement();
232         if (elem != null) {
233             this.element.replaceChild(keyId, elem);
234         } else {
235             this.element.appendChild(keyId);
236         }
237     }
238 
239     /***
240      * Sets the KeyIdentifer Element as a X509 Subject-Key-Identifier (SKI).
241      * Takes a X509 certificate, gets it SKI data, converts into base 64 and
242      * inserts it into a <code>wsse:KeyIdentifier</code> element, which is placed
243      * in the <code>wsse:SecurityTokenReference</code> element.
244      *
245      * @param cert   is the X509 certficate to get the SKI
246      * @param crypto is the Crypto implementation. Used to read SKI info bytes from certificate
247      */
248     public void setKeyIdentifierSKI(X509Certificate cert, Crypto crypto)
249             throws WSSecurityException {
250         Document doc = this.element.getOwnerDocument();
251         byte data[] = crypto.getSKIBytesFromCert(cert);
252         org.w3c.dom.Text skiText = doc.createTextNode(Base64.encode(data));
253         Element keyId =
254                 doc.createElementNS(wssConfig.getWsseNS(), "wsse:KeyIdentifier");
255         if (wssConfig.isBSTAttributesQualified()) {
256             keyId.setAttributeNS(wssConfig.getWsseNS(),
257                     WSConstants.WSSE_PREFIX + ":ValueType",
258                     SKI_URI);
259             keyId.setAttributeNS(wssConfig.getWsseNS(),
260                     WSConstants.WSSE_PREFIX + ":EncodingType",
261                     BinarySecurity.getBase64EncodingValue(wssConfig));
262         } else {
263             keyId.setAttributeNS(null, "ValueType", SKI_URI);
264             keyId.setAttributeNS(null,
265                     "EncodingType",
266                     BinarySecurity.getBase64EncodingValue(wssConfig));
267         }
268         keyId.appendChild(skiText);
269         Element elem = getFirstElement();
270         if (elem != null) {
271             this.element.replaceChild(keyId, elem);
272         } else {
273             this.element.appendChild(keyId);
274         }
275     }
276 
277 	public void setSAMLKeyIdentifier(String keyIdVal)
278 			throws WSSecurityException {
279 		Document doc = this.element.getOwnerDocument();
280 		Element keyId =
281 				doc.createElementNS(wssConfig.getWsseNS(), "wsse:KeyIdentifier");
282 			keyId.setAttributeNS(wssConfig.getWsseNS(),
283 					"ValueType",
284 					"http://docs.oasis-open.org/wss/2004/XX/oasis-2004XX-wss-saml-token-profile-1.0#SAMLAssertionID");
285 		keyId.appendChild(doc.createTextNode(keyIdVal));
286 		Element elem = getFirstElement();
287 		if (elem != null) {
288 			this.element.replaceChild(keyId, elem);
289 		} else {
290 			this.element.appendChild(keyId);
291 		}
292 	}
293 
294     /***
295      * Gets the KeyIdentifer.
296      *
297      * @return the {@link BinarySecurity} containing the X509
298      *         certificate or zero if a unknown key identifier
299      *         type was detected.
300      */
301     public X509Certificate[] getKeyIdentifier(Crypto crypto)
302             throws WSSecurityException {
303         X509Security token = null;
304         Element elem = getFirstElement();
305         String value = elem.getAttribute("ValueType");
306         // attempt to get the attribute if it was qualified
307         // NYI iterate through all the possible namespaces
308         if (value.length() == 0 &&
309                 (wssConfig.getProcessNonCompliantMessages() || wssConfig.isBSTAttributesQualified())) {
310             value = WSSecurityUtil.getAttributeValueWSSE(elem, "ValueType", null);
311         }
312         if (value.endsWith(X509Security.X509_V3)) {
313             token = new X509Security(wssConfig, elem);
314             if (token != null) {
315                 X509Certificate cert = token.getX509Certificate(crypto);
316                 X509Certificate[] certs = new X509Certificate[1];
317                 certs[0] = cert;
318                 return certs;
319             }
320         } else if (value.equals(SKI_URI)) {
321             String alias = getX509SKIAlias(crypto);
322             if (alias != null) {
323                 return crypto.getCertificates(alias);
324             }
325         }
326         return null;
327     }
328 
329     public String getX509SKIAlias(Crypto crypto) throws WSSecurityException {
330         if (skiBytes == null) {
331             skiBytes = getSKIBytes();
332             if (skiBytes == null) {
333                 return null;
334             }
335         }
336         String alias = crypto.getAliasForX509Cert(skiBytes);
337         if (doDebug) {
338             log.info("X509 SKI alias: " + alias);
339         }
340         return alias;
341     }
342 
343     public byte[] getSKIBytes() {
344         if (skiBytes != null) {
345             return skiBytes;
346         }
347         Node node = getFirstElement().getFirstChild();
348         if (node == null) {
349             return null;
350         }
351         if (node.getNodeType() == Node.TEXT_NODE) {
352             try {
353                 skiBytes = Base64.decode(((Text) node).getData());
354             } catch (Exception e) {
355                 return null;
356             }
357         }
358         return skiBytes;
359     }
360 
361     /*
362      * Here the methods that handle the IssuerSerial key identifiaton
363      */
364 
365     /***
366      * Sets the X509 IssuerSerial data.
367      *
368      * @param ref the {@link XMLX509IssuerSerial} to put into this
369      *            SecurityTokenReference
370      */
371     public void setX509IssuerSerial(XMLX509IssuerSerial ref) {
372         Element elem = getFirstElement();
373         if (elem != null) {
374             this.element.replaceChild(ref.getElement(), elem);
375         } else {
376             this.element.appendChild(ref.getElement());
377         }
378     }
379 
380     /***
381      * Gets the certificate identified with X509 issuerSerial data.
382      * This method first tries to get the embedded certificate.
383      * If this fails it checks if the certificate is  in the
384      * keystore.
385      *
386      * @return a certificate array or null if nothing found
387      */
388     public X509Certificate[] getX509IssuerSerial(Crypto crypto)
389             throws WSSecurityException {
390         String alias = getX509IssuerSerialAlias(crypto);
391         if (alias != null) {
392             return crypto.getCertificates(alias);
393         }
394         return null;
395     }
396 
397     /***
398      * Gets the alias name of the certificate identified with X509 issuerSerial data.
399      * The keystore identifies the certificate and the key with this alias name.
400      *
401      * @return the alias name for the certificate or null if nothing found
402      */
403     public String getX509IssuerSerialAlias(Crypto crypto)
404             throws WSSecurityException {
405         if (issuerSerial == null) {
406             issuerSerial = getIssuerSerial();
407             if (issuerSerial == null) {
408                 return null;
409             }
410         }
411 
412         String alias = crypto.getAliasForX509Cert(issuerSerial.getIssuerName(),
413                 issuerSerial.getSerialNumber());
414 
415         if (doDebug) {
416             log.info("X509IssuerSerial alias: " + alias);
417         }
418         return alias;
419     }
420 
421     private XMLX509IssuerSerial getIssuerSerial() throws WSSecurityException {
422         if (issuerSerial != null) {
423             return issuerSerial;
424         }
425         Element elem = getFirstElement();
426         if (elem == null) {
427             return null;
428         }
429         try {
430             issuerSerial = new XMLX509IssuerSerial(elem, "");
431         } catch (XMLSecurityException e) {
432             throw new WSSecurityException(WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
433                     "noToken",
434                     new Object[]{"Issuer/Serial data element missing"});
435         }
436         return issuerSerial;
437     }
438 
439     /*
440      * Several helper and utility mehtods.
441      */
442 
443     /***
444      * get the first child element.
445      *
446      * @return the first <code>Element</code> child node
447      */
448     public Element getFirstElement() {
449         for (Node currentChild = this.element.getFirstChild();
450              currentChild != null;
451              currentChild = currentChild.getNextSibling()) {
452             if (currentChild instanceof Element) {
453                 return (Element) currentChild;
454             }
455         }
456         return null;
457     }
458 
459     /***
460      * Method containsKeyName
461      *
462      * @return true if the <code>SecurtityTokenReference</code> contains
463      *         a <code>wsse:KeyName</code> element
464      */
465     public boolean containsKeyName() {
466         return element.getLocalName().equals(KEY_NAME);
467     }
468 
469     public String getKeyNameValue() {
470         return element.getFirstChild().getNodeValue();
471     }
472 
473     /***
474      * Method containsReference
475      *
476      * @return true if the <code>SecurtityTokenReference</code> contains
477      *         a <code>wsse:Reference</code> element
478      */
479     public boolean containsReference() {
480         return this.lengthReference() > 0;
481     }
482 
483     /***
484      * Method lengthReference.
485      *
486      * @return number of <code>wsse:Reference</code> elements in
487      *         the <code>SecurtityTokenReference</code>
488      */
489     public int lengthReference() {
490         if (wssConfig.getProcessNonCompliantMessages()) {
491             int length = 0;
492             for (int i = 0; length == 0 && i < WSConstants.WSSE_NS_ARRAY.length; ++i) {
493                 length = this.length(WSConstants.WSSE_NS_ARRAY[i], "Reference");
494             }
495             return length;
496         } else {
497             return this.length(wssConfig.getWsseNS(), "Reference");
498         }
499     }
500 
501     /***
502      * Method containsX509IssuerSerial
503      *
504      * @return true if the <code>SecurtityTokenReference</code> contains
505      *         a <code>ds:IssuerSerial</code> element
506      */
507     public boolean containsX509IssuerSerial() {
508         return this.lengthX509IssuerSerial() > 0;
509     }
510 
511     /***
512      * Method lengthX509IssuerSerial.
513      *
514      * @return number of <code>ds:IssuerSerial</code> elements in
515      *         the <code>SecurtityTokenReference</code>
516      */
517     public int lengthX509IssuerSerial() {
518         return this.length(WSConstants.SIG_NS, "X509IssuerSerial");
519     }
520 
521     /***
522      * Method containsKeyIdentifier.
523      *
524      * @return true if the <code>SecurtityTokenReference</code> contains
525      *         a <code>wsse:KeyIdentifier</code> element
526      */
527     public boolean containsKeyIdentifier() {
528         return this.lengthKeyIdentifier() > 0;
529     }
530 
531     /***
532      * Method lengthKeyIdentifier.
533      *
534      * @return number of <code>wsse:KeyIdentifier</code> elements in
535      *         the <code>SecurtityTokenReference</code>
536      */
537     public int lengthKeyIdentifier() {
538         if (wssConfig.getProcessNonCompliantMessages()) {
539             for (int i = 0; i < WSConstants.WSSE_NS_ARRAY.length; ++i) {
540                 int len = this.length(WSConstants.WSSE_NS_ARRAY[i], "KeyIdentifier");
541                 if (len > 0) {
542                     return len;
543                 }
544             }
545         } else {
546             return this.length(wssConfig.getWsseNS(), "KeyIdentifier");
547         }
548         return 0;
549     }
550 
551     /***
552      * Method length.
553      *
554      * @param namespace
555      * @param localname
556      * @return number of elements with matching localname and namespace
557      */
558     public int length(String namespace, String localname) {
559         NodeList childNodes = this.element.getChildNodes();
560         int maxLength = childNodes.getLength();
561         int result = 0;
562         for (int i = 0; i < maxLength; i++) {
563             Node n = childNodes.item(i);
564             if (n.getNodeType() == Node.ELEMENT_NODE) {
565                 String ns = n.getNamespaceURI();
566                 String name = n.getLocalName();
567                 if (((namespace != null)
568                         && (ns != null)
569                         && namespace.equals(ns))
570                         || ((namespace == null) && (ns == null))) {
571                     if (localname.equals(name)) {
572                         result++;
573                     }
574                 }
575             }
576         }
577         return result;
578     }
579 
580     /***
581      * get the dom element.
582      * <p/>
583      *
584      * @return
585      */
586     public Element getElement() {
587         return this.element;
588     }
589 
590     /***
591      * set the id.
592      * <p/>
593      *
594      * @param id
595      */
596     public void setID(String id) {
597         String prefix =
598                 WSSecurityUtil.setNamespace(this.element,
599                         wssConfig.getWsuNS(),
600                         WSConstants.WSU_PREFIX);
601         this.element.setAttributeNS(wssConfig.getWsuNS(), prefix + ":Id", id);
602     }
603 
604     /***
605      * return the string representation.
606      * <p/>
607      *
608      * @return
609      */
610     public String toString() {
611         return DOM2Writer.nodeToString((Node) this.element);
612     }
613 }