View Javadoc
1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements. See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership. The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License. You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied. See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.wss4j.stax.test.saml;
20  
21  import java.io.IOException;
22  import java.io.InputStream;
23  import java.security.KeyStore;
24  import java.security.cert.X509Certificate;
25  import java.util.ArrayList;
26  import java.util.Collections;
27  import java.util.List;
28  
29  import javax.crypto.KeyGenerator;
30  import javax.crypto.SecretKey;
31  import javax.security.auth.callback.Callback;
32  import javax.security.auth.callback.CallbackHandler;
33  import javax.security.auth.callback.UnsupportedCallbackException;
34  import javax.xml.parsers.DocumentBuilder;
35  import javax.xml.parsers.DocumentBuilderFactory;
36  
37  import org.apache.wss4j.common.crypto.Merlin;
38  import org.apache.wss4j.common.saml.SAMLCallback;
39  import org.apache.wss4j.common.saml.bean.ActionBean;
40  import org.apache.wss4j.common.saml.bean.AttributeBean;
41  import org.apache.wss4j.common.saml.bean.AttributeStatementBean;
42  import org.apache.wss4j.common.saml.bean.AuthDecisionStatementBean;
43  import org.apache.wss4j.common.saml.bean.AuthenticationStatementBean;
44  import org.apache.wss4j.common.saml.bean.ConditionsBean;
45  import org.apache.wss4j.common.saml.bean.KeyInfoBean;
46  import org.apache.wss4j.common.saml.bean.NameIDBean;
47  import org.apache.wss4j.common.saml.bean.SubjectBean;
48  import org.apache.wss4j.common.saml.bean.SubjectConfirmationDataBean;
49  import org.apache.wss4j.common.saml.bean.SubjectLocalityBean;
50  import org.apache.wss4j.common.saml.bean.Version;
51  import org.apache.wss4j.common.saml.builder.SAML1Constants;
52  import org.apache.wss4j.common.saml.builder.SAML2Constants;
53  import org.apache.wss4j.common.util.KeyUtils;
54  import org.apache.wss4j.dom.WSConstants;
55  import org.apache.wss4j.dom.message.WSSecEncryptedKey;
56  import org.w3c.dom.Document;
57  import org.w3c.dom.Element;
58  
59  public class SAMLCallbackHandlerImpl implements CallbackHandler {
60  
61      public enum Statement {
62          AUTHN, ATTR, AUTHZ
63      }
64  
65      private String subjectName = "uid=joe,ou=people,ou=saml-demo,o=example.com";
66      private String subjectQualifier = "www.example.com";
67      private String confirmationMethod = SAML1Constants.CONF_SENDER_VOUCHES;
68      private X509Certificate[] certs;
69      private Statement statement = Statement.AUTHN;
70      private KeyInfoBean.CERT_IDENTIFIER certIdentifier = KeyInfoBean.CERT_IDENTIFIER.X509_CERT;
71      private byte[] ephemeralKey;
72      private String issuer;
73      private String issuerFormat;
74      private Version samlVersion = Version.SAML_11;
75  
76      private String subjectNameIDFormat;
77      private String subjectLocalityIpAddress;
78      private String subjectLocalityDnsAddress;
79      private String resource;
80      private List<Object> customAttributeValues;
81      private ConditionsBean conditions;
82      private SubjectConfirmationDataBean subjectConfirmationData;
83  
84      private boolean signAssertion = true;
85  
86      public SAMLCallbackHandlerImpl() {
87      }
88  
89      @Override
90      public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
91          if (callbacks[0] instanceof SAMLCallback) {
92              try {
93                  SAMLCallback samlCallback = (SAMLCallback) callbacks[0];
94                  KeyStore keyStore = KeyStore.getInstance("jks");
95                  InputStream input = this.getClass().getClassLoader().getResourceAsStream("saml/issuer.jks");
96                  keyStore.load(input, "default".toCharArray());
97                  input.close();
98  
99                  Merlin crypto = new Merlin();
100                 crypto.setKeyStore(keyStore);
101                 samlCallback.setIssuerCrypto(crypto);
102                 samlCallback.setIssuerKeyName("samlissuer");
103                 samlCallback.setIssuerKeyPassword("default");
104                 samlCallback.setSignAssertion(this.signAssertion);
105                 samlCallback.setIssuer(issuer);
106                 samlCallback.setIssuerFormat(issuerFormat);
107 
108                 if (conditions != null) {
109                     samlCallback.setConditions(conditions);
110                 }
111 
112                 SubjectBean subjectBean =
113                         new SubjectBean(subjectName, subjectQualifier, confirmationMethod);
114                 if (subjectNameIDFormat != null) {
115                     subjectBean.setSubjectNameIDFormat(subjectNameIDFormat);
116                 }
117                 if (subjectConfirmationData != null) {
118                     subjectBean.setSubjectConfirmationNameID(new NameIDBean("dummy", null, null));
119                     subjectBean.setSubjectConfirmationData(subjectConfirmationData);
120                 }
121 
122                 if (SAML1Constants.CONF_HOLDER_KEY.equals(confirmationMethod)
123                         || SAML2Constants.CONF_HOLDER_KEY.equals(confirmationMethod)) {
124                     try {
125                         KeyInfoBean keyInfo = createKeyInfo();
126                         subjectBean.setKeyInfo(keyInfo);
127                     } catch (Exception ex) {
128                         throw new IOException("Problem creating KeyInfo: " + ex.getMessage());
129                     }
130                 }
131                 samlCallback.setSubject(subjectBean);
132 
133                 if (getSamlVersion() == Version.SAML_11) {
134                     samlCallback.setSamlVersion(Version.SAML_11);
135                     createAndSetStatement(subjectBean, samlCallback);
136                 } else {
137                     samlCallback.setSamlVersion(Version.SAML_20);
138                     createAndSetStatement(null, samlCallback);
139                 }
140             } catch (Exception e) {
141                 throw new IOException(e);
142             }
143         }
144     }
145 
146     /**
147      * Note that the SubjectBean parameter should be null for SAML2.0
148      */
149     protected void createAndSetStatement(SubjectBean subjectBean, SAMLCallback callback) {
150         if (statement == Statement.AUTHN) {
151             AuthenticationStatementBean authBean = new AuthenticationStatementBean();
152             if (subjectBean != null) {
153                 authBean.setSubject(subjectBean);
154             }
155             if (subjectLocalityIpAddress != null || subjectLocalityDnsAddress != null) {
156                 SubjectLocalityBean subjectLocality = new SubjectLocalityBean();
157                 subjectLocality.setIpAddress(subjectLocalityIpAddress);
158                 subjectLocality.setDnsAddress(subjectLocalityDnsAddress);
159                 authBean.setSubjectLocality(subjectLocality);
160             }
161             authBean.setAuthenticationMethod("Password");
162             callback.setAuthenticationStatementData(Collections.singletonList(authBean));
163         } else if (statement == Statement.ATTR) {
164             AttributeStatementBean attrBean = new AttributeStatementBean();
165             AttributeBean attributeBean = new AttributeBean();
166             if (subjectBean != null) {
167                 attrBean.setSubject(subjectBean);
168                 attributeBean.setSimpleName("role");
169                 attributeBean.setQualifiedName("http://custom-ns");
170             } else {
171                 attributeBean.setQualifiedName("role");
172             }
173             if (customAttributeValues != null) {
174                 attributeBean.setAttributeValues(customAttributeValues);
175             } else {
176                 List<Object> attributes = new ArrayList<>();
177                 attributes.add("user");
178                 attributeBean.setAttributeValues(attributes);
179             }
180             attrBean.setSamlAttributes(Collections.singletonList(attributeBean));
181             callback.setAttributeStatementData(Collections.singletonList(attrBean));
182         } else {
183             AuthDecisionStatementBean authzBean = new AuthDecisionStatementBean();
184             if (subjectBean != null) {
185                 authzBean.setSubject(subjectBean);
186             }
187             ActionBean actionBean = new ActionBean();
188             actionBean.setContents("Read");
189             authzBean.setActions(Collections.singletonList(actionBean));
190             authzBean.setResource("endpoint");
191             authzBean.setDecision(AuthDecisionStatementBean.Decision.PERMIT);
192             authzBean.setResource(resource);
193             callback.setAuthDecisionStatementData(Collections.singletonList(authzBean));
194         }
195     }
196 
197     protected KeyInfoBean createKeyInfo() throws Exception {
198         KeyInfoBean keyInfo = new KeyInfoBean();
199         if (statement == Statement.AUTHN) {
200             keyInfo.setCertificate(certs[0]);
201             keyInfo.setCertIdentifer(certIdentifier);
202         } else if (statement == Statement.ATTR) {
203             // Build a new Document
204             DocumentBuilderFactory docBuilderFactory =
205                     DocumentBuilderFactory.newInstance();
206             docBuilderFactory.setNamespaceAware(true);
207             DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
208             Document doc = docBuilder.newDocument();
209 
210             // Create an Encrypted Key
211             WSSecEncryptedKey encrKey = new WSSecEncryptedKey(doc);
212             encrKey.setKeyIdentifierType(WSConstants.ISSUER_SERIAL);
213             encrKey.setUseThisCert(certs[0]);
214 
215             KeyGenerator keyGen = KeyUtils.getKeyGenerator(WSConstants.AES_128);
216             SecretKey symmetricKey = keyGen.generateKey();
217             encrKey.prepare(null, symmetricKey);
218             ephemeralKey = symmetricKey.getEncoded();
219             keyInfo.setEphemeralKey(ephemeralKey);
220             Element encryptedKeyElement = encrKey.getEncryptedKeyElement();
221 
222             // Append the EncryptedKey to a KeyInfo element
223             Element keyInfoElement =
224                     doc.createElementNS(
225                             WSConstants.SIG_NS, WSConstants.SIG_PREFIX + ":" + WSConstants.KEYINFO_LN
226                     );
227             keyInfoElement.setAttributeNS(
228                     WSConstants.XMLNS_NS, "xmlns:" + WSConstants.SIG_PREFIX, WSConstants.SIG_NS
229             );
230             keyInfoElement.appendChild(encryptedKeyElement);
231 
232             keyInfo.setElement(keyInfoElement);
233         }
234         return keyInfo;
235     }
236 
237     public String getSubjectName() {
238         return subjectName;
239     }
240 
241     public void setSubjectName(String subjectName) {
242         this.subjectName = subjectName;
243     }
244 
245     public String getSubjectQualifier() {
246         return subjectQualifier;
247     }
248 
249     public void setSubjectQualifier(String subjectQualifier) {
250         this.subjectQualifier = subjectQualifier;
251     }
252 
253     public String getConfirmationMethod() {
254         return confirmationMethod;
255     }
256 
257     public void setConfirmationMethod(String confirmationMethod) {
258         this.confirmationMethod = confirmationMethod;
259     }
260 
261     public X509Certificate[] getCerts() {
262         return certs;
263     }
264 
265     public void setCerts(X509Certificate[] certs) {
266         this.certs = certs;
267     }
268 
269     public Statement getStatement() {
270         return statement;
271     }
272 
273     public void setStatement(Statement statement) {
274         this.statement = statement;
275     }
276 
277     public KeyInfoBean.CERT_IDENTIFIER getCertIdentifier() {
278         return certIdentifier;
279     }
280 
281     public void setCertIdentifier(KeyInfoBean.CERT_IDENTIFIER certIdentifier) {
282         this.certIdentifier = certIdentifier;
283     }
284 
285     public byte[] getEphemeralKey() {
286         return ephemeralKey;
287     }
288 
289     public void setEphemeralKey(byte[] ephemeralKey) {
290         this.ephemeralKey = ephemeralKey;
291     }
292 
293     public String getIssuer() {
294         return issuer;
295     }
296 
297     public void setIssuer(String issuer) {
298         this.issuer = issuer;
299     }
300 
301     public void setIssuerFormat(String issuerFormat) {
302         this.issuerFormat = issuerFormat;
303     }
304 
305     public boolean isSignAssertion() {
306         return signAssertion;
307     }
308 
309     public void setSignAssertion(boolean signAssertion) {
310         this.signAssertion = signAssertion;
311     }
312 
313     public Version getSamlVersion() {
314         return samlVersion;
315     }
316 
317     public void setSamlVersion(Version samlVersion) {
318         this.samlVersion = samlVersion;
319     }
320 
321     public void setConditions(ConditionsBean conditionsBean) {
322         this.conditions = conditionsBean;
323     }
324 
325     public void setSubjectNameIDFormat(String subjectNameIDFormat) {
326         this.subjectNameIDFormat = subjectNameIDFormat;
327     }
328 
329     public void setSubjectLocality(String ipAddress, String dnsAddress) {
330         this.subjectLocalityIpAddress = ipAddress;
331         this.subjectLocalityDnsAddress = dnsAddress;
332     }
333 
334     public void setResource(String resource) {
335         this.resource = resource;
336     }
337 
338     public void setCustomAttributeValues(List<Object> customAttributeValues) {
339         this.customAttributeValues = customAttributeValues;
340     }
341 
342     public SubjectConfirmationDataBean getSubjectConfirmationData() {
343         return subjectConfirmationData;
344     }
345 
346     public void setSubjectConfirmationData(SubjectConfirmationDataBean subjectConfirmationData) {
347         this.subjectConfirmationData = subjectConfirmationData;
348     }
349 }