Hi folks!

A time ago I wrote a high-level description about the signatures in Digital signatures blog post. And today I want to demonstrate how this works in a real world.

In a real world there are too many signature types, including RSA signatures (plain), Authenticode, XML, Document-specific (MS Word, Adobe PDF, etc.). The simplest signature type is plain RSA signatures. This type of signatures is widely used in PKI (certificates, CRLs, signed BLOBs and so on). In ASN.1 modules (as well as in unmanaged structures), signed BLOB is written like this:

Certificate  ::=  SEQUENCE  {
        tbsCertificate       TBSCertificate,
        signatureAlgorithm   AlgorithmIdentifier,
        signatureValue       BIT STRING  }

AlgorithmIdentifier  ::=  SEQUENCE  {
        algorithm               OBJECT IDENTIFIER,
        parameters              ANY DEFINED BY algorithm OPTIONAL  }

tbsCertificate contains an arbitrary data to be signed (look at the prefix, tbs means ToBeSigned). This data is always wrapped to a SEQUENCE (SEQUENCE models an ordered collection of variables of different type) which acts as a generic container in ASN.1. The next field is signatureAlgorithm of AlgorithmIdentifier complex type and signatureValue field of primitive BIT STRING type.

AlgorithmIdentifier is exposed in next structure and contains 2 properties:

  • algorithm of primitive OBJECT IDENTIFIER type (encoded form of Oid class).
  • parameters of ANY. In most cases it is set to primitive type NULL.

if you look into actual object you will see the following:

image

Here we see:

  • tbsCertificate starts at offset 4 (first number in parentheses). In expanded view we see some data which represents certificate content (as you see in UI).
  • signatureAlgorithm starts at offset 1408.
  • algorithm starts at offset 1410.
  • parameters starts at offset 1421 (NULL type).
  • signatureValue starts at 1423 (BIT STRING).

Here we have almost all required information to verify the signature. The only thing is missing — issuer certificate. Since issuer information is retrieved by using external means (through certificate chaining engine), it's certificate is not included in the certificate content. If issuer information is not retrieved by using external means, it is added explicitly. Such example is OCSP response:

image

Generally signed content retains the same almost in all cases and CryptoAPI has appropriate structure CERT_SIGNED_CONTENT_INFO:

typedef struct _CERT_SIGNED_CONTENT_INFO {
  CRYPT_DER_BLOB             ToBeSigned;
  CRYPT_ALGORITHM_IDENTIFIER SignatureAlgorithm;
  CRYPT_BIT_BLOB             Signature;
} CERT_SIGNED_CONTENT_INFO, *PCERT_SIGNED_CONTENT_INFO;

In .NET signature creation and verification stuff is implemented in RSACryptoServiceProvider class. As we remember, signature is verified against issuer's public key. Let's do some manual PowerShell stuff:

[↓] [vPodans] # get certificate objects.
[↓] [vPodans] $digicert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 .\Desktop\digicert.c
er
[↓] [vPodans] $issuer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 .\Desktop\digicert_iss
uer.cer
[↓] [vPodans] # get tbsCertificate BLOB which starts at offset 4 and ends at 1407 (-1 to algorithmIdentifier)
[↓] [vPodans] $tbsCertificate = $digicert.RawData[4..1407]
[↓] [vPodans] $tbsCertificate.Length
1404
[↓] [vPodans] # get signature value (minus tag byte and tag length bytes):
[↓] [vPodans] $signatureValue = $digicert.RawData[1428..$digicert.RawData.Length]
[↓] [vPodans] $signatureValue.Length
256
[↓] [vPodans] # retrieve RSACryptoServiceProvider object of issuer' public key
[↓] [vPodans] $pubkey = $issuer.PublicKey.Key
[↓] [vPodans] $pubkey


PublicOnly           : True
CspKeyContainerInfo  : System.Security.Cryptography.CspKeyContainerInfo
KeySize              : 2048
KeyExchangeAlgorithm : RSA-PKCS1-KeyEx
SignatureAlgorithm   : http://www.w3.org/2000/09/xmldsig#rsa-sha1
PersistKeyInCsp      : False
LegalKeySizes        : {System.Security.Cryptography.KeySizes}



[↓] [vPodans] $pubkey.GetType().FullName
System.Security.Cryptography.RSACryptoServiceProvider
[↓] [vPodans]

We will use RSACryptoServiceProvider.VerifyData() method to verify it:

[↓] [vPodans] $pubkey.VerifyData($tbsCertificate,"sha1",$signatureValue)
True
[↓] [vPodans]

And here we go! If we take different certificate (which is not an issuer of the subject's certificate):

[↓] [vPodans] $issuer = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2 .\Desktop\pica-1.cer
[↓] [vPodans] $pubkey = $issuer.PublicKey.Key
[↓] [vPodans] $pubkey


PublicOnly           : True
CspKeyContainerInfo  : System.Security.Cryptography.CspKeyContainerInfo
KeySize              : 2048
KeyExchangeAlgorithm : RSA-PKCS1-KeyEx
SignatureAlgorithm   : http://www.w3.org/2000/09/xmldsig#rsa-sha1
PersistKeyInCsp      : False
LegalKeySizes        : {System.Security.Cryptography.KeySizes}



[↓] [vPodans] $pubkey.VerifyData($tbsCertificate,"sha1",$signatureValue)
False
[↓] [vPodans]

And signature verification fails.

Though, this manual stuff is just an example. In the real world it is more efficient to deal with unmanaged structures, rather than each part manual parsing. Because you can take entire signed BLOB, convert it to unmanaged structure and marshal back to managed parts. I'll demonstrate unmanaged stuff in next post.


Share this article:

Comments:


Post your comment:

Please, solve this little equation and enter result below. Captcha