【问题标题】:Generating X509Certificate using bouncycastle X509v3CertificateBuilder使用 bouncycastle X509v3CertificateBuilder 生成 X509Certificate
【发布时间】:2012-04-13 20:23:31
【问题描述】:

我正在尝试移植 JXTA 以在 App Engine 上运行。鉴于 App Engine 尚不支持 BouncyCastle“BC”提供程序,我必须移植现有的 JXTA 代码以使用列入白名单的类生成 X509Certificate。我对加密的了解很少,我不确定我想要完成的事情是否可能。这是来自 JXTA 项目的 PSEUtils.java 的原始代码:

PSEUtils.java

有一个帮助类,其中包含 java.security.cert.X509Certificate

public static class IssuerInfo {
    public X509Certificate cert; // subject Cert
    public PrivateKey subjectPkey; // subject private key
    public X509Certificate issuer; // issuer Cert
    public PrivateKey issuerPkey; // issuer private key
}

在方法中:

public static IssuerInfo genCert(X500Principal subject, KeyPair keypair, IssuerInfo issuerinfo)

我将主题传递为:

new X500Principal("CN="+useCN)

keypair as(来自原始代码):

KeyPairGenerator g = KeyPairGenerator.getInstance("RSA");
g.initialize(1024, UTILS.srng);
KeyPair keypair = g.generateKeyPair();

和 jxta 编码的 IssuerInfo。

现在,由于我无法引入 bouncycastle.jce 包,因此我不得不删除 JXTA 使用的 X509Principal 和 X509V3CertificateGenerator 代码,并尝试将其替换为符合 GAE 限制的实现。以下是我目前使用 org.bouncycastle.X509.X509v3CertificateBuilder 的 genCert 方法。

SubjectPublicKeyInfo subPubKeyInfo =  SubjectPublicKeyInfo.getInstance(keypair.getPublic().getEncoded());

X509v3CertificateBuilder v3CertGen = new X509v3CertificateBuilder(
     new X500Name(issuer.getName()), 
     BigInteger.ONE, 
     today, until, 
     new X500Name(subject.getName()), 
     subPubKeyInfo);

问题是我无法让keypair.getPublic().getEncoded() 使用SubjectPublicKeyInfo.getInstance() 方法。抛出 java.lang.IllegalArgumentException:工厂中的未知对象:[B

在检查时似乎已填充了公钥:

Sun RSA public key, 1024 bits
  modulus: 117521430893506212334140912845641570591161279468597426442875306202350445904550279678434051874985419676760802566018092318362676224355315431299979507080364677679613392086245588766565617009250512996843008784370448997729071786062596049780632058501646041736216482596596901215941577208285499619376322050871534546271
  public exponent: 65537

我找到了以下 SO 链接,它演示了此代码的工作原理:

Sign CSR using Bouncy Castle

下面是我转换 genCert 的尝试,但由于某种原因,我无法通过编码的公钥创建 SubjectPublicKeyInfo?

非常感谢任何帮助。

public static IssuerInfo genCert(X500Principal subject, KeyPair keypair, IssuerInfo issuerinfo)  {
    IssuerInfo info = new IssuerInfo();
    try {
        // set up issuer
        PrivateKey signer;
        X500Principal issuer;

        if (null == issuerinfo) { // self-signed root cert
            signer = keypair.getPrivate();
            issuer = new X500Principal(subject.getEncoded());
        } else { // issuer signed service sert
            signer = issuerinfo.subjectPkey;
            X500Principal issuer_subject = issuerinfo.cert.getSubjectX500Principal();
            issuer = new X500Principal(issuer_subject.getEncoded());
        }

        // set validity 10 years from today
        Date today = new Date();
        Calendar cal = Calendar.getInstance();

        cal.setTime(today);
        cal.add(Calendar.YEAR, 10);
        Date until = cal.getTime();

        SubjectPublicKeyInfo subPubKeyInfo =  SubjectPublicKeyInfo.getInstance(keypair.getPublic().getEncoded());

    //**Can't get here so i'm not sure if the rest of this works?**

        AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
        AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);

        RSAPrivateCrtKeyParameters cps = (RSAPrivateCrtKeyParameters) keypair.getPrivate();
        ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(cps);            

     X509CertificateHolder certHolder = v3CertGen.build(sigGen);

     CertificateFactory cf = CertificateFactory.getInstance("X.509");

     // Read user Certificate
     InputStream is1 = new ByteArrayInputStream(certHolder.getEncoded());
     X509Certificate eeCert = (X509Certificate) cf.generateCertificate(is1);
     is1.close();

【问题讨论】:

    标签: google-app-engine x509certificate bouncycastle x509 jxta


    【解决方案1】:

    在 Rene Mayrhofer 的代码的帮助下,我能够做到这一点。 我提供了我的实现,它只在本地测试环境中进行了测试,但它似乎可以工作:

    package net.jxta.impl.membership.pse;
    
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.FileNotFoundException;
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.math.BigInteger;
    import java.security.InvalidKeyException;
    import java.security.Key;
    import java.security.KeyFactory;
    import java.security.KeyPair;
    import java.security.KeyPairGenerator;
    import java.security.KeyStore;
    import java.security.KeyStoreException;
    import java.security.NoSuchAlgorithmException;
    import java.security.NoSuchProviderException;
    import java.security.PrivateKey;
    import java.security.PublicKey;
    import java.security.SecureRandom;
    import java.security.Signature;
    import java.security.SignatureException;
    import java.security.UnrecoverableKeyException;
    import java.security.cert.CertificateException;
    import java.security.cert.X509Certificate;
    import java.security.interfaces.RSAPrivateCrtKey;
    import java.security.spec.InvalidKeySpecException;
    import java.security.spec.RSAPrivateCrtKeySpec;
    import java.security.spec.RSAPublicKeySpec;
    import java.util.Calendar;
    import java.util.Date;
    import java.util.logging.Logger;
    
    import javax.security.auth.x500.X500Principal;
    
    import org.apache.commons.codec.binary.Hex;
    
    import org.bouncycastle.asn1.ASN1Encodable;
    import org.bouncycastle.asn1.ASN1EncodableVector;
    import org.bouncycastle.asn1.ASN1InputStream;
    import org.bouncycastle.asn1.ASN1Sequence;
    import org.bouncycastle.asn1.DERBMPString;
    import org.bouncycastle.asn1.DERBitString;
    import org.bouncycastle.asn1.DERInteger;
    import org.bouncycastle.asn1.DERNull;
    import org.bouncycastle.asn1.DERObjectIdentifier;
    import org.bouncycastle.asn1.DEROutputStream;
    import org.bouncycastle.asn1.DERSequence;
    import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
    import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
    import org.bouncycastle.asn1.x509.DigestInfo;
    import org.bouncycastle.asn1.x509.RSAPublicKeyStructure;
    import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
    import org.bouncycastle.asn1.x509.TBSCertificateStructure;
    import org.bouncycastle.asn1.x509.Time;
    import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
    import org.bouncycastle.asn1.x509.X509CertificateStructure;
    import org.bouncycastle.asn1.x509.X509Name;
    import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
    import org.bouncycastle.crypto.AsymmetricBlockCipher;
    import org.bouncycastle.crypto.AsymmetricCipherKeyPair;
    import org.bouncycastle.crypto.CryptoException;
    import org.bouncycastle.crypto.DataLengthException;
    import org.bouncycastle.crypto.digests.SHA1Digest;
    import org.bouncycastle.crypto.encodings.PKCS1Encoding;
    import org.bouncycastle.crypto.engines.RSAEngine;
    import org.bouncycastle.crypto.generators.RSAKeyPairGenerator;
    import org.bouncycastle.crypto.params.RSAKeyGenerationParameters;
    import org.bouncycastle.crypto.params.RSAKeyParameters;
    import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
    import org.bouncycastle.jce.PrincipalUtil;
    import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
    import org.bouncycastle.jce.provider.X509CertificateObject;
    import org.bouncycastle.x509.X509Util;
    import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;
    
    /** This class uses the Bouncycastle lightweight API to generate X.509 certificates programmatically.
     * It assumes a CA certificate and its private key to be available and can sign the new certificate with
     * this CA. Some of the code for this class was taken from 
     * org.bouncycastle.x509.X509V3CertificateGenerator, but adapted to work with the lightweight API instead of
     * JCE (which is usually not available on MIDP2.0). 
     * 
     * @author Rene Mayrhofer
     */
    public class X509CertificateGenerator {
        /** Our log4j logger. */
        private static Logger logger = Logger.getLogger(X509CertificateGenerator.class.getName());
    
        /** This holds the certificate of the CA used to sign the new certificate. The object is created in the constructor. */
        private X509Certificate caCert;
        /** This holds the private key of the CA used to sign the new certificate. The object is created in the constructor. */
        private RSAPrivateCrtKeyParameters caPrivateKey;
    
        private boolean useBCAPI;
        private boolean useCACert;
    
        public X509CertificateGenerator(String caFile, String caPassword, String caAlias, boolean useBCAPI) 
                throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, InvalidKeyException, NoSuchProviderException, SignatureException {
            this.useBCAPI = useBCAPI;
            this.useCACert = true;
            logger.info("Loading CA certificate and private key from file '" + caFile + "', using alias '" + caAlias + "' with "
                    + (this.useBCAPI ? "Bouncycastle lightweight API" : "JCE API"));
            KeyStore caKs = KeyStore.getInstance("PKCS12");
            caKs.load(new FileInputStream(new File(caFile)), caPassword.toCharArray());
    
            // load the key entry from the keystore
            Key key = caKs.getKey(caAlias, caPassword.toCharArray());
            if (key == null) {
                throw new RuntimeException("Got null key from keystore!"); 
            }
            RSAPrivateCrtKey privKey = (RSAPrivateCrtKey) key;
            caPrivateKey = new RSAPrivateCrtKeyParameters(privKey.getModulus(), privKey.getPublicExponent(), privKey.getPrivateExponent(),
                    privKey.getPrimeP(), privKey.getPrimeQ(), privKey.getPrimeExponentP(), privKey.getPrimeExponentQ(), privKey.getCrtCoefficient());
            // and get the certificate
            caCert = (X509Certificate) caKs.getCertificate(caAlias);
            if (caCert == null) {
                throw new RuntimeException("Got null cert from keystore!"); 
            }
            logger.info("Successfully loaded CA key and certificate. CA DN is '" + caCert.getSubjectDN().getName() + "'");
            caCert.verify(caCert.getPublicKey());
            logger.info("Successfully verified CA certificate with its own public key.");
        }
        public X509CertificateGenerator(boolean useBCAPI) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, FileNotFoundException, IOException, UnrecoverableKeyException, InvalidKeyException, NoSuchProviderException, SignatureException {
            this.useBCAPI = useBCAPI;
            this.useCACert = false;
        }
    
        public X509Certificate createCertificate(String dn, int validityDays, X500Principal issuer, KeyPair keypairca) throws 
                IOException, InvalidKeyException, SecurityException, SignatureException, NoSuchAlgorithmException, DataLengthException, CryptoException, KeyStoreException, NoSuchProviderException, CertificateException, InvalidKeySpecException {
            logger.info("Generating certificate for distinguished subject name '" + 
                    dn + "', valid for " + validityDays + " days");
    
            RSAPrivateCrtKey pK = (RSAPrivateCrtKey) keypairca.getPrivate();
            caPrivateKey = new RSAPrivateCrtKeyParameters(pK.getModulus(), pK.getPublicExponent(), pK.getPrivateExponent(),
                    pK.getPrimeP(), pK.getPrimeQ(), pK.getPrimeExponentP(), pK.getPrimeExponentQ(), pK.getCrtCoefficient());
    
            SecureRandom sr = new SecureRandom();
    
            PublicKey pubKey;
            PrivateKey privKey;
    
            logger.info("Creating RSA keypair");
            // generate the keypair for the new certificate
            if (useBCAPI) {
                RSAKeyPairGenerator gen = new RSAKeyPairGenerator();
                gen.init(new RSAKeyGenerationParameters(BigInteger.valueOf(3), sr, 1024, 80));
                AsymmetricCipherKeyPair keypair = gen.generateKeyPair();
                logger.info("Generated keypair, extracting components and creating public structure for certificate");
                RSAKeyParameters publicKey = (RSAKeyParameters) keypair.getPublic();
                RSAPrivateCrtKeyParameters privateKey = (RSAPrivateCrtKeyParameters) keypair.getPrivate();
                // used to get proper encoding for the certificate
                RSAPublicKeyStructure pkStruct = new RSAPublicKeyStructure(publicKey.getModulus(), publicKey.getExponent());
                logger.info("New public key is '" + new String(Hex.encodeHex(pkStruct.getEncoded())) + 
                        ", exponent=" + publicKey.getExponent() + ", modulus=" + publicKey.getModulus());
                // JCE format needed for the certificate - because getEncoded() is necessary...
                pubKey = KeyFactory.getInstance("RSA").generatePublic(
                        new RSAPublicKeySpec(publicKey.getModulus(), publicKey.getExponent()));
                // and this one for the KeyStore
                privKey = KeyFactory.getInstance("RSA").generatePrivate(
                        new RSAPrivateCrtKeySpec(publicKey.getModulus(), publicKey.getExponent(),
                                privateKey.getExponent(), privateKey.getP(), privateKey.getQ(), 
                                privateKey.getDP(), privateKey.getDQ(), privateKey.getQInv()));
            }
            else {
                // this is the JSSE way of key generation
                KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
                keyGen.initialize(1024, sr);
                KeyPair keypair = keyGen.generateKeyPair();
                privKey = keypair.getPrivate();
                pubKey = keypair.getPublic();
            }
    
            Calendar expiry = Calendar.getInstance();
            expiry.add(Calendar.DAY_OF_YEAR, validityDays);
    
            X509Name x509Name = new X509Name("CN=" + dn);
            X509Name x509Issuer = new X509Name(issuer.getName());
            V3TBSCertificateGenerator certGen = new V3TBSCertificateGenerator();
            certGen.setSerialNumber(new DERInteger(BigInteger.valueOf(System.currentTimeMillis())));
            certGen.setIssuer(x509Issuer);//issuer.getName());//PrincipalUtil.getSubjectX509Principal(caCert));
            certGen.setSubject(x509Name);
            DERObjectIdentifier sigOID = X509Util.getAlgorithmOID("SHA1WithRSAEncryption");
            AlgorithmIdentifier sigAlgId = new AlgorithmIdentifier(sigOID, new DERNull());
            certGen.setSignature(sigAlgId);
            certGen.setSubjectPublicKeyInfo(new SubjectPublicKeyInfo((ASN1Sequence)new ASN1InputStream(
                    new ByteArrayInputStream(pubKey.getEncoded())).readObject()));
            certGen.setStartDate(new Time(new Date(System.currentTimeMillis())));
            certGen.setEndDate(new Time(expiry.getTime()));
    
            logger.info("Certificate structure generated, creating SHA1 digest");
            // attention: hard coded to be SHA1+RSA!
            SHA1Digest digester = new SHA1Digest();
            AsymmetricBlockCipher rsa = new PKCS1Encoding(new RSAEngine());
            TBSCertificateStructure tbsCert = certGen.generateTBSCertificate();
    
            ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
            DEROutputStream         dOut = new DEROutputStream(bOut);
            dOut.writeObject(tbsCert);
    
            // and now sign
            byte[] signature;
            if (useBCAPI) {
                byte[] certBlock = bOut.toByteArray();
                // first create digest
                logger.info("Block to sign is '" + new String(Hex.encodeHex(certBlock)) + "'");     
                digester.update(certBlock, 0, certBlock.length);
                byte[] hash = new byte[digester.getDigestSize()];
                digester.doFinal(hash, 0);
                // and sign that
                rsa.init(true, caPrivateKey);
                DigestInfo dInfo = new DigestInfo( new AlgorithmIdentifier(X509ObjectIdentifiers.id_SHA1, null), hash);
                byte[] digest = dInfo.getEncoded(ASN1Encodable.DER);
                signature = rsa.processBlock(digest, 0, digest.length);
            }
            else {
                // or the JCE way
                PrivateKey caPrivKey = KeyFactory.getInstance("RSA").generatePrivate(
                        new RSAPrivateCrtKeySpec(caPrivateKey.getModulus(), caPrivateKey.getPublicExponent(),
                                caPrivateKey.getExponent(), caPrivateKey.getP(), caPrivateKey.getQ(), 
                                caPrivateKey.getDP(), caPrivateKey.getDQ(), caPrivateKey.getQInv()));
    
                Signature sig = Signature.getInstance(sigOID.getId());
                sig.initSign(caPrivKey, sr);
                sig.update(bOut.toByteArray());
                signature = sig.sign();
            }
            logger.info("SHA1/RSA signature of digest is '" + new String(Hex.encodeHex(signature)) + "'");
    
            // and finally construct the certificate structure
            ASN1EncodableVector  v = new ASN1EncodableVector();
    
            v.add(tbsCert);
            v.add(sigAlgId);
            v.add(new DERBitString(signature));
    
            X509CertificateObject clientCert = new X509CertificateObject(new X509CertificateStructure(new DERSequence(v))); 
            logger.info("Verifying certificate for correct signature with CA public key");
    
            if(useCACert) clientCert.verify(caCert.getPublicKey());
    
            return clientCert;
        }
    
    
    }
    

    【讨论】:

    • 能否请您更新这个类,因为其中许多方法/类现在在 BC 已被弃用?
    • App Engine 现在支持 BC,因此不需要它。
    • @ChukDiesel 我们这些不使用 App Engine 的人怎么样?请问?
    • 不完全确定,但如果问题涉及 X509V3CertificateBuilder,那么解决方案是否应该至少在任何地方包含该字符串 - 如果只是在评论中?
    • 这个问题在 12 年得到了回答。请积极推进您的决议。
    猜你喜欢
    • 2013-10-24
    • 2022-01-04
    • 2013-12-30
    • 2011-03-24
    • 2020-02-29
    • 1970-01-01
    • 2016-08-20
    • 1970-01-01
    • 2022-09-25
    相关资源
    最近更新 更多