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.ByteArrayInputStream;
021 import java.io.IOException;
022 import java.io.InputStreamReader;
023 import java.io.Reader;
024 import java.nio.CharBuffer;
025 import java.util.Arrays;
026
027 import javax.servlet.http.HttpServletRequest;
028 import javax.ws.rs.WebApplicationException;
029 import javax.ws.rs.core.Context;
030 import javax.ws.rs.core.Response;
031 import javax.ws.rs.core.Response.Status;
032
033 import org.cumulus4j.keymanager.front.shared.Error;
034 import org.cumulus4j.keystore.AuthenticationException;
035 import org.cumulus4j.keystore.KeyNotFoundException;
036 import org.cumulus4j.keystore.KeyStore;
037 import org.slf4j.Logger;
038 import org.slf4j.LoggerFactory;
039
040 import com.sun.jersey.core.util.Base64;
041
042 /**
043 * Abstract base class for all REST services of the key-server.
044 *
045 * @author Marco หงุ่ยตระกูล-Schulze - marco at nightlabs dot de
046 */
047 public abstract class AbstractService
048 {
049 private static final Logger logger = LoggerFactory.getLogger(AbstractService.class);
050
051 protected @Context HttpServletRequest request;
052
053 // protected @Context KeyStore keyStore;
054
055 protected @Context KeyStoreManager keyStoreManager;
056
057 /**
058 * Get the authentication information. This method does <b>not</b> verify, if the given authentication information
059 * is correct! It merely checks, if the client sent a 'Basic' authentication header. If it did not,
060 * this method throws a {@link WebApplicationException} with {@link Status#UNAUTHORIZED} or {@link Status#FORBIDDEN}.
061 * If it did, it extracts the information and puts it into an {@link Auth} instance.
062 * @return the {@link Auth} instance extracted from the client's headers. Never <code>null</code>.
063 * @throws WebApplicationException with {@link Status#UNAUTHORIZED}, if the client did not send an 'Authorization' header;
064 * with {@link Status#FORBIDDEN}, if there is an 'Authorization' header, but no 'Basic' authentication header (other authentication modes, like e.g. 'Digest'
065 * are not supported).
066 */
067 protected Auth getAuth()
068 throws WebApplicationException
069 {
070 String authorizationHeader = request.getHeader("Authorization");
071 if (authorizationHeader == null || authorizationHeader.isEmpty()) {
072 logger.debug("getAuth: There is no 'Authorization' header. Replying with a Status.UNAUTHORIZED response asking for 'Basic' authentication.");
073
074 throw new WebApplicationException(Response.status(Status.UNAUTHORIZED).header("WWW-Authenticate", "Basic realm=\"Cumulus4jKeyServer\"").build());
075 }
076
077 logger.debug("getAuth: 'Authorization' header: {}", authorizationHeader);
078
079 if (!authorizationHeader.startsWith("Basic"))
080 throw new WebApplicationException(Response.status(Status.FORBIDDEN).entity(new Error("Only 'Basic' authentication is supported!")).build());
081
082 String basicAuthEncoded = authorizationHeader.substring("Basic".length()).trim();
083 byte[] basicAuthDecodedBA = Base64.decode(basicAuthEncoded);
084 StringBuilder userNameSB = new StringBuilder();
085 char[] password = null;
086
087 ByteArrayInputStream in = new ByteArrayInputStream(basicAuthDecodedBA);
088 CharBuffer cb = CharBuffer.allocate(basicAuthDecodedBA.length + 1);
089 try {
090 Reader r = new InputStreamReader(in, "UTF-8");
091 int charsReadTotal = 0;
092 int charsRead;
093 do {
094 charsRead = r.read(cb);
095
096 if (charsRead > 0)
097 charsReadTotal += charsRead;
098 } while (charsRead >= 0);
099 cb.position(0);
100
101 while (cb.position() < charsReadTotal) {
102 char c = cb.get();
103 if (c == ':')
104 break;
105
106 userNameSB.append(c);
107 }
108
109 if (cb.position() < charsReadTotal) {
110 password = new char[charsReadTotal - cb.position()];
111 int idx = 0;
112 while (cb.position() < charsReadTotal)
113 password[idx++] = cb.get();
114 }
115 } catch (Exception e) {
116 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(new Error(e)).build());
117 } finally {
118 // For extra safety: Overwrite all sensitive memory with 0.
119 Arrays.fill(basicAuthDecodedBA, (byte)0);
120
121 cb.position(0);
122 for (int i = 0; i < cb.capacity(); ++i)
123 cb.put((char)0);
124 }
125
126 Auth auth = new Auth();
127 auth.setUserName(userNameSB.toString());
128 auth.setPassword(password);
129 return auth;
130 }
131
132 /**
133 * Get the {@link Auth} information via {@link #getAuth()} and verify, if they are valid. The validity is checked
134 * by trying to access the key-store.
135 * @param keyStoreID identifier of the key-store to work with.
136 * @return the {@link Auth} information via {@link #getAuth()}; never <code>null</code>.
137 * @throws WebApplicationException with {@link Status#UNAUTHORIZED}, if the client did not send an 'Authorization' header
138 * or if user-name / password is wrong;
139 * with {@link Status#FORBIDDEN}, if there is an 'Authorization' header, but no 'Basic' authentication header (other authentication modes, like e.g. 'Digest'
140 * are not supported); with {@link Status#INTERNAL_SERVER_ERROR}, if there was an {@link IOException}.
141 */
142 protected Auth authenticate(String keyStoreID)
143 throws WebApplicationException
144 {
145 Auth auth = getAuth();
146 try {
147 KeyStore keyStore = keyStoreManager.getKeyStore(keyStoreID);
148 keyStore.getKey(auth.getUserName(), auth.getPassword(), Long.MAX_VALUE);
149 } catch (IOException e) {
150 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).entity(new Error(e)).build());
151 } catch (AuthenticationException e) {
152 throw new WebApplicationException(Response.status(Status.UNAUTHORIZED).entity(new Error(e)).build());
153 } catch (KeyNotFoundException e) {
154 // ignore this - it's expected
155 doNothing(); // Remove warning from PMD report: http://cumulus4j.org/pmd.html
156 }
157 return auth;
158 }
159
160 private static final void doNothing() { }
161 }