Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Signed file support #30

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
27 changes: 27 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,33 @@ Yields
FD_2.DRIVER.bin FD_3.ASW.bin FD_4.CAL.bin
```

# Digitally signing BIN files
The following is how you sign a full bin. Optionally including some notes about the file and a secondary_key to sign it with (if you have your own private key). If you don't provide a secondary key, only the private key in `data/VW_Flash.key` is used
```
python VW_Flash.py --action prepare --input_bin FILENAME --output_bin FILENAME_OUT [--signed [--notes "Some notes about the file" ] [--secondary_key PRIVATE_KEY ] ]
```

validation is simple as well. The following command would read in the input_bin and check the signatures against the public key in data/VW_Flash.pub as well as the secondary_key (if the file contains a dual signature)
```
python VW_Flash.py --action validate --input_bin FILENAME [ --secondary_key PUBLIC_KEY ]
```

If you *do* want to sign files yourself, you can create a keypair with the following python code:
```python
from Crypto.PublicKey import RSA
key = RSA.generate(1024)

private = open('private.key', 'wb')
public = open('public.key', 'wb')

private.write(key.exportKey("PEM"))
public.write(key.public_key().exportKey("PEM"))

private.close()
public.close()
exit()
```

# Tools

[VW_Flash.py](VW_Flash.py) provides a complete "port flashing" toolchain - it's a command line interface which has the capability of performing various operations, including fixing checksums for Application Software and Calibration blocks, fixing ECM2->ECM3 monitoring checksums for CAL, encrypting, compressing, and finally, flashing blocks to the ECU.
Expand Down
28 changes: 26 additions & 2 deletions VW_Flash.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import lib.modules.simos184 as simos184
import lib.modules.dq250mqb as dq250mqb
import lib.modules.simosshared as simosshared
import lib.signature_tools as signature_tools

import shutil

Expand Down Expand Up @@ -67,6 +68,7 @@
"flash_unlock",
"get_ecu_info",
"get_dtcs",
"validate",
],
required=True,
)
Expand Down Expand Up @@ -124,6 +126,27 @@
required=False,
)

parser.add_argument(
"--signed",
help="If writing to an output_bin, sign it using the VW_Flash private key (and optionally, a secondary key)",
action="store_true",
required=False,
)

parser.add_argument(
"--secondary_key",
help="If signing an output file, the optional secondary key used to sign it",
type=str,
required=False,
)

parser.add_argument(
"--notes",
help="Used with signed bins, this is a string that will be included in the signature of the file",
type=str,
required=False,
)

parser.add_argument(
"--interface",
help="specify an interface type",
Expand Down Expand Up @@ -228,7 +251,8 @@ def input_blocks_from_frf(frf_path: str) -> dict:
input_blocks = input_blocks_from_frf(args.frf)

if args.input_bin:
input_blocks = binfile.blocks_from_bin(args.input_bin, flash_info)
bin_info = binfile.blocks_from_bin(args.input_bin, flash_info, secondary_key_path = args.secondary_key)
input_blocks = bin_info.input_blocks
logger.info(binfile.input_block_info(input_blocks, flash_info))

# build the dict that's used to proces the blocks
Expand Down Expand Up @@ -300,7 +324,7 @@ def wrap_callback_function(flasher_step, flasher_status, flasher_progress):

if args.output_bin:
outfile_data = binfile.bin_from_blocks(output_blocks, flash_info)
Path(args.output_bin).write_bytes(outfile_data)
signature_tools.write_bytes(args.output_bin, outfile_data, signed = args.signed, secondary_key_path = args.secondary_key, boxcode = "", notes = args.notes or "")
else:
for filename in output_blocks:
output_block: BlockData = output_blocks[filename]
Expand Down
13 changes: 7 additions & 6 deletions VW_Flash_GUI.py
Original file line number Diff line number Diff line change
Expand Up @@ -278,9 +278,10 @@ def on_flash(self, event):
self.feedback_text.AppendText(
"Extracting Calibration from full binary...\n"
)
input_blocks = binfile.blocks_from_bin(
self.row_obj_dict[selected_file], self.flash_info
)

bin_info = binfile.blocks_from_bin(self.row_obj_dict[selected_file], self.flash_info, secondary_key_path = None)
input_blocks = bin_info.input_blocks

# Filter to only CAL block.
self.input_blocks = {
k: v
Expand Down Expand Up @@ -338,9 +339,9 @@ def on_flash(self, event):
)
self.flash_bin(get_info=False)
elif len(input_bytes) == self.flash_info.binfile_size:
self.input_blocks = binfile.blocks_from_bin(
self.row_obj_dict[selected_file], self.flash_info
)
bin_info = binfile.blocks_from_bin(self.row_obj_dict[selected_file], self.flash_info, secondary_key_path = None)
self.input_blocks = bin_info.input_blocks

self.flash_bin(get_info=False)
else:
self.feedback_text.AppendText(
Expand Down
15 changes: 15 additions & 0 deletions data/VW_Flash.key
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCmWzXOqGzr16R+K9h48J/vIxkZO/dp8Gj9zgxEjKHUql5d6+44
IffHJXXOmFN4f9PnqLy0dZdnzUuHoaWympQMBPG+V3IfL8/fq03hF6FnTK9zBdJL
jk/AME9dhEuJRdycSqr8YuOsaAyHOKOXMkNeP7HxKQrIUcNlJeOPGqfpNQIDAQAB
AoGAAgVdT8+Q/y56o5kXGKUHtF6EnrSxSpquk9gYeGQNpT6VReZ+BDWOw98VzCGN
RgtQ1jbLur+A9t8Hb17h20Zwd6M3++zRBA93urNO4wOYMBoLusroDZWr1lGDLMhb
hMbnBeOhn8rKBbJL66SqW3tFVrDVKdDa9gbGVoIDuX+/W4ECQQC/udRrqalOB5J6
yrrrcHNEdjkCAHfkH98LgY5tpVg1K5LPuyScQ4F38vC/wqeIS8jDiYsj3zYsgKgC
fcxTa6hBAkEA3iAgijblTRV4iChe+/Li6lIk9r1btimqlXPX66BqDo8FTd0A9gFi
g/iYeyEXAu5TPwE638hOcGJR2D40/N8j9QJBAKHQ5MENjBCIgY/TpVlrKk5A/bJ7
1LScVbMvYJeYMs+FfD6Jc8fTjeVADQO79YwqckLexqm7Dc0XtTWNGTPbLEECQGv1
t3sV9VsC3YNoA8p3Idz7seWO4X1nQPbEyCRI4mNTFiPjD62BvM0hzZLC4XlWNnW/
9kqAA8fRsa/lhEGHfuUCQGoJgwZiLxGrT00xj+iGLZJJ3AyJs5XKw+OZnhT78CwY
JMbNZbkGN77hNiNQAD/tCebFR+6P/u3l3A0bY3yiGlk=
-----END RSA PRIVATE KEY-----
6 changes: 6 additions & 0 deletions data/VW_Flash.pub
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmWzXOqGzr16R+K9h48J/vIxkZ
O/dp8Gj9zgxEjKHUql5d6+44IffHJXXOmFN4f9PnqLy0dZdnzUuHoaWympQMBPG+
V3IfL8/fq03hF6FnTK9zBdJLjk/AME9dhEuJRdycSqr8YuOsaAyHOKOXMkNeP7Hx
KQrIUcNlJeOPGqfpNQIDAQAB
-----END PUBLIC KEY-----
1 change: 1 addition & 0 deletions lib/__init.py__
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ import encrypt
import checksum
import flash_uds
import decryptdsg
import signature_tools
15 changes: 13 additions & 2 deletions lib/binfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
from pathlib import Path
from .constants import FlashInfo
from .constants import BlockData
from .constants import FullBinData
from .modules import simosshared
from . import signature_tools

logger = logging.getLogger("VWFlash")

Expand Down Expand Up @@ -119,10 +121,16 @@ def filter_blocks(input_blocks: dict, flash_info: FlashInfo):
return input_blocks


def blocks_from_bin(bin_path: str, flash_info: FlashInfo) -> dict:
def blocks_from_bin(bin_path: str, flash_info: FlashInfo, secondary_key_path: str = None) -> dict:
#Read the bin from the file
bin_data = Path(bin_path).read_bytes()

#Run the bin through check_signature_data, it'll return a FullBinData object
bin_info = signature_tools.check_signature_data(bin_data, secondary_key_path = secondary_key_path)

input_blocks = {}


for i in flash_info.block_names_frf.keys():
filename = flash_info.block_names_frf[i]
input_blocks[filename] = BlockData(
Expand All @@ -135,4 +143,7 @@ def blocks_from_bin(bin_path: str, flash_info: FlashInfo) -> dict:

input_blocks = filter_blocks(input_blocks, flash_info)

return input_blocks
#set the input_block in the bin_info object to the input_blocks, and return it
bin_info.input_blocks = input_blocks

return bin_info
12 changes: 12 additions & 0 deletions lib/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,18 @@ def __init__(self, address, parse_type, description):
self.parse_type = parse_type
self.description = description

class FullBinData:
input_blocks: dict
metadata: str
valid_signature_one: bool
valid_signature_two: bool

def __init__(self, input_blocks, metadata, valid_signature_one, valid_signature_two):
self.input_blocks = input_blocks
self.metadata = metadata
self.valid_signature_one = valid_signature_one
self.valid_signature_two = valid_signature_two


class ControlModuleIdentifier:
rxid: int
Expand Down
118 changes: 118 additions & 0 deletions lib/signature_tools.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import logging
from Crypto.PublicKey import RSA
from pathlib import Path
from Crypto.Signature.pkcs1_15 import PKCS115_SigScheme
from Crypto.Hash import SHA256
from . import constants as constants
from .constants import FullBinData

logger = logging.getLogger("VWFlash")

VW_Flash_key = constants.internal_path("data", "VW_Flash.key")
VW_Flash_pub = constants.internal_path("data", "VW_Flash.pub")


#Build the metadata... basically just make sure the boxcode and the notes aren't too long
def build_metadata(boxcode = "", notes = ""):
metadata = b'METADATA:' + boxcode[0:15].ljust(15,' ').encode("utf-8") + notes[0:70].ljust(70, ' ').encode("utf-8")
return metadata

#sign binary data using a private key path
def sign_datablock(bin_file, private_key_path):
with open(private_key_path, 'rb') as private_key_file:
private_key = private_key_file.read()

the_hash = SHA256.new(bin_file)
pkcs115 = PKCS115_SigScheme(RSA.import_key(private_key))
return pkcs115.sign(the_hash)

#sign a bin... this will append metadata to a bin, and sign it (optionally twice), then return it
def sign_bin(bin_file, secondary_key_path = None, boxcode = "", notes = ""):
metadata = build_metadata(boxcode = boxcode, notes = notes)
bin_file += metadata

signature1 = sign_datablock(bin_file, VW_Flash_key)

if secondary_key_path:
signature2 = sign_datablock(bin_file, secondary_key_path)
else:
signature2 = signature1

signed_file = bin_file + signature1 + signature2

return signed_file

#Verify a bin
def verify_bin(bin_file, signature, public_key_path):
with open(public_key_path, 'rb') as public_key_file:
public_key = public_key_file.read()


the_hash = SHA256.new(bin_file)
pkcs115 = PKCS115_SigScheme(RSA.import_key(public_key))

try:
verified = pkcs115.verify(the_hash, signature)
return True
except:
return False

#write_bytes function... used to replace write_bytes throughout the code so it can be handled in a more
#centralized way
def write_bytes(outfile, binary_data, signed = False, secondary_key_path = None, boxcode = "", notes = ""):
#Default, just write out to the file_path as bytes:
if signed:
binary_data = sign_bin(bin_file = binary_data, secondary_key_path = secondary_key_path, boxcode = boxcode, notes = notes)

Path(outfile).write_bytes(binary_data)


#read_bytes function... used to replace read_bytes throughout the code so it can be handled in a more
#centralized way
def check_signature_data(bin_data, secondary_key_path = None):

valid_signature_one = False
valid_signature_two = False
metadata = None

#Check if there's metadata and signature(s) at the end of the file:
sig_block = bin_data[-350:]
if sig_block[0:9] == b'METADATA:':
logger.info("Found signature block in bin file, validating")
#Print out the metadata that's included in the file
metadata = str(sig_block[0:-256])

logger.info(str(sig_block[0:-256]))

#Pull the signatures out
signature1 = sig_block[-256:-128]
signature2 = sig_block[-128:]

#Validate the first signature using the VW_Flash public key
if verify_bin(bin_data[0:-256], signature1, VW_Flash_pub):
logger.info("First signature validated")
valid_signature_one = True
else:
logger.critical("First signature failed! File has been modified!")

#if the signatures are the same, there's no point checking the second one, just continue on
if signature1 == signature2:
logger.info("No secondary signature found")

elif secondary_key_path:
if verify_bin(bin_data[0:-256], signature2, secondary_key_path):
logger.info("Second signature validated")
valid_signature_two = True
else:
logger.critical("Second signature failed!")

else:
logger.info("File contains additional signature, but no public key arg provided")

#Pull the signature block off the end of the bin file so we can process it by itself
bin_data = bin_data[0:-350]


return FullBinData({"full_bin": bin_data}, metadata, valid_signature_one, valid_signature_two)