Skip to content

Commit

Permalink
Support providing keys as PEM string
Browse files Browse the repository at this point in the history
This comes with some necessary refactoring or the key reading machinery.
  • Loading branch information
ivanyu committed Jun 2, 2023
1 parent 53ddb5d commit acb4152
Show file tree
Hide file tree
Showing 12 changed files with 350 additions and 217 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,10 @@ public void configure(final Map<String, ?> configs) {
encryptionEnabled = config.encryptionEnabled();
if (encryptionEnabled) {
rsaEncryptionProvider = RsaEncryptionProvider.of(
config.encryptionPublicKeyFile(),
config.encryptionPrivateKeyFile()
config.encryptionPublicKey(),
config.encryptionPublicKeyFormat(),
config.encryptionPrivateKey(),
config.encryptionPrivateKeyFormat()
);
aesEncryptionProvider = new AesEncryptionProvider();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package io.aiven.kafka.tieredstorage;

import java.nio.file.Path;
import java.time.Duration;
import java.util.Map;
import java.util.Optional;
Expand All @@ -28,6 +27,7 @@

import io.aiven.kafka.tieredstorage.cache.ChunkCache;
import io.aiven.kafka.tieredstorage.cache.UnboundInMemoryChunkCache;
import io.aiven.kafka.tieredstorage.security.RsaKeyFormat;
import io.aiven.kafka.tieredstorage.storage.StorageBackend;

public class RemoteStorageManagerConfig extends AbstractConfig {
Expand Down Expand Up @@ -70,14 +70,28 @@ public class RemoteStorageManagerConfig extends AbstractConfig {

private static final String ENCRYPTION_CONFIG = "encryption.enabled";
private static final String ENCRYPTION_DOC = "Whether to enable encryption";
private static final String ENCRYPTION_PUBLIC_KEY_FILE_CONFIG = "encryption.public.key.file";
private static final String ENCRYPTION_PUBLIC_KEY_FILE_DOC = "The path to the RSA public key file";
private static final String ENCRYPTION_PRIVATE_KEY_FILE_CONFIG = "encryption.private.key.file";
private static final String ENCRYPTION_PRIVATE_KEY_FILE_DOC = "The path to the RSA private key file";
// TODO add possibility to pass keys as strings
private static final String ENCRYPTION_PUBLIC_KEY_CONFIG = "encryption.public.key";
private static final String ENCRYPTION_PUBLIC_KEY_DOC = "The RSA public key";
private static final String ENCRYPTION_PUBLIC_KEY_FORMAT_CONFIG = "encryption.public.key.format";
private static final String ENCRYPTION_PUBLIC_KEY_FORMAT_DOC = "The RSA public key format";
private static final String ENCRYPTION_PRIVATE_KEY_CONFIG = "encryption.private.key";
private static final String ENCRYPTION_PRIVATE_KEY_DOC = "The RSA private key";
private static final String ENCRYPTION_PRIVATE_KEY_FORMAT_CONFIG = "encryption.private.key.format";
private static final String ENCRYPTION_PRIVATE_KEY_FORMAT_DOC = "The RSA private key format";

private static final ConfigDef CONFIG;

private static final ConfigDef.Validator ENCRYPTION_KEY_FORMAT_VALIDATOR;

static {
final RsaKeyFormat[] values = RsaKeyFormat.values();
final String[] array = new String[values.length];
for (int i = 0; i < values.length; i++) {
array[i] = values[i].name;
}
ENCRYPTION_KEY_FORMAT_VALIDATOR = ConfigDef.ValidString.in(array);
}

static {
CONFIG = new ConfigDef();

Expand Down Expand Up @@ -158,22 +172,37 @@ public class RemoteStorageManagerConfig extends AbstractConfig {
ENCRYPTION_DOC
);
CONFIG.define(
ENCRYPTION_PUBLIC_KEY_FILE_CONFIG,
ENCRYPTION_PUBLIC_KEY_CONFIG,
ConfigDef.Type.STRING,
null,
ConfigDef.Importance.HIGH,
ENCRYPTION_PUBLIC_KEY_FILE_DOC
ENCRYPTION_PUBLIC_KEY_DOC
);
CONFIG.define(
ENCRYPTION_PRIVATE_KEY_FILE_CONFIG,
ENCRYPTION_PUBLIC_KEY_FORMAT_CONFIG,
ConfigDef.Type.STRING,
RsaKeyFormat.PEM.name,
ENCRYPTION_KEY_FORMAT_VALIDATOR,
ConfigDef.Importance.MEDIUM,
ENCRYPTION_PUBLIC_KEY_FORMAT_DOC
);
CONFIG.define(
ENCRYPTION_PRIVATE_KEY_CONFIG,
ConfigDef.Type.STRING,
null,
ConfigDef.Importance.HIGH,
ENCRYPTION_PRIVATE_KEY_FILE_DOC
ENCRYPTION_PRIVATE_KEY_DOC
);
CONFIG.define(
ENCRYPTION_PRIVATE_KEY_FORMAT_CONFIG,
ConfigDef.Type.STRING,
RsaKeyFormat.PEM.name,
ENCRYPTION_KEY_FORMAT_VALIDATOR,
ConfigDef.Importance.MEDIUM,
ENCRYPTION_PRIVATE_KEY_FORMAT_DOC
);
}


RemoteStorageManagerConfig(final Map<String, ?> props) {
super(CONFIG, props);
validate();
Expand Down Expand Up @@ -201,13 +230,13 @@ private void validateCaching() {
}

private void validateEncryption() {
if (getBoolean(ENCRYPTION_CONFIG) && getString(ENCRYPTION_PUBLIC_KEY_FILE_CONFIG) == null) {
if (getBoolean(ENCRYPTION_CONFIG) && getString(ENCRYPTION_PUBLIC_KEY_CONFIG) == null) {
throw new ConfigException(
ENCRYPTION_PUBLIC_KEY_FILE_CONFIG + " must be provided if encryption is enabled");
ENCRYPTION_PUBLIC_KEY_CONFIG + " must be provided if encryption is enabled");
}
if (getBoolean(ENCRYPTION_CONFIG) && getString(ENCRYPTION_PRIVATE_KEY_FILE_CONFIG) == null) {
if (getBoolean(ENCRYPTION_CONFIG) && getString(ENCRYPTION_PRIVATE_KEY_CONFIG) == null) {
throw new ConfigException(
ENCRYPTION_PRIVATE_KEY_FILE_CONFIG + " must be provided if encryption is enabled");
ENCRYPTION_PRIVATE_KEY_CONFIG + " must be provided if encryption is enabled");
}
}

Expand Down Expand Up @@ -265,19 +294,19 @@ boolean encryptionEnabled() {
return getBoolean(ENCRYPTION_CONFIG);
}

Path encryptionPublicKeyFile() {
final String value = getString(ENCRYPTION_PUBLIC_KEY_FILE_CONFIG);
if (value == null) {
return null;
}
return Path.of(value);
String encryptionPublicKey() {
return getString(ENCRYPTION_PUBLIC_KEY_CONFIG);
}

Path encryptionPrivateKeyFile() {
final String value = getString(ENCRYPTION_PRIVATE_KEY_FILE_CONFIG);
if (value == null) {
return null;
}
return Path.of(value);
RsaKeyFormat encryptionPublicKeyFormat() {
return RsaKeyFormat.parse(getString(ENCRYPTION_PUBLIC_KEY_FORMAT_CONFIG));
}

String encryptionPrivateKey() {
return getString(ENCRYPTION_PRIVATE_KEY_CONFIG);
}

RsaKeyFormat encryptionPrivateKeyFormat() {
return RsaKeyFormat.parse(getString(ENCRYPTION_PRIVATE_KEY_FORMAT_CONFIG));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,49 +22,32 @@
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Objects;

import org.bouncycastle.util.io.pem.PemReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public final class RsaEncryptionProvider {

private static final Logger LOGGER = LoggerFactory.getLogger(RsaEncryptionProvider.class);


private static final String RSA_TRANSFORMATION = "RSA/NONE/OAEPWithSHA3-512AndMGF1Padding";


private final KeyPair rsaKeyPair;

private RsaEncryptionProvider(final KeyPair rsaKeyPair) {
this.rsaKeyPair = rsaKeyPair;
}

public static RsaEncryptionProvider of(final Path rsaPublicKey, final Path rsaPrivateKey) {
LOGGER.info("Read RSA keys");
Objects.requireNonNull(rsaPublicKey, "rsaPublicKey hasn't been set");
Objects.requireNonNull(rsaPrivateKey, "rsaPrivateKey hasn't been set");
final KeyPair rsaKeyPair = RsaKeysReader.readRsaKeyPair(rsaPublicKey, rsaPrivateKey);
public static RsaEncryptionProvider of(final String publicKey,
final RsaKeyFormat publicKeyFormat,
final String privateKey,
final RsaKeyFormat privateKeyFormat) {
Objects.requireNonNull(publicKey, "publicKey cannot be null");
Objects.requireNonNull(publicKeyFormat, "publicKeyFormat cannot be null");
Objects.requireNonNull(privateKey, "privateKey cannot be null");
Objects.requireNonNull(privateKeyFormat, "privateKeyFormat cannot be null");
final KeyPair rsaKeyPair = RsaKeyReader.readKeyPair(publicKey, publicKeyFormat, privateKey, privateKeyFormat);
return new RsaEncryptionProvider(rsaKeyPair);
}

Expand Down Expand Up @@ -108,54 +91,4 @@ private Cipher createDecryptingCipher(final Key key) {
throw new RuntimeException("Couldn't create decrypt cipher", e);
}
}

static class RsaKeysReader {

static KeyPair readRsaKeyPair(final Path publicKeyPath, final Path privateKeyPath) {
try (final InputStream publicKeyIn = Files.newInputStream(publicKeyPath);
final InputStream privateKeyIn = Files.newInputStream(privateKeyPath)) {
return readRsaKeyPair(publicKeyIn, privateKeyIn);
} catch (final IOException e) {
throw new IllegalArgumentException("Couldn't read RSA key pair paths", e);
}
}

static KeyPair readRsaKeyPair(final InputStream publicKeyIn, final InputStream privateKeyIn) {
try {
final var publicKey = readPublicKey(publicKeyIn);
final var privateKey = readPrivateKey(privateKeyIn);
return new KeyPair(publicKey, privateKey);
} catch (final NoSuchAlgorithmException | InvalidKeySpecException e) {
throw new IllegalArgumentException("Couldn't read RSA key pair", e);
}
}

private static PublicKey readPublicKey(final InputStream in)
throws NoSuchAlgorithmException, InvalidKeySpecException {
final var pemContent = readPemContent(new InputStreamReader(in));
final var keySpec = new X509EncodedKeySpec(pemContent);
final var kf = KeyFactory.getInstance("RSA");
return kf.generatePublic(keySpec);
}

private static PrivateKey readPrivateKey(final InputStream in)
throws NoSuchAlgorithmException, InvalidKeySpecException {
final var pemContent = readPemContent(new InputStreamReader(in));
final var keySpec = new PKCS8EncodedKeySpec(pemContent);
final var kf = KeyFactory.getInstance("RSA");
return kf.generatePrivate(keySpec);
}

private static byte[] readPemContent(final Reader reader) {
try (final var pemReader = new PemReader(reader)) {
final var pemObject = pemReader.readPemObject();
if (Objects.isNull(pemObject)) {
throw new IllegalArgumentException("Couldn't read PEM file");
}
return pemObject.getContent();
} catch (final IOException e) {
throw new IllegalArgumentException("Couldn't read PEM file", e);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright 2023 Aiven Oy
*
* Licensed 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 io.aiven.kafka.tieredstorage.security;

public enum RsaKeyFormat {
PEM("pem"),
PEM_FILE("pem_file");

public final String name;

RsaKeyFormat(final String name) {
this.name = name;
}

public static RsaKeyFormat parse(final String value) {
if (value.equals(PEM.name)) {
return PEM;
} else if (value.equals(PEM_FILE.name)) {
return PEM_FILE;
} else {
throw new IllegalArgumentException("Unsupported value: " + value);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*
* Copyright 2023 Aiven Oy
*
* Licensed 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 io.aiven.kafka.tieredstorage.security;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Objects;

import org.bouncycastle.util.io.pem.PemReader;

public class RsaKeyReader {
static KeyPair readKeyPair(final String publicKey,
final RsaKeyFormat publicKeyFormat,
final String privateKey,
final RsaKeyFormat privateKeyFormat) {
final KeyFactory keyFactory;
try {
keyFactory = KeyFactory.getInstance("RSA");
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
try {
final byte[] publicKeyPemContent = readPemContent(publicKey, publicKeyFormat);
final var publicKeySpec = new X509EncodedKeySpec(publicKeyPemContent);

final byte[] privateKeyPemContent = readPemContent(privateKey, privateKeyFormat);
final var privateKeySpec = new PKCS8EncodedKeySpec(privateKeyPemContent);

return new KeyPair(
keyFactory.generatePublic(publicKeySpec),
keyFactory.generatePrivate(privateKeySpec));
} catch (final IOException | InvalidKeySpecException e) {
throw new RuntimeException("Couldn't read RSA key pair", e);
}
}

private static byte[] readPemContent(final String key,
final RsaKeyFormat keyFormat) throws IOException {
final InputStream inputStream;
if (keyFormat == RsaKeyFormat.PEM) {
inputStream = new ByteArrayInputStream(key.getBytes());
} else if (keyFormat == RsaKeyFormat.PEM_FILE) {
inputStream = Files.newInputStream(Path.of(key));
} else {
throw new IllegalArgumentException("Unsupported RSA key format: " + keyFormat);
}

try (inputStream;
final InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
final var pemReader = new PemReader(inputStreamReader)) {
final var pemObject = pemReader.readPemObject();
if (Objects.isNull(pemObject)) {
throw new IllegalArgumentException("Couldn't read PEM");
}
return pemObject.getContent();
} catch (final IOException e) {
throw new IllegalArgumentException("Couldn't read PEM", e);
}
}
}
Loading

0 comments on commit acb4152

Please sign in to comment.