Skip to content

PKIResponse creation and decoding #1452

Description

@OrangeDog

I'm attempting to create a basic EST (RFC 7030) error response. If my reading of RFC 5272 is correct, then it should look like this:

$ echo "MD8CAQMxADAyBggrBgEFBQcMA6AmMCQwHjAcAgEBBggrBgEFBQcHGTENMAsCAQIwAwIBAQIBBzAAMACgAKEAMQA=" | openssl asn1parse
    0:d=0  hl=2 l=  63 cons: SEQUENCE
    2:d=1  hl=2 l=   1 prim: INTEGER           :03
    5:d=1  hl=2 l=   0 cons: SET
    7:d=1  hl=2 l=  50 cons: SEQUENCE
    9:d=2  hl=2 l=   8 prim: OBJECT            :id-cct-PKIResponse
   19:d=2  hl=2 l=  38 cons: cont [ 0 ]
   21:d=3  hl=2 l=  36 cons: SEQUENCE
   23:d=4  hl=2 l=  30 cons: SEQUENCE
   25:d=5  hl=2 l=  28 cons: SEQUENCE
   27:d=6  hl=2 l=   1 prim: INTEGER           :01
   30:d=6  hl=2 l=   8 prim: OBJECT            :1.3.6.1.5.5.7.7.25
   40:d=6  hl=2 l=  13 cons: SET
   42:d=7  hl=2 l=  11 cons: SEQUENCE
   44:d=8  hl=2 l=   1 prim: INTEGER           :02
   47:d=8  hl=2 l=   3 cons: SEQUENCE
   49:d=9  hl=2 l=   1 prim: INTEGER           :01
   52:d=8  hl=2 l=   1 prim: INTEGER           :07
   55:d=4  hl=2 l=   0 cons: SEQUENCE
   57:d=4  hl=2 l=   0 cons: SEQUENCE
   59:d=1  hl=2 l=   0 cons: cont [ 0 ]
   61:d=1  hl=2 l=   0 cons: cont [ 1 ]
   63:d=1  hl=2 l=   0 cons: SET

However, unlike SimplePKIResponse, BC does not appear to provide any convenient holder for this, only the raw ASN1 types. Additionally, CMSSignedData does not count the outer object as valid, as it lacks certificates.

As a result, the code to get the status is quite tedious, and would be even more so to account for there being other or different controls:

SignedData signedData = SignedData.getInstance(bytes);
PKIResponse pkiResponse = PKIResponse.getInstance(signedData.getEncapContentInfo().getContent());
TaggedAttribute attr = TaggedAttribute.getInstance(pkiResponse.getControlSequence().getObjectAt(0));
CMCStatusInfoV2 info = CMCStatusInfoV2.getInstance(attr.getAttrValues().getObjectAt(0));
CMCStatus status = info.getcMCStatus();

The code to build such a response on the server side is also tedious, and with no validity checks other than a size==3.

CMCStatusInfoV2 info = new CMCStatusInfoV2Builder(CMCStatus.failed, new BodyPartID(1))
        .setOtherInfo(CMCFailInfo.badIdentity)
        .build();  // good so far

TaggedAttribute attr = new TaggedAttribute(
        new BodyPartID(1), CMCObjectIdentifiers.id_cmc_statusInfoV2, new DERSet(info))
);
PKIResponse pkiResponse = PKIResponse.getInstance(new DERSequence(new ASN1Encodable[]{
        new DERSequence(attr), new DERSequence(), new DERSequence()
});
SignedData err = new SignedData(
        new DERSet(),
        new ContentInfo(CMCObjectIdentifiers.id_cct_PKIResponse, pkiResponse),
        new DERSet(),
        new DERSet(),
        new DERSet()
);

Have I missed something, either in my understanding of what the structure should be, or some classes that make this easier to deal with? If not, then can such things be added, and ideally integrated into ESTService?

Activity

  1. dghgit commented on Sep 10, 2023

    @dghgit
    Contributor

    CMSSignedData expects a ContentInfo - as a SignedData is (usually) always wrapped in a ContentInfo unless it's specified specifically in some other ASN.1 structure. "application/pkcs7-mime" which I think is what you are referring to requires the use of a ContentInfo though. Also encapsulated contentinfo (which is where the ContentInfo object is) should be an OCTET STRING. I have a feeling this should be using the CMSSignedDataGenerator though.

    That said, we're certainly interested in ways to improve the API, and occasionally resourcing has meant we've only had time for what is necessary as opposed to what would also be useful. I've had a go at PKIResponse, if you have any further feedback let me know.

  2. OrangeDog commented on Sep 10, 2023

    @OrangeDog
    Author

    Let me just grab the relevant bits for reference:

    The Full PKI Response consists of a SignedData or AuthenticatedData encapsulating a PKIResponse content type.

    PKIResponse ::= SEQUENCE {
        controlSequence   SEQUENCE SIZE(0..MAX) OF TaggedAttribute,
        cmsSequence       SEQUENCE SIZE(0..MAX) OF TaggedContentInfo,
        otherMsgSequence  SEQUENCE SIZE(0..MAX) OF OtherMsg
    }
    

    Response from server to client:

    ContentInfo.contentType = id-signedData
    ContentInfo.content
      SignedData.encapContentInfo
        eContentType = id-ct-PKIResponse
        eContent
          controlSequence
            {102, id-cmc-statusInfoV2, {success, 201}}
            {103, id-cmc-senderNonce, 10005}
            {104, id-cmc-recipientNonce, 10001}
      certificates
        Newly issued certificate
        Other certificates
      SignedData.SignerInfos
        Signed by CA
    

    In an error response, the certificates would be empty.

  3. dghgit commented on Sep 10, 2023

    @dghgit
    Contributor

    So that looks right. It's the ContentInfo that you want to pass to the CMSSignedData object though (in PKCS land it's the ContentInfo OID that tells everyone what to expect - empty certificates should be fine though. You would want to use a CMSSignedDataGenerator if a SignedData based response is required.

    Can you tell me what error you're getting due to the lack of certificates?

  4. OrangeDog commented on Sep 11, 2023

    @OrangeDog
    Author

    Can you tell me what error you're getting due to the lack of certificates?

    This is what it is now. I don't remember why I reached that conclusion. Perhaps that was an exception from CMSSignedDataGenerator instead.

    public class Test {
        public static void main(String... args) throws Exception {
            new CMSSignedData(Base64.getDecoder().decode(
                "MD8CAQMxADAyBggrBgEFBQcMA6AmMCQwHjAcAgEBBggrBgEFBQcHGTENMAsCAQIwAwIBAQIBBzAAMACgAKEAMQA="
            ));
        }
    }
    Exception in thread "main" org.bouncycastle.cms.CMSException: Malformed content.
    	at org.bouncycastle.cms.CMSUtils.readContentInfo(Unknown Source)
    	at org.bouncycastle.cms.CMSUtils.readContentInfo(Unknown Source)
    	at org.bouncycastle.cms.CMSSignedData.<init>(Unknown Source)
    	at Test.main(Test.java:7)
    Caused by: java.lang.IllegalArgumentException: Bad sequence size: 6
    	at org.bouncycastle.asn1.cms.ContentInfo.<init>(Unknown Source)
    	at org.bouncycastle.asn1.cms.ContentInfo.getInstance(Unknown Source)
    	... 4 more
    

    we're certainly interested in ways to improve the API

    The immediate problem with what you already have is that, if any ESTService methods fail, there's no simple way to find out why.

  5. dghgit commented on Sep 11, 2023

    @dghgit
    Contributor

    It's always a bit tricky when you're only dealing with ASN.1 encodings. The message is not a ContentInfo, I think it's just a SignedData for a id-cct-PKIResponse. Are you saying it came back without the encapsulating ContentInfo?

  6. OrangeDog commented on Sep 11, 2023

    @OrangeDog
    Author

    CMSSignedData expects a ContentInfo

    I missed that bit earlier, sorry. The message is err.getEncoded() from the example at the top.

    My confusion here is that the example from the RFC shows a SignedData inside a ContentInfo, but the org.bouncycastle.asn1.cms package implements ContentInfo inside a SignedData. But I'm relatively new to all this so maybe I'm reading it wrong?

    Is a PKIResponse therefore supposed to be inside a ContentInfo inside a SignedData inside another ContentInfo?

    If I try building that with CMSSignedDataGenerator, I get this (the data is missing):

    byte[] responseBody = new CMSSignedDataGenerator()
            .generate(new PKCS7ProcessableObject(CMCObjectIdentifiers.id_cct_PKIResponse, pkiResponse))
            .getEncoding();
        0:d=0  hl=2 l=  34 cons: SEQUENCE
        2:d=1  hl=2 l=   9 prim: OBJECT            :pkcs7-signedData
       13:d=1  hl=2 l=  21 cons: cont [ 0 ]
       15:d=2  hl=2 l=  19 cons: SEQUENCE
       17:d=3  hl=2 l=   1 prim: INTEGER           :03
       20:d=3  hl=2 l=   0 cons: SET
       22:d=3  hl=2 l=  10 cons: SEQUENCE
       24:d=4  hl=2 l=   8 prim: OBJECT            :id-cct-PKIResponse
       34:d=3  hl=2 l=   0 cons: SET
    

    Constructing the ASN1 object does work though:

    byte[] responseBody = new ContentInfo(CMSObjectIdentifiers.signedData, signedData).getEncoding();

    That adds even more steps to the process, further calling out for some more org.bouncycastle.cmc classes and/or expanding org.bouncycastle.est.ESTResponse to be able to return CMCStatus.

  7. winfriedgerlach commented on Dec 13, 2024

    @winfriedgerlach
    Collaborator

    @OrangeDog @dghgit can this issue be closed?

  8. OrangeDog commented on Dec 13, 2024

    @OrangeDog
    Author

    There are still no classes for building or querying these objects, other than manually dealing with the complex ASN1 tree.

    Rather than all the code above, some additional constructors / accessors could be added to SimplePKIResponse (or some new classes in the same package) to handle CMCFailInfo.

    ESTException in particular should them in order to surface the failure reason from its body.

  9. dghgit commented on May 3, 2026

    @dghgit
    Contributor

    I've added a more complete SimplePKIResponse class as well as a PKIResponseBuilder class for constructing them. These are available on https://www.bouncycastle.org/betas and should appear in github shortly.

  10. dghgit commented on May 11, 2026

    @dghgit
    Contributor

    Closing as assuming current updates are doing the job. If there's a shortfall in the new ones as well, please open another issue.

  11. OrangeDog commented on May 11, 2026

    @OrangeDog
    Author

    Are the betas in a public Maven repo somewhere?

  12. dghgit commented on May 11, 2026

    @dghgit
    Contributor

    Not yet, sorry, publishing to central is a manual process for us, the best I can offer at the moment is the https://www.bouncycastle.org/betas the source should now be synced up to bc-java on github as well.

  13. OrangeDog commented on May 11, 2026

    @OrangeDog
    Author

    The APIs look good, but a small note: As the CMCStatusInfoV2 already contains the BodyPartID information, I don't think addStatusInfoV2 should need a separate parameter for it?

    I also realise I only asked for error support in this issue, but the new PKIResponseBuilder should probably also have addCertificate to similarly streamline the success case instead of e.g.

    CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
    gen.addCertificate(new X509CertificateHolder(cert.getEncoded()));
    CMSSignedData signed = gen.generate(new CMSAbsentContent());
    return new SimplePKIResponse(signed.getEncoded());
  14. dghgit commented on May 11, 2026

    @dghgit
    Contributor

    Good points. I've updated the snapshot, addCertificate is there now. There's an override for addStatusInfoV2 to simplify things, if I read correctly technically it's allowed, although at the moment I'm not sure why, but then I've felt like that about RFCs before. Betas area is updated with the new version.

  15. OrangeDog commented on May 11, 2026

    @OrangeDog
    Author

    I can delete so much code 😄

  16. dghgit commented on May 11, 2026

    @dghgit
    Contributor

    That's what we're here for! Let me know if there's anything still that looks unnecessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions