Java tutorial
/* * * Copyright (c) 2016 by Alex Shpurov. All rights reserved. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package com.rcn.service; import com.rcn.core.Tuple; import com.rcn.domain.LicenseKey; import com.rcn.repository.HistoryRepository; import com.rcn.repository.ResourceRepository; import org.bouncycastle.asn1.*; import org.bouncycastle.asn1.x509.*; import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; import org.bouncycastle.jce.PKCS10CertificationRequest; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.openssl.PEMReader; import org.bouncycastle.openssl.PEMWriter; import org.bouncycastle.openssl.PasswordFinder; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.security.auth.x500.X500Principal; import java.io.*; import java.math.BigInteger; import java.security.*; import java.security.cert.Certificate; import java.security.cert.X509Certificate; import java.util.*; import static com.rcn.repository.ResourceRepository.TYPE_Resource; import static com.rcn.repository.ResourceRepository.TYPE_Token; /******************************************************************************* * $ Date: 5/20/2016 $ * $ Author: Alex Shpurov $ ********************************************************************************/ @Service public class CertificateService { private static final Logger log = LoggerFactory.getLogger(CertificateService.class); @Autowired private ResourceRepository resourceRepository; @Autowired private HistoryRepository historyRepository; private static final String ANY_POLICY = "2.5.29.32.0"; public void storeCert(Long resourceId, String pemCert, String password) { Tuple<KeyPair, X509Certificate> c = fromPem(pemCert, password); X509Certificate certificate = c.getY(); String thumb = getThumbPrint(certificate); resourceRepository.updateCertInfo(resourceId, thumb, certificate.getNotBefore().getTime(), certificate.getNotAfter().getTime(), certificate.getIssuerDN().toString(), String.valueOf(certificate.getSerialNumber()), pemCert); } private KeyPair generateKey() { KeyPairGenerator keyGen = null; try { keyGen = KeyPairGenerator.getInstance("RSA", "BC"); keyGen.initialize(2048); return keyGen.genKeyPair(); } catch (Exception e) { throw new SecurityException(e); } } public String generateCert(String certName, String password, int validDays, Optional<String> caPem, String caPassword, boolean generateCaCert, Optional<String> pkc10Request) { try { Optional<Tuple<KeyPair, X509Certificate>> caTuple = caPem.map(c -> fromPem(c, caPassword)); Optional<KeyPair> ca = caTuple.map(a -> a.getX()); Optional<KeyPair> optKeyPair = Optional.ofNullable(pkc10Request.isPresent() ? null : generateKey()); PublicKey publicKey = pkc10Request.map(this::fromPkcs10).orElseGet(() -> optKeyPair.get().getPublic()); Date now = new Date(); Calendar tenYears = Calendar.getInstance(); tenYears.add(Calendar.DATE, validDays); X500Principal x500Principal = new X500Principal(certName); BigInteger serial = rndBigInt(new BigInteger("8180385048")); //max value for SN X500Principal issuer = caTuple.map(a -> a.getY().getSubjectX500Principal()) .orElseGet(() -> x500Principal); JcaX509v3CertificateBuilder v3CertGen = new JcaX509v3CertificateBuilder(issuer, serial, now, new Date(tenYears.getTimeInMillis()), x500Principal, publicKey); v3CertGen.addExtension(X509Extension.subjectKeyIdentifier, false, new SubjectKeyIdentifier(getSubjectPublicKeyInfo(publicKey))); ca.ifPresent(caKey -> v3CertGen.addExtension(X509Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifier(getSubjectPublicKeyInfo(caKey.getPublic())))); if (generateCaCert) { addCaExtension(v3CertGen); } else { addRegularExtension(Optional.empty(), v3CertGen); } KeyPair caKey = ca.orElseGet( () -> optKeyPair.orElseThrow(() -> new SecurityException("no private key for self-sign cert"))); X509Certificate certificate = new JcaX509CertificateConverter() .setProvider(BouncyCastleProvider.PROVIDER_NAME) .getCertificate(v3CertGen.build(new JcaContentSignerBuilder("SHA256WithRSAEncryption") .setProvider(BouncyCastleProvider.PROVIDER_NAME).build(caKey.getPrivate()))); String certPem = toPem(certificate, Optional.empty()); String keyPem = optKeyPair .map(k -> toPem(k, Optional.ofNullable(password.length() > 0 ? password : null))).orElse(""); return String.format("%s%s", certPem, keyPem); } catch (Exception e) { throw new SecurityException(e); } } private void addCaExtension(JcaX509v3CertificateBuilder v3CertGen) { v3CertGen.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(true)); v3CertGen.addExtension(X509Extension.keyUsage, false, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign)); ASN1EncodableVector intPolicies = new ASN1EncodableVector(); intPolicies.add(new PolicyInformation(new DERObjectIdentifier(ANY_POLICY))); v3CertGen.addExtension(X509Extension.certificatePolicies, false, new DERSequence(intPolicies)); } private void addRegularExtension(Optional<GeneralNames> generalNames, JcaX509v3CertificateBuilder v3CertGen) { v3CertGen.addExtension(X509Extension.keyUsage, false, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment)); Vector<KeyPurposeId> authTypes = new Vector<>(); authTypes.add(KeyPurposeId.id_kp_clientAuth); authTypes.add(KeyPurposeId.id_kp_serverAuth); v3CertGen.addExtension(X509Extension.extendedKeyUsage, false, new ExtendedKeyUsage(authTypes)); generalNames.ifPresent(n -> v3CertGen.addExtension(X509Extension.subjectAlternativeName, false, n)); } private BigInteger rndBigInt(BigInteger max) { Random rnd = new Random(); do { BigInteger i = new BigInteger(max.bitLength(), rnd); if (i.compareTo(max) <= 0) return i; } while (true); } private GeneralNames toGeneralNames(String altName, Map<String, String> generalNameMap) { GeneralName subjectAltName = new GeneralName(GeneralName.rfc822Name, altName); List<GeneralName> generalNameList = new ArrayList<GeneralName>(); generalNameList.add(subjectAltName); generalNameMap.keySet().forEach(oid -> { String value = generalNameMap.get(oid); DERUTF8String derUtf8 = new DERUTF8String(value); ASN1Encodable oidObj = new DERObjectIdentifier(oid); ASN1Encodable valueObj = new DERTaggedObject(true, 0, derUtf8); ASN1Encodable[] asn1Seq = new ASN1Encodable[] { oidObj, valueObj }; generalNameList.add(new GeneralName(GeneralName.otherName, new DERSequence(asn1Seq))); }); return new GeneralNames(new DERSequence(generalNameList.toArray(new GeneralName[0]))); } private SubjectPublicKeyInfo getSubjectPublicKeyInfo(PublicKey pub) { ByteArrayInputStream bIn = new ByteArrayInputStream(pub.getEncoded()); try { return new SubjectPublicKeyInfo((ASN1Sequence) new ASN1InputStream(bIn).readObject()); } catch (IOException e) { throw new SecurityException(e); } } private byte[] p12Write(PrivateKey key, Certificate[] certChain, String password) { //BC 1.42 for jdk 1.6 provider is buggy for PKSC12, so we use default KeyStore store = null; try { store = KeyStore.getInstance("PKCS12", "SunJSSE"); store.load(null, null); store.setKeyEntry("rcn-key", key, password.toCharArray(), certChain); ByteArrayOutputStream stream = new ByteArrayOutputStream(); store.store(stream, password.toCharArray()); return stream.toByteArray(); } catch (Exception e) { throw new SecurityException(e); } } private Tuple<PrivateKey, Certificate[]> p12Read(String base64, String password) { KeyStore store = null; try { byte[] bytes = Base64.getDecoder().decode(base64.getBytes()); store = KeyStore.getInstance("PKCS12", "SunJSSE"); store.load(new ByteArrayInputStream(bytes), password.toCharArray()); KeyStore.PrivateKeyEntry key = (KeyStore.PrivateKeyEntry) store.getEntry("rcn-key", new KeyStore.PasswordProtection(password.toCharArray())); return Tuple.of(key.getPrivateKey(), key.getCertificateChain()); } catch (Exception e) { throw new SecurityException(e); } } private String toPem(Object obj, Optional<String> pwd) { StringWriter writer = new StringWriter(); PEMWriter pemWriter = new PEMWriter(writer); try { if (pwd.isPresent()) { pemWriter.writeObject(obj, "DES-EDE3-CBC", pwd.get().toCharArray(), SecureRandom.getInstance("SHA1PRNG")); } else { pemWriter.writeObject(obj); } pemWriter.flush(); } catch (Exception e) { throw new SecurityException(e); } return writer.getBuffer().toString(); } public static String getThumbPrint(Certificate cert) { MessageDigest md = null; try { md = MessageDigest.getInstance("SHA-1"); byte[] der = cert.getEncoded(); md.update(der); byte[] digest = md.digest(); return hexify(digest); } catch (Exception e) { throw new SecurityException(e); } } public static String hexify(byte bytes[]) { char[] hexDigits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; StringBuffer buf = new StringBuffer(bytes.length * 2); for (int i = 0; i < bytes.length; ++i) { buf.append(hexDigits[(bytes[i] & 0xf0) >> 4]); buf.append(hexDigits[bytes[i] & 0x0f]); } return buf.toString(); } public Tuple<KeyPair, X509Certificate> fromPem(String pem, String password) { try { KeyPair key = null; X509Certificate cert = null; PEMReader reader = new PEMReader(new StringReader(pem), () -> password.toCharArray(), "BC"); Object o; while ((o = reader.readObject()) != null) { if (o instanceof X509Certificate) cert = (X509Certificate) o; else if (o instanceof KeyPair) key = (KeyPair) o; } reader.close(); return Tuple.of(key, cert); } catch (IOException e) { throw new SecurityException(e); } } public byte[] toPkcs12(String cert, String password) { Tuple<KeyPair, X509Certificate> t = fromPem(cert, password); byte[] p12 = p12Write(t.getX().getPrivate(), new Certificate[] { t.getY() }, password); return p12; } public PublicKey fromPkcs10(String pkcs10) { PEMReader reader = new PEMReader(new StringReader(pkcs10), () -> new char[0], "BC"); try { PKCS10CertificationRequest request = PKCS10CertificationRequest.class.cast(reader.readObject()); return request.getPublicKey(); } catch (Exception e) { throw new SecurityException(e); } } public Optional<String> issueCert(String licenseKey, String pkcs10, String displayName, String description, String regType) { Optional<LicenseKey> license = Optional.ofNullable(resourceRepository.findLicenseKey(licenseKey)); return license.map(lk -> { Long rId = resourceRepository.createResourceFromLicense(lk.getId(), displayName, description, regType); resourceRepository.attachGroupsFromLk(lk.getId(), rId); String ca = new Scanner(getClass().getResourceAsStream("/cert/uni-subca1Sub.pem")).useDelimiter("\\Z") .next(); String cert = generateCert("cn=" + displayName + "." + rId, "", lk.getCertValidDays(), Optional.of(ca), "", false, Optional.of(pkcs10)); storeCert(rId, cert, ""); historyRepository.histLicenseKey(lk.getUserId(), lk.getId(), rId, licenseKey, displayName, lk.getCertValidDays(), lk.getName(), lk.getActivationsNumber()); return cert; }); } }