/* * 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.cassandra.security; import java.io.IOException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.Map; import javax.crypto.Cipher; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Objects; import org.apache.cassandra.config.TransparentDataEncryptionOptions; import org.apache.cassandra.exceptions.ConfigurationException; import org.apache.cassandra.io.compress.ICompressor; import org.apache.cassandra.io.compress.LZ4Compressor; import org.apache.cassandra.utils.Hex; /** * A (largely) immutable wrapper for the application-wide file-level encryption settings. */ public class EncryptionContext { public static final String ENCRYPTION_CIPHER = "encCipher"; public static final String ENCRYPTION_KEY_ALIAS = "encKeyAlias"; public static final String ENCRYPTION_IV = "encIV"; private final TransparentDataEncryptionOptions tdeOptions; private final ICompressor compressor; private final CipherFactory cipherFactory; private final byte[] iv; private final int chunkLength; public EncryptionContext() { this(new TransparentDataEncryptionOptions()); } public EncryptionContext(TransparentDataEncryptionOptions tdeOptions) { this(tdeOptions, null, true); } @VisibleForTesting public EncryptionContext(TransparentDataEncryptionOptions tdeOptions, byte[] iv, boolean init) { this.tdeOptions = tdeOptions; compressor = LZ4Compressor.create(Collections.<String, String>emptyMap()); chunkLength = tdeOptions.chunk_length_kb * 1024; this.iv = iv; // always attempt to load the cipher factory, as we could be in the situation where the user has disabled encryption, // but has existing commitlogs and sstables on disk that are still encrypted (and still need to be read) CipherFactory factory = null; if (tdeOptions.enabled && init) { try { factory = new CipherFactory(tdeOptions); } catch (Exception e) { throw new ConfigurationException("failed to load key provider for transparent data encryption", e); } } cipherFactory = factory; } public ICompressor getCompressor() { return compressor; } public Cipher getEncryptor() throws IOException { return cipherFactory.getEncryptor(tdeOptions.cipher, tdeOptions.key_alias); } public Cipher getDecryptor() throws IOException { if (iv == null || iv.length == 0) throw new IllegalStateException("no initialization vector (IV) found in this context"); return cipherFactory.getDecryptor(tdeOptions.cipher, tdeOptions.key_alias, iv); } public boolean isEnabled() { return tdeOptions.enabled; } public int getChunkLength() { return chunkLength; } public byte[] getIV() { return iv; } public TransparentDataEncryptionOptions getTransparentDataEncryptionOptions() { return tdeOptions; } public boolean equals(Object o) { return o instanceof EncryptionContext && equals((EncryptionContext) o); } public boolean equals(EncryptionContext other) { return Objects.equal(tdeOptions, other.tdeOptions) && Objects.equal(compressor, other.compressor) && Arrays.equals(iv, other.iv); } public Map<String, String> toHeaderParameters() { Map<String, String> map = new HashMap<>(3); // add compression options, someday ... if (tdeOptions.enabled) { map.put(ENCRYPTION_CIPHER, tdeOptions.cipher); map.put(ENCRYPTION_KEY_ALIAS, tdeOptions.key_alias); if (iv != null && iv.length > 0) map.put(ENCRYPTION_IV, Hex.bytesToHex(iv)); } return map; } /** * If encryption headers are found in the {@code parameters}, * those headers are merged with the application-wide {@code encryptionContext}. */ public static EncryptionContext createFromMap(Map<?, ?> parameters, EncryptionContext encryptionContext) { if (parameters == null || parameters.isEmpty()) return new EncryptionContext(new TransparentDataEncryptionOptions(false)); String keyAlias = (String)parameters.get(ENCRYPTION_KEY_ALIAS); String cipher = (String)parameters.get(ENCRYPTION_CIPHER); String ivString = (String)parameters.get(ENCRYPTION_IV); if (keyAlias == null || cipher == null) return new EncryptionContext(new TransparentDataEncryptionOptions(false)); TransparentDataEncryptionOptions tdeOptions = new TransparentDataEncryptionOptions(cipher, keyAlias, encryptionContext.getTransparentDataEncryptionOptions().key_provider); byte[] iv = ivString != null ? Hex.hexToBytes(ivString) : null; return new EncryptionContext(tdeOptions, iv, true); } }