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 26706f8ef2..0000000000 Binary files a/source/java/org/alfresco/heartbeat/heartbeatpublic.keystore and /dev/null differ diff --git a/source/java/org/alfresco/repo/admin/RepoServerMgmt.java b/source/java/org/alfresco/repo/admin/RepoServerMgmt.java index 8bfcfe478c..b4f75271b0 100644 --- a/source/java/org/alfresco/repo/admin/RepoServerMgmt.java +++ b/source/java/org/alfresco/repo/admin/RepoServerMgmt.java @@ -105,7 +105,7 @@ public class RepoServerMgmt implements RepoServerMgmtMBean, ApplicationContextAw LicenseService licenseService = null; try { - licenseService = (LicenseService) ctx.getBean("org.alfresco.license.LicenseComponent"); + licenseService = (LicenseService) ctx.getBean("org.alfresco.enterprise.license.LicenseComponent"); readOnly = !licenseService.isLicenseValid(); } catch (NoSuchBeanDefinitionException e) diff --git a/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java b/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java index 3060e75cec..bac21f1e7b 100644 --- a/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java +++ b/source/java/org/alfresco/repo/descriptor/DescriptorServiceImpl.java @@ -172,13 +172,10 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean implements Desc public Descriptor execute() throws ClassNotFoundException { boolean initialiseHeartBeat = false; - + // initialise license service (if installed) - try - { - DescriptorServiceImpl.this.licenseService = (LicenseService) constructSpecialService("org.alfresco.license.LicenseComponent"); - } - catch (ClassNotFoundException e) + DescriptorServiceImpl.this.licenseService = (LicenseService) constructSpecialService("org.alfresco.enterprise.license.LicenseComponent"); + if (DescriptorServiceImpl.this.licenseService == null) { DescriptorServiceImpl.this.licenseService = new NOOPLicenseService(); initialiseHeartBeat = true; @@ -192,13 +189,13 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean implements Desc // Initialise the heartbeat unless it is disabled by the license if (initialiseHeartBeat || l == null || !l.isHeartBeatDisabled()) { - DescriptorServiceImpl.this.heartBeat = constructSpecialService("org.alfresco.heartbeat.HeartBeat"); + DescriptorServiceImpl.this.heartBeat = constructSpecialService("org.alfresco.enterprise.heartbeat.HeartBeat"); } } catch (LicenseException e) { // Initialise heart beat anyway - DescriptorServiceImpl.this.heartBeat = constructSpecialService("org.alfresco.heartbeat.HeartBeat"); + DescriptorServiceImpl.this.heartBeat = constructSpecialService("org.alfresco.enterprise.heartbeat.HeartBeat"); throw e; } @@ -255,10 +252,8 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean implements Desc * @param className * the class name * @return the object - * @throws ClassNotFoundException - * the class not found exception */ - private Object constructSpecialService(String className) throws ClassNotFoundException + private Object constructSpecialService(String className) { try { @@ -274,7 +269,7 @@ public class DescriptorServiceImpl extends AbstractLifecycleBean implements Desc } catch (ClassNotFoundException e) { - throw e; + return null; } catch (RuntimeException e) { diff --git a/source/java/org/alfresco/util/security/DecryptingInputStream.java b/source/java/org/alfresco/util/security/DecryptingInputStream.java deleted file mode 100644 index 07abbde9a5..0000000000 --- a/source/java/org/alfresco/util/security/DecryptingInputStream.java +++ /dev/null @@ -1,372 +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.DataInputStream; -import java.io.DataOutputStream; -import java.io.EOFException; -import java.io.IOException; -import java.io.InputStream; -import java.security.GeneralSecurityException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.PrivateKey; -import java.security.SecureRandom; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.Mac; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -/** - * An input stream that encrypts data produced by a {@link EncryptingOutputStream}. A lightweight yet secure hybrid - * encryption scheme is used. A random symmetric key is decrypted using the receiver's private key. The supplied data is - * then decrypted using the symmetric key and read on a streaming basis. When the end of the stream is reached or the - * stream is closed, a HMAC checksum of the entire stream contents is validated. - */ -public class DecryptingInputStream extends InputStream -{ - - /** The wrapped stream. */ - private final DataInputStream wrapped; - - /** The input cipher. */ - private final Cipher inputCipher; - - /** 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); - - /** The current unencrypted data block. */ - private byte[] currentDataBlock; - - /** The next encrypted data block. (could be the HMAC checksum) */ - private byte[] nextDataBlock; - - /** Have we read to the end of the underlying stream?. */ - private boolean isAtEnd; - - /** Our current position within currentDataBlock. */ - private int currentDataPos; - - /** - * Constructs a DecryptingInputStream using default symmetric encryption parameters. - * - * @param wrapped - * the input stream to decrypt - * @param privKey - * the receiver's private key for decrypting the symmetric key - * @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 IllegalBlockSizeException - * the illegal block size exception - * @throws BadPaddingException - * the bad padding exception - * @throws InvalidAlgorithmParameterException - * the invalid algorithm parameter exception - * @throws NoSuchProviderException - * the no such provider exception - */ - public DecryptingInputStream(final InputStream wrapped, final PrivateKey privKey) throws IOException, - NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, - BadPaddingException, InvalidAlgorithmParameterException, NoSuchProviderException - { - this(wrapped, privKey, "AES", "CBC", "PKCS5PADDING"); - } - - /** - * Constructs a DecryptingInputStream. - * - * @param wrapped - * the input stream to decrypt - * @param privKey - * the receiver's private key for decrypting the symmetric key - * @param algorithm - * encryption algorithm (e.g. "AES") - * @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 IllegalBlockSizeException - * the illegal block size exception - * @throws BadPaddingException - * the bad padding exception - * @throws InvalidAlgorithmParameterException - * the invalid algorithm parameter exception - * @throws NoSuchProviderException - * the no such provider exception - */ - public DecryptingInputStream(final InputStream wrapped, final PrivateKey privKey, final String algorithm, - final String mode, final String padding) throws IOException, NoSuchAlgorithmException, - NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, - InvalidAlgorithmParameterException, NoSuchProviderException - { - // Initialise a secure source of randomness - this.wrapped = new DataInputStream(wrapped); - final SecureRandom secRand = SecureRandom.getInstance("SHA1PRNG"); - - // Set up RSA - final Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWITHSHA1ANDMGF1PADDING"); - rsa.init(Cipher.DECRYPT_MODE, privKey, secRand); - - // Read and decrypt the symmetric key - final SecretKey symKey = new SecretKeySpec(rsa.doFinal(readBlock()), algorithm); - - // Read and decrypt initialisation vector - final byte[] keyIV = rsa.doFinal(readBlock()); - - // Set up cipher for decryption - this.inputCipher = Cipher.getInstance(algorithm + "/" + mode + "/" + padding); - this.inputCipher.init(Cipher.DECRYPT_MODE, symKey, new IvParameterSpec(keyIV)); - - // Read and decrypt the MAC key - final SecretKey macKey = new SecretKeySpec(this.inputCipher.doFinal(readBlock()), "HMACSHA1"); - - // Set up HMAC - this.mac = Mac.getInstance("HMACSHA1"); - this.mac.init(macKey); - - // Always read a block ahead so we can intercept the HMAC block - this.nextDataBlock = readBlock(false); - } - - /** - * Reads the next block of data, adding it to the HMAC checksum. Strips the header recording the number of bytes in - * the block. - * - * @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() 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())); - - } -}