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