/* * Copyright (c) 2017 OBiBa. All rights reserved. * * This program and the accompanying materials * are made available under the terms of the GNU Public License v3.0. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.obiba.magma.datasource.crypt; import java.io.IOException; import java.security.AlgorithmParameters; import java.security.GeneralSecurityException; import java.security.KeyFactory; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.EncodedKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.Cipher; import javax.crypto.SecretKey; import org.obiba.magma.AttributeAware; import org.obiba.magma.Datasource; import org.obiba.magma.NoSuchAttributeException; import org.obiba.magma.Value; import org.obiba.magma.crypt.KeyProvider; import org.obiba.magma.crypt.MagmaCryptRuntimeException; import org.obiba.magma.crypt.NoSuchKeyException; /** * Creates a {@link DatasourceCipherFactory} that creates {@code Cipher} instances using a {@code SecretKey} obtained * from the {@code Datasource} instance's attributes. The {@code SecretKey} is expected to be encrypted using a * {@code PublicKey} for which the instance of {@code KeyProvider} can provide the corresponding {@code KeyPair}. * <p/> * The required attributes are: * <ul> * <li>{@link CipherAttributeConstants#SECRET_KEY}</li> * <li>{@link CipherAttributeConstants#SECRET_KEY_ALGORITHM}</li> * <li>{@link CipherAttributeConstants#CIPHER_TRANSFORMATION}</li> * <li>{@link CipherAttributeConstants#CIPHER_ALGORITHM_PARAMETERS}</li> * <li>{@link CipherAttributeConstants#PUBLIC_KEY}</li> * <li>{@link CipherAttributeConstants#PUBLIC_KEY_FORMAT}</li> * <li>{@link CipherAttributeConstants#PUBLIC_KEY_ALGORITHM}</li> * </ul> * * @see GeneratedSecretKeyDatasourceEncryptionStrategy */ public class EncryptedSecretKeyDatasourceEncryptionStrategy implements DatasourceEncryptionStrategy { private static final String PKCS8_KEYSPEC_FORMAT = "PKCS#8"; private static final String X509_KEYSPEC_FORMAT = "X.509"; private transient KeyProvider keyProvider; // // DatasourceEncryptionStrategy Methods // @Override public void setKeyProvider(KeyProvider keyProvider) { this.keyProvider = keyProvider; } @Override public boolean canDecryptExistingDatasource() { return true; } @Override public DatasourceCipherFactory createDatasourceCipherFactory(Datasource ds) { try { SecretKey secretKey = getSecretKey(ds); String transformation = ds.getAttributeStringValue(CipherAttributeConstants.CIPHER_TRANSFORMATION); return new DefaultDatasourceCipherFactory(transformation, secretKey, getAlgorithmParameters(ds, secretKey.getAlgorithm())); } catch(NoSuchAttributeException e) { throw new MagmaCryptRuntimeException( "Missing metadata in Datasource '" + ds.getName() + "' to extract secret key. Expected attribute '" + e.getAttributeName() + "' is absent.", e); } catch(GeneralSecurityException e) { throw new MagmaCryptRuntimeException("Unable to decrypt Datasource '" + ds.getName() + "' secret key", e); } catch(IOException e) { throw new MagmaCryptRuntimeException( "Unexpected error while reading encryption metadata for Datasource '" + ds.getName() + "'", e); } } // // Methods // private AlgorithmParameters getAlgorithmParameters(AttributeAware datasource, String algorithm) throws IOException, NoSuchAlgorithmException { AlgorithmParameters algorithmParameters = null; // It is assumed here that there may be no algorithmParameters entry in the metadata // (since some algorithms may not require any). if(datasource.hasAttribute(CipherAttributeConstants.CIPHER_ALGORITHM_PARAMETERS)) { Value value = datasource.getAttribute(CipherAttributeConstants.CIPHER_ALGORITHM_PARAMETERS).getValue(); algorithmParameters = AlgorithmParameters.getInstance(algorithm); algorithmParameters.init((byte[]) (value.isNull() ? null : value.getValue())); } return algorithmParameters; } private SecretKey getSecretKey(AttributeAware datasource) throws MagmaCryptRuntimeException, GeneralSecurityException { String algorithm = datasource.getAttributeStringValue(CipherAttributeConstants.SECRET_KEY_ALGORITHM); Value value = datasource.getAttribute(CipherAttributeConstants.SECRET_KEY).getValue(); byte[] wrappedKey = (byte[]) (value.isNull() ? null : value.getValue()); PrivateKey privateKey = getPrivateKey(datasource); Cipher cipher = Cipher.getInstance(privateKey.getAlgorithm()); cipher.init(Cipher.UNWRAP_MODE, privateKey); return (SecretKey) cipher.unwrap(wrappedKey, algorithm, Cipher.SECRET_KEY); } private PrivateKey getPrivateKey(AttributeAware datasource) throws NoSuchKeyException, NoSuchAlgorithmException, InvalidKeySpecException { KeyPair keyPair = keyProvider.getKeyPair(getPublicKey(datasource)); return keyPair.getPrivate(); } /** * Extract the public key that was used to encrypt the secret key. * * @param datasource * @return the {@code PublicKey} used to encrypt the {@code SecretKey} * @throws NoSuchAlgorithmException * @throws InvalidKeySpecException */ private PublicKey getPublicKey(AttributeAware datasource) throws NoSuchAlgorithmException, InvalidKeySpecException { String algorithm = datasource.getAttributeStringValue(CipherAttributeConstants.PUBLIC_KEY_ALGORITHM); String format = datasource.getAttributeStringValue(CipherAttributeConstants.PUBLIC_KEY_FORMAT); Value value = datasource.getAttribute(CipherAttributeConstants.PUBLIC_KEY).getValue(); byte[] encodedKey = (byte[]) (value.isNull() ? null : value.getValue()); EncodedKeySpec keySpec = getEncodedKeySpec(format, encodedKey); KeyFactory keyFactory = KeyFactory.getInstance(algorithm); return keyFactory.generatePublic(keySpec); } private EncodedKeySpec getEncodedKeySpec(String format, byte... encodedKey) { switch(format) { case X509_KEYSPEC_FORMAT: return new X509EncodedKeySpec(encodedKey); case PKCS8_KEYSPEC_FORMAT: return new PKCS8EncodedKeySpec(encodedKey); default: // TODO: Support other formats. throw new RuntimeException("Unsupported KeySpec format (" + format + ")"); } } }