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 }