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 }