0
$\begingroup$

I need to use Azure Key Vault HSM to sign arbitrary data in a PKCS#7 CMS. There are plenty of libraries and packages that can do this if you supply them with the private key, but obviously I don't want to export the private key from the HSM, so I've built my own PKCS#7 object in C# using Asnwriter.

I'm using the ITU-T spec as a reference: https://www.itu.int/ITU-T/formal-language/itu-t/x/x420/1999/PKCS7.html#PKCS7.signed-data

Everything was going well until I got to the "encryptedDigest" section of the SignerInfo. I need to know how this is constructed so I can inject it into my PKCS#7.

I've tried so many different ways of building this based on my understanding of the reference above, the relevent RFC's and tons of similar suggestions found on forums like this, but I can never create a "valid" signature.

From my latest understanding the Digest Info is constructed something like this:

DigestInfo Sequence {
    DigestAlgorithm Sequence{
        algorithm OID;
    }
    Digest Sequence {
        AlgorithmIdentifier Sequence {
            algorithm OID;
        }
        hashValue BitString;
    }
}

Where hashValue is a hash of the authenticatedAttributes (which includes its self a hash of data to be signed(both hashed in SHA256))

This whole DigestInfo object is BER encoded and then encrypted using the private key according to RSAES‐PKCS‐v1_5 and put in an OctetString at the end of the PKCS#7.

When I import my PKCS#7 into a verification tool, it all looks correct and the whole structure of the PKCS#7 matches that of an example PKCS#7 I have that has a valid signature. However when I try and validate the signature of my PKCS#7, it comes back as invalid.

I'm assuming the data in my hashValue is wrong (I have seen posts about not including the ASN1Tag [0] and length of the authenticatedAttributes in this, but that still didn't work) or my structure of the DigestInfo above is wrong, perhaps I need to write null bytes for the optional parameters in the algorithms?

Any insight would be greatly appreciated! EDIT================================================

That is a lot of useful information dave, much appreciated. However I'm still generating invalid signatures. I've amended my DigestInfo object based on the RFC's you referenced including the Null bytes for the parameters value and made it so everything is DER:

Base64 of actual object with data: MDEwDQYJYIZIAWUDBAIBBQAEIFFbecM/Y/Kw1qRLyqJn4VfXLoHebSpj9bVSy2V77pdc

DigestInfo Sequence {
    DigestAlgorithm Sequence{
        algorithm OID (SHA256: 2.16.840.1.101.3.4.2.1);
        parameters NULL;
    }
    digest OctetString (bytes in base64 below hashed);
}

And I swapped the A0 byte for the 31 of the SET tag, here is the base 64 of the ASN1 I am hashing(prehash):

MWkwGAYJKoZIhvcNAQkDMQsGCSqGSIb3DQEHATAcBgkqhkiG9w0BCQUxDxcNMjIwOTEwMTExNjE4WjAvBgkqhkiG9w0BCQQxIgQg0dbdNJbW+Q5yalayQ0T4oBRhfvsTBtGEBLtSBf7C7PM=

So providing all of that is correct this issue lies with how I'm actually signing, and as this is occuring on Azure, I don't have a lot of control over it. I'm currently asking Azure to sign the bytes of the ASN1 digestInfo (top base64) using RS256 - RSASSA-PKCS-v1_5 using SHA-256. It seems Azure hashes the digestInfo object to make it 32bytes then signs it using the private key stored on the HSM.

$\endgroup$
3
  • $\begingroup$ If RS256 means the Jose/JWS/JWA designation, that is indeed RSASSA-PKCS1-v1_5 with SHA256, and it would be correct to apply it to the signedAttrs directly, i.e. it will do the hash and generate the DigestInfo (plus the 0001FF...00) as part of its implementation of EMSA-PKCS1-v1_5. Whether the to-be-signed data should be in base64 depends on the interface you are using which I don't know anything about. $\endgroup$ Commented Sep 11, 2022 at 3:05
  • $\begingroup$ Jackpot, the documentation around Azure HSM is flakey at best. I was making the mistake of hashing the encoded SignedAttributes myself, then passing that hash into various encrypt/sign algorithms to generate the signature. It is worth noting your previous comment definately helped. As I have also just tried reproducing a valid signature using the ASN [0] tag on the signed data rather than the SET and excluding the null bytes and they all fail. So you got me there in the end and for that I am very greatful! :) $\endgroup$ Commented Sep 11, 2022 at 12:04
  • $\begingroup$ By the by, you can use the built in SignedCms for what you want, you just need the RSAKeyVaultProvider package from NuGet (or any compatible component). $\endgroup$
    – bartonjs
    Commented Sep 12, 2022 at 5:18

2 Answers 2

2
$\begingroup$

ITU may provide information about PKCS (and CMS) but they are not the author. PKCS7 (like all PKCS) was originally from RSALabs and now remains available as rfc2315; several versions of CMS -- which starts out almost but not exactly the same, and then progressively adds new features -- are only RFCs: rfc2630 rfc3369 rfc3852 rfc5652.

First of all, 'encrypted digest' and 'digest encryption' is a misnomer: signature is NOT encryption; see my list of links about this next door at https://security.stackexchange.com/questions/159282/ . This terminology was an attempt to be clever in PKCS1 v1 that backfired; it was corrected in PKCS1 v2 (2.0, 2.1, and 2.2) but PKCS7 had already been superseded by CMS which also corrected it.

In particular RSAES-PKCS1-v1_5 is not used here; rather PKCS7 signature uses what was called "block type 1" in PKCS1 v1 -- also republished as rfc2313 though not referenced in rfc2315 -- and retronymed RSASSA-PKCS1-v1_5 in PKCS1 v2 (RFCs 2437, 3447, 8017) which are used in most implementations now. (CMS continues to use RSASSA-PKCS1-v1_5 for RSA, but adds other non-RSA algorithms, which RSALabs didn't have motivation to do when it had responsibility.)

The DigestInfo structure is specified in detail in section 9.4 in rfc2315; this is (and says it is) a copy of PKCS1, specifically section 10.1.2 in rfc2313, and is not at all what you have. PKCS1 v2 changed the presentation of this material (more than once!) and for example rfc3447 section 9.2 shows how the DER (not just BER) encoding of DigestInfo is actually just a prefix which depends only on the hash algorithm concatenated with the hash value. (Since signature depends on having exactly the same bits, and the whole point of DER is to produce the same bits suitable for signature, it's not at all clear why v1 only specified BER for data so small no sane person would ever want indefinite encoding anyway. PKCS1 v2.0 only a few months after PKCS7 changed to DER, without even flagging it as a change.)

Over the lifetime of X.509 it has not been consistently clear whether the AlgorithmIdentifier for an algorithm with no parameters should omit them or explicitly encode NULL, and this has caused quite a few problems. For the DigestInfo in PKCS1 RSA signature rfc3447 et seq shows them as explicitly NULL and that's what everybody does.

I have seen posts about not including the ASN1Tag [0] and length of the authenticatedAttributes in [the hash input]

The correct formulation is that you should hash the encoding of authenticatedAttributes (renamed signedAttributes in CMS) without the 'implicit context-0' tag but WITH its 'natural' SET tag -- i.e. 31 instead of A0. Perhaps you or someone got this confused with the hashing of eContent which DOES exclude the tag and length. Compare (disclosure: my) PKCS#7 CMS - Message digest calculation process .

$\endgroup$
4
  • $\begingroup$ Hey dave, I've updated my post to include changes I've made from the information you provided, however I'm still getting an invalid signature, but I'm sure I'm a lot closer, if you could take a look that would be amazing! $\endgroup$ Commented Sep 10, 2022 at 12:22
  • $\begingroup$ I don't know about Azure's HSM, nor specifically what you are asking it (IME there are usually many options) -- but this is approaching the area of a problem with specific code or library implementing a crypto algorithm, which is likely out of scope. But see one comment on the Q. Also as generic applicable techniques, you may be able to export a test-only or example key, and match the HSM operations to more visible and debuggable software operations; or conversely IME HSM's are sometimes more amenable to importing and using a key already defined in software. $\endgroup$ Commented Sep 11, 2022 at 3:06
  • $\begingroup$ @user15045901 did you get this working? $\endgroup$ Commented Dec 21, 2022 at 20:15
  • $\begingroup$ @IgalFlegmann Yup, thanks to dave's guidance and a bit more tweaking everything is working great. $\endgroup$ Commented Dec 23, 2022 at 14:04
0
$\begingroup$

Have you had a look at https://github.com/clairernovotny/SignedCmsKeyVault/ which does cms with azure keyvault keys?

$\endgroup$
1
  • $\begingroup$ From glancing at the code, that seems to export the private key of the certificate, which may be fine for normal keyvault certificate operations. However, for regulatory compliance reasons, I needed to use "HSM" which means all cryptographic operations happen on the hardware in the datacentre and do not expose the private key outside of that hardware. $\endgroup$ Commented Oct 6, 2022 at 8:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.