Java tutorial
/*- * -\-\- * ssh-agent-tls * -- * Copyright (C) 2016 - 2017 Spotify AB * -- * 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. * -/-/- */ package com.spotify.sshagenttls; import static java.util.concurrent.TimeUnit.HOURS; import com.google.common.annotations.VisibleForTesting; import com.google.common.io.BaseEncoding; import java.math.BigInteger; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.security.Security; import java.security.cert.X509Certificate; import java.util.Calendar; import java.util.Date; import javax.security.auth.x500.X500Principal; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.X500NameBuilder; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x509.AlgorithmIdentifier; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.KeyUsage; import org.bouncycastle.asn1.x509.SubjectKeyIdentifier; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cert.X509CertificateHolder; import org.bouncycastle.cert.X509ExtensionUtils; import org.bouncycastle.cert.X509v3CertificateBuilder; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.DigestCalculator; import org.bouncycastle.operator.bc.BcDigestCalculatorProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class X509CertKeyCreator implements CertKeyCreator { private static final Logger LOG = LoggerFactory.getLogger(X509CertKeyCreator.class); private static final JcaX509CertificateConverter CERT_CONVERTER = new JcaX509CertificateConverter() .setProvider("BC"); private static final BaseEncoding KEY_ID_ENCODING = BaseEncoding.base16().upperCase().withSeparator(":", 2); private static final int KEY_SIZE = 2048; private final ContentSigner contentSigner; private final int validBeforeMillis; private final int validAfterMillis; private X509CertKeyCreator(final ContentSigner contentSigner, final int validBeforeMillis, final int validAfterMillis) { this.validBeforeMillis = validBeforeMillis; this.validAfterMillis = validAfterMillis; this.contentSigner = contentSigner; } public static X509CertKeyCreator create(final ContentSigner contentSigner) { return X509CertKeyCreator.create(contentSigner, (int) HOURS.toMillis(1), (int) HOURS.toMillis(48)); } @VisibleForTesting static X509CertKeyCreator create(final ContentSigner contentSigner, final int validBeforeMillis, final int validAfterMills) { return new X509CertKeyCreator(contentSigner, validBeforeMillis, validAfterMills); } @Override public CertKey createCertKey(final String username, final X500Principal x500Principal) { final Calendar calendar = Calendar.getInstance(); final BigInteger serialNumber = BigInteger.valueOf(calendar.getTimeInMillis()).abs(); final X500Name issuerDn = new X500Name(x500Principal.getName(X500Principal.RFC1779)); final X500Name subjectDn = new X500NameBuilder().addRDN(BCStyle.UID, username).build(); calendar.add(Calendar.MILLISECOND, -validBeforeMillis); final Date notBefore = calendar.getTime(); calendar.add(Calendar.MILLISECOND, validBeforeMillis + validAfterMillis); final Date notAfter = calendar.getTime(); try { final KeyPair keyPair = generateRandomKeyPair(); final SubjectPublicKeyInfo subjectPublicKeyInfo = SubjectPublicKeyInfo .getInstance(ASN1Sequence.getInstance(keyPair.getPublic().getEncoded())); final X509v3CertificateBuilder builder = new X509v3CertificateBuilder(issuerDn, serialNumber, notBefore, notAfter, subjectDn, subjectPublicKeyInfo); final DigestCalculator digestCalculator = new BcDigestCalculatorProvider() .get(new AlgorithmIdentifier(OIWObjectIdentifiers.idSHA1)); final X509ExtensionUtils utils = new X509ExtensionUtils(digestCalculator); final SubjectKeyIdentifier keyId = utils.createSubjectKeyIdentifier(subjectPublicKeyInfo); final String keyIdHex = KEY_ID_ENCODING.encode(keyId.getKeyIdentifier()); LOG.info("generating an X.509 certificate for {} with key ID={}", username, keyIdHex); builder.addExtension(Extension.subjectKeyIdentifier, false, keyId); builder.addExtension(Extension.authorityKeyIdentifier, false, utils.createAuthorityKeyIdentifier(subjectPublicKeyInfo)); builder.addExtension(Extension.keyUsage, false, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyCertSign)); builder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false)); final X509CertificateHolder holder = builder.build(contentSigner); final X509Certificate cert = CERT_CONVERTER.getCertificate(holder); LOG.debug("generated certificate:\n{}", Utils.asPemString(cert)); return CertKey.create(cert, keyPair.getPrivate()); } catch (Exception e) { throw new RuntimeException(e); } } private static KeyPair generateRandomKeyPair() throws NoSuchAlgorithmException, NoSuchProviderException { Security.addProvider(new BouncyCastleProvider()); final KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", "BC"); keyPairGenerator.initialize(KEY_SIZE, new SecureRandom()); return keyPairGenerator.generateKeyPair(); } }