package io.cattle.platform.ssh.common;
import io.cattle.platform.archaius.util.ArchaiusUtil;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Random;
import javax.crypto.KeyAgreement;
import javax.security.auth.x500.X500Principal;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
import org.bouncycastle.asn1.x509.Extension;
import org.bouncycastle.asn1.x509.GeneralName;
import org.bouncycastle.asn1.x509.GeneralNames;
import org.bouncycastle.asn1.x509.KeyPurposeId;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import com.netflix.config.DynamicLongProperty;
import com.netflix.config.DynamicStringProperty;
public class SshKeyGen {
private static final byte[] HEADER = new byte[] { 's', 's', 'h', '-', 'r', 's', 'a' };
private static final DynamicStringProperty SSH_FORMAT = ArchaiusUtil.getString("ssh.key.text.format");
private static final DynamicLongProperty EXPIRATION = ArchaiusUtil.getLong("cert.expiry.days");
private static final JcaPEMKeyConverter CONVERTER = new JcaPEMKeyConverter().setProvider("BC");
private static final Random RANDOM = new Random();
public static final String BOUNCY_CASTLE = "BC";
static {
if (java.security.Security.getProvider(BOUNCY_CASTLE) == null) {
java.security.Security.addProvider(new BouncyCastleProvider());
try {
MessageDigest.getInstance("MD5", BOUNCY_CASTLE);
KeyAgreement.getInstance("DH", BOUNCY_CASTLE);
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
throw new RuntimeException(e);
}
}
}
public static String[] generateKeys() throws Exception {
KeyPair pair = generateKeyPair();
String publicString = sshRsaTextFormat((RSAPublicKey) pair.getPublic());
return new String[] { publicString, toPEM(pair) };
}
public static X509Certificate createRootCACert(KeyPair keyPair) throws Exception {
X509v3CertificateBuilder certBldr = new JcaX509v3CertificateBuilder(
new X500Name("O=cattle"),
BigInteger.valueOf(Math.abs(RANDOM.nextLong())),
new Date(System .currentTimeMillis()),
new Date(System.currentTimeMillis() + EXPIRATION.get() * 24 * 60 * 60 * 1000),
new X500Name("O=cattle"), keyPair.getPublic());
certBldr.addExtension(Extension.basicConstraints,
true, new BasicConstraints(true))
.addExtension(Extension.keyUsage,
true, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.digitalSignature | KeyUsage.keyEncipherment));
ContentSigner signer = new JcaContentSignerBuilder("SHA512withRSA").setProvider("BC").build(keyPair.getPrivate());
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certBldr.build(signer));
}
public static X509Certificate generateClientCert(String subject, PublicKey entityKey, PrivateKey caKey,
X509Certificate caCert, String... sans) throws NoSuchAlgorithmException, CertIOException, OperatorCreationException, CertificateException {
X509v3CertificateBuilder certBldr = new JcaX509v3CertificateBuilder(
caCert.getSubjectX500Principal(),
BigInteger.valueOf(Math.abs(RANDOM.nextLong())),
new Date(System.currentTimeMillis()),
new Date(System.currentTimeMillis() + EXPIRATION.get() * 24 * 60 * 60 * 1000),
new X500Principal("CN=" + subject),
entityKey);
List<GeneralName> sanNameList = new ArrayList<>();
for (String san : sans) {
if (san.startsWith("IP:")) {
sanNameList.add(new GeneralName(GeneralName.iPAddress, san.substring(3)));
sanNameList.add(new GeneralName(GeneralName.dNSName, san.substring(3)));
} else {
sanNameList.add(new GeneralName(GeneralName.dNSName, san));
}
}
GeneralName[] sanNames = sanNameList.toArray(new GeneralName[sanNameList.size()]);
certBldr.addExtension(Extension.subjectAlternativeName,
false, new GeneralNames(sanNames))
.addExtension(Extension.basicConstraints,
true, new BasicConstraints(false))
.addExtension(Extension.keyUsage,
true, new KeyUsage(KeyUsage.digitalSignature
| KeyUsage.keyEncipherment))
.addExtension(Extension.extendedKeyUsage,
true, ExtendedKeyUsage.getInstance(new DERSequence(new ASN1Encodable[]{
KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth
}))
);
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").setProvider("BC").build(caKey);
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(certBldr.build(signer));
}
public static KeyPair generateKeyPair(int keySize) throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", BOUNCY_CASTLE);
generator.initialize(keySize);
return generator.generateKeyPair();
}
public static KeyPair generateKeyPair() throws Exception {
return generateKeyPair(2048);
}
public static X509Certificate readCACert(String encoded) throws Exception {
try (PEMParser r = new PEMParser(new StringReader(encoded))) {
X509CertificateHolder holder = ((X509CertificateHolder) r.readObject());
return new JcaX509CertificateConverter().setProvider("BC").getCertificate(holder);
}
}
public static KeyPair readKeyPair(String key) throws Exception {
PEMParser r = null;
try {
if (key.startsWith("---")) {
r = new PEMParser(new StringReader(key));
} else {
/*
* Backward compatibility with how the key was stored in data
* table
*/
ByteArrayInputStream bais = new ByteArrayInputStream(Base64.decodeBase64(key));
r = new PEMParser(new InputStreamReader(bais));
}
PEMKeyPair kp = (PEMKeyPair) r.readObject();
return CONVERTER.getKeyPair(kp);
} finally {
IOUtils.closeQuietly(r);
}
}
public static String toPEM(Object obj) throws Exception {
StringWriter stringWriter = new StringWriter();
try (JcaPEMWriter w = new JcaPEMWriter(stringWriter)) {
w.writeObject(obj);
w.flush();
}
return stringWriter.toString();
}
public static String writePublicKey(PublicKey pk) throws Exception {
StringWriter stringWriter = new StringWriter();
JcaPEMWriter w = new JcaPEMWriter(stringWriter);
w.writeObject(pk);
w.flush();
IOUtils.closeQuietly(w);
return stringWriter.toString();
}
public static String sshRsaTextFormat(RSAPublicKey key) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
write(out, HEADER);
write(out, key.getPublicExponent().toByteArray());
write(out, key.getModulus().toByteArray());
return String.format(SSH_FORMAT.get(), Base64.encodeBase64String(out.toByteArray()));
}
protected static void write(OutputStream os, byte[] content) throws IOException {
byte[] length = new byte[4];
length[0] = (byte) ((content.length >>> 24) & 0xff);
length[1] = (byte) ((content.length >>> 16) & 0xff);
length[2] = (byte) ((content.length >>> 8) & 0xff);
length[3] = (byte) (content.length & 0xff);
os.write(length);
os.write(content);
}
}