From de05f999e4570322587c4770995e85aae3194e02 Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Fri, 19 Dec 2008 17:25:33 +0000 Subject: [PATCH] Merged DEV/3.1_ENTERPRISE_ONLY to HEAD 12520: Move supporting heartbeat code into enterprise tree (public part) 12510: Move license and heartbeat into enterprise only code (public part) 12509: Move license and heartbeat into enterprise only code (public part) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@12521 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../org/alfresco/heartbeat/HeartBeat.java | 434 ------------------ .../org/alfresco/heartbeat/HeartBeatTest.java | 312 ------------- .../heartbeat/heartbeatpublic.keystore | Bin 3092 -> 0 bytes .../alfresco/repo/admin/RepoServerMgmt.java | 2 +- .../descriptor/DescriptorServiceImpl.java | 19 +- .../util/security/DecryptingInputStream.java | 372 --------------- .../util/security/EncryptingOutputStream.java | 269 ----------- .../security/EncryptingOutputStreamTest.java | 86 ---- 8 files changed, 8 insertions(+), 1486 deletions(-) delete mode 100644 source/java/org/alfresco/heartbeat/HeartBeat.java delete mode 100644 source/java/org/alfresco/heartbeat/HeartBeatTest.java delete mode 100644 source/java/org/alfresco/heartbeat/heartbeatpublic.keystore delete mode 100644 source/java/org/alfresco/util/security/DecryptingInputStream.java delete mode 100644 source/java/org/alfresco/util/security/EncryptingOutputStream.java delete mode 100644 source/java/org/alfresco/util/security/EncryptingOutputStreamTest.java diff --git a/source/java/org/alfresco/heartbeat/HeartBeat.java b/source/java/org/alfresco/heartbeat/HeartBeat.java deleted file mode 100644 index a39b1aec57..0000000000 --- a/source/java/org/alfresco/heartbeat/HeartBeat.java +++ /dev/null @@ -1,434 +0,0 @@ -/* - * Copyright (C) 2005-2008 Alfresco Software Limited. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have received a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" - */ -package org.alfresco.heartbeat; - -import java.beans.XMLEncoder; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.URL; -import java.security.GeneralSecurityException; -import java.security.KeyStore; -import java.security.PublicKey; -import java.security.SecureRandom; -import java.security.cert.Certificate; -import java.sql.Connection; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.Date; -import java.util.Enumeration; -import java.util.Map; -import java.util.TreeMap; -import java.util.zip.GZIPOutputStream; - -import javax.sql.DataSource; - -import org.alfresco.repo.descriptor.DescriptorDAO; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.descriptor.Descriptor; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.Base64; -import org.alfresco.util.security.EncryptingOutputStream; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.quartz.Job; -import org.quartz.JobDataMap; -import org.quartz.JobDetail; -import org.quartz.JobExecutionContext; -import org.quartz.JobExecutionException; -import org.quartz.Scheduler; -import org.quartz.SimpleTrigger; -import org.quartz.Trigger; -import org.springframework.context.ApplicationContext; - -import de.schlichtherle.util.ObfuscatedString; - -/** - * This class communicates some very basic repository statistics to Alfresco on a regular basis. - * - * @author dward - */ -public class HeartBeat -{ - - /** The logger. */ - private static final Log logger = LogFactory.getLog(HeartBeat.class); - - /** The relative path to the public keystore resource. */ - static final String PUBLIC_STORE = "heartbeatpublic.keystore"; - - /** The password protecting this store. */ - static final char[] PUBLIC_STORE_PWD = new ObfuscatedString(new long[] - { - 0x7D47AC5E71B3B560L, 0xD6F1405DC20AE70AL - }).toString().toCharArray(); - - /** - * Are we running in test mode? If so we send data to local port 9999 rather than an alfresco server. We also use a - * special test encryption certificate and ping on a more frequent basis. - */ - private final boolean testMode; - - /** The transaction service. */ - private final TransactionService transactionService; - - /** DAO for current repository descriptor. */ - private final DescriptorDAO currentRepoDescriptorDAO; - - /** The person service. */ - private final PersonService personService; - - /** The data source. */ - private final DataSource dataSource; - - /** - * The parameters that we expect to remain static throughout the lifetime of the repository. There is no need to - * continuously update these. - */ - private final Map staticParameters; - - /** A secure source of random numbers used for encryption. */ - private final SecureRandom random; - - /** The public key used for encryption. */ - private final PublicKey publicKey; - - /** - * Initialises the heart beat service. Note that dependencies are intentionally 'pulled' rather than injected - * because we don't want these to be reconfigured. - * - * @param context - * the context - */ - public HeartBeat(final ApplicationContext context) - { - this(context, false); - } - - /** - * Initialises the heart beat service, potentially in test mode. Note that dependencies are intentionally 'pulled' - * rather than injected because we don't want these to be reconfigured. - * - * @param context - * the context - * @param testMode - * are we running in test mode? If so we send data to local port 9999 rather than an alfresco server. We - * also use a special test encryption certificate and ping on a more frequent basis. - */ - public HeartBeat(final ApplicationContext context, final boolean testMode) - { - this.testMode = testMode; - this.transactionService = (TransactionService) context.getBean("transactionService"); - this.currentRepoDescriptorDAO = (DescriptorDAO) context.getBean("currentRepoDescriptorDAO"); - this.personService = (PersonService) context.getBean("personService"); - this.dataSource = (DataSource) context.getBean("dataSource"); - this.staticParameters = new TreeMap(); - try - { - // Load up the static parameters - final String ip = getLocalIps(); - this.staticParameters.put("ip", ip); - final String uid; - final Descriptor currentRepoDescriptor = this.currentRepoDescriptorDAO.getDescriptor(); - if (currentRepoDescriptor != null) - { - uid = currentRepoDescriptor.getId(); - this.staticParameters.put("uid", uid); - } - else - { - uid = "Unknown"; - } - final Descriptor serverDescriptor = ((DescriptorDAO) context.getBean("serverDescriptorDAO")) - .getDescriptor(); - this.staticParameters.put("edition", serverDescriptor.getEdition()); - this.staticParameters.put("versionMajor", serverDescriptor.getVersionMajor()); - this.staticParameters.put("versionMinor", serverDescriptor.getVersionMinor()); - this.staticParameters.put("schema", String.valueOf(serverDescriptor.getSchema())); - - // Use some of the unique parameters to seed the random number generator used for encryption - this.random = SecureRandom.getInstance("SHA1PRNG"); - this.random.setSeed((uid + ip + System.currentTimeMillis()).getBytes("UTF-8")); - - // Load the public key from the key store (use the trial one if this is a unit test) - final KeyStore keyStore = KeyStore.getInstance("JKS"); - final InputStream in = getClass().getResourceAsStream(HeartBeat.PUBLIC_STORE); - keyStore.load(in, HeartBeat.PUBLIC_STORE_PWD); - in.close(); - final String jobName = testMode ? "test" : "heartbeat"; - final Certificate cert = keyStore.getCertificate(jobName); - this.publicKey = cert.getPublicKey(); - - // Schedule the heart beat to run regularly - final Scheduler scheduler = (Scheduler) context.getBean("schedulerFactory"); - final JobDetail jobDetail = new JobDetail(jobName, Scheduler.DEFAULT_GROUP, HeartBeatJob.class); - jobDetail.getJobDataMap().put("heartBeat", this); - final Trigger trigger = new SimpleTrigger(jobName + "Trigger", Scheduler.DEFAULT_GROUP, new Date(), null, - SimpleTrigger.REPEAT_INDEFINITELY, testMode ? 1000 : 4 * 60 * 60 * 1000); - scheduler.scheduleJob(jobDetail, trigger); - } - catch (final RuntimeException e) - { - throw e; - } - catch (final Exception e) - { - throw new RuntimeException(e); - } - } - - /** - * Sends encrypted data over HTTP. - * - * @throws IOException - * Signals that an I/O exception has occurred. - * @throws GeneralSecurityException - * an encryption related exception - */ - public void sendData() throws IOException, GeneralSecurityException - { - final HttpURLConnection req = (HttpURLConnection) new URL(this.testMode ? "http://localhost:9999/heartbeat/" - : "http://DAVIDW01.activiti.local:8080/heartbeat/" /*"http://heartbeat.alfresco.com/heartbeat/"*/).openConnection(); - try - { - req.setRequestMethod("POST"); - req.setRequestProperty("Content-Type", "application/octet-stream"); - req.setChunkedStreamingMode(1024); - req.setConnectTimeout(2000); - req.setDoOutput(true); - req.connect(); - sendData(req.getOutputStream()); - if (req.getResponseCode() != HttpURLConnection.HTTP_OK) - { - throw new IOException(req.getResponseMessage()); - } - } - finally - { - try - { - req.disconnect(); - } - catch (final Exception e) - { - } - - } - } - - /** - * Writes the heartbeat data to a given output stream. Parameters are serialized in XML format for maximum forward - * compatibility. - * - * @param dest - * the stream to write to - * @throws IOException - * Signals that an I/O exception has occurred. - * @throws GeneralSecurityException - * an encryption related exception - */ - public void sendData(final OutputStream dest) throws IOException, GeneralSecurityException - { - // Complement the static parameters with some dynamic ones - final Map params = this.transactionService.getRetryingTransactionHelper().doInTransaction( - new RetryingTransactionCallback>() - { - public Map execute() - { - final Map params = new TreeMap(HeartBeat.this.staticParameters); - params.put("numUsers", String.valueOf(HeartBeat.this.personService.getAllPeople().size())); - params.put("maxNodeId", String.valueOf(getMaxNodeId())); - final byte[] licenseKey = HeartBeat.this.currentRepoDescriptorDAO.getLicenseKey(); - if (licenseKey != null) - { - params.put("licenseKey", Base64.encodeBytes(licenseKey, Base64.DONT_BREAK_LINES)); - } - return params; - } - }, true /* readOnly */, false /* requiresNew */); - - // Compress and encrypt the output stream - OutputStream out = new GZIPOutputStream(new EncryptingOutputStream(dest, this.publicKey, this.random), 1024); - - // Encode the parameters to XML - XMLEncoder encoder = null; - try - { - encoder = new XMLEncoder(out); - encoder.writeObject(params); - } - finally - { - if (encoder != null) - { - try - { - encoder.close(); - out = null; - } - catch (final Exception e) - { - } - } - if (out != null) - { - try - { - out.close(); - } - catch (final Exception e) - { - } - } - } - } - - /** - * The scheduler job responsible for triggering a heartbeat on a regular basis. - */ - public static class HeartBeatJob implements Job - { - /* - * (non-Javadoc) - * @see org.quartz.Job#execute(org.quartz.JobExecutionContext) - */ - public void execute(final JobExecutionContext jobexecutioncontext) throws JobExecutionException - { - final JobDataMap dataMap = jobexecutioncontext.getJobDetail().getJobDataMap(); - final HeartBeat heartBeat = (HeartBeat) dataMap.get("heartBeat"); - try - { - heartBeat.sendData(); - } - catch (final Exception e) - { - // Heartbeat errors are non-fatal and will show as single line warnings - HeartBeat.logger.warn(e.toString()); - throw new JobExecutionException(e); - } - } - } - - /** - * Attempts to get all the local IP addresses of this machine in order to distinguish it from other nodes in the - * same network. The machine may use a static IP address in conjunction with a loopback adapter (e.g. to support - * Oracle on Windows), so the IP of the default network interface may not be enough to uniquely identify this - * machine. - * - * @return the local IP addresses, separated by the '/' character - */ - private String getLocalIps() - { - final StringBuilder ip = new StringBuilder(1024); - boolean first = true; - try - { - final Enumeration i = NetworkInterface.getNetworkInterfaces(); - while (i.hasMoreElements()) - { - final NetworkInterface n = i.nextElement(); - final Enumeration j = n.getInetAddresses(); - while (j.hasMoreElements()) - { - InetAddress a = j.nextElement(); - if (a.isLoopbackAddress()) - { - continue; - } - if (first) - { - first = false; - } - else - { - ip.append('/'); - } - ip.append(a.getHostAddress()); - } - } - } - catch (final Exception e) - { - // Ignore - } - return first ? "127.0.0.1" : ip.toString(); - } - - /** - * Gets the maximum repository node id. Note that this isn't the best indication of size, because on oracle, all - * unique IDs are generated from the same sequence. A count(*) would result in an index scan. - * - * @return the max node id - */ - private int getMaxNodeId() - { - Connection connection = null; - Statement stmt = null; - try - { - connection = this.dataSource.getConnection(); - connection.setAutoCommit(true); - stmt = connection.createStatement(); - final ResultSet rs = stmt.executeQuery("select max(id) from alf_node"); - if (!rs.next()) - { - return 0; - } - return rs.getInt(1); - } - catch (final SQLException e) - { - return 0; - } - finally - { - if (stmt != null) - { - try - { - stmt.close(); - } - catch (final Exception e) - { - } - } - if (connection != null) - { - try - { - connection.close(); - } - catch (final Exception e) - { - } - } - } - } -} diff --git a/source/java/org/alfresco/heartbeat/HeartBeatTest.java b/source/java/org/alfresco/heartbeat/HeartBeatTest.java deleted file mode 100644 index 7d9a21a3ae..0000000000 --- a/source/java/org/alfresco/heartbeat/HeartBeatTest.java +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Copyright (C) 2005-2008 Alfresco Software Limited. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have received a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" - */ -package org.alfresco.heartbeat; - -import java.beans.XMLDecoder; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.ServerSocket; -import java.net.Socket; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.util.Map; -import java.util.zip.GZIPInputStream; - -import org.alfresco.util.BaseSpringTest; -import org.alfresco.util.security.DecryptingInputStream; - -/** - * An integration test for the heartbeat service. Fakes an HTTP endpoint with a server socket in order to sure the - * service is functioning correctly. - * - * @author dward - */ -public class HeartBeatTest extends BaseSpringTest -{ - - /** - * Test heart beat. - * - * @throws Exception - * the exception - */ - @SuppressWarnings("unchecked") - public void testHeartBeat() throws Exception - { - // Load the private key from the trial key store - PrivateKey privateKey; - { - final KeyStore keyStore = KeyStore.getInstance("JKS"); - final InputStream in = getClass().getResourceAsStream(HeartBeat.PUBLIC_STORE); - keyStore.load(in, HeartBeat.PUBLIC_STORE_PWD); - in.close(); - privateKey = (PrivateKey) keyStore.getKey("test", HeartBeat.PUBLIC_STORE_PWD); - } - - // Construct a heartbeat instance in test mode (beats every second using test public key) - new HeartBeat(getApplicationContext(), true); - ServerSocket serverSocket = new ServerSocket(9999); - - // Now attempt to parse 4 of the 'beats' - for (int i = 0; i < 4; i++) - { - Socket clientSocket = serverSocket.accept(); - XMLDecoder decoder = null; - InputStream in = null; - OutputStream out = null; - try - { - in = new GZIPInputStream(new DecryptingInputStream(new HttpChunkedInputStream(clientSocket - .getInputStream()), privateKey), 1024); - out = clientSocket.getOutputStream(); - decoder = new XMLDecoder(in); - Map params = (Map) decoder.readObject(); - out.write("HTTP/1.1 200 OK\r\n\r\n".getBytes("ASCII")); - System.out.println(params); - } - finally - { - if (decoder != null) - { - try - { - decoder.close(); - in = null; - } - catch (final Exception e) - { - } - } - if (in != null) - { - try - { - in.close(); - } - catch (final Exception e) - { - } - } - if (out != null) - { - try - { - out.close(); - } - catch (final Exception e) - { - } - } - try - { - clientSocket.close(); - } - catch (Exception e) - { - } - } - - } - serverSocket.close(); - - } - - /** - * Wraps a raw byte stream in a chunked HTTP request to look like a regular input stream. Skips headers and parses - * chunk sizes. - */ - public static class HttpChunkedInputStream extends InputStream - { - /** The raw input stream. */ - private final InputStream socketIn; - - /** A buffer for parsing headers. */ - private StringBuilder headerBuff = new StringBuilder(100); - - /** The current chunk size. */ - private int chunkSize; - - /** The current position in the chunk. */ - private int chunkPosition; - - /** Have we got to the end of the last chunk? */ - private boolean isAtEnd; - - /** - * Instantiates a new http chunked input stream. - * - * @param socketIn - * raw input stream from an HTTP request - * @throws IOException - * Signals that an I/O exception has occurred. - */ - public HttpChunkedInputStream(InputStream socketIn) throws IOException - { - this.socketIn = socketIn; - for (;;) - { - if (getNextHeader().length() == 0) - { - break; - } - } - setNextChunkSize(); - } - - /** - * Gets the next header. - * - * @return the next header - * @throws IOException - * Signals that an I/O exception has occurred. - */ - private String getNextHeader() throws IOException - { - int b; - while ((b = socketIn.read()) != '\n') - { - if (b == -1) - { - throw new EOFException(); - } - headerBuff.append((char) b); // cast to char acceptable because this is ASCII - } - String header = headerBuff.toString().trim(); - headerBuff.setLength(0); - return header; - } - - /** - * Sets the next chunk size by parsing a chunk header. May detect an end of file condition and set isAtEnd = - * true. - * - * @return the next chunk size - * @throws IOException - * Signals that an I/O exception has occurred. - */ - private int setNextChunkSize() throws IOException - { - String chunkHeader = getNextHeader(); - int sepIndex = chunkHeader.indexOf(';'); - if (sepIndex != -1) - { - chunkHeader = chunkHeader.substring(0, sepIndex).trim(); - } - this.chunkSize = Integer.parseInt(chunkHeader, 16); - this.chunkPosition = 0; - if (this.chunkSize == 0) - { - this.isAtEnd = true; - } - return this.chunkSize; - } - - /* - * (non-Javadoc) - * @see java.io.InputStream#close() - */ - @Override - public void close() throws IOException - { - // We intentionally avoid closing the socket input stream here, as that seems to close the entire socket, - // and stops us from being able to write a response! - // this.socketIn.close(); - } - - /* - * (non-Javadoc) - * @see java.io.InputStream#read() - */ - @Override - public int read() throws IOException - { - final byte[] buf = new byte[1]; - int bytesRead; - while ((bytesRead = read(buf)) == 0) - { - ; - } - return bytesRead == -1 ? -1 : buf[0] & 0xFF; - } - - /* - * (non-Javadoc) - * @see java.io.InputStream#read(byte[], int, int) - */ - @Override - public int read(byte[] b, int off, int len) throws IOException - { - if (len == 0) - { - return 0; - } - if (this.isAtEnd) - { - return -1; - } - int bytesToRead = len; - while (bytesToRead > 0) - { - if (this.chunkPosition >= this.chunkSize) - { - // Skip the \r\n after this chunk - String eol = getNextHeader(); - if (eol.length() > 0) - { - throw new IOException("Bad chunk format"); - } - // Read the new chunk header - setNextChunkSize(); - if (this.isAtEnd) - { - // Skip past the trailers. We have to do this in case the same connection is recycled for the - // next request - for (;;) - { - if (getNextHeader().length() == 0) - { - break; - } - } - break; - } - - } - int bytesRead = Math.min(bytesToRead, this.chunkSize - this.chunkPosition); - bytesRead = this.socketIn.read(b, off, bytesRead); - if (bytesRead == -1) - { - break; - } - bytesToRead -= bytesRead; - off += bytesRead; - this.chunkPosition += bytesRead; - } - return bytesToRead == len ? -1 : len - bytesToRead; - } - } -} diff --git a/source/java/org/alfresco/heartbeat/heartbeatpublic.keystore b/source/java/org/alfresco/heartbeat/heartbeatpublic.keystore deleted file mode 100644 index 26706f8ef24df87c40d89d032236dbda38b87cf8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3092 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 if the end of the stream has been reached - * @throws IOException - * Signals that an I/O exception has occurred. - */ - private byte[] readBlock() throws IOException - { - return readBlock(true); - } - - /** - * Reads the next block of data, optionally adding it to the HMAC checksum. Strips the header recording the number - * of bytes in the block. - * - * @param updateMac - * should the block be added to the HMAC checksum? - * @return the data block, or null if the end of the stream has been reached - * @throws IOException - * Signals that an I/O exception has occurred. - */ - private byte[] readBlock(final boolean updateMac) throws IOException - { - int len; - try - { - len = this.wrapped.readInt(); - } - catch (final EOFException e) - { - return null; - } - final byte[] in = new byte[len]; - this.wrapped.readFully(in); - if (updateMac) - { - macBlock(in); - } - return in; - } - - /** - * Updates the HMAC checksum with the given data block. - * - * @param block - * the block - * @throws IOException - * Signals that an I/O exception has occurred. - */ - private void macBlock(final byte[] block) throws IOException - { - this.dataStr.writeInt(block.length); - this.dataStr.write(block); - // If we don't have the MAC key yet, buffer up until we do - if (this.mac != null) - { - this.dataStr.flush(); - final byte[] bytes = this.buffer.toByteArray(); - this.buffer.reset(); - this.mac.update(bytes); - } - } - - /* - * (non-Javadoc) - * @see java.io.InputStream#read() - */ - @Override - public int read() throws IOException - { - final byte[] buf = new byte[1]; - int bytesRead; - while ((bytesRead = read(buf)) == 0) - { - ; - } - return bytesRead == -1 ? -1 : buf[0] & 0xFF; - } - - /* - * (non-Javadoc) - * @see java.io.InputStream#read(byte[]) - */ - @Override - public int read(final byte b[]) throws IOException - { - return read(b, 0, b.length); - } - - /* - * (non-Javadoc) - * @see java.io.InputStream#read(byte[], int, int) - */ - @Override - public int read(final byte b[], int off, final int len) throws IOException - { - if (b == null) - { - throw new NullPointerException(); - } - else if (off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0) - { - throw new IndexOutOfBoundsException(); - } - else if (len == 0) - { - return 0; - } - - int bytesToRead = len; - OUTER: while (bytesToRead > 0) - { - // Fetch another block if necessary - while (this.currentDataBlock == null || this.currentDataPos >= this.currentDataBlock.length) - { - byte[] newDataBlock; - // We're right at the end of the last block so finish - if (this.isAtEnd) - { - this.currentDataBlock = this.nextDataBlock = null; - break OUTER; - } - // We've already read the last block so validate the MAC code - else if ((newDataBlock = readBlock(false)) == null) - { - if (!MessageDigest.isEqual(this.mac.doFinal(), this.nextDataBlock)) - { - throw new IOException("Invalid HMAC"); - } - // We still have what's left in the cipher to read - try - { - this.currentDataBlock = this.inputCipher.doFinal(); - } - catch (final GeneralSecurityException e) - { - throw new RuntimeException(e); - } - this.isAtEnd = true; - } - // We have an ordinary data block to MAC and decrypt - else - { - macBlock(this.nextDataBlock); - this.currentDataBlock = this.inputCipher.update(this.nextDataBlock); - this.nextDataBlock = newDataBlock; - } - this.currentDataPos = 0; - } - final int bytesRead = Math.min(bytesToRead, this.currentDataBlock.length - this.currentDataPos); - System.arraycopy(this.currentDataBlock, this.currentDataPos, b, off, bytesRead); - bytesToRead -= bytesRead; - off += bytesRead; - this.currentDataPos += bytesRead; - } - return bytesToRead == len ? -1 : len - bytesToRead; - } - - /* - * (non-Javadoc) - * @see java.io.InputStream#available() - */ - @Override - public int available() throws IOException - { - return this.currentDataBlock == null ? 0 : this.currentDataBlock.length - this.currentDataPos; - } - - /* - * (non-Javadoc) - * @see java.io.InputStream#close() - */ - @Override - public void close() throws IOException - { - // Read right to the end, just to ensure the MAC code is valid! - if (this.nextDataBlock != null) - { - final byte[] skipBuff = new byte[1024]; - while (read(skipBuff) != -1) - { - ; - } - } - this.wrapped.close(); - this.dataStr.close(); - } - -} diff --git a/source/java/org/alfresco/util/security/EncryptingOutputStream.java b/source/java/org/alfresco/util/security/EncryptingOutputStream.java deleted file mode 100644 index 49db8231bb..0000000000 --- a/source/java/org/alfresco/util/security/EncryptingOutputStream.java +++ /dev/null @@ -1,269 +0,0 @@ -/* - * Copyright (C) 2005-2008 Alfresco Software Limited. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have received a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" - */ -package org.alfresco.util.security; - -import java.io.ByteArrayOutputStream; -import java.io.DataOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.security.GeneralSecurityException; -import java.security.InvalidKeyException; -import java.security.Key; -import java.security.NoSuchAlgorithmException; -import java.security.PublicKey; -import java.security.SecureRandom; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.KeyGenerator; -import javax.crypto.Mac; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.SecretKeySpec; - -/** - * An output stream that encrypts data to another output stream. A lightweight yet secure hybrid encryption scheme is - * used. A random symmetric key is generated and encrypted using the receiver's public key. The supplied data is then - * encrypted using the symmetric key and sent to the underlying stream on a streaming basis. An HMAC checksum is also - * computed on an ongoing basis and appended to the output when the stream is closed. This class can be used in - * conjunction with {@link DecryptingInputStream} to transport data securely. - */ -public class EncryptingOutputStream extends OutputStream -{ - /** The wrapped stream. */ - private final OutputStream wrapped; - - /** The output cipher. */ - private final Cipher outputCipher; - - /** The MAC generator. */ - private final Mac mac; - - /** Internal buffer for MAC computation. */ - private final ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024); - - /** A DataOutputStream on top of our interal buffer. */ - private final DataOutputStream dataStr = new DataOutputStream(this.buffer); - - /** - * Constructs an EncryptingOutputStream using default symmetric encryption parameters. - * - * @param wrapped - * outputstream to store the encrypted data - * @param receiverKey - * the receiver's public key for encrypting the symmetric key - * @param rand - * a secure source of randomness - * @throws IOException - * Signals that an I/O exception has occurred. - * @throws NoSuchAlgorithmException - * the no such algorithm exception - * @throws NoSuchPaddingException - * the no such padding exception - * @throws InvalidKeyException - * the invalid key exception - * @throws BadPaddingException - * the bad padding exception - * @throws IllegalBlockSizeException - * the illegal block size exception - */ - public EncryptingOutputStream(final OutputStream wrapped, final PublicKey receiverKey, final SecureRandom rand) - throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, - IllegalBlockSizeException, BadPaddingException - { - this(wrapped, receiverKey, "AES", rand, 128, "CBC", "PKCS5PADDING"); - } - - /** - * Constructs an EncryptingOutputStream. - * - * @param wrapped - * outputstream to store the encrypted data - * @param receiverKey - * the receiver's public key for encrypting the symmetric key - * @param algorithm - * symmetric encryption algorithm (e.g. "AES") - * @param rand - * a secure source of randomness - * @param strength - * the key size in bits (e.g. 128) - * @param mode - * encryption mode (e.g. "CBC") - * @param padding - * padding scheme (e.g. "PKCS5PADDING") - * @throws IOException - * Signals that an I/O exception has occurred. - * @throws NoSuchAlgorithmException - * the no such algorithm exception - * @throws NoSuchPaddingException - * the no such padding exception - * @throws InvalidKeyException - * the invalid key exception - * @throws BadPaddingException - * the bad padding exception - * @throws IllegalBlockSizeException - * the illegal block size exception - */ - public EncryptingOutputStream(final OutputStream wrapped, final PublicKey receiverKey, final String algorithm, - final SecureRandom rand, final int strength, final String mode, final String padding) throws IOException, - NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, - BadPaddingException - { - // Initialise - this.wrapped = wrapped; - - // Generate a random symmetric key - final KeyGenerator keyGen = KeyGenerator.getInstance(algorithm); - keyGen.init(strength, rand); - final Key symKey = keyGen.generateKey(); - - // Instantiate Symmetric cipher for encryption. - this.outputCipher = Cipher.getInstance(algorithm + "/" + mode + "/" + padding); - this.outputCipher.init(Cipher.ENCRYPT_MODE, symKey, rand); - - // Set up HMAC - this.mac = Mac.getInstance("HMACSHA1"); - final byte[] macKeyBytes = new byte[20]; - rand.nextBytes(macKeyBytes); - final Key macKey = new SecretKeySpec(macKeyBytes, "HMACSHA1"); - this.mac.init(macKey); - - // Set up RSA to encrypt symmetric key - final Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING"); - rsa.init(Cipher.ENCRYPT_MODE, receiverKey, rand); - - // Write the header - - // Write out an RSA-encrypted block for the key of the cipher. - writeBlock(rsa.doFinal(symKey.getEncoded())); - - // Write out RSA-encrypted Initialisation Vector block - writeBlock(rsa.doFinal(this.outputCipher.getIV())); - - // Write out key for HMAC. - writeBlock(this.outputCipher.doFinal(macKey.getEncoded())); - } - - /* - * (non-Javadoc) - * @see java.io.OutputStream#write(int) - */ - @Override - public void write(final int b) throws IOException - { - write(new byte[] - { - (byte) b - }, 0, 1); - } - - /* - * (non-Javadoc) - * @see java.io.OutputStream#write(byte[]) - */ - @Override - public void write(final byte b[]) throws IOException - { - write(b, 0, b.length); - } - - /* - * (non-Javadoc) - * @see java.io.OutputStream#write(byte[], int, int) - */ - @Override - public void write(final byte b[], final int off, final int len) throws IOException - { - if (b == null) - { - throw new NullPointerException(); - } - else if (off < 0 || off > b.length || len < 0 || off + len > b.length || off + len < 0) - { - throw new IndexOutOfBoundsException(); - } - else if (len == 0) - { - return; - } - final byte[] out = this.outputCipher.update(b, off, len); // Encrypt data. - if (out != null && out.length > 0) - { - writeBlock(out); - } - } - - /** - * Writes a block of data, preceded by its length, and adds it to the HMAC checksum. - * - * @param out - * the data to be written. - * @throws IOException - * Signals that an I/O exception has occurred. - */ - private void writeBlock(final byte[] out) throws IOException - { - this.dataStr.writeInt(out.length); // Write length. - this.dataStr.write(out); // Write encrypted data. - this.dataStr.flush(); - final byte[] block = this.buffer.toByteArray(); - this.buffer.reset(); - this.mac.update(block); - this.wrapped.write(block); - } - - /* - * (non-Javadoc) - * @see java.io.OutputStream#flush() - */ - @Override - public void flush() throws IOException - { - this.wrapped.flush(); - } - - /* - * (non-Javadoc) - * @see java.io.OutputStream#close() - */ - @Override - public void close() throws IOException - { - try - { - // Write the last block - writeBlock(this.outputCipher.doFinal()); - } - catch (final GeneralSecurityException e) - { - throw new RuntimeException(e); - } - // Write the MAC code - writeBlock(this.mac.doFinal()); - this.wrapped.close(); - this.dataStr.close(); - } - -} diff --git a/source/java/org/alfresco/util/security/EncryptingOutputStreamTest.java b/source/java/org/alfresco/util/security/EncryptingOutputStreamTest.java deleted file mode 100644 index 122ca63231..0000000000 --- a/source/java/org/alfresco/util/security/EncryptingOutputStreamTest.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2005-2008 Alfresco Software Limited. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - - * As a special exception to the terms and conditions of version 2.0 of - * the GPL, you may redistribute this Program in connection with Free/Libre - * and Open Source Software ("FLOSS") applications as described in Alfresco's - * FLOSS exception. You should have received a copy of the text describing - * the FLOSS exception, and it is also available here: - * http://www.alfresco.com/legal/licensing" - */ -package org.alfresco.util.security; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.SecureRandom; -import java.util.Arrays; - -import junit.framework.TestCase; - -/** - * Tests that the EncryptingOutputStream and EncryptingInputStream classes work correctly. - */ -public class EncryptingOutputStreamTest extends TestCase -{ - - /** - * Tests encryption / decryption by attempting to encrypt and decrypt the bytes forming this class definition and - * comparing it with the unencrypted bytes. - * - * @throws Exception - * an exception - */ - public void testEncrypt() throws Exception - { - final KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA"); - final SecureRandom random = SecureRandom.getInstance("SHA1PRNG"); - final byte[] seed = getClass().getName().getBytes("UTF-8"); - random.setSeed(seed); - keyGen.initialize(1024, random); - final KeyPair pair = keyGen.generateKeyPair(); - - final ByteArrayOutputStream buff = new ByteArrayOutputStream(2048); - final OutputStream encrypting = new EncryptingOutputStream(buff, pair.getPublic(), random); - final ByteArrayOutputStream plainText1 = new ByteArrayOutputStream(2048); - - final InputStream in = getClass().getResourceAsStream(getClass().getSimpleName() + ".class"); - final byte[] inbuff = new byte[17]; - int bytesRead; - while ((bytesRead = in.read(inbuff)) != -1) - { - encrypting.write(inbuff, 0, bytesRead); - plainText1.write(inbuff, 0, bytesRead); - } - in.close(); - encrypting.close(); - plainText1.close(); - final InputStream decrypting = new DecryptingInputStream(new ByteArrayInputStream(buff.toByteArray()), pair - .getPrivate()); - final ByteArrayOutputStream plainText2 = new ByteArrayOutputStream(2048); - while ((bytesRead = decrypting.read(inbuff)) != -1) - { - plainText2.write(inbuff, 0, bytesRead); - } - - assertTrue(Arrays.equals(plainText1.toByteArray(), plainText2.toByteArray())); - - } -}