В Java, какой самый простой способ создать SSLContext только с файлом PEM?
Я использовал CertBot LetsEncrypt для генерации файлов PEM бесплатно. На других языках легко запустить HTTPS-сервер, используя всего пару строк кода и файлы PEM/key. Решения, которые я нашел в Java, слишком сложны, и я ищу что-то более простое.
- Я не хочу использовать командную строку Java "keytool". Я просто хочу перетащить мои файлы PEM/key в мое затмение и программно запустить HTTPS-сервер, используя SSLContext.
- Я не хочу включать массивные внешние библиотеки, такие как BouncyCastle. Смотрите следующую ссылку для предполагаемого решения с использованием BouncyCastle: Как создать SSLSocketFactory из сертификата PEM и ключа без преобразования в хранилище ключей?
Есть ли лучший / более простой способ сделать это?
2 ответа
Хотя ответ был дан, я хотел бы предложить альтернативу, которая также может сработать для вас. См. Ниже пример настройки:
X509ExtendedKeyManager keyManager = PemUtils.loadIdentityMaterial("certificate-chain.pem", "private-key.pem", "private-key-password".toCharArray());
X509ExtendedTrustManager trustManager = PemUtils.loadTrustMaterial("some-trusted-certificate.pem");
SSLFactory sslFactory = SSLFactory.builder()
.withIdentityMaterial(keyManager)
.withTrustMaterial(trustManager)
.build();
SSLContext sslContext = sslFactory.getSslContext();
Чтобы использовать указанную выше настройку, вы можете использовать эту библиотеку:
<dependency>
<groupId>io.github.hakky54</groupId>
<artifactId>sslcontext-kickstart-for-pem</artifactId>
<version>5.4.0</version>
</dependency>
Приведенная выше установка требует меньшего количества настраиваемого кода, пока она все еще выполняет то, что вы пытаетесь выполнить.
Следующий код в целом показывает, как создать SSLContext для HTTPS-сервера путем анализа файла PEM с несколькими записями, например, несколькими сертификатами и одним RSA PRIVATE KEY
, Однако он неполон, потому что простая Java 8 не может проанализировать данные закрытого ключа RSA PKCS#1. Поэтому кажется, что ваше желание сделать это без какой-либо библиотеки невозможно. По крайней мере, BouncyCastle для анализа данных PKCS#1 требуется (и тогда можно использовать PEM-анализатор BouncyCastle).
private SSLContext createSslContext() throws Exception {
URL url = getClass().getResource("/a.pem");
InputStream in = url.openStream();
String pem = new String(in.readAllBytes(), StandardCharsets.UTF_8);
Pattern parse = Pattern.compile("(?m)(?s)^---*BEGIN ([^-]+)---*$([^-]+)^---*END[^-]+-+$");
Matcher m = parse.matcher(pem);
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
Decoder decoder = Base64.getMimeDecoder();
List<Certificate> certList = new ArrayList<>(); // java.security.cert.Certificate
PrivateKey privateKey = null;
int start = 0;
while (m.find(start)) {
String type = m.group(1);
String base64Data = m.group(2);
byte[] data = decoder.decode(base64Data);
start += m.group(0).length();
type = type.toUpperCase();
if (type.contains("CERTIFICATE")) {
Certificate cert = certFactory.generateCertificate(new ByteArrayInputStream(data));
certList.add(cert);
} else if (type.contains("RSA PRIVATE KEY")) {
// TODO: load and parse PKCS1 data structure to get the RSA private key
privateKey = ...
} else {
System.err.println("Unsupported type: " + type);
}
}
if (privateKey == null)
throw new RuntimeException("RSA private key not found in PEM file");
char[] keyStorePassword = new char[0];
KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, null);
int count = 0;
for (Certificate cert : certList) {
keyStore.setCertificateEntry("cert" + count, cert);
count++;
}
Certificate[] chain = certList.toArray(new Certificate[certList.size()]);
keyStore.setKeyEntry("key", privateKey, keyStorePassword, chain);
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
tmf.init(keyStore);
KeyManagerFactory kmf = KeyManagerFactory.getInstance("RSA");
kmf.init(keyStore, keyStorePassword);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new SecureRandom());
return sslContext;
}
Мое полное решение, которое я сейчас использую:
- Используйте certbot на своем сервере для создания сертификата. Я использую команду "certbot certonly -d myawesomedomain.com"
- Я использую следующий код для преобразования этого сертификата certbot в java SSLContext: https://github.com/mirraj2/bowser/blob/master/src/bowser/SSLUtils.java
package bowser;
import static com.google.common.base.Preconditions.checkState;
import static ox.util.Utils.propagate;
import java.io.File;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManagerFactory;
import com.google.common.base.Splitter;
import ox.IO;
import ox.Log;
public class SSLUtils {
public static SSLContext createContext(String domain) {
String pass = "spamspam";
File dir = new File("/etc/letsencrypt/live/" + domain);
if (!dir.exists()) {
Log.warn("Could not find letsencrypt dir: " + dir);
return null;
}
File keystoreFile = new File(dir, "keystore.jks");
File pemFile = new File(dir, "fullchain.pem");
boolean generateKeystore = false;
if (keystoreFile.exists()) {
if (keystoreFile.lastModified() < pemFile.lastModified()) {
Log.info("SSUtils: It looks like a new PEM file was created. Regenerating the keystore.");
keystoreFile.delete();
generateKeystore = true;
}
} else {
generateKeystore = true;
}
if (generateKeystore) {
Splitter splitter = Splitter.on(' ');
try {
String command = "openssl pkcs12 -export -out keystore.pkcs12 -in fullchain.pem -inkey privkey.pem -passout pass:"
+ pass;
Log.debug(command);
Process process = new ProcessBuilder(splitter.splitToList(command))
.directory(dir).inheritIO().start();
checkState(process.waitFor() == 0);
command = "keytool -importkeystore -srckeystore keystore.pkcs12 -srcstoretype PKCS12 -destkeystore keystore.jks -srcstorepass "
+ pass + " -deststorepass " + pass;
Log.debug(command);
process = new ProcessBuilder(splitter.splitToList(command))
.directory(dir).inheritIO().start();
checkState(process.waitFor() == 0);
new File(dir, "keystore.pkcs12").delete();// cleanup
} catch (Exception e) {
throw propagate(e);
}
}
try {
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(IO.from(keystoreFile).asStream(), pass.toCharArray());
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keystore, pass.toCharArray());
SSLContext ret = SSLContext.getInstance("TLSv1.2");
TrustManagerFactory factory = TrustManagerFactory.getInstance(
TrustManagerFactory.getDefaultAlgorithm());
factory.init(keystore);
ret.init(keyManagerFactory.getKeyManagers(), factory.getTrustManagers(), null);
return ret;
} catch (Exception e) {
throw propagate(e);
}
}
}
Вы можете сделать что-то вроде этого, чтобы получить правильный SSLContext:
final String ca1 = "..load PEM file in string..";
final CertificateFactory cf = CertificateFactory.getInstance("X.509");
final Certificate cert1 = cf.generateCertificate(new ByteArrayInputStream(ca1.getBytes()));
final KeyStore keyStore = KeyStore.getInstance("JKS");
keyStore.load(null, null);
// Can add multiple truststore certificates here...
keyStore.setCertificateEntry("my-ca-1", cert1);
final SSLContext sslContext = SSLContexts.custom().setKeyStoreType(saToken)
.loadTrustMaterial(keyStore, null)
.build();
Это временное решение, потому что приведенный ниже код позволяет вам принять любой сервер, поэтому вам следует тщательно проверять свой код, когда вы пробуете решения такого рода.
Этот код не нуждается ни в каком сертификате вообще.
Вопрос в том, почему вы пытаетесь избежать этого процесса, если запрашиваемое дело не должно использовать небезопасный сервер?
logger.info("Starting instance ");
TrustManager[] tm = new TrustManager[]{new X509TrustManager() {
public X509Certificate[] getAcceptedIssuers(){return new X509Certificate[]{};}
public void checkClientTrusted(X509Certificate[] chain, String authType) {logger.info(" checkClientTrusted");}
public void checkServerTrusted(X509Certificate[] chain, String authType) {logger.info(" checkServerTrusted");}
}};
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(null, tm , new SecureRandom());
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory());