Search code examples
encryptionopensslpublic-key-encryptionpublic-keypki

How to manually calculate and verify the signature of a certificate signing request


  • I have created a keypair

openssl genrsa -out test1 2048

  • then created a certificate signing request using that key

openssl req -new -key test1 -subj "/CN=foo" -out foo.csr

  • verifying that certificate using openssl req -in foo.csr -text
    • It Contains the public key of the keypair i have generated

            Public Key Algorithm: rsaEncryption
            Public-Key: (2048 bit)
            Modulus:
                00:ca:c5:29:98:08:05:30:30:03:08:eb:23:c2:af:
                3e:2e:2d:dc:11:96:cb:2f:d1:1f:7f:41:a4:00:13:
                8a:ee:4b:36:5b:f2:c1:d1:0f:8b:27:11:34:08:bd:
                4d:df:7e:6d:7a:d7:f9:dd:ea:62:ad:fa:8f:8c:eb:
                47:5f:55:82:2c:13:c2:11:41:12:b9:87:0b:3d:08:
                86:1b:ad:71:16:89:1c:fa:07:4a:86:8f:80:a9:99:
                37:f7:e2:d4:d3:d8:b2:5f:7f:c9:05:51:73:f0:c8:
                59:ec:c3:09:a2:03:a5:6e:ec:8b:d9:9c:11:de:d3:
                df:55:a5:3f:0c:36:d6:93:8a:70:a0:b9:61:cd:c9:
                4a:09:ad:f7:3e:fd:ce:6f:5c:bb:00:69:e9:3b:3d:
                85:3b:01:1d:8f:6a:a7:d4:61:f9:b5:07:1e:90:ed:
                ab:3b:41:cc:db:e8:a0:e7:88:b7:77:35:66:30:b7:
                a6:cd:ea:d6:12:f5:ef:82:63:e9:46:29:2e:7c:10:
                0e:32:fd:04:2d:cd:62:0e:4b:74:46:f7:fd:f6:4a:
                8d:fb:82:9d:37:11:50:ea:9f:f0:d6:64:2b:50:a4:
                f0:18:6e:81:28:11:04:db:2a:0a:f7:b1:70:c5:78:
                fe:ed:e3:55:2c:64:f4:a5:a0:96:f5:11:3a:27:2c:
                5a:51
            Exponent: 65537 (0x10001)
      
    • Subject attributes i have provided while creating the CSR

              Subject: CN=foo
      
    • Digital Signature Information

      Signature Algorithm: sha256WithRSAEncryption 92:b0:82:a5:aa:98:4a:62:5a:84:8a:15:5c:6f:48:dc:e3:ec: 7f:d5:04:e8:c1:47:55:3c:b3:57:84:16:ff:5a:0d:29:2c:16: f2:cc:0c:18:c3:1f:d5:e1:57:3a:dd:8b:b1:c6:92:c3:fe:cb: 2b:9d:7d:79:d5:64:eb:31:00:8b:5e:77:48:ce:66:6f:dd:7b: 71:41:f9:aa:6e:ea:ea:59:e0:cd:f8:db:a9:13:18:d2:2a:fc: 12:25:b3:01:44:0c:b1:02:f7:1a:0f:d0:07:04:1d:9f:6f:a1: 58:91:87:25:4a:d6:47:a6:b5:4e:3c:a1:fd:b6:6c:c3:96:16: c1:ab:00:d2:4c:95:ee:2c:01:2d:cf:0e:d0:62:1b:4f:0e:34: e3:e8:85:50:63:74:eb:1f:ac:95:30:d4:df:43:7f:58:11:90: 35:29:9d:85:94:dc:c8:c2:29:81:46:71:20:62:9c:9c:f8:ef: ed:bc:8b:e3:d5:41:b3:14:f7:43:c6:b2:74:c2:22:06:a2:af: 88:68:2e:67:c4:de:ed:61:37:41:d6:df:8a:76:7d:42:5d:98: d3:c9:19:8b:1d:26:73:92:95:0f:ba:c1:78:3a:55:87:e0:3e: 16:13:34:6e:21:13:b9:da:b8:66:f3:0a:ec:79:ae:1a:95:6c: 04:cf:b8:b5

Following are my doubts :

  1. what all data fields it has considered to calculate the signature ?
  2. can we manually create the signature and verify it with the one mentioned in CSR ?

Solution

  • The data over which the signature is calculated is pretty much everything in the CSR except for the signature algorithm and signature. This is called the certificationRequestInfo in RFC 2986.

    The second question: how do we verify the signature?

    The short answer: have openssl do it for you.

    The easiest way by far is to ask openssl itself to verify it:

    $ openssl genrsa -out test.key 2048
    $ openssl req -new -key test.key -subj "/CN=foo" -out foo.csr
    $ openssl req -in foo.csr -verify -noout
    verify OK
    

    Done!


    The long (and tedious) answer: do it manually

    Ok, so you really want to do it manually. Let's give it a shot.

    Given the above, we first need to extract the desired information from the CSR. Dumping the ASN.1 structure, we get:

    $ openssl asn1parse -i -in foo.csr 
        0:d=0  hl=4 l= 595 cons: SEQUENCE          
        4:d=1  hl=4 l= 315 cons:  SEQUENCE          
        8:d=2  hl=2 l=   1 prim:   INTEGER           :00
       11:d=2  hl=2 l=  14 cons:   SEQUENCE          
       13:d=3  hl=2 l=  12 cons:    SET               
       15:d=4  hl=2 l=  10 cons:     SEQUENCE          
       17:d=5  hl=2 l=   3 prim:      OBJECT            :commonName
       22:d=5  hl=2 l=   3 prim:      UTF8STRING        :foo
       27:d=2  hl=4 l= 290 cons:   SEQUENCE          
       31:d=3  hl=2 l=  13 cons:    SEQUENCE          
       33:d=4  hl=2 l=   9 prim:     OBJECT            :rsaEncryption
       44:d=4  hl=2 l=   0 prim:     NULL              
       46:d=3  hl=4 l= 271 prim:    BIT STRING        
      321:d=2  hl=2 l=   0 cons:   cont [ 0 ]        
      323:d=1  hl=2 l=  13 cons:  SEQUENCE          
      325:d=2  hl=2 l=   9 prim:   OBJECT            :sha256WithRSAEncryption
      336:d=2  hl=2 l=   0 prim:   NULL              
      338:d=1  hl=4 l= 257 prim:  BIT STRING        
    

    This is a structured CertificationRequest defined by RFC 2986 as:

    CertificationRequest ::= SEQUENCE {
        certificationRequestInfo CertificationRequestInfo,
        signatureAlgorithm       AlgorithmIdentifier{{ SignatureAlgorithms }},
        signature                BIT STRING
    }
    

    The certificationRequestInfo (see RFC for structure details) in DER-encoded ASN.1 format is signed using the algorithm described in signatureAlgorithm and the private key to yield signature.


    Let's extract all the parts we need from the CSR. The strparse value is the offset you want to export, this is the first number on each line in the output above.

    # Extract the certificationRequestInfo (data to be signed)
    $ openssl asn1parse -in foo.csr -strparse 4 -out info.der
    
    # Extract the public key.
    $ openssl req -pubkey -in foo.csr -noout -out pub.pem
    
    # Alternatively, you can use:
    $ openssl asn1parse -in foo.csr -strparse 27 -out tmp.der
    $ openssl rsa -pubin -inform DER -in tmp.der -out pub.pem
    
    # Extract the raw signature bytes:
    $ openssl asn1parse -in foo.csr -strparse 338 -out sig.raw
        0:d=0  hl=2 l=  70 cons: cont [ 3 ]        
    Error in encoding
    139935063934272:error:0D07209B:asn1 encoding routines:ASN1_get_object:too long:../crypto/asn1/asn1_lib.c:91:
    

    Ignore the error on the last one, this is because the extracted data is the raw signature bytes, it's not ASN.1 encoded. openssl still happily wrote it out to the file.

    We now have the following files:

    • info.der: the DER-encoded data that was signed
    • pub.pem: the CSR requester's public key
    • sig.raw: the signature included in the CSR

    Let's verify the RSA signature (because that's what the signature algorithm says) using the public key and extract the original hash:

    $ openssl rsautl -verify -pubin -inkey pub.pem -in sig.raw -out hash.der
    $ openssl asn1parse -i -in hash.der -inform DER
        0:d=0  hl=2 l=  49 cons: SEQUENCE          
        2:d=1  hl=2 l=  13 cons:  SEQUENCE          
        4:d=2  hl=2 l=   9 prim:   OBJECT            :sha256
       15:d=2  hl=2 l=   0 prim:   NULL              
       17:d=1  hl=2 l=  32 prim:  OCTET STRING      [HEX DUMP]:192E0909DABC7454006628AA3F7FB009AFA62A17A44908CAE5E166E528DCDD11
    

    It didn't fail, so we already know the public key is the one that matches the private key used to sign the data.

    The last section, the long OCTET STRING is the raw hash of the message as computed by the CSR requester:

    192e0909dabc7454006628aa3f7fb009afa62a17a44908cae5e166e528dcdd11
    

    Let's compute the sha256 hash (once again: because the signature algorithm tells us to) of the certificationRequestInfo:

    $ sha256sum info.der 
    192e0909dabc7454006628aa3f7fb009afa62a17a44908cae5e166e528dcdd11  info.der
    

    Yay! The hash is equal to the one extracted from the signature.


    The hashes match and the signature was signed by the private key corresponding to the public key listed in the CSR. This is a valid CSR.

    Done! See, I said it would be tedious.