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.saml;
21  
22  import java.security.PublicKey;
23  import java.security.cert.X509Certificate;
24  import java.util.ArrayList;
25  import java.util.List;
26  
27  import javax.xml.crypto.XMLStructure;
28  import javax.xml.crypto.dom.DOMStructure;
29  import javax.xml.crypto.dsig.SignatureMethod;
30  import javax.xml.crypto.dsig.SignedInfo;
31  import javax.xml.crypto.dsig.XMLSignContext;
32  import javax.xml.crypto.dsig.dom.DOMSignContext;
33  import javax.xml.crypto.dsig.spec.C14NMethodParameterSpec;
34  import javax.xml.crypto.dsig.spec.ExcC14NParameterSpec;
35  
36  import org.apache.ws.security.WSConstants;
37  import org.apache.ws.security.WSDocInfo;
38  import org.apache.ws.security.WSEncryptionPart;
39  import org.apache.ws.security.WSSConfig;
40  import org.apache.ws.security.WSSecurityException;
41  import org.apache.ws.security.components.crypto.Crypto;
42  import org.apache.ws.security.components.crypto.CryptoType;
43  import org.apache.ws.security.handler.RequestData;
44  import org.apache.ws.security.message.WSSecHeader;
45  import org.apache.ws.security.message.WSSecSignature;
46  import org.apache.ws.security.message.token.DOMX509Data;
47  import org.apache.ws.security.message.token.DOMX509IssuerSerial;
48  import org.apache.ws.security.message.token.Reference;
49  import org.apache.ws.security.message.token.SecurityTokenReference;
50  import org.apache.ws.security.message.token.X509Security;
51  import org.apache.ws.security.saml.ext.AssertionWrapper;
52  import org.apache.ws.security.saml.ext.OpenSAMLUtil;
53  import org.apache.ws.security.transform.STRTransform;
54  import org.apache.ws.security.util.WSSecurityUtil;
55  import org.w3c.dom.Document;
56  import org.w3c.dom.Element;
57  
58  public class WSSecSignatureSAML extends WSSecSignature {
59  
60      private static org.apache.commons.logging.Log log = 
61          org.apache.commons.logging.LogFactory.getLog(WSSecSignatureSAML.class);
62      private boolean senderVouches = false;
63      private SecurityTokenReference secRefSaml = null;
64      private String secRefID = null;
65      private Element samlToken = null;
66      private Crypto userCrypto = null;
67      private Crypto issuerCrypto = null;
68      private String issuerKeyName = null;
69      private String issuerKeyPW = null;
70      private boolean useDirectReferenceToAssertion = false;
71      
72      /**
73       * Constructor.
74       */
75      public WSSecSignatureSAML() {
76          super();
77          doDebug = log.isDebugEnabled();
78      }
79      /**
80       * Constructor.
81       */
82      public WSSecSignatureSAML(WSSConfig config) {
83          super(config);
84          doDebug = log.isDebugEnabled();
85      }
86  
87      /**
88       * Builds a signed soap envelope with SAML token.
89       * 
90       * The method first gets an appropriate security header. According to the
91       * defined parameters for certificate handling the signature elements are
92       * constructed and inserted into the <code>wsse:Signature</code>
93       * 
94       * @param doc
95       *            The unsigned SOAP envelope as <code>Document</code>
96       * @param uCrypto
97       *            The user's Crypto instance
98       * @param assertion
99       *            the complete SAML assertion
100      * @param iCrypto
101      *            An instance of the Crypto API to handle keystore SAML token
102      *            issuer and to generate certificates
103      * @param iKeyName
104      *            Private key to use in case of "sender-Vouches"
105      * @param iKeyPW
106      *            Password for issuer private key
107      * @param secHeader
108      *            The Security header
109      * @return A signed SOAP envelope as <code>Document</code>
110      * @throws org.apache.ws.security.WSSecurityException
111      */
112     public Document build(
113         Document doc, Crypto uCrypto, AssertionWrapper assertion, 
114         Crypto iCrypto, String iKeyName, String iKeyPW, WSSecHeader secHeader
115     ) throws WSSecurityException {
116 
117         prepare(doc, uCrypto, assertion, iCrypto, iKeyName, iKeyPW, secHeader);
118 
119         String soapNamespace = WSSecurityUtil.getSOAPNamespace(doc.getDocumentElement());
120         if (parts == null) {
121             parts = new ArrayList<WSEncryptionPart>(1);
122             WSEncryptionPart encP = 
123                 new WSEncryptionPart(WSConstants.ELEM_BODY, soapNamespace, "Content");
124             parts.add(encP);
125         } else {
126             for (WSEncryptionPart part : parts) {
127                 if ("STRTransform".equals(part.getName()) && part.getId() == null) {
128                     part.setId(strUri);
129                 }
130             }
131         }
132         
133         //
134         // Add the STRTransform for the SecurityTokenReference to the SAML assertion
135         // if it exists
136         //
137         if (secRefID != null) {
138             WSEncryptionPart encP =
139                 new WSEncryptionPart("STRTransform", soapNamespace, "Content");
140             encP.setId(secRefID);
141             parts.add(encP);
142         }
143         
144         List<javax.xml.crypto.dsig.Reference> referenceList = 
145             addReferencesToSign(parts, secHeader);
146 
147         prependSAMLElementsToHeader(secHeader);
148 
149         if (senderVouches) {
150             computeSignature(referenceList, secHeader, secRefSaml.getElement());
151         } else {
152             computeSignature(referenceList, secHeader, samlToken);
153         }
154         
155         //
156         // if we have a BST prepend it in front of the Signature according to
157         // strict layout rules.
158         //
159         if (bstToken != null) {
160             prependBSTElementToHeader(secHeader);
161         }
162 
163         return doc;
164     }
165 
166     /**
167      * Initialize a WSSec SAML Signature.
168      * 
169      * The method sets up and initializes a WSSec SAML Signature structure after
170      * the relevant information was set. After setup of the references to
171      * elements to sign may be added. After all references are added they can be
172      * signed.
173      * 
174      * This method does not add the Signature element to the security header.
175      * See <code>prependSignatureElementToHeader()</code> method.
176      * 
177      * @param doc
178      *            The SOAP envelope as <code>Document</code>
179      * @param uCrypto
180      *            The user's Crypto instance
181      * @param assertion
182      *            the complete SAML assertion
183      * @param iCrypto
184      *            An instance of the Crypto API to handle keystore SAML token
185      *            issuer and to generate certificates
186      * @param iKeyName
187      *            Private key to use in case of "sender-Vouches"
188      * @param iKeyPW
189      *            Password for issuer private key
190      * @param secHeader
191      *            The Security header
192      * @throws WSSecurityException
193      */
194     public void prepare(
195         Document doc, Crypto uCrypto, AssertionWrapper assertion, Crypto iCrypto, 
196         String iKeyName, String iKeyPW, WSSecHeader secHeader
197     ) throws WSSecurityException {
198 
199         if (doDebug) {
200             log.debug("Beginning ST signing...");
201         }
202 
203         userCrypto = uCrypto;
204         issuerCrypto = iCrypto;
205         document = doc;
206         issuerKeyName = iKeyName;
207         issuerKeyPW = iKeyPW;
208         
209         samlToken = (Element) assertion.toDOM(doc);
210 
211         //
212         // Get some information about the SAML token content. This controls how
213         // to deal with the whole stuff. First get the Authentication statement
214         // (includes Subject), then get the _first_ confirmation method only
215         // thats if "senderVouches" is true.
216         //
217         String confirmMethod = null;
218         List<String> methods = assertion.getConfirmationMethods();
219         if (methods != null && methods.size() > 0) {
220             confirmMethod = methods.get(0);
221         }
222         if (OpenSAMLUtil.isMethodSenderVouches(confirmMethod)) {
223             senderVouches = true;
224         }
225         //
226         // Gather some info about the document to process and store it for
227         // retrieval
228         //
229         wsDocInfo = new WSDocInfo(doc);
230         
231 
232         X509Certificate[] certs = null;
233         PublicKey publicKey = null;
234 
235         if (senderVouches) {
236             CryptoType cryptoType = new CryptoType(CryptoType.TYPE.ALIAS);
237             cryptoType.setAlias(issuerKeyName);
238             certs = issuerCrypto.getX509Certificates(cryptoType);
239             wsDocInfo.setCrypto(issuerCrypto);
240         }
241         //
242         // in case of key holder: - get the user's certificate that _must_ be
243         // included in the SAML token. To ensure the cert integrity the SAML
244         // token must be signed (by the issuer).
245         //
246         else {
247             if (userCrypto == null || !assertion.isSigned()) {
248                 throw new WSSecurityException(
249                     WSSecurityException.FAILURE,
250                     "invalidSAMLsecurity",
251                     new Object[] { "for SAML Signature (Key Holder)" }
252                 );
253             }
254             if (secretKey == null) {
255                 RequestData data = new RequestData();
256                 data.setSigCrypto(userCrypto);
257                 data.setWssConfig(getWsConfig());
258                 SAMLKeyInfo samlKeyInfo = 
259                     SAMLUtil.getCredentialFromSubject(
260                         assertion, data, wsDocInfo, getWsConfig().isWsiBSPCompliant()
261                     );
262                 publicKey = samlKeyInfo.getPublicKey();
263                 certs = samlKeyInfo.getCerts();
264                 wsDocInfo.setCrypto(userCrypto);
265             }
266         }
267         if ((certs == null || certs.length == 0 || certs[0] == null) 
268             && publicKey == null && secretKey == null) {
269             throw new WSSecurityException(
270                 WSSecurityException.FAILURE,
271                 "noCertsFound",
272                 new Object[] { "SAML signature" }
273             );
274         }
275         
276         if (sigAlgo == null) {
277             PublicKey key = null;
278             if (certs != null && certs[0] != null) {
279                 key = certs[0].getPublicKey();
280             } else if (publicKey != null) {
281                 key = publicKey;
282             } else {
283                 throw new WSSecurityException(
284                     WSSecurityException.FAILURE, "unknownSignatureAlgorithm"
285                 );
286             }
287             
288             String pubKeyAlgo = key.getAlgorithm();
289             log.debug("automatic sig algo detection: " + pubKeyAlgo);
290             if (pubKeyAlgo.equalsIgnoreCase("DSA")) {
291                 sigAlgo = WSConstants.DSA;
292             } else if (pubKeyAlgo.equalsIgnoreCase("RSA")) {
293                 sigAlgo = WSConstants.RSA;
294             } else {
295                 throw new WSSecurityException(
296                     WSSecurityException.FAILURE,
297                     "unknownSignatureAlgorithm",
298                     new Object[] {
299                         pubKeyAlgo
300                     }
301                 );
302             }
303         }
304         sig = null;
305         
306         try {
307             C14NMethodParameterSpec c14nSpec = null;
308             if (getWsConfig().isWsiBSPCompliant() && canonAlgo.equals(WSConstants.C14N_EXCL_OMIT_COMMENTS)) {
309                 List<String> prefixes = 
310                     getInclusivePrefixes(secHeader.getSecurityHeader(), false);
311                 c14nSpec = new ExcC14NParameterSpec(prefixes);
312             }
313             
314            c14nMethod = signatureFactory.newCanonicalizationMethod(canonAlgo, c14nSpec);
315         } catch (Exception ex) {
316             log.error("", ex);
317             throw new WSSecurityException(
318                 WSSecurityException.FAILED_SIGNATURE, "noXMLSig", null, ex
319             );
320         }
321 
322         keyInfoUri = getWsConfig().getIdAllocator().createSecureId("KeyId-", keyInfo);
323         secRef = new SecurityTokenReference(doc);
324         strUri = getWsConfig().getIdAllocator().createSecureId("STRId-", secRef);
325         secRef.setID(strUri);
326         
327         if (certs != null && certs.length != 0) {
328             certUri = getWsConfig().getIdAllocator().createSecureId("CertId-", certs[0]);
329         }
330         
331         //
332         // If the sender vouches, then we must sign the SAML token _and_ at
333         // least one part of the message (usually the SOAP body). To do so we
334         // need to - put in a reference to the SAML token. Thus we create a STR
335         // and insert it into the wsse:Security header - set a reference of the
336         // created STR to the signature and use STR Transform during the
337         // signature
338         //
339         try {
340             if (senderVouches) {
341                 secRefSaml = new SecurityTokenReference(doc);
342                 secRefID = getWsConfig().getIdAllocator().createSecureId("STRSAMLId-", secRefSaml);
343                 secRefSaml.setID(secRefID);
344 
345                 if (useDirectReferenceToAssertion) {
346                     Reference ref = new Reference(doc);
347                     ref.setURI("#" + assertion.getId());
348                     if (assertion.getSaml1() != null) {
349                         ref.setValueType(WSConstants.WSS_SAML_KI_VALUE_TYPE);
350                         secRefSaml.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE);
351                     } else if (assertion.getSaml2() != null) {
352                         secRefSaml.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE);
353                     }
354                     secRefSaml.setReference(ref);
355                 } else {
356                     Element keyId = doc.createElementNS(WSConstants.WSSE_NS, "wsse:KeyIdentifier");
357                     String valueType = null;
358                     if (assertion.getSaml1() != null) {
359                         valueType = WSConstants.WSS_SAML_KI_VALUE_TYPE;
360                         secRefSaml.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE);
361                     } else if (assertion.getSaml2() != null) {
362                         valueType = WSConstants.WSS_SAML2_KI_VALUE_TYPE;
363                         secRefSaml.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE);
364                     }
365                     keyId.setAttributeNS(
366                         null, "ValueType", valueType
367                     );
368                     keyId.appendChild(doc.createTextNode(assertion.getId()));
369                     Element elem = secRefSaml.getElement();
370                     elem.appendChild(keyId);
371                 }
372                 wsDocInfo.addTokenElement(secRefSaml.getElement(), false);
373             }
374         } catch (Exception ex) {
375             throw new WSSecurityException(
376                 WSSecurityException.FAILED_SIGNATURE, "noXMLSig", null, ex
377             );
378         }
379         
380         if (senderVouches) {
381             switch (keyIdentifierType) {
382             case WSConstants.BST_DIRECT_REFERENCE:
383                 Reference ref = new Reference(doc);
384                 ref.setURI("#" + certUri);
385                 bstToken = new X509Security(doc);
386                 ((X509Security) bstToken).setX509Certificate(certs[0]);
387                 bstToken.setID(certUri);
388                 wsDocInfo.addTokenElement(bstToken.getElement(), false);
389                 ref.setValueType(bstToken.getValueType());
390                 secRef.setReference(ref);
391                 break;
392                 
393             case WSConstants.X509_KEY_IDENTIFIER :
394                 secRef.setKeyIdentifier(certs[0]);
395                 break;
396                 
397             case WSConstants.SKI_KEY_IDENTIFIER:
398                 secRef.setKeyIdentifierSKI(certs[0], iCrypto != null ? iCrypto : uCrypto);
399                 break;
400 
401             case WSConstants.THUMBPRINT_IDENTIFIER:
402                 secRef.setKeyIdentifierThumb(certs[0]);
403                 break;
404 
405             case WSConstants.ISSUER_SERIAL:
406                 final String issuer = certs[0].getIssuerDN().getName();
407                 final java.math.BigInteger serialNumber = certs[0].getSerialNumber();
408                 final DOMX509IssuerSerial domIssuerSerial =
409                         new DOMX509IssuerSerial(document, issuer, serialNumber);
410                 final DOMX509Data domX509Data = new DOMX509Data(document, domIssuerSerial);
411                 secRef.setX509Data(domX509Data);
412                 break;
413 
414             default:
415                 throw new WSSecurityException(
416                     WSSecurityException.FAILURE, "unsupportedKeyId", new Object[]{}
417                 );
418             }
419         } else if (useDirectReferenceToAssertion) {
420             Reference ref = new Reference(doc);
421             ref.setURI("#" + assertion.getId());
422             if (assertion.getSaml1() != null) {
423                 ref.setValueType(WSConstants.WSS_SAML_KI_VALUE_TYPE);
424                 secRef.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE);
425             } else if (assertion.getSaml2() != null) {
426                 secRef.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE);
427             }
428             secRef.setReference(ref);
429         } else {
430             Element keyId = doc.createElementNS(WSConstants.WSSE_NS, "wsse:KeyIdentifier");
431             String valueType = null;
432             if (assertion.getSaml1() != null) {
433                 valueType = WSConstants.WSS_SAML_KI_VALUE_TYPE;
434                 secRef.addTokenType(WSConstants.WSS_SAML_TOKEN_TYPE);
435             } else if (assertion.getSaml2() != null) {
436                 valueType = WSConstants.WSS_SAML2_KI_VALUE_TYPE;
437                 secRef.addTokenType(WSConstants.WSS_SAML2_TOKEN_TYPE);
438             }
439             keyId.setAttributeNS(
440                 null, "ValueType", valueType
441             );
442             keyId.appendChild(doc.createTextNode(assertion.getId()));
443             Element elem = secRef.getElement();
444             elem.appendChild(keyId);
445         }
446         XMLStructure structure = new DOMStructure(secRef.getElement());
447         wsDocInfo.addTokenElement(secRef.getElement(), false);
448 
449         keyInfo = 
450             keyInfoFactory.newKeyInfo(
451                 java.util.Collections.singletonList(structure), keyInfoUri
452             );
453 
454         wsDocInfo.addTokenElement(samlToken, false);
455     }
456 
457     /**
458      * Prepend the SAML elements to the elements already in the Security header.
459      * 
460      * The method can be called any time after <code>prepare()</code>. This
461      * allows to insert the SAML elements at any position in the Security
462      * header.
463      * 
464      * This methods first prepends the SAML security reference if mode is
465      * <code>senderVouches</code>, then the SAML token itself,
466      * 
467      * @param secHeader
468      *            The security header that holds the BST element.
469      */
470     public void prependSAMLElementsToHeader(WSSecHeader secHeader) {
471         if (senderVouches) {
472             WSSecurityUtil.prependChildElement(
473                 secHeader.getSecurityHeader(), secRefSaml.getElement()
474             );
475         }
476 
477         WSSecurityUtil.prependChildElement(secHeader.getSecurityHeader(), samlToken);
478     }
479 
480     
481     /**
482      * Compute the Signature over the references.
483      * 
484      * After references are set this method computes the Signature for them.
485      * This method can be called any time after the references were set. See
486      * <code>addReferencesToSign()</code>.
487      * 
488      * @throws WSSecurityException
489      */
490     public void computeSignature(
491         List<javax.xml.crypto.dsig.Reference> referenceList, 
492         WSSecHeader secHeader, 
493         Element siblingElement
494     ) throws WSSecurityException {
495         try {
496             java.security.Key key;
497             if (senderVouches) {
498                 key = issuerCrypto.getPrivateKey(issuerKeyName, issuerKeyPW);
499             } else if (secretKey != null) {
500                 key = WSSecurityUtil.prepareSecretKey(sigAlgo, secretKey);
501             } else {
502                 key = userCrypto.getPrivateKey(user, password);
503             }
504             SignatureMethod signatureMethod = 
505                 signatureFactory.newSignatureMethod(sigAlgo, null);
506             SignedInfo signedInfo = 
507                 signatureFactory.newSignedInfo(c14nMethod, signatureMethod, referenceList);
508             
509             sig = signatureFactory.newXMLSignature(
510                     signedInfo, 
511                     keyInfo,
512                     null,
513                     getWsConfig().getIdAllocator().createId("SIG-", null),
514                     null);
515             
516             org.w3c.dom.Element securityHeaderElement = secHeader.getSecurityHeader();
517             //
518             // Prepend the signature element to the security header (after the assertion)
519             //
520             XMLSignContext signContext = null;
521             if (siblingElement != null && siblingElement.getNextSibling() != null) {
522                 signContext = 
523                     new DOMSignContext(key, securityHeaderElement, siblingElement.getNextSibling());
524             } else {
525                 signContext = new DOMSignContext(key, securityHeaderElement);
526             }
527             signContext.putNamespacePrefix(WSConstants.SIG_NS, WSConstants.SIG_PREFIX);
528             if (WSConstants.C14N_EXCL_OMIT_COMMENTS.equals(canonAlgo)) {
529                 signContext.putNamespacePrefix(
530                     WSConstants.C14N_EXCL_OMIT_COMMENTS, 
531                     WSConstants.C14N_EXCL_OMIT_COMMENTS_PREFIX
532                 );
533             }
534             signContext.setProperty(STRTransform.TRANSFORM_WS_DOC_INFO, wsDocInfo);
535             wsDocInfo.setCallbackLookup(callbackLookup);
536             
537             // Add the elements to sign to the Signature Context
538             wsDocInfo.setTokensOnContext((DOMSignContext)signContext);
539 
540             if (secRefSaml != null && secRefSaml.getElement() != null) {
541                 WSSecurityUtil.storeElementInContext((DOMSignContext)signContext, secRefSaml.getElement());
542             }
543             if (secRef != null && secRef.getElement() != null) {
544                 WSSecurityUtil.storeElementInContext((DOMSignContext)signContext, secRef.getElement());
545             }
546             sig.sign(signContext);
547             
548             signatureValue = sig.getSignatureValue().getValue();
549         } catch (Exception ex) {
550             log.error(ex);
551             throw new WSSecurityException(
552                 WSSecurityException.FAILED_SIGNATURE, null, null, ex
553             );
554         }
555     }
556 
557     /**
558      * Return whether a Direct Reference is to be used to reference the assertion. The
559      * default is false.
560      * @return whether a Direct Reference is to be used to reference the assertion
561      */
562     public boolean isUseDirectReferenceToAssertion() {
563         return useDirectReferenceToAssertion;
564     }
565     
566     /**
567      * Set whether a Direct Reference is to be used to reference the assertion. The
568      * default is false.
569      * @param useDirectReferenceToAssertion whether a Direct Reference is to be used
570      *        to reference the assertion
571      */
572     public void setUseDirectReferenceToAssertion(boolean useDirectReferenceToAssertion) {
573         this.useDirectReferenceToAssertion = useDirectReferenceToAssertion;
574     }
575     
576 }