On Mon, 2022-07-04 at 14:04 +0200, Ansgar wrote: > On Sun, 19 Jun 2022 12:59:55 +0200 Ben Hutchings wrote: > > > I'm now looking at whether the missing bytes are recoverable (e.g. are > > > they always zeroes). > > [...] > > > > I wrote a script to try all possible byte values for 2 bytes before or > > after the short signature. For this particular file, none of them > > producd a valid signature. So the short signatures seem to be > > corrupted in a more complex way. > > The "OCTET STRING" containing the actual signature is shorter for the > seemingly corrupted signatures: [...] Yes I know, and my script uses a library to manipulate the ASN.1 structure when adding bytes. I'm attaching the script, so you can check the logic. Ben. -- Ben Hutchings Any smoothly functioning technology is indistinguishable from a rigged demo.
#!/usr/bin/python3
# Fix broken detached signatures
import os.path
import sys
import asn1crypto.algos
import asn1crypto.cms
import asn1crypto.core
from M2Crypto import BIO, SMIME, X509
# Signature algorithm should be RSA
SIG_ALGO_OID = asn1crypto.core.ObjectIdentifier('1.2.840.113549.1.1.1')
# Signature length should match key length (2048 bits)
SIG_LEN = 2048 // 8
def make_smime_context(cert):
# We don't verify against a CA, just one specific certificate
smime = SMIME.SMIME()
signer_key = X509.X509_Stack()
signer_key.push(X509.load_cert(cert))
smime.set_x509_stack(signer_key)
smime.set_x509_store(X509.X509_Store())
return smime
def verify_payload(smime, sig, data):
smime.verify(
SMIME.load_pkcs7_bio_der(BIO.MemoryBuffer(sig.dump(force=True))),
BIO.MemoryBuffer(data),
flags=(SMIME.PKCS7_BINARY | SMIME.PKCS7_DETACHED
| SMIME.PKCS7_NOVERIFY))
def brute_force_sig_2bytes(smime, sig, sig_os, data):
orig_raw_sig = bytes(sig_os)
for byte1 in range(256):
for byte2 in range(256):
sig_os.set(bytes((byte1, byte2)) + orig_raw_sig)
try:
verify_payload(smime, sig, data)
except Exception:
pass
else:
print(f'prepended {byte1}, {byte2} to start of sig')
return True
sig_os.set(bytes((byte1,)) + orig_raw_sig + bytes((byte2,)))
try:
verify_payload(smime, sig, data)
except Exception:
pass
else:
print(f'added {byte1}, {byte2} at start and end of sig resp.')
return True
sig_os.set(orig_raw_sig + bytes((byte1, byte2)))
try:
verify_payload(smime, sig, data)
except Exception:
pass
else:
print(f'appended {byte1}, {byte2} to end of sig')
return True
return False
def fix_detached_sig(smime, sig, bin_name):
# The ContentInfo should be a SEQUENCE with signed data at index 1
if len(sig) < 2 or not isinstance(sig[1], asn1crypto.cms.SignedData):
return 'no signed data found', False
sd = sig[1]
# The SignedData should be a SEQUENCE with signer infos at index 5
if len(sd) < 6 or not isinstance(sd[5], asn1crypto.cms.SignerInfos):
return 'no signer infos found', False
infos = sd[5]
# The SignerInfos should be a SET with 1 item
if len(infos) != 1:
return f'found { len(infos) } signer infos; expected 1', False
info = infos[0]
# The SignerInfo should be a SEQUENCE with the signature algorithm
# at index 4 and signature at index 5
if (len(info) < 6
or not isinstance(info[4], asn1crypto.algos.SignedDigestAlgorithm)
or len(info[4]) < 1
or not isinstance(info[5], asn1crypto.core.OctetString)):
return 'expected fields not found in signer info', False
# Check the signature algorithm and length (see bug #1012741)
if info[4][0] != SIG_ALGO_OID:
return f'unexpected signature algorithm { info[4][0].dotted }', False
actual_sig_len = len(bytes(info[5]))
with open(bin_name, 'rb') as f:
data = f.read()
if (actual_sig_len == SIG_LEN - 2
and brute_force_sig_2bytes(smime, sig, info[5], bin_name)):
return (f'signature length is { actual_sig_len } bytes;'
f' expected { SIG_LEN }; filled in missing 2 bytes',
True)
if actual_sig_len != SIG_LEN:
return (f'signature length is { actual_sig_len } bytes;'
f' expected { SIG_LEN }',
False)
verify_payload(smime, sig, data)
return None, False
def load_detached_sig(name):
with open(name, 'rb') as f:
return asn1crypto.cms.ContentInfo.load(f.read())
def save_detached_sig(sig, name):
with open(name, 'wb') as f:
f.write(sig.dump())
def main(sig_dir, bin_dir, cert):
err_count = 0
smime = make_smime_context(cert)
for dirpath, dirnames, filenames in os.walk(sig_dir):
for name in filenames:
sig_name = os.path.join(dirpath, name)
# We can only fix module signatures, because hashes on
# PE executables need to be calculated differently
if not sig_name.endswith('.ko.sig'):
print(f'{sig_name}: ignoring, no .ko.sig extension')
continue
bin_name = os.path.join(
bin_dir, os.path.relpath(sig_name[:-4], sig_dir))
try:
sig = load_detached_sig(sig_name)
except Exception as e:
err, fixed = str(e), False
else:
err, fixed = fix_detached_sig(smime, sig, bin_name)
if err:
print(f'{name}: {err}', file=sys.stderr)
if fixed:
save_detached_sig(sig, sig_name)
else:
err_count += 1
return err_count
if __name__ == '__main__':
sys.exit(main(*sys.argv[1:]) != 0)
Attachment:
signature.asc
Description: This is a digitally signed message part