web-dev-qa-db-ja.com

Bouncy Castleを使用してCSRに署名する

BCを使用してCSRに署名する方法を説明するコード/ドキュメントが見つかりません。入力として、バイト配列としての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形式で保存するための証明書ジェネレーターからの取得を理解するのに問題があります。

それとも私は間違った道を一緒に下っていますか?

32
Fredrik Jansson

わかりました...同じことをしたいと思っていたのですが、私の人生の中で、どうすればいいのかわかりませんでした。 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

33
Archie

次のコードは上記の回答に基づいていますが、コンパイルされ、(keytoolによってエクスポートされた種類の)PEMエンコードされたCSRが指定されると、(インポート可能なタイプの)署名済み証明書チェーンを含む有効なPEMエンコードされたsignedDataオブジェクトを返します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");
}
8
Mike B

アーチーありがとう!

コードをいくつか変更しました。以下を参照してください。

主な変更は、発行者の名前を渡し、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))
5
Fredrik Jansson

結局、これは私にとってうまくいったものです:

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();
  }
2
EpicPandaForce

@マイクB-例を徹底的にテストしましたか?私はあなたのコードで奇妙な振る舞いをします:私はbc15onバージョンを使用しています。自己署名CAを使用してクライアント要求に署名すると、IEにインポートされ、チェーン内のCAで証明書が有効であることが示されますenter image description here

ただし、FFにインポートすると、チェーン内のCAの右側にあるイメージが欠落していることがわかり、ffはそれを信頼された機関に確認することができません。また、IEまたはFFを使用すると、それを使用してWebサーバーを認証しようとすると、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"));

[〜#〜] update [〜#〜]:OKケースが解決されました。おかげで this thread 明らかに使用する場合:

cacert.getSubjectX500Principal()。getName()

私は代わりに発行者の名前を逆に取得しましたが、チェーンを壊しました:

cert.getSubjectX500Principal()。getEncoded()が解決しました!したがって、CAが信頼できる機関まで検証されない場合は、名前を正しく取得していることを確認してください。

1
Tsonev