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 }