Skip to content
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)