wbond / asn1crypto

Python ASN.1 library with a focus on performance and a pythonic API
MIT License
335 stars 140 forks source link

Failing to load timestamp from globalsign #179

Closed hejsan closed 4 years ago

hejsan commented 4 years ago

Hi, thanks for a great library. I am writing a small library intended to be a high level (as in simple to use) library for digitally signing pdf's generated with the WeasyPrint library (https://github.com/Kozea/WeasyPrint).

I have already got it working for self-signed certificates and now I'm working on an adapter for digital signatures from the Globalsign DSS API (https://www.globalsign.com/en/resources/apis/api-documentation/digital-signing-service-api-documentation.html)

This is a direct link to the API Doc for the API call for a timestamp: https://www.globalsign.com/en/resources/apis/api-documentation/digital-signing-service-api-documentation.html#timestamp__digest__get

I get an error when I try to load the timestamp token:

resp = requests.get(self._url + '/timestamp/{digest}'.format(digest=signed_value.hex().upper()))
timestamp_token = resp.json()['token']
timestamp_token = base64.b64decode(timestamp_token)
tsp_resp = tsp.TimeStampResp.load(timestamp_token)

# When I try to access properties I get exceptions
print(tsp_resp['status'])

  File "/usr/local/src/python/WeasySign/weasysign/globalsign.py", line 335, in _sign
    print(tsp_resp['status'])
  File "/usr/local/lib/python3.6/site-packages/asn1crypto/core.py", line 3514, in __getitem__
    raise e
  File "/usr/local/lib/python3.6/site-packages/asn1crypto/core.py", line 3509, in __getitem__
    return self._lazy_child(key)
  File "/usr/local/lib/python3.6/site-packages/asn1crypto/core.py", line 3456, in _lazy_child
    child = self.children[index] = _build(*child)
  File "/usr/local/lib/python3.6/site-packages/asn1crypto/core.py", line 5535, in _build
    METHOD_NUM_TO_NAME_MAP.get(method, method)
ValueError: Error parsing asn1crypto.tsp.PKIStatusInfo - method should have been constructed, but primitive was found
    while parsing asn1crypto.tsp.TimeStampResp

# And when I try to access the 'time_stamp_token' property:
print(tsp_resp['time_stamp_token'])

 File "/usr/local/src/python/WeasySign/weasysign/globalsign.py", line 336, in _sign
    print(tsp_resp['time_stamp_token'])
  File "/usr/local/lib/python3.6/site-packages/asn1crypto/core.py", line 3514, in __getitem__
    raise e
  File "/usr/local/lib/python3.6/site-packages/asn1crypto/core.py", line 3509, in __getitem__
    return self._lazy_child(key)
  File "/usr/local/lib/python3.6/site-packages/asn1crypto/core.py", line 3456, in _lazy_child
    child = self.children[index] = _build(*child)
  File "/usr/local/lib/python3.6/site-packages/asn1crypto/core.py", line 5522, in _build
    CLASS_NUM_TO_NAME_MAP.get(class_, class_)
ValueError: Error parsing asn1crypto.cms.ContentInfo - class should have been universal, but context was found
    while parsing asn1crypto.tsp.TimeStampResp

Do you have an idea of what is wrong? Is globalsign using it a different format for the timestamp token? Thanks Bjarni

wbond commented 4 years ago

I would need an example of the raw data you are passing into tsp.TimeStampResp.load.

hejsan commented 4 years ago

Thank you for the quick response Here's what the timestamp looks like:

b'0\x82\t\x8c\x06\t*\x86H\x86\xf7\r\x01\x07\x02\xa0\x82\t}0\x82\ty\x02\x01\x031\x0f0\r\x06\t`\x86H\x01e\x03\x04\x02\x01\x05\x000\x81\xe6\x06\x0b*\x86H\x86\xf7\r\x01\t\x10\x01\x04\xa0\x81\xd6\x04\x81\xd30\x81\xd0\x02\x01\x01\x06\t+\x06\x01\x04\x01\xa02\x01\x1f010\r\x06\t`\x86H\x01e\x03\x04\x02\x01\x05\x00\x04 \x04|\x11\xd9\xcf\xf9\xb53\x91C\x9f\x01mi\x0e\xd3]4\xc7\xa4\x1f\x146#\xa6\xa8Ve(\xaf\xe1s\x02\x10\tlN|\x11#\x8aFWm\x01O\xfe\xc1\xdft\x18\x0f20200403111050Z0\x03\x02\x01\x01\xa0e\xa4c0a1503\x06\x03U\x04\x03\x0c,Globalsign TSA for AATL on DSS - SHA384 - G51\x1b0\x19\x06\x03U\x04\n\x0c\x12GMO GlobalSign Ltd1\x0b0\t\x06\x03U\x04\x06\x13\x02GB\xa0\x82\x05\xd40\x82\x05\xd00\x82\x03\xb8\xa0\x03\x02\x01\x02\x02\x0c\x167\xae\x1d\xe7\xf0\xf1T\xc7\x90\xde\xa00\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0c\x05\x000W1\x0b0\t\x06\x03U\x04\x06\x13\x02BE1\x190\x17\x06\x03U\x04\n\x13\x10GlobalSign nv-sa1-0+\x06\x03U\x04\x03\x13$GlobalSign CA for AATL - SHA384 - G40\x1e\x17\r191024000000Z\x17\r310121000000Z0a1503\x06\x03U\x04\x03\x0c,Globalsign TSA for AATL on DSS - SHA384 - G51\x1b0\x19\x06\x03U\x04\n\x0c\x12GMO GlobalSign Ltd1\x0b0\t\x06\x03U\x04\x06\x13\x02GB0\x82\x01"0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x01\x05\x00\x03\x82\x01\x0f\x000\x82\x01\n\x02\x82\x01\x01\x00\x88e9&Bd\xfcVa\x92\x14E0no\xdfu\xbdg\xc9\xa2&\x15\x1b\x86\xc4\xff\x12\x88n1\xd2M\x8f(\x9b\xf30\x08pi\xba\x08\x8b\xe3\xce\xcc\x91\x81\x1a\x95\x90\x1b\x15\xd3d\x9ew"\xb1\x8b\x0f.\x805h:\x1dG,\xe5\x10\xb6>\xd4\xd5\xe3\x01*I;\x00\xdc\xe0\x17\xa9\x01\xfe9\xefGt\xd0P$<\x8a=\xe9\xcd\x16.L^\x11\xda\x02k\x1a\xe1\xb0:\xba\xa1\x80W\x9eu\xf0\x82\xb6\xf2\xc9\xe3\x95F\xb2W\xe4\xdd\xab<\r2\xab\x1c\x0c\x1bie\xfc\xca\x90:\xb5\xb6! w\xc4ne\xe8;\xeb$92I!\xb2\xc7\x15\xc0\x05\xdb\xde+\x86\xc8\x92\xb4x\x84\xe5\xc6!\xc0l\xaf\xf4\x02A\xcb^\x16~\x97\x95\xc4o\xff\x8a\xdc\xe6\x9bU!<\x90\xc7;\xf9\x90\xc7\x85\xe3\x1d\x84!\x8c\x9c\xeb\xb0!\xe3\xf1\xab\x8f\x95\x976\xc5\xa1\xfa\x9f)3\xc5\xc4\xa3?\xd0\xa5\xcb\xc5i\x8b\x80;\x9a\xa8\xf83\x00\xe0\x8d\xd9\\\x1b\x1e\x9a\x1f>\xcf\x99\x02\x03\x01\x00\x01\xa3\x82\x01\x900\x82\x01\x8c0\x0e\x06\x03U\x1d\x0f\x01\x01\xff\x04\x04\x03\x02\x07\x800L\x06\x03U\x1d \x04E0C0A\x06\t+\x06\x01\x04\x01\xa02\x01\x1f0402\x06\x08+\x06\x01\x05\x05\x07\x02\x01\x16&https://www.globalsign.com/repository/0\t\x06\x03U\x1d\x13\x04\x020\x000\x16\x06\x03U\x1d%\x01\x01\xff\x04\x0c0\n\x06\x08+\x06\x01\x05\x05\x07\x03\x080>\x06\x03U\x1d\x1f\x0470503\xa01\xa0/\x86-http://crl.globalsign.com/ca/gsaatlsha2g4.crl0\x81\x88\x06\x08+\x06\x01\x05\x05\x07\x01\x01\x04|0z0@\x06\x08+\x06\x01\x05\x05\x070\x02\x864http://secure.globalsign.com/cacert/gsaatlsha2g4.crt06\x06\x08+\x06\x01\x05\x05\x070\x01\x86*http://ocsp.globalsign.com/ca/gsaatlsha2g40\x1f\x06\x03U\x1d#\x04\x180\x16\x80\x14\x89\xefuqz_G\x1b\x97#\xdc\x90J\xcb\xff\xc0&6\x08\xd50\x1d\x06\x03U\x1d\x0e\x04\x16\x04\x14MG{=\x16q\x8a\x99\xe3\x12\xef\x8b\x06\xbe9\xb6\xbd\xd8N\xca0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0c\x05\x00\x03\x82\x02\x01\x006r\xac\x11\x160\x8fX|b!\xf7\x88\x82Wj9{o\xf84N-\xca\x87\xc8A\xdd\xac_\xc1\x94\xb8r\x82\xca\xf0\x16\nx\xe2\x9ch\xf4\x9d\xde\x9c\x04\x16\x01\xa8\xe8\xed{\x0b\x8c\xd2d\xcc\xa7\x98\\\xdaZ#\x83\x0b\xc0\x17i\x9b\x14\xaa\xd9\xd5@\x11y\x9fW\xf0\x1df\x01\xab_\x07q+\x8bx7\x1fS\xf9\xfd\xb2\x89\x83\xdf\xaeS\x8d\xde\x80\x93%\xc1tt\xbe\xd7`\xae`G\\1\xe3\xeeo\x9e\xff\xf0\x8e\xe2\xbaEn\xb3\xba\x8dw6H,\xea\x9a!\x15\x82\xc8.\x8e\xd4\xfc\xfb\xb6\xc4t\xa3\x87\xcd\x80\x85\xc1\xe3E\xf3\xddp)\x99\xd0\x99*.\x8fh]\x92:u\x14]0v\xa7\x98Z\xb1LD\x1d*^\xb0A\xb2\n\xfd\x96\xc0\xe4\xf4\xcdp\x81\x133*\x17\n\x92\xcf\x93f\x0e\xec\x86\xa5\xd8\x1c\x07`c\xa5\xa1\xa2\xee\xb4ge\x01\x86\xe8\x0e\xbd\x88d\x98w\x99\r\xe5u\xc2\x94<cC\x19\x14\xdaY\x8a\\g\xb75m\xb7\xe9\x8f\xfdh\x94\xf6\x82\x10p\xb6\xdc\x00o\x85)K\x0f\xc1XG\x02\xceY\x1dh4\x17\xeb\xecpd\xecV\xb7\xf0\xdb\xcc\xe1\x84AP\x1d\n\xf0\xcf{p\x82\x86\xd4\xc7\xec\xc6D\x17\\\xc6\x95\xa7\xbd}\xb2\x81\xc2\x9c(\xd3:u\xd8\xceY\xfd\xdc\xc9`\x7f\x15\xf2=\x93-)h\xc0\x90\xd3\xf9\x15~\xe4\xaaO\\\xe8\xc2R\xec3\x12\xd8\xca\xb7\x13\xc0\x14P\x14\xeby\x04\xb7\xe8\x06\xf7t"yB\x16{\xe3VL\xd4\x1fS\xbaTe)\xca\xf7\xbf\x08\n\x9e\x04g\xa8lJ\x02Y\x95L\xb8\x0b\x01\x83\x85v\xfc$N|~\x85hg$\x8d\xf5\xa9[<\xb9#\x8ex\xd9\xdc\xf5\xc7\xc9\xe6\xd4(\xd21\xf2\xae\x9f\xa1\x1c\x94\x96\xe7\x00\x04\x946\'\xa4\xea6\xd7\xd9[@\xe6#\xc1\xb2\x10]\xcd\t\xd6\xf5q\xd2\x9b\xffdJ\x13\x8d\xe0\xf8%\xfc \x9e\xa7dA-\xd0\xa0\x0bsU\x9c\xd7\x8bf\x03E\x05K\x848\xa6\xaa\xf4\xf0\x87t\x9dLV\xa3\xd9C-\xbe\x03\xcf61\x82\x02\xa00\x82\x02\x9c\x02\x01\x010g0W1\x0b0\t\x06\x03U\x04\x06\x13\x02BE1\x190\x17\x06\x03U\x04\n\x13\x10GlobalSign nv-sa1-0+\x06\x03U\x04\x03\x13$GlobalSign CA for AATL - SHA384 - G4\x02\x0c\x167\xae\x1d\xe7\xf0\xf1T\xc7\x90\xde\xa00\r\x06\t`\x86H\x01e\x03\x04\x02\x01\x05\x00\xa0\x82\x01\n0\x1c\x06\t*\x86H\x86\xf7\r\x01\t\x051\x0f\x17\r200403111050Z0\x1a\x06\t*\x86H\x86\xf7\r\x01\t\x031\r\x06\x0b*\x86H\x86\xf7\r\x01\t\x10\x01\x040\x81\x9c\x06\x0b*\x86H\x86\xf7\r\x01\t\x10\x02\x0c1\x81\x8c0\x81\x890\x81\x860\x81\x83\x04\x14\x17\xf84`\\=\xd4|\xbc\x83\x8b\x8a\xb3>\x84\xda\xf1\xf1\xc1\xb50k0[\xa4Y0W1\x0b0\t\x06\x03U\x04\x06\x13\x02BE1\x190\x17\x06\x03U\x04\n\x13\x10GlobalSign nv-sa1-0+\x06\x03U\x04\x03\x13$GlobalSign CA for AATL - SHA384 - G4\x02\x0c\x167\xae\x1d\xe7\xf0\xf1T\xc7\x90\xde\xa00/\x06\t*\x86H\x86\xf7\r\x01\t\x041"\x04 \x94\x97\x84`\x9c)p\x1f\x9b\xf0\x9c\xc6\xd6\xcf\xca\x9d\tBX\x8e.b\x8b\xee\x05\xeb\x8b\xcd\xb2\xf7\xf0?0\r\x06\t*\x86H\x86\xf7\r\x01\x01\x0b\x05\x00\x04\x82\x01\x00\x120-\xf2\x07U\x8e+\x94\xa4\x02\xec4\x06d\xc5\xd6\xc8tP\xe4\xa6\xdd<\xb8m}h\xd4\x15\xa2\xf1\xe5\x86o-\xdf]:\xdf27\xeb\xff\xd9\x9c\xa2\n\xe4+m\xa6\x85\xb7a~\xdf\xa9\xb3\x1fHs\xa8\x8c\x98\xad\xc1!\x920\nVnK\xbf?m4\x18\xff\x10$\x8a\x14H\t\x85\xb2\x08\xa0Y\xa6\xc3\xe3\xcf6\xf6\xf8\x96\x19T\xa1\xc5\xe53\xb8E\x88\x14\xb9?\xddJRz\xee\xed\xf2\xf9\xb3\xa3`H\xc3\xd9b\xe8\x05\xef\x05\xdc\xa4\x17h\x03\xb5\x95\xa6\xd3\x81\x9d=\xc6\xe66\xbd\x89M\xb49\xb0L\xb8\xbaZPZ\x90\x9e\xeb\nmk\xef\x0b\x8f\x90\xa4\xb5\xee4\nq_\xe8\xfb\xe4\xfe\xc6\xe6;\xf7eJ*\x04\x8f\xd2\x8f\xf1\xda\x96V\xd1\x9d#\xa3\xb3\xaa0\xe6\xe1]m:(\xe3\xc4\xbf\xf1\xd5\xe4\xad\x98\x88\x81*1mTo,S\xc8,Lu+\xce+p\xc8d\xf6\xf3\xa9\xc5\x17\xaf\xf4E\xdb\xe7G#iX\x02IpHA%\xb6\x13{'
hejsan commented 4 years ago

Does pdf.AdobeTimestamp have a solution maybe?

wbond commented 4 years ago

Looks like a signedData object inside of a CMS ContentInfo.

Try:

from asn1crypto import cms, tsp  # importing tsp adds relevant structures to cms

timestamp_token = base64.b64decode(timestamp_token)
resp = cms.ContentInfo.load(timestamp_token)
resp.native
hejsan commented 4 years ago
OrderedDict([('content_type', 'signed_data'), ('content', OrderedDict([('version', 'v3'), ('digest_algorithms', [OrderedDict([('algorithm', 'sha256'), ('parameters', None)])]), ('encap_content_info', Orde
redDict([('content_type', 'tst_info'), ('content', OrderedDict([('version', 'v1'), ('policy', '1.3.6.1.4.1.4146.1.31'), ('message_imprint', OrderedDict([('hash_algorithm', OrderedDict([('algorithm', 'sha2
56'), ('parameters', None)])), ('hashed_message', b'\xa3\xd1\xcb\xd2|\xf8\x1c1#\xd4\xc7\x1c\x8e51\x1f\xb2\x14b\xee\xfct\xd7\x9c\x18&\xae\xb2\xabB\xdb?')])), ('serial_number', 13135542045286184509407547912
963992356), ('gen_time', datetime.datetime(2020, 4, 3, 12, 14, 49, tzinfo=datetime.timezone.utc)), ('accuracy', OrderedDict([('seconds', 1), ('millis', None), ('micros', None)])), ('ordering', False), ('n
once', None), ('tsa', OrderedDict([('common_name', 'Globalsign TSA for AATL on DSS - SHA384 - G5'), ('organization_name', 'GMO GlobalSign Ltd'), ('country_name', 'GB')])), ('extensions', None)]))])), ('ce
rtificates', [OrderedDict([('tbs_certificate', OrderedDict([('version', 'v3'), ('serial_number', 6875983379585117135997820576), ('signature', OrderedDict([('algorithm', 'sha384_rsa'), ('parameters', None)
])), ('issuer', OrderedDict([('country_name', 'BE'), ('organization_name', 'GlobalSign nv-sa'), ('common_name', 'GlobalSign CA for AATL - SHA384 - G4')])), ('validity', OrderedDict([('not_before', datetim
e.datetime(2019, 10, 24, 0, 0, tzinfo=datetime.timezone.utc)), ('not_after', datetime.datetime(2031, 1, 21, 0, 0, tzinfo=datetime.timezone.utc))])), ('subject', OrderedDict([('common_name', 'Globalsign TS
A for AATL on DSS - SHA384 - G5'), ('organization_name', 'GMO GlobalSign Ltd'), ('country_name', 'GB')])), ('subject_public_key_info', OrderedDict([('algorithm', OrderedDict([('algorithm', 'rsa'), ('param
eters', None)])), ('public_key', OrderedDict([('modulus', 17218324515407407201512494811033403035174248665311860625630767085416783692205788940816841717160724751799774887271888023108501034377480777433522311
313480786937929209658484945118164275404578533799811011889484301698696835440768293580350136804239827328879470313280883965964761270681460864365662521470536858120087964617431771037668806621095274858526361985
131509386926435488782290975260939109078008906500882807290091233923844415234807503451004544330605516006833929612467093007931099210641299368465542631478927741090099679243706474326475341655174582218791675094
326683591373586355394603205420196435402750626273067984270315417), ('public_exponent', 65537)]))])), ('issuer_unique_id', None), ('subject_unique_id', None), ('extensions', [OrderedDict([('extn_id', 'key_u
sage'), ('critical', True), ('extn_value', {'digital_signature'})]), OrderedDict([('extn_id', 'certificate_policies'), ('critical', False), ('extn_value', [OrderedDict([('policy_identifier', '1.3.6.1.4.1.
4146.1.31'), ('policy_qualifiers', [OrderedDict([('policy_qualifier_id', 'certification_practice_statement'), ('qualifier', 'https://www.globalsign.com/repository/')])])])])]), OrderedDict([('extn_id', 'basic_constraints'), ('critical', False), ('extn_value', OrderedDict([('ca', False), ('path_len_constraint', None)]))]), OrderedDict([('extn_id', 'extended_key_usage'), ('critical', True), ('extn_value', ['time_stamping'])]), OrderedDict([('extn_id', 'crl_distribution_points'), ('critical', False), ('extn_value', [OrderedDict([('distribution_point', ['http://crl.globalsign.com/ca/gsaatlsha2g4.crl']), ('reasons', None), ('crl_issuer', None)])])]), OrderedDict([('extn_id', 'authority_information_access'), ('critical', False), ('extn_value', [OrderedDict([('access_method', 'ca_issuers'), ('access_location', 'http://secure.globalsign.com/cacert/gsaatlsha2g4.crt')]), OrderedDict([('access_method', 'ocsp'), ('access_location', 'http://ocsp.globalsign.com/ca/gsaatlsha2g4')])])]), OrderedDict([('extn_id', 'authority_key_identifier'), ('critical', False), ('extn_value', OrderedDict([('key_identifier', b'\x89\xefuqz_G\x1b\x97#\xdc\x90J\xcb\xff\xc0&6\x08\xd5'), ('authority_cert_issuer', None), ('authority_cert_serial_number', None)]))]), OrderedDict([('extn_id', 'key_identifier'), ('critical', False), ('extn_value', b'MG{=\x16q\x8a\x99\xe3\x12\xef\x8b\x06\xbe9\xb6\xbd\xd8N\xca')])])])), ('signature_algorithm', OrderedDict([('algorithm', 'sha384_rsa'), ('parameters', None)])), ('signature_value', b'6r\xac\x11\x160\x8fX|b!\xf7\x88\x82Wj9{o\xf84N-\xca\x87\xc8A\xdd\xac_\xc1\x94\xb8r\x82\xca\xf0\x16\nx\xe2\x9ch\xf4\x9d\xde\x9c\x04\x16\x01\xa8\xe8\xed{\x0b\x8c\xd2d\xcc\xa7\x98\\\xdaZ#\x83\x0b\xc0\x17i\x9b\x14\xaa\xd9\xd5@\x11y\x9fW\xf0\x1df\x01\xab_\x07q+\x8bx7\x1fS\xf9\xfd\xb2\x89\x83\xdf\xaeS\x8d\xde\x80\x93%\xc1tt\xbe\xd7`\xae`G\\1\xe3\xeeo\x9e\xff\xf0\x8e\xe2\xbaEn\xb3\xba\x8dw6H,\xea\x9a!\x15\x82\xc8.\x8e\xd4\xfc\xfb\xb6\xc4t\xa3\x87\xcd\x80\x85\xc1\xe3E\xf3\xddp)\x99\xd0\x99*.\x8fh]\x92:u\x14]0v\xa7\x98Z\xb1LD\x1d*^\xb0A\xb2\n\xfd\x96\xc0\xe4\xf4\xcdp\x81\x133*\x17\n\x92\xcf\x93f\x0e\xec\x86\xa5\xd8\x1c\x07`c\xa5\xa1\xa2\xee\xb4ge\x01\x86\xe8\x0e\xbd\x88d\x98w\x99\r\xe5u\xc2\x94<cC\x19\x14\xdaY\x8a\\g\xb75m\xb7\xe9\x8f\xfdh\x94\xf6\x82\x10p\xb6\xdc\x00o\x85)K\x0f\xc1XG\x02\xceY\x1dh4\x17\xeb\xecpd\xecV\xb7\xf0\xdb\xcc\xe1\x84AP\x1d\n\xf0\xcf{p\x82\x86\xd4\xc7\xec\xc6D\x17\\\xc6\x95\xa7\xbd}\xb2\x81\xc2\x9c(\xd3:u\xd8\xceY\xfd\xdc\xc9`\x7f\x15\xf2=\x93-)h\xc0\x90\xd3\xf9\x15~\xe4\xaaO\\\xe8\xc2R\xec3\x12\xd8\xca\xb7\x13\xc0\x14P\x14\xeby\x04\xb7\xe8\x06\xf7t"yB\x16{\xe3VL\xd4\x1fS\xbaTe)\xca\xf7\xbf\x08\n\x9e\x04g\xa8lJ\x02Y\x95L\xb8\x0b\x01\x83\x85v\xfc$N|~\x85hg$\x8d\xf5\xa9[<\xb9#\x8ex\xd9\xdc\xf5\xc7\xc9\xe6\xd4(\xd21\xf2\xae\x9f\xa1\x1c\x94\x96\xe7\x00\x04\x946\'\xa4\xea6\xd7\xd9[@\xe6#\xc1\xb2\x10]\xcd\t\xd6\xf5q\xd2\x9b\xffdJ\x13\x8d\xe0\xf8%\xfc \x9e\xa7dA-\xd0\xa0\x0bsU\x9c\xd7\x8bf\x03E\x05K\x848\xa6\xaa\xf4\xf0\x87t\x9dLV\xa3\xd9C-\xbe\x03\xcf6')])]), ('crls', None), ('signer_infos', [OrderedDict([('version', 'v1'), ('sid', OrderedDict([('issuer', OrderedDict([('country_name', 'BE'), ('organization_name', 'GlobalSign nv-sa'), ('common_name', 'GlobalSign CA for AATL - SHA384 - G4')])), ('serial_number', 6875983379585117135997820576)])), ('digest_algorithm', OrderedDict([('algorithm', 'sha256'), ('parameters', None)])), ('signed_attrs', [OrderedDict([('type', 'signing_time'), ('values', [datetime.datetime(2020, 4, 3, 12, 14, 49, tzinfo=datetime.timezone.utc)])]), OrderedDict([('type', 'content_type'), ('values', ['tst_info'])]), OrderedDict([('type', 'signing_certificate'), ('values', [OrderedDict([('certs', [OrderedDict([('cert_hash', b'\x17\xf84`\\=\xd4|\xbc\x83\x8b\x8a\xb3>\x84\xda\xf1\xf1\xc1\xb5'), ('issuer_serial', OrderedDict([('issuer', [OrderedDict([('country_name', 'BE'), ('organization_name', 'GlobalSign nv-sa'), ('common_name', 'GlobalSign CA for AATL - SHA384 - G4')])]), ('serial_number', 6875983379585117135997820576)]))])]), ('policies', None)])])]), OrderedDict([('type', 'message_digest'), ('va
lues', [b'\x16\xdfH\xca\x1aS\nh:\x8f\x84\x8c\xe9\x87\xd72\x13\xd5rx\x1e\xde\x90{\xb20r\xb6G\x18\x0eC'])])]), ('signature_algorithm', OrderedDict([('algorithm', 'sha256_rsa'), ('parameters', None)])), ('si
gnature', b")9\x04\xb0\xee\x00tc<\x01^\x8fj\xb1(\xd9\x11\xf3\x9e\xdb\xecJ\xef\x186\x895\xcfJ\xdbO|\xc8\x14ncR\x1b\xee^\x84\xa5\xa7\xc0\xf0G_\xe5 \xbe\x03\x009W\xc54e\x92>\x1c\xc9\x80\x17\x8a\x02,\x1f\xf1\
x8a\xe1\x0em]\xd8\x83\r\xd4\xbc\x99ik\x90\xbeP\xd8k\xd7\x10\x80\x8b\n.\xfe\x93g\rPjZ$\xfc>\xb2\x9b\xfcr\r\xb1+FXj: \x87\xdc\xfc\xac\x02\xe6m\xdbj)\xe2\xb0G\xac\xa0\xfb8\xbf\xe9/\x19\x00\xe2c\xc7!\xdb\xa6b
\xd5%\xb6f}\xdc|\xe1\xb7\x0ew~gg<\x96S\xb6\xfa]\x01\xed5F\xf4A,\xc1\xcf9\xea\x02\x05\x1d\xcc\xbc\xcf\xcfWA\x146\xb6\r$\xabL\\m\x80\xb1\x7f7\xdc\xa8\xae\x07\xcc\x14'Y\x9f\xdc\x06\x01\xa7\x0f\xbaDp\xe5a\xcb
\xd4r\xb3\x1fS\xc4\xe1{\xdc!9\xa6\\a4\x10\x8f\xd0<\xaa\xba\x87\x85\x06:\xac\xaf\x07l\x8c\xf2\x98[\x87\xde\xe2\xfd%\xe1\xe1"), ('unsigned_attrs', None)])])]))])
wbond commented 4 years ago

That looks like it!

hejsan commented 4 years ago

Ok, great that's awesome, thanks so much for taking the time, I was at my wits end :) I actually got the massive project of enabling all my Universities documents to be served digitally signed asap due to the coronavirus, to facilitate working from home. I've never had to learn such a massive amount of complex stuff in such a short time, just to let you know how appreciated your help is ๐Ÿ‘ I'm going to ask to make my project open sourced also asap. Only thing I haven't figured out is how to enable LTV - Long Term Validation.

For possible future readers I added the timestamp to my datas dictionary like this:

tsp_attrs = [
    cms.CMSAttribute({
        'type': cms.CMSAttributeType('signature_time_stamp_token'),
        'values': cms.SetOfContentInfo([
            cms.ContentInfo({
                'content_type': cms.ContentType('signed_data'),
                'content': tsp_dict['content'],
            })
        ])
    })
]
datas['content']['signer_infos'][0]['unsigned_attrs'] = tsp_attrs
wbond commented 4 years ago

LTV is much more complex. You'll have to look into the PDF spec for that. I started the process of writing asn1crypto originally to solve LTV certificate signing. I've never had the time to finish that project.

It involves storing full certificate chains for the signature, along with cached revocation info. certvalidator includes some features like fetching OCSP and CRL info, which I believe will have to be cached and timestamped and then added to the PDF.

hejsan commented 4 years ago

Yes I've seen a lot of questions but few answers. Fortunately Globalsigns API recently made it a bit easier because you can request the full trust chain and their respective ocsp responses as well as the ocsp response for the autogenerated signing certificate.

I just can't quite grasp yet how and where I make the dss section. Just fyi this is how I'm adding the ocsp stuff to the datas dict:

for rev in self._revocation_info:
    rev = base64.b64decode(rev)
    rev = ocsp.OCSPResponse.load(rev)
    ocsp_revocation.append(
        cms.RevocationInfoChoice({
            'other': cms.OtherRevocationInfoFormat({
                'other_rev_info_format': cms.OtherRevInfoFormatId('ocsp_response'),
                'other_rev_info': rev
            })
        })
    )

config = {
    'version': 'v1',
    'digest_algorithms': cms.DigestAlgorithms((
        algos.DigestAlgorithm({'algorithm': hashalgo}),
    )),
    'encap_content_info': {
        'content_type': 'data',
    },
    'certificates': certificates,
    'crls': ocsp_revocation,
    'signer_infos': [
        signer,
    ],
}
datas = cms.ContentInfo({
    'content_type': cms.ContentType('signed_data'),
    'content': cms.SignedData(config),
})

But then if I read the spec correctly I have to also add some new dictionaries to the PDF to actually enable the LTV - which I thought would mess up the digest ๐Ÿ˜•

wbond commented 4 years ago

The digest is only supposed to be on part of the PDF - you have to provide a range of bytes that includes everything but the signature and the trailer, if I recall correctly.

You are correct that there are lots of questions and not answers. I got to around 95% of the way there, but putting the final bits together I've just never had time to fully work out. Looking at some existing LTV PDFs may be helpful, assuming you have a parser that can break down the portions of the PDF for you so you can understand the structure.

Unfortunately I don't have time to work on this any time soon (such is life), and the work I've done so far is commercial in nature, not open source.

Good luck!

hejsan commented 4 years ago

Thanks! Do you have any recommendations for a nice pdf parser?

wbond commented 4 years ago

Unfortunately, no. ๐Ÿ˜• I ended writing my own because I couldn't find one - kind of like asn1crypto.

hejsan commented 4 years ago

ok, I see, so the one you wrote is not available?

wbond commented 4 years ago

Correct. It is part of the commercial project I am working on related to PDF signatures.

hejsan commented 4 years ago

Hi again, I thought you might appreciate knowing that I figured out the LTV part and am now able to properly Digitally sign pdf's. My first very rough edition is here: https://github.com/hejsan/WeasySign

I dug up this here treasure of a document that actually explains DSS, Timestamps and LTV in quite a concise read. https://www.etsi.org/deliver/etsi_ts/102700_102799/10277804/01.01.02_60/ts_10277804v010102p.pdf

Oh, and I found this utility collection that helped me debug PDF syntax errors: https://pdfbox.apache.org/ - It kinda sucks - but it worked and saved my sanity.