Ssl certificate 向生成的证书添加新扩展名

Ssl certificate 向生成的证书添加新扩展名,ssl-certificate,x509certificate,bouncycastle,csr,java-security,Ssl Certificate,X509certificate,Bouncycastle,Csr,Java Security,我需要在我的证书中添加OID1.3.6.1.5.5.7.1.26的新扩展。我在证书中获得了此OID扩展,但出现以下错误: 证书延期:10 [1] :ObjectId:1.3.6.1.5.5.7.1.26临界性=假 扩展名未知:DER编码的八位字节字符串= 0000:04 0C 30 0A 13 08 33 39 20 64 63 20 32 62..0… 39 dc 2b 我希望此OID与其他扩展(如AuthorityInfoAccess等)类似 我需要编辑Bouncy Castle的罐子吗 我

我需要在我的证书中添加OID1.3.6.1.5.5.7.1.26的新扩展。我在证书中获得了此OID扩展,但出现以下错误:

证书延期:10 [1] :ObjectId:1.3.6.1.5.5.7.1.26临界性=假
扩展名未知:DER编码的八位字节字符串=
0000:04 0C 30 0A 13 08 33 39 20 64 63 20 32 62..0…
39 dc 2b

我希望此OID与其他扩展(如AuthorityInfoAccess等)类似

我需要编辑Bouncy Castle的罐子吗

我使用ACME4j作为客户端,Letsencrypt Boulder作为服务器

以下是注册证书的CSR生成器代码

public void sign(KeyPair keypair) throws IOException {
    //Security.addProvider(new BouncyCastleProvider());
    Objects.requireNonNull(keypair, "keypair");
    if (namelist.isEmpty()) {
        throw new IllegalStateException("No domain was set");
    }

    try {
        GeneralName[] gns = new GeneralName[namelist.size()];
        for (int ix = 0; ix < namelist.size(); ix++) {
            gns[ix] = new GeneralName(GeneralName.dNSName,namelist.get(ix));
        }
        SignatureAlgorithmIdentifierFinder algFinder = new 
                DefaultSignatureAlgorithmIdentifierFinder();
        GeneralNames subjectAltName = new GeneralNames(gns);


        PKCS10CertificationRequestBuilder p10Builder = new     JcaPKCS10CertificationRequestBuilder(namebuilder.build(), keypair.getPublic());

        ExtensionsGenerator extensionsGenerator = new ExtensionsGenerator();
        extensionsGenerator.addExtension(Extension.subjectAlternativeName,     false, subjectAltName);
        //extensionsGenerator.addExtension(Extension.authorityInfoAccess,         true, subjectAltName);
        //extensionsGenerator.addExtension(new ASN1ObjectIdentifier("TBD"),     false, subjectAltName);
        //extensionsGenerator.addExtension(new     ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.24"), false, subjectAltName);
        extensionsGenerator.addExtension(new     ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.26").intern(), false, subjectAltName);
        //extentionsGenerator.addExtension();
            p10Builder.addAttribute(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest,     extensionsGenerator.generate());


        PrivateKey pk = keypair.getPrivate();
        /*JcaContentSignerBuilder csBuilder = new JcaContentSignerBuilder(
                        pk instanceof ECKey ? EC_SIGNATURE_ALG :     EC_SIGNATURE_ALG);
        ContentSigner signer = csBuilder.build(pk);*/

        if(pk instanceof ECKey)
        {
            AlgorithmIdentifier sigAlg = algFinder.find("SHA1withECDSA");
              AlgorithmIdentifier digAlg = new     DefaultDigestAlgorithmIdentifierFinder().
                    find(sigAlg);
            ContentSigner signer = new     JcaContentSignerBuilder("SHA256with"+pk.getAlgorithm()).setProvider(BOUNCY_CASTL    E_PROVIDER).build(keypair.getPrivate());

            csr=p10Builder.build(signer);
            System.out.println("ZIPED CSR ECDSA: "+csr);
        }
        else
        {
            ContentSigner signer = new     JcaContentSignerBuilder("SHA256with"+pk.getAlgorithm()).build(keypair.getPrivate    ()); 
            csr = p10Builder.build(signer);
            System.out.println("ZIPED CSR RSA: "+csr);
        }

        //csr = p10Builder.build(signer);
    } catch (Exception ex) {
        ex.printStackTrace();;
    }
}
公共无效符号(密钥对密钥对)引发IOException{
//addProvider(新的BouncyCastleProvider());
对象。requirennull(keypair,“keypair”);
if(namelist.isEmpty()){
抛出新的IllegalStateException(“未设置域”);
}
试一试{
GeneralName[]gns=新的GeneralName[namelist.size()];
对于(intix=0;ix
由于OID1.3.6.1.5.5.7.1.26仍然是一个草案,我相信像Let's Encrypt这样的工具和系统不太可能认识到这个扩展(他们可能会在这个扩展成为正式版本后这样做,我真的不知道这种批准背后的官僚程序)

这意味着您可能需要对其进行编码。我已经使用Bouncy Castle
好几年了,但从未创建过新的ASN1结构。但如果有必要的话,我会先看看它的源代码作为初始指导

考虑到本次扩建的ASN1结构:

 TNAuthorizationList ::= SEQUENCE SIZE (1..MAX) OF TNEntry

 TNEntry ::= CHOICE {
   spc   [0] ServiceProviderCodeList,
   range [1] TelephoneNumberRange,
   one       E164Number
   }

 ServiceProviderCodeList ::= SEQUENCE SIZE (1..3) OF IA5String

 -- Service Provider Codes may be OCNs, various SPIDs, or other
 -- SP identifiers from the telephone network

 TelephoneNumberRange ::= SEQUENCE {
   start E164Number,
   count INTEGER
   }

 E164Number ::= IA5String (SIZE (1..15)) (FROM ("0123456789#*"))
扩展值必须是
TNEntry
序列。因此,您可以使用
ASN1Sequence
(或其子类
DERSequence
)并将
TNEntry
的实例放在其中

要创建
TNEntry
,您需要实现
ASN1Choice
(查看
GeneralName
类的源代码并执行类似操作)

依此类推,直到您将整个结构映射到各自的类,使用Bouncy Castle内置类来支持您(对于
IA5String
DERIA5String
,对于
INTEGER
DERInteger
,可用于
ServiceProviderCodeList
TelephoneNumberRange


之后,您可以构建自己的解析器,它可以识别此扩展。但是正如我所说的,不要指望其他工具能够识别它。

现在,出于测试目的,我只是从我的CA Boulder中传递一个字符串值。要了解这一点,这是TNAUthList的自定义ASN1对象结构

public class TNAuthorizationList extends ASN1Object implements ASN1Choice{

public static final int spc                     = 0;
public static final int range                   = 1;

private ASN1Encodable obj;
private int           tag;

public TNAuthorizationList(
        int           tag,
        ASN1Encodable name)
    {
        this.obj = name;
        this.tag = tag;
    }

public TNAuthorizationList(
        int       tag,
        String    name)
    {
        this.tag = tag;

        if (tag == spc)
        {
            this.obj = new DERIA5String(name);
        }
        else if (tag == range)
        {
            this.obj = new ASN1ObjectIdentifier(name);
        }
        else
        {
            throw new IllegalArgumentException("can't process String for tag: " + tag);
        }
    }

public static TNAuthorizationList getInstance(
        Object obj)
    {
        if (obj == null || obj instanceof TNAuthorizationList)
        {
            return (TNAuthorizationList)obj;
        }

        if (obj instanceof ASN1TaggedObject)
        {
            ASN1TaggedObject    tagObj = (ASN1TaggedObject)obj;
            int                 tag = tagObj.getTagNo();

            switch (tag)
            {
            case spc:
                return new TNAuthorizationList(tag, DERIA5String.getInstance(tagObj, false));
            }
        }

        if (obj instanceof byte[])
        {
            try
            {
                return getInstance(ASN1Primitive.fromByteArray((byte[])obj));
            }
            catch (IOException e)
            {
                throw new IllegalArgumentException("unable to parse encoded general name");
            }
        }

        throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
    }

public static TNAuthorizationList getInstance(
        ASN1TaggedObject tagObj,
        boolean          explicit)
    {
        return TNAuthorizationList.getInstance(ASN1TaggedObject.getInstance(tagObj, true));
    }

    public int getTagNo()
    {
        return tag;
    }

    public ASN1Encodable getSpc()
    {
        return obj;
    }

    public String toString()
    {
        StringBuffer buf = new StringBuffer();

        buf.append(tag);
        buf.append(": ");
        switch (tag)
        {
        case spc:
            buf.append(DERIA5String.getInstance(obj).getString());
            break;
        default:
            buf.append(obj.toString());
        }
        return buf.toString();
    }



/**
*TNEntry ::= CHOICE {
*       spc   [0] ServiceProviderCodeList,
*       range [1] TelephoneNumberRange,
*       one       E164Number
*       }
*/
@Override
public ASN1Primitive toASN1Primitive() {
    // TODO Auto-generated method stub
    return new DERTaggedObject(false, tag, obj);
}
}

正如您所建议的,我已将OID值传递给X509Util类并打印输出

ASN1Object o = X509ExtensionUtil.fromExtensionValue(cert.getExtensionValue("1.3.6.1.5.5.7.1.26"));
    System.out.println("ASN1 Object: "+o);
    System.out.println("get Class "+o.getClass());
而O/p是

ASN1 Object: [SPID : 39 dc 2b]
get Class class org.bouncycastle.asn1.DLSequence

这很好。我如何用我的自定义ASN1结构解析它?

注意:对于这些代码,我使用了1.56上的bcprov-jdk15

关于代码的一些注释。首先,请注意ASN1结构:

TNAuthorizationList::=TNEntry的序列大小(1..MAX)
TNEntry::=CHOICE{
spc[0]ServiceProviderCodeList,
范围[1]电话号码排列,
一个E164号码
}
请注意,
TNEntry
是一个选择
TNAuthorizationList
TNEntry
对象的一个序列
public class RSASignedCertificate {

private static final int KEY_SIZE = 2048;

private static final Logger LOG = Logger.getLogger(CCIDClient.class);

@SuppressWarnings("unused")
public void fetchCertificate(String domain,String spid, String email, int port,
        String username, String password, String certPath) throws Exception {
    // Load or create a key pair for the user's account
    boolean createdNewKeyPair = true;
    KeyPair domainKeyPair = null;

    DomainKeyStore details = null;
    KeyPair userKeyPair = null;

    userKeyPair = KeyPairUtils.createKeyPair(KEY_SIZE);

    DateFormat dateTime = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
    Date date;
    details = new DomainKeyStore();

    // Create Hibernate Util class Object

    // dao=new HibernateDAO();
    boolean isDomainExist = new HibernateDAO().isDomainExist(domain);
    if (isDomainExist) {

        details.setDomain(domain);
        details.setEmail(email);
        date = new Date();
        details.setUpdatedOn(dateTime.parse(dateTime.format(date)));
        boolean updateresult = new HibernateDAO().updateDetails(details);

        LOG.info("User Details Updated ");
    }

    else {

        date = new Date();
        // Date currentDateTime = dateTime.parse(dateTime.format(date));
        details.setEmail(email);
        details.setDomain(domain);
        details.setStatus("Not Registered");
        details.setCreatedOn(dateTime.parse(dateTime.format(date)));
        details.setUpdatedOn(dateTime.parse(dateTime.format(date)));

        boolean isInserted = new HibernateDAO().insertDetails(details);
        if (!isInserted) {
            throw new AcmeException("Unable to insert details");
        }
        LOG.info("User Details inserted ");
    }
    // details=dao.getDetails(domain);

    Session session = null;
    if (userKeyPair != null) {
        session = new Session("http://192.168.1.143:4000/directory", userKeyPair);
        System.out.println(session.getServerUri().toString());
        System.out.println(session.resourceUri(Resource.NEW_REG));
    }
    Registration reg = null;
    try {
        reg = new RegistrationBuilder().create(session);
        LOG.info("Registered a new user, URI: " + reg.getLocation());
    } catch (AcmeConflictException ex) {
        reg = Registration.bind(session, ex.getLocation());
        LOG.info("Account does already exist, URI: " + reg.getLocation());
    }
    date = new Date();
    details.setStatus("Registered");
    details.setRegistrationDate(dateTime.parse(dateTime.format(date)));
    details.setUpdatedOn(dateTime.parse(dateTime.format(date)));

    new HibernateDAO().updateRegistration(details);

    URI agreement = reg.getAgreement();
    LOG.info("Terms of Service: " + agreement);

    if (createdNewKeyPair) {
        boolean accepted = acceptAgreement(reg, agreement);
        if (!accepted) {
            return;
        }
    }

    Authorization auth = null;
    try {
        auth = reg.authorizeDomain(spid);
    } catch (AcmeUnauthorizedException ex) {
        // Maybe there are new T&C to accept?
        boolean accepted = acceptAgreement(reg, agreement);
        if (!accepted) {
            return;
        }
        // Then try again...
        auth = reg.authorizeDomain(spid);
    }
    LOG.info("New authorization for domain " + spid);
    LOG.info("Authorization " + auth);

    Challenge challenge = tokenChallenge(auth);
    // System.out.println("Challendg status before trigger :"+challenge.getStatus());
    if (challenge == null) {
        throw new AcmeException("No Challenge found");
    }

    if (challenge.getStatus() == Status.VALID) {
        return;
    }
    challenge.trigger();
    int attempts = 1;
    // System.out.println("Challendg status after trigger :"+challenge.getStatus());
    while (challenge.getStatus() != Status.VALID && attempts-- > 0) {
        // System.out.println(challenge.getStatus());
        if (challenge.getStatus().equals(Status.PENDING)) {
            challenge.update();
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                LOG.warn("interrupted", e);
                e.printStackTrace();
            }
        }
        if (challenge.getStatus() == Status.INVALID) {
            LOG.error("Challenge failed... Giving up.");
            throw new AcmeServerException("Challenge Failed");
        }
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException ex) {
            LOG.warn("interrupted", ex);
        }
        challenge.update();
    }
    if (challenge.getStatus() != Status.VALID) {
        LOG.error("Failed to pass the challenge... Giving up.");
        throw new AcmeServerException("Challenge Failed");
    }

    date = new Date();
    details.setStatus("Clallenge Completed");
    details.setUpdatedOn(dateTime.parse(dateTime.format(date)));
    new HibernateDAO().updateChallenge(details);

    domainKeyPair = KeyPairUtils.createKeyPair(KEY_SIZE);

    // Generate a CSR for the domain
    CSRBuilder csrb = new CSRBuilder();
    csrb.addDomains(spid);
    csrb.sign(domainKeyPair);

    // System.out.println("CSR:" +csrb.getCSR());

    LOG.info("Keys Algorithm: "
            + domainKeyPair.getPrivate().getAlgorithm());

    PrivateKeyStore privatekey = new PrivateKeyStore();
    privatekey.setDomain(spid);
    privatekey.setEmail(email);
    privatekey.setPrivateKey(domainKeyPair.getPrivate().getEncoded());

    PublicKeyStore publickey = new PublicKeyStore();
    publickey.setDomain(spid);
    publickey.setEmail(email);
    publickey.setPublicKey(domainKeyPair.getPublic().getEncoded());

        // Request a signed certificate
    Certificate certificate = reg.requestCertificate(csrb.getEncoded());
    LOG.info("Success! The certificate for spids " + spid
            + " has been generated!");
    LOG.info("Certificate URI: " + certificate.getLocation());

    String nameFile = spid.replace(".", "") + ".cer";

    X509Certificate sscert = CertificateUtils.createTlsSniCertificate(domainKeyPair,spid);

    System.out.println("Certificate :" +sscert);

    ASN1Primitive o = X509ExtensionUtil.fromExtensionValue(sscert.getExtensionValue(TNAuthorizationList.TN_AUTH_LIST_OID.getId()));
    System.out.println("ASN1:Object "+o+" class: "+o.getClass());
    TNAuthorizationList TNList = TNAuthorizationList.getInstance(o);
    System.out.println(TNList.toString());

    File createFile = new File(certPath + nameFile);
    if (!createFile.exists()) {
        createFile.createNewFile();
    }

    try (FileWriter fw = new FileWriter(createFile.getAbsoluteFile())) {
        CertificateUtils.writeX509Certificate(sscert, fw);
        System.out.println("Certificate " + sscert);
        System.out.println("Certificate Content" + fw);
    }

    date = new Date();
    Calendar c = Calendar.getInstance();
    c.setTime(new Date());
    c.add(Calendar.DATE, 90);
    details.setIssueDate(dateTime.parse(dateTime.format(date)));
    details.setUpdatedOn(dateTime.parse(dateTime.format(date)));
    details.setValidUntil(dateTime.parse(dateTime.format(c.getTime())));
    details.setStatus("Issued");



    details.setCertPath(certPath + nameFile);
    new HibernateDAO().updateCertificate(details);

}

public boolean acceptAgreement(Registration reg, URI agreement) throws AcmeException
         {

    reg.modify().setAgreement(agreement).commit();
    LOG.info("Updated user's ToS");

    return true;
}

public Challenge tokenChallenge(Authorization auth)
{
    TokenChallenge chall = auth.findChallenge(TokenChallenge.TYPE);

    LOG.info("File name: " + chall.getType());
    //LOG.info("Content: " + chall.`);
    return chall;

}