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 }