Files
funnygame/external/steamworks/tools/codesigning/build_steam_signatures_file.py
2025-07-13 15:47:42 +03:00

242 lines
7.3 KiB
Python

import os
import io
import sys
import re
# wrapper for print
def printw( message ):
print( message )
try:
import Crypto
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA
from Crypto.PublicKey import RSA
except Exception as e:
printw( "Missing required module: "+str(e) )
sys.exit( -1 )
try:
import zlib
except Exception as e:
printw( "Missing required module: "+str(e) )
sys.exit( -1 )
try:
import hashlib
except Exception as e:
printw( "Missing required module: "+str(e) )
sys.exit( -1 )
try:
import ctypes
except Exception as e:
printw( "Missing required module: "+str(e) )
sys.exit( -1 )
def usage():
printw( "usage to verify a signaturefile:"+ sys.argv[0]+ " signaturefile publickeyfile" )
printw( "usage to write a new signaturefile:"+ sys.argv[0]+ " signaturefile publickeyfile privatekeyfile newfilename" )
printw( "" )
def readkeyfile( publickeyfilename ):
# read the public key file.
rawkey = open(publickeyfilename, mode='rb').read()
# import as an RSA key
key = RSA.importKey(rawkey)
return key
def signmessage_add_digest( message, privatekeyfile ):
key = RSA.importKey(open( privatekeyfile, mode='rb' ).read())
h = SHA.new(message)
signer = PKCS1_v1_5.new(key)
signature = signer.sign(h)
sighex = signature.encode("hex")
return "DIGEST:"+sighex.upper()+"\r\n"
def checkdigest( signaturefilename, key ):
with open(signaturefilename, mode='rb') as file:
fileContent = file.read()
# find the start of the digest
idxtodigest = fileContent.find('DIGEST:')
if idxtodigest == 0:
return 0
# the message is everything else
message = fileContent[0:idxtodigest]
digestpart = fileContent[idxtodigest+7:]
digestpart = digestpart.replace("\r\n","")
signature = digestpart.decode("hex")
try:
h = SHA.new(message)
verifier = PKCS1_v1_5.new(key)
if not verifier.verify(h, signature):
return 0
except Exception as e:
printw( "could not verify signature: "+str(e) )
return message
def crchex( crc32 ):
crc32b = ctypes.c_uint32(crc32).value
crc32hex = hex(crc32b).upper().replace("0X","").replace("L","")
lenhex = len(crc32hex)
# pad to 8 chars with leading 0s
padding = 8 - lenhex
while padding:
crc32hex = "0"+crc32hex
padding = padding - 1
# byte swap
crc32hex_reverse = crc32hex[6:8]+crc32hex[4:6]+crc32hex[2:4]+crc32hex[0:2]
return crc32hex_reverse
def parsehashes( message, pathto ):
# now verify all the file hashes
newmessage = ""
lines = message.split("\r\n")
for line in lines:
if len(line) == 0:
break
# parse the line, should be 5 parts
# filename "~SHA1" sha1 ";CRC:" crc32
parts=re.split(';|:|~',line)
if len(parts) != 5:
printw( "The file format is unexpected line:"+line )
break
if parts[1] != "SHA1" or parts[3] != "CRC":
printw( "The file format is unexpected line:"+line )
break
hashprovided = parts[2]
crcprovided = parts[4]
onefile = parts[0]
onefile = onefile.replace("...\\","")
onefilepath = os.path.join( pathto, onefile )
try:
with open(onefilepath, mode='rb') as file:
targetcontent = file.read()
except:
printw( "could not open: "+onefilepath )
continue
# compute sha1 of file
mm = hashlib.sha1()
mm.update(targetcontent)
newhash = mm.hexdigest().upper()
# compute crc32 of file
crc32 = zlib.crc32(targetcontent)
crc32hex = crchex(crc32)
if ( newhash == hashprovided.upper() and
crc32hex == crcprovided.upper() ):
printw( "The hashes are correct for "+onefile )
else:
printw( "The hashes are different for "+onefile+" sha: "+newhash+" "+hashprovided.upper()+" CRC "+crc32hex+" "+crcprovided.upper() )
# accumulate new hashes
linenew = "...\\"+onefile+"~SHA1:"+newhash+";CRC:"+crc32hex+"\r\n"
newmessage += linenew
return newmessage
def signatures_need_update( signaturefilename, publickeyfilename ):
if not os.path.exists( signaturefilename ):
printw( "Signature file does not exist" )
sys.stdout.flush()
return False
pathto = os.path.split(signaturefilename)[0]
if not os.path.exists( publickeyfilename ):
printw( "Public key file does not exist" )
sys.stdout.flush()
return False
key = readkeyfile( publickeyfilename )
message = checkdigest( signaturefilename, key )
if len(message) == 0:
printw( "failed to parse signature file " )
return False
newmessage = parsehashes( message, pathto )
if len(newmessage) == 0:
printw( "failed to parse hashes in signature data " )
return False
return newmessage != message
def write_new_signature_file( signaturefilename, publickeyfilename, privatekeyfilename, newsignaturefilename ):
if not os.path.exists( signaturefilename ):
printw( "Signature file does not exist" )
sys.stdout.flush()
return -1
pathto = os.path.split(signaturefilename)[0]
if not os.path.exists( publickeyfilename ):
printw( "Public key file does not exist" )
sys.stdout.flush()
return -2
if not os.path.exists( privatekeyfilename ):
printw( "Private key file does not exist" )
sys.stdout.flush()
return -3
if os.path.exists( newsignaturefilename ) and not os.access( newsignaturefilename, os.W_OK ):
printw( "new signatures file not writeable " )
sys.stdout.flush()
return -4
key = readkeyfile( publickeyfilename )
message = checkdigest( signaturefilename, key )
if len(message) == 0:
printw( "failed to parse old signature file " )
return -5
newmessage = parsehashes( message, pathto )
if len(newmessage) == 0:
printw( "failed to create new signature data " )
return -6
with open( newsignaturefilename, mode='wb') as file:
file.write( newmessage )
hexdigest = signmessage_add_digest( newmessage, privatekeyfilename )
file.write( hexdigest )
printw( "new signatures file written successfully " )
sys.stdout.flush()
return 0
def main():
if len( sys.argv ) != 5 and len( sys.argv ) != 3:
usage()
sys.exit(2)
# common args
signaturefilename = sys.argv[1]
publickeyfilename = sys.argv[2]
if len( sys.argv ) == 5:
# only if writing a new file
privatekeyfilename = sys.argv[3]
newsignaturefilename = sys.argv[4]
write_new_signature_file( signaturefilename, publickeyfilename, privatekeyfilename, newsignaturefilename )
else:
if signatures_need_update( signaturefilename, publickeyfilename ):
printw( "Some signatures did not match" )
else:
printw( "All signatures matched" )
if __name__ == '__main__':
main()