Attempt to fix address checking when IPv6 addresses are used. - SIDP-235
[java-idp.git] / src / main / java / edu / internet2 / middleware / shibboleth / idp / session / IdPSessionFilter.java
1 /*
2  * Copyright 2008 University Corporation for Advanced Internet Development, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package edu.internet2.middleware.shibboleth.idp.session;
18
19 import java.io.IOException;
20 import java.security.GeneralSecurityException;
21 import java.util.Arrays;
22
23 import javax.crypto.Mac;
24 import javax.crypto.SecretKey;
25 import javax.servlet.Filter;
26 import javax.servlet.FilterChain;
27 import javax.servlet.FilterConfig;
28 import javax.servlet.ServletException;
29 import javax.servlet.ServletRequest;
30 import javax.servlet.ServletResponse;
31 import javax.servlet.http.Cookie;
32 import javax.servlet.http.HttpServletRequest;
33
34 import org.joda.time.DateTime;
35 import org.opensaml.xml.util.Base64;
36 import org.opensaml.xml.util.DatatypeHelper;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39 import org.slf4j.MDC;
40
41 import edu.internet2.middleware.shibboleth.common.session.SessionManager;
42 import edu.internet2.middleware.shibboleth.idp.authn.AuthenticationEngine;
43
44 /**
45  * A filter that adds the current users {@link Session} the request, if the user has a session.
46  */
47 public class IdPSessionFilter implements Filter {
48
49     /** Class Logger. */
50     private final Logger log = LoggerFactory.getLogger(IdPSessionFilter.class);
51
52     /** Whether the client must always come back from the same address. */
53     private boolean consistentAddress;
54
55     /** IdP session manager. */
56     private SessionManager<Session> sessionManager;
57
58     /** {@inheritDoc} */
59     public void destroy() {
60
61     }
62
63     /** {@inheritDoc} */
64     public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException,
65             ServletException {
66         HttpServletRequest httpRequest = (HttpServletRequest) request;
67
68         Cookie sessionCookie = getIdPSessionCookie(httpRequest);
69         Session idpSession = validateCookie(sessionCookie, httpRequest);
70         if (idpSession != null) {
71             log.trace("Updating IdP session activity time and adding session object to the request");
72             idpSession.setLastActivityInstant(new DateTime());
73             MDC.put("idpSessionId", idpSession.getSessionID());
74             httpRequest.setAttribute(Session.HTTP_SESSION_BINDING_ATTRIBUTE, idpSession);
75         }
76
77         filterChain.doFilter(request, response);
78     }
79
80     /** {@inheritDoc} */
81     public void init(FilterConfig filterConfig) throws ServletException {
82         String sessionManagerId = filterConfig.getInitParameter("sessionManagedId");
83         if (DatatypeHelper.isEmpty(sessionManagerId)) {
84             sessionManagerId = "shibboleth.SessionManager";
85         }
86
87         sessionManager = (SessionManager<Session>) filterConfig.getServletContext().getAttribute(sessionManagerId);
88
89         String consistentAddressParam = filterConfig.getInitParameter("ensureConsistentClientAddress");
90         if (DatatypeHelper.isEmpty(consistentAddressParam)) {
91             consistentAddress = true;
92         } else {
93             consistentAddress = Boolean.parseBoolean(consistentAddressParam);
94         }
95     }
96
97     /**
98      * Gets the IdP session cookie from the current request, if the user currently has a session.
99      * 
100      * @param httpRequest current HTTP request
101      * 
102      * @return the user's current IdP session cookie, if they have a current session, otherwise null
103      */
104     protected Cookie getIdPSessionCookie(HttpServletRequest httpRequest) {
105         log.trace("Attempting to retrieve IdP session cookie.");
106         Cookie[] requestCookies = httpRequest.getCookies();
107
108         if (requestCookies != null) {
109             for (Cookie requestCookie : requestCookies) {
110                 if (DatatypeHelper.safeEquals(requestCookie.getName(), AuthenticationEngine.IDP_SESSION_COOKIE_NAME)) {
111                     log.trace("Found IdP session cookie.");
112                     return requestCookie;
113                 }
114             }
115         }
116
117         return null;
118     }
119
120     /**
121      * Validates the given session cookie against the associated session.
122      * 
123      * @param sessionCookie the session cookie
124      * @param httpRequest the current HTTP request
125      * 
126      * @return the session against which the cookie was validated
127      */
128     protected Session validateCookie(Cookie sessionCookie, HttpServletRequest httpRequest) {
129         if (sessionCookie == null) {
130             return null;
131         }
132
133         // index 0: remote address
134         // index 1: session ID
135         // index 2: Base64(HMAC(index 0 + index 1))
136         String[] valueComponents = sessionCookie.getValue().split("\\|");
137         byte[] remoteAddressBytes = Base64.decode(valueComponents[0]);
138         byte[] sessionIdBytes = Base64.decode(valueComponents[1]);
139         byte[] signatureBytes = Base64.decode(valueComponents[2]);
140
141         if (consistentAddress) {
142             String remoteAddress = new String(remoteAddressBytes);
143             if (!httpRequest.getRemoteAddr().equals(remoteAddress)) {
144                 log.error("Client sent a cookie from addres {} but the cookie was issued to address {}", httpRequest
145                         .getRemoteAddr(), remoteAddress);
146                 return null;
147             }
148         }
149
150         String sessionId = new String(sessionIdBytes);
151         Session userSession = sessionManager.getSession(sessionId);
152
153         if (userSession != null) {
154             SecretKey signingKey = userSession.getSessionSecretKey();
155             try {
156                 Mac mac = Mac.getInstance("HmacSHA256");
157                 mac.init(signingKey);
158                 mac.update(remoteAddressBytes);
159                 mac.update(sessionIdBytes);
160                 byte[] signature = mac.doFinal();
161
162                 if (!Arrays.equals(signature, signatureBytes)) {
163                     log.error("Session cookie signature did not match, the session cookie has been tampered with");
164                     return null;
165                 }
166             } catch (GeneralSecurityException e) {
167                 log.error("Unable to computer over session cookie material", e);
168             }
169         } else {
170             log.debug("No session associated with session ID {} - session must have timed out",
171                             valueComponents[1]);
172         }
173         return userSession;
174     }
175 }