botan icon indicating copy to clipboard operation
botan copied to clipboard

DER encoded PKCS#7 SignedData with Botan::DER_Encoder

Open w31mann opened this issue 5 years ago • 9 comments

Hi,

I'm currently trying hard to construct a DER encoded PKCS#7 version 1.5 SignedData structure (RFC2315) using Botan::DER_Encoder and could need some help with the encoding.

This is what I have sor far:

std::vector<std::uint8_t> signedData = Botan::DER_Encoder()                                                                                                                                                         
        .start_cons( Botan::ASN1_Tag::SEQUENCE )                                                    
            .encode( static_cast<size_t>( 1 ) ) // version                                          
            .start_cons( Botan::ASN1_Tag::SET ) // digestAlgorithms                                    
                .encode( Botan::AlgorithmIdentifier( "SHA-256", Botan::AlgorithmIdentifier::USE_NULL_PARAM ) ) 
            .end_cons()                                                                             
            .start_cons( Botan::ASN1_Tag::SEQUENCE ) // contentInfo                                 
                .encode( Botan::OID( "1.2.840.113549.1.7.1" ) ) // PKCS#7 "data"                       
            .end_cons()                                                                                
            .start_cons( Botan::ASN1_Tag::SET ) //XXX certificates                                     
                .encode( signerCert )                                                                  
            .end_cons()                                                                                
            .start_cons( Botan::ASN1_Tag::SET ) // signerInfos                                         
                .start_cons( Botan::ASN1_Tag::SEQUENCE ) // SignerInfo                                 
                    .encode( static_cast<size_t>( 1 ) ) // version                                     
                    .start_cons( Botan::ASN1_Tag::SEQUENCE ) // issuerAndSerialNumber                  
                        .encode( issuerDN )                                                            
                        .encode( serialNumber )                                                        
                    .end_cons()                                                                        
                    .encode( Botan::AlgorithmIdentifier( "RSA/EMSA3(SHA-256)", Botan::AlgorithmIdentifier::USE_NULL_PARAM ) ) // digestAlgorithm
                    .encode( signedSerializationHash, Botan::ASN1_Tag::OCTET_STRING ) // encryptedDigest
                .end_cons()                                                                            
            .end_cons()                                                                                
        .end_cons()                                                                                    
        .get_contents_unlocked();

An example result produced by this snippet can be seen here https://bit.ly/2MtSIAI. While the certificates structure is introduced by a SET tag (offset 56) the example (just click the example button on the same webpage) uses a different encoding to wrap the certificates (offset 48).

So, my question is how to use Botan::DER_Encoder to build the correct encoding of SignedData->certificates which is defined as certificates [0] IMPLICIT ExtendedCertificatesAndCertificates in RFC2315?

Btw., what I acutally want to do is described in more detail in UEFI specification v2.7, page 252, "8.2.2 Using the EFI_VARIABLE_AUTHENTICATION_2 descriptor". The document can be downloaded here http://www.uefi.org/specifications

Any kind of help would be appreciated!

Regards, Philip

w31mann avatar Sep 13 '18 14:09 w31mann

I think instead of .start_cons( Botan::ASN1_Tag::SET ) you want start_cons(ASN1_Tag(0), ASN1_Tag(CONSTRUCTED)).

This will not be completely correct, because the encoder will treat it like a sequence rather than a set, and so will not be sorted. You can work around this by sorting the certificates in advance. (Oh but it looks like you are just encoding a single certificate, in which case there is no problem.) I think the fix for that would be adding a variant of start_cons which allows specifying both the actual underlying constructed type as well as the desired tagging. I guess implicitly tagged SET hasn't come up so far. I will take care of this before 2.8

randombit avatar Sep 13 '18 14:09 randombit

Indeed, a single certificate would be sufficient for my purpose.

I just gave your solution a try but it does not seem to produce the desired encoding.

When examining the hexdump using that ASN.1 javascript decoder mentioned in my first post, the example wraps the certificates with A080 ... 0000 while your solution adds a preceeding A0 82 03 13.

Thank for the amazingly fast reply!

w31mann avatar Sep 13 '18 15:09 w31mann

When examining the hexdump using that ASN.1 javascript decoder mentioned in my first post, the example wraps the certificates with A080 ... 0000 while your solution adds a preceeding A0 82 03 13.

I think this is ok - if I am reading things correctly, the example is using an indefinite length encoding while Botan uses definite length (which is required for DER, IIRC).

randombit avatar Sep 13 '18 15:09 randombit

Okay, I will test it out and report back asap. Thanks again!

w31mann avatar Sep 13 '18 17:09 w31mann

@0xdefaced Any luck with this? If there is something we should change upstream it would be good to know soon as we are 1 week away from the feature freeze for the next release.

randombit avatar Sep 17 '18 19:09 randombit

Sry for the delay! I had a cold the last few days but will continue today. I don't necessarily need a fast release with that stuff.

I tested with OVMF UEFI which uses openssl to verify the PKCS#7 header, parsing the blob into an openssl PKCS7 structure using d2i_PKCS7() leaving me with a wrong tag error.

What is generated by my code is

30 82 04 86 // SEQ
	02 01 01 // INT
		31 0f // SET
			30 0d // SEQUENCE
				06 09 60 86 48 01 65 03 04 02 01 // OID sha256
				05 00
		30 0b // SEQUENCE
			06 09 2a 86 48 86 f7 0d 01 07 01 // OID pkcs-7 1 data
			
		20 82 03 16 //XXX certificates IMPLICIT SET/SEQ
			30 82 03 12 // SEQUENCE
				30 82 01 fa // SEQUENCE
					a0 03 02 .......

At least the first byte of the certificates attribute looks somehow odd, since 0x20 encodes an EOC tag.

w31mann avatar Sep 19 '18 09:09 w31mann

It works with 0xA0 in place of 0x20 - tag class set to context-specific and P/C bit set (constructed). At least parsing the PKCS#7 structure continues.

It still fails at parsing the OCTET_STRING of encryptedDigest but I am kind of sure that I messed something up there...

w31mann avatar Sep 19 '18 16:09 w31mann

Just for completeness... from my POV this issue is done and can be closed. At least for my purpose this code generates a valid DER encoded PKCS#7 SignedData structure. Compared to the op I had to change the encoding of the certificates attribute and the OID in SignerInfos.

std::vector<std::uint8_t> signedData =  Botan::DER_Encoder()                                         
        .start_cons( Botan::ASN1_Tag::SEQUENCE ) // signedData                                      
            .encode( static_cast<size_t>( 1 ) ) // version                                          
            .start_cons( Botan::ASN1_Tag::SET ) // digestAlgorithms                                    
                .encode( Botan::AlgorithmIdentifier( "SHA-256", Botan::AlgorithmIdentifier::USE_NULL_PARAM ) ) // OID SHA256
            .end_cons()                                                                             
            .start_cons( Botan::ASN1_Tag::SEQUENCE ) // contentInfo                                 
                .encode( Botan::OID( "1.2.840.113549.1.7.1" ) ) // pkcs-7 1 (PKCS#7 "data")         
            .end_cons()                                                                             
            .start_cons( Botan::ASN1_Tag( 0 ), Botan::ASN1_Tag( Botan::ASN1_Tag::CONTEXT_SPECIFIC ) ) // certificates
                .encode( signerCert )                                                                  
            .end_cons()                                                                             
            .start_cons( Botan::ASN1_Tag::SET ) // signerInfos                                      
                .start_cons( Botan::ASN1_Tag::SEQUENCE ) // signerInfo                              
                    .encode( static_cast<size_t>( 1 ) ) // version                                  
                    .start_cons( Botan::ASN1_Tag::SEQUENCE ) // issuerAndSerialNumber               
                        .encode( issuerDN )                                                            
                        .encode( serialNumber )                                                     
                    .end_cons()                                                                     
                    .encode( Botan::AlgorithmIdentifier( "SHA-256", Botan::AlgorithmIdentifier::USE_NULL_PARAM ) ) // digestEncryptionAlgorithm
                    .encode( Botan::AlgorithmIdentifier( "RSA", Botan::AlgorithmIdentifier::USE_NULL_PARAM ) ) // digestEncryptionAlgorithm
                    .encode( serializationSig, Botan::ASN1_Tag::OCTET_STRING ) // encryptedDigest   
                .end_cons()                                                                         
            .end_cons()                                                                             
        .end_cons()                                                                                 
        .get_contents_unlocked(); 

w31mann avatar Sep 27 '18 19:09 w31mann

Hi,

Do we have support for decoding the above mentioned sequence?

Above sequence :: std::vectorstd::uint8_t signedData = Botan::DER_Encoder()
.start_cons( Botan::ASN1_Tag::SEQUENCE ) // signedData
.encode( static_cast<size_t>( 1 ) ) // version
.start_cons( Botan::ASN1_Tag::SET ) // digestAlgorithms
.encode( Botan::AlgorithmIdentifier( "SHA-256", Botan::AlgorithmIdentifier::USE_NULL_PARAM ) ) // OID SHA256 .end_cons()
.start_cons( Botan::ASN1_Tag::SEQUENCE ) // contentInfo
.encode( Botan::OID( "1.2.840.113549.1.7.1" ) ) // pkcs-7 1 (PKCS#7 "data")
.end_cons()
.start_cons( Botan::ASN1_Tag( 0 ), Botan::ASN1_Tag( Botan::ASN1_Tag::CONTEXT_SPECIFIC ) ) // certificates .encode( signerCert )
.end_cons()
.start_cons( Botan::ASN1_Tag::SET ) // signerInfos
.start_cons( Botan::ASN1_Tag::SEQUENCE ) // signerInfo
.encode( static_cast<size_t>( 1 ) ) // version
.start_cons( Botan::ASN1_Tag::SEQUENCE ) // issuerAndSerialNumber
.encode( issuerDN )
.encode( serialNumber )
.end_cons()
.encode( Botan::AlgorithmIdentifier( "SHA-256", Botan::AlgorithmIdentifier::USE_NULL_PARAM ) ) // digestEncryptionAlgorithm .encode( Botan::AlgorithmIdentifier( "RSA", Botan::AlgorithmIdentifier::USE_NULL_PARAM ) ) // digestEncryptionAlgorithm .encode( serializationSig, Botan::ASN1_Tag::OCTET_STRING ) // encryptedDigest
.end_cons()
.end_cons()
.end_cons()
.get_contents_unlocked();

Can it be done with BER_DECODER? If yes, can you please share some small sample for how to use?

Thanks

ra185148 avatar Feb 07 '22 09:02 ra185148