001 /*
002 * Cumulus4j - Securing your data in the cloud - http://cumulus4j.org
003 * Copyright (C) 2011 NightLabs Consulting GmbH
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Affero General Public License as
007 * published by the Free Software Foundation, either version 3 of the
008 * License, or (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
013 * GNU Affero General Public License for more details.
014 *
015 * You should have received a copy of the GNU Affero General Public License
016 * along with this program. If not, see <http://www.gnu.org/licenses/>.
017 */
018 package org.cumulus4j.keymanager.front.webapp;
019
020 import java.io.IOException;
021
022 import javax.ws.rs.Consumes;
023 import javax.ws.rs.DELETE;
024 import javax.ws.rs.POST;
025 import javax.ws.rs.Path;
026 import javax.ws.rs.PathParam;
027 import javax.ws.rs.Produces;
028 import javax.ws.rs.WebApplicationException;
029 import javax.ws.rs.core.MediaType;
030 import javax.ws.rs.core.Response;
031 import javax.ws.rs.core.Response.Status;
032
033 import org.cumulus4j.keymanager.AppServer;
034 import org.cumulus4j.keymanager.AppServerManager;
035 import org.cumulus4j.keymanager.Session;
036 import org.cumulus4j.keymanager.SessionManager;
037 import org.cumulus4j.keymanager.front.shared.AcquireCryptoSessionResponse;
038 import org.cumulus4j.keymanager.front.shared.Error;
039 import org.cumulus4j.keystore.AuthenticationException;
040 import org.cumulus4j.keystore.KeyStore;
041 import org.slf4j.Logger;
042 import org.slf4j.LoggerFactory;
043
044 /**
045 * <p>
046 * REST service for session management.
047 * </p><p>
048 * Whenever the app-server wants to read or write data, it requires access to keys. The keys
049 * are sent to the app-server, held in memory temporarily, and forgotten after a while.
050 * </p><p>
051 * In order
052 * to make it impossible to ask a key-server for keys without being authorised to do so, the
053 * key-server manages crypto-sessions. Only someone knowing a valid crypto-session's ID can query
054 * keys. This should already exclude everyone except for the app-server who is told the crypto-session-ID
055 * (originating from the client).
056 * </p><p>
057 * But to make things even more secure, each crypto-session can additionally be locked and unlocked.
058 * Most of the time, a session
059 * is locked and thus prevents keys from being read. Only in those moments when the client delegates
060 * work to the app-server (and the app-server thus requires key-access to fulfill the client's command), the
061 * corresponding crypto-session is unlocked.
062 * </p>
063 *
064 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
065 */
066 @Path("CryptoSession")
067 @Consumes({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
068 @Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON})
069 public class CryptoSessionService extends AbstractService
070 {
071 private static final Logger logger = LoggerFactory.getLogger(CryptoSessionService.class);
072
073 /**
074 * <p>
075 * Acquire a session.
076 * </p><p>
077 * Even if there exists already a session for the combination of <code>keyStoreID</code> and
078 * <code>appServerID</code>, a new session might be created. Old sessions are only re-used and refreshed,
079 * if they are currently in the 'released' state.
080 * </p><p>
081 * The session can be explicitely {@link #delete(String, String, String)deleted} or automatically disappears
082 * after a {@link AcquireCryptoSessionResponse#getExpiry() certain time}. Thus, refreshing it is necessary to keep
083 * it "alive".
084 * </p>
085 * @param keyStoreID identifier of the {@link KeyStore} to work with.
086 * @param appServerID identifier of the (logical) app-server (who will access the key-store on behalf of the client).
087 */
088 @Path("{keyStoreID}/{appServerID}/acquire")
089 @POST
090 public AcquireCryptoSessionResponse acquire(@PathParam("keyStoreID") String keyStoreID, @PathParam("appServerID") String appServerID)
091 {
092 Auth auth = getAuth();
093 try {
094 SessionManager sessionManager = getSessionManager(keyStoreID, appServerID);
095
096 Session session;
097 try {
098 session = sessionManager.acquireSession(auth.getUserName(), auth.getPassword());
099 } catch (AuthenticationException e) {
100 throw new WebApplicationException(Response.status(Status.FORBIDDEN).entity(new Error(e)).build());
101 }
102
103 logger.debug("open: authUserName='{}' cryptoSessionID='{}'", auth.getUserName(), session.getCryptoSessionID());
104
105 AcquireCryptoSessionResponse result = new AcquireCryptoSessionResponse();
106 result.setCryptoSessionID(session.getCryptoSessionID());
107 result.setExpiry(session.getExpiry());
108 return result;
109 } finally {
110 // extra safety => overwrite password
111 auth.clear();
112 }
113 }
114
115 private SessionManager getSessionManager(String keyStoreID, String appServerID)
116 {
117 AppServerManager appServerManager;
118 try {
119 appServerManager = keyStoreManager.getAppServerManager(keyStoreID);
120 } catch (IOException e) {
121 logger.error("getSessionManager: " + e, e);
122 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(new Error(e)).build());
123 }
124
125 AppServer appServer = appServerManager.getAppServerForAppServerID(appServerID);
126 if (appServer == null) {
127 String message = "There is no AppServer with appServerID=\"" + appServerID + "\"!";
128 logger.debug("getSessionManager: " + message);
129 throw new WebApplicationException(Response.status(Status.NOT_FOUND).entity(new Error(message)).build());
130 }
131
132 return appServer.getSessionManager();
133 }
134
135 /**
136 * Refresh (reacquire) an already acquired crypto-session.
137 * Prevent it from being automatically released+deleted due to timeout.
138 *
139 * @param keyStoreID identifier of the {@link KeyStore} to work with.
140 * @param appServerID identifier of the (logical) app-server (who will access the key-store on behalf of the client).
141 * @param cryptoSessionID identifier of the crypto-session to refresh (generated by {@link #acquire(String, String)}).
142 */
143 @Path("{keyStoreID}/{appServerID}/{cryptoSessionID}/reacquire")
144 @POST
145 public AcquireCryptoSessionResponse reacquire(@PathParam("keyStoreID") String keyStoreID, @PathParam("appServerID") String appServerID, @PathParam("cryptoSessionID") String cryptoSessionID)
146 {
147 SessionManager sessionManager = getSessionManager(keyStoreID, appServerID);
148 Session session = sessionManager.getSessionForCryptoSessionID(cryptoSessionID);
149 if (session == null) {
150 String message = "There is no session with cryptoSessionID='" + cryptoSessionID + "'!";
151 logger.debug("reacquire: " + message);
152 throw new WebApplicationException(Response.status(Status.NOT_FOUND).entity(new Error(message)).build());
153 }
154
155 try {
156 session.reacquire();
157 } catch (Exception x) {
158 logger.debug("reacquire: " + x, x);
159 throw new WebApplicationException(Response.status(Status.NOT_FOUND).entity(new Error(x)).build());
160 }
161
162 AcquireCryptoSessionResponse result = new AcquireCryptoSessionResponse();
163 result.setCryptoSessionID(session.getCryptoSessionID());
164 result.setExpiry(session.getExpiry());
165 return result;
166 }
167
168 // @Path("{keyStoreID}/{appServerID}/{cryptoSessionID}/unlock")
169 // @GET
170 // public void unlock_GET(@PathParam("keyStoreID") String keyStoreID, @PathParam("appServerID") String appServerID, @PathParam("cryptoSessionID") String cryptoSessionID)
171 // {
172 // unlock(keyStoreID, appServerID, cryptoSessionID);
173 // }
174
175 // /**
176 // * Unlock a crypto-session (grant access to keys).
177 // *
178 // * @param keyStoreID identifier of the {@link KeyStore} to work with.
179 // * @param appServerID identifier of the (logical) app-server (who will access the key-store on behalf of the client).
180 // * @param cryptoSessionID identifier of the crypto-session to unlock (generated by {@link #open(String, String)}).
181 // */
182 // @Path("{keyStoreID}/{appServerID}/{cryptoSessionID}/unlock")
183 // @POST
184 // public void unlock(@PathParam("keyStoreID") String keyStoreID, @PathParam("appServerID") String appServerID, @PathParam("cryptoSessionID") String cryptoSessionID)
185 // {
186 // SessionManager sessionManager = getSessionManager(keyStoreID, appServerID);
187 // Session session = sessionManager.getSessionForCryptoSessionID(cryptoSessionID);
188 // if (session == null)
189 // throw new WebApplicationException(Response.status(Status.NOT_FOUND).entity(new Error("There is no session with cryptoSessionID='" + cryptoSessionID + "'!")).build());
190 //
191 // session.setLocked(false);
192 // }
193
194 // @Path("{keyStoreID}/{appServerID}/{cryptoSessionID}/lock")
195 // @GET
196 // public void lock_GET(@PathParam("keyStoreID") String keyStoreID, @PathParam("appServerID") String appServerID, @PathParam("cryptoSessionID") String cryptoSessionID)
197 // {
198 // lock(keyStoreID, appServerID, cryptoSessionID);
199 // }
200
201 /**
202 * Release a crypto-session (prevent further access to keys).
203 *
204 * @param keyStoreID identifier of the {@link KeyStore} to work with.
205 * @param appServerID identifier of the (logical) app-server (who will access the key-store on behalf of the client).
206 * @param cryptoSessionID identifier of the crypto-session to lock (generated by {@link #acquire(String, String)}).
207 */
208 @Path("{keyStoreID}/{appServerID}/{cryptoSessionID}/release")
209 @POST
210 public void release(@PathParam("keyStoreID") String keyStoreID, @PathParam("appServerID") String appServerID, @PathParam("cryptoSessionID") String cryptoSessionID)
211 {
212 SessionManager sessionManager = getSessionManager(keyStoreID, appServerID);
213 Session session = sessionManager.getSessionForCryptoSessionID(cryptoSessionID);
214 if (session == null) {
215 String message = "There is no session with cryptoSessionID='" + cryptoSessionID + "'!";
216 logger.debug("release: " + message);
217 throw new WebApplicationException(Response.status(Status.NOT_FOUND).entity(new Error(message)).build());
218 }
219
220 session.release();
221 }
222
223 /**
224 * Destroy a crypto-session. No further key-exchange will be possible within the scope
225 * of this session. This is similar to {@link #release(String, String, String)}, but
226 * instead of only locking the session (setting a boolean state), it removes the session completely
227 * and thus releases any memory and other resources allocated.
228 *
229 * @param keyStoreID identifier of the {@link KeyStore} to work with.
230 * @param appServerID identifier of the (logical) app-server (who will access the key-store on behalf of the client).
231 * @param cryptoSessionID identifier of the crypto-session to be closed (generated by {@link #acquire(String, String)}).
232 */
233 @Path("{keyStoreID}/{appServerID}/{cryptoSessionID}")
234 @DELETE
235 public void delete(@PathParam("keyStoreID") String keyStoreID, @PathParam("appServerID") String appServerID, @PathParam("cryptoSessionID") String cryptoSessionID)
236 {
237 logger.debug("delete: appServerID='{}' cryptoSessionID='{}'", appServerID, cryptoSessionID);
238
239 SessionManager sessionManager = getSessionManager(keyStoreID, appServerID);
240 Session session = sessionManager.getSessionForCryptoSessionID(cryptoSessionID);
241 if (session != null)
242 session.destroy();
243 }
244 }