mirror of
https://github.com/vacp2p/linea-besu.git
synced 2026-01-09 15:37:54 -05:00
[BESU-80] TLS to orion (#324)
* enable SSL on connection to enclave Signed-off-by: Sally MacFarlane <sally.macfarlane@consensys.net>
This commit is contained in:
@@ -1,8 +1,15 @@
|
||||
dependencies {
|
||||
api project(':util')
|
||||
api project(':crypto')
|
||||
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind'
|
||||
implementation 'io.vertx:vertx-core'
|
||||
implementation 'org.apache.tuweni:tuweni-net'
|
||||
implementation 'org.apache.logging.log4j:log4j-api'
|
||||
|
||||
runtimeOnly('org.bouncycastle:bcpkix-jdk15on')
|
||||
|
||||
|
||||
// test dependencies.
|
||||
testImplementation project(':testutil')
|
||||
|
||||
@@ -10,6 +17,9 @@ dependencies {
|
||||
|
||||
// integration test dependencies.
|
||||
integrationTestImplementation project(':testutil')
|
||||
integrationTestImplementation 'org.bouncycastle:bcpkix-jdk15on'
|
||||
|
||||
|
||||
|
||||
integrationTestImplementation 'junit:junit'
|
||||
integrationTestImplementation 'net.consensys:orion'
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright ConsenSys AG.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.hyperledger.besu.enclave;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URL;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import com.google.common.io.Resources;
|
||||
|
||||
public class TlsCertificateDefinition {
|
||||
|
||||
private final File pkcs12File;
|
||||
private final String password;
|
||||
|
||||
public static TlsCertificateDefinition loadFromResource(
|
||||
final String resourcePath, final String password) {
|
||||
try {
|
||||
final URL sslCertificate = Resources.getResource(resourcePath);
|
||||
final Path keystorePath = Path.of(sslCertificate.getPath());
|
||||
|
||||
return new TlsCertificateDefinition(keystorePath.toFile(), password);
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException("Failed to load TLS certificates", e);
|
||||
}
|
||||
}
|
||||
|
||||
public TlsCertificateDefinition(final File pkcs12File, final String password) {
|
||||
this.pkcs12File = pkcs12File;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public File getPkcs12File() {
|
||||
return pkcs12File;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
* Copyright ConsenSys AG.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.hyperledger.besu.enclave;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.fail;
|
||||
import static org.hyperledger.besu.enclave.TlsHelpers.populateFingerprintFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.Optional;
|
||||
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.core.http.HttpServer;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
public class TlsEnabledEnclaveTest {
|
||||
|
||||
private TlsEnabledHttpServerFactory serverFactory;
|
||||
private Vertx vertx;
|
||||
|
||||
final TlsCertificateDefinition httpServerCert =
|
||||
TlsCertificateDefinition.loadFromResource("tls/cert1.pfx", "password");
|
||||
final TlsCertificateDefinition besuCert =
|
||||
TlsCertificateDefinition.loadFromResource("tls/cert2.pfx", "password2");
|
||||
|
||||
public void shutdown() {
|
||||
vertx.close();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
serverFactory = new TlsEnabledHttpServerFactory();
|
||||
this.vertx = Vertx.vertx();
|
||||
}
|
||||
|
||||
@After
|
||||
public void cleanup() {
|
||||
serverFactory.shutdown();
|
||||
this.shutdown();
|
||||
}
|
||||
|
||||
private Enclave createEnclave(
|
||||
final int httpServerPort, final Path workDir, final boolean tlsEnabled) throws IOException {
|
||||
|
||||
final Path serverFingerprintFile = workDir.resolve("server_known_clients");
|
||||
final Path besuCertPasswordFile = workDir.resolve("password_file");
|
||||
try {
|
||||
populateFingerprintFile(serverFingerprintFile, httpServerCert, Optional.of(httpServerPort));
|
||||
Files.write(besuCertPasswordFile, besuCert.getPassword().getBytes(Charset.defaultCharset()));
|
||||
|
||||
final EnclaveFactory factory = new EnclaveFactory(vertx);
|
||||
if (tlsEnabled) {
|
||||
final URI httpServerUri = new URI("https://localhost:" + httpServerPort);
|
||||
return factory.createVertxEnclave(
|
||||
httpServerUri,
|
||||
besuCert.getPkcs12File().toPath(),
|
||||
besuCertPasswordFile,
|
||||
serverFingerprintFile);
|
||||
} else {
|
||||
return factory.createVertxEnclave(new URI("http://localhost:" + httpServerPort));
|
||||
}
|
||||
} catch (KeyStoreException | NoSuchAlgorithmException | CertificateException e) {
|
||||
fail("unable to populate fingerprint file");
|
||||
return null;
|
||||
} catch (URISyntaxException e) {
|
||||
fail("unable to create URI");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonTlsEnclaveCannotConnectToTlsServer() throws IOException {
|
||||
|
||||
Path workDir = Files.createTempDirectory("test-certs");
|
||||
|
||||
// Note: the HttpServer always responds with a JsonRpcSuccess, result="I'm up".
|
||||
final HttpServer httpServer = serverFactory.create(httpServerCert, besuCert, workDir, true);
|
||||
|
||||
final Enclave enclave = createEnclave(httpServer.actualPort(), workDir, false);
|
||||
|
||||
assertThat(enclave.upCheck()).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void nonTlsEnclaveCanConnectToNonTlsServer() throws IOException {
|
||||
|
||||
Path workDir = Files.createTempDirectory("test-certs");
|
||||
|
||||
// Note: the HttpServer always responds with a JsonRpcSuccess, result="I'm up".
|
||||
final HttpServer httpServer = serverFactory.create(httpServerCert, besuCert, workDir, false);
|
||||
|
||||
final Enclave enclave = createEnclave(httpServer.actualPort(), workDir, false);
|
||||
|
||||
assertThat(enclave.upCheck()).isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tlsEnclaveCannotConnectToNonTlsServer() throws IOException {
|
||||
|
||||
Path workDir = Files.createTempDirectory("test-certs");
|
||||
|
||||
// Note: the HttpServer always responds with a JsonRpcSuccess, result="I'm up!".
|
||||
final HttpServer httpServer = serverFactory.create(httpServerCert, besuCert, workDir, false);
|
||||
|
||||
final Enclave enclave = createEnclave(httpServer.actualPort(), workDir, true);
|
||||
|
||||
assertThat(enclave.upCheck()).isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tlsEnclaveCanConnectToTlsServer() throws IOException {
|
||||
|
||||
Path workDir = Files.createTempDirectory("test-certs");
|
||||
|
||||
// Note: the HttpServer always responds with a JsonRpcSuccess, result="I'm up".
|
||||
final HttpServer httpServer = serverFactory.create(httpServerCert, besuCert, workDir, true);
|
||||
|
||||
final Enclave enclave = createEnclave(httpServer.actualPort(), workDir, true);
|
||||
|
||||
assertThat(enclave.upCheck()).isEqualTo(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* Copyright ConsenSys AG.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.hyperledger.besu.enclave;
|
||||
|
||||
import static org.hyperledger.besu.enclave.TlsHelpers.populateFingerprintFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import io.netty.handler.codec.http.HttpHeaderValues;
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.core.http.ClientAuth;
|
||||
import io.vertx.core.http.HttpMethod;
|
||||
import io.vertx.core.http.HttpServer;
|
||||
import io.vertx.core.http.HttpServerOptions;
|
||||
import io.vertx.core.net.PfxOptions;
|
||||
import io.vertx.ext.web.Router;
|
||||
import org.apache.tuweni.net.tls.VertxTrustOptions;
|
||||
|
||||
public class TlsEnabledHttpServerFactory {
|
||||
|
||||
private final Vertx vertx;
|
||||
private final List<HttpServer> serversCreated = Lists.newArrayList();
|
||||
|
||||
public TlsEnabledHttpServerFactory() {
|
||||
this.vertx = Vertx.vertx();
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
serversCreated.forEach(HttpServer::close);
|
||||
vertx.close();
|
||||
}
|
||||
|
||||
public HttpServer create(
|
||||
final TlsCertificateDefinition serverCert,
|
||||
final TlsCertificateDefinition acceptedClientCerts,
|
||||
final Path workDir,
|
||||
final boolean tlsEnabled) {
|
||||
try {
|
||||
|
||||
final Path serverFingerprintFile = workDir.resolve("server_known_clients");
|
||||
populateFingerprintFile(serverFingerprintFile, acceptedClientCerts, Optional.empty());
|
||||
|
||||
final HttpServerOptions web3HttpServerOptions = new HttpServerOptions();
|
||||
web3HttpServerOptions.setPort(0);
|
||||
if (tlsEnabled) {
|
||||
web3HttpServerOptions.setSsl(true);
|
||||
web3HttpServerOptions.setClientAuth(ClientAuth.REQUIRED);
|
||||
web3HttpServerOptions.setTrustOptions(
|
||||
VertxTrustOptions.whitelistClients(serverFingerprintFile));
|
||||
web3HttpServerOptions.setPfxKeyCertOptions(
|
||||
new PfxOptions()
|
||||
.setPath(serverCert.getPkcs12File().toString())
|
||||
.setPassword(serverCert.getPassword()));
|
||||
}
|
||||
final Router router = Router.router(vertx);
|
||||
router
|
||||
.route(HttpMethod.GET, "/upcheck")
|
||||
.produces(HttpHeaderValues.APPLICATION_JSON.toString())
|
||||
.handler(context -> context.response().end("I'm up!"));
|
||||
|
||||
final HttpServer mockOrionHttpServer = vertx.createHttpServer(web3HttpServerOptions);
|
||||
|
||||
final CompletableFuture<Boolean> serverConfigured = new CompletableFuture<>();
|
||||
mockOrionHttpServer.requestHandler(router).listen(result -> serverConfigured.complete(true));
|
||||
|
||||
serverConfigured.get();
|
||||
|
||||
serversCreated.add(mockOrionHttpServer);
|
||||
return mockOrionHttpServer;
|
||||
} catch (KeyStoreException
|
||||
| NoSuchAlgorithmException
|
||||
| CertificateException
|
||||
| IOException
|
||||
| ExecutionException
|
||||
| InterruptedException e) {
|
||||
throw new RuntimeException("Failed to construct a TLS Enabled Server", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/*
|
||||
* Copyright ConsenSys AG.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package org.hyperledger.besu.enclave;
|
||||
|
||||
import org.hyperledger.besu.crypto.MessageDigestFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
public class TlsHelpers {
|
||||
|
||||
private static KeyStore loadP12KeyStore(final File pkcsFile, final String password)
|
||||
throws KeyStoreException, NoSuchAlgorithmException, CertificateException {
|
||||
final KeyStore store = KeyStore.getInstance("pkcs12");
|
||||
try (final InputStream keystoreStream = new FileInputStream(pkcsFile)) {
|
||||
store.load(keystoreStream, password.toCharArray());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Unable to load keystore.", e);
|
||||
}
|
||||
return store;
|
||||
}
|
||||
|
||||
public static void populateFingerprintFile(
|
||||
final Path knownClientsPath,
|
||||
final TlsCertificateDefinition certDef,
|
||||
final Optional<Integer> serverPortToAppendToHostname)
|
||||
throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
|
||||
|
||||
final List<X509Certificate> certs = getCertsFromPkcs12(certDef);
|
||||
final StringBuilder fingerprintsToAdd = new StringBuilder();
|
||||
final String portFragment = serverPortToAppendToHostname.map(port -> ":" + port).orElse("");
|
||||
for (final X509Certificate cert : certs) {
|
||||
final String fingerprint = generateFingerprint(cert);
|
||||
fingerprintsToAdd.append(String.format("localhost%s %s%n", portFragment, fingerprint));
|
||||
fingerprintsToAdd.append(String.format("127.0.0.1%s %s%n", portFragment, fingerprint));
|
||||
}
|
||||
Files.writeString(knownClientsPath, fingerprintsToAdd.toString());
|
||||
}
|
||||
|
||||
public static List<X509Certificate> getCertsFromPkcs12(final TlsCertificateDefinition certDef)
|
||||
throws KeyStoreException, NoSuchAlgorithmException, CertificateException {
|
||||
final List<X509Certificate> results = Lists.newArrayList();
|
||||
|
||||
final KeyStore p12 = loadP12KeyStore(certDef.getPkcs12File(), certDef.getPassword());
|
||||
final Enumeration<String> aliases = p12.aliases();
|
||||
while (aliases.hasMoreElements()) {
|
||||
results.add((X509Certificate) p12.getCertificate(aliases.nextElement()));
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private static String generateFingerprint(final X509Certificate cert)
|
||||
throws NoSuchAlgorithmException, CertificateEncodingException {
|
||||
final MessageDigest md = MessageDigestFactory.create("SHA-256");
|
||||
md.update(cert.getEncoded());
|
||||
final byte[] digest = md.digest();
|
||||
|
||||
final StringJoiner joiner = new StringJoiner(":");
|
||||
for (final byte b : digest) {
|
||||
joiner.add(String.format("%02X", b));
|
||||
}
|
||||
|
||||
return joiner.toString().toLowerCase();
|
||||
}
|
||||
}
|
||||
BIN
enclave/src/integration-test/resources/tls/cert1.pfx
Normal file
BIN
enclave/src/integration-test/resources/tls/cert1.pfx
Normal file
Binary file not shown.
BIN
enclave/src/integration-test/resources/tls/cert2.pfx
Normal file
BIN
enclave/src/integration-test/resources/tls/cert2.pfx
Normal file
Binary file not shown.
@@ -14,21 +14,42 @@
|
||||
*/
|
||||
package org.hyperledger.besu.enclave;
|
||||
|
||||
import java.net.URI;
|
||||
import org.hyperledger.besu.util.InvalidConfigurationException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.nio.file.AccessDeniedException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import io.vertx.core.Vertx;
|
||||
import io.vertx.core.http.HttpClientOptions;
|
||||
import io.vertx.core.net.PfxOptions;
|
||||
import org.apache.tuweni.net.tls.VertxTrustOptions;
|
||||
|
||||
public class EnclaveFactory {
|
||||
|
||||
private final Vertx vertx;
|
||||
private static final int CONNECT_TIMEOUT = 1000;
|
||||
private static final boolean TRUST_CA = false;
|
||||
|
||||
public EnclaveFactory(final Vertx vertx) {
|
||||
this.vertx = vertx;
|
||||
}
|
||||
|
||||
public Enclave createVertxEnclave(final URI enclaveUri) {
|
||||
final HttpClientOptions clientOptions = createNonTlsClientOptions(enclaveUri);
|
||||
|
||||
final RequestTransmitter vertxTransmitter =
|
||||
new VertxRequestTransmitter(vertx.createHttpClient(clientOptions));
|
||||
|
||||
return new Enclave(vertxTransmitter);
|
||||
}
|
||||
|
||||
private HttpClientOptions createNonTlsClientOptions(final URI enclaveUri) {
|
||||
|
||||
if (enclaveUri.getPort() == -1) {
|
||||
throw new EnclaveIOException("Illegal URI - no port specified");
|
||||
}
|
||||
@@ -37,10 +58,62 @@ public class EnclaveFactory {
|
||||
clientOptions.setDefaultHost(enclaveUri.getHost());
|
||||
clientOptions.setDefaultPort(enclaveUri.getPort());
|
||||
clientOptions.setConnectTimeout(CONNECT_TIMEOUT);
|
||||
return clientOptions;
|
||||
}
|
||||
|
||||
private HttpClientOptions createTlsClientOptions(
|
||||
final URI enclaveUri,
|
||||
final Path privacyKeyStoreFile,
|
||||
final Path privacyKeyStorePasswordFile,
|
||||
final Path privacyWhitelistFile) {
|
||||
|
||||
final HttpClientOptions clientOptions = createNonTlsClientOptions(enclaveUri);
|
||||
try {
|
||||
if (privacyKeyStoreFile != null && privacyKeyStorePasswordFile != null) {
|
||||
clientOptions.setSsl(true);
|
||||
clientOptions.setPfxKeyCertOptions(
|
||||
convertFrom(privacyKeyStoreFile, privacyKeyStorePasswordFile));
|
||||
}
|
||||
clientOptions.setTrustOptions(
|
||||
VertxTrustOptions.whitelistServers(privacyWhitelistFile, TRUST_CA));
|
||||
} catch (final NoSuchFileException e) {
|
||||
throw new InvalidConfigurationException(
|
||||
"Requested file " + e.getMessage() + " does not exist at specified location.");
|
||||
} catch (final AccessDeniedException e) {
|
||||
throw new InvalidConfigurationException(
|
||||
"Current user does not have permissions to access " + e.getMessage());
|
||||
} catch (final IllegalArgumentException e) {
|
||||
throw new InvalidConfigurationException("Illegally formatted client fingerprint file.");
|
||||
} catch (final IOException e) {
|
||||
throw new InvalidConfigurationException("Failed to load TLS files " + e.getMessage());
|
||||
}
|
||||
return clientOptions;
|
||||
}
|
||||
|
||||
public Enclave createVertxEnclave(
|
||||
final URI enclaveUri,
|
||||
final Path privacyKeyStoreFile,
|
||||
final Path privacyKeyStorePasswordFile,
|
||||
final Path privacyWhitelistFile) {
|
||||
|
||||
final HttpClientOptions clientOptions =
|
||||
createTlsClientOptions(
|
||||
enclaveUri, privacyKeyStoreFile, privacyKeyStorePasswordFile, privacyWhitelistFile);
|
||||
|
||||
final RequestTransmitter vertxTransmitter =
|
||||
new VertxRequestTransmitter(vertx.createHttpClient(clientOptions));
|
||||
|
||||
return new Enclave(vertxTransmitter);
|
||||
}
|
||||
|
||||
private static PfxOptions convertFrom(final Path keystoreFile, final Path keystorePasswordFile)
|
||||
throws IOException {
|
||||
final String password = readSecretFromFile(keystorePasswordFile);
|
||||
return new PfxOptions().setPassword(password).setPath(keystoreFile.toString());
|
||||
}
|
||||
|
||||
private static String readSecretFromFile(final Path path) throws IOException {
|
||||
final byte[] fileContent = Files.readAllBytes(path);
|
||||
return new String(fileContent, Charsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user