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