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    }