Letsencrypt Challenge Keymanager
Key Manager that handle the AMCE tls-sni-01 challenge. It is written in plain java using sun methods but no external dependency.
ACME is the protocol used by
Letsencrypt
package org.suche.web.acme;
import java.net.Socket;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.Principal;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.net.ssl.ExtendedSSLSession;
import javax.net.ssl.SNIHostName;
import javax.net.ssl.SNIServerName;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.X509ExtendedKeyManager;
import sun.security.x509.AlgorithmId;
import sun.security.x509.CertificateAlgorithmId;
import sun.security.x509.CertificateExtensions;
import sun.security.x509.CertificateSerialNumber;
import sun.security.x509.CertificateValidity;
import sun.security.x509.CertificateVersion;
import sun.security.x509.CertificateX509Key;
import sun.security.x509.GeneralName;
import sun.security.x509.GeneralNames;
import sun.security.x509.SubjectAlternativeNameExtension;
import sun.security.x509.X500Name;
import sun.security.x509.X509CertImpl;
import sun.security.x509.X509CertInfo;
public final class ChallengeKeyManager extends X509ExtendedKeyManager {
private final Logger log = Logger.getLogger(this.getClass().getCanonicalName());
private final KeyPair kp; // secp256r1 , secp384r1 , secp521r1
private final String KEY_CURVE = "secp521r1";
private final String SIG_ALG = "SHA256withECDSA"; // SHA256withRSA, SHA256withECDSA, SHA384withRSA, SHA384withECDSA, SHA1withRSA, SHA1withECDSA
private static final List valid = new LinkedList<>();
public static final void addValid(final String fqdn) {
if(fqdn == null) return;
if(valid.size() >= 10) valid.remove(0);
valid.add(fqdn.toLowerCase());
}
public ChallengeKeyManager() {
try {
final KeyPairGenerator g = KeyPairGenerator.getInstance("EC");
g.initialize(new ECGenParameterSpec(this.KEY_CURVE));
this.kp = g.generateKeyPair();
} catch(final Throwable t) { throw new RuntimeException(t); }
}
private String chooseServerAlias(final String keyType, final Principal[] issuers, final SSLSession session) {
if("RSA".equals(keyType)) return null;
if(!(session instanceof ExtendedSSLSession)) return null;
final List snis = ((ExtendedSSLSession)session).getRequestedServerNames();
if(snis == null || snis.size() == 0) return null;
final SNIServerName n = snis.get(0);
if(!(n instanceof SNIHostName)) return null;
final SNIHostName shn = (SNIHostName)n;
final String fqdn = shn.getAsciiName().toLowerCase();
if(!fqdn.endsWith(".acme.invalid")) return null;
if(valid.contains(shn.getAsciiName().toLowerCase())) {
this.log.log(Level.WARNING, "chooseEngineServerAlias("+keyType+", "+(issuers==null?-1:issuers.length)+" , {sni:"+shn.getAsciiName()+"})="+shn.getAsciiName());
return shn.getAsciiName();
}
this.log.log(Level.WARNING, "chooseEngineServerAlias("+keyType+", "+(issuers==null?-1:issuers.length)+" , {sni:"+shn.getAsciiName()+"})=>Unknown");
throw new RuntimeException("BLOCKED");
}
@Override public String chooseEngineClientAlias(final String[] keyType, final Principal[] issuers, final SSLEngine engine) { return null; }
@Override public String chooseClientAlias (final String[] keyType, final Principal[] issuers, final Socket socket) { return null; }
@Override public String[] getClientAliases (final String keyType, final Principal[] issuers) { return null; }
@Override public String[] getServerAliases(final String keyType, final Principal[] issuers) { return null; }
@Override public String chooseEngineServerAlias(final String keyType, final Principal[] issuers, final SSLEngine engine) {
return chooseServerAlias(keyType, issuers, engine.getHandshakeSession());
}
@Override public String chooseServerAlias(final String keyType, final Principal[] issuers, final Socket socket) {
return chooseServerAlias(keyType, issuers, ((SSLSocket)socket).getHandshakeSession());
}
@Override public X509Certificate[] getCertificateChain(final String alias) {
try {
final X500Name name = new X500Name("cn=dummy");
final X509CertInfo ci = new X509CertInfo();
final CertificateExtensions ext = new CertificateExtensions();
final GeneralNames gnames = new GeneralNames();
ci.set(X509CertInfo.VERSION , new CertificateVersion(2));
ci.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(new Random().nextInt() & 0x7FFFFFFF));
ci.set(X509CertInfo.ALGORITHM_ID , new CertificateAlgorithmId(AlgorithmId.get(this.SIG_ALG)));
ci.set(X509CertInfo.SUBJECT , name);
ci.set(X509CertInfo.KEY , new CertificateX509Key(this.kp.getPublic()));
ci.set(X509CertInfo.VALIDITY , new CertificateValidity(new Date(), new Date(System.currentTimeMillis() + 3600000))); // 1.Hour
ci.set(X509CertInfo.ISSUER , name);
gnames.add(new GeneralName(new AcmeDNSName(alias)));
ext.set(SubjectAlternativeNameExtension.NAME, new SubjectAlternativeNameExtension(false, gnames));
ci.set(X509CertInfo.EXTENSIONS , ext);
final X509CertImpl certImpl = new X509CertImpl(ci);
certImpl.sign(this.kp.getPrivate(), this.SIG_ALG);
return new X509Certificate[]{certImpl};
} catch(final Throwable t) {
this.log.log(Level.SEVERE, "getCertificateChain()=>"+t.getMessage(), t);
return null;
}
}
@Override public PrivateKey getPrivateKey(final String alias) { return this.kp.getPrivate(); }
}