def _tamper_with_sig_obj(tamper_fun): input_buf = BytesIO(MINIMAL) w = IncrementalPdfFileWriter(input_buf) md_algorithm = 'sha256' cms_writer = cms_embedder.PdfCMSEmbedder().write_cms( field_name='Signature', writer=w ) next(cms_writer) sig_obj = signers.SignatureObject(bytes_reserved=8192) cms_writer.send(cms_embedder.SigObjSetup(sig_placeholder=sig_obj)) tamper_fun(w, sig_obj) prep_document_hash, output = cms_writer.send( cms_embedder.SigIOSetup(md_algorithm=md_algorithm, in_place=True) ) signer: signers.SimpleSigner = signers.SimpleSigner( signing_cert=FROM_CA.signing_cert, signing_key=FROM_CA.signing_key, cert_registry=FROM_CA.cert_registry, signature_mechanism=SignedDigestAlgorithm({ 'algorithm': 'rsassa_pkcs1v15' }) ) with pytest.deprecated_call(): # noinspection PyDeprecation cms_obj = signer.sign( data_digest=prep_document_hash.document_digest, digest_algorithm=md_algorithm, ) cms_writer.send(cms_obj) return output
async def test_sign_weak_sig_digest(): # We have to jump through some hoops to put together a signature # where the signing method's digest is not the same as the "external" # digest. This is intentional, since it's bad practice. input_buf = BytesIO(MINIMAL) w = IncrementalPdfFileWriter(input_buf) cms_writer = cms_embedder.PdfCMSEmbedder().write_cms( field_name='Signature', writer=w ) next(cms_writer) timestamp = datetime.now(tz=tzlocal.get_localzone()) sig_obj = signers.SignatureObject(timestamp=timestamp, bytes_reserved=8192) external_md_algorithm = 'sha256' cms_writer.send(cms_embedder.SigObjSetup(sig_placeholder=sig_obj)) prep_digest, output = cms_writer.send( cms_embedder.SigIOSetup(md_algorithm=external_md_algorithm, in_place=True) ) signer = signers.SimpleSigner( signing_cert=TESTING_CA.get_cert(CertLabel('signer1')), signing_key=TESTING_CA.key_set.get_private_key(KeyLabel('signer1')), cert_registry=SimpleCertificateStore.from_certs([ROOT_CERT, INTERM_CERT]) ) cms_obj = await signer.async_sign( data_digest=prep_digest.document_digest, digest_algorithm=external_md_algorithm, signed_attr_settings=PdfCMSSignedAttributes(signing_time=timestamp) ) si_obj: cms.SignerInfo = cms_obj['content']['signer_infos'][0] bad_algo = SignedDigestAlgorithm({'algorithm': 'md5_rsa'}) si_obj['signature_algorithm'] = signer.signature_mechanism = bad_algo attrs = si_obj['signed_attrs'] cms_prot = find_cms_attribute(attrs, 'cms_algorithm_protection')[0] cms_prot['signature_algorithm'] = bad_algo # recompute the signature si_obj['signature'] = signer.sign_raw(attrs.untag().dump(), 'md5') sig_contents = cms_writer.send(cms_obj) # we requested in-place output assert output is input_buf r = PdfFileReader(input_buf) emb = r.embedded_signatures[0] with pytest.raises(WeakHashAlgorithmError): await async_val_trusted(emb) lenient_vc = ValidationContext( trust_roots=[ROOT_CERT], weak_hash_algos=set() ) await async_val_trusted(emb, vc=lenient_vc)
def _tamper_with_signed_attrs(attr_name, *, duplicate=False, delete=False, replace_with=None, resign=False): input_buf = BytesIO(MINIMAL) w = IncrementalPdfFileWriter(input_buf) md_algorithm = 'sha256' cms_writer = cms_embedder.PdfCMSEmbedder().write_cms( field_name='Signature', writer=w ) next(cms_writer) sig_obj = signers.SignatureObject(bytes_reserved=8192) cms_writer.send(cms_embedder.SigObjSetup(sig_placeholder=sig_obj)) prep_digest, output = cms_writer.send( cms_embedder.SigIOSetup(md_algorithm=md_algorithm, in_place=True) ) signer: signers.SimpleSigner = signers.SimpleSigner( signing_cert=FROM_CA.signing_cert, signing_key=FROM_CA.signing_key, cert_registry=FROM_CA.cert_registry, signature_mechanism=SignedDigestAlgorithm({ 'algorithm': 'rsassa_pkcs1v15' }) ) with pytest.deprecated_call(): # noinspection PyDeprecation cms_obj = signer.sign( data_digest=prep_digest.document_digest, digest_algorithm=md_algorithm, ) sd = cms_obj['content'] si, = sd['signer_infos'] signed_attrs = si['signed_attrs'] ix = next( ix for ix, attr in enumerate(signed_attrs) if attr['type'].native == attr_name ) # mess with the attribute in the requested way if delete: del signed_attrs[ix] elif duplicate: vals = signed_attrs[ix]['values'] vals.append(vals[0]) else: vals = signed_attrs[ix]['values'] vals[0] = replace_with # ... and replace the signature if requested if resign: si['signature'] = \ signer.sign_raw(si['signed_attrs'].untag().dump(), md_algorithm) cms_writer.send(cms_obj) return output
def digest(self): self.cms_writer.send( cms_embedder.SigObjSetup( sig_placeholder=self.sig_obj, mdp_setup=cms_embedder.SigMDPSetup( md_algorithm='sha256', certify=True, docmdp_perms=fields.MDPPerm.NO_CHANGES, ))) digest, self.out_stream = self.cms_writer.send( cms_embedder.SigIOSetup(md_algorithm='sha256', in_place=True)) result = base64.b64encode(digest.document_digest) return result.decode('ascii')
async def test_no_certificates(delete): input_buf = BytesIO(MINIMAL) w = IncrementalPdfFileWriter(input_buf) md_algorithm = 'sha256' cms_writer = cms_embedder.PdfCMSEmbedder().write_cms( field_name='Signature', writer=w ) next(cms_writer) sig_obj = signers.SignatureObject(bytes_reserved=8192) cms_writer.send(cms_embedder.SigObjSetup(sig_placeholder=sig_obj)) prep_digest, output = cms_writer.send( cms_embedder.SigIOSetup(md_algorithm=md_algorithm, in_place=True) ) signer: signers.SimpleSigner = signers.SimpleSigner( signing_cert=FROM_CA.signing_cert, signing_key=FROM_CA.signing_key, cert_registry=FROM_CA.cert_registry, signature_mechanism=SignedDigestAlgorithm({ 'algorithm': 'rsassa_pkcs1v15' }) ) cms_obj = await signer.async_sign( data_digest=prep_digest.document_digest, digest_algorithm=md_algorithm, ) sd = cms_obj['content'] if delete: del sd['certificates'] else: sd['certificates'] = cms.CertificateSet([]) cms_writer.send(cms_obj) r = PdfFileReader(output) with pytest.raises(CMSExtractionError, match='signer cert.*includ'): emb = r.embedded_signatures[0] await collect_validation_info( embedded_sig=emb, validation_context=ValidationContext() ) with pytest.raises(SignatureValidationError, match='signer cert.*includ'): r.embedded_signatures[0].signer_cert.dump()
async def test_add_revinfo_wrong_subfilter(): sv = fields.SigSeedValueSpec(flags=fields.SigSeedValFlags.ADD_REV_INFO, add_rev_info=True) sig_field_spec = fields.SigFieldSpec( sig_field_name='Sig', seed_value_dict=sv, ) w = IncrementalPdfFileWriter(BytesIO(MINIMAL)) fields.append_signature_field(w, sig_field_spec) cms_writer = cms_embedder.PdfCMSEmbedder().write_cms(field_name='Sig', writer=w) next(cms_writer) # wrong subfilter: PAdES # but we do embed an (empty) RevInfoArchival attribute sig_obj = signers.SignatureObject(bytes_reserved=8192, subfilter=PADES) cms_writer.send(cms_embedder.SigObjSetup(sig_placeholder=sig_obj)) prep_digest, output = cms_writer.send( cms_embedder.SigIOSetup(md_algorithm='sha256', in_place=True)) cms_obj = await FROM_CA.async_sign( data_digest=prep_digest.document_digest, digest_algorithm='sha256', signed_attr_settings=PdfCMSSignedAttributes( # empty adobe_revinfo_attr=asn1_pdf.RevocationInfoArchival({'ocsp': []}))) await PdfTBSDocument.async_finish_signing(output, prep_digest, cms_obj) r = PdfFileReader(output) s = r.embedded_signatures[0] status = await async_validate_pdf_signature(s, dummy_ocsp_vc()) summary = status.pretty_print_details() assert status.intact and status.valid assert 'not satisfy the SV constraints' in summary assert 'requires subfilter' in status.seed_value_constraint_error.args[0] assert not status.seed_value_ok
def test_direct_pdfcmsembedder_usage(): # CMS-agnostic signing example # # write an in-place certification signature using the PdfCMSEmbedder # low-level API directly. input_buf = BytesIO(MINIMAL) w = IncrementalPdfFileWriter(input_buf) # Phase 1: coroutine sets up the form field cms_writer = cms_embedder.PdfCMSEmbedder().write_cms( field_name='Signature', writer=w ) sig_field_ref = next(cms_writer) # just for kicks, let's check assert sig_field_ref.get_object()['/T'] == 'Signature' # Phase 2: make a placeholder signature object, # wrap it up together with the MDP config we want, and send that # on to cms_writer timestamp = datetime.now(tz=tzlocal.get_localzone()) sig_obj = signers.SignatureObject(timestamp=timestamp, bytes_reserved=8192) md_algorithm = 'sha256' cms_writer.send( cms_embedder.SigObjSetup( sig_placeholder=sig_obj, mdp_setup=cms_embedder.SigMDPSetup( md_algorithm=md_algorithm, certify=True, docmdp_perms=fields.MDPPerm.NO_CHANGES ) ) ) # Phase 3: write & hash the document (with placeholder) prep_digest, output = cms_writer.send( cms_embedder.SigIOSetup(md_algorithm=md_algorithm, in_place=True) ) # Phase 4: construct CMS signature object, and pass it on to cms_writer # NOTE: I'm using a regular SimpleSigner here, but you can substitute # whatever CMS supplier you want. signer: signers.SimpleSigner = FROM_CA # let's supply the CMS object as a raw bytestring with pytest.deprecated_call(): # noinspection PyDeprecation cms_bytes = signer.sign( data_digest=prep_digest.document_digest, digest_algorithm=md_algorithm, timestamp=timestamp ).dump() sig_contents = cms_writer.send(cms_bytes) # we requested in-place output assert output is input_buf r = PdfFileReader(input_buf) val_trusted(r.embedded_signatures[0]) # add some stuff to the DSS for kicks DocumentSecurityStore.add_dss( output, sig_contents, certs=FROM_CA.cert_registry, ocsps=(FIXED_OCSP,) ) r = PdfFileReader(input_buf) dss = DocumentSecurityStore.read_dss(handler=r) val_trusted(r.embedded_signatures[0], extd=True) assert dss is not None assert len(dss.certs) == 3 assert len(dss.ocsps) == 1