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