1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.opensaml.saml2.binding.encoding;
18
19 import java.io.ByteArrayOutputStream;
20 import java.io.IOException;
21 import java.io.UnsupportedEncodingException;
22 import java.util.List;
23 import java.util.zip.Deflater;
24 import java.util.zip.DeflaterOutputStream;
25
26 import org.opensaml.common.SAMLObject;
27 import org.opensaml.common.SignableSAMLObject;
28 import org.opensaml.common.binding.SAMLMessageContext;
29 import org.opensaml.common.xml.SAMLConstants;
30 import org.opensaml.saml2.core.RequestAbstractType;
31 import org.opensaml.saml2.core.StatusResponseType;
32 import org.opensaml.util.URLBuilder;
33 import org.opensaml.ws.message.MessageContext;
34 import org.opensaml.ws.message.encoder.MessageEncodingException;
35 import org.opensaml.ws.transport.http.HTTPOutTransport;
36 import org.opensaml.ws.transport.http.HTTPTransportUtils;
37 import org.opensaml.xml.Configuration;
38 import org.opensaml.xml.security.SecurityConfiguration;
39 import org.opensaml.xml.security.SecurityException;
40 import org.opensaml.xml.security.SecurityHelper;
41 import org.opensaml.xml.security.SigningUtil;
42 import org.opensaml.xml.security.credential.Credential;
43 import org.opensaml.xml.util.Base64;
44 import org.opensaml.xml.util.Pair;
45 import org.opensaml.xml.util.XMLHelper;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49
50
51
52
53
54 public class HTTPRedirectDeflateEncoder extends BaseSAML2MessageEncoder {
55
56
57 private final Logger log = LoggerFactory.getLogger(HTTPRedirectDeflateEncoder.class);
58
59
60 public HTTPRedirectDeflateEncoder() {
61 super();
62 }
63
64
65 public String getBindingURI() {
66 return SAMLConstants.SAML2_REDIRECT_BINDING_URI;
67 }
68
69
70 public boolean providesMessageConfidentiality(MessageContext messageContext) throws MessageEncodingException {
71 return false;
72 }
73
74
75 public boolean providesMessageIntegrity(MessageContext messageContext) throws MessageEncodingException {
76 return false;
77 }
78
79
80 protected void doEncode(MessageContext messageContext) throws MessageEncodingException {
81 if (!(messageContext instanceof SAMLMessageContext)) {
82 log.error("Invalid message context type, this encoder only support SAMLMessageContext");
83 throw new MessageEncodingException(
84 "Invalid message context type, this encoder only support SAMLMessageContext");
85 }
86
87 if (!(messageContext.getOutboundMessageTransport() instanceof HTTPOutTransport)) {
88 log.error("Invalid outbound message transport type, this encoder only support HTTPOutTransport");
89 throw new MessageEncodingException(
90 "Invalid outbound message transport type, this encoder only support HTTPOutTransport");
91 }
92
93 SAMLMessageContext samlMsgCtx = (SAMLMessageContext) messageContext;
94
95 String endpointURL = getEndpointURL(samlMsgCtx).buildURL();
96
97 setResponseDestination(samlMsgCtx.getOutboundSAMLMessage(), endpointURL);
98
99 removeSignature(samlMsgCtx);
100
101 String encodedMessage = deflateAndBase64Encode(samlMsgCtx.getOutboundSAMLMessage());
102
103 String redirectURL = buildRedirectURL(samlMsgCtx, endpointURL, encodedMessage);
104
105 HTTPOutTransport out = (HTTPOutTransport) messageContext.getOutboundMessageTransport();
106 HTTPTransportUtils.addNoCacheHeaders(out);
107 HTTPTransportUtils.setUTF8Encoding(out);
108
109 out.sendRedirect(redirectURL);
110 }
111
112
113
114
115
116
117 protected void removeSignature(SAMLMessageContext messageContext) {
118 SignableSAMLObject message = (SignableSAMLObject) messageContext.getOutboundSAMLMessage();
119 if (message.isSigned()) {
120 log.debug("Removing SAML protocol message signature");
121 message.setSignature(null);
122 }
123 }
124
125
126
127
128
129
130
131
132
133
134 protected String deflateAndBase64Encode(SAMLObject message) throws MessageEncodingException {
135 log.debug("Deflating and Base64 encoding SAML message");
136 try {
137 String messageStr = XMLHelper.nodeToString(marshallMessage(message));
138
139 ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
140 Deflater deflater = new Deflater(Deflater.DEFLATED, true);
141 DeflaterOutputStream deflaterStream = new DeflaterOutputStream(bytesOut, deflater);
142 deflaterStream.write(messageStr.getBytes("UTF-8"));
143 deflaterStream.finish();
144
145 return Base64.encodeBytes(bytesOut.toByteArray(), Base64.DONT_BREAK_LINES);
146 } catch (IOException e) {
147 throw new MessageEncodingException("Unable to DEFLATE and Base64 encode SAML message", e);
148 }
149 }
150
151
152
153
154
155
156
157
158
159
160
161
162 protected String buildRedirectURL(SAMLMessageContext messagesContext, String endpointURL, String message)
163 throws MessageEncodingException {
164 log.debug("Building URL to redirect client to");
165 URLBuilder urlBuilder = new URLBuilder(endpointURL);
166
167 List<Pair<String, String>> queryParams = urlBuilder.getQueryParams();
168 queryParams.clear();
169
170 if (messagesContext.getOutboundSAMLMessage() instanceof RequestAbstractType) {
171 queryParams.add(new Pair<String, String>("SAMLRequest", message));
172 } else if (messagesContext.getOutboundSAMLMessage() instanceof StatusResponseType) {
173 queryParams.add(new Pair<String, String>("SAMLResponse", message));
174 } else {
175 throw new MessageEncodingException(
176 "SAML message is neither a SAML RequestAbstractType or StatusResponseType");
177 }
178
179 String relayState = messagesContext.getRelayState();
180 if (checkRelayState(relayState)) {
181 queryParams.add(new Pair<String, String>("RelayState", relayState));
182 }
183
184 Credential signingCredential = messagesContext.getOuboundSAMLMessageSigningCredential();
185 if (signingCredential != null) {
186
187 String sigAlgURI = getSignatureAlgorithmURI(signingCredential, null);
188 Pair<String, String> sigAlg = new Pair<String, String>("SigAlg", sigAlgURI);
189 queryParams.add(sigAlg);
190 String sigMaterial = urlBuilder.buildQueryString();
191
192 queryParams.add(new Pair<String, String>("Signature", generateSignature(signingCredential, sigAlgURI,
193 sigMaterial)));
194 }
195
196 return urlBuilder.buildURL();
197 }
198
199
200
201
202
203
204
205
206
207
208
209 protected String getSignatureAlgorithmURI(Credential credential, SecurityConfiguration config)
210 throws MessageEncodingException {
211
212 SecurityConfiguration secConfig;
213 if (config != null) {
214 secConfig = config;
215 } else {
216 secConfig = Configuration.getGlobalSecurityConfiguration();
217 }
218
219 String signAlgo = secConfig.getSignatureAlgorithmURI(credential);
220
221 if (signAlgo == null) {
222 throw new MessageEncodingException("The signing credential's algorithm URI could not be derived");
223 }
224
225 return signAlgo;
226 }
227
228
229
230
231
232
233
234
235
236
237
238
239 protected String generateSignature(Credential signingCredential, String algorithmURI, String queryString)
240 throws MessageEncodingException {
241
242 log.debug(String.format("Generating signature with key type '%s', algorithm URI '%s' over query string '%s'",
243 SecurityHelper.extractSigningKey(signingCredential).getAlgorithm(), algorithmURI, queryString));
244
245 String b64Signature = null;
246 try {
247 byte[] rawSignature = SigningUtil.signWithURI(signingCredential, algorithmURI, queryString
248 .getBytes("UTF-8"));
249 b64Signature = Base64.encodeBytes(rawSignature, Base64.DONT_BREAK_LINES);
250 log.debug("Generated digital signature value (base64-encoded) {}", b64Signature);
251 } catch (SecurityException e) {
252 log.error("Error during URL signing process", e);
253 throw new MessageEncodingException("Unable to sign URL query string", e);
254 } catch (UnsupportedEncodingException e) {
255
256 }
257
258 return b64Signature;
259 }
260 }