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    }