001    /**
002     * Copyright 2010 The Kuali Foundation Licensed under the
003     * Educational Community License, Version 2.0 (the "License"); you may
004     * not use this file except in compliance with the License. You may
005     * obtain a copy of the License at
006     *
007     * http://www.osedu.org/licenses/ECL-2.0
008     *
009     * Unless required by applicable law or agreed to in writing,
010     * software distributed under the License is distributed on an "AS IS"
011     * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
012     * or implied. See the License for the specific language governing
013     * permissions and limitations under the License.
014     */
015    
016    package org.kuali.student.security.util;
017    
018    import java.io.IOException;
019    import java.net.URL;
020    import java.security.KeyStore;
021    import java.security.KeyStoreException;
022    import java.security.NoSuchAlgorithmException;
023    import java.security.PrivateKey;
024    import java.security.PublicKey;
025    import java.security.UnrecoverableKeyException;
026    import java.security.cert.CertificateException;
027    import java.security.cert.X509Certificate;
028    import java.util.Arrays;
029    import java.util.Date;
030    import java.util.Map;
031    
032    import javax.xml.parsers.DocumentBuilder;
033    import javax.xml.parsers.DocumentBuilderFactory;
034    import javax.xml.parsers.ParserConfigurationException;
035    import javax.xml.transform.TransformerException;
036    
037    import org.apache.xml.security.exceptions.XMLSecurityException;
038    import org.apache.xml.security.keys.KeyInfo;
039    import org.apache.xml.security.signature.XMLSignature;
040    import org.apache.xml.security.signature.XMLSignatureException;
041    import org.apache.xml.security.transforms.Transforms;
042    import org.apache.xml.security.utils.Constants;
043    import org.apache.xpath.XPathAPI;
044    import org.opensaml.SAMLAssertion;
045    import org.opensaml.SAMLAttribute;
046    import org.opensaml.SAMLAttributeStatement;
047    import org.opensaml.SAMLAuthenticationStatement;
048    import org.opensaml.SAMLException;
049    import org.opensaml.SAMLNameIdentifier;
050    import org.opensaml.SAMLSubject;
051    import org.w3c.dom.DOMException;
052    import org.w3c.dom.Document;
053    import org.w3c.dom.Element;
054    import org.w3c.dom.Node;
055    import org.w3c.dom.NodeList;
056    
057    /**
058     * This is a description of what this class does - Rich don't forget to fill this in. 
059     * 
060     * @author Kuali Rice Team (kuali-rice@googlegroups.com)
061     *
062     */
063    public class SamlUtils {
064    
065        static {
066            org.apache.xml.security.Init.init();
067         }
068        
069        //All the parameters for the keystore
070        private static String keystoreType;
071        private static String keystoreFile;
072        private static String keystorePass;
073        private static String privateKeyAlias;
074        private static String privateKeyPass;
075        private static String certificateAlias;
076        
077        private static ThreadLocal<Map<String,String>> samlPropertiesHolder = new ThreadLocal<Map<String,String>>();
078        
079        public static SAMLAssertion createAssertion() throws SAMLException, CloneNotSupportedException{
080            
081            String user = getSamlProperties().get("user");
082            String pgt = getSamlProperties().get("proxyGrantingTicket");
083            String proxies = getSamlProperties().get("proxies");
084            String issuer = getSamlProperties().get("samlIssuerForUser");
085            String nameQualifier = getSamlProperties().get("samlIssuerForUser");
086            
087            SAMLAssertion assertion = new SAMLAssertion();
088            assertion.setIssuer(issuer);
089            
090            // prepare subject
091            SAMLNameIdentifier nameId = new SAMLNameIdentifier(user, nameQualifier, "");
092            String[] confirmationMethods = {SAMLSubject.CONF_SENDER_VOUCHES};
093            SAMLSubject subject = new SAMLSubject();
094            subject.setNameIdentifier(nameId);
095            subject.setConfirmationMethods(Arrays.asList(confirmationMethods));
096        
097            // prepare auth statement
098            SAMLAuthenticationStatement authStmt = new SAMLAuthenticationStatement();
099            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    }