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 }