Merge pull request #711 from dod-ccpo/pdf-signature-verification
Verify PDF signatures
This commit is contained in:
commit
27314b8120
2
Pipfile
2
Pipfile
@ -23,6 +23,7 @@ lockfile = "*"
|
|||||||
defusedxml = "*"
|
defusedxml = "*"
|
||||||
"flask-rq2" = "*"
|
"flask-rq2" = "*"
|
||||||
simplejson = "*"
|
simplejson = "*"
|
||||||
|
asn1crypto = "*"
|
||||||
|
|
||||||
[dev-packages]
|
[dev-packages]
|
||||||
bandit = "*"
|
bandit = "*"
|
||||||
@ -39,6 +40,7 @@ pytest-cov = "*"
|
|||||||
selenium = "*"
|
selenium = "*"
|
||||||
honcho = "*"
|
honcho = "*"
|
||||||
blinker = "*"
|
blinker = "*"
|
||||||
|
pytest-mock = "*"
|
||||||
|
|
||||||
[requires]
|
[requires]
|
||||||
python_version = "3.6.6"
|
python_version = "3.6.6"
|
||||||
|
85
Pipfile.lock
generated
85
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"hash": {
|
"hash": {
|
||||||
"sha256": "7aff94ddfb4f3f3ebf7f7910f3ade4eebd546b297cf72863a618824f87ec76fc"
|
"sha256": "975303153664e6936b5118686cb7056e8135e7c8184b7c0c029fa120c9e0b67e"
|
||||||
},
|
},
|
||||||
"pipfile-spec": 6,
|
"pipfile-spec": 6,
|
||||||
"requires": {
|
"requires": {
|
||||||
@ -36,6 +36,7 @@
|
|||||||
"sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87",
|
"sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87",
|
||||||
"sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"
|
"sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49"
|
||||||
],
|
],
|
||||||
|
"index": "pypi",
|
||||||
"version": "==0.24.0"
|
"version": "==0.24.0"
|
||||||
},
|
},
|
||||||
"certifi": {
|
"certifi": {
|
||||||
@ -94,10 +95,10 @@
|
|||||||
},
|
},
|
||||||
"croniter": {
|
"croniter": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:5389776e54a5e285d0c8e7b9a7e139a4d590f96f32958b0822d6d1b2faa12c0d",
|
"sha256:79a5eeaa10a7d5fb9bdae54211b8c1d306e0ed481fa970934bf3197940650d6f",
|
||||||
"sha256:fbd72189a0ff38c27e953d15175c5fedafb953479559240a1afcf8e8e7523757"
|
"sha256:c31adf6a9b0b1981d362538bfa57769acaade1d62f80c264f402ce1f8d1210b4"
|
||||||
],
|
],
|
||||||
"version": "==0.3.27"
|
"version": "==0.3.28"
|
||||||
},
|
},
|
||||||
"cryptography": {
|
"cryptography": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -209,9 +210,9 @@
|
|||||||
},
|
},
|
||||||
"mako": {
|
"mako": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:4e02fde57bd4abb5ec400181e4c314f56ac3e49ba4fb8b0d50bba18cb27d25ae"
|
"sha256:04092940c0df49b01f43daea4f5adcecd0e50ef6a4b222be5ac003d5d84b2843"
|
||||||
],
|
],
|
||||||
"version": "==1.0.7"
|
"version": "==1.0.8"
|
||||||
},
|
},
|
||||||
"markupsafe": {
|
"markupsafe": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -333,11 +334,11 @@
|
|||||||
},
|
},
|
||||||
"redis": {
|
"redis": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:724932360d48e5407e8f82e405ab3650a36ed02c7e460d1e6fddf0f038422b54",
|
"sha256:6946b5dca72e86103edc8033019cc3814c031232d339d5f4533b02ea85685175",
|
||||||
"sha256:9b19425a38fd074eb5795ff2b0d9a55b46a44f91f5347995f27e3ad257a7d775"
|
"sha256:8ca418d2ddca1b1a850afa1680a7d2fd1f3322739271de4b704e0d4668449273"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==3.2.0"
|
"version": "==3.2.1"
|
||||||
},
|
},
|
||||||
"requests": {
|
"requests": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -417,10 +418,10 @@
|
|||||||
},
|
},
|
||||||
"werkzeug": {
|
"werkzeug": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
|
"sha256:590abe38f8be026d78457fe3b5200895b3543e58ac3fc1dd792c6333ea11af64",
|
||||||
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
|
"sha256:ee11b0f0640c56fb491b43b38356c4b588b3202b415a1e03eacf1c5561c961cf"
|
||||||
],
|
],
|
||||||
"version": "==0.14.1"
|
"version": "==0.15.0"
|
||||||
},
|
},
|
||||||
"wtforms": {
|
"wtforms": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -491,11 +492,11 @@
|
|||||||
},
|
},
|
||||||
"black": {
|
"black": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:817243426042db1d36617910df579a54f1afd659adb96fc5032fcf4b36209739",
|
"sha256:09a9dcb7c46ed496a9850b76e4e825d6049ecd38b611f1224857a79bd985a8cf",
|
||||||
"sha256:e030a9a28f542debc08acceb273f228ac422798e5215ba2a791a6ddeaaca22a5"
|
"sha256:68950ffd4d9169716bcb8719a56c07a2f4485354fec061cdd5910aa07369731c"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==18.9b0"
|
"version": "==19.3b0"
|
||||||
},
|
},
|
||||||
"blinker": {
|
"blinker": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -554,10 +555,10 @@
|
|||||||
},
|
},
|
||||||
"decorator": {
|
"decorator": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:33cd704aea07b4c28b3eb2c97d288a06918275dac0ecebdaf1bc8a48d98adb9e",
|
"sha256:86156361c50488b84a3f148056ea716ca587df2f0de1d34750d35c21312725de",
|
||||||
"sha256:cabb249f4710888a2fc0e13e9a16c343d932033718ff62e1e9bc93a9d3a9122b"
|
"sha256:f069f3a01830ca754ba5258fde2278454a0b5b79e0d7f5c13b3b97e57d4acff6"
|
||||||
],
|
],
|
||||||
"version": "==4.3.2"
|
"version": "==4.4.0"
|
||||||
},
|
},
|
||||||
"docopt": {
|
"docopt": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -575,10 +576,10 @@
|
|||||||
},
|
},
|
||||||
"faker": {
|
"faker": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:16342dca4d92bfc83bab6a7daf6650e0ab087605a66bc38f17523fdb01757910",
|
"sha256:00b7011757c4907546f17d0e47df098b542ea2b04c966ee0e80a493aae2c13c8",
|
||||||
"sha256:d871ea315b2dcba9138b8344f2c131a76ac62d6227ca39f69b0c889fec97376c"
|
"sha256:745ac8b9c9526e338696e07b7f2e206e5e317e5744e22fdd7c2894bf19af41f1"
|
||||||
],
|
],
|
||||||
"version": "==1.0.2"
|
"version": "==1.0.4"
|
||||||
},
|
},
|
||||||
"flask": {
|
"flask": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -612,10 +613,10 @@
|
|||||||
},
|
},
|
||||||
"ipdb": {
|
"ipdb": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:7081c65ed7bfe7737f83fa4213ca8afd9617b42ff6b3f1daf9a3419839a2a00a"
|
"sha256:dce2112557edfe759742ca2d0fee35c59c97b0cc7a05398b791079d78f1519ce"
|
||||||
],
|
],
|
||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.11"
|
"version": "==0.12"
|
||||||
},
|
},
|
||||||
"ipython": {
|
"ipython": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -851,6 +852,14 @@
|
|||||||
"index": "pypi",
|
"index": "pypi",
|
||||||
"version": "==0.14.0"
|
"version": "==0.14.0"
|
||||||
},
|
},
|
||||||
|
"pytest-mock": {
|
||||||
|
"hashes": [
|
||||||
|
"sha256:4d0d06d173eecf172703219a71dbd4ade0e13904e6bbce1ce660e2e0dc78b5c4",
|
||||||
|
"sha256:bfdf02789e3d197bd682a758cae0a4a18706566395fbe2803badcd1335e0173e"
|
||||||
|
],
|
||||||
|
"index": "pypi",
|
||||||
|
"version": "==1.10.1"
|
||||||
|
},
|
||||||
"pytest-watch": {
|
"pytest-watch": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:06136f03d5b361718b8d0d234042f7b2f203910d8568f63df2f866b547b3d4b9"
|
"sha256:06136f03d5b361718b8d0d234042f7b2f203910d8568f63df2f866b547b3d4b9"
|
||||||
@ -867,19 +876,19 @@
|
|||||||
},
|
},
|
||||||
"pyyaml": {
|
"pyyaml": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:544a0050e76e9b60751c58617fa28c253ad5d23af2e5f0b1c250390bf90bb0df",
|
"sha256:1adecc22f88d38052fb787d959f003811ca858b799590a5eaa70e63dca50308c",
|
||||||
"sha256:594bf80477a58b6fd53e8b3f24ccf965c25eeeb6e05e4b1fb18c82c2d2090603",
|
"sha256:436bc774ecf7c103814098159fbb84c2715d25980175292c648f2da143909f95",
|
||||||
"sha256:75e20ca689d0a2bf0c84f0e2028cc68ebef34b213fa66d73c410c53f870c49f4",
|
"sha256:460a5a4248763f6f37ea225d19d5c205677d8d525f6a83357ca622ed541830c2",
|
||||||
"sha256:994da68a1dc1050f290f8017f044172360b608c0f2562b47645ecc69d7a61c0a",
|
"sha256:5a22a9c84653debfbf198d02fe592c176ea548cccce47553f35f466e15cf2fd4",
|
||||||
"sha256:ad902e00088c50bdced94a57b819c24fdadaeaed5494df7a9a67d63774f210fd",
|
"sha256:7a5d3f26b89d688db27822343dfa25c599627bc92093e788956372285c6298ad",
|
||||||
"sha256:b11aff75875ffc73541c4e4b1ac2f5e21717c1fc4396238943b9a44d962e74e1",
|
"sha256:9372b04a02080752d9e6f990179a4ab840227c6e2ce15b95e1278456664cf2ba",
|
||||||
"sha256:bc733b5a9047c3e4848c0e80eeacfa6a799139242606410260c5450d665ea58c",
|
"sha256:a5dcbebee834eaddf3fa7366316b880ff4062e4bcc9787b78c7fbb4a26ff2dd1",
|
||||||
"sha256:d960c68931b96bb215f385baa8ef867b8ebac66af60fa06cc1008f963848c7ad",
|
"sha256:aee5bab92a176e7cd034e57f46e9df9a9862a71f8f37cad167c6fc74c65f5b4e",
|
||||||
"sha256:dd461c04e6a91e4eef7d5b75c1fc1c7013d3f8d354033b16526baadddd524079",
|
"sha256:c51f642898c0bacd335fc119da60baae0824f2cde95b0330b56c0553439f0673",
|
||||||
"sha256:e4d6b5d6218a06f3141189d75c93876dd525a6d15f1b00ef4f274726c93719f1",
|
"sha256:c68ea4d3ba1705da1e0d85da6684ac657912679a649e8868bd850d2c299cce13",
|
||||||
"sha256:f3c386fa12415bde8a0162745c4badf98fe171c6dfd67e54831f05ec88feeebb"
|
"sha256:e23d0cc5299223dcc37885dae624f382297717e459ea24053709675a976a3e19"
|
||||||
],
|
],
|
||||||
"version": "==5.1b5"
|
"version": "==5.1"
|
||||||
},
|
},
|
||||||
"selenium": {
|
"selenium": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
@ -978,10 +987,10 @@
|
|||||||
},
|
},
|
||||||
"werkzeug": {
|
"werkzeug": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
"sha256:c3fd7a7d41976d9f44db327260e263132466836cef6f91512889ed60ad26557c",
|
"sha256:590abe38f8be026d78457fe3b5200895b3543e58ac3fc1dd792c6333ea11af64",
|
||||||
"sha256:d5da73735293558eb1651ee2fddc4d0dedcfa06538b8813a2e20011583c9e49b"
|
"sha256:ee11b0f0640c56fb491b43b38356c4b588b3202b415a1e03eacf1c5561c961cf"
|
||||||
],
|
],
|
||||||
"version": "==0.14.1"
|
"version": "==0.15.0"
|
||||||
},
|
},
|
||||||
"wrapt": {
|
"wrapt": {
|
||||||
"hashes": [
|
"hashes": [
|
||||||
|
@ -52,7 +52,7 @@ def get_current_user():
|
|||||||
|
|
||||||
def logout():
|
def logout():
|
||||||
if session.get("user_id"): # pragma: no branch
|
if session.get("user_id"): # pragma: no branch
|
||||||
del (session["user_id"])
|
del session["user_id"]
|
||||||
|
|
||||||
|
|
||||||
def _unprotected_route(request):
|
def _unprotected_route(request):
|
||||||
|
224
atst/utils/pdf_verification.py
Normal file
224
atst/utils/pdf_verification.py
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
import hashlib
|
||||||
|
from OpenSSL import crypto
|
||||||
|
from asn1crypto import cms, pem, core
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import padding
|
||||||
|
|
||||||
|
|
||||||
|
class PDFSignature:
|
||||||
|
def __init__(self, byte_range_start=None, crl_check=None, pdf=None):
|
||||||
|
self._signers_cert = None
|
||||||
|
self._openssl_loaded_certificate = None
|
||||||
|
self.byte_range_start = byte_range_start
|
||||||
|
self.crl_check = crl_check
|
||||||
|
self.pdf = pdf
|
||||||
|
|
||||||
|
@property
|
||||||
|
def byte_range(self):
|
||||||
|
"""
|
||||||
|
This returns an array of 4 numbers that represent the byte range of
|
||||||
|
the PDF binary file that is signed by the certificate.
|
||||||
|
|
||||||
|
E.G: [0, 2045, 3012, 5012]
|
||||||
|
|
||||||
|
Bytes 0 to 2045 - represent part A of the signed file
|
||||||
|
Bytes 2046 to 3012 - would contain the signature and certificate information
|
||||||
|
Bytes 3013 to 5012 - represent part B of the signed file
|
||||||
|
"""
|
||||||
|
start = self.pdf.find(b"[", self.byte_range_start)
|
||||||
|
stop = self.pdf.find(b"]", start)
|
||||||
|
contents_range = [int(i, 10) for i in self.pdf[start + 1 : stop].split()]
|
||||||
|
|
||||||
|
return contents_range
|
||||||
|
|
||||||
|
@property
|
||||||
|
def signed_binary_data(self):
|
||||||
|
"""
|
||||||
|
This is the binary data stored in the signature
|
||||||
|
"""
|
||||||
|
br = self.byte_range
|
||||||
|
contents = self.pdf[br[0] + br[1] + 1 : br[2] - 1]
|
||||||
|
data = []
|
||||||
|
|
||||||
|
for i in range(0, len(contents), 2):
|
||||||
|
data.append(int(contents[i : i + 2], 16))
|
||||||
|
|
||||||
|
return cms.ContentInfo.load(bytes(data))["content"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def signers_cert(self):
|
||||||
|
"""
|
||||||
|
This returns the certificate used to sign the PDF
|
||||||
|
"""
|
||||||
|
if self._signers_cert is None:
|
||||||
|
for cert in self.signed_binary_data["certificates"]:
|
||||||
|
if (
|
||||||
|
self.signers_serial
|
||||||
|
== cert.native["tbs_certificate"]["serial_number"]
|
||||||
|
):
|
||||||
|
cert = cert.dump()
|
||||||
|
self._signers_cert = pem.armor("CERTIFICATE", cert)
|
||||||
|
break
|
||||||
|
|
||||||
|
return self._signers_cert
|
||||||
|
|
||||||
|
@property
|
||||||
|
def signers_serial(self):
|
||||||
|
"""
|
||||||
|
Return the signers serial from their certificate
|
||||||
|
"""
|
||||||
|
return self.signed_binary_data["signer_infos"][0]["sid"].native["serial_number"]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hashing_algorithm(self):
|
||||||
|
"""
|
||||||
|
This is the hashing algorithm used to generate the hash of binary file content
|
||||||
|
which is then signed by the certificate.
|
||||||
|
|
||||||
|
E.G. sha256, sha1
|
||||||
|
"""
|
||||||
|
return self.signed_binary_data["digest_algorithms"][0]["algorithm"].native
|
||||||
|
|
||||||
|
@property
|
||||||
|
def openssl_loaded_certificate(self):
|
||||||
|
if self._openssl_loaded_certificate is None:
|
||||||
|
self._openssl_loaded_certificate = crypto.load_certificate(
|
||||||
|
crypto.FILETYPE_PEM, self.signers_cert
|
||||||
|
)
|
||||||
|
return self._openssl_loaded_certificate
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cert_common_name(self):
|
||||||
|
"""
|
||||||
|
This returns the common name on the certificate. This might be a name or
|
||||||
|
a DOD ID for example.
|
||||||
|
"""
|
||||||
|
return self.openssl_loaded_certificate.get_subject().commonName
|
||||||
|
|
||||||
|
@property
|
||||||
|
def encrypted_hash_of_signed_document(self):
|
||||||
|
"""
|
||||||
|
This is the calculated hash of the PDF binary data stored in the
|
||||||
|
signature. We calculate it outselves and then compare to this
|
||||||
|
so we can see if data has changed.
|
||||||
|
"""
|
||||||
|
stored_hash = None
|
||||||
|
|
||||||
|
for attr in self.signed_binary_data["signer_infos"][0]["signed_attrs"]:
|
||||||
|
if attr["type"].native == "message_digest":
|
||||||
|
stored_hash = attr["values"].native[0]
|
||||||
|
break
|
||||||
|
|
||||||
|
return stored_hash
|
||||||
|
|
||||||
|
@property
|
||||||
|
def binary_data(self):
|
||||||
|
"""
|
||||||
|
Take the byte range and return the binary data for that rage.
|
||||||
|
"""
|
||||||
|
br = self.byte_range
|
||||||
|
data1 = self.pdf[br[0] : br[0] + br[1]]
|
||||||
|
data2 = self.pdf[br[2] : br[2] + br[3]]
|
||||||
|
|
||||||
|
return data1 + data2
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hashed_binary_data(self):
|
||||||
|
"""
|
||||||
|
Takes the data in the byte range and hashes it using
|
||||||
|
the hashing algorithm specified in the signed PDF. We
|
||||||
|
can later compare this to the encrypted_hash_of_signed_document.
|
||||||
|
"""
|
||||||
|
return getattr(hashlib, self.hashing_algorithm)(self.binary_data)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_cert_valid(self):
|
||||||
|
"""
|
||||||
|
Takes the signing certificate and runs it through the CRLCache
|
||||||
|
checker. Returns a boolean.
|
||||||
|
"""
|
||||||
|
return self.crl_check(self.signers_cert)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_signature_valid(self):
|
||||||
|
"""
|
||||||
|
Get signed PDF signature and determine if it was actually signed
|
||||||
|
by the certificate that it claims it was. Returns a boolean.
|
||||||
|
"""
|
||||||
|
public_key = self.openssl_loaded_certificate.get_pubkey().to_cryptography_key()
|
||||||
|
attrs = self.signed_binary_data["signer_infos"][0]["signed_attrs"]
|
||||||
|
signed_data = None
|
||||||
|
|
||||||
|
if attrs is not None and not isinstance(attrs, core.Void):
|
||||||
|
signed_data = attrs.dump()
|
||||||
|
signed_data = b"\x31" + signed_data[1:]
|
||||||
|
else:
|
||||||
|
signed_data = self.binary_data
|
||||||
|
|
||||||
|
try:
|
||||||
|
public_key.verify(
|
||||||
|
bytes(self.signed_binary_data["signer_infos"][0]["signature"]),
|
||||||
|
signed_data,
|
||||||
|
padding.PKCS1v15(),
|
||||||
|
getattr(hashes, self.hashing_algorithm.upper())(),
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
||||||
|
@property
|
||||||
|
def to_dict(self):
|
||||||
|
is_cert_valid = self.is_cert_valid
|
||||||
|
is_signature_valid = self.is_signature_valid
|
||||||
|
is_hash_valid = (
|
||||||
|
self.hashed_binary_data.digest() == self.encrypted_hash_of_signed_document
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"cert_common_name": self.cert_common_name,
|
||||||
|
"hashed_binary_data": self.hashed_binary_data.hexdigest(),
|
||||||
|
"hashing_algorithm": self.hashing_algorithm,
|
||||||
|
"is_valid": is_cert_valid and is_hash_valid and is_signature_valid,
|
||||||
|
"is_valid_cert": is_cert_valid,
|
||||||
|
"is_valid_hash": is_hash_valid,
|
||||||
|
"is_valid_signature": is_signature_valid,
|
||||||
|
"signers_serial": self.signers_serial,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def pdf_signature_validations(pdf=None, crl_check=None):
|
||||||
|
"""
|
||||||
|
As arguments we accept a pdf binary blob and a callable crl_check.
|
||||||
|
An example implementation of the crl_check can be found in the
|
||||||
|
tests (test/utils/test_pdf_verification.py)
|
||||||
|
"""
|
||||||
|
signatures = []
|
||||||
|
start_byte = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
start = start_byte + 1
|
||||||
|
n = pdf.find(b"/ByteRange", start)
|
||||||
|
|
||||||
|
if n == -1:
|
||||||
|
break
|
||||||
|
|
||||||
|
signatures.append(
|
||||||
|
PDFSignature(byte_range_start=n, crl_check=crl_check, pdf=pdf)
|
||||||
|
)
|
||||||
|
start_byte = n
|
||||||
|
|
||||||
|
response = {"result": None, "signature_count": len(signatures), "signatures": []}
|
||||||
|
|
||||||
|
for signature in signatures:
|
||||||
|
sig = signature.to_dict
|
||||||
|
response["signatures"].append(sig)
|
||||||
|
|
||||||
|
if not sig["is_valid"]:
|
||||||
|
response["result"] = False
|
||||||
|
elif response["result"] is not False:
|
||||||
|
response["result"] = True
|
||||||
|
|
||||||
|
if len(signatures) == 0:
|
||||||
|
response["result"] = False
|
||||||
|
|
||||||
|
return response
|
BIN
tests/fixtures/sally-darth-signed.pdf
vendored
Normal file
BIN
tests/fixtures/sally-darth-signed.pdf
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/signed-expired-cert.pdf
vendored
Normal file
BIN
tests/fixtures/signed-expired-cert.pdf
vendored
Normal file
Binary file not shown.
BIN
tests/fixtures/signed-pdf-not-dod.pdf
vendored
Normal file
BIN
tests/fixtures/signed-pdf-not-dod.pdf
vendored
Normal file
Binary file not shown.
145
tests/utils/test_pdf_verification.py
Normal file
145
tests/utils/test_pdf_verification.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
import cryptography
|
||||||
|
from atst.domain.authnid.crl import CRLCache, CRLRevocationException
|
||||||
|
from atst.utils.pdf_verification import pdf_signature_validations
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def crl_check():
|
||||||
|
def _crl_check(signers_cert):
|
||||||
|
try:
|
||||||
|
cache = CRLCache(
|
||||||
|
"ssl/server-certs/ca-chain.pem",
|
||||||
|
crl_locations=["ssl/client-certs/client-ca.der.crl"],
|
||||||
|
)
|
||||||
|
return cache.crl_check(signers_cert)
|
||||||
|
except CRLRevocationException:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return _crl_check
|
||||||
|
|
||||||
|
|
||||||
|
def test_unsigned_pdf(crl_check):
|
||||||
|
unsigned_pdf = open("tests/fixtures/sample.pdf", "rb").read()
|
||||||
|
result = pdf_signature_validations(pdf=unsigned_pdf, crl_check=crl_check)
|
||||||
|
|
||||||
|
assert result == {"result": False, "signature_count": 0, "signatures": []}
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_signed_pdf(crl_check):
|
||||||
|
valid_signed_pdf = open("tests/fixtures/sally-darth-signed.pdf", "rb").read()
|
||||||
|
result = pdf_signature_validations(pdf=valid_signed_pdf, crl_check=crl_check)
|
||||||
|
|
||||||
|
assert result == {
|
||||||
|
"result": True,
|
||||||
|
"signature_count": 2,
|
||||||
|
"signatures": [
|
||||||
|
{
|
||||||
|
"cert_common_name": "WILLIAMS.SALLY.3453453453",
|
||||||
|
"hashed_binary_data": "b879a15e19eece534dc63019d3fe539ff4a3efbf8e8f5403a8bdae26a9b713ea",
|
||||||
|
"hashing_algorithm": "sha256",
|
||||||
|
"is_valid": True,
|
||||||
|
"is_valid_cert": True,
|
||||||
|
"is_valid_hash": True,
|
||||||
|
"is_valid_signature": True,
|
||||||
|
"signers_serial": 9_662_248_800_192_484_626,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cert_common_name": "VADER.DARTH.9012345678",
|
||||||
|
"hashed_binary_data": "d98339766c20a369219f236220d7b450111554acc902e242d015dd6d306c7809",
|
||||||
|
"hashing_algorithm": "sha256",
|
||||||
|
"is_valid": True,
|
||||||
|
"is_valid_cert": True,
|
||||||
|
"is_valid_hash": True,
|
||||||
|
"is_valid_signature": True,
|
||||||
|
"signers_serial": 9_662_248_800_192_484_627,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_signed_pdf_thats_been_modified(crl_check):
|
||||||
|
valid_signed_pdf = open("tests/fixtures/sally-darth-signed.pdf", "rb").read()
|
||||||
|
modified_pdf = valid_signed_pdf.replace(b"PDF-1.6", b"PDF-1.7")
|
||||||
|
result = pdf_signature_validations(pdf=modified_pdf, crl_check=crl_check)
|
||||||
|
|
||||||
|
assert result == {
|
||||||
|
"result": False,
|
||||||
|
"signature_count": 2,
|
||||||
|
"signatures": [
|
||||||
|
{
|
||||||
|
"cert_common_name": "WILLIAMS.SALLY.3453453453",
|
||||||
|
"hashed_binary_data": "d1fb3c955b57f139331586276ba4abca90ecc5d36b53fe6bbbbbd8707d7124bb",
|
||||||
|
"hashing_algorithm": "sha256",
|
||||||
|
"is_valid": False,
|
||||||
|
"is_valid_cert": True,
|
||||||
|
"is_valid_hash": False,
|
||||||
|
"is_valid_signature": True,
|
||||||
|
"signers_serial": 9_662_248_800_192_484_626,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"cert_common_name": "VADER.DARTH.9012345678",
|
||||||
|
"hashed_binary_data": "75ef47824de4b5477c75665c5a90e39a2b8a8985422cf2f7f641661a7b5217a8",
|
||||||
|
"hashing_algorithm": "sha256",
|
||||||
|
"is_valid": False,
|
||||||
|
"is_valid_cert": True,
|
||||||
|
"is_valid_hash": False,
|
||||||
|
"is_valid_signature": True,
|
||||||
|
"signers_serial": 9_662_248_800_192_484_627,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def test_signed_pdf_that_has_invalid_signature(mocker):
|
||||||
|
def mock_crl_check(_):
|
||||||
|
return True
|
||||||
|
|
||||||
|
mocker.patch.object(
|
||||||
|
cryptography.hazmat.backends.openssl.rsa._RSAPublicKey, "verify", Exception()
|
||||||
|
)
|
||||||
|
|
||||||
|
valid_signed_pdf = open("tests/fixtures/signed-pdf-not-dod.pdf", "rb").read()
|
||||||
|
result = pdf_signature_validations(pdf=valid_signed_pdf, crl_check=mock_crl_check)
|
||||||
|
|
||||||
|
assert result == {
|
||||||
|
"result": False,
|
||||||
|
"signature_count": 1,
|
||||||
|
"signatures": [
|
||||||
|
{
|
||||||
|
"cert_common_name": "John B Harris",
|
||||||
|
"hashed_binary_data": "3f0047e6cb5b9bb089254b20d174445c3ba4f513",
|
||||||
|
"hashing_algorithm": "sha1",
|
||||||
|
"is_valid": False,
|
||||||
|
"is_valid_cert": True,
|
||||||
|
"is_valid_hash": True,
|
||||||
|
"is_valid_signature": False,
|
||||||
|
"signers_serial": 514,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skip(reason="Need fixture file")
|
||||||
|
def test_signed_pdf_dod_revoked(crl_check):
|
||||||
|
signed_pdf_dod_revoked = open(
|
||||||
|
"tests/fixtures/signed-pdf-dod_revoked.pdf", "rb"
|
||||||
|
).read()
|
||||||
|
result = pdf_signature_validations(pdf=signed_pdf_dod_revoked, crl_check=crl_check)
|
||||||
|
|
||||||
|
assert result == {
|
||||||
|
"result": False,
|
||||||
|
"signature_count": 1,
|
||||||
|
"signatures": [
|
||||||
|
{
|
||||||
|
"cert_common_name": None,
|
||||||
|
"hashed_binary_data": None,
|
||||||
|
"hashing_algorithm": None,
|
||||||
|
"is_valid": None,
|
||||||
|
"is_valid_cert": None,
|
||||||
|
"is_valid_hash": None,
|
||||||
|
"signers_serial": None,
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user