向我生成的证书添加新扩展

问题描述 投票:1回答:4

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

证书扩展:10 [1]:ObjectId:1.3.6.1.5.5.7.1.26 Criticality = false 扩展名未知:DER编码的OCTET字符串= 0000:04 0C 30 0A 13 08 33 39 20 64 63 20 32 62 ..0 ... 39 dc 2b

我希望这个OID被识别类似于其他扩展,如AuthorityInfoAccess等。

我是否需要编辑Bouncy Castle X509类的罐子?

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

以下是用于注册证书的CSR Builder代码。

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();;
    }
}
ssl-certificate x509certificate bouncycastle csr java-security
4个回答
1
投票

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

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

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

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

请注意,TNEntry是一个选择,TNAuthorizationList是一系列TNEntry对象。所以your class name应该改为TNEntry。在下面的代码中,请记住我已将类名更改为TNEntry

我也改变了这堂课的一些东西。在getInstance(Object obj)方法中,spc和range字段的类型不正确(根据ASN1定义,它们都是序列):

switch (tag) {
    case spc:
    case range: // both are sequences
        return new TNEntry(tag, ASN1Sequence.getInstance(tagObj, false));
    // not sure about "one" field, as it's not tagged
}

我只是不知道如何处理这个字段,因为它没有标记。也许它应该是一个DERIA5String,或者也许还有另一种类型的“无标记”选择。

在同一个类中(请记住,我已将其名称更改为TNEntry),我还删除了构造函数public TNEntry(int tag, String name),因为我不确定它是否适用(至少我不需要使用它,但是你可以保留它,如果你想要),我已经改变了toString方法来返回一个更易读的字符串:

public String toString() {
    String sep = System.getProperty("line.separator");
    StringBuffer buf = new StringBuffer();

    buf.append(this.getClass().getSimpleName());
    buf.append(" [").append(tag);
    buf.append("]: ");
    switch (tag) {
        case spc:
            buf.append("ServiceProviderCodeList: ").append(sep);
            ASN1Sequence seq = (ASN1Sequence) this.obj;
            int size = seq.size();
            for (int i = 0; i < size; i++) {
                // all elements are DERIA5Strings
                DERIA5String str = (DERIA5String) seq.getObjectAt(i);
                buf.append("    ");
                buf.append(str.getString());
                buf.append(sep);
            }
            break;

        case range:
            buf.append("TelephoneNumberRange: ").append(sep);

            // there are always 2 elements in TelephoneNumberRange
            ASN1Sequence s = (ASN1Sequence) this.obj;
            DERIA5String str = (DERIA5String) s.getObjectAt(0);
            buf.append("    start: ");
            buf.append(str.getString());
            buf.append(sep);
            ASN1Integer count = (ASN1Integer) s.getObjectAt(1);
            buf.append("    count: ");
            buf.append(count.toString());
            buf.append(sep);
            break;

        default:
            buf.append(obj.toString());
    }

    return buf.toString();
}

我还创建了一个TNAuthorizationList类,它保存了TNEntry对象的序列(记住我已经将你的类名改为TNEntry,所以这个TNAuthorizationList类是另一个)。请注意,我还创建了一个常量来保存OID(只是为了让事情变得更容易):

public class TNAuthorizationList extends ASN1Object {
    // put OID in a constant, so I don't have to remember it all the time
    public static final ASN1ObjectIdentifier TN_AUTH_LIST_OID = new ASN1ObjectIdentifier("1.3.6.1.5.5.7.1.26");

    private TNEntry[] entries;

    public TNAuthorizationList(TNEntry[] entries) {
        this.entries = entries;
    }

    public static TNAuthorizationList getInstance(Object obj) {
        if (obj instanceof TNAuthorizationList) {
            return (TNAuthorizationList) obj;
        }
        if (obj != null) {
            return new TNAuthorizationList(ASN1Sequence.getInstance(obj));
        }

        return null;
    }

    public static TNAuthorizationList getInstance(ASN1TaggedObject obj, boolean explicit) {
        return getInstance(ASN1Sequence.getInstance(obj, explicit));
    }

    private TNAuthorizationList(ASN1Sequence seq) {
        this.entries = new TNEntry[seq.size()];

        for (int i = 0; i != seq.size(); i++) {
            entries[i] = TNEntry.getInstance(seq.getObjectAt(i));
        }
    }

    public TNEntry[] getEntries() {
        TNEntry[] tmp = new TNEntry[entries.length];
        System.arraycopy(entries, 0, tmp, 0, entries.length);
        return tmp;
    }

    @Override
    public ASN1Primitive toASN1Primitive() {
        return new DERSequence(entries);
    }

    public String toString() {
        String sep = System.getProperty("line.separator");
        StringBuffer buf = new StringBuffer();

        buf.append(this.getClass().getSimpleName());
        buf.append(":").append(sep);
        for (TNEntry tnEntry : entries) {
            buf.append("  ");
            buf.append(tnEntry.toString());
            buf.append(sep);
        }
        return buf.toString();
    }
}

现在,要将此扩展添加到证书中,我已完成此代码(包含一些示例数据,因为我不知道在现实世界中每个字段应该是什么):

X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(etc...);

// create TNEntries for TNAuthorizationList
TNEntry[] entries = new TNEntry[2];

// create a "spc" entry
DERIA5String[] cList = new DERIA5String[] { new DERIA5String("spc1"), new DERIA5String("spc2") };
DERSequence spc = new DERSequence(cList);
entries[0] = TNEntry.getInstance(new DERTaggedObject(false, TNEntry.spc, spc));

// create a "range" entry
DERSequence range = new DERSequence(new ASN1Encodable[] { new DERIA5String("123456"), new ASN1Integer(1) });
entries[1] = TNEntry.getInstance(new DERTaggedObject(false, TNEntry.range, range));

TNAuthorizationList tnAuthList = new TNAuthorizationList(entries);
builder.addExtension(TNAuthorizationList.TN_AUTH_LIST_OID, false, tnAuthList);

获得证书对象(在我的示例中为X509Certificate)后,您可以执行以下操作:

// cert is a X509Certificate instance
ASN1Primitive value = X509ExtensionUtil.fromExtensionValue(cert.getExtensionValue(TNAuthorizationList.TN_AUTH_LIST_OID.getId()));
TNAuthorizationList authList = TNAuthorizationList.getInstance(value);
System.out.println(authList.toString());

输出将是:

TNAuthorizationList:
  TNEntry [0]: ServiceProviderCodeList: 
    spc1
    spc2

  TNEntry [1]: TelephoneNumberRange: 
    start: 123456
    count: 1

笔记:

  • 正如我所说,这段代码是不完整的,因为我不知道如何处理TNEntry的一个字段,因为它没有标记(我不知道它是否必须是DERIA5String或者是否有另一种类型的对象为“未标记” “场”。
  • 你也可以做一些改进: ServiceProviderCodeList可以有1到3个元素,因此您可以验证其大小 TelephoneNumberRange:起始字段有一个特定的格式(FROM ("0123456789#*"),我认为这意味着只接受这些字符),所以你也可以验证它 要创建ServiceProviderCodeListTelephoneNumberRange的值,我已经手动创建了DERSequence对象,但是如果需要,可以为它们创建自定义类:ServiceProviderCodeList可以包含DERIA5String列表并在其构造函数中执行适当的验证(大小从1到1) 3),和TelephoneNumberRange可以有起始和计数字段(正确验证起始值) - 并且toASN1Primitive只需要以正确的顺序返回其字段的DERSequence

对于你的parsing issues,我检查了acme4j code,它使用了java.security.cert.X509Certificate类。此类的toString()方法(当使用Sun的默认提供程序时)生成此“扩展名未知”输出(根据相应的code)。

因此,为了正确解析它(显示如上所述的格式化输出),您可能必须更改acme4j的代码(或编写自己的代码),创建新的toString()方法并在此方法中包含新的TNAuthorizationList类。

当您提供显示如何使用acme4j的代码时,如果需要,我会相应地更新此答案。


1
投票

由于OID 1.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#*"))

扩展值必须是SEQUENCETNEntry。所以你可以使用ASN1Sequence(或它的子类DERSequence)并将TNEntry的实例放入其中。

要创建一个TNEntry,你需要实现ASN1Choice(看一下GeneralName类的来源并做类似的事情)。

依此类推,直到你将整个结构映射到各自的类,使用Bouncy Castle内置类来支持你(DERIA5StringIA5StringDERIntegerINTEGER,可用于ServiceProviderCodeListTelephoneNumberRange

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


1
投票

现在,出于测试目的,我只是从我的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结构解析此问题?


0
投票

这是我如何使用ACME4j。

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;

}
© www.soinside.com 2019 - 2024. All rights reserved.