Extract certificate from pkcs7

Hey!

I am new to Rust and I am currently working on an application which handles PKCS7 sign and verify. This is how I verify signedData from an PKCS7:

    let mut content = Vec::new();
    let pkcs7_vec = base64::decode(&content).unwrap();
    let pkcs7 = openssl::pkcs7::Pkcs7::from_der(&pkcs7_vec).expect("Unable to convert from DER");
    let _ = pkcs7
        .verify(&certs, &store, None, Some(&mut content), flags)
        .expect("Unable to verify PKCS7 file");

in content I have the raw data which works without any problem. But how can I extract the inner certificates? In openssl the command would be:

openssl pkcs7 -in file.pem -print_certs -out certs.pem

How can I achieve this with Rust?

Thank you in advance

Best regards,
Casi

You could parse the content using whatever parser most appropriate for the content? If it's certificates you probably want to use X509's from_pem or from_der.

Or what exactly do you mean by "inner certificates"?

Thank you for your reply!

For example, my PEM encoded pkcs7 looks like this:

-----BEGIN PKCS7-----
    MIIFrgYJKoZIhvcNAQcCoIIFnzCCBZsCAQExDzANBglghkgBZQMEAgEFADCCAkEG
    CSqGSIb3DQEHAaCCAjIEggIueyJ0ZWxlY29tIjpbeyJzeXN0ZW0iOiJwaG9uZSIs
    InZhbHVlIjoiMDMwLzQwMDQxMCIsInJhbmsiOjF9LHsic3lzdGVtIjoib3RoZXIi
    LCJ2YWx1ZSI6Imh0dHBzOi8vd3d3Lm1lZ2FhcG90aGVrZS5kZS9yZXNlcnZpZXJ1
    bmciLCJyYW5rIjoxMDB9LHsic3lzdGVtIjoib3RoZXIiLCJ2YWx1ZSI6Imh0dHBz
    Oi8vd3d3Lm1lZ2FhcG90aGVrZS5kZS9ib3RlbmRpZW5zdCIsInJhbmsiOjIwMH0s
[...]
-----END PKCS7-----

If I open up the PKCS7 data, I have this in it:

Certificate:
    Data:
         [...]
    Signature Algorithm: ecdsa-with-SHA256
         30:44:02:20:2e:da:b8:4f:ee:90:2d:9d:b6:6a:2f:e1:f5:73:
         17:17:43:10:18:b6:eb:f5:34:98:9b:dc:be:fd:a5:58:89:28:
         02:20:01:61:dd:22:cf:a8:be:31:5a:76:27:a0:cc:cd:b1:5c:
         fb:7b:e4:e2:96:a5:9c:e8:5c:3f:ef:1f:44:68:94:e8
-----BEGIN CERTIFICATE-----
MIIBhTCCASwCFBZuvh7Um5OcmxOOGR4Mv9ib8tY7MAoGCCqGSM49BAMCMEUxCzAJ
BgNVBAYTAkRFMRMwEQYDVQQIDApTb21lLVN0YXRlMQ0wCwYDVQQKDARlaGV4MRIw
EAYDVQQDDAlmZF9pZF9lbmMwHhcNMjAxMjA5MDk0MjI5WhcNMjMwOTA2MDk0MjI5
WjBFMQswCQYDVQQGEwJERTETMBEGA1UECAwKU29tZS1TdGF0ZTENMAsGA1UECgwE
[...]
-----END CERTIFICATE-----

Now I need only to extract the certificate from signedData so to say.

BR
Casi

From the docs, openssl::pkcs7 crate doesn't implement decoding the wrapped content, which makes some sense, as the RFC initially describes the format:

This document describes a general syntax for data that may have cryptography applied to it

and only at the end of the introduction notes:

A degenerate case of the syntax provides a means for disseminating certificates and certificate-revocation lists.

The degenerate case seems to be described in section 9, which describes the "Signed Data" content type, and notes:

the syntax has a degenerate case in which there are no signers on the content. The degenerate case provides a means for disseminating certificates and certificate-revocation lists.

and goes on to describe the encoded data with the ASN.1 schema:

   SignedData ::= SEQUENCE {
     version Version,
     digestAlgorithms DigestAlgorithmIdentifiers,
     contentInfo ContentInfo,
     certificates
        [0] IMPLICIT ExtendedCertificatesAndCertificates
          OPTIONAL,
     crls
       [1] IMPLICIT CertificateRevocationLists OPTIONAL,
     signerInfos SignerInfos }

which means it definitely is defined by PKCS #7, and not imported from some other ASN.1 module.

Since neither openssl nor the pkcs7 crate (written by the Rust Crypto team) implement the SignedData type, you'll probably have to give it a go yourself.

I suggest grabbing the der crate and start implementing Decode from the RFC syntax. I've found it's generally not too tough when I've messed with this before, for example, the above should look like (completely untested!):

#[derive(der::Sequence)]
struct SignedData {
  pub version: Version,
  pub digest_algorithms: DigestAlgorithmIdentifiers,
  pub content_info: ContentInfo,
  #[asn1(context_specific = "0")]
  pub certificates: Option<ExtendedCertificatesAndCertificates>,
  #[asn1(context_specific = "1")]
  pub crls: Option<CertificateRevocationLists>,
}

#[repr(u8)]
enum Version { V1 = 1 };

// Or you can define `AlgorithmIdentifier` yourself, it's even the `der` crate example!
type DigestAlgorithmIdentifiers = der::asn1::SetOfVec<spki::AlgorithmIdentifier>;

// This is presumably what "content" you get out of openssl::pkcs7::Pkcs7::verify(),
// which the docs imply is probably empty!
#[derive(der::Sequence)]
struct ContentInfo {
  pub content_type: der::asn1::ObjectIdentifier,
  #[asn1(context_specific = "0")]
  pub content: Option<der::asn1::Any>,
}

type ExtendedCertificatesAndCertificates = der::asn1::SetOfVec<ExtendedCertificateOrCertificate>;

#[derive(der::Choice)]
enum ExtendedCertificateOrCertificate {
  Certificate(x509::Certificate),
  // ExtendedCertificate(pkcs6::Certificate), // not implemented... 
}

Thank you very much! I'll go through it and report afterwards

One thing to remember here, is this is the whole PKCS#7 file, not the content. It's not clear that this SignedData payload is what you're getting, so you might have to start poking at the openssl asn1parse output to see what you're getting.

On the other hand, since this "degenerate case" has no signers, you probably can drop the openssl crate completely then.

1 Like