2018年9月13日 星期四

Check the signature of kernel module (PKCS#7 format)

I have written a blog for how to check the signature of kernel module (old format). In the mainline kernel, 3f1e1bea3 patch in v4.3-rc1 switches the format of kernel module signature to PKCS#7. This blog introduces how to verify the PKCS#7 signature manually.

Success Case

For testing, I built a mainline kernel that it built with CONFIG_MODULE_SIG_ALL. Then I got many signed kernel modules and a auto-generated key-pair. As last time, I still use acer-wmi.ko as the sample for verification.

The kernel auto-generated key-pair can be found in the certs folder in kernel source:

linux-gsoz:~/linux.nfs # ls certs/signing_key.*
certs/signing_key.pem  certs/signing_key.x509

The signing_key.pem is private key, and signing_key.x509 is the certificate that it includes public key.

STEP 1. Extract the PKCS#7 signature in acer-wmi.ko

The PKCS#7 is attached on acer-wmi.ko. The layout of kernel module structure is as following:

PKCS#7 (PKEY_ID_PKCS7 = 2):
     module start +-----------------------+ info->hdr = mod
                  |                       |
                  |                       |
                  |                       |
                  |                       |
                  |     module data       |
                  |                       |
                  |                       |
                  |                       |
               -+-+-----------------------|
                | |                       |
ms->sig_len---> | | pkcs#7 signature      |
                | |                       |
               -+-+-----------------------|
                | |struct module_signature|
                | |                       |
ms 12 bytes --> | |  u8      id_type;     |
                | |                       |
                | |  __be32  sig_len      |
               -+-+-----------------------|
   markerlen ---> | MODULE_SIG_STRING     |
      module end  +-----------------------+ info->len

The module_signature code  in kernel is:

kernel/module_signing.c
        /*
         * Module signature information block.
         *       
         * The constituents of the signature section are, in order:
         *       
         *      - Signer's name
         *      - Key identifier
         *      - Signature data
         *      - Information block
         */      
        struct module_signature {
                u8      algo;           /* Public-key crypto algorithm [0] */
                u8      hash;           /* Digest algorithm [0] */
                u8      id_type;        /* Key identifier type [PKEY_ID_PKCS7] */
                u8      signer_len;     /* Length of signer's name [0] */
                u8      key_id_len;     /* Length of key identifier [0] */
                u8      __pad[3];       /* 3 bytes */
                __be32  sig_len;        /* Length of signature data *//* 4 bytes */
        };

kernel/module_signing.c
        enum pkey_id_type {
                PKEY_ID_PGP,            /* OpenPGP generated key ID */
                PKEY_ID_X509,           /* X.509 arbitrary subjectKeyIdentifier */
                PKEY_ID_PKCS7,          /* Signature in PKCS#7 message */
        };    

So, first, the MODULE_SIG_STRING is in the end of kernel module. The string is "~Module signature appended~.", we can use the string to locate the 12 bytes module_signature that it's just before the MODULE_SIG_STRING.

linux-gsoz:~/tmp/modsign-acer-wmi-pkcs7 # hexdump -C acer-wmi.ko | less
...
00010940  00 00 00 00 00 00 00 00  30 82 01 e6 06 09 2a 86  |........0.....*.| <=== Signature start: 0x10948 
00010950  48 86 f7 0d 01 07 02 a0  82 01 d7 30 82 01 d3 02  |H..........0....|
...
00010940  00 00 00 00 00 00 00 00  30 82 01 e6 06 09 2a 86  |........0.....*.|
00010950  48 86 f7 0d 01 07 02 a0  82 01 d7 30 82 01 d3 02  |H..........0....|
00010960  01 01 31 0d 30 0b 06 09  60 86 48 01 65 03 04 02  |..1.0...`.H.e...|
...
00010b20  ce 2d df 65 23 81 12 bd  b4 80 27 4e e4 58 23 72  |.-.e#.....'N.X#r|
00010b30  df d9 00 00 02 00 00 00  00 00 00 00 01 ea 7e 4d  |..............~M| <=== Signature end: 0x10b31
00010b40 6f 64 75 6c 65 20 73 69 67 6e 61 74 75 72 65 20 |odule signature | 00010b50 61 70 70 65 6e 64 65 64 7e 0a |appended~.| 00010b5a

The above blue numbers are module_signature. The id_type is 02 which means PKCS#7 format. And the sig_len is 0x1ea, it means that the length of signature is 490 bytes. The start - end is 0x10b31 - 0x10948+ 1 = 0x1ea. It matches with the sig_len number.

After finding out the location of signature and the length, then we can extract the signature from ko file. Using dd:

dd bs=1 skip=67912 count=490 if=./acer-wmi.ko of=./acer-wmi.ko.pkcs7

The 67912 is the start location of encrypted digest 0x10948, and the 490 is the 0x1ea length. We can also truncate the ko file to remove the signature for getting a unsigned ko file:

dd bs=1 count=67912 if=./acer-wmi.ko of=./acer-wmi-unsigned.ko

The later we can use the unsigned ko file to compare the hash result with the decrypted digest.

STEP 2. Extract the digest from encrypted digest in PKCS#7

OK, we just extract the PKCS#7 signature from ko file. The signature is a digest that it is encrypted by private key. We can extract the encrypted digest from PKCS#7 blog then using public key to decrypt it. Then we can get a hash number of acer-wmi.ko file.

The PKCS#7 signature is encoded to ASN1 format. So we can use openssl's ASN1 parser to decode the signature:

linux-gsoz:~/tmp/modsign-acer-wmi-pkcs7 # openssl asn1parse -inform der -in acer-wmi.ko.pkcs7
    0:d=0  hl=4 l= 486 cons: SEQUENCE          
    4:d=1  hl=2 l=   9 prim: OBJECT            :pkcs7-signedData
   15:d=1  hl=4 l= 471 cons: cont [ 0 ]        
   19:d=2  hl=4 l= 467 cons: SEQUENCE          
   23:d=3  hl=2 l=   1 prim: INTEGER           :01
   26:d=3  hl=2 l=  13 cons: SET               
   28:d=4  hl=2 l=  11 cons: SEQUENCE          
   30:d=5  hl=2 l=   9 prim: OBJECT            :sha256
   41:d=3  hl=2 l=  11 cons: SEQUENCE          
   43:d=4  hl=2 l=   9 prim: OBJECT            :pkcs7-data
   54:d=3  hl=4 l= 432 cons: SET               
   58:d=4  hl=4 l= 428 cons: SEQUENCE          
   62:d=5  hl=2 l=   1 prim: INTEGER           :01
   65:d=5  hl=3 l= 134 cons: SEQUENCE          
   68:d=6  hl=2 l= 121 cons: SEQUENCE          
   70:d=7  hl=2 l=  22 cons: SET               
   72:d=8  hl=2 l=  20 cons: SEQUENCE          
   74:d=9  hl=2 l=   3 prim: OBJECT            :organizationName
   79:d=9  hl=2 l=  13 prim: UTF8STRING        :openSUSE Test
   94:d=7  hl=2 l=  65 cons: SET               
   96:d=8  hl=2 l=  63 cons: SEQUENCE          
   98:d=9  hl=2 l=   3 prim: OBJECT            :commonName
  103:d=9  hl=2 l=  56 prim: UTF8STRING        :Build time autogenerated kernel key for openSUSE testing
  161:d=7  hl=2 l=  28 cons: SET               
  163:d=8  hl=2 l=  26 cons: SEQUENCE          
  165:d=9  hl=2 l=   9 prim: OBJECT            :emailAddress
  176:d=9  hl=2 l=  13 prim: IA5STRING         :user@suse.com
  191:d=6  hl=2 l=   9 prim: INTEGER           :9B47FAF791E7D1E3
  202:d=5  hl=2 l=  11 cons: SEQUENCE          
  204:d=6  hl=2 l=   9 prim: OBJECT            :sha256
  215:d=5  hl=2 l=  13 cons: SEQUENCE          
  217:d=6  hl=2 l=   9 prim: OBJECT            :rsaEncryption
  228:d=6  hl=2 l=   0 prim: NULL              
  230:d=5  hl=4 l= 256 prim: OCTET STRING      [HEX DUMP]:0C3B4D84F348F7DA533FCA7836E459A696BFF5695A049934EC0B6BC4A3EB9FC3E3D392A65D61EA0AD430DF4CF41D87F75F20F11B16D793E07A7B259F25CAB280E030D3F69710D4ADA2A080F8E0186C77C2927556FEB05F070F430829C7BA5103C0E332D9E7F4AB4C3F3C7E0C6B010FF3CFCAB6716B869FD436A8018BD107269521D5369E94C4BEA02186228E781CD9D71C2DBDA7F3A4D148260BB6AF8E704BA9A223401B7B43832CE0068768A28EB0F8E69FDDA1479BC4FAB1CF12D0DEDB05FAA6B0718B91F6B42148230059E6BEC6350455A4692C9EA63B9E23C04B17E5EAC49FFDA08BDE8D108CD998E244FCF9CE2DDF65238112BDB480274EE4582372DFD9

It shows the PKCS#7 structure. It includes hash algorithm for digest, sign key, encryption algorithm, and the encrypted digest in the end of signature. In the above case, the encrypted digest starts from 230 + 4 bytes, and its length is 256 bytes. So we can use dd to extract the encrypted digest to a file:

dd if=acer-wmi.ko.pkcs7 of=acer-wmi.ko.encrypted_digest bs=1 skip=$[ 230 + 4 ] count=256

linux-gsoz:~/tmp/modsign-acer-wmi-pkcs7 # hexdump -C acer-wmi.ko.encrypted_digest 
00000000  0c 3b 4d 84 f3 48 f7 da  53 3f ca 78 36 e4 59 a6  |.;M..H..S?.x6.Y.|
00000010  96 bf f5 69 5a 04 99 34  ec 0b 6b c4 a3 eb 9f c3  |...iZ..4..k.....|
00000020  e3 d3 92 a6 5d 61 ea 0a  d4 30 df 4c f4 1d 87 f7  |....]a...0.L....|
00000030  5f 20 f1 1b 16 d7 93 e0  7a 7b 25 9f 25 ca b2 80  |_ ......z{%.%...|
00000040  e0 30 d3 f6 97 10 d4 ad  a2 a0 80 f8 e0 18 6c 77  |.0............lw|
00000050  c2 92 75 56 fe b0 5f 07  0f 43 08 29 c7 ba 51 03  |..uV.._..C.)..Q.|
00000060  c0 e3 32 d9 e7 f4 ab 4c  3f 3c 7e 0c 6b 01 0f f3  |..2....L?<~.k...|
00000070  cf ca b6 71 6b 86 9f d4  36 a8 01 8b d1 07 26 95  |...qk...6.....&.|
00000080  21 d5 36 9e 94 c4 be a0  21 86 22 8e 78 1c d9 d7  |!.6.....!.".x...|
00000090  1c 2d bd a7 f3 a4 d1 48  26 0b b6 af 8e 70 4b a9  |.-.....H&....pK.|
000000a0  a2 23 40 1b 7b 43 83 2c  e0 06 87 68 a2 8e b0 f8  |.#@.{C.,...h....|
000000b0  e6 9f dd a1 47 9b c4 fa  b1 cf 12 d0 de db 05 fa  |....G...........|
000000c0  a6 b0 71 8b 91 f6 b4 21  48 23 00 59 e6 be c6 35  |..q....!H#.Y...5|
000000d0  04 55 a4 69 2c 9e a6 3b  9e 23 c0 4b 17 e5 ea c4  |.U.i,..;.#.K....|
000000e0  9f fd a0 8b de 8d 10 8c  d9 98 e2 44 fc f9 ce 2d  |...........D...-|
000000f0  df 65 23 81 12 bd b4 80  27 4e e4 58 23 72 df d9  |.e#.....'N.X#r..|
00000100

STEP 3. Using public key to decrypt digest

Now we have a acer-wmi.ko.encrypted_digest, then we can use public key to decrypt the blob. Finally we will get the hash of acer-wmi.ko.

First, we need to extract the public key from X.509 certificate:

openssl x509 -inform der -in signing_key.x509 -noout -pubkey > signing_key_public.pem
linux-gsoz:~/tmp/modsign-acer-wmi-pkcs7 # hexdump -C signing_key_public.pem 
00000000  2d 2d 2d 2d 2d 42 45 47  49 4e 20 50 55 42 4c 49  |-----BEGIN PUBLI|
00000010  43 20 4b 45 59 2d 2d 2d  2d 2d 0a 4d 49 49 42 49  |C KEY-----.MIIBI|
00000020  6a 41 4e 42 67 6b 71 68  6b 69 47 39 77 30 42 41  |jANBgkqhkiG9w0BA|
00000030  51 45 46 41 41 4f 43 41  51 38 41 4d 49 49 42 43  |QEFAAOCAQ8AMIIBC|
00000040  67 4b 43 41 51 45 41 79  6b 79 78 65 4c 51 63 44  |gKCAQEAykyxeLQcD|
00000050  42 77 6f 65 7a 75 50 44  76 32 4b 0a 56 49 59 6c  |BwoezuPDv2K.VIYl|
00000060  4d 49 62 62 6c 38 42 45  44 49 35 54 5a 38 7a 66  |MIbbl8BEDI5TZ8zf|
00000070  39 32 41 6b 52 4b 71 59  57 6a 41 52 68 4f 44 39  |92AkRKqYWjARhOD9|
00000080  51 4c 45 59 2f 79 47 79  6c 2b 36 6a 59 4a 68 65  |QLEY/yGyl+6jYJhe|
00000090  47 4a 74 2b 43 68 6a 66  4b 32 67 36 0a 64 52 51  |GJt+ChjfK2g6.dRQ|
000000a0  52 35 33 6f 6c 2b 6e 39  4c 52 49 38 44 61 64 61  |R53ol+n9LRI8Dada|
000000b0  70 78 4a 31 34 4e 53 59  45 4d 67 72 46 30 39 69  |pxJ14NSYEMgrF09i|
000000c0  72 59 33 42 55 78 72 54  39 51 39 75 67 55 50 62  |rY3BUxrT9Q9ugUPb|
000000d0  6b 32 33 67 50 72 51 43  54 35 74 54 7a 0a 36 43  |k23gPrQCT5tTz.6C|
000000e0  4e 6f 50 2f 2f 33 6b 4b  67 30 6c 45 31 51 71 66  |NoP//3kKg0lE1Qqf|
000000f0  52 44 58 35 34 70 53 49  49 51 6c 5a 45 47 6b 34  |RDX54pSIIQlZEGk4|
00000100  2f 4e 51 4d 4f 67 36 6e  43 5a 4e 44 58 57 48 53  |/NQMOg6nCZNDXWHS|
00000110  4d 37 61 69 4e 79 4f 44  57 63 64 53 6a 61 0a 6e  |M7aiNyODWcdSja.n|
00000120  39 59 69 64 76 33 59 74  36 2f 35 47 39 33 51 30  |9Yidv3Yt6/5G93Q0|
00000130  74 59 52 2f 68 44 5a 35  6b 49 32 4b 4e 39 53 4b  |tYR/hDZ5kI2KN9SK|
00000140  69 34 53 6f 75 39 55 36  67 35 6a 33 2b 49 61 66  |i4Sou9U6g5j3+Iaf|
00000150  38 62 6b 71 4d 57 61 72  2f 4d 62 73 34 52 46 0a  |8bkqMWar/Mbs4RF.|
00000160  44 33 70 71 34 62 59 46  66 74 63 32 6e 31 71 31  |D3pq4bYFftc2n1q1|
00000170  68 6a 55 50 33 47 49 49  4b 63 53 64 54 78 59 49  |hjUP3GIIKcSdTxYI|
00000180  2b 44 55 34 75 78 76 39  2b 6c 77 4c 49 42 30 43  |+DU4uxv9+lwLIB0C|
00000190  69 59 54 50 6b 65 79 72  2f 35 73 54 2f 70 61 63  |iYTPkeyr/5sT/pac|
000001a0  0a 54 77 49 44 41 51 41  42 0a 2d 2d 2d 2d 2d 45  |.TwIDAQAB.-----E|
000001b0  4e 44 20 50 55 42 4c 49  43 20 4b 45 59 2d 2d 2d  |ND PUBLIC KEY---|
000001c0  2d 2d 0a                                          |--.|
000001c3

Then using openssl to decrypt the digest by public key:

openssl rsautl -verify -pubin -inkey signing_key_public.pem < acer-wmi.ko.encrypted_digest > acer-wmi.sha256

If the signature is invalid or broken, then the above decrypt process can not success. Finally we got a blob. This is also a ASN.1 encoded blob:

linux-gsoz:~/tmp/modsign-acer-wmi-pkcs7 # openssl asn1parse -inform der -in acer-wmi.sha256
    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]:EBDA845DD7B1FE77EEF2B1AC06617C7C74075F919D64DCB3B496B0042F70A83F

This hash should equals to the sha256 resume of unsigned acer-wmi.ko file:

linux-gsoz:~/tmp/modsign-acer-wmi-pkcs7 # sha256sum acer-wmi-unsigned.ko 
ebda845dd7b1fe77eef2b1ac06617c7c74075f919d64dcb3b496b0042f70a83f  acer-wmi-unsigned.ko

OK, then we confirmed that the PKCS#7 signature is valid.