Java tutorial
/** * Copyright 2016 Yahoo Inc. * * 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.yahoo.athenz.auth.util; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.math.BigInteger; import java.net.InetAddress; import java.net.UnknownHostException; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SecureRandom; import java.security.Security; import java.security.PrivateKey; import java.security.PublicKey; import java.security.MessageDigest; import java.security.SignatureException; import java.security.cert.CertificateException; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateCrtKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAPublicKeySpec; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.Iterator; import java.util.List; import javax.security.auth.x500.X500Principal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.DERIA5String; import org.bouncycastle.asn1.DEROctetString; import org.bouncycastle.asn1.pkcs.PrivateKeyInfo; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.asn1.x500.style.IETFUtils; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.ExtendedKeyUsage; import org.bouncycastle.asn1.x509.Extension; import org.bouncycastle.asn1.x509.Extensions; import org.bouncycastle.asn1.x509.ExtensionsGenerator; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.KeyPurposeId; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.asn1.pkcs.Attribute; import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers; import org.bouncycastle.asn1.x9.ECNamedCurveTable; import org.bouncycastle.asn1.x9.X9ECParameters; 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.cms.CMSException; import org.bouncycastle.cms.CMSProcessable; import org.bouncycastle.cms.CMSProcessableByteArray; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.SignerInformationStore; import org.bouncycastle.cms.SignerInformationVerifier; import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey; import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey; import org.bouncycastle.jce.X509KeyUsage; import org.bouncycastle.jce.spec.ECParameterSpec; import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.jce.spec.ECPrivateKeySpec; import org.bouncycastle.math.ec.ECMultiplier; import org.bouncycastle.math.ec.ECPoint; import org.bouncycastle.math.ec.FixedPointCombMultiplier; import org.bouncycastle.openssl.PEMException; 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.openssl.jcajce.JceOpenSSLPKCS8DecryptorProviderBuilder; import org.bouncycastle.operator.ContentSigner; import org.bouncycastle.operator.InputDecryptorProvider; import org.bouncycastle.operator.OperatorCreationException; import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; import org.bouncycastle.pkcs.PKCS10CertificationRequest; import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder; import org.bouncycastle.pkcs.PKCS8EncryptedPrivateKeyInfo; import org.bouncycastle.pkcs.PKCSException; import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest; import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequestBuilder; import org.bouncycastle.util.encoders.Base64; import org.bouncycastle.util.io.pem.PemObject; public class Crypto { private static final Logger LOG = LoggerFactory.getLogger(Crypto.class); private static final String RSA = "RSA"; private static final String RSA_SHA1 = "SHA1withRSA"; private static final String RSA_SHA256 = "SHA256withRSA"; private static final String ECDSA = "ECDSA"; private static final String ECDSA_SHA1 = "SHA1withECDSA"; private static final String ECDSA_SHA256 = "SHA256withECDSA"; public static final String SHA1 = "SHA1"; public static final String SHA256 = "SHA256"; private static final String BC_PROVIDER = "BC"; static final SecureRandom RANDOM; static { Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider()); SecureRandom r = null; try { r = SecureRandom.getInstance("NativePRNGNonBlocking"); } catch (NoSuchAlgorithmException nsa) { r = new SecureRandom(); } RANDOM = r; // force seeding. RANDOM.nextBytes(new byte[] { 8 }); } /** * Sign the message with the shared secret using HmacSHA256 * The result is a ybase64 (url safe) string. * @param message the UTF-8 string to be signed * @param sharedSecret the secret to sign with * @return the ybase64 representation of the signature. * @throws CryptoException for any issues with provider/algorithm/signature/key */ public static String hmac(String message, String sharedSecret) throws CryptoException { //this has not been optimized! String method = "HmacSHA256"; byte[] bsig = null; try { javax.crypto.Mac hmac = javax.crypto.Mac.getInstance(method); javax.crypto.spec.SecretKeySpec secretKey = new javax.crypto.spec.SecretKeySpec(utf8Bytes(sharedSecret), method); hmac.init(secretKey); bsig = hmac.doFinal(message.getBytes()); } catch (NoSuchAlgorithmException e) { LOG.error( "hmac: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider."); throw new CryptoException(e); } catch (InvalidKeyException e) { LOG.error("hmac: Caught InvalidKeyException, incorrect key type is being used."); throw new CryptoException(e); } return ybase64(bsig); } static String getSignatureAlgorithm(String keyAlgorithm) throws NoSuchAlgorithmException { return getSignatureAlgorithm(keyAlgorithm, SHA256); } static String getSignatureAlgorithm(String keyAlgorithm, String digestAlgorithm) throws NoSuchAlgorithmException { String signatureAlgorithm = null; switch (keyAlgorithm) { case RSA: if (SHA256.equals(digestAlgorithm)) { signatureAlgorithm = RSA_SHA256; } else if (SHA1.equals(digestAlgorithm)) { signatureAlgorithm = RSA_SHA1; } break; case ECDSA: if (SHA256.equals(digestAlgorithm)) { signatureAlgorithm = ECDSA_SHA256; } else if (SHA1.equals(digestAlgorithm)) { signatureAlgorithm = ECDSA_SHA1; } break; } if (signatureAlgorithm == null) { LOG.error("getSignatureAlgorithm: Unknown key algorithm: " + keyAlgorithm + " digest algorithm: " + digestAlgorithm); throw new NoSuchAlgorithmException(); } if (LOG.isDebugEnabled()) { LOG.debug("Signature Algorithm: " + signatureAlgorithm); } return signatureAlgorithm; } /** * Sign the text with with given digest algorithm and private key. Returns the ybase64 encoding of it. * @param message the message to sign, as a UTF8 string * @param key the private key to sign with * @param digestAlgorithm supported values SHA1 and SHA256 * @return the ybase64 encoded signature for the data * @throws CryptoException for any issues with provider/algorithm/signature/key */ public static String sign(String message, PrivateKey key, String digestAlgorithm) throws CryptoException { try { byte[] sig = null; String signatureAlgorithm = getSignatureAlgorithm(key.getAlgorithm(), digestAlgorithm); java.security.Signature signer = java.security.Signature.getInstance(signatureAlgorithm, BC_PROVIDER); signer.initSign(key); signer.update(utf8Bytes(message)); sig = signer.sign(); return ybase64(sig); } catch (NoSuchProviderException e) { LOG.error("sign: Caught NoSuchProviderException, check to make sure the provider is loaded correctly."); throw new CryptoException(e); } catch (NoSuchAlgorithmException e) { LOG.error( "sign: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider."); throw new CryptoException(e); } catch (SignatureException e) { LOG.error("sign: Caught SignatureException."); throw new CryptoException(e); } catch (InvalidKeyException e) { LOG.error("sign: Caught InvalidKeyException, incorrect key type is being used."); throw new CryptoException(e); } } /** * Sign the text with with SHA-256 and the private key. Returns the ybase64 encoding of it. * @param message the message to sign, as a UTF8 string * @param key the private key to sign with * @return the ybase64 encoded signature for the data * @throws CryptoException for any issues with provider/algorithm/signature/key */ public static String sign(String message, PrivateKey key) throws CryptoException { return sign(message, key, SHA256); } /** * Verify the signed data with given digest algorithm and the private key against the ybase64 encoded signature. * @param message the message to sign, as a UTF8 string * @param key the public key corresponding to the signing key * @param signature the ybase64 encoded signature for the data * @param digestAlgorithm supported values SHA1 and SHA256 * @return true if the message was indeed signed by the signature. * @throws CryptoException for any issues with provider/algorithm/signature/key */ public static boolean verify(String message, PublicKey key, String signature, String digestAlgorithm) throws CryptoException { try { byte[] sig = ybase64Decode(signature); String signatureAlgorithm = getSignatureAlgorithm(key.getAlgorithm(), digestAlgorithm); java.security.Signature signer = java.security.Signature.getInstance(signatureAlgorithm, BC_PROVIDER); signer.initVerify(key); signer.update(utf8Bytes(message)); return signer.verify(sig); } catch (NoSuchProviderException e) { LOG.error( "verify: Caught NoSuchProviderException, check to make sure the provider is loaded correctly."); throw new CryptoException(e); } catch (NoSuchAlgorithmException e) { LOG.error( "verify: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider."); throw new CryptoException(e); } catch (SignatureException e) { LOG.error("verify: Caught SignatureException."); throw new CryptoException(e); } catch (InvalidKeyException e) { LOG.error("verify: Caught InvalidKeyException, invalid key type is being used."); throw new CryptoException(e); } } /** * Verify the signed data with SHA-256 and private key against the ybase64 encoded signature. * @param message the message to sign, as a UTF8 string * @param key the public key corresponding to the signing key * @param signature the ybase64 encoded signature for the data * @return true if the message was indeed signed by the signature. * @throws CryptoException for any issues with provider/algorithm/signature/key */ public static boolean verify(String message, PublicKey key, String signature) throws CryptoException { return verify(message, key, signature, SHA256); } static String utf8String(byte[] b) { return new String(b, StandardCharsets.UTF_8); } static byte[] utf8Bytes(String s) { return s.getBytes(StandardCharsets.UTF_8); } public static byte[] sha256(byte[] data) throws CryptoException { MessageDigest sha256; try { sha256 = MessageDigest.getInstance("SHA-256"); } catch (NoSuchAlgorithmException e) { LOG.error( "sha256: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider."); throw new CryptoException(e); } return sha256.digest(data); } public static byte[] sha256(String text) throws CryptoException { return sha256(utf8Bytes(text)); } /** * ybase64 is url-safe base64 encoding, using Y's unique convention. * The industry standard urlsafe solution is ("+/=" => "-_."). * The Y! convention is ("+/=" => "._-"). * @param data the data to encode * @return the ybase64-encoded data as a String */ public static String ybase64(byte[] data) { return utf8String(YBase64.encode(data)); } /** * ybase64 is url-safe base64 encoding, using Y's unique convention. * The industry standard urlsafe solution is ("+/=" => "-_."). * The Y! convention is ("+/=" => "._-"). * @param b64 the ybase64-encoded data * @return the decoded data */ public static byte[] ybase64Decode(String b64) { return YBase64.decode(utf8Bytes(b64)); } public static String ybase64DecodeString(String b64) { return utf8String(ybase64Decode(b64)); } public static String ybase64EncodeString(String str) { return utf8String(YBase64.encode(utf8Bytes(str))); } public static X509Certificate loadX509Certificate(File certFile) throws CryptoException { try (FileReader fileReader = new FileReader(certFile)) { return loadX509Certificate(fileReader); } catch (FileNotFoundException e) { LOG.error( "loadX509Certificate: Caught FileNotFoundException while attempting to load certificate for file: " + certFile.getAbsolutePath()); throw new CryptoException(e); } catch (IOException e) { LOG.error("loadX509Certificate: Caught IOException while attempting to load certificate for file: " + certFile.getAbsolutePath()); throw new CryptoException(e); } } public static X509Certificate loadX509Certificate(String pemEncoded) throws CryptoException { return Crypto.loadX509Certificate(new StringReader(pemEncoded)); } public static X509Certificate loadX509Certificate(Reader reader) throws CryptoException { try (PEMParser pemParser = new PEMParser(reader)) { Object pemObj = pemParser.readObject(); if (pemObj instanceof X509Certificate) { return (X509Certificate) pemObj; } else if (pemObj instanceof X509CertificateHolder) { try { return new JcaX509CertificateConverter().setProvider(BC_PROVIDER) .getCertificate((X509CertificateHolder) pemObj); } catch (CertificateException ex) { LOG.error("loadX509Certificate: Caught CertificateException, unable to parse X509 certficate: " + ex.getMessage()); throw new CryptoException(ex); } } } catch (IOException ex) { LOG.error( "loadX509Certificate: Caught IOException, unable to parse X509 certficate: " + ex.getMessage()); throw new CryptoException(ex); } return null; } public static PublicKey loadPublicKey(String pemEncoded) throws CryptoException { return Crypto.loadPublicKey(new StringReader(pemEncoded)); } public static PublicKey loadPublicKey(Reader r) throws CryptoException { try (org.bouncycastle.openssl.PEMParser pemReader = new org.bouncycastle.openssl.PEMParser(r)) { PublicKey pubKey = null; Object pemObj = pemReader.readObject(); JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter(); SubjectPublicKeyInfo keyInfo = null; X9ECParameters ecParam = null; if (pemObj instanceof ASN1ObjectIdentifier) { // make sure this is EC Parameter we're handling. In which case // we'll store it and read the next object which should be our // EC Public Key ASN1ObjectIdentifier ecOID = (ASN1ObjectIdentifier) pemObj; ecParam = ECNamedCurveTable.getByOID(ecOID); if (ecParam == null) { throw new PEMException("Unable to find EC Parameter for the given curve oid: " + ((ASN1ObjectIdentifier) pemObj).getId()); } pemObj = pemReader.readObject(); } else if (pemObj instanceof X9ECParameters) { ecParam = (X9ECParameters) pemObj; pemObj = pemReader.readObject(); } if (pemObj instanceof org.bouncycastle.cert.X509CertificateHolder) { keyInfo = ((org.bouncycastle.cert.X509CertificateHolder) pemObj).getSubjectPublicKeyInfo(); } else { keyInfo = (SubjectPublicKeyInfo) pemObj; } pubKey = pemConverter.getPublicKey(keyInfo); if (ecParam != null && ECDSA.equals(pubKey.getAlgorithm())) { ECParameterSpec ecSpec = new ECParameterSpec(ecParam.getCurve(), ecParam.getG(), ecParam.getN(), ecParam.getH(), ecParam.getSeed()); KeyFactory keyFactory = KeyFactory.getInstance(ECDSA, BC_PROVIDER); ECPublicKeySpec keySpec = new ECPublicKeySpec(((BCECPublicKey) pubKey).getQ(), ecSpec); pubKey = (PublicKey) keyFactory.generatePublic(keySpec); } return pubKey; } catch (PEMException e) { throw new CryptoException(e); } catch (NoSuchProviderException e) { LOG.error( "loadPublicKey: Caught NoSuchProviderException, check to make sure the provider is loaded correctly."); throw new CryptoException(e); } catch (NoSuchAlgorithmException e) { LOG.error( "loadPublicKey: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider."); throw new CryptoException(e); } catch (InvalidKeySpecException e) { LOG.error("loadPublicKey: Caught InvalidKeySpecException, invalid key spec is being used."); throw new CryptoException("InvalidKeySpecException"); } catch (IOException e) { throw new CryptoException(e); } } public static PublicKey loadPublicKey(File f) throws CryptoException { try (FileReader fileReader = new FileReader(f)) { return loadPublicKey(fileReader); } catch (FileNotFoundException e) { LOG.error("loadPublicKey: Caught FileNotFoundException while attempting to load public key for file: " + f.getAbsolutePath()); throw new CryptoException(e); } catch (IOException e) { LOG.error("loadPublicKey: Caught IOException while attempting to load public key for file: " + f.getAbsolutePath()); throw new CryptoException(e); } } public static PublicKey extractPublicKey(PrivateKey privateKey) throws CryptoException { // we only support RSA and ECDSA private keys PublicKey publicKey = null; switch (privateKey.getAlgorithm()) { case RSA: try { KeyFactory kf = KeyFactory.getInstance(RSA, BC_PROVIDER); RSAPrivateCrtKey rsaCrtKey = (RSAPrivateCrtKey) privateKey; RSAPublicKeySpec keySpec = new RSAPublicKeySpec(rsaCrtKey.getModulus(), rsaCrtKey.getPublicExponent()); publicKey = kf.generatePublic(keySpec); } catch (NoSuchProviderException ex) { LOG.error("extractPublicKey: RSA - Caught NoSuchProviderException exception: " + ex.getMessage()); throw new CryptoException(ex); } catch (NoSuchAlgorithmException ex) { LOG.error("extractPublicKey: RSA - Caught NoSuchAlgorithmException exception: " + ex.getMessage()); throw new CryptoException(ex); } catch (InvalidKeySpecException ex) { LOG.error("extractPublicKey: RSA - Caught InvalidKeySpecException exception: " + ex.getMessage()); throw new CryptoException(ex); } break; case ECDSA: try { KeyFactory kf = KeyFactory.getInstance(ECDSA, BC_PROVIDER); BCECPrivateKey ecPrivKey = (BCECPrivateKey) privateKey; ECMultiplier ecMultiplier = new FixedPointCombMultiplier(); ECParameterSpec ecParamSpec = (ECParameterSpec) ecPrivKey.getParameters(); ECPoint ecPointQ = ecMultiplier.multiply(ecParamSpec.getG(), ecPrivKey.getD()); ECPublicKeySpec keySpec = new ECPublicKeySpec(ecPointQ, ecParamSpec); publicKey = kf.generatePublic(keySpec); } catch (NoSuchProviderException ex) { LOG.error("extractPublicKey: ECDSA - Caught NoSuchProviderException exception: " + ex.getMessage()); throw new CryptoException(ex); } catch (NoSuchAlgorithmException ex) { LOG.error( "extractPublicKey: ECDSA - Caught NoSuchAlgorithmException exception: " + ex.getMessage()); throw new CryptoException(ex); } catch (InvalidKeySpecException ex) { LOG.error("extractPublicKey: ECDSA - Caught InvalidKeySpecException exception: " + ex.getMessage()); throw new CryptoException(ex); } break; default: String msg = "Unsupported Key Algorithm: " + privateKey.getAlgorithm(); LOG.error("extractPublicKey: " + msg); throw new CryptoException(msg); } return publicKey; } public static PrivateKey loadPrivateKey(String pemEncoded) throws CryptoException { return Crypto.loadPrivateKey(new StringReader(pemEncoded), null); } public static PrivateKey loadPrivateKey(Reader reader) throws CryptoException { return Crypto.loadPrivateKey(reader, null); } public static PrivateKey loadPrivateKey(File file) throws CryptoException { return Crypto.loadPrivateKey(file, null); } public static PrivateKey loadPrivateKey(File file, String pwd) throws CryptoException { try (java.io.FileReader fileReader = new java.io.FileReader(file)) { return loadPrivateKey(fileReader, pwd); } catch (FileNotFoundException e) { LOG.error("loadPrivateKey: Caught FileNotFoundException while attempting to load private key for file: " + file.getAbsolutePath()); throw new CryptoException(e); } catch (IOException e) { LOG.error("loadPrivateKey: Caught IOException while attempting to load private key for file: " + file.getAbsolutePath()); throw new CryptoException(e); } } public static PrivateKey loadPrivateKey(String pemEncoded, String pwd) throws CryptoException { return Crypto.loadPrivateKey(new StringReader(pemEncoded), pwd); } public static PrivateKey loadPrivateKey(Reader reader, String pwd) throws CryptoException { try (PEMParser pemReader = new PEMParser(reader)) { PrivateKey privKey = null; X9ECParameters ecParam = null; Object pemObj = pemReader.readObject(); if (pemObj instanceof ASN1ObjectIdentifier) { // make sure this is EC Parameter we're handling. In which case // we'll store it and read the next object which should be our // EC Private Key ASN1ObjectIdentifier ecOID = (ASN1ObjectIdentifier) pemObj; ecParam = ECNamedCurveTable.getByOID(ecOID); if (ecParam == null) { throw new PEMException("Unable to find EC Parameter for the given curve oid: " + ((ASN1ObjectIdentifier) pemObj).getId()); } pemObj = pemReader.readObject(); } else if (pemObj instanceof X9ECParameters) { ecParam = (X9ECParameters) pemObj; pemObj = pemReader.readObject(); } if (pemObj instanceof PEMKeyPair) { PrivateKeyInfo pKeyInfo = ((PEMKeyPair) pemObj).getPrivateKeyInfo(); JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter(); privKey = pemConverter.getPrivateKey(pKeyInfo); } else if (pemObj instanceof PKCS8EncryptedPrivateKeyInfo) { PKCS8EncryptedPrivateKeyInfo pKeyInfo = (PKCS8EncryptedPrivateKeyInfo) pemObj; if (pwd == null) { throw new CryptoException("No password specified to decrypt encrypted private key"); } // Decrypt the private key with the specified password InputDecryptorProvider pkcs8Prov = new JceOpenSSLPKCS8DecryptorProviderBuilder() .setProvider(BC_PROVIDER).build(pwd.toCharArray()); PrivateKeyInfo privateKeyInfo = pKeyInfo.decryptPrivateKeyInfo(pkcs8Prov); JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter(); privKey = pemConverter.getPrivateKey(privateKeyInfo); } // if our private key is EC type and we have parameters specified // then we need to set it accordingly if (ecParam != null && ECDSA.equals(privKey.getAlgorithm())) { ECParameterSpec ecSpec = new ECParameterSpec(ecParam.getCurve(), ecParam.getG(), ecParam.getN(), ecParam.getH(), ecParam.getSeed()); KeyFactory keyFactory = KeyFactory.getInstance(ECDSA, BC_PROVIDER); ECPrivateKeySpec keySpec = new ECPrivateKeySpec(((BCECPrivateKey) privKey).getS(), ecSpec); privKey = (PrivateKey) keyFactory.generatePrivate(keySpec); } return privKey; } catch (PEMException e) { LOG.error("loadPrivateKey: Caught PEMException, problem with format of key detected."); throw new CryptoException(e); } catch (NoSuchProviderException e) { LOG.error( "loadPrivateKey: Caught NoSuchProviderException, check to make sure the provider is loaded correctly."); throw new CryptoException(e); } catch (NoSuchAlgorithmException e) { LOG.error( "loadPrivateKey: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider."); throw new CryptoException(e); } catch (InvalidKeySpecException e) { LOG.error("loadPrivateKey: Caught InvalidKeySpecException, invalid key spec is being used."); throw new CryptoException(e); } catch (OperatorCreationException e) { LOG.error( "loadPrivateKey: Caught OperatorCreationException when creating JceOpenSSLPKCS8DecryptorProviderBuilder."); throw new CryptoException(e); } catch (PKCSException e) { LOG.error("loadPrivateKey: Caught PKCSException when decrypting private key."); throw new CryptoException(e); } catch (IOException e) { LOG.error("loadPrivateKey: Caught IOException, while trying to read key."); throw new CryptoException(e); } } /** * Generate a RSA private with the given number of bits * @param bits numbers of bits * @return PrivateKey private key * @throws CryptoException */ public static PrivateKey generateRSAPrivateKey(int bits) throws CryptoException { KeyPairGenerator keyGen; try { keyGen = KeyPairGenerator.getInstance(RSA); } catch (NoSuchAlgorithmException e) { LOG.error( "generatePrivateKey: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider."); throw new CryptoException(e); } keyGen.initialize(bits); return keyGen.genKeyPair().getPrivate(); } public static String randomSalt() { long v = RANDOM.nextLong(); return Long.toHexString(v); } public static String encodedFile(File f) { try (FileInputStream in = new FileInputStream(f)) { byte[] buf = new byte[(int) f.length()]; in.read(buf); return ybase64(buf); } catch (FileNotFoundException e) { LOG.error("encodedFile: Caught FileNotFoundException while attempting to read encoded file: " + f.getAbsolutePath()); throw new RuntimeException(e); } catch (IOException e) { LOG.error("encodedFile: Caught IOException while attempting to read encoded file: " + f.getAbsolutePath()); throw new RuntimeException(e); } } public static String encodedFile(FileInputStream is) { try { byte[] buf = new byte[4096]; int readBytes = 0; String contents = null; while ((readBytes = is.read(buf)) > 0) { if (contents == null) { contents = new String(buf, 0, readBytes - 1); } else { contents = contents.concat(new String(buf, 0, readBytes - 1)); } } return ybase64(utf8Bytes(contents)); } catch (IOException e) { throw new RuntimeException(e); } } public static PKCS10CertificationRequest getPKCS10CertRequest(String csr) { if (csr == null || csr.isEmpty()) { LOG.error("getPKCS10CertRequest: CSR is null or empty"); throw new CryptoException("CSR is null or empty"); } try { Reader csrReader = new StringReader(csr); try (PEMParser pemParser = new PEMParser(csrReader)) { Object pemObj = pemParser.readObject(); if (pemObj instanceof PKCS10CertificationRequest) { return (PKCS10CertificationRequest) pemObj; } } } catch (IOException ex) { LOG.error("getPKCS10CertRequest: unable to parse csr: " + ex.getMessage()); throw new CryptoException(ex); } return null; } public static String extractX509CSRCommonName(PKCS10CertificationRequest certReq) { String cn = null; X500Name x500name = certReq.getSubject(); RDN cnRdn = x500name.getRDNs(BCStyle.CN)[0]; if (cnRdn != null) { cn = IETFUtils.valueToString(cnRdn.getFirst().getValue()); } return cn; } public static String extractX509CSREmail(PKCS10CertificationRequest certReq) { String rfc822 = null; Attribute[] attributes = certReq.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest); for (Attribute attribute : attributes) { for (ASN1Encodable value : attribute.getAttributeValues()) { Extensions extensions = Extensions.getInstance(value); GeneralNames gns = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName); for (GeneralName name : gns.getNames()) { if (name.getTagNo() == GeneralName.rfc822Name) { rfc822 = (((DERIA5String) name.getName()).getString()); break; } } } } return rfc822; } public static List<String> extractX509CSRDnsNames(PKCS10CertificationRequest certReq) { List<String> dnsNames = new ArrayList<>(); Attribute[] attributes = certReq.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest); for (Attribute attribute : attributes) { for (ASN1Encodable value : attribute.getAttributeValues()) { Extensions extensions = Extensions.getInstance(value); GeneralNames gns = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName); for (GeneralName name : gns.getNames()) { if (name.getTagNo() == GeneralName.dNSName) { dnsNames.add(((DERIA5String) name.getName()).getString()); } } } } return dnsNames; } public static List<String> extractX509CSRIPAddresses(PKCS10CertificationRequest certReq) { List<String> ipAddresses = new ArrayList<>(); Attribute[] attributes = certReq.getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest); for (Attribute attribute : attributes) { for (ASN1Encodable value : attribute.getAttributeValues()) { Extensions extensions = Extensions.getInstance(value); GeneralNames gns = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName); for (GeneralName name : gns.getNames()) { if (name.getTagNo() == GeneralName.iPAddress) { try { InetAddress addr = InetAddress .getByAddress(((DEROctetString) name.getName()).getOctets()); ipAddresses.add(addr.getHostAddress()); } catch (UnknownHostException e) { } } } } } return ipAddresses; } public static String extractX509CSRPublicKey(PKCS10CertificationRequest certReq) { JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter(); PublicKey publicKey = null; try { publicKey = pemConverter.getPublicKey(certReq.getSubjectPublicKeyInfo()); } catch (PEMException ex) { LOG.error("extractX509CSRPublicKey: unable to get public key: {}", ex.getMessage()); return null; } return convertToPEMFormat(publicKey); } public static String generateX509CSR(PrivateKey privateKey, String x500Principal, GeneralName[] sanArray) throws OperatorCreationException, IOException { final PublicKey publicKey = extractPublicKey(privateKey); if (publicKey == null) { throw new CryptoException("Unable to extract public key from private key"); } return generateX509CSR(privateKey, publicKey, x500Principal, sanArray); } public static String generateX509CSR(PrivateKey privateKey, PublicKey publicKey, String x500Principal, GeneralName[] sanArray) throws OperatorCreationException, IOException { // Create Distinguished Name X500Principal subject = new X500Principal(x500Principal); // Create ContentSigner JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder(Crypto.RSA_SHA256); ContentSigner signer = csBuilder.build(privateKey); // Create the CSR PKCS10CertificationRequestBuilder p10Builder = new JcaPKCS10CertificationRequestBuilder(subject, publicKey); // Add SubjectAlternativeNames (SAN) if specified if (sanArray != null) { ExtensionsGenerator extGen = new ExtensionsGenerator(); GeneralNames subjectAltNames = new GeneralNames(sanArray); extGen.addExtension(Extension.subjectAlternativeName, false, subjectAltNames); p10Builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest, extGen.generate()); } PKCS10CertificationRequest csr = p10Builder.build(signer); // write to openssl PEM format PemObject pemObject = new PemObject("CERTIFICATE REQUEST", csr.getEncoded()); StringWriter strWriter; try (JcaPEMWriter pemWriter = new JcaPEMWriter(strWriter = new StringWriter())) { pemWriter.writeObject(pemObject); } return strWriter.toString(); } public static String extractX509CertCommonName(X509Certificate x509Cert) { // in case there are multiple CNs, we're only looking at the first one String cn = null; String principalName = x509Cert.getSubjectX500Principal().getName(); if (principalName != null && !principalName.isEmpty()) { X500Name x500name = new X500Name(principalName); RDN cnRdn = x500name.getRDNs(BCStyle.CN)[0]; if (cnRdn != null) { cn = IETFUtils.valueToString(cnRdn.getFirst().getValue()); } } return cn; } public static List<String> extractX509CertDnsNames(X509Certificate x509Cert) { Collection<List<?>> altNames = null; try { altNames = x509Cert.getSubjectAlternativeNames(); } catch (CertificateParsingException ex) { LOG.error("extractX509IPAddresses: Caught CertificateParsingException when parsing certificate: " + ex.getMessage()); } if (altNames == null) { return Collections.emptyList(); } List<String> dnsNames = new ArrayList<>(); for (@SuppressWarnings("rawtypes") List item : altNames) { Integer type = (Integer) item.get(0); // GeneralName ::= CHOICE { // otherName [0] OtherName, // rfc822Name [1] IA5String, // dNSName [2] IA5String, // x400Address [3] ORAddress, // directoryName [4] Name, // ediPartyName [5] EDIPartyName, // uniformResourceIdentifier [6] IA5String, // iPAddress [7] OCTET STRING, // registeredID [8] OBJECT IDENTIFIER} if (type == GeneralName.dNSName) { dnsNames.add((String) item.get(1)); } } return dnsNames; } public static List<String> extractX509CertEmails(X509Certificate x509Cert) { Collection<List<?>> altNames = null; try { altNames = x509Cert.getSubjectAlternativeNames(); } catch (CertificateParsingException ex) { LOG.error("extractX509IPAddresses: Caught CertificateParsingException when parsing certificate: " + ex.getMessage()); } if (altNames == null) { return Collections.emptyList(); } List<String> emails = new ArrayList<>(); for (@SuppressWarnings("rawtypes") List item : altNames) { Integer type = (Integer) item.get(0); // GeneralName ::= CHOICE { // otherName [0] OtherName, // rfc822Name [1] IA5String, // dNSName [2] IA5String, // x400Address [3] ORAddress, // directoryName [4] Name, // ediPartyName [5] EDIPartyName, // uniformResourceIdentifier [6] IA5String, // iPAddress [7] OCTET STRING, // registeredID [8] OBJECT IDENTIFIER} if (type == GeneralName.rfc822Name) { emails.add((String) item.get(1)); } } return emails; } public static List<String> extractX509CertIPAddresses(X509Certificate x509Cert) { Collection<List<?>> altNames = null; try { altNames = x509Cert.getSubjectAlternativeNames(); } catch (CertificateParsingException ex) { LOG.error("extractX509IPAddresses: Caught CertificateParsingException when parsing certificate: " + ex.getMessage()); } if (altNames == null) { return Collections.emptyList(); } List<String> ipAddresses = new ArrayList<>(); for (@SuppressWarnings("rawtypes") List item : altNames) { Integer type = (Integer) item.get(0); // GeneralName ::= CHOICE { // otherName [0] OtherName, // rfc822Name [1] IA5String, // dNSName [2] IA5String, // x400Address [3] ORAddress, // directoryName [4] Name, // ediPartyName [5] EDIPartyName, // uniformResourceIdentifier [6] IA5String, // iPAddress [7] OCTET STRING, // registeredID [8] OBJECT IDENTIFIER} if (type == GeneralName.iPAddress) { ipAddresses.add((String) item.get(1)); } } return ipAddresses; } public static String extractX509CertPublicKey(X509Certificate x509Cert) { PublicKey publicKey = x509Cert.getPublicKey(); if (publicKey == null) { LOG.error("extractX509CertPublicKey: unable to get public key"); return null; } return convertToPEMFormat(publicKey); } public static X509Certificate generateX509Certificate(PKCS10CertificationRequest certReq, PrivateKey caPrivateKey, X509Certificate caCertificate, int validityTimeout, boolean basicConstraints) { return generateX509Certificate(certReq, caPrivateKey, X500Name.getInstance(caCertificate.getSubjectX500Principal().getEncoded()), validityTimeout, basicConstraints); } public static X509Certificate generateX509Certificate(PKCS10CertificationRequest certReq, PrivateKey caPrivateKey, X500Name issuer, int validityTimeout, boolean basicConstraints) { // set validity for the given number of minutes from now Date notBefore = new Date(); Calendar cal = Calendar.getInstance(); cal.setTime(notBefore); cal.add(Calendar.MINUTE, validityTimeout); Date notAfter = cal.getTime(); // Generate self-signed certificate X509Certificate cert = null; try { JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest = new JcaPKCS10CertificationRequest( certReq); PublicKey publicKey = jcaPKCS10CertificationRequest.getPublicKey(); X509v3CertificateBuilder caBuilder = new JcaX509v3CertificateBuilder(issuer, BigInteger.valueOf(System.currentTimeMillis()), notBefore, notAfter, certReq.getSubject(), publicKey) .addExtension(Extension.basicConstraints, false, new BasicConstraints(basicConstraints)) .addExtension(Extension.keyUsage, true, new X509KeyUsage(X509KeyUsage.digitalSignature | X509KeyUsage.keyEncipherment)) .addExtension(Extension.extendedKeyUsage, true, new ExtendedKeyUsage(new KeyPurposeId[] { KeyPurposeId.id_kp_clientAuth, KeyPurposeId.id_kp_serverAuth })); // see if we have the dns/rfc822/ip address extensions specified in the csr ArrayList<GeneralName> altNames = new ArrayList<>(); Attribute[] certAttributes = jcaPKCS10CertificationRequest .getAttributes(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest); if (certAttributes != null && certAttributes.length > 0) { for (Attribute attribute : certAttributes) { Extensions extensions = Extensions.getInstance(attribute.getAttrValues().getObjectAt(0)); GeneralNames gns = GeneralNames.fromExtensions(extensions, Extension.subjectAlternativeName); if (gns == null) { continue; } GeneralName[] names = gns.getNames(); for (int i = 0; i < names.length; i++) { switch (names[i].getTagNo()) { case GeneralName.dNSName: case GeneralName.iPAddress: case GeneralName.rfc822Name: altNames.add(names[i]); break; } } } if (!altNames.isEmpty()) { caBuilder.addExtension(Extension.subjectAlternativeName, false, new GeneralNames(altNames.toArray(new GeneralName[altNames.size()]))); } } String signatureAlgorithm = getSignatureAlgorithm(caPrivateKey.getAlgorithm(), SHA256); ContentSigner caSigner = new JcaContentSignerBuilder(signatureAlgorithm).setProvider(BC_PROVIDER) .build(caPrivateKey); JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC_PROVIDER); cert = converter.getCertificate(caBuilder.build(caSigner)); } catch (CertificateException ex) { LOG.error("generateX509Certificate: Caught CertificateException when generating certificate: " + ex.getMessage()); throw new CryptoException(ex); } catch (OperatorCreationException ex) { LOG.error( "generateX509Certificate: Caught OperatorCreationException when creating JcaContentSignerBuilder: " + ex.getMessage()); throw new CryptoException(ex); } catch (InvalidKeyException ex) { LOG.error("generateX509Certificate: Caught InvalidKeySpecException, invalid key spec is being used: " + ex.getMessage()); throw new CryptoException(ex); } catch (NoSuchAlgorithmException ex) { LOG.error( "generateX509Certificate: Caught NoSuchAlgorithmException, check to make sure the algorithm is supported by the provider: " + ex.getMessage()); throw new CryptoException(ex); } catch (Exception ex) { LOG.error("generateX509Certificate: unable to generate X509 Certificate: " + ex.getMessage()); throw new CryptoException("Unable to generate X509 Certificate"); } return cert; } public static boolean validatePKCS7Signature(String data, String signature, PublicKey publicKey) { try { SignerInformationStore signerStore = null; try (InputStream sigIs = new ByteArrayInputStream( Base64.decode(signature.getBytes(StandardCharsets.UTF_8)))) { CMSProcessable content = new CMSProcessableByteArray(data.getBytes(StandardCharsets.UTF_8)); CMSSignedData signedData = new CMSSignedData(content, sigIs); signerStore = signedData.getSignerInfos(); } Collection<SignerInformation> signers = signerStore.getSigners(); Iterator<SignerInformation> it = signers.iterator(); SignerInformationVerifier infoVerifier = new JcaSimpleSignerInfoVerifierBuilder() .setProvider(BC_PROVIDER).build(publicKey); while (it.hasNext()) { SignerInformation signerInfo = (SignerInformation) it.next(); if (signerInfo.verify(infoVerifier)) { return true; } } } catch (CMSException ex) { LOG.error("validatePKCS7Signature: unable to initialize CMSSignedData object: " + ex.getMessage()); throw new CryptoException(ex); } catch (OperatorCreationException ex) { LOG.error( "validatePKCS7Signature: Caught OperatorCreationException when creating JcaSimpleSignerInfoVerifierBuilder: " + ex.getMessage()); throw new CryptoException(ex); } catch (IOException ex) { LOG.error("validatePKCS7Signature: Caught IOException when closing InputStream: " + ex.getMessage()); throw new CryptoException(ex); } catch (Exception ex) { LOG.error("validatePKCS7Signature: unable to validate signature: " + ex.getMessage()); throw new CryptoException(ex.getMessage()); } return false; } public static String convertToPEMFormat(Object obj) { StringWriter writer = new StringWriter(); try { try (JcaPEMWriter pemWriter = new JcaPEMWriter(writer)) { pemWriter.writeObject(obj); pemWriter.flush(); pemWriter.close(); } } catch (IOException ex) { LOG.error("convertToPEMFormat: unable to convert object to PEM: " + ex.getMessage()); return null; } return writer.toString(); } public static void main(String[] args) throws CryptoException { if (args.length >= 2) { String op = args[0]; if ("sign".equals(op)) { if (args.length == 3) { String sig = Crypto.sign(args[1], Crypto.loadPrivateKey(new File(args[2]))); System.out.println(sig); System.exit(0); } } else if ("verify".equals(op)) { if (args.length == 4) { if (Crypto.verify(args[1], Crypto.loadPublicKey(new File(args[2])), args[3])) { System.out.println("Verified."); } else { System.out.println("NOT VERIFIED"); } System.exit(0); } } else if ("public".equals(op)) { if (args.length == 2) { String pub = encodedFile(new File(args[1])); Crypto.loadPublicKey(ybase64DecodeString(pub)); //throws if something is wrong System.out.println(pub); System.exit(0); } } else if ("private".equals(op)) { if (args.length == 2) { try { String priv = encodedFile(new File(args[1])); Crypto.loadPrivateKey(ybase64DecodeString(priv)); //throws if something is wrong System.out.println(priv); System.exit(0); } catch (Exception e) { System.out.println("*** " + e.getMessage()); System.exit(1); } } } } System.out.println("usage: r Crypto private privateKeyFile"); System.out.println("usage: r Crypto public publicKeyFile"); System.out.println("usage: r Crypto sign msg privateKeyFile"); System.out.println("usage: r Crypto verify msg privateKeyFile signature"); System.exit(1); } }