def test_identity_crypt_filter(use_alias, with_never_decrypt): w = writer.PdfFileWriter() sh = StandardSecurityHandler.build_from_pw("secret") w.security_handler = sh idf: IdentityCryptFilter = IdentityCryptFilter() assert sh.crypt_filter_config[pdf_name("/Identity")] is idf if use_alias: sh.crypt_filter_config._crypt_filters[pdf_name("/IdentityAlias")] = idf assert sh.crypt_filter_config[pdf_name("/IdentityAlias")] is idf if use_alias: # identity filter can't be serialised, so this should throw an error with pytest.raises(misc.PdfError): w._assign_security_handler(sh) return else: w._assign_security_handler(sh) test_bytes = b'This is some test data that should remain unencrypted.' test_stream = generic.StreamObject(stream_data=test_bytes, handler=sh) test_stream.apply_filter("/Crypt", params={pdf_name("/Name"): pdf_name("/Identity")}) ref = w.add_object(test_stream).reference out = BytesIO() w.write(out) r = PdfFileReader(out) r.decrypt("secret") the_stream = r.get_object(ref, never_decrypt=with_never_decrypt) assert the_stream.encoded_data == test_bytes assert the_stream.data == test_bytes
def test_double_sig_create_deep_field_post_sign(): w = IncrementalPdfFileWriter(BytesIO(MINIMAL_ONE_FIELD)) # create part of the structure already fields._insert_or_get_field_at( w, w.root['/AcroForm']['/Fields'], ('NewSigs', 'NewSig1'), field_obj=generic.DictionaryObject({pdf_name('/FT'): pdf_name('/Sig')})) out = signers.sign_pdf(w, signers.PdfSignatureMetadata( field_name='Sig1', certify=True, docmdp_permissions=fields.MDPPerm.FILL_FORMS), signer=FROM_CA, in_place=True) w = IncrementalPdfFileWriter(out) fqn = 'NewSigs.NewSig2' meta = signers.PdfSignatureMetadata(field_name=fqn) out = signers.sign_pdf(w, meta, signer=FROM_CA) r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'Sig1' status = validate_pdf_signature( s, signer_validation_context=SIMPLE_V_CONTEXT()) # the /Kids array of NewSigs was modified, which we don't allow (right now) assert status.modification_level == ModificationLevel.OTHER
def test_write_embedded_string_objstream(): ffile = ttLib.TTFont(NOTO_SERIF_JP) ga = GlyphAccumulator(ffile) cid_hx, _ = ga.feed_string('テスト') assert cid_hx == '0637062a0639' w = IncrementalPdfFileWriter(BytesIO(MINIMAL_XREF)) obj_stream = w.prepare_object_stream() font_ref = ga.embed_subset(w, obj_stream=obj_stream) stream = generic.StreamObject( stream_data=f'BT /FEmb 18 Tf 0 100 Td <{cid_hx}> Tj ET'.encode( 'ascii')) stream_ref = w.add_object(stream) w.add_stream_to_page(0, stream_ref, resources=generic.DictionaryObject({ pdf_name('/Font'): generic.DictionaryObject( {pdf_name('/FEmb'): font_ref}) })) out = BytesIO() w.write(out) out.seek(0) r = PdfFileReader(out) page_obj = r.root['/Pages']['/Kids'][0].get_object() conts = page_obj['/Contents'] assert len(conts) == 2 assert stream_ref.idnum in (c.idnum for c in conts) assert font_ref.idnum in r.xrefs.in_obj_stream out.seek(0) # attempt to grab the font from the object stream font_ref.pdf = r font = font_ref.get_object() assert font['/Type'] == pdf_name('/Font')
def test_duplicate_resource(): res1 = PdfResources() res2 = PdfResources() res1[ResourceType.XOBJECT][pdf_name('/Bleh')] = generic.NullObject() res1[ResourceType.PATTERN][pdf_name('/Blih')] = generic.NullObject() res2[ResourceType.XOBJECT][pdf_name('/Bleh')] = generic.NullObject() res2[ResourceType.FONT][pdf_name('/Bluh')] = generic.NullObject() with pytest.raises(ResourceManagementError): res1 += res2
def test_custom_crypt_filter(with_hex_filter, main_unencrypted): w = writer.PdfFileWriter() custom = pdf_name('/Custom') crypt_filters = { custom: StandardRC4CryptFilter(keylen=16), } if main_unencrypted: # streams/strings are unencrypted by default cfc = CryptFilterConfiguration(crypt_filters=crypt_filters) else: crypt_filters[STD_CF] = StandardAESCryptFilter(keylen=16) cfc = CryptFilterConfiguration(crypt_filters=crypt_filters, default_string_filter=STD_CF, default_stream_filter=STD_CF) sh = StandardSecurityHandler.build_from_pw_legacy( rev=StandardSecuritySettingsRevision.RC4_OR_AES128, id1=w.document_id[0], desired_user_pass="******", desired_owner_pass="******", keylen_bytes=16, crypt_filter_config=cfc) w._assign_security_handler(sh) test_data = b'This is test data!' dummy_stream = generic.StreamObject(stream_data=test_data) dummy_stream.add_crypt_filter(name=custom, handler=sh) ref = w.add_object(dummy_stream) dummy_stream2 = generic.StreamObject(stream_data=test_data) ref2 = w.add_object(dummy_stream2) if with_hex_filter: dummy_stream.apply_filter(pdf_name('/AHx')) out = BytesIO() w.write(out) r = PdfFileReader(out) r.decrypt("ownersecret") obj: generic.StreamObject = r.get_object(ref.reference) assert obj.data == test_data if with_hex_filter: cf_dict = obj['/DecodeParms'][1] else: cf_dict = obj['/DecodeParms'] assert cf_dict['/Name'] == pdf_name('/Custom') obj2: generic.DecryptedObjectProxy = r.get_object( ref2.reference, transparent_decrypt=False) raw = obj2.raw_object assert isinstance(raw, generic.StreamObject) if main_unencrypted: assert raw.encoded_data == test_data else: assert raw.encoded_data != test_data
def test_pubkey_unsupported_filter(delete_subfilter): w = writer.PdfFileWriter() w.encrypt_pubkey([PUBKEY_TEST_DECRYPTER.cert]) encrypt = w._encrypt.get_object() encrypt['/Filter'] = pdf_name('/FooBar') if delete_subfilter: del encrypt['/SubFilter'] else: encrypt['/SubFilter'] = pdf_name('/baz.quux') out = BytesIO() w.write(out) with pytest.raises(misc.PdfReadError): PdfFileReader(out)
def test_custom_crypt_filter_errors(): w = writer.PdfFileWriter() custom = pdf_name('/Custom') crypt_filters = { custom: StandardRC4CryptFilter(keylen=16), STD_CF: StandardAESCryptFilter(keylen=16) } cfc = CryptFilterConfiguration(crypt_filters=crypt_filters, default_string_filter=STD_CF, default_stream_filter=STD_CF) sh = StandardSecurityHandler.build_from_pw_legacy( rev=StandardSecuritySettingsRevision.RC4_OR_AES128, id1=w.document_id[0], desired_user_pass="******", desired_owner_pass="******", keylen_bytes=16, crypt_filter_config=cfc) w._assign_security_handler(sh) test_data = b'This is test data!' dummy_stream = generic.StreamObject(stream_data=test_data) with pytest.raises(misc.PdfStreamError): dummy_stream.add_crypt_filter(name='/Idontexist', handler=sh) # no handler dummy_stream.add_crypt_filter(name=custom) dummy_stream._handler = None w.add_object(dummy_stream) out = BytesIO() with pytest.raises(misc.PdfStreamError): w.write(out)
def test_sv_subfilter_unsupported_partial(): sv_spec = fields.SigSeedValueSpec( flags=fields.SigSeedValFlags.SUBFILTER, subfilters=[fields.SigSeedSubFilter.ADOBE_PKCS7_DETACHED, PADES]) w = IncrementalPdfFileWriter(prepare_sv_field(sv_spec)) field_name, _, sig_field = next(fields.enumerate_sig_fields(w)) sig_field = sig_field.get_object() sv_ref = sig_field.raw_get('/SV') w.mark_update(sv_ref) sv_ref.get_object()['/SubFilter'][0] = pdf_name('/this.doesnt.exist') out = BytesIO() w.write(out) out.seek(0) frozen = out.getvalue() signers.sign_pdf(IncrementalPdfFileWriter(BytesIO(frozen)), signers.PdfSignatureMetadata(field_name='Sig'), signer=FROM_CA, timestamper=DUMMY_TS) with pytest.raises(SigningError): signers.sign_pdf( IncrementalPdfFileWriter(BytesIO(frozen)), signers.PdfSignatureMetadata( field_name='Sig', subfilter=fields.SigSeedSubFilter.ADOBE_PKCS7_DETACHED), signer=FROM_CA, timestamper=DUMMY_TS)
def test_resource_add_test(): res1 = PdfResources() res2 = PdfResources() res1[ResourceType.XOBJECT][pdf_name('/Bleh')] = generic.NullObject() res1[ResourceType.PATTERN][pdf_name('/Blih')] = generic.NullObject() res2[ResourceType.XOBJECT][pdf_name('/Blah')] = generic.NullObject() res2[ResourceType.FONT][pdf_name('/Bluh')] = generic.NullObject() res1 += res2 res1_dict = res1.as_pdf_object() assert pdf_name('/XObject') in res1_dict assert pdf_name('/Pattern') in res1_dict assert pdf_name('/Font') in res1_dict assert pdf_name('/Bleh') in res1_dict['/XObject'] assert pdf_name('/Blah') in res1_dict['/XObject'] assert pdf_name('/Shading') not in res1_dict
def test_double_sig_add_field(file_ix): w = IncrementalPdfFileWriter(BytesIO(DOUBLE_SIG_TESTDATA_FILES[file_ix])) out = signers.sign_pdf( w, signers.PdfSignatureMetadata( field_name='Sig1', certify=True, docmdp_permissions=fields.MDPPerm.FILL_FORMS), signer=FROM_CA, ) # create a new signature field after signing w = IncrementalPdfFileWriter(out) # throw in an /Info update for good measure dt = generic.pdf_date(datetime(2020, 10, 10, tzinfo=pytz.utc)) info = generic.DictionaryObject({pdf_name('/CreationDate'): dt}) w.set_info(info) out = signers.sign_pdf( w, signers.PdfSignatureMetadata(field_name='SigNew'), signer=FROM_CA, ) r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'Sig1' status = val_trusted(s, extd=True) assert status.modification_level == ModificationLevel.FORM_FILLING assert status.docmdp_ok s = r.embedded_signatures[1] assert s.field_name == 'SigNew' val_trusted(s)
def test_no_changes_policy(): w = IncrementalPdfFileWriter(BytesIO(MINIMAL_ONE_FIELD)) out = signers.sign_pdf( w, signers.PdfSignatureMetadata( field_name='Sig1', certify=True, docmdp_permissions=fields.MDPPerm.FILL_FORMS), signer=FROM_CA, ) w = IncrementalPdfFileWriter(out) # do an /Info update dt = generic.pdf_date(datetime(2020, 10, 10, tzinfo=pytz.utc)) info = generic.DictionaryObject({pdf_name('/CreationDate'): dt}) w.set_info(info) w.write_in_place() # check with normal diff policy r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'Sig1' status = val_trusted(s, extd=True) assert status.modification_level == ModificationLevel.LTA_UPDATES assert status.docmdp_ok # now check with the ultra-strict no-op policy r = PdfFileReader(out) s = r.embedded_signatures[0] status = validate_pdf_signature(s, diff_policy=NO_CHANGES_DIFF_POLICY) assert isinstance(s.diff_result, SuspiciousModification) assert not status.docmdp_ok
def test_pubkey_alternative_filter(): w = writer.PdfFileWriter() w.encrypt_pubkey([PUBKEY_TEST_DECRYPTER.cert]) # subfilter should be picked up w._encrypt.get_object()['/Filter'] = pdf_name('/FooBar') out = BytesIO() w.write(out) r = PdfFileReader(out) assert isinstance(r.security_handler, PubKeySecurityHandler)
def test_trailer_update(): w = IncrementalPdfFileWriter(BytesIO(MINIMAL_ONE_FIELD)) dt = generic.pdf_date(datetime.datetime(2020, 10, 10, tzinfo=pytz.utc)) info = generic.DictionaryObject({pdf_name('/CreationDate'): dt}) w.trailer['/Info'] = w.add_object(info) out = BytesIO() w.write(out) r = PdfFileReader(out) assert r.trailer['/Info']['/CreationDate'] == dt
def test_pubkey_encryption_block_cfs_s4(): w = writer.PdfFileWriter() w.encrypt_pubkey([PUBKEY_TEST_DECRYPTER.cert]) encrypt = w._encrypt.get_object() encrypt['/SubFilter'] = pdf_name('/adbe.pkcs7.s4') out = BytesIO() w.write(out) with pytest.raises(misc.PdfReadError): PdfFileReader(out)
def test_pubkey_encryption_dict_errors(): sh = PubKeySecurityHandler.build_from_certs([PUBKEY_TEST_DECRYPTER.cert]) original = sh.as_pdf_object() encrypt = generic.DictionaryObject(original) encrypt['/SubFilter'] = pdf_name('/asdflakdsjf') with pytest.raises(misc.PdfReadError): PubKeySecurityHandler.build(encrypt) encrypt = generic.DictionaryObject(original) encrypt['/Length'] = generic.NumberObject(13) with pytest.raises(misc.PdfError): PubKeySecurityHandler.build(encrypt) encrypt = generic.DictionaryObject(original) del encrypt['/CF']['/DefaultCryptFilter']['/CFM'] with pytest.raises(misc.PdfReadError): PubKeySecurityHandler.build(encrypt) encrypt = generic.DictionaryObject(original) encrypt['/CF']['/DefaultCryptFilter']['/CFM'] = pdf_name('/None') with pytest.raises(misc.PdfReadError): PubKeySecurityHandler.build(encrypt)
def test_sv_sign_reason_prohibited(reasons_param): sv = fields.SigSeedValueSpec(flags=fields.SigSeedValFlags.REASONS, reasons=reasons_param) aw_yiss = signers.PdfSignatureMetadata(reason='Aw yiss', field_name='Sig') with pytest.raises(SigningError): sign_with_sv(sv, aw_yiss) sign_with_sv(sv, aw_yiss, test_violation=True) dot = signers.PdfSignatureMetadata(reason='.', field_name='Sig') with pytest.raises(SigningError): sign_with_sv(sv, dot) sign_with_sv(sv, dot, test_violation=True) emb_sig = sign_with_sv(sv, signers.PdfSignatureMetadata(field_name='Sig')) assert pdf_name('/Reason') not in emb_sig.sig_object
def test_form_field_ft_tamper(): w = IncrementalPdfFileWriter(BytesIO(SIMPLE_FORM)) # sign, then fill meta = signers.PdfSignatureMetadata(field_name='Sig1') out = signers.sign_pdf(w, meta, signer=FROM_CA) w = IncrementalPdfFileWriter(out) tf = w.root['/AcroForm']['/Fields'][1].get_object() tf['/FT'] = pdf_name('/Sig') w.update_container(tf) out = BytesIO() w.write(out) r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'Sig1' val_trusted_but_modified(s)
def test_sv_deserialisation(): sv_input = generic.DictionaryObject({ pdf_name('/SubFilter'): generic.ArrayObject( map(pdf_name, ['/foo', '/adbe.pkcs7.detached', '/bleh'])), pdf_name('/LegalAttestation'): generic.ArrayObject(['xyz', 'abc', 'def']), pdf_name('/AppearanceFilter'): generic.pdf_string('blah'), pdf_name('/LockDocument'): generic.pdf_name('/true') }) sv = fields.SigSeedValueSpec.from_pdf_object(sv_input) assert len(sv.subfilters) == 1 assert len(sv.legal_attestations) == 3 assert sv.lock_document == fields.SeedLockDocument.LOCK sv_output = sv.as_pdf_object() assert sv_output['/AppearanceFilter'] == sv_input['/AppearanceFilter'] assert sv_output['/LockDocument'] == sv_input['/LockDocument'] assert sv_output['/LegalAttestation'] == sv_input['/LegalAttestation'] with pytest.raises(SigningError): fields.SigSeedValueSpec.from_pdf_object( generic.DictionaryObject( {pdf_name('/LockDocument'): generic.pdf_name('/nonsense')})) fields.SigSeedValueSpec.from_pdf_object( generic.DictionaryObject( {pdf_name('/LockDocument'): generic.BooleanObject(True)})) bad_filter = generic.DictionaryObject( {pdf_name('/Filter'): pdf_name('/unsupported')}) # this should run fields.SigSeedValueSpec.from_pdf_object(bad_filter) with pytest.raises(SigningError): bad_filter[pdf_name('/Ff')] = \ generic.NumberObject(fields.SigSeedValFlags.FILTER.value) fields.SigSeedValueSpec.from_pdf_object(bad_filter)
def test_custom_pubkey_crypt_filter(with_hex_filter, main_unencrypted): w = writer.PdfFileWriter() custom = pdf_name('/Custom') crypt_filters = { custom: PubKeyRC4CryptFilter(keylen=16), } if main_unencrypted: # streams/strings are unencrypted by default cfc = CryptFilterConfiguration(crypt_filters=crypt_filters) else: crypt_filters[DEFAULT_CRYPT_FILTER] = PubKeyAESCryptFilter( keylen=16, acts_as_default=True) cfc = CryptFilterConfiguration( crypt_filters=crypt_filters, default_string_filter=DEFAULT_CRYPT_FILTER, default_stream_filter=DEFAULT_CRYPT_FILTER) sh = PubKeySecurityHandler(version=SecurityHandlerVersion.RC4_OR_AES128, pubkey_handler_subfilter=PubKeyAdbeSubFilter.S5, legacy_keylen=16, crypt_filter_config=cfc) # if main_unencrypted, these should be no-ops sh.add_recipients([PUBKEY_TEST_DECRYPTER.cert]) # (this is always pointless, but it should be allowed) sh.add_recipients([PUBKEY_TEST_DECRYPTER.cert]) crypt_filters[custom].add_recipients([PUBKEY_TEST_DECRYPTER.cert]) w._assign_security_handler(sh) encrypt_dict = w._encrypt.get_object() cfs = encrypt_dict['/CF'] # no /Recipients in S5 mode assert '/Recipients' not in encrypt_dict assert isinstance(cfs[custom]['/Recipients'], generic.ByteStringObject) if main_unencrypted: assert DEFAULT_CRYPT_FILTER not in cfs else: default_rcpts = cfs[DEFAULT_CRYPT_FILTER]['/Recipients'] assert isinstance(default_rcpts, generic.ArrayObject) assert len(default_rcpts) == 2 # custom crypt filters can only have one set of recipients with pytest.raises(misc.PdfError): crypt_filters[custom].add_recipients([PUBKEY_TEST_DECRYPTER.cert]) test_data = b'This is test data!' dummy_stream = generic.StreamObject(stream_data=test_data) dummy_stream.add_crypt_filter(name=custom, handler=sh) ref = w.add_object(dummy_stream) dummy_stream2 = generic.StreamObject(stream_data=test_data) ref2 = w.add_object(dummy_stream2) if with_hex_filter: dummy_stream.apply_filter(pdf_name('/AHx')) out = BytesIO() w.write(out) r = PdfFileReader(out) r.decrypt_pubkey(PUBKEY_TEST_DECRYPTER) # the custom test filter shouldn't have been decrypted yet # so attempting to decode the stream should cause the crypt filter # to throw an error obj: generic.StreamObject = r.get_object(ref.reference) with pytest.raises(misc.PdfError): # noinspection PyStatementEffect obj.data r.security_handler.crypt_filter_config[custom].authenticate( PUBKEY_TEST_DECRYPTER) assert obj.data == test_data if with_hex_filter: cf_dict = obj['/DecodeParms'][1] else: cf_dict = obj['/DecodeParms'] assert cf_dict['/Name'] == pdf_name('/Custom') obj2: generic.DecryptedObjectProxy = r.get_object( ref2.reference, transparent_decrypt=False) raw = obj2.raw_object assert isinstance(raw, generic.StreamObject) if main_unencrypted: assert raw.encoded_data == test_data else: assert raw.encoded_data != test_data
def test_sv_mdp_type(): sv_dict = fields.SigSeedValueSpec().as_pdf_object() assert '/MDP' not in sv_dict sv_dict = fields.SigSeedValueSpec( seed_signature_type=fields.SeedSignatureType(None)).as_pdf_object() assert sv_dict['/MDP'] == generic.DictionaryObject( {pdf_name('/P'): generic.NumberObject(0)}) sv_dict = fields.SigSeedValueSpec( seed_signature_type=fields.SeedSignatureType( fields.MDPPerm.NO_CHANGES)).as_pdf_object() assert sv_dict['/MDP'] == generic.DictionaryObject( {pdf_name('/P'): generic.NumberObject(1)}) sv_spec = fields.SigSeedValueSpec.from_pdf_object( generic.DictionaryObject({ pdf_name('/MDP'): generic.DictionaryObject({pdf_name('/P'): generic.NumberObject(0)}) })) assert sv_spec.seed_signature_type == fields.SeedSignatureType(None) sv_spec = fields.SigSeedValueSpec.from_pdf_object( generic.DictionaryObject({ pdf_name('/MDP'): generic.DictionaryObject({pdf_name('/P'): generic.NumberObject(2)}) })) assert sv_spec.seed_signature_type == fields.SeedSignatureType( fields.MDPPerm.FILL_FORMS) with pytest.raises(SigningError): fields.SigSeedValueSpec.from_pdf_object( generic.DictionaryObject({ pdf_name('/MDP'): generic.DictionaryObject( {pdf_name('/P'): generic.NumberObject(5)}) })) with pytest.raises(SigningError): fields.SigSeedValueSpec.from_pdf_object( generic.DictionaryObject({ pdf_name('/MDP'): generic.DictionaryObject( {pdf_name('/P'): generic.NullObject()}) })) with pytest.raises(SigningError): fields.SigSeedValueSpec.from_pdf_object( generic.DictionaryObject( {pdf_name('/MDP'): generic.DictionaryObject()})) with pytest.raises(SigningError): fields.SigSeedValueSpec.from_pdf_object( generic.DictionaryObject({pdf_name('/MDP'): generic.NullObject()}))
def root_ref(self) -> generic.Reference: raise NotImplementedError @property def document_id(self) -> Tuple[bytes, bytes]: raise NotImplementedError def get_object(self, ref, *_args, **_kwargs): if ref.idnum == 0: return generic.TextStringObject('OK') else: return generic.ArrayObject([generic.NumberObject(7)]) path_test_obj = generic.DictionaryObject({ pdf_name('/Blah'): generic.DictionaryObject({ pdf_name('/Bleh'): generic.ArrayObject([generic.NumberObject(5), pdf_name('/Foo')]), pdf_name('/Null'): generic.NullObject(), }), pdf_name('/WithRefs'): generic.DictionaryObject({ pdf_name('/Arr'): generic.IndirectObject(1, 0, PathMockHandler()), pdf_name('/String'): generic.IndirectObject(0, 0, PathMockHandler()) }) })
w.root['/DSS'] = generic.IndirectObject(idnum=dummy_ref.idnum, generation=dummy_ref.generation, pdf=w) w.update_root() out = BytesIO() w.write(out) r = PdfFileReader(out) s = r.embedded_signatures[0] assert s.field_name == 'Sig1' val_trusted_but_modified(s) BOGUS_DSS_VALUES = [ generic.pdf_string("Hi there"), generic.DictionaryObject({pdf_name('/Blah'): generic.NullObject()}), generic.DictionaryObject({pdf_name('/Certs'): generic.NullObject()}), generic.DictionaryObject({pdf_name('/VRI'): generic.NullObject()}), generic.DictionaryObject({ pdf_name('/VRI'): generic.DictionaryObject({pdf_name('/Bleh'): generic.NullObject()}) }), generic.DictionaryObject({ pdf_name('/VRI'): generic.DictionaryObject( {pdf_name('/' + 'A' * 40): generic.NullObject()}) }), generic.DictionaryObject({ pdf_name('/VRI'): generic.DictionaryObject({ pdf_name('/' + 'A' * 40):