Подпишите CSR используя Bouncy Castle - PullRequest
30 голосов
/ 29 августа 2011

Я не могу найти какой-либо код / ​​документ, описывающий, как подписать CSR, используя BC.В качестве входных данных у меня есть CSR в виде байтового массива, и я хотел бы получить сертификат в формате PEM и / или DER.

Я получил это далеко

def signCSR(csrData:Array[Byte], ca:CACertificate, caPassword:String) = {
  val csr = new PKCS10CertificationRequestHolder(csrData)
  val spi = csr.getSubjectPublicKeyInfo

  val ks = new java.security.spec.X509EncodedKeySpec(spi.getDEREncoded())
  val kf = java.security.KeyFactory.getInstance("RSA")
  val pk = kf.generatePublic(ks)

  val (caCert, caPriv) = parsePKCS12(ca.pkcs12data, caPassword)

  val fromDate : java.util.Date = new java.util.Date // FixMe
  val toDate = fromDate // FixMe
  val issuer = PrincipalUtil.getIssuerX509Principal(caCert)
  val contentSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(caPriv)
  val serial = BigInt(CertSerialnumber.nextSerialNumber)
  val certgen = new JcaX509v3CertificateBuilder(new X500Name(issuer.getName), serial.bigInteger, fromDate, toDate, csr.getSubject, pk)

У меня проблемы с вычислениемполучить из генератора сертификатов, чтобы сохранить его в формате PEM или DER.

Или я все вместе иду по неправильному пути?

Ответы [ 5 ]

33 голосов
/ 09 сентября 2011

Хорошо ... Я хотел сделать то же самое, и для жизни я не мог понять, как. Все API говорят о генерации пар ключей, а затем о генерации сертификата, но не о том, как подписать CSR. Как-то совершенно случайно - вот что я нашел.

Поскольку PKCS10 представляет формат запроса (CSR), сначала необходимо поместить CSR в PKCS10Holder. Затем вы передаете его в CertificateBuilder (поскольку CertificateGenerator устарел). То, как вы это передаете, это вызов getSubject для владельца.

Вот код (Java, пожалуйста, адаптируйте его по необходимости):

public static X509Certificate sign(PKCS10CertificationRequest inputCSR, PrivateKey caPrivate, KeyPair pair)
        throws InvalidKeyException, NoSuchAlgorithmException,
        NoSuchProviderException, SignatureException, IOException,
        OperatorCreationException, CertificateException {   

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

    AsymmetricKeyParameter foo = PrivateKeyFactory.createKey(caPrivate
            .getEncoded());
    SubjectPublicKeyInfo keyInfo = SubjectPublicKeyInfo.getInstance(pair
            .getPublic().getEncoded());

    PKCS10CertificationRequestHolder pk10Holder = new PKCS10CertificationRequestHolder(inputCSR);
    //in newer version of BC such as 1.51, this is 
    //PKCS10CertificationRequest pk10Holder = new PKCS10CertificationRequest(inputCSR);

    X509v3CertificateBuilder myCertificateGenerator = new X509v3CertificateBuilder(
            new X500Name("CN=issuer"), new BigInteger("1"), new Date(
                    System.currentTimeMillis()), new Date(
                    System.currentTimeMillis() + 30 * 365 * 24 * 60 * 60
                            * 1000), pk10Holder.getSubject(), keyInfo);

    ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId)
            .build(foo);        

    X509CertificateHolder holder = myCertificateGenerator.build(sigGen);
    X509CertificateStructure eeX509CertificateStructure = holder.toASN1Structure(); 
    //in newer version of BC such as 1.51, this is 
    //org.spongycastle.asn1.x509.Certificate eeX509CertificateStructure = holder.toASN1Structure(); 

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

    // Read Certificate
    InputStream is1 = new ByteArrayInputStream(eeX509CertificateStructure.getEncoded());
    X509Certificate theCert = (X509Certificate) cf.generateCertificate(is1);
    is1.close();
    return theCert;
    //return null;
}

Как видите, я сгенерировал запрос вне этого метода, но передал его. Затем у меня есть PKCS10CertificationRequestHolder, чтобы принять его как аргумент конструктора.

Далее, в аргументах X509v3CertificateBuilder вы увидите pk10Holder.getSubject - это, очевидно, все, что вам нужно? Если чего-то не хватает, пожалуйста, дайте мне знать тоже !!! Это сработало для меня. Сертификат, который я сгенерировал правильно, содержал необходимую информацию DN.

В Википедии есть раздел убийцы на PKCS - http://en.wikipedia.org/wiki/PKCS

7 голосов
/ 24 июня 2013

Следующий код основан на вышеприведенных ответах, но скомпилируется и, учитывая CSR в кодировке PEM (типа, экспортируемого keytool), вернет действительный объект подписанного PEM в кодировке PEM, содержащий подписанную цепочку сертификатов (типа, которыйможет быть импортирован с помощью keytool).

О, и это против BouncyCastle 1.49.

import java.security.*;
import java.io.*;
import java.util.Date;
import java.math.BigInteger;
import java.security.cert.X509Certificate;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.asn1.x500.*;
import org.bouncycastle.asn1.pkcs.*;
import org.bouncycastle.openssl.*;
import org.bouncycastle.pkcs.*;
import org.bouncycastle.cert.*;
import org.bouncycastle.cms.*;
import org.bouncycastle.cms.jcajce.*;
import org.bouncycastle.crypto.util.*;
import org.bouncycastle.operator.*;
import org.bouncycastle.operator.bc.*;
import org.bouncycastle.operator.jcajce.*;
import org.bouncycastle.util.encoders.Base64;

/**
 * Given a Keystore containing a private key and certificate and a Reader containing a PEM-encoded
 * Certificiate Signing Request (CSR), sign the CSR with that private key and return the signed
 * certificate as a PEM-encoded PKCS#7 signedData object. The returned value can be written to a file
 * and imported into a Java KeyStore with "keytool -import -trustcacerts -alias subjectalias -file file.pem"
 *
 * @param pemcsr a Reader from which will be read a PEM-encoded CSR (begins "-----BEGIN NEW CERTIFICATE REQUEST-----")
 * @param validity the number of days to sign the Certificate for
 * @param keystore the KeyStore containing the CA signing key
 * @param alias the alias of the CA signing key in the KeyStore
 * @param password the password of the CA signing key in the KeyStore
 *
 * @return a String containing the PEM-encoded signed Certificate (begins "-----BEGIN PKCS #7 SIGNED DATA-----")
 */
public static String signCSR(Reader pemcsr, int validity, KeyStore keystore, String alias, char[] password) throws Exception {
    PrivateKey cakey = (PrivateKey)keystore.getKey(alias, password);
    X509Certificate cacert = (X509Certificate)keystore.getCertificate(alias);
    PEMReader reader = new PEMReader(pemcsr);
    PKCS10CertificationRequest csr = new PKCS10CertificationRequest((CertificationRequest)reader.readObject());

    AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA");
    AlgorithmIdentifier digAlgId = new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
    X500Name issuer = new X500Name(cacert.getSubjectX500Principal().getName());
    BigInteger serial = new BigInteger(32, new SecureRandom());
    Date from = new Date();
    Date to = new Date(System.currentTimeMillis() + (validity * 86400000L));

    X509v3CertificateBuilder certgen = new X509v3CertificateBuilder(issuer, serial, from, to, csr.getSubject(), csr.getSubjectPublicKeyInfo());
    certgen.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false));
    certgen.addExtension(X509Extension.subjectKeyIdentifier, false, new SubjectKeyIdentifier(csr.getSubjectPublicKeyInfo()));
    certgen.addExtension(X509Extension.authorityKeyIdentifier, false, new AuthorityKeyIdentifier(new GeneralNames(new GeneralName(new X509Name(cacert.getSubjectX500Principal().getName()))), cacert.getSerialNumber()));

    ContentSigner signer = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(PrivateKeyFactory.createKey(cakey.getEncoded()));
    X509CertificateHolder holder = certgen.build(signer);
    byte[] certencoded = holder.toASN1Structure().getEncoded();

    CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
    signer = new JcaContentSignerBuilder("SHA1withRSA").build(cakey);
    generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(signer, cacert));
    generator.addCertificate(new X509CertificateHolder(certencoded));
    generator.addCertificate(new X509CertificateHolder(cacert.getEncoded()));
    CMSTypedData content = new CMSProcessableByteArray(certencoded);
    CMSSignedData signeddata = generator.generate(content, true);

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    out.write("-----BEGIN PKCS #7 SIGNED DATA-----\n".getBytes("ISO-8859-1"));
    out.write(Base64.encode(signeddata.getEncoded()));
    out.write("\n-----END PKCS #7 SIGNED DATA-----\n".getBytes("ISO-8859-1"));
    out.close();
    return new String(out.toByteArray(), "ISO-8859-1");
}
5 голосов
/ 22 сентября 2011

Archie, спасибо!

Я внес некоторые изменения в ваш код, см. Ниже.

Основные изменения - передача имени эмитента и использование открытого ключа из CSR.

val caCert = PEMToCert(issuerPEM).get
val issuer = PrincipalUtil.getIssuerX509Principal(caCert)
val csr = new PKCS10CertificationRequestHolder(csrData)
val serial = BigInt(CertSerialNumber.nextSerialNumber)
val spi = csr.getSubjectPublicKeyInfo();

val certgen = new X509v3CertificateBuilder(
    new X500Name(issuer.getName),
    serial.bigInteger,
    new java.util.Date(),
    new Date(System.currentTimeMillis() + 30 * 365 * 24 * 60 * 60 * 1000),
    csr.getSubject,
    csr.getSubjectPublicKeyInfo())

certgen.addExtension(
    X509Extension.subjectKeyIdentifier,
    false,
    spi
)

val issuerPK = PEMToPK(issuerPKPEM, caPassword).get
val contentSigner = new JcaContentSignerBuilder(contentSignerAlg).setProvider(BC).build(issuerPK.getPrivate())
val x509 = (new JcaX509CertificateConverter).setProvider(BC).getCertificate(certgen.build(contentSigner))
2 голосов
/ 06 ноября 2014

В конце концов это то, что у меня сработало:

KeyPair serverKeyPair = keyPairLoader.getKeyPair(); //my own class
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509", "BC");
X509Certificate serverCertificate = getServerCertificate(certificateFactory);

org.spongycastle.asn1.x509.Certificate eeX509CertificateStructure = signCertificateSigningRequest(
  jcaPKCS10CertificationRequest, keyPair, serverCertificate);

java.security.cert.X509Certificate signedCertificate = readCertificateFromASN1Certificate(
  eeX509CertificateStructure, certificateFactory);

Где код

  private org.spongycastle.asn1.x509.Certificate signCertificateSigningRequest(
    JcaPKCS10CertificationRequest jcaPKCS10CertificationRequest,
    KeyPair keyPair, X509Certificate serverCertificate)
      throws IOException, OperatorCreationException, NoSuchAlgorithmException, InvalidKeyException
  {
    // Signing CSR
    AlgorithmIdentifier sigAlgId = new DefaultSignatureAlgorithmIdentifierFinder()
      .find("SHA1withRSA");

    X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(
        serverCertificate, 
        new BigInteger("1"), //serial
        new Date(System.currentTimeMillis()),
        new Date(System.currentTimeMillis() + 30L * 365L * 24L * 60L * 60L * 1000L),
        jcaPKCS10CertificationRequest.getSubject(),
        jcaPKCS10CertificationRequest.getPublicKey()
    /*).addExtension(
        new ASN1ObjectIdentifier("2.5.29.35"),
        false,
        new AuthorityKeyIdentifier(keyPair.getPublic().getEncoded())*/
    ).addExtension(
            new ASN1ObjectIdentifier("2.5.29.19"),
            false,
            new BasicConstraints(false) // true if it is allowed to sign other certs
    ).addExtension(
            new ASN1ObjectIdentifier("2.5.29.15"),
            true,
            new X509KeyUsage(
                X509KeyUsage.digitalSignature |
                    X509KeyUsage.nonRepudiation   |
                    X509KeyUsage.keyEncipherment  |
                    X509KeyUsage.dataEncipherment));

    AsymmetricKeyParameter asymmetricKeyParameter =
          PrivateKeyFactory.createKey(keyPair.getPrivate().getEncoded());
    //ContentSigner sigGen = new BcRSAContentSignerBuilder(sigAlgId, digAlgId).build(asymmetricKeyParameter);
    ContentSigner sigGen = new JcaContentSignerBuilder("SHA1withRSA").build(keyPair.getPrivate());


    X509CertificateHolder x509CertificateHolder = certificateBuilder.build(sigGen);
    org.spongycastle.asn1.x509.Certificate eeX509CertificateStructure =
      x509CertificateHolder.toASN1Structure();
    return eeX509CertificateStructure;
  }

  private X509Certificate readCertificateFromASN1Certificate(
    org.spongycastle.asn1.x509.Certificate eeX509CertificateStructure,
    CertificateFactory certificateFactory)
    throws IOException, CertificateException {
    // Read Certificate
    InputStream is1 = new ByteArrayInputStream(eeX509CertificateStructure.getEncoded());
    X509Certificate signedCertificate =
      (X509Certificate) certificateFactory.generateCertificate(is1);
    return signedCertificate;
  }

И это можно преобразовать в PEM:

  private String convertCertificateToPEM(X509Certificate signedCertificate) throws IOException {
    StringWriter signedCertificatePEMDataStringWriter = new StringWriter();
    JcaPEMWriter pemWriter = new JcaPEMWriter(signedCertificatePEMDataStringWriter);
    pemWriter.writeObject(signedCertificate);
    pemWriter.close();
    log.info("PEM data:");
    log.info("" + signedCertificatePEMDataStringWriter.toString());
    return signedCertificatePEMDataStringWriter.toString();
  }
1 голос
/ 28 августа 2014

@ Майк Б - ты тщательно проверил свой пример? Я получаю странное поведение с вашим кодом: Я использую версию bc15on. Когда я подписываю запрос клиента самозаверяющим CA, я импортирую его в IE, и он показывает, что сертификат действителен с CA в цепочке enter image description here

Однако вы можете видеть, что при импорте в FF изображения в правой части CA в цепочке отсутствуют, и ff не может проверить это в Trusted Authority. Также с IE или FF при попытке аутентификации на веб-сервере с ним происходит сбой, поскольку http не может проверить это и доверенному органу.

Я внес некоторые изменения в ваш код, чтобы удовлетворить мои потребности, но в целом он должен быть таким же, может кто-нибудь дать мне несколько советов о том, что я делаю здесь неправильно:

    public static String GenCert(long SerNum, int addYear, int addHours,
                             String reqText,
                             String reqName) throws Exception,
                                                    SQLException {

    String result = "";
    reqText = csr; // hard code base64 csr for testing purposes
    reqText =
        "-----BEGIN CERTIFICATE REQUEST-----\n" + reqText +
        "\n-----END CERTIFICATE REQUEST-----\n";

    try {


        String castr = ca + "\n"; // hard code base64 CA pub key for testing
        String strPriv = caPrivk + "\n"; // hard code base64 CA private key for testing


        byte[] encKey = castr.getBytes();
        CertificateFactory cf = CertificateFactory.getInstance("X.509");
        X509Certificate caCert =
            (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(encKey));

        PEMParser pr = new PEMParser(new StringReader(strPriv));
        Object obj = pr.readObject();
        JcaPEMKeyConverter converter =
            new JcaPEMKeyConverter().setProvider("BC");
        KeyPair kp;
        kp = converter.getKeyPair((PEMKeyPair)obj);

        PrivateKey privateKey = kp.getPrivate();

        // parse the request
        PEMParser pRd =
            new PEMParser(new InputStreamReader(new ByteArrayInputStream(reqText.getBytes())));
        PKCS10CertificationRequest csr =
            (PKCS10CertificationRequest)pRd.readObject();

        String strReq = csr.getSubject().toString();

        strReq = strReq.substring(strReq.indexOf("CN=") + 3).trim();
        if (strReq.indexOf(",") > 0)
            strReq = strReq.substring(0, strReq.indexOf(",")).trim();
        if (!strReq.equals(reqName)) {
            return "";
        }

        AlgorithmIdentifier sigAlgId =
            new DefaultSignatureAlgorithmIdentifierFinder().find("SHA1withRSA"); //SHA1withRSA
        AlgorithmIdentifier digAlgId =
            new DefaultDigestAlgorithmIdentifierFinder().find(sigAlgId);
        X500Name issuer =
            new X500Name(caCert.getSubjectX500Principal().getName());
        BigInteger serial = BigInteger.valueOf(SerNum);

        // The date object returns GMT format
        Date date = new Date(System.currentTimeMillis() - 180 * 1000);
        date.setHours(date.getHours() + addHours);
        Calendar cal = Calendar.getInstance();
        Date from = date;
        cal.setTime(date);
        cal.add(1, addYear);
        Date to = cal.getTime();

        SubjectPublicKeyInfo pkInfo = csr.getSubjectPublicKeyInfo();
        //SubjectPublicKeyInfo pkInfo = SubjectPublicKeyInfo.getInstance(kp.getPublic().getEncoded());
        RSAKeyParameters rsa =
            (RSAKeyParameters)PublicKeyFactory.createKey(pkInfo);
        RSAPublicKeySpec rsaSpec =
            new RSAPublicKeySpec(rsa.getModulus(), rsa.getExponent());
        KeyFactory kf = KeyFactory.getInstance("RSA");
        PublicKey rsaPub = kf.generatePublic(rsaSpec);


        X509v3CertificateBuilder certgen =
            new X509v3CertificateBuilder(issuer, serial, from, to,
                                         csr.getSubject(),
                                         csr.getSubjectPublicKeyInfo());

        certgen.addExtension(X509Extension.basicConstraints, false,
                             new BasicConstraints(false));
        certgen.addExtension(X509Extension.subjectKeyIdentifier, false,
                             new SubjectKeyIdentifier(pkInfo));
        //            certgen.addExtension(X509Extension.subjectKeyIdentifier, false,
        //                                 new SubjectKeyIdentifierStructure(rsaPub)); // In old version done with much more extensive parsing
        certgen.addExtension(X509Extensions.AuthorityKeyIdentifier, false,
                             new AuthorityKeyIdentifierStructure(caCert));
        //            certgen.addExtension(X509Extension.authorityKeyIdentifier, false,
        //                                 new AuthorityKeyIdentifier(new GeneralNames(new GeneralName(new X509Name(caCert.getSubjectX500Principal().getName()))),
        //                                                            caCert.getSerialNumber()));

        // add certificate purposes
        ASN1EncodableVector vector = new ASN1EncodableVector();
        vector.add(new DERObjectIdentifier("1.3.6.1.5.5.7.3.2"));
        vector.add(new DERObjectIdentifier("1.3.6.1.4.1.311.20.2.2"));
        vector.add(new DERObjectIdentifier("1.3.6.1.4.1.311.10.3.12"));
        vector.add(new DERObjectIdentifier("1.3.6.1.5.5.7.3.4"));


        DERSequence seq = new DERSequence(vector);
        certgen.addExtension(X509Extensions.ExtendedKeyUsage, false, seq);


        ContentSigner signer =
            new BcRSAContentSignerBuilder(sigAlgId,
                                          digAlgId).build(PrivateKeyFactory.createKey(privateKey.getEncoded()));
        X509CertificateHolder holder = certgen.build(signer);
        byte[] certencoded = holder.toASN1Structure().getEncoded();

        CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
        signer =
            new JcaContentSignerBuilder("SHA1withRSA").build(privateKey);
        generator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(new JcaDigestCalculatorProviderBuilder().build()).build(signer,
                                                                                                                                   caCert));
        generator.addCertificate(new X509CertificateHolder(certencoded));
        generator.addCertificate(new X509CertificateHolder(caCert.getEncoded()));
        CMSTypedData content = new CMSProcessableByteArray(certencoded);
        CMSSignedData signeddata = generator.generate(content, true);

        result = Base64Utils.base64Encode(signeddata.getEncoded());

    } catch (Exception e) {
        result = e.toString();
        getStackTrace(e);
    }
    return result;
}

В старой версии моего кода, где я использовал надувной замок 1.4, мы использовали X509V3CertificateGenerator и непосредственно перед возвратом содержимого, которое мы использовали для построения цепочки, примерно так:

            X509Certificate newCert =
            certGen.generateX509Certificate(privateKey, "BC");
        //=============================
        List chain = new ArrayList();
        chain.add(newCert);
        //-------------------------------------------------
        //  create the CertPath with old BouncyCastle
        CertificateFactory fact =
            CertificateFactory.getInstance("X.509", "BC");
        CertPath path = fact.generateCertPath(chain);
        result = Base64Utils.base64Encode(path.getEncoded("PKCS7"));

ОБНОВЛЕНИЕ : ОК Дело решено. Благодаря этой теме Очевидно при использовании:

cacert.getSubjectX500Principal (). GetName ()

Я получил имена эмитента в обратном порядке, который разорвал цепочку, используя вместо этого:

cert.getSubjectX500Principal (). GetEncoded () решил это за меня! Поэтому, когда ваш ЦС не прошел проверку в доверенном органе, убедитесь, что вы правильно получаете имена.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...