asn1crypto icon indicating copy to clipboard operation
asn1crypto copied to clipboard

Make direct accessor from cms.SignerInfo to the signing certificate

Open erny opened this issue 7 years ago • 9 comments

The SignerInfo['sid'] (SignerIdentifier) holds normally a pointer into the Certificates collection inside a CMS Structure. The sames as x509.Certificate has some shortcuts for subject, issuer, etc. there should be a shortcut to get the signers certificate.

erny avatar Apr 09 '18 16:04 erny

Technically, SignerInfo['sid'] cannot point at any Certificate. They are in a different branch of the SignedData object. Building something into SignedData to create this link feels wrong to me. Also there is no guarantee that a CMS includes the signer cert (rfc5652 allows to omit it).

However, a SignedData object could have such an accessor, could take an index into the signer_infos list and return the matching Certificate. Or the SignerInfo could get a method which takes a list of certificates and returns the matching one.

@erny can you suggest an interface?

joernheissler avatar Jun 30 '18 23:06 joernheissler

Reviewing RFC 5652 explains that:

SignedData ::= SEQUENCE {
    version CMSVersion,
    digestAlgorithms DigestAlgorithmIdentifiers,
    encapContentInfo EncapsulatedContentInfo,
    certificates [0] IMPLICIT CertificateSet OPTIONAL,
    crls [1] IMPLICIT RevocationInfoChoices OPTIONAL,
    signerInfos SignerInfos }

In other words, certificates is a collection of certificates, and signerInfos are really signature infos for the different signers (a sequence of signature infos):

SignerInfo ::= SEQUENCE {
    version CMSVersion,
    sid SignerIdentifier,
    digestAlgorithm DigestAlgorithmIdentifier,
    signedAttrs [0] IMPLICIT SignedAttributes OPTIONAL,
    signatureAlgorithm SignatureAlgorithmIdentifier,
    signature SignatureValue,
    unsignedAttrs [1] IMPLICIT UnsignedAttributes OPTIONAL }

and the sid is of type:

SignerIdentifier ::= CHOICE {
    issuerAndSerialNumber IssuerAndSerialNumber,
    subjectKeyIdentifier [0] SubjectKeyIdentifier }

The spec states that:

sid specifies the signer's certificate (and thereby the signer's public key). The signer's public key is needed by the recipient to verify the signature. SignerIdentifier provides two alternatives for specifying the signer's public key. The issuerAndSerialNumber alternative identifies the signer's certificate by the issuer's distinguished name and the certificate serial number; the subjectKeyIdentifier identifies the signer's certificate by a key identifier. When an X.509 certificate is referenced, the key identifier matches the X.509 subjectKeyIdentifier extension value. When other certificate formats are referenced, the documents that specify the certificate format and their use with the CMS must include details on matching the key identifier to the appropriate certificate field. Implementations MUST support the reception of the issuerAndSerialNumber and subjectKeyIdentifier forms of SignerIdentifier. When generating a SignerIdentifier, implementations MAY support one of the forms (either issuerAndSerialNumber or subjectKeyIdentifier) and always use it, or implementations MAY arbitrarily mix the two forms. However, subjectKeyIdentifier MUST be used to refer to a public key contained in a non-X.509 certificate.

For this reason, I do not understand your statement:

Technically, SignerInfo['sid'] cannot point at any Certificate.

The spec states this explicitly.

If a signer certificate is not included in the certificates collection, Signature validation can't be done under normal circumstances as there is no global method to retrieve a user certificate (including the signer's public key) by issuerAndSerialNumber or subjectKeyIdentifier.

In Long Term Signature enabled signatures, the complete OCSP / CRL checks and certificate chains are included in the CAdES structure to allow complete off-line signature validation (i.e., we must include all validation info, including the signer certificates with the complete certificate chains, OCSP responses, and OCSP certificates chains).

In the case, we can't find the certificate in the certificates collection, we could return None.

erny avatar Jan 26 '19 00:01 erny

A SignerInfo object does not hold (contain? include?) any Certificate. Neither does a SignerIdentifier object. Maybe "cannot point at any Certificate" was badly worded. I meant "point" like "memory address" or better "object reference".

If I gave you a SignerInfo object and nothing else, there would be no way whatsoever to retrieve the matching Certificate. Because it's somewhere else in the SignedData object, which I didn't give you.

joernheissler avatar Jan 26 '19 07:01 joernheissler

Interesting. Could you suggest an interface to do that?

My tests do something like this:

with open(os.path.join(fixtures_dir, 'cades-bes-implicit.der'), 'rb') as f:
    info = cades.ContentInfo.load(f.read())
self.assertEqual(
    'signed_data',                  # type signed_data
    info['content_type'].native
)
...

So we could continue, e.g., like this (?):

for signer in info.signers:
    certificate = signer.certificate
    signer_info = signer.info
...

erny avatar Jan 30 '19 17:01 erny

@erny Hello, I have the same requirement as you. I want to find the corresponding certificate based on signer_info. This problem was raised three years ago. Have you found the right solution now?

@joernheissler I looked at the parsing of digital signatures by the C# library developed by Microsoft. There is a property named "Certificate" in class SignerInfo. Here is the document: https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.pkcs.signerinfo?view=dotnet-plat-ext-6.0&viewFallbackFrom=net-5.0. However, the specific implementation logic is not explained in the document. I guess the corresponding certificate is also found based on the sid.

In code design, if as a attribute is not appropriate, can it be designed as an external function to implement this requirement?

jiliguluss avatar Feb 26 '22 01:02 jiliguluss

I don't think doing this as an attribute on SignerInfo is a good idea. As @joernheissler already explained, CMS objects only store certificates higher up in the SignedData, and SignerInfo objects don't "know" about any certificates as such.

But that's not even the main issue here, IMO. Sure, in the vast majority of cases, SignedData payloads have only one SignerInfo value, and that SignerInfo value's sid will match a unique certificate in the certificates field of the signed data. In those cases, I guess it would be possible to implement a function that takes a SignedData value and spits out a certificate. However, in at least the following three scenarios, that's not possible:

  • There are multiple SignerInfo objects in the SignedData
  • The SignedData doesn't include any certificates at all, and the driving application is supposed to fetch a matching certificate from its internal certificate store, or from somewhere else.
  • The sid matches more than one certificate in the SignedData, e.g. with the same public key but different chains of trust (this is a bit contrived and it's questionable practice, but it's not impossible/disallowed)

Note that the certificates field of the SignedData isn't actually covered by the signature, so it's even plausible that certificates may have been added or removed in post-processing. And in addition to all of the above, there are signed attributes that further constrain certificate selection in some signature standards.

I'm not saying that you have to support all of that, but given how low asn1crypto sits in the stack, I'm not sure it's a good idea to make so many assumptions on the internal structure of the CMS object. You're probably better off implementing it yourself; the "works most of the time" implementation isn't all that complicated.

This is more or less the way I currently do it in one of my own projects, FWIW: https://github.com/MatthiasValvekens/pyHanko/blob/c7d41dc638b1e7a03d60814a7c5c47ee17897a96/pyhanko/sign/general.py#L464-L495.


TL;DR: is it possible to write a function that takes a SignedData object and outputs a unique certificate for most uses of SignedData? Sure. Does it have a place in a generic ASN.1 processing library? I'm not convinced that that's the case, but I'm open to having my mind changed.


EDIT: I just now read this suggestion from @joernheissler further up in the thread:

Or the SignerInfo could get a method which takes a list of certificates and returns the matching one.

This seems like a much more sensible interface, because it also pushes the burden to deal with non-matches onto the caller.

MatthiasValvekens avatar Feb 26 '22 08:02 MatthiasValvekens

@MatthiasValvekens Thank you for your detailed analysis and explanation. I can understand that as an attribute is not a good idea. However, as an invoker, parsing the CMS message may be only the first step. Then, the parsed signature and certificate may need to be checked or verified, like what "CheckSignature" do in Microsoft library "System.Security.Cryptography.Pkcs.dll". I think in the Python environment, it seems better if there is a similar python library to implement the whole process. Otherwise, calling the .net library in Python seems not be pythonic.

jiliguluss avatar Feb 28 '22 02:02 jiliguluss

Sure, I'm well aware of the overall complexity of both certificate validation and CMS signature validation, since I'm also doing work in that area in my own projects. Both of those can be very involved processes: X.509 certificate validation has a whole slew of parameters, and for CMS validation, the content of RFC 5652 is really only half the story (e.g. the AdES family of signature standards introduce lots of extra requirements).

To the best of my knowledge, there's currently no single Python library that implements all of that, but either way, a generic verify(...) function that "just works" no matter what CMS signed data you throw at it pretty much can't exist. Ultimately, X.509 and CMS are format standards that aren't designed as "batteries included" specs. The expectation is that they are only fit for purpose when supplemented with profiles & policy documents that outline precisely what constitutes a valid certificate/signature. There's no single right answer.

The CheckSignature function in the .NET API just fills in the system's default trust parameters, which may or may not be appropriate for the use case you want. The documentation is also very opaque, so it doesn't even tell you what kind of checks it is performing (Revocation status? How? Timestamps? Which CMS attributes are checked? Which X.509 extensions are supported? Which signing algorithms are supported? etc.). There's a certain baseline level of complexity that's very hard to avoid here... :/


Anyhow, speaking purely for myself, I'd say that doing that sort of validation is way beyond the scope of asn1crypto. For one, it would require a bunch of dependencies to handle cryptographic operations & network I/O, which goes beyond the niche it currently occupies as a dependency-free "low-level" ASN.1 processing tool. However, there are some projects out there that implement certain parts of the CMS validation process:

  • @wbond's certvalidator. Implements X.509 certificate validation with support for most of the things in RFC 5280 and RFC 6960.
  • My own pyHanko project (which relies on a fork of the above) is mostly geared towards CMS as used in PDF signatures, but also has some fairly generic CMS validation capabilities. At the moment, they're not aligned with AdES requirements (ETSI EN 319 102-1), though, that happens to be what I'm working on right now. :)

(Note: obviously these are just the ones that I happen to be familiar with, I'm certainly not claiming that this list is exhaustive :) )


TL;DR: It's complicated, and I don't think asn1crypto will ever offer any sort of comprehensive CMS validation functionality. At best, it could provide a certificate extraction helper function that "usually works" (see the last part of my previous comment), but that's probably it.

MatthiasValvekens avatar Feb 28 '22 07:02 MatthiasValvekens

@MatthiasValvekens Thank you very much. I'm curious about what CheckSignature verifies, but I only find less of instructions in .NET API document. After reading your explanation, I think it's a very complicated process.

jiliguluss avatar Mar 02 '22 10:03 jiliguluss