Home | History | Annotate | Download | only in Pkcs7Sign
      1 ## @file

      2 # This tool adds EFI_FIRMWARE_IMAGE_AUTHENTICATION for a binary.

      3 #

      4 # This tool only support CertType - EFI_CERT_TYPE_PKCS7_GUID

      5 #   {0x4aafd29d, 0x68df, 0x49ee, {0x8a, 0xa9, 0x34, 0x7d, 0x37, 0x56, 0x65, 0xa7}}

      6 #

      7 # This tool has been tested with OpenSSL.

      8 #

      9 # Copyright (c) 2016, Intel Corporation. All rights reserved.<BR>

     10 # This program and the accompanying materials

     11 # are licensed and made available under the terms and conditions of the BSD License

     12 # which accompanies this distribution.  The full text of the license may be found at

     13 # http://opensource.org/licenses/bsd-license.php

     14 #

     15 # THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,

     16 # WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

     17 #

     18 
     19 '''
     20 Pkcs7Sign
     21 '''
     22 
     23 import os
     24 import sys
     25 import argparse
     26 import subprocess
     27 import uuid
     28 import struct
     29 import collections
     30 from Common.BuildVersion import gBUILD_VERSION
     31 
     32 #

     33 # Globals for help information

     34 #

     35 __prog__      = 'Pkcs7Sign'
     36 __version__   = '%s Version %s' % (__prog__, '0.9 ' + gBUILD_VERSION)
     37 __copyright__ = 'Copyright (c) 2016, Intel Corporation. All rights reserved.'
     38 __usage__     = '%s -e|-d [options] <input_file>' % (__prog__)
     39 
     40 #

     41 # GUID for PKCS7 from UEFI Specification

     42 #

     43 WIN_CERT_REVISION      = 0x0200
     44 WIN_CERT_TYPE_EFI_GUID = 0x0EF1
     45 EFI_CERT_TYPE_PKCS7_GUID = uuid.UUID('{4aafd29d-68df-49ee-8aa9-347d375665a7}')
     46 
     47 #

     48 # typedef struct _WIN_CERTIFICATE {

     49 #   UINT32 dwLength;

     50 #   UINT16 wRevision;

     51 #   UINT16 wCertificateType;

     52 # //UINT8 bCertificate[ANYSIZE_ARRAY];

     53 # } WIN_CERTIFICATE;

     54 #

     55 # typedef struct _WIN_CERTIFICATE_UEFI_GUID {

     56 #   WIN_CERTIFICATE Hdr;

     57 #   EFI_GUID        CertType;

     58 # //UINT8 CertData[ANYSIZE_ARRAY];

     59 # } WIN_CERTIFICATE_UEFI_GUID;

     60 #

     61 # typedef struct {

     62 #   UINT64                    MonotonicCount;

     63 #   WIN_CERTIFICATE_UEFI_GUID AuthInfo;

     64 # } EFI_FIRMWARE_IMAGE_AUTHENTICATION;

     65 #

     66 
     67 #

     68 # Filename of test signing private cert that is stored in same directory as this tool

     69 #

     70 TEST_SIGNER_PRIVATE_CERT_FILENAME = 'TestCert.pem'
     71 TEST_OTHER_PUBLIC_CERT_FILENAME = 'TestSub.pub.pem'
     72 TEST_TRUSTED_PUBLIC_CERT_FILENAME = 'TestRoot.pub.pem'
     73 
     74 if __name__ == '__main__':
     75   #

     76   # Create command line argument parser object

     77   #

     78   parser = argparse.ArgumentParser(prog=__prog__, version=__version__, usage=__usage__, description=__copyright__, conflict_handler='resolve')
     79   group = parser.add_mutually_exclusive_group(required=True)
     80   group.add_argument("-e", action="store_true", dest='Encode', help='encode file')
     81   group.add_argument("-d", action="store_true", dest='Decode', help='decode file')
     82   parser.add_argument("-o", "--output", dest='OutputFile', type=str, metavar='filename', help="specify the output filename", required=True)
     83   parser.add_argument("--signer-private-cert", dest='SignerPrivateCertFile', type=argparse.FileType('rb'), help="specify the signer private cert filename.  If not specified, a test signer private cert is used.")
     84   parser.add_argument("--other-public-cert", dest='OtherPublicCertFile', type=argparse.FileType('rb'), help="specify the other public cert filename.  If not specified, a test other public cert is used.")
     85   parser.add_argument("--trusted-public-cert", dest='TrustedPublicCertFile', type=argparse.FileType('rb'), help="specify the trusted public cert filename.  If not specified, a test trusted public cert is used.")
     86   parser.add_argument("--monotonic-count", dest='MonotonicCountStr', type=str, help="specify the MonotonicCount in FMP capsule.  If not specified, 0 is used.")
     87   parser.add_argument("--signature-size", dest='SignatureSizeStr', type=str, help="specify the signature size for decode process.")
     88   parser.add_argument("-v", "--verbose", dest='Verbose', action="store_true", help="increase output messages")
     89   parser.add_argument("-q", "--quiet", dest='Quiet', action="store_true", help="reduce output messages")
     90   parser.add_argument("--debug", dest='Debug', type=int, metavar='[0-9]', choices=range(0,10), default=0, help="set debug level")
     91   parser.add_argument(metavar="input_file", dest='InputFile', type=argparse.FileType('rb'), help="specify the input filename")
     92 
     93   #

     94   # Parse command line arguments

     95   #

     96   args = parser.parse_args()
     97 
     98   #

     99   # Generate file path to Open SSL command

    100   #

    101   OpenSslCommand = 'openssl'
    102   try:
    103     OpenSslPath = os.environ['OPENSSL_PATH']
    104     OpenSslCommand = os.path.join(OpenSslPath, OpenSslCommand)
    105   except:
    106     pass
    107 
    108   #

    109   # Verify that Open SSL command is available

    110   #

    111   try:
    112     Process = subprocess.Popen('%s version' % (OpenSslCommand), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    113   except:
    114     print 'ERROR: Open SSL command not available.  Please verify PATH or set OPENSSL_PATH'
    115     sys.exit(1)
    116 
    117   Version = Process.communicate()
    118   if Process.returncode <> 0:
    119     print 'ERROR: Open SSL command not available.  Please verify PATH or set OPENSSL_PATH'
    120     sys.exit(Process.returncode)
    121   print Version[0]
    122 
    123   #

    124   # Read input file into a buffer and save input filename

    125   #

    126   args.InputFileName   = args.InputFile.name
    127   args.InputFileBuffer = args.InputFile.read()
    128   args.InputFile.close()
    129 
    130   #

    131   # Save output filename and check if path exists

    132   #

    133   OutputDir = os.path.dirname(args.OutputFile)
    134   if not os.path.exists(OutputDir):
    135     print 'ERROR: The output path does not exist: %s' % OutputDir
    136     sys.exit(1)
    137   args.OutputFileName = args.OutputFile
    138 
    139   try:
    140     if args.MonotonicCountStr.upper().startswith('0X'):
    141       args.MonotonicCountValue = (long)(args.MonotonicCountStr, 16)
    142     else:
    143       args.MonotonicCountValue = (long)(args.MonotonicCountStr)
    144   except:
    145     args.MonotonicCountValue = (long)(0)
    146 
    147   if args.Encode:
    148     #

    149     # Save signer private cert filename and close private cert file

    150     #

    151     try:
    152       args.SignerPrivateCertFileName = args.SignerPrivateCertFile.name
    153       args.SignerPrivateCertFile.close()
    154     except:
    155       try:
    156         #

    157         # Get path to currently executing script or executable

    158         #

    159         if hasattr(sys, 'frozen'):
    160             Pkcs7ToolPath = sys.executable
    161         else:
    162             Pkcs7ToolPath = sys.argv[0]
    163         if Pkcs7ToolPath.startswith('"'):
    164             Pkcs7ToolPath = Pkcs7ToolPath[1:]
    165         if Pkcs7ToolPath.endswith('"'):
    166             Pkcs7ToolPath = RsaToolPath[:-1]
    167         args.SignerPrivateCertFileName = os.path.join(os.path.dirname(os.path.realpath(Pkcs7ToolPath)), TEST_SIGNER_PRIVATE_CERT_FILENAME)
    168         args.SignerPrivateCertFile = open(args.SignerPrivateCertFileName, 'rb')
    169         args.SignerPrivateCertFile.close()
    170       except:
    171         print 'ERROR: test signer private cert file %s missing' % (args.SignerPrivateCertFileName)
    172         sys.exit(1)
    173 
    174     #

    175     # Save other public cert filename and close public cert file

    176     #

    177     try:
    178       args.OtherPublicCertFileName = args.OtherPublicCertFile.name
    179       args.OtherPublicCertFile.close()
    180     except:
    181       try:
    182         #

    183         # Get path to currently executing script or executable

    184         #

    185         if hasattr(sys, 'frozen'):
    186             Pkcs7ToolPath = sys.executable
    187         else:
    188             Pkcs7ToolPath = sys.argv[0]
    189         if Pkcs7ToolPath.startswith('"'):
    190             Pkcs7ToolPath = Pkcs7ToolPath[1:]
    191         if Pkcs7ToolPath.endswith('"'):
    192             Pkcs7ToolPath = RsaToolPath[:-1]
    193         args.OtherPublicCertFileName = os.path.join(os.path.dirname(os.path.realpath(Pkcs7ToolPath)), TEST_OTHER_PUBLIC_CERT_FILENAME)
    194         args.OtherPublicCertFile = open(args.OtherPublicCertFileName, 'rb')
    195         args.OtherPublicCertFile.close()
    196       except:
    197         print 'ERROR: test other public cert file %s missing' % (args.OtherPublicCertFileName)
    198         sys.exit(1)
    199 
    200     format = "%dsQ" % len(args.InputFileBuffer)
    201     FullInputFileBuffer = struct.pack(format, args.InputFileBuffer, args.MonotonicCountValue)
    202 
    203     #

    204     # Sign the input file using the specified private key and capture signature from STDOUT

    205     #

    206     Process = subprocess.Popen('%s smime -sign -binary -signer "%s" -outform DER -md sha256 -certfile "%s"' % (OpenSslCommand, args.SignerPrivateCertFileName, args.OtherPublicCertFileName), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    207     Signature = Process.communicate(input=FullInputFileBuffer)[0]
    208     if Process.returncode <> 0:
    209       sys.exit(Process.returncode)
    210 
    211     #

    212     # Write output file that contains Signature, and Input data

    213     #

    214     args.OutputFile = open(args.OutputFileName, 'wb')
    215     args.OutputFile.write(Signature)
    216     args.OutputFile.write(args.InputFileBuffer)
    217     args.OutputFile.close()
    218 
    219   if args.Decode:
    220     #

    221     # Save trusted public cert filename and close public cert file

    222     #

    223     try:
    224       args.TrustedPublicCertFileName = args.TrustedPublicCertFile.name
    225       args.TrustedPublicCertFile.close()
    226     except:
    227       try:
    228         #

    229         # Get path to currently executing script or executable

    230         #

    231         if hasattr(sys, 'frozen'):
    232             Pkcs7ToolPath = sys.executable
    233         else:
    234             Pkcs7ToolPath = sys.argv[0]
    235         if Pkcs7ToolPath.startswith('"'):
    236             Pkcs7ToolPath = Pkcs7ToolPath[1:]
    237         if Pkcs7ToolPath.endswith('"'):
    238             Pkcs7ToolPath = RsaToolPath[:-1]
    239         args.TrustedPublicCertFileName = os.path.join(os.path.dirname(os.path.realpath(Pkcs7ToolPath)), TEST_TRUSTED_PUBLIC_CERT_FILENAME)
    240         args.TrustedPublicCertFile = open(args.TrustedPublicCertFileName, 'rb')
    241         args.TrustedPublicCertFile.close()
    242       except:
    243         print 'ERROR: test trusted public cert file %s missing' % (args.TrustedPublicCertFileName)
    244         sys.exit(1)
    245 
    246     if not args.SignatureSizeStr:
    247       print "ERROR: please use the option --signature-size to specify the size of the signature data!"
    248       sys.exit(1)
    249     else:
    250       if args.SignatureSizeStr.upper().startswith('0X'):
    251         SignatureSize = (long)(args.SignatureSizeStr, 16)
    252       else:
    253         SignatureSize = (long)(args.SignatureSizeStr)
    254     if SignatureSize < 0:
    255         print "ERROR: The value of option --signature-size can't be set to negative value!"
    256         sys.exit(1)
    257     elif SignatureSize > len(args.InputFileBuffer):
    258         print "ERROR: The value of option --signature-size is exceed the size of the input file !"
    259         sys.exit(1)
    260 
    261     args.SignatureBuffer = args.InputFileBuffer[0:SignatureSize]
    262     args.InputFileBuffer = args.InputFileBuffer[SignatureSize:]
    263 
    264     format = "%dsQ" % len(args.InputFileBuffer)
    265     FullInputFileBuffer = struct.pack(format, args.InputFileBuffer, args.MonotonicCountValue)
    266 
    267     #

    268     # Save output file contents from input file

    269     #

    270     open(args.OutputFileName, 'wb').write(FullInputFileBuffer)
    271 
    272     #

    273     # Verify signature

    274     #

    275     Process = subprocess.Popen('%s smime -verify -inform DER -content %s -CAfile %s' % (OpenSslCommand, args.OutputFileName, args.TrustedPublicCertFileName), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    276     Process.communicate(input=args.SignatureBuffer)[0]
    277     if Process.returncode <> 0:
    278       print 'ERROR: Verification failed'
    279       os.remove (args.OutputFileName)
    280       sys.exit(Process.returncode)
    281 
    282     open(args.OutputFileName, 'wb').write(args.InputFileBuffer)
    283