View Javadoc

1   /**
2    * Copyright 2010 The Kuali Foundation Licensed under the
3    * Educational Community License, Version 2.0 (the "License"); you may
4    * not use this file except in compliance with the License. You may
5    * obtain a copy of the License at
6    *
7    * http://www.osedu.org/licenses/ECL-2.0
8    *
9    * Unless required by applicable law or agreed to in writing,
10   * software distributed under the License is distributed on an "AS IS"
11   * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
12   * or implied. See the License for the specific language governing
13   * permissions and limitations under the License.
14   */
15  
16  package org.kuali.student.security.util;
17  
18  import java.io.IOException;
19  import java.net.URL;
20  import java.security.KeyStore;
21  import java.security.KeyStoreException;
22  import java.security.NoSuchAlgorithmException;
23  import java.security.PrivateKey;
24  import java.security.PublicKey;
25  import java.security.UnrecoverableKeyException;
26  import java.security.cert.CertificateException;
27  import java.security.cert.X509Certificate;
28  import java.util.Arrays;
29  import java.util.Date;
30  import java.util.Map;
31  
32  import javax.xml.parsers.DocumentBuilder;
33  import javax.xml.parsers.DocumentBuilderFactory;
34  import javax.xml.parsers.ParserConfigurationException;
35  import javax.xml.transform.TransformerException;
36  
37  import org.apache.xml.security.exceptions.XMLSecurityException;
38  import org.apache.xml.security.keys.KeyInfo;
39  import org.apache.xml.security.signature.XMLSignature;
40  import org.apache.xml.security.signature.XMLSignatureException;
41  import org.apache.xml.security.transforms.Transforms;
42  import org.apache.xml.security.utils.Constants;
43  import org.apache.xml.security.utils.ElementProxy;
44  import org.apache.xpath.XPathAPI;
45  import org.opensaml.SAMLAssertion;
46  import org.opensaml.SAMLAttribute;
47  import org.opensaml.SAMLAttributeStatement;
48  import org.opensaml.SAMLAuthenticationStatement;
49  import org.opensaml.SAMLException;
50  import org.opensaml.SAMLNameIdentifier;
51  import org.opensaml.SAMLSubject;
52  import org.w3c.dom.DOMException;
53  import org.w3c.dom.Document;
54  import org.w3c.dom.Element;
55  import org.w3c.dom.Node;
56  import org.w3c.dom.NodeList;
57  
58  /**
59   * This is a description of what this class does - Rich don't forget to fill this in. 
60   * 
61   * @author Kuali Rice Team (kuali-rice@googlegroups.com)
62   *
63   */
64  public class SamlUtils {
65  
66      static {
67          org.apache.xml.security.Init.init();
68       }
69      
70      //All the parameters for the keystore
71      private static String keystoreType;
72      private static String keystoreFile;
73      private static String keystorePass;
74      private static String privateKeyAlias;
75      private static String privateKeyPass;
76      private static String certificateAlias;
77      
78      private static ThreadLocal<Map<String,String>> samlPropertiesHolder = new ThreadLocal<Map<String,String>>();
79      
80      public static SAMLAssertion createAssertion() throws SAMLException, CloneNotSupportedException{
81          
82          String user = getSamlProperties().get("user");
83          String pgt = getSamlProperties().get("proxyGrantingTicket");
84          String proxies = getSamlProperties().get("proxies");
85          String issuer = getSamlProperties().get("samlIssuerForUser");
86          String nameQualifier = getSamlProperties().get("samlIssuerForUser");
87          
88          SAMLAssertion assertion = new SAMLAssertion();
89          assertion.setIssuer(issuer);
90          
91          // prepare subject
92          SAMLNameIdentifier nameId = new SAMLNameIdentifier(user, nameQualifier, "");
93          String[] confirmationMethods = {SAMLSubject.CONF_SENDER_VOUCHES};
94          SAMLSubject subject = new SAMLSubject();
95          subject.setNameIdentifier(nameId);
96          subject.setConfirmationMethods(Arrays.asList(confirmationMethods));
97      
98          // prepare auth statement
99          SAMLAuthenticationStatement authStmt = new SAMLAuthenticationStatement();
100         authStmt.setAuthInstant(new Date());
101         authStmt.setAuthMethod(SAMLAuthenticationStatement.AuthenticationMethod_Password);
102         authStmt.setSubject(subject);
103     
104         // prepare attributes
105         SAMLAttributeStatement attrStatement = new SAMLAttributeStatement();
106         SAMLAttribute attr1 = new SAMLAttribute();
107         attr1.setName("proxyGrantingTicket");
108         attr1.setNamespace("http://student.kuali.org/wsdl/security/saml");
109     
110         SAMLAttribute attr2 = new SAMLAttribute();
111         attr2.setName("proxies");
112         attr2.setNamespace("http://student.kuali.org/wsdl/security/saml");
113     
114         attr1.addValue(pgt);
115         attr2.addValue(proxies);
116         
117         attrStatement.addAttribute(attr1);
118         attrStatement.addAttribute(attr2);
119         
120         SAMLSubject subjectInAttr = (SAMLSubject)subject.clone();
121         attrStatement.setSubject(subjectInAttr);
122         
123         // prepare Assertion
124         assertion.addStatement(authStmt);
125         assertion.addStatement(attrStatement);
126         
127         return assertion;    
128     }
129     
130     public static void setSamlProperties(Map<String, String> samlProperties){
131         //this.samlProperties = samlProperties;
132         SamlUtils.samlPropertiesHolder.set(samlProperties);
133     }
134 
135     public static Map<String, String> getSamlProperties() {
136         //return samlProperties;
137         return SamlUtils.samlPropertiesHolder.get();
138     }
139     
140     public static Document signAssertion(SAMLAssertion assertion) throws XMLSecurityException, KeyStoreException, 
141                             NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException, 
142                             ParserConfigurationException, DOMException, SAMLException {
143         ElementProxy.setDefaultPrefix(Constants.SignatureSpecNS, "ds");
144         
145         ClassLoader classLoader = SamlUtils.class.getClassLoader();
146         URL url = classLoader.getResource(keystoreFile);
147 
148         //load the keystore
149         KeyStore ks = KeyStore.getInstance(keystoreType);
150         ks.load(url.openStream(), keystorePass.toCharArray());
151 
152         //get the private key for signing.
153         PrivateKey privateKey = (PrivateKey) ks.getKey(privateKeyAlias, privateKeyPass.toCharArray());
154         
155         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
156 
157         //XML Signature needs to be namespace aware
158         dbf.setNamespaceAware(true);
159 
160         DocumentBuilder db = dbf.newDocumentBuilder();
161         Document doc = db.newDocument();
162 
163         Element root = doc.createElementNS("http://student.kuali.org", "ks:KSSecureToken");
164         root.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ks", "http://student.kuali.org");
165         
166         doc.appendChild(root);
167         // append the SAML assertion to the root.
168         root.appendChild(assertion.toDOM(doc));
169         
170         XMLSignature sig = new XMLSignature(doc, null, XMLSignature.ALGO_ID_SIGNATURE_RSA);
171                                                         //XMLSignature.ALGO_ID_SIGNATURE_DSA);
172 
173         //Append the signature element to the root element before signing because
174         //this is going to be an enveloped signature.
175         //This means the signature is going to be enveloped by the document.
176         //Two other possible forms are enveloping where the document is inside the
177         //signature and detached where they are seperate.
178         //Note that they can be mixed in 1 signature with seperate references as
179         //shown below.
180         root.appendChild(sig.getElement());
181         
182         //create the transforms object for the Document/Reference
183         Transforms transforms = new Transforms(doc);
184 
185         //First we have to strip away the signature element (it's not part of the
186         //signature calculations). The enveloped transform can be used for this.
187         transforms.addTransform(Transforms.TRANSFORM_ENVELOPED_SIGNATURE);
188         //Part of the signature element needs to be canonicalized. It is a kind
189         //of normalizing algorithm for XML. For more information please take a
190         //look at the W3C XML Digital Signature webpage.
191         transforms.addTransform(Transforms.TRANSFORM_C14N_WITH_COMMENTS);
192         //Add the above Document/Reference
193         sig.addDocument("", transforms, Constants.ALGO_ID_DIGEST_SHA1);
194         
195 
196         //Add in the KeyInfo for the certificate that we used the private key of
197         X509Certificate cert = (X509Certificate) ks.getCertificate(certificateAlias);
198 
199         sig.addKeyInfo(cert);
200         sig.addKeyInfo(cert.getPublicKey());
201         // Sign the document
202         sig.sign(privateKey);
203         
204         return doc;
205     }
206 
207     public static SAMLAssertion unsignAssertion(Document doc) throws TransformerException, XMLSecurityException, SAMLException {
208         boolean validSig = false;
209         Element nscontext = doc.createElementNS(null, "namespaceContext");
210         nscontext.setAttributeNS(Constants.NamespaceSpecNS, "xmlns:ds", Constants.SignatureSpecNS);
211         
212         Element sigElement = (Element) XPathAPI.selectSingleNode(doc, "//ds:Signature[1]", nscontext);
213         
214         XMLSignature signature = new XMLSignature(sigElement, null);
215         
216         KeyInfo ki = signature.getKeyInfo();
217         
218         if (ki != null) {
219            X509Certificate cert = ki.getX509Certificate();
220 
221            if (cert != null) {
222                validSig = signature.checkSignatureValue(cert);
223               
224            } else {
225               PublicKey pk = ki.getPublicKey();
226 
227               if (pk != null) {
228                  validSig = signature.checkSignatureValue(pk);
229                  
230               } else {
231                  throw new XMLSignatureException("Could not find a certificate or a public key in the message, can't check the signature");
232               }
233            }
234         } else {
235            throw new XMLSignatureException("Could not find a KeyInfo element in message");
236         }
237         
238         // if the signature is valid get the assertion and return it.
239         if(validSig){
240             NodeList nodeList = doc.getDocumentElement().getChildNodes();
241             Node childNode = null;
242             
243             for(int i=0; i < nodeList.getLength(); i++){
244                 childNode = nodeList.item(i);
245                 if((childNode.getNodeName().equals("Assertion")) && (childNode.getNodeType() == Node.ELEMENT_NODE)){
246                     SAMLAssertion assertion = new SAMLAssertion((Element)childNode);
247                     return assertion;
248                 }
249             }
250         }
251         
252         throw new XMLSignatureException("The message signature was invalid");
253     }
254     
255     /**
256      * @return the keystoreType
257      */
258     public String getKeystoreType() {
259         return keystoreType;
260     }
261 
262     /**
263      * @param keystoreType the keystoreType to set
264      */
265     public void setKeystoreType(String keystoreType) {
266         SamlUtils.keystoreType = keystoreType;
267     }
268 
269     /**
270      * @return the keystoreFile
271      */
272     public String getKeystoreFile() {
273         return keystoreFile;
274     }
275 
276     /**
277      * @param keystoreFile the keystoreFile to set
278      */
279     public void setKeystoreFile(String keystoreFile) {
280         SamlUtils.keystoreFile = keystoreFile;
281     }
282 
283     /**
284      * @return the keystorePass
285      */
286     public String getKeystorePass() {
287         return keystorePass;
288     }
289 
290     /**
291      * @param keystorePass the keystorePass to set
292      */
293     public void setKeystorePass(String keystorePass) {
294         SamlUtils.keystorePass = keystorePass;
295     }
296 
297     /**
298      * @return the privateKeyAlias
299      */
300     public String getPrivateKeyAlias() {
301         return privateKeyAlias;
302     }
303 
304     /**
305      * @param privateKeyAlias the privateKeyAlias to set
306      */
307     public void setPrivateKeyAlias(String privateKeyAlias) {
308         SamlUtils.privateKeyAlias = privateKeyAlias;
309     }
310 
311     /**
312      * @return the privateKeyPass
313      */
314     public String getPrivateKeyPass() {
315         return privateKeyPass;
316     }
317 
318     /**
319      * @param privateKeyPass the privateKeyPass to set
320      */
321     public void setPrivateKeyPass(String privateKeyPass) {
322         SamlUtils.privateKeyPass = privateKeyPass;
323     }
324 
325     /**
326      * @return the certificateAlias
327      */
328     public String getCertificateAlias() {
329         return certificateAlias;
330     }
331 
332     /**
333      * @param certificateAlias the certificateAlias to set
334      */
335     public void setCertificateAlias(String certificateAlias) {
336         SamlUtils.certificateAlias = certificateAlias;
337     }
338 }