1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30 package dk.sosi.seal.model;
31
32 import dk.sosi.seal.model.constants.*;
33 import dk.sosi.seal.modelbuilders.ModelBuildException;
34 import dk.sosi.seal.pki.CredentialVaultSignatureProvider;
35 import dk.sosi.seal.vault.CredentialVault;
36 import dk.sosi.seal.xml.XmlUtil;
37 import org.w3c.dom.Document;
38 import org.w3c.dom.Element;
39 import org.w3c.dom.NodeList;
40 import org.xml.sax.SAXException;
41
42 import javax.xml.transform.dom.DOMSource;
43 import javax.xml.validation.Schema;
44 import javax.xml.validation.Validator;
45 import java.io.IOException;
46 import java.util.Date;
47 import java.util.LinkedList;
48 import java.util.List;
49
50
51
52
53
54 public class LibertyRequestDOMEnhancer {
55
56 private static Schema schema;
57
58 private final CredentialVault credentialVault;
59 private final Document document;
60
61 private String wsAddressingMessageID;
62 private String wsAddressingAction;
63 private String wsAddressingTo;
64 private IdentityToken identityToken;
65
66 public LibertyRequestDOMEnhancer(CredentialVault credentialVault, Document document) {
67 if (credentialVault == null) throw new IllegalArgumentException("CredentialVault cannot be null");
68 if (document == null) throw new IllegalArgumentException("Document cannot be null");
69 this.credentialVault = credentialVault;
70 this.document = document;
71
72 schemaValidate(document);
73 }
74
75
76
77
78
79
80 public void setWSAddressingMessageID(String wsAddressingMessageID) {
81 if (isNullOrEmpty(wsAddressingMessageID)) {
82 throw new IllegalArgumentException("'wsAddressingMessageID' cannot be null or empty");
83 }
84 this.wsAddressingMessageID = wsAddressingMessageID;
85 }
86
87
88
89
90
91
92 public void setWSAddressingAction(String wsAddressingAction) {
93 if (isNullOrEmpty(wsAddressingAction)) {
94 throw new IllegalArgumentException("'wsAddressingAction' cannot be null or empty");
95 }
96 this.wsAddressingAction = wsAddressingAction;
97 }
98
99
100
101
102
103
104 public void setWSAddressingTo(String wsAddressingTo) {
105 if (isNullOrEmpty(wsAddressingTo)) {
106 throw new IllegalArgumentException("'wsAddressingTo' cannot be null or empty");
107 }
108 this.wsAddressingTo = wsAddressingTo;
109 }
110
111
112
113
114
115
116 public void setIdentityToken(IdentityToken identityToken) {
117 if (identityToken == null) {
118 throw new IllegalArgumentException("'identityToken' cannot be null");
119 }
120 this.identityToken = identityToken;
121 }
122
123
124 public void enhanceAndSign() {
125 final Element header = XmlUtil.getFirstChildElementNS(document.getDocumentElement(), NameSpaces.SOAP_SCHEMA, SOAPTags.HEADER_UNPREFIXED);
126
127 checkIdentityTokenSet();
128 checkForOldWSAddressingVersion(header);
129 checkForExistingWSSecurityHeader(header);
130
131 ensureNameSpaceDeclarations(document);
132
133 String messageIDWsuId = ensureWSAddressingMessageIDHeader(header);
134 String actionWsuId = ensureWSAddressingActionHeader(header);
135 String toWsuId = null;
136
137 if (wsAddressingTo != null || hasWsAddressingToHeader(header)) {
138 toWsuId = ensureWSAddressingTo(header);
139 }
140
141 String sbfWsuID = ensureLibertyFrameworkHeader(header);
142
143 Element securityHeader = addWSSecurityHeader(header);
144 String securityWsuId = ensureIdAttribute(securityHeader, "security");
145
146 String timestampWsuID = addWsuTimestamp(securityHeader);
147 String securityTokenReferenceID = addIdentityTokenAndCorrespondingSecurityTokenReference(securityHeader);
148 String bodyWsuId = ensureIdAttributeInBody(document);
149
150 final List<SignatureConfiguration.Reference> references = new LinkedList<SignatureConfiguration.Reference>();
151 references.add(new SignatureConfiguration.Reference(messageIDWsuId, SignatureConfiguration.Type.DIRECT_REFERENCE));
152 references.add(new SignatureConfiguration.Reference(actionWsuId, SignatureConfiguration.Type.DIRECT_REFERENCE));
153 if (toWsuId != null) {
154 references.add(new SignatureConfiguration.Reference(toWsuId, SignatureConfiguration.Type.DIRECT_REFERENCE));
155 }
156 references.add(new SignatureConfiguration.Reference(sbfWsuID, SignatureConfiguration.Type.DIRECT_REFERENCE));
157 references.add(new SignatureConfiguration.Reference(timestampWsuID, SignatureConfiguration.Type.DIRECT_REFERENCE));
158 references.add(new SignatureConfiguration.Reference(securityTokenReferenceID, SignatureConfiguration.Type.SECURITY_TOKEN_REFERENCE));
159 references.add(new SignatureConfiguration.Reference(bodyWsuId, SignatureConfiguration.Type.DIRECT_REFERENCE));
160
161 final SignatureConfiguration config = getSignatureConfiguration(securityWsuId, references);
162 SignatureUtil.sign(new CredentialVaultSignatureProvider(credentialVault), document, config);
163 }
164
165
166 protected SignatureConfiguration getSignatureConfiguration(String securityWsuId, List<SignatureConfiguration.Reference> references) {
167 return new SignatureConfiguration(references.toArray(new SignatureConfiguration.Reference[0]), securityWsuId, null);
168 }
169
170 private void checkIdentityTokenSet() throws ModelBuildException {
171 if (identityToken == null) {
172 throw new ModelBuildException("IdentityToken must be set!");
173 }
174 }
175
176 private void checkForOldWSAddressingVersion(Element header) throws ModelException {
177 final String message = "Document contains WS-Addressing headers in '" + NameSpaces.WSA_SCHEMA
178 + "' namespace. Only WS-Addressing 1.0 (namespace '" + NameSpaces.WSA_1_0_SCHEMA + "') supported as required "
179 + "by the Liberty Basic SOAP Binding is supported.";
180 checkForElementWithNamespace(header, NameSpaces.WSA_SCHEMA, message);
181 }
182
183 private void checkForExistingWSSecurityHeader(Element header) throws ModelException {
184 checkForElementWithNamespace(header, NameSpaces.WSSE_SCHEMA, "Document already contains a WS-Security header!");
185 }
186
187 private void checkForElementWithNamespace(Element element, String namesSpace, String errorMessage) throws ModelException {
188 final NodeList childNodes = element.getChildNodes();
189 for (int i = 0; i < childNodes.getLength(); i++) {
190 if (namesSpace.equals(childNodes.item(i).getNamespaceURI())) {
191 throw new ModelException(errorMessage);
192 }
193 }
194 }
195
196 private void ensureNameSpaceDeclarations(Document document) {
197 final Element root = document.getDocumentElement();
198
199 root.setAttributeNS(NameSpaces.XMLNS_SCHEMA, NameSpaces.NS_XMLNS + ":" + NameSpaces.NS_WSA, NameSpaces.WSA_1_0_SCHEMA);
200 root.setAttributeNS(NameSpaces.XMLNS_SCHEMA, NameSpaces.NS_XMLNS + ":" + NameSpaces.NS_WSU, NameSpaces.WSU_SCHEMA);
201 root.setAttributeNS(NameSpaces.XMLNS_SCHEMA, NameSpaces.NS_XMLNS + ":" + NameSpaces.NS_SBF, NameSpaces.LIBERTY_SBF_SCHEMA);
202 root.setAttributeNS(NameSpaces.XMLNS_SCHEMA, NameSpaces.NS_XMLNS + ":" + NameSpaces.NS_SBFPROFILE, NameSpaces.LIBERTY_SBF_PROFILE_SCHEMA);
203 root.setAttributeNS(NameSpaces.XMLNS_SCHEMA, NameSpaces.NS_XMLNS + ":" + NameSpaces.NS_SAML, NameSpaces.SAML2ASSERTION_SCHEMA);
204 root.setAttributeNS(NameSpaces.XMLNS_SCHEMA, NameSpaces.NS_XMLNS + ":" + NameSpaces.NS_DS, NameSpaces.DSIG_SCHEMA);
205 root.setAttributeNS(NameSpaces.XMLNS_SCHEMA, NameSpaces.NS_XMLNS + ":" + NameSpaces.NS_WSSE, NameSpaces.WSSE_SCHEMA);
206 }
207
208 private String ensureIdAttributeInBody(Document document) {
209 final Element body = XmlUtil.getFirstChildElementNS(document.getDocumentElement(), NameSpaces.SOAP_SCHEMA, SOAPTags.BODY_UNPREFIXED);
210 return ensureIdAttribute(body, "body");
211 }
212
213 private String ensureWSAddressingMessageIDHeader(Element header) {
214 String value;
215 boolean overwrite;
216 if (wsAddressingMessageID != null) {
217 value = wsAddressingMessageID;
218 overwrite = true;
219 } else {
220 value = XmlUtil.generateUUID();
221 overwrite = false;
222 }
223 return ensureElement(header, NameSpaces.WSA_1_0_SCHEMA, WSATags.MESSAGE_ID, WSATags.MESSAGE_ID_PREFIXED, value, overwrite, "messageID");
224 }
225
226 private String ensureWSAddressingActionHeader(Element header) {
227 final boolean overwrite = wsAddressingAction != null;
228 return ensureElement(header, NameSpaces.WSA_1_0_SCHEMA, WSATags.ACTION, WSATags.ACTION_PREFIXED, wsAddressingAction, overwrite, "action");
229 }
230
231 private String ensureWSAddressingTo(Element header) {
232 final boolean overwrite = wsAddressingTo != null;
233 return ensureElement(header, NameSpaces.WSA_1_0_SCHEMA, WSATags.TO, WSATags.TO_PREFIXED, wsAddressingTo, overwrite, "to");
234 }
235
236 private String ensureLibertyFrameworkHeader(Element header) {
237 Element framework = XmlUtil.getFirstChildElementNS(header, NameSpaces.LIBERTY_SBF_SCHEMA, LibertyTags.FRAMEWORK);
238 if (framework == null) {
239 framework = document.createElementNS(NameSpaces.LIBERTY_SBF_SCHEMA, LibertyTags.FRAMEWORK_PREFIXED);
240 header.appendChild(framework);
241 }
242 framework.setAttribute(LibertyAttributes.VERSION, LibertyValues.VERSION);
243 framework.setAttributeNS(NameSpaces.LIBERTY_SBF_PROFILE_SCHEMA, LibertyAttributes.PROFILE_PREFIXED, LibertyValues.PROFILE);
244 return ensureIdAttribute(framework, "sbf");
245 }
246
247 private String addWsuTimestamp(Element securityHeader) {
248 final Element timestamp = document.createElementNS(NameSpaces.WSU_SCHEMA, WSUTags.TIMESTAMP_PREFIXED);
249 securityHeader.appendChild(timestamp);
250 final Element created = document.createElementNS(NameSpaces.WSU_SCHEMA, WSUTags.CREATED_PREFIXED);
251
252 created.setTextContent(XmlUtil.toXMLTimeStamp(new Date(), true));
253 timestamp.appendChild(created);
254 return ensureIdAttribute(timestamp, "ts");
255 }
256
257 private String addIdentityTokenAndCorrespondingSecurityTokenReference(Element securityHeader) {
258 securityHeader.appendChild(document.importNode(identityToken.getDOM().getDocumentElement(), true));
259
260 final Element securityTokenReference = document.createElementNS(NameSpaces.WSSE_SCHEMA, WSSETags.SECURITY_TOKEN_REFERENCE_PREFIXED);
261 securityTokenReference.setAttributeNS(NameSpaces.WSSE_1_1_SCHEMA, WSSE11Attributes.TOKEN_TYPE_PREFIXED, WSSEValues.SAML_TOKEN_TYPE);
262 securityHeader.appendChild(securityTokenReference);
263
264 final Element keyIdentifier = document.createElementNS(NameSpaces.WSSE_SCHEMA, WSSETags.KEY_IDENTIFIER_PREFIXED);
265 keyIdentifier.setAttribute(WSSEAttributes.VALUE_TYPE, WSSEValues.VALUE_TYPE_SAML_ID);
266 keyIdentifier.setTextContent(identityToken.getID());
267 securityTokenReference.appendChild(keyIdentifier);
268 return ensureIdAttribute(securityTokenReference, "str");
269 }
270
271
272 private String ensureElement(Element parent, String nameSpaceURI, String tagName, String tagNamePrefixed, String value, boolean overwriteValue, String idValue) {
273 Element child = XmlUtil.getFirstChildElementNS(parent, nameSpaceURI, tagName);
274 if (child == null) {
275 if (isNullOrEmpty(value)) {
276 throw new ModelBuildException("Required element '" + tagName + "' in namespace '" + nameSpaceURI + "' not present in document. " +
277 "Failed to set it as no value has been provided for it.");
278 }
279 child = document.createElementNS(nameSpaceURI, tagNamePrefixed);
280 child.setTextContent(value);
281 parent.appendChild(child);
282 } else if (overwriteValue) {
283 child.setTextContent(value);
284 }
285 return ensureIdAttribute(child, idValue);
286 }
287
288 private String ensureIdAttribute(Element element, String value) {
289 final String id = element.getAttributeNS(NameSpaces.WSU_SCHEMA, WSUAttributes.ID_UNPREFIXED);
290 if (isNullOrEmpty(id)) {
291 element.setAttributeNS(NameSpaces.WSU_SCHEMA, WSUAttributes.ID_PREFIXED, value);
292 return value;
293 } else {
294 return id;
295 }
296 }
297
298 private boolean hasWsAddressingToHeader(Element header) {
299 return XmlUtil.getFirstChildElementNS(header, NameSpaces.WSA_1_0_SCHEMA, WSATags.TO) != null;
300 }
301
302 private Element addWSSecurityHeader(Element header) {
303 final Element securityHeader = document.createElementNS(NameSpaces.WSSE_SCHEMA, WSSETags.SECURITY_PREFIXED);
304 securityHeader.setAttribute(WSSEAttributes.MUST_UNDERSTAND, "1");
305 header.appendChild(securityHeader);
306 return securityHeader;
307 }
308
309 private void schemaValidate(Document envelope) {
310 try {
311 final Validator validator = getSchema().newValidator();
312 validator.validate(new DOMSource(envelope));
313 } catch (SAXException e) {
314 throw new ModelBuildException("Error validating SOAP message", e);
315 } catch (IOException e) {
316 throw new ModelBuildException("Error validating SOAP message", e);
317 }
318
319 }
320
321 private static synchronized Schema getSchema() throws SAXException {
322 if (schema == null) {
323 schema = SchemaUtil.loadSchema("/liberty/req/standard-soap.xsd");
324 }
325 return schema;
326 }
327
328 private boolean isNullOrEmpty(String string) {
329 return string == null || string.equals("");
330 }
331
332 }