blob: 3bae2d60990bf8ddd7cb168cc3b4876f38cfdef0 [file] [log] [blame]
/*
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*/
package org.apache.qpid.client.message;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.security.GeneralSecurityException;
import java.security.PrivateKey;
import java.util.Collection;
import java.util.Iterator;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.security.auth.x500.X500Principal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.qpid.QpidException;
import org.apache.qpid.client.AMQSession;
import org.apache.qpid.framing.BasicContentHeaderProperties;
public class Encrypted091MessageFactory extends AbstractJMSMessageFactory
{
public static final String ENCRYPTED_0_9_1_CONTENT_TYPE = "application/qpid-0-9-1-encrypted";
private static final Logger LOGGER = LoggerFactory.getLogger(Encrypted091MessageFactory.class);
private final MessageFactoryRegistry _messageFactoryRegistry;
public Encrypted091MessageFactory(final MessageFactoryRegistry messageFactoryRegistry)
{
_messageFactoryRegistry = messageFactoryRegistry;
}
@Override
protected AbstractJMSMessage createMessage(final AbstractAMQMessageDelegate delegate, final ByteBuffer data)
throws QpidException
{
SecretKeySpec secretKeySpec;
String algorithm;
byte[] initVector;
try
{
try
{
if (delegate.hasProperty(MessageEncryptionHelper.ENCRYPTION_ALGORITHM_PROPERTY))
{
algorithm = delegate.getProperty(MessageEncryptionHelper.ENCRYPTION_ALGORITHM_PROPERTY).toString();
if (delegate.hasProperty(MessageEncryptionHelper.KEY_INIT_VECTOR_PROPERTY))
{
Object ivObj = delegate.getProperty(MessageEncryptionHelper.KEY_INIT_VECTOR_PROPERTY);
if (ivObj instanceof byte[])
{
initVector = (byte[]) ivObj;
}
else
{
throw new QpidException("If the property '"
+ MessageEncryptionHelper.KEY_INIT_VECTOR_PROPERTY
+ "' is present, it must contain a byte array");
}
}
else
{
initVector = null;
}
if (delegate.hasProperty(MessageEncryptionHelper.ENCRYPTED_KEYS_PROPERTY))
{
Object keyInfoObj = delegate.getProperty(MessageEncryptionHelper.ENCRYPTED_KEYS_PROPERTY);
if (keyInfoObj instanceof Collection)
{
secretKeySpec = getContentEncryptionKey((Collection) keyInfoObj,
algorithm,
_messageFactoryRegistry.getSession());
}
else
{
throw new QpidException("An encrypted message must contain the property '"
+ MessageEncryptionHelper.ENCRYPTED_KEYS_PROPERTY
+ "'");
}
}
else
{
throw new QpidException("An encrypted message must contain the property '"
+ MessageEncryptionHelper.ENCRYPTED_KEYS_PROPERTY
+ "'");
}
}
else
{
throw new QpidException("Encrypted message must carry the encryption algorithm in the property '"
+ MessageEncryptionHelper.ENCRYPTED_KEYS_PROPERTY
+ "'");
}
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(initVector));
byte[] encryptedData;
int offset;
int length;
if (data.hasArray())
{
encryptedData = data.array();
offset = data.arrayOffset() + data.position();
length = data.remaining();
}
else
{
encryptedData = new byte[data.remaining()];
data.duplicate().get(encryptedData);
offset = 0;
length = encryptedData.length;
}
final byte[] unencryptedBytes = decryptData(cipher, encryptedData, offset, length);
BasicContentHeaderProperties properties = new BasicContentHeaderProperties();
int payloadOffset;
ByteBuffer dataInput = ByteBuffer.wrap(unencryptedBytes);
payloadOffset = properties.read(dataInput);
final ByteBuffer unencryptedData =
ByteBuffer.wrap(unencryptedBytes, payloadOffset, unencryptedBytes.length - payloadOffset);
final AbstractAMQMessageDelegate newDelegate =
new AMQMessageDelegate_0_8(properties, delegate.getDeliveryTag());
newDelegate.setJMSDestination(delegate.getJMSDestination());
final AbstractJMSMessageFactory unencryptedMessageFactory =
_messageFactoryRegistry.getMessageFactory(properties.getContentTypeAsString());
return unencryptedMessageFactory.createMessage(newDelegate, unencryptedData);
}
catch (GeneralSecurityException | IOException e)
{
throw new QpidException("Could not decode encrypted message", e);
}
}
catch(QpidException e)
{
LOGGER.error("Error when attempting to decrypt message " + delegate.getDeliveryTag() + " to address ("+delegate.getJMSDestination()+"). Message will be delivered to the client encrypted", e);
return _messageFactoryRegistry.getDefaultFactory().createMessage(delegate, data);
}
}
private byte[] decryptData(final Cipher cipher, final byte[] encryptedData, final int offset, final int length)
throws IOException
{
final byte[] unencryptedBytes;
try (CipherInputStream cipherInputStream = new CipherInputStream(new ByteArrayInputStream(encryptedData,
offset,
length), cipher))
{
byte[] buf = new byte[512];
int pos = 0;
int read;
while ((read = cipherInputStream.read(buf, pos, buf.length - pos)) != -1)
{
pos += read;
if (pos == buf.length)
{
byte[] tmp = buf;
buf = new byte[buf.length + 512];
System.arraycopy(tmp, 0, buf, 0, tmp.length);
}
}
unencryptedBytes= new byte[pos];
System.arraycopy(buf, 0, unencryptedBytes, 0, pos);
}
return unencryptedBytes;
}
private SecretKeySpec getContentEncryptionKey(final Collection keyInfoObjList,
final String algorithm,
final AMQSession<?, ?> session)
throws QpidException, GeneralSecurityException, IOException
{
for(Object keyInfoObject : keyInfoObjList)
{
try
{
Iterator iter = ((Collection)keyInfoObject).iterator();
int type = ((Number)iter.next()).intValue();
switch(type)
{
case 1:
String keyEncryptionAlgorithm = (String) iter.next();
X500Principal issuer = new X500Principal((String)iter.next());
BigInteger serialNumber = new BigInteger((String)iter.next());
byte[] encryptedKey = (byte[])iter.next();
PrivateKey privateKey = getPrivateKey(session, issuer, serialNumber);
if(privateKey != null)
{
Cipher cipher = Cipher.getInstance(keyEncryptionAlgorithm);
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decryptedData = decryptData(cipher, encryptedKey, 0, encryptedKey.length);
SecretKeySpec keySpec = new SecretKeySpec(decryptedData, algorithm.split("/")[0]);
return keySpec;
}
break;
default:
throw new QpidException("Invalid format of 'x-qpid-encrypted-keys' - unknown key info type: " + type);
}
}
catch(ClassCastException e)
{
throw new QpidException("Invalid format of 'x-qpid-encrypted-keys'");
}
}
return null;
}
private PrivateKey getPrivateKey(final AMQSession<?, ?> session,
final X500Principal issuer,
final BigInteger serialNumber)
throws GeneralSecurityException, IOException
{
return session.getMessageEncryptionHelper().getEncryptionPrivateKey(issuer, serialNumber);
}
}