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 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(new Error(e)).build());
122 }
123
124 AppServer appServer = appServerManager.getAppServerForAppServerID(appServerID);
125 if (appServer == null)
126 throw new WebApplicationException(Response.status(Status.NOT_FOUND).entity(new Error("There is no AppServer with appServerID=\"" + appServerID + "\"!")).build());
127
128 return appServer.getSessionManager();
129 }
130
131 /**
132 * Refresh (reacquire) an already acquired crypto-session.
133 * Prevent it from being automatically released+deleted due to timeout.
134 *
135 * @param keyStoreID identifier of the {@link KeyStore} to work with.
136 * @param appServerID identifier of the (logical) app-server (who will access the key-store on behalf of the client).
137 * @param cryptoSessionID identifier of the crypto-session to refresh (generated by {@link #acquire(String, String)}).
138 */
139 @Path("{keyStoreID}/{appServerID}/{cryptoSessionID}/reacquire")
140 @POST
141 public AcquireCryptoSessionResponse reacquire(@PathParam("keyStoreID") String keyStoreID, @PathParam("appServerID") String appServerID, @PathParam("cryptoSessionID") String cryptoSessionID)
142 {
143 SessionManager sessionManager = getSessionManager(keyStoreID, appServerID);
144 Session session = sessionManager.getSessionForCryptoSessionID(cryptoSessionID);
145 if (session == null)
146 throw new WebApplicationException(Response.status(Status.NOT_FOUND).entity(new Error("There is no session with cryptoSessionID='" + cryptoSessionID + "'!")).build());
147
148 try {
149 session.reacquire();
150 } catch (Exception x) {
151 throw new WebApplicationException(Response.status(Status.NOT_FOUND).entity(new Error(x)).build());
152 }
153
154 AcquireCryptoSessionResponse result = new AcquireCryptoSessionResponse();
155 result.setCryptoSessionID(session.getCryptoSessionID());
156 result.setExpiry(session.getExpiry());
157 return result;
158 }
159
160 // @Path("{keyStoreID}/{appServerID}/{cryptoSessionID}/unlock")
161 // @GET
162 // public void unlock_GET(@PathParam("keyStoreID") String keyStoreID, @PathParam("appServerID") String appServerID, @PathParam("cryptoSessionID") String cryptoSessionID)
163 // {
164 // unlock(keyStoreID, appServerID, cryptoSessionID);
165 // }
166
167 // /**
168 // * Unlock a crypto-session (grant access to keys).
169 // *
170 // * @param keyStoreID identifier of the {@link KeyStore} to work with.
171 // * @param appServerID identifier of the (logical) app-server (who will access the key-store on behalf of the client).
172 // * @param cryptoSessionID identifier of the crypto-session to unlock (generated by {@link #open(String, String)}).
173 // */
174 // @Path("{keyStoreID}/{appServerID}/{cryptoSessionID}/unlock")
175 // @POST
176 // public void unlock(@PathParam("keyStoreID") String keyStoreID, @PathParam("appServerID") String appServerID, @PathParam("cryptoSessionID") String cryptoSessionID)
177 // {
178 // SessionManager sessionManager = getSessionManager(keyStoreID, appServerID);
179 // Session session = sessionManager.getSessionForCryptoSessionID(cryptoSessionID);
180 // if (session == null)
181 // throw new WebApplicationException(Response.status(Status.NOT_FOUND).entity(new Error("There is no session with cryptoSessionID='" + cryptoSessionID + "'!")).build());
182 //
183 // session.setLocked(false);
184 // }
185
186 // @Path("{keyStoreID}/{appServerID}/{cryptoSessionID}/lock")
187 // @GET
188 // public void lock_GET(@PathParam("keyStoreID") String keyStoreID, @PathParam("appServerID") String appServerID, @PathParam("cryptoSessionID") String cryptoSessionID)
189 // {
190 // lock(keyStoreID, appServerID, cryptoSessionID);
191 // }
192
193 /**
194 * Release a crypto-session (prevent further access to keys).
195 *
196 * @param keyStoreID identifier of the {@link KeyStore} to work with.
197 * @param appServerID identifier of the (logical) app-server (who will access the key-store on behalf of the client).
198 * @param cryptoSessionID identifier of the crypto-session to lock (generated by {@link #acquire(String, String)}).
199 */
200 @Path("{keyStoreID}/{appServerID}/{cryptoSessionID}/release")
201 @POST
202 public void release(@PathParam("keyStoreID") String keyStoreID, @PathParam("appServerID") String appServerID, @PathParam("cryptoSessionID") String cryptoSessionID)
203 {
204 SessionManager sessionManager = getSessionManager(keyStoreID, appServerID);
205 Session session = sessionManager.getSessionForCryptoSessionID(cryptoSessionID);
206 if (session == null)
207 throw new WebApplicationException(Response.status(Status.NOT_FOUND).entity(new Error("There is no session with cryptoSessionID='" + cryptoSessionID + "'!")).build());
208
209 session.release();
210 }
211
212 /**
213 * Destroy a crypto-session. No further key-exchange will be possible within the scope
214 * of this session. This is similar to {@link #release(String, String, String)}, but
215 * instead of only locking the session (setting a boolean state), it removes the session completely
216 * and thus releases any memory and other resources allocated.
217 *
218 * @param keyStoreID identifier of the {@link KeyStore} to work with.
219 * @param appServerID identifier of the (logical) app-server (who will access the key-store on behalf of the client).
220 * @param cryptoSessionID identifier of the crypto-session to be closed (generated by {@link #acquire(String, String)}).
221 */
222 @Path("{keyStoreID}/{appServerID}/{cryptoSessionID}")
223 @DELETE
224 public void delete(@PathParam("keyStoreID") String keyStoreID, @PathParam("appServerID") String appServerID, @PathParam("cryptoSessionID") String cryptoSessionID)
225 {
226 logger.debug("delete: appServerID='{}' cryptoSessionID='{}'", appServerID, cryptoSessionID);
227
228 SessionManager sessionManager = getSessionManager(keyStoreID, appServerID);
229 Session session = sessionManager.getSessionForCryptoSessionID(cryptoSessionID);
230 if (session != null)
231 session.destroy();
232 }
233 }