Skip to content

Commit

Permalink
NIFI-14001 Added Framework Support for PEM Keys and Certificates
Browse files Browse the repository at this point in the history
  • Loading branch information
exceptionfactory committed Nov 13, 2024
1 parent 6c4ddf8 commit f44696a
Show file tree
Hide file tree
Showing 21 changed files with 1,358 additions and 54 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,11 @@ public class NiFiProperties extends ApplicationProperties {
public static final String SECURITY_KEYSTORE = "nifi.security.keystore";
public static final String SECURITY_KEYSTORE_TYPE = "nifi.security.keystoreType";
public static final String SECURITY_KEYSTORE_PASSWD = "nifi.security.keystorePasswd";
public static final String SECURITY_KEYSTORE_PRIVATE_KEY = "nifi.security.keystore.privateKey";
public static final String SECURITY_KEYSTORE_CERTIFICATE = "nifi.security.keystore.certificate";
public static final String SECURITY_KEY_PASSWD = "nifi.security.keyPasswd";
public static final String SECURITY_TRUSTSTORE = "nifi.security.truststore";
public static final String SECURITY_TRUSTSTORE_CERTIFICATE = "nifi.security.truststore.certificate";
public static final String SECURITY_TRUSTSTORE_TYPE = "nifi.security.truststoreType";
public static final String SECURITY_TRUSTSTORE_PASSWD = "nifi.security.truststorePasswd";
public static final String SECURITY_AUTO_RELOAD_ENABLED = "nifi.security.autoreload.enabled";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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.nifi.security.ssl;

import java.io.InputStream;

/**
* Extension of Key Store Builder supporting reading from an Input Stream
*/
public interface InputStreamKeyStoreBuilder extends KeyStoreBuilder {
/**
* Set Key Store InputStream to be loaded
*
* @param inputStream Key Store InputStream
* @return Builder
*/
KeyStoreBuilder inputStream(InputStream inputStream);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* 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.nifi.security.ssl;

import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.cert.Certificate;
import java.util.List;
import java.util.Objects;

/**
* Key Store Builder capable of reading one or more X.509 Certificates formatted with PEM headers and footers
*/
public class PemCertificateKeyStoreBuilder implements InputStreamKeyStoreBuilder {
private static final String CERTIFICATE_ALIAS = "certificate-%d";

private InputStream inputStream;

/**
* Build Key Store using configured properties
*
* @return Key Store
*/
@Override
public KeyStore build() {
final KeyStore keyStore = getInitializedKeyStore();

if (inputStream == null) {
throw new BuilderConfigurationException("Key Store InputStream not configured");
}

loadKeyStore(keyStore);

return keyStore;
}

/**
* Set Key Store InputStream to be loaded
*
* @param inputStream Key Store InputStream
* @return Builder
*/
@Override
public PemCertificateKeyStoreBuilder inputStream(final InputStream inputStream) {
this.inputStream = Objects.requireNonNull(inputStream, "Key Store InputStream required");
return this;
}

private void loadKeyStore(final KeyStore keyStore) {
final PemCertificateReader pemCertificateReader = new StandardPemCertificateReader();
final List<Certificate> certificates = pemCertificateReader.readCertificates(inputStream);

int certificateIndex = 0;
for (final Certificate certificate : certificates) {
final String alias = CERTIFICATE_ALIAS.formatted(certificateIndex++);

try {
keyStore.setCertificateEntry(alias, certificate);
} catch (final KeyStoreException e) {
final String message = String.format("Set certificate entry [%s] failed", alias);
throw new BuilderConfigurationException(message, e);
}
}
}

private KeyStore getInitializedKeyStore() {
final String keyStoreType = KeyStore.getDefaultType();
try {
final KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null);
return keyStore;
} catch (final Exception e) {
final String message = String.format("Key Store Type [%s] initialization failed", keyStoreType);
throw new BuilderConfigurationException(message, e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* 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.nifi.security.ssl;

import java.io.InputStream;
import java.security.cert.Certificate;
import java.util.List;

/**
* Abstraction for reading one or more Certificates from a stream containing PEM headers and footers
*/
interface PemCertificateReader {
/**
* Read Certificates from stream of PEM sections
*
* @param inputStream Input Stream required
* @return Parsed certificates or empty when none found
*/
List<Certificate> readCertificates(InputStream inputStream);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/*
* 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.nifi.security.ssl;

import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.util.List;
import java.util.Objects;

/**
* Key Store Builder capable of reading a Private Key and one or more X.509 Certificates formatted with PEM headers and footers
*/
public class PemPrivateKeyCertificateKeyStoreBuilder implements KeyStoreBuilder {
private static final String ALIAS = "private-key-1";

private static final char[] EMPTY_PROTECTION_PARAMETER = new char[]{};

private InputStream privateKeyInputStream;

private InputStream certificateInputStream;

/**
* Build Key Store using configured properties
*
* @return Key Store
*/
@Override
public KeyStore build() {
final KeyStore keyStore = getInitializedKeyStore();

if (certificateInputStream == null) {
throw new BuilderConfigurationException("Certificate InputStream not configured");
}
if (privateKeyInputStream == null) {
throw new BuilderConfigurationException("Private Key InputStream not configured");
}

loadKeyStore(keyStore);

return keyStore;
}

/**
* Set Certificate InputStream to be loaded
*
* @param certificateInputStream Certificate InputStream
* @return Builder
*/
public PemPrivateKeyCertificateKeyStoreBuilder certificateInputStream(final InputStream certificateInputStream) {
this.certificateInputStream = Objects.requireNonNull(certificateInputStream, "Certificate InputStream required");
return this;
}

/**
* Set Private Key InputStream to be loaded
*
* @param privateKeyInputStream Private Key InputStream
* @return Builder
*/
public PemPrivateKeyCertificateKeyStoreBuilder privateKeyInputStream(final InputStream privateKeyInputStream) {
this.privateKeyInputStream = Objects.requireNonNull(privateKeyInputStream, "Private Key InputStream required");
return this;
}

private void loadKeyStore(final KeyStore keyStore) {
final PemCertificateReader pemCertificateReader = new StandardPemCertificateReader();
final List<Certificate> certificates = pemCertificateReader.readCertificates(certificateInputStream);
final Certificate[] certificateChain = certificates.toArray(new Certificate[]{});

final PemPrivateKeyReader pemPrivateKeyReader = new StandardPemPrivateKeyReader();
final PrivateKey privateKey = pemPrivateKeyReader.readPrivateKey(privateKeyInputStream);

try {
keyStore.setKeyEntry(ALIAS, privateKey, EMPTY_PROTECTION_PARAMETER, certificateChain);
} catch (final KeyStoreException e) {
throw new BuilderConfigurationException("Set key entry failed", e);
}
}

private KeyStore getInitializedKeyStore() {
final String keyStoreType = KeyStore.getDefaultType();
try {
final KeyStore keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(null);
return keyStore;
} catch (final Exception e) {
final String message = String.format("Key Store Type [%s] initialization failed", keyStoreType);
throw new BuilderConfigurationException(message, e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* 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.nifi.security.ssl;

import java.io.InputStream;
import java.security.PrivateKey;

/**
* Abstraction for reading a Private Key from a stream containing PEM headers and footers
*/
interface PemPrivateKeyReader {
/**
* Read Private Key from stream
*
* @param inputStream Stream containing PEM header and footer
* @return Private Key
*/
PrivateKey readPrivateKey(InputStream inputStream);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* 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.nifi.security.ssl;

/**
* Exception indicating runtime failure to read specified entity
*/
public class ReadEntityException extends RuntimeException {
/**
* Read Entity Exception Constructor with standard properties
*
* @param message Exception Message
* @param cause Exception Cause
*/
public ReadEntityException(final String message, final Throwable cause) {
super(message, cause);
}

/**
* Read Entity Exception Constructor without Throwable cause
*
* @param message Exception Message
*/
public ReadEntityException(final String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
/**
* Standard implementation of Key Store Builder
*/
public class StandardKeyStoreBuilder implements KeyStoreBuilder {
public class StandardKeyStoreBuilder implements InputStreamKeyStoreBuilder {
private Provider provider;

private String type = KeyStore.getDefaultType();
Expand Down Expand Up @@ -98,6 +98,7 @@ public StandardKeyStoreBuilder password(final char[] password) {
* @param inputStream Key Store InputStream
* @return Builder
*/
@Override
public StandardKeyStoreBuilder inputStream(final InputStream inputStream) {
this.inputStream = Objects.requireNonNull(inputStream, "Key Store InputStream required");
return this;
Expand Down
Loading

0 comments on commit f44696a

Please sign in to comment.