View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.ws.security.message.token;
21  
22  import org.apache.ws.security.WSConstants;
23  import org.apache.ws.security.WSDocInfo;
24  import org.apache.ws.security.WSPasswordCallback;
25  import org.apache.ws.security.WSSecurityException;
26  import org.apache.ws.security.components.crypto.Crypto;
27  import org.apache.ws.security.components.crypto.CryptoType;
28  import org.apache.ws.security.components.crypto.Merlin;
29  import org.apache.ws.security.message.CallbackLookup;
30  import org.apache.ws.security.message.DOMCallbackLookup;
31  import org.apache.ws.security.util.DOM2Writer;
32  import org.apache.ws.security.util.WSSecurityUtil;
33  import org.apache.ws.security.util.Base64;
34  
35  import org.w3c.dom.Document;
36  import org.w3c.dom.Element;
37  import org.w3c.dom.Node;
38  import org.w3c.dom.Text;
39  
40  import javax.xml.namespace.QName;
41  import javax.security.auth.callback.Callback;
42  import javax.security.auth.callback.CallbackHandler;
43  
44  import java.math.BigInteger;
45  import java.security.cert.CertificateEncodingException;
46  import java.security.cert.X509Certificate;
47  import java.util.Arrays;
48  
49  /**
50   * Security Token Reference.
51   *
52   * @author Davanum Srinivas (dims@yahoo.com).
53   */
54  public class SecurityTokenReference {
55      public static final String SECURITY_TOKEN_REFERENCE = "SecurityTokenReference";
56      public static final QName STR_QNAME = 
57          new QName(WSConstants.WSSE_NS, SECURITY_TOKEN_REFERENCE);
58      public static final String SKI_URI = 
59          WSConstants.X509TOKEN_NS + "#X509SubjectKeyIdentifier";
60      public static final String THUMB_URI = 
61          WSConstants.SOAPMESSAGE_NS11 + "#" + WSConstants.THUMBPRINT;
62      public static final String ENC_KEY_SHA1_URI = 
63          WSConstants.SOAPMESSAGE_NS11 + "#" + WSConstants.ENC_KEY_SHA1_URI;
64      private static org.apache.commons.logging.Log log =
65          org.apache.commons.logging.LogFactory.getLog(SecurityTokenReference.class);
66      protected Element element = null;
67      private DOMX509IssuerSerial issuerSerial = null;
68      private byte[] skiBytes = null;
69      private Reference reference = null;
70  
71      /**
72       * Constructor.
73       *
74       * @param elem A SecurityTokenReference element
75       * @throws WSSecurityException
76       */
77      public SecurityTokenReference(Element elem) throws WSSecurityException {
78          this(elem, true);
79      }
80      
81      /**
82       * Constructor.
83       *
84       * @param elem A SecurityTokenReference element
85       * @param bspCompliant whether the SecurityTokenReference processing complies with the 
86       * BSP spec
87       * @throws WSSecurityException
88       */
89      public SecurityTokenReference(Element elem, boolean bspCompliant) throws WSSecurityException {
90          element = elem;
91          QName el = new QName(element.getNamespaceURI(), element.getLocalName());
92          if (!STR_QNAME.equals(el)) {
93              throw new WSSecurityException(WSSecurityException.FAILURE, "badElement", null);
94          }
95          if (bspCompliant) {
96              checkBSPCompliance();
97          }
98          if (containsReference()) {
99              Node node = element.getFirstChild();
100             while (node != null) {
101                 if (Node.ELEMENT_NODE == node.getNodeType()
102                     && WSConstants.WSSE_NS.equals(node.getNamespaceURI())
103                     && "Reference".equals(node.getLocalName())) {
104                     reference = new Reference((Element)node);
105                     break;
106                 }
107                 node = node.getNextSibling();
108             }
109         }
110     }
111 
112     /**
113      * Constructor.
114      *
115      * @param doc The Document
116      */
117     public SecurityTokenReference(Document doc) {
118         element = doc.createElementNS(WSConstants.WSSE_NS, "wsse:SecurityTokenReference");
119     }
120     
121     /**
122      * Add the WSSE Namespace to this STR. The namespace is not added by default for
123      * efficiency purposes.
124      */
125     public void addWSSENamespace() {
126         WSSecurityUtil.setNamespace(element, WSConstants.WSSE_NS, WSConstants.WSSE_PREFIX);
127     }
128     
129     /**
130      * Add the WSU Namespace to this STR. The namespace is not added by default for
131      * efficiency purposes.
132      */
133     public void addWSUNamespace() {
134         WSSecurityUtil.setNamespace(element, WSConstants.WSU_NS, WSConstants.WSU_PREFIX);
135     }
136     
137     /**
138      * Add a wsse11:TokenType attribute to this SecurityTokenReference
139      * @param tokenType the wsse11:TokenType attribute to add
140      */
141     public void addTokenType(String tokenType) {
142         WSSecurityUtil.setNamespace(element, WSConstants.WSSE11_NS, WSConstants.WSSE11_PREFIX);
143         element.setAttributeNS(
144             WSConstants.WSSE11_NS, 
145             WSConstants.WSSE11_PREFIX + ":" + WSConstants.TOKEN_TYPE, 
146             tokenType
147         );
148     }
149     
150     /**
151      * Get the wsse11:TokenType attribute of this SecurityTokenReference
152      * @return the value of the wsse11:TokenType attribute
153      */
154     public String getTokenType() {
155         return element.getAttributeNS(
156             WSConstants.WSSE11_NS, WSConstants.TOKEN_TYPE
157         );
158     }
159 
160     /**
161      * set the reference.
162      *
163      * @param ref
164      */
165     public void setReference(Reference ref) {
166         Element elem = getFirstElement();
167         if (elem != null) {
168             element.replaceChild(ref.getElement(), elem);
169         } else {
170             element.appendChild(ref.getElement());
171         }
172         this.reference = ref;
173     }
174 
175     /**
176      * Gets the Reference.
177      *
178      * @return the <code>Reference</code> element contained in this
179      *         SecurityTokenReference
180      * @throws WSSecurityException
181      */
182     public Reference getReference() throws WSSecurityException {
183         return reference;
184     }
185 
186     /**
187      * Gets the signing token element, which may be a <code>BinarySecurityToken
188      * </code> or a SAML token.
189      * 
190      * The method gets the URI attribute of the {@link Reference} contained in
191      * the {@link SecurityTokenReference} and tries to find the referenced
192      * Element in the document. Alternatively, it gets the value of the KeyIdentifier 
193      * contained in the {@link SecurityTokenReference} and tries to find the referenced
194      * Element in the document.
195      *
196      * @param doc the document that contains the binary security token
197      *            element. This could be different from the document
198      *            that contains the SecurityTokenReference (STR). See
199      *            STRTransform.derefenceBST() method
200      * @param docInfo A WSDocInfo object containing previous results
201      * @param cb A CallbackHandler object to obtain tokens that are not in the message
202      * @return Element containing the signing token, must be a BinarySecurityToken
203      * @throws WSSecurityException if the referenced element is not found.
204      */
205     public Element getTokenElement(
206         Document doc, WSDocInfo docInfo, CallbackHandler cb
207     ) throws WSSecurityException {
208         Reference ref = getReference();
209         String uri = null;
210         String valueType = null;
211         if (ref != null) {
212             uri = ref.getURI();
213             valueType = ref.getValueType();
214         } else {
215             uri = getKeyIdentifierValue();
216             valueType = getKeyIdentifierValueType();
217         }
218         if (log.isDebugEnabled()) {
219             log.debug("Token reference uri: " + uri);
220         }
221         
222         if (uri == null) {
223             throw new WSSecurityException(
224                 WSSecurityException.INVALID_SECURITY, "badReferenceURI"
225             );
226         }
227         
228         Element tokElement = 
229             findProcessedTokenElement(doc, docInfo, cb, uri, valueType);
230         if (tokElement == null) {
231             tokElement = findUnprocessedTokenElement(doc, docInfo, cb, uri, valueType);
232         }
233         
234         if (tokElement == null) {
235             throw new WSSecurityException(
236                 WSSecurityException.SECURITY_TOKEN_UNAVAILABLE,
237                 "noToken",
238                 new Object[]{uri}
239             );
240         }
241         return tokElement;
242     }
243     
244     /**
245      * Find a token that has not been processed already - in other words, it searches for
246      * the element, rather than trying to access previous results to find the element
247      * @param doc Parent Document
248      * @param docInfo WSDocInfo instance
249      * @param cb CallbackHandler instance
250      * @param uri URI of the element
251      * @param type Type of the element
252      * @return A DOM element
253      * @throws WSSecurityException
254      */
255     public Element findUnprocessedTokenElement(
256         Document doc,
257         WSDocInfo docInfo,
258         CallbackHandler cb,
259         String uri,
260         String type
261     ) throws WSSecurityException {
262         String id = uri;
263         if (id.charAt(0) == '#') {
264             id = id.substring(1);
265         }
266         //
267         // Delegate finding the element to the CallbackLookup instance
268         //
269         CallbackLookup callbackLookup = null;
270         if (docInfo != null) {
271             callbackLookup = docInfo.getCallbackLookup();
272         }
273         if (callbackLookup == null) {
274             callbackLookup = new DOMCallbackLookup(doc);
275         }
276         return callbackLookup.getElement(id, type, true);
277     }
278     
279     /**
280      * Find a token that has been processed already - in other words, it access previous
281      * results to find the element, rather than conducting a general search
282      * @param doc Parent Document
283      * @param docInfo WSDocInfo instance
284      * @param cb CallbackHandler instance
285      * @param uri URI of the element
286      * @param type Type of the element
287      * @return A DOM element
288      * @throws WSSecurityException
289      */
290     public Element findProcessedTokenElement(
291         Document doc,
292         WSDocInfo docInfo,
293         CallbackHandler cb,
294         String uri,
295         String type
296     ) throws WSSecurityException {
297         String id = uri;
298         if (id.charAt(0) == '#') {
299             id = id.substring(1);
300         }
301         //
302         // Try to find it from the WSDocInfo instance first
303         //
304         if (docInfo != null) {
305             Element token = docInfo.getTokenElement(id);
306             if (token != null) {
307                 return token;
308             }
309         }
310 
311         // 
312         // Try to find a custom token
313         //
314         if (cb != null && (WSConstants.WSC_SCT.equals(type)
315             || WSConstants.WSC_SCT_05_12.equals(type)
316             || WSConstants.WSS_SAML_KI_VALUE_TYPE.equals(type) 
317             || WSConstants.WSS_SAML2_KI_VALUE_TYPE.equals(type)
318             || KerberosSecurity.isKerberosToken(type))) {
319             //try to find a custom token
320             WSPasswordCallback pwcb = 
321                 new WSPasswordCallback(id, WSPasswordCallback.CUSTOM_TOKEN);
322             try {
323                 cb.handle(new Callback[]{pwcb});
324                 Element assertionElem = pwcb.getCustomToken();
325                 if (assertionElem != null) {
326                     return (Element)doc.importNode(assertionElem, true);
327                 }
328             } catch (Exception e) {
329                 log.debug(e.getMessage(), e);
330                 // Consume this failure
331             }
332         }
333         return null;
334     }
335 
336 
337     /**
338      * Sets the KeyIdentifier Element as a X509 certificate.
339      * Takes a X509 certificate, converts its data into base 64 and inserts
340      * it into a <code>wsse:KeyIdentifier</code> element, which is placed
341      * in the <code>wsse:SecurityTokenReference</code> element.
342      *
343      * @param cert is the X509 certificate to be inserted as key identifier
344      */
345     public void setKeyIdentifier(X509Certificate cert)
346         throws WSSecurityException {
347         Document doc = element.getOwnerDocument();
348         byte data[] = null;
349         try {
350             data = cert.getEncoded();
351         } catch (CertificateEncodingException e) {
352             throw new WSSecurityException(
353                 WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "encodeError", null, e
354             );
355         }
356         Text text = doc.createTextNode(Base64.encode(data));
357         
358         createKeyIdentifier(doc, X509Security.X509_V3_TYPE, text, true);
359     }
360 
361     /**
362      * Sets the KeyIdentifier Element as a X509 Subject-Key-Identifier (SKI).
363      * Takes a X509 certificate, gets the SKI data, converts it into base 64 and
364      * inserts it into a <code>wsse:KeyIdentifier</code> element, which is placed
365      * in the <code>wsse:SecurityTokenReference</code> element.
366      *
367      * @param cert   is the X509 certificate to get the SKI
368      * @param crypto is the Crypto implementation. Used to read SKI info bytes from certificate
369      */
370     public void setKeyIdentifierSKI(X509Certificate cert, Crypto crypto)
371         throws WSSecurityException {
372         //
373         // As per the 1.1 specification, SKI can only be used for a V3 certificate
374         //
375         if (cert.getVersion() != 3) {
376             throw new WSSecurityException(
377                 WSSecurityException.UNSUPPORTED_SECURITY_TOKEN,
378                 "invalidCertForSKI",
379                 new Object[]{Integer.valueOf(cert.getVersion())}
380             );
381         }
382         
383         Document doc = element.getOwnerDocument();
384         // Fall back to Merlin if crypto parameter is null
385         Crypto skiCrypto = crypto;
386         if (skiCrypto == null) {
387             skiCrypto = new Merlin();
388         }
389         byte data[] = skiCrypto.getSKIBytesFromCert(cert);
390         
391         Text text = doc.createTextNode(Base64.encode(data));
392         createKeyIdentifier(doc, SKI_URI, text, true);        
393     }
394 
395     /**
396      * Sets the KeyIdentifier Element as a Thumbprint.
397      * 
398      * Takes a X509 certificate, computes its thumbprint using SHA-1, converts
399      * into base 64 and inserts it into a <code>wsse:KeyIdentifier</code>
400      * element, which is placed in the <code>wsse:SecurityTokenReference</code>
401      * element.
402      * 
403      * @param cert is the X509 certificate to get the thumbprint
404      */
405     public void setKeyIdentifierThumb(X509Certificate cert) throws WSSecurityException {
406         Document doc = element.getOwnerDocument();
407         byte[] encodedCert = null;
408         try {
409             encodedCert = cert.getEncoded();
410         } catch (CertificateEncodingException e1) {
411             throw new WSSecurityException(
412                 WSSecurityException.SECURITY_TOKEN_UNAVAILABLE, "encodeError", null, e1
413             );
414         }
415         try {
416             byte[] encodedBytes = WSSecurityUtil.generateDigest(encodedCert);
417             org.w3c.dom.Text text = doc.createTextNode(Base64.encode(encodedBytes));
418             createKeyIdentifier(doc, THUMB_URI, text, true);
419         } catch (WSSecurityException e1) {
420             throw new WSSecurityException(
421                 WSSecurityException.FAILURE, "noSHA1availabe", null, e1
422             );
423         }
424     }
425     
426     public void setKeyIdentifierEncKeySHA1(String value) throws WSSecurityException {
427         Document doc = element.getOwnerDocument();
428         org.w3c.dom.Text text = doc.createTextNode(value);
429         createKeyIdentifier(doc, ENC_KEY_SHA1_URI, text, true);
430     }
431     
432     public void setKeyIdentifier(String valueType, String keyIdVal) throws WSSecurityException {
433         setKeyIdentifier(valueType, keyIdVal, false);
434     }
435     
436     public void setKeyIdentifier(String valueType, String keyIdVal, boolean base64) 
437         throws WSSecurityException {
438         Document doc = element.getOwnerDocument();
439         createKeyIdentifier(doc, valueType, doc.createTextNode(keyIdVal), base64);
440     }
441 
442     private void createKeyIdentifier(Document doc, String uri, Node node, boolean base64) {
443         Element keyId = doc.createElementNS(WSConstants.WSSE_NS, "wsse:KeyIdentifier");
444         keyId.setAttributeNS(null, "ValueType", uri);
445         if (base64) {
446             keyId.setAttributeNS(null, "EncodingType", BinarySecurity.BASE64_ENCODING);
447         }
448 
449         keyId.appendChild(node);
450         Element elem = getFirstElement();
451         if (elem != null) {
452             element.replaceChild(keyId, elem);
453         } else {
454             element.appendChild(keyId);
455         }
456     }
457 
458     /**
459      * get the first child element.
460      *
461      * @return the first <code>Element</code> child node
462      */
463     public Element getFirstElement() {
464         for (Node currentChild = element.getFirstChild();
465              currentChild != null;
466              currentChild = currentChild.getNextSibling()
467         ) {
468             if (Node.ELEMENT_NODE == currentChild.getNodeType()) {
469                 return (Element) currentChild;
470             }
471         }
472         return null;
473     }
474 
475     /**
476      * Gets the KeyIdentifier.
477      *
478      * @return the the X509 certificate or zero if a unknown key identifier
479      *         type was detected.
480      */
481     public X509Certificate[] getKeyIdentifier(Crypto crypto) throws WSSecurityException {
482         Element elem = getFirstElement();
483         String value = elem.getAttribute("ValueType");
484 
485         if (X509Security.X509_V3_TYPE.equals(value)) {
486             X509Security token = new X509Security(elem);
487             if (token != null) {
488                 X509Certificate cert = token.getX509Certificate(crypto);
489                 return new X509Certificate[]{cert};
490             }
491         } else if (SKI_URI.equals(value)) {
492             X509Certificate cert = getX509SKIAlias(crypto);
493             if (cert != null) {
494                 return new X509Certificate[]{cert};
495             }
496         } else if (THUMB_URI.equals(value)) {
497             Node node = getFirstElement().getFirstChild();
498             if (node == null) {
499                 return null;
500             }
501             if (Node.TEXT_NODE == node.getNodeType()) {
502                 byte[] thumb = Base64.decode(((Text) node).getData());
503                 CryptoType cryptoType = new CryptoType(CryptoType.TYPE.THUMBPRINT_SHA1);
504                 cryptoType.setBytes(thumb);
505                 X509Certificate[] certs = crypto.getX509Certificates(cryptoType);
506                 if (certs != null) {
507                     return new X509Certificate[]{certs[0]};
508                 }
509             }
510         }
511         
512         return null;
513     }
514     
515     public String getKeyIdentifierValue() {
516         if (containsKeyIdentifier()) {
517             Node node = getFirstElement().getFirstChild();
518             if (node == null) {
519                 return null;
520             }
521             if (node.getNodeType() == Node.TEXT_NODE) {
522                 return ((Text) node).getData();
523             }
524         } 
525         return null;
526     }
527     
528     public String getKeyIdentifierValueType() {
529         if (containsKeyIdentifier()) {
530             Element elem = getFirstElement();
531             return elem.getAttribute("ValueType");
532         } 
533         return null;
534     }
535     
536     public String getKeyIdentifierEncodingType() {
537         if (containsKeyIdentifier()) {
538             Element elem = getFirstElement();
539             return elem.getAttribute("EncodingType");
540         } 
541         return null;
542     }
543     
544     public X509Certificate getX509SKIAlias(Crypto crypto) throws WSSecurityException {
545         if (skiBytes == null) {
546             skiBytes = getSKIBytes();
547             if (skiBytes == null) {
548                 return null;
549             }
550         }
551         CryptoType cryptoType = new CryptoType(CryptoType.TYPE.SKI_BYTES);
552         cryptoType.setBytes(skiBytes);
553         X509Certificate[] certs = crypto.getX509Certificates(cryptoType);
554         if (certs != null) {
555             return certs[0];
556         }
557         return null;
558     }
559 
560     public byte[] getSKIBytes() {
561         if (skiBytes != null) {
562             return skiBytes;
563         }
564         Node node = getFirstElement().getFirstChild();
565         if (node == null) {
566             return null;
567         }
568         if (node.getNodeType() == Node.TEXT_NODE) {
569             try {
570                 skiBytes = Base64.decode(((Text) node).getData());
571             } catch (WSSecurityException e) {
572                 return null;
573             }
574         }
575         return skiBytes;
576     }
577 
578 
579     /**
580      * Sets the X509Data.
581      *
582      * @param domX509Data the {@link DOMX509Data} to put into this
583      *            SecurityTokenReference
584      */
585     public void setX509Data(DOMX509Data domX509Data) {
586         Element elem = getFirstElement();
587         if (elem != null) {
588             element.replaceChild(domX509Data.getElement(), elem);
589         } else {
590             element.appendChild(domX509Data.getElement());
591         }
592     }
593     
594     
595     /**
596      * Set an unknown element.
597      *
598      * @param unknownElement the org.w3c.dom.Element to put into this
599      *        SecurityTokenReference
600      */
601     public void setUnknownElement(Element unknownElement) {
602         Element elem = getFirstElement();
603         if (elem != null) {
604             element.replaceChild(unknownElement, elem);
605         } else {
606             element.appendChild(unknownElement);
607         }
608     }
609 
610     /**
611      * Gets the certificate identified with X509 issuerSerial data.
612      *
613      * @return a certificate array or null if nothing found
614      */
615     public X509Certificate[] getX509IssuerSerial(Crypto crypto) throws WSSecurityException {
616         if (issuerSerial == null) {
617             issuerSerial = getIssuerSerial();
618             if (issuerSerial == null) {
619                 return null;
620             }
621         }
622         CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ISSUER_SERIAL);
623         cryptoType.setIssuerSerial(issuerSerial.getIssuer(), issuerSerial.getSerialNumber());
624         return crypto.getX509Certificates(cryptoType);
625     }
626 
627     private DOMX509IssuerSerial getIssuerSerial() throws WSSecurityException {
628         if (issuerSerial != null) {
629             return issuerSerial;
630         }
631         Element elem = getFirstElement();
632         if (elem == null) {
633             return null;
634         }
635         if (WSConstants.X509_DATA_LN.equals(elem.getLocalName())) {
636             elem = 
637                 WSSecurityUtil.findElement(
638                     elem, WSConstants.X509_ISSUER_SERIAL_LN, WSConstants.SIG_NS
639                 );
640         }
641         issuerSerial = new DOMX509IssuerSerial(elem);
642 
643         return issuerSerial;
644     }
645 
646     /**
647      * Method containsReference
648      *
649      * @return true if the <code>SecurityTokenReference</code> contains
650      *         a <code>wsse:Reference</code> element
651      */
652     public boolean containsReference() {
653         return lengthReference() > 0;
654     }
655 
656     /**
657      * Method lengthReference.
658      *
659      * @return number of <code>wsse:Reference</code> elements in
660      *         the <code>SecurityTokenReference</code>
661      */
662     public int lengthReference() {
663         return length(WSConstants.WSSE_NS, "Reference");
664     }
665 
666     /**
667      * Method containsX509IssuerSerial
668      *
669      * @return true if the <code>SecurityTokenReference</code> contains
670      *         a <code>ds:IssuerSerial</code> element
671      */
672     public boolean containsX509IssuerSerial() {
673         return lengthX509IssuerSerial() > 0;
674     }
675 
676     /**
677      * Method containsX509Data
678      *
679      * @return true if the <code>SecurityTokenReference</code> contains
680      *         a <code>ds:X509Data</code> element
681      */
682     public boolean containsX509Data() {
683         return lengthX509Data() > 0;
684     }
685     
686     /**
687      * Method lengthX509IssuerSerial.
688      *
689      * @return number of <code>ds:IssuerSerial</code> elements in
690      *         the <code>SecurityTokenReference</code>
691      */
692     public int lengthX509IssuerSerial() {
693         return length(WSConstants.SIG_NS, WSConstants.X509_ISSUER_SERIAL_LN);
694     }
695 
696     /**
697      * Method lengthX509Data.
698      *
699      * @return number of <code>ds:IssuerSerial</code> elements in
700      *         the <code>SecurityTokenReference</code>
701      */
702     public int lengthX509Data() {
703         return length(WSConstants.SIG_NS, WSConstants.X509_DATA_LN);
704     }
705     
706     /**
707      * Method containsKeyIdentifier.
708      *
709      * @return true if the <code>SecurityTokenReference</code> contains
710      *         a <code>wsse:KeyIdentifier</code> element
711      */
712     public boolean containsKeyIdentifier() {
713         return lengthKeyIdentifier() > 0;
714     }
715     
716     /**
717      * Method lengthKeyIdentifier.
718      *
719      * @return number of <code>wsse:KeyIdentifier</code> elements in
720      *         the <code>SecurityTokenReference</code>
721      */
722     public int lengthKeyIdentifier() {
723         return length(WSConstants.WSSE_NS, "KeyIdentifier");
724     }
725 
726     /**
727      * Method length.
728      *
729      * @param namespace
730      * @param localname
731      * @return number of elements with matching localname and namespace
732      */
733     public int length(String namespace, String localname) {
734         int result = 0;
735         Node node = element.getFirstChild();
736         while (node != null) {
737             if (Node.ELEMENT_NODE == node.getNodeType()) {
738                 String ns = node.getNamespaceURI();
739                 String name = node.getLocalName();
740                 if ((((namespace != null) && namespace.equals(ns))
741                     || ((namespace == null) && (ns == null)))
742                     && (localname.equals(name))
743                 ) {
744                     result++;
745                 }
746             }
747             node = node.getNextSibling();
748         }
749         return result;
750     }
751 
752     /**
753      * Get the DOM element.
754      *
755      * @return the DOM element
756      */
757     public Element getElement() {
758         return element;
759     }
760 
761     /**
762      * set the id.
763      *
764      * @param id
765      */
766     public void setID(String id) {
767         element.setAttributeNS(WSConstants.WSU_NS, WSConstants.WSU_PREFIX + ":Id", id);
768     }
769     
770     /**
771      * Get the id
772      * @return the wsu ID of the element
773      */
774     public String getID() {
775         return element.getAttributeNS(WSConstants.WSU_NS, "Id");
776     }
777 
778     /**
779      * return the string representation.
780      *
781      * @return a representation of this SecurityTokenReference element as a String
782      */
783     public String toString() {
784         return DOM2Writer.nodeToString((Node) element);
785     }
786     
787     /**
788      * A method to check that the SecurityTokenReference is compliant with the BSP spec.
789      * @throws WSSecurityException
790      */
791     private void checkBSPCompliance() throws WSSecurityException {
792         // We can only have one token reference
793         int result = 0;
794         Node node = element.getFirstChild();
795         Element child = null;
796         while (node != null) {
797             if (Node.ELEMENT_NODE == node.getNodeType()) {
798                 result++;
799                 child = (Element)node;
800             }
801             node = node.getNextSibling();
802         }
803         if (result != 1) {
804             throw new WSSecurityException(
805                 WSSecurityException.INVALID_SECURITY, "invalidDataRef"
806             );
807         }
808         if ("KeyIdentifier".equals(child.getLocalName()) 
809             && WSConstants.WSSE_NS.equals(child.getNamespaceURI())) {
810             
811             String valueType = getKeyIdentifierValueType();
812             // ValueType cannot be null
813             if (valueType == null || "".equals(valueType)) {
814                 throw new WSSecurityException(
815                     WSSecurityException.INVALID_SECURITY, "invalidValueType"
816                 );
817             }
818             String encodingType = getFirstElement().getAttribute("EncodingType");
819             // Encoding Type must be equal to Base64Binary if it's specified
820             if (encodingType != null && !"".equals(encodingType)
821                 && !BinarySecurity.BASE64_ENCODING.equals(encodingType)) {
822                 throw new WSSecurityException(
823                     WSSecurityException.INVALID_SECURITY, 
824                     "badEncodingType", 
825                     new Object[] {encodingType}
826                 );
827             }
828             // Encoding type must be specified other than for a SAML Assertion
829             if (!WSConstants.WSS_SAML_KI_VALUE_TYPE.equals(valueType) 
830                 && !WSConstants.WSS_SAML2_KI_VALUE_TYPE.equals(valueType)
831                 && (encodingType == null || "".equals(encodingType))) {
832                 throw new WSSecurityException(
833                     WSSecurityException.INVALID_SECURITY, "noEncodingType"
834                 );
835             }
836         } else if ("Embedded".equals(child.getLocalName())) {
837             result = 0;
838             node = child.getFirstChild();
839             while (node != null) {
840                 if (Node.ELEMENT_NODE == node.getNodeType()) {
841                     result++;
842                     // We cannot have a SecurityTokenReference child element
843                     if ("SecurityTokenReference".equals(node.getLocalName())
844                         && WSConstants.WSSE_NS.equals(node.getNamespaceURI())) {
845                         throw new WSSecurityException(
846                             WSSecurityException.INVALID_SECURITY, "invalidEmbeddedRef"
847                         );
848                     }
849                 }
850                 node = node.getNextSibling();
851             }
852             // We can only have one embedded child
853             if (result != 1) {
854                 throw new WSSecurityException(
855                     WSSecurityException.INVALID_SECURITY, "invalidEmbeddedRef"
856                 );
857             }
858         }
859     }
860     
861     @Override
862     public int hashCode() {
863         int result = 17;
864         try {
865             Reference reference = getReference();
866             if (reference != null) {
867                 result = 31 * result + reference.hashCode();
868             }
869         } catch (WSSecurityException e) {
870             log.error(e);
871         }
872         String keyIdentifierEncodingType = getKeyIdentifierEncodingType();
873         if (keyIdentifierEncodingType != null) {
874             result = 31 * result + keyIdentifierEncodingType.hashCode();
875         }
876         String keyIdentifierValueType = getKeyIdentifierValueType();
877         if (keyIdentifierValueType != null) {
878             result = 31 * result + keyIdentifierValueType.hashCode();
879         }
880         String keyIdentifierValue = getKeyIdentifierValue();
881         if (keyIdentifierValue != null) {
882             result = 31 * result + keyIdentifierValue.hashCode();
883         }
884         String tokenType = getTokenType();
885         if (tokenType != null) {
886             result = 31 * result + tokenType.hashCode();
887         }
888         byte[] skiBytes = getSKIBytes();
889         if (skiBytes != null) {
890             result = 31 * result + Arrays.hashCode(skiBytes);
891         }
892         String issuer = null;
893         BigInteger serialNumber = null;
894         
895         try {
896             issuer = getIssuerSerial().getIssuer();
897             serialNumber = getIssuerSerial().getSerialNumber();
898         } catch (WSSecurityException e) {
899            log.error(e);
900         }
901         if (issuer != null) {
902             result = 31 * result + issuer.hashCode();
903         }
904         if (serialNumber != null) {
905             result = 31 * result + serialNumber.hashCode();
906         }
907         return result;
908     }
909     
910     @Override
911     public boolean equals(Object object) {
912         if (!(object instanceof SecurityTokenReference)) {
913             return false;
914         }
915         SecurityTokenReference tokenReference = (SecurityTokenReference)object;
916         try {
917             if (!getReference().equals(tokenReference.getReference())) {
918                 return false;
919             }
920         } catch (WSSecurityException e) {
921            log.error(e);
922            return false;
923         }
924         if (!compare(getKeyIdentifierEncodingType(), tokenReference.getKeyIdentifierEncodingType())) {
925             return false;
926         }
927         if (!compare(getKeyIdentifierValueType(), tokenReference.getKeyIdentifierValueType())) {
928             return false;
929         }
930         if (!compare(getKeyIdentifierValue(), tokenReference.getKeyIdentifierValue())) {
931             return false;
932         }
933         if (!compare(getTokenType(), tokenReference.getTokenType())) {
934             return false;
935         }
936         if (!Arrays.equals(getSKIBytes(), tokenReference.getSKIBytes())) {
937             return false;
938         }
939         try {
940             if (getIssuerSerial() != null && tokenReference.getIssuerSerial() != null) {
941                 if (!compare(getIssuerSerial().getIssuer(), tokenReference.getIssuerSerial().getIssuer())) {
942                     return false;
943                 }
944                 if (!compare(getIssuerSerial().getSerialNumber(), tokenReference.getIssuerSerial().getSerialNumber())) {
945                     return false;
946                 } 
947             }
948         } catch (WSSecurityException e) {
949            log.error(e);
950            return false;
951         }
952             
953         return true;
954     }
955     
956     private boolean compare(String item1, String item2) {
957         if (item1 == null && item2 != null) { 
958             return false;
959         } else if (item1 != null && !item1.equals(item2)) {
960             return false;
961         }
962         return true;
963     }
964     
965     private boolean compare(BigInteger item1, BigInteger item2) {
966         if (item1 == null && item2 != null) { 
967             return false;
968         } else if (item1 != null && !item1.equals(item2)) {
969             return false;
970         }
971         return true;
972     }
973 }