pax_global_header00006660000000000000000000000064137470225060014520gustar00rootroot0000000000000052 comment=e56ea22c93a0f4809374d9c216801b16c441d0c4 rfc3161ng-2.1.3/000077500000000000000000000000001374702250600131355ustar00rootroot00000000000000rfc3161ng-2.1.3/.github/000077500000000000000000000000001374702250600144755ustar00rootroot00000000000000rfc3161ng-2.1.3/.github/FUNDING.yml000066400000000000000000000000171374702250600163100ustar00rootroot00000000000000github: [trbs] rfc3161ng-2.1.3/.github/workflows/000077500000000000000000000000001374702250600165325ustar00rootroot00000000000000rfc3161ng-2.1.3/.github/workflows/main.yml000066400000000000000000000023531374702250600202040ustar00rootroot00000000000000name: CI on: push: branches: [ master ] pull_request: branches: [ master ] jobs: tox: name: "Test" runs-on: ubuntu-latest strategy: matrix: python-version: [3.8] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install dependencies run: | python -m pip install --upgrade pip setuptools wheel pip install tox tox-gh-actions pip install -e . - name: Test with tox run: | python -m tox package: name: "Build & verify package" runs-on: "ubuntu-latest" steps: - uses: "actions/checkout@v2" - uses: "actions/setup-python@v2" with: python-version: "3.8" - name: Install dependencies run: | python -m pip install --upgrade pip setuptools wheel - name: "Build package" run: "python ./setup.py sdist bdist_wheel" - name: "Show build artifacts" run: "ls -alR dist" - name: "Install twine" run: "python -m pip install twine" - name: "Twine check" run: "python -m twine check dist/*" rfc3161ng-2.1.3/.gitignore000066400000000000000000000022441374702250600151270ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] *$py.class # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ *.egg-info/ .installed.cfg *.egg # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover .hypothesis/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder target/ # Jupyter Notebook .ipynb_checkpoints # pyenv .python-version # celery beat schedule file celerybeat-schedule # SageMath parsed files *.sage.py # dotenv .env # virtualenv .venv venv/ venv3/ ENV/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ # pytest .pytest_cache rfc3161ng-2.1.3/CHANGELOG000066400000000000000000000024201374702250600143450ustar00rootroot00000000000000Changelog ========= 2.1.3 ----- - fix: update directoryName to be of type Name (#19) 2.1.2 ----- - Support custom TSAPolicyId - Allow for fetching a TSR without validation 2.1.1 ----- - Use seconds as argument for tzoffset() for python 2 compatibility - Added two more timestamping services in README 2.1.0 ----- - Fix for #11 Some versions of prettyPrint() include quotes, others do not - Add test verify timestamp response with openssl - Add test of freetsa.org - Add return_tsr option to RemoteTimerstamper object - Added License File - Updated README - ASN.1 generalizedTime to Python datetime parser 2.0.4 ----- - Add utility functions; make_timestamp_request, encode_timestamp_request, encode_timestamp_response, decode_timestamp_request, decode_timestamp_response 2.0.3 ----- - Use [bdist_wheel] universal = 1 2.0.2 ----- - Update example in README.rst 2.0.1 ----- - Python 3 support - Fix certum_certificate.crt - Fix style - Fix bug(s) with handling requests library and errors from requests - Improved API by using exceptions instead of returning error codes - Switched to cryptography instead of M2Crypto 2.0.0 ----- - Fork rfc3161 1.0.7 ----- - use dateutil to parse genTime - fix bad instantiation of UTF8String in PKIFreeText - use tox and py.test rfc3161ng-2.1.3/LICENSE000066400000000000000000000024071374702250600141450ustar00rootroot00000000000000MIT License Copyright (c) 2018 authors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. Except as contained in this notice, the name(s) of the above copyright holders shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization. rfc3161ng-2.1.3/MANIFEST.in000066400000000000000000000001621374702250600146720ustar00rootroot00000000000000include tests/*.py include stdeb.cfg include tox.ini recursive-include data *.crt recursive-include spec *.asn88 rfc3161ng-2.1.3/PKG-INFO000066400000000000000000000024001374702250600142260ustar00rootroot00000000000000Metadata-Version: 1.0 Name: rfc3161 Version: 1.0.7 Summary: Python implementation of the RFC3161 specification, using pyasn1 Home-page: https://dev.entrouvert.org/projects/python-rfc3161 Author: Benjamin Dauvergne Author-email: bdauvergne@entrouvert.com License: MIT Description: rfc3161 ======= A simple client library for cryptographic timestamping service implementing the protocol from RFC3161. >>> import rfc3161 >>> certificate = file('data/certum_certificate.crt').read() >>> rt = rfc3161.RemoteTimestamper('http://time.certum.pl', certificate=certificate) >>> rt.timestamp(data='John Doe') ('...', '') >>> rt.check(_, data='John Doe') (True, '') >>> rfc3161.get_timestamp(tst) datetime.datetime(2014, 4, 25, 9, 34, 16) Authors ======= Benjamin Dauvergne Michael Gebetsroither Changelog ========= 1.0.7 ----- - use dateutil to parse genTime - fix bad instantiation of UTF8String in PKIFreeText - use tox and py.test Platform: UNKNOWN rfc3161ng-2.1.3/README.rst000066400000000000000000000057141374702250600146330ustar00rootroot00000000000000========= rfc3161ng ========= .. image:: https://img.shields.io/pypi/l/rfc3161ng.svg :target: https://raw.githubusercontent.com/trbs/rfc3161ng/master/LICENSE .. image:: https://github.com/trbs/rfc3161ng/workflows/CI/badge.svg?branch=master :target: https://github.com/trbs/rfc3161ng/actions?workflow=CI :alt: CI Status .. image:: https://img.shields.io/pypi/v/rfc3161ng.svg :target: https://pypi.python.org/pypi/rfc3161ng/ :alt: Latest PyPI version .. image:: https://img.shields.io/pypi/wheel/rfc3161ng.svg :target: https://pypi.python.org/pypi/rfc3161ng/ :alt: Supports Wheel format A simple client library for cryptographic timestamping service implementing the protocol from RFC3161. This started as a fork of https://dev.entrouvert.org/projects/python-rfc3161 and has some additional patches such as Python3 support. The latest version of this library is available from https://github.com/trbs/rfc3161ng/ . Public providers ================ There are several timestamping services around. Here is a list of publicly available services you can try: * http://freetsa.org/tsr * http://time.certum.pl * http://timestamp.comodoca.com/rfc3161 * http://timestamp.geotrust.com/tsa * http://timestamp.globalsign.com/scripts/timstamp.dll * http://tsa.starfieldtech.com * https://teszt.e-szigno.hu:440/tsa Example ======= >>> import rfc3161ng >>> certificate = open('data/certum_certificate.crt', 'rb').read() >>> rt = rfc3161ng.RemoteTimestamper('http://time.certum.pl', certificate=certificate) >>> tst = rt.timestamp(data=b'John Doe') >>> rt.check(tst, data=b'John Doe') True >>> rfc3161ng.get_timestamp(tst) datetime.datetime(2017, 8, 31, 15, 42, 58, tzinfo=tzutc()) Example for a server that insist on SHA256: >> import rfc3161ng >> timestamper = rfc3161ng.RemoteTimestamper('https://interop.redwax.eu/test/timestamp', hashname='sha256') >> tsr = timestamper(data=b'The RedWax Project', return_tsr=True) >> print('{}'.format(tsr)) Verifying timestamp using OpenSSL ================================= One can verify the timestamp returned by the timeserver by using OpenSSL. For example with: $ openssl ts -verify -data data_file.txt -in data_file.tsr -CAfile cacert.pem -untrusted tsa.crt To save the tsr you can use code similar to: >>> from pyasn1.codec.der import encoder >>> import rfc3161ng >>> ... >>> timestamper = rfc3161ng.RemoteTimestamper('http://freetsa.org/tsr', certificate=certificate_data) >>> tsr = timestamper(data=data_file.read(), return_tsr=True) >>> with open("data_file.tsr", "wb") as f: >>> f.write(encoder.encode(tsr)) Alternatively you can just save the raw `response.content` returned from the certification server. There is a test which also covers this in `test_verify_timestamp_response_with_openssl`. Authors ======= * Benjamin Dauvergne * Michael Gebetsroither * Bas van Oostveen rfc3161ng-2.1.3/creating_release.rst000066400000000000000000000015021374702250600171610ustar00rootroot00000000000000================== Creating a release ================== :synopsis: Creating a rfc3161ng release How to make a new release ------------------------- Run tests:: $ tox -r Change version numbers in `setup.py` and `rfc3161ng/__init__.py`:: $ vi setup.py $ vi rfc3161ng/__init__.py $ git commit -m 'v2.0.0' setup.py rfc3161ng/__init__.py Tag it: $ git tag 2.0.0 Remove old build directory (if exists):: $ rm -r build dist Prepare the release tarball:: $ python ./setup.py sdist bdist_wheel Upload release to pypi:: $ twine upload -s dist/* Bumb version number to new in-development pre version:: $ vi setup.py $ vi rfc3161ng/__init__.py $ git commit -m 'bumped version number' setup.py rfc3161ng/__init__.py Push changes back to github:: $ git push --tags $ git push rfc3161ng-2.1.3/data/000077500000000000000000000000001374702250600140465ustar00rootroot00000000000000rfc3161ng-2.1.3/data/certum_certificate.crt000066400000000000000000000125401374702250600204230ustar00rootroot00000000000000Certificate: Data: Version: 3 (0x2) Serial Number: fe:67:e4:f1:5a:24:e3:c6:0d:54:7c:a0:20:c2:76:70 Signature Algorithm: sha256WithRSAEncryption Issuer: C=PL, O=Unizeto Technologies S.A., OU=Certum Certification Authority, CN=Certum Trusted Network CA Validity Not Before: Mar 8 13:10:43 2016 GMT Not After : May 30 13:10:43 2027 GMT Subject: C=PL, O=Unizeto Technologies S.A., OU=Certum Certification Authority, CN=Certum EV TSA SHA2 Subject Public Key Info: Public Key Algorithm: rsaEncryption RSA Public Key: (2048 bit) Modulus (2048 bit): 00:bf:57:8b:bc:91:33:5a:2e:57:31:7f:25:57:f4: 5b:53:58:6a:25:aa:e8:81:20:cc:0d:1d:47:38:70: 1a:a3:16:37:9d:b7:44:74:b5:1a:81:24:fa:f5:18: 4a:38:4b:0b:a0:ba:c5:bf:34:6f:ea:8c:f5:9d:42: 1a:d0:31:a8:56:dd:a8:25:0e:b4:3c:25:c5:ac:c6: cb:5c:e3:80:92:ea:a3:af:2d:00:40:40:7c:d2:41: 28:1f:2b:08:e8:51:d0:5d:89:70:22:6c:69:76:d0: 76:12:3c:2e:4b:07:29:26:e9:f8:01:e1:d0:e5:28: f6:24:c3:15:fa:a6:90:84:74:85:5c:83:2c:0e:c4: da:78:49:94:6a:1d:24:86:97:e9:22:c0:c6:0c:35: e0:74:32:aa:3d:bb:01:d8:7e:cc:13:4c:20:c7:95: 2d:49:f6:f1:2d:17:bc:c6:29:f7:f8:55:76:9c:7d: 15:dc:de:e1:42:95:93:60:99:f7:43:c5:94:42:21: bd:98:ac:1c:b3:66:dc:fd:78:60:44:3f:3d:c4:95: 69:67:ee:61:cb:da:d0:b9:e6:76:f7:a1:3f:04:f4: d3:e7:71:af:21:02:5e:11:da:53:a4:a6:b5:91:78: d9:90:11:5b:b4:a1:ee:a7:6e:0a:d9:73:a4:38:05: 52:23 Exponent: 65537 (0x10001) X509v3 extensions: X509v3 Basic Constraints: critical CA:FALSE X509v3 Subject Key Identifier: F3:35:CA:8E:46:08:0D:39:8D:DF:41:C7:33:6E:64:E4:16:3B:98:2B X509v3 Authority Key Identifier: keyid:08:76:CD:CB:07:FF:24:F6:C5:CD:ED:BB:90:BC:E2:84:37:46:75:F7 X509v3 Key Usage: critical Digital Signature X509v3 Extended Key Usage: critical Time Stamping X509v3 CRL Distribution Points: URI:http://crl.certum.pl/ctnca.crl Authority Information Access: OCSP - URI:http://subca.ocsp-certum.com CA Issuers - URI:http://repository.certum.pl/ctnca.cer X509v3 Certificate Policies: Policy: 1.2.616.1.113527.2.5.1.11 CPS: http://www.certum.pl/CPS Signature Algorithm: sha256WithRSAEncryption ca:74:e4:38:bd:32:c9:1a:3c:fa:2b:58:c5:57:2d:35:53:0b: 90:de:2d:05:b2:f1:33:32:40:1b:96:ff:22:45:87:39:ae:0c: f0:19:ce:c1:d0:32:44:1a:33:0c:80:eb:3d:33:2b:7c:79:34: 4e:c6:81:44:34:58:56:ba:39:0d:f0:e4:b3:03:ac:37:75:c0: 7a:eb:b7:40:f6:9a:fc:7e:80:6d:a9:b4:55:89:3d:98:48:ca: 56:f4:55:a4:2e:e9:e1:d3:7e:b5:38:91:95:c4:cf:bb:a6:1e: 75:6b:56:50:9b:6e:a7:eb:d1:9c:e1:71:20:d5:d5:a6:58:ab: e1:e5:29:5d:81:f1:04:b5:e4:1b:64:42:a1:38:71:3d:7b:73: 64:c6:69:d4:22:30:96:b0:24:c3:02:5b:11:a8:3c:34:62:7b: 67:67:e1:46:86:57:7c:e8:8a:9f:90:bb:38:5b:d9:b5:89:ea: 03:28:db:8d:b7:5b:07:6d:32:bb:87:7f:dc:26:ce:2e:5e:e8: d6:ab:db:e6:a6:d0:e2:19:02:12:f9:a0:db:3e:9d:52:c4:4c: bd:57:85:ed:78:ed:c1:96:44:cd:46:b0:ce:65:d5:57:72:3a: 24:c6:10:e1:b0:73:c4:8f:5a:83:ac:f6:c6:72:94:f9:72:77: ff:01:d5:21 -----BEGIN CERTIFICATE----- MIIE3DCCA8SgAwIBAgIRAP5n5PFaJOPGDVR8oCDCdnAwDQYJKoZIhvcNAQELBQAw fjELMAkGA1UEBhMCUEwxIjAgBgNVBAoTGVVuaXpldG8gVGVjaG5vbG9naWVzIFMu QS4xJzAlBgNVBAsTHkNlcnR1bSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTEiMCAG A1UEAxMZQ2VydHVtIFRydXN0ZWQgTmV0d29yayBDQTAeFw0xNjAzMDgxMzEwNDNa Fw0yNzA1MzAxMzEwNDNaMHcxCzAJBgNVBAYTAlBMMSIwIAYDVQQKDBlVbml6ZXRv IFRlY2hub2xvZ2llcyBTLkEuMScwJQYDVQQLDB5DZXJ0dW0gQ2VydGlmaWNhdGlv biBBdXRob3JpdHkxGzAZBgNVBAMMEkNlcnR1bSBFViBUU0EgU0hBMjCCASIwDQYJ KoZIhvcNAQEBBQADggEPADCCAQoCggEBAL9Xi7yRM1ouVzF/JVf0W1NYaiWq6IEg zA0dRzhwGqMWN523RHS1GoEk+vUYSjhLC6C6xb80b+qM9Z1CGtAxqFbdqCUOtDwl xazGy1zjgJLqo68tAEBAfNJBKB8rCOhR0F2JcCJsaXbQdhI8LksHKSbp+AHh0OUo 9iTDFfqmkIR0hVyDLA7E2nhJlGodJIaX6SLAxgw14HQyqj27Adh+zBNMIMeVLUn2 8S0XvMYp9/hVdpx9Fdze4UKVk2CZ90PFlEIhvZisHLNm3P14YEQ/PcSVaWfuYcva 0LnmdvehPwT00+dxryECXhHaU6SmtZF42ZARW7Sh7qduCtlzpDgFUiMCAwEAAaOC AVowggFWMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPM1yo5GCA05jd9BxzNuZOQW O5grMB8GA1UdIwQYMBaAFAh2zcsH/yT2xc3tu5C84oQ3RnX3MA4GA1UdDwEB/wQE AwIHgDAWBgNVHSUBAf8EDDAKBggrBgEFBQcDCDAvBgNVHR8EKDAmMCSgIqAghh5o dHRwOi8vY3JsLmNlcnR1bS5wbC9jdG5jYS5jcmwwawYIKwYBBQUHAQEEXzBdMCgG CCsGAQUFBzABhhxodHRwOi8vc3ViY2Eub2NzcC1jZXJ0dW0uY29tMDEGCCsGAQUF BzAChiVodHRwOi8vcmVwb3NpdG9yeS5jZXJ0dW0ucGwvY3RuY2EuY2VyMEAGA1Ud IAQ5MDcwNQYLKoRoAYb2dwIFAQswJjAkBggrBgEFBQcCARYYaHR0cDovL3d3dy5j ZXJ0dW0ucGwvQ1BTMA0GCSqGSIb3DQEBCwUAA4IBAQDKdOQ4vTLJGjz6K1jFVy01 UwuQ3i0FsvEzMkAblv8iRYc5rgzwGc7B0DJEGjMMgOs9Myt8eTROxoFENFhWujkN 8OSzA6w3dcB667dA9pr8foBtqbRViT2YSMpW9FWkLunh0361OJGVxM+7ph51a1ZQ m26n69Gc4XEg1dWmWKvh5SldgfEEteQbZEKhOHE9e3NkxmnUIjCWsCTDAlsRqDw0 YntnZ+FGhld86IqfkLs4W9m1ieoDKNuNt1sHbTK7h3/cJs4uXujWq9vmptDiGQIS +aDbPp1SxEy9V4XteO3BlkTNRrDOZdVXcjokxhDhsHPEj1qDrPbGcpT5cnf/AdUh -----END CERTIFICATE----- rfc3161ng-2.1.3/data/e_szigno_test_tsa2.crt000066400000000000000000000033521374702250600203700ustar00rootroot0000000000000000Π0  *H  0|1 0 UHU10UBudapest10U  Microsec Ltd.10U  e-Szigno CA1,0*U#Microsec e-Szigno Test Root CA 20080 080926074336Z 180926074336Z0k1 0 UHU10U Budapest10U Microsec Ltd.10U e-Szigno CA10U e-Szigno Test TSA20"0  *H 0 rFTbM V!qʃ>nKWH}.)v0$l?c:#0J{2nTv_Ulb\ 5v+{lEz)PXX 8h^O֎ ƟZE^WuYFH.oW{IK({»Eh HBOE@yV&;ymAsFQAB_jc8$sQ H҂GuqT0~0 U00U0U% 0 +0U 00 + 00'+http://www.e-szigno.hu/THR/0X+0JFTesztelsi clra kiadott TESZT tanstvny. A hasznlatval kapcsolatosan felmerlQ krokrt az e-Szign Hitelests Szolgltat semmilyen felelQssget nem vllal!0U羰KM]beW Hp0U#0/қsPRÿځj90U00.,*(http://teszt.e-szigno.hu/TRootCA2008.crl0ldap://srv.e-szigno.hu/CN=Microsec%20e-Szigno%20Test%20Root%20CA%202008,OU=e-Szigno%20CA,O=Microsec%20Ltd.,L=Budapest,C=HU?certificateRevocationList;binary0u+i0g0/+0#https://teszt.e-szigno.hu/testocsp204+0(http://teszt.e-szigno.hu/TRootCA2008.crt0  *H   x޿롦A(=,E,»_]u}6Q%Xr0B" E獜ԤFG̈'^<:b+vB`byx:( f)I6FLoWGgtnQ &N~-, ȼ hsͳ00U0U% 0 +0CU <0:08`8 0.0,+ http://repository.pki.belgium.be0Uv"WCVCC}07U00.0,*(&http://crl.pki.belgium.be/belgium2.crl0 U00U#0ŻYր90  *H "GV@ w#{ -5fg#SVT`/g몄vt#r/^4, b tߢͺlbz~F=UKyaDUm\q`lFypk{E24c9KUkb:Z t Czz9⁩kX"E{C[~d hC(W˜1 bVvv|#ߴ$Grfc3161ng-2.1.3/data/freetsa.crt000066400000000000000000000054251374702250600162170ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIIATCCBemgAwIBAgIJAMHphhYNqOmCMA0GCSqGSIb3DQEBDQUAMIGVMREwDwYD VQQKEwhGcmVlIFRTQTEQMA4GA1UECxMHUm9vdCBDQTEYMBYGA1UEAxMPd3d3LmZy ZWV0c2Eub3JnMSIwIAYJKoZIhvcNAQkBFhNidXNpbGV6YXNAZ21haWwuY29tMRIw EAYDVQQHEwlXdWVyemJ1cmcxDzANBgNVBAgTBkJheWVybjELMAkGA1UEBhMCREUw HhcNMTYwMzEzMDE1NzM5WhcNMjYwMzExMDE1NzM5WjCCAQkxETAPBgNVBAoTCEZy ZWUgVFNBMQwwCgYDVQQLEwNUU0ExdjB0BgNVBA0TbVRoaXMgY2VydGlmaWNhdGUg ZGlnaXRhbGx5IHNpZ25zIGRvY3VtZW50cyBhbmQgdGltZSBzdGFtcCByZXF1ZXN0 cyBtYWRlIHVzaW5nIHRoZSBmcmVldHNhLm9yZyBvbmxpbmUgc2VydmljZXMxGDAW BgNVBAMTD3d3dy5mcmVldHNhLm9yZzEiMCAGCSqGSIb3DQEJARYTYnVzaWxlemFz QGdtYWlsLmNvbTESMBAGA1UEBxMJV3VlcnpidXJnMQswCQYDVQQGEwJERTEPMA0G A1UECBMGQmF5ZXJuMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtZEE jE5IbzTp3Ahif8I3UWIjaYS4LLEwvv9RfPw4+EvOXGWodNqyYhrgvOfjNWPg7ek0 /V+IIxWfB4SICCJ0YMHtiCYXBvQoEzQ1nfu4G9E1P8F5YQrxqMjIZdwA6iOzqJvm vQO6hansgn1gVlkF4i1qWE7ROArhUCgM7jl+mKAS84BGQAeGJEO8B3y5X0Ia8xcS 2Wg8223/uvPIululZq5SPUWdYXc0bU2EDieIa3wBxbiQ14ouJ7uo3S+aKBLhV9Yv khxlliVIBp3Nt9Bt4YHeDpVw1m+HIgzii2KKtVkG8+4MIQ9wUej0hYr4uaktCeRq 8tnLpb/PrRaM32BEkaSwZgOxFMr3Ax8GXn7u+lPFdfNJDAWdLjLdx2rE1MTHEGg7 l/0b5ZG8YQVRhtiPmgORswe2+R7ZVNqjb5rNah4Uqi5K3xdGS1TbGNu2/+MAgCRl RzcENs5Od7rl3m/g8/nW5/++tGHnlOkvsJUfiq5hpBLM6bIQdGNci+MnrhoPa0pk brD4RjvGO/hFUwQ10Z6AJRHsn2bDSWlS2L7LabCqTUxB9gUV/n3LuJMZzdpZumrq S+POrnGOb8tszX25/FC7FbEvNmWwqjByicLm3UsRHOSLotnv21prmlBgaTNPs09v x64zDws0IIqsgN8yZv3ZBGWHa6LLiY2VBTFbbnsCAwEAAaOCAdswggHXMAkGA1Ud EwQCMAAwHQYDVR0OBBYEFG52C3tOT5zhYMptLOknoqKUs3c3MB8GA1UdIwQYMBaA FPpVDYw0ZlFDTPfns6dsla965qSXMAsGA1UdDwQEAwIGwDAWBgNVHSUBAf8EDDAK BggrBgEFBQcDCDBjBggrBgEFBQcBAQRXMFUwKgYIKwYBBQUHMAKGHmh0dHA6Ly93 d3cuZnJlZXRzYS5vcmcvdHNhLmNydDAnBggrBgEFBQcwAYYbaHR0cDovL3d3dy5m cmVldHNhLm9yZzoyNTYwMDcGA1UdHwQwMC4wLKAqoCiGJmh0dHA6Ly93d3cuZnJl ZXRzYS5vcmcvY3JsL3Jvb3RfY2EuY3JsMIHGBgNVHSAEgb4wgbswgbgGAQAwgbIw MwYIKwYBBQUHAgEWJ2h0dHA6Ly93d3cuZnJlZXRzYS5vcmcvZnJlZXRzYV9jcHMu aHRtbDAyBggrBgEFBQcCARYmaHR0cDovL3d3dy5mcmVldHNhLm9yZy9mcmVldHNh X2Nwcy5wZGYwRwYIKwYBBQUHAgIwOxo5RnJlZVRTQSB0cnVzdGVkIHRpbWVzdGFt cGluZyBTb2Z0d2FyZSBhcyBhIFNlcnZpY2UgKFNhYVMpMA0GCSqGSIb3DQEBDQUA A4ICAQClyUTixvrAoU2TCn/QoLFytB/BSDw+lXxoorzZuXZPGpUBYf1yRy1Bpe7S d3hiA7VCIkD7OibN4XYIe2+xAR30zBniVxqkoFEQlmXpTEb1C9Kt7mrEE34lGyWj navaRRUV2P+eByCejsILeHT34aDt58AJN/6EozT4syZc7S2O2d9hOWWDZ3/rOCwe 47I+bqXwXfMN57n4kAXSUmb2EvOci09tq6bXv7rBljK5Bjcyn1Km8GahDkPqqB+E mmxf4/6LXqIydfaH8gUuUC6mwwdipmjM4Hhx3Y6X4xW7qSniVYmXegoxLOlsUQax Q3x3nys2GxgoiPPuiiNDdPoGPpVhkmJ/fEMQc5ZdEmCSjroAnoA0Ka4yTPlvBCNU 83vKWv3cefeTRqs4i/x58B3JhhJU6mzBKZQQdrg9IFVvO+UTJoN/KHb3gzs3Dnw9 QQUjgn1PU0AMciGNdSKf8QxviJOpo6HAxCu0yJjBPfQcf2VztPxWUVlxphCnsNKF fIIlqfsgTqzsouiXGqGvh4hqKuPHL+CgquhCmAp3vvFrkhFUWAkNmCtZRmA3ZOda CtPRFFS5mG9ni5q2r+hJcDOuOr/U60O3vJ3uaIFZSeZIFYKoLnhSd/IoIQfv45Ag DgUIrLjqguolBSdvPJ2io9O0rTi7+IQr2jb8JEgpH1WNwC3R4A== -----END CERTIFICATE----- rfc3161ng-2.1.3/data/freetsa_cacert.pem000066400000000000000000000054211374702250600175250ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIIH/zCCBeegAwIBAgIJAMHphhYNqOmAMA0GCSqGSIb3DQEBDQUAMIGVMREwDwYD VQQKEwhGcmVlIFRTQTEQMA4GA1UECxMHUm9vdCBDQTEYMBYGA1UEAxMPd3d3LmZy ZWV0c2Eub3JnMSIwIAYJKoZIhvcNAQkBFhNidXNpbGV6YXNAZ21haWwuY29tMRIw EAYDVQQHEwlXdWVyemJ1cmcxDzANBgNVBAgTBkJheWVybjELMAkGA1UEBhMCREUw HhcNMTYwMzEzMDE1MjEzWhcNNDEwMzA3MDE1MjEzWjCBlTERMA8GA1UEChMIRnJl ZSBUU0ExEDAOBgNVBAsTB1Jvb3QgQ0ExGDAWBgNVBAMTD3d3dy5mcmVldHNhLm9y ZzEiMCAGCSqGSIb3DQEJARYTYnVzaWxlemFzQGdtYWlsLmNvbTESMBAGA1UEBxMJ V3VlcnpidXJnMQ8wDQYDVQQIEwZCYXllcm4xCzAJBgNVBAYTAkRFMIICIjANBgkq hkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtgKODjAy8REQ2WTNqUudAnjhlCrpE6ql mQfNppeTmVvZrH4zutn+NwTaHAGpjSGv4/WRpZ1wZ3BRZ5mPUBZyLgq0YrIfQ5Fx 0s/MRZPzc1r3lKWrMR9sAQx4mN4z11xFEO529L0dFJjPF9MD8Gpd2feWzGyptlel b+PqT+++fOa2oY0+NaMM7l/xcNHPOaMz0/2olk0i22hbKeVhvokPCqhFhzsuhKsm q4Of/o+t6dI7sx5h0nPMm4gGSRhfq+z6BTRgCrqQG2FOLoVFgt6iIm/BnNffUr7V DYd3zZmIwFOj/H3DKHoGik/xK3E82YA2ZulVOFRW/zj4ApjPa5OFbpIkd0pmzxzd EcL479hSA9dFiyVmSxPtY5ze1P+BE9bMU1PScpRzw8MHFXxyKqW13Qv7LWw4sbk3 SciB7GACbQiVGzgkvXG6y85HOuvWNvC5GLSiyP9GlPB0V68tbxz4JVTRdw/Xn/XT FNzRBM3cq8lBOAVt/PAX5+uFcv1S9wFE8YjaBfWCP1jdBil+c4e+0tdywT2oJmYB BF/kEt1wmGwMmHunNEuQNzh1FtJY54hbUfiWi38mASE7xMtMhfj/C4SvapiDN837 gYaPfs8x3KZxbX7C3YAsFnJinlwAUss1fdKar8Q/YVs7H/nU4c4Ixxxz4f67fcVq M2ITKentbCMCAwEAAaOCAk4wggJKMAwGA1UdEwQFMAMBAf8wDgYDVR0PAQH/BAQD AgHGMB0GA1UdDgQWBBT6VQ2MNGZRQ0z357OnbJWveuaklzCBygYDVR0jBIHCMIG/ gBT6VQ2MNGZRQ0z357OnbJWveuakl6GBm6SBmDCBlTERMA8GA1UEChMIRnJlZSBU U0ExEDAOBgNVBAsTB1Jvb3QgQ0ExGDAWBgNVBAMTD3d3dy5mcmVldHNhLm9yZzEi MCAGCSqGSIb3DQEJARYTYnVzaWxlemFzQGdtYWlsLmNvbTESMBAGA1UEBxMJV3Vl cnpidXJnMQ8wDQYDVQQIEwZCYXllcm4xCzAJBgNVBAYTAkRFggkAwemGFg2o6YAw MwYDVR0fBCwwKjAooCagJIYiaHR0cDovL3d3dy5mcmVldHNhLm9yZy9yb290X2Nh LmNybDCBzwYDVR0gBIHHMIHEMIHBBgorBgEEAYHyJAEBMIGyMDMGCCsGAQUFBwIB FidodHRwOi8vd3d3LmZyZWV0c2Eub3JnL2ZyZWV0c2FfY3BzLmh0bWwwMgYIKwYB BQUHAgEWJmh0dHA6Ly93d3cuZnJlZXRzYS5vcmcvZnJlZXRzYV9jcHMucGRmMEcG CCsGAQUFBwICMDsaOUZyZWVUU0EgdHJ1c3RlZCB0aW1lc3RhbXBpbmcgU29mdHdh cmUgYXMgYSBTZXJ2aWNlIChTYWFTKTA3BggrBgEFBQcBAQQrMCkwJwYIKwYBBQUH MAGGG2h0dHA6Ly93d3cuZnJlZXRzYS5vcmc6MjU2MDANBgkqhkiG9w0BAQ0FAAOC AgEAaK9+v5OFYu9M6ztYC+L69sw1omdyli89lZAfpWMMh9CRmJhM6KBqM/ipwoLt nxyxGsbCPhcQjuTvzm+ylN6VwTMmIlVyVSLKYZcdSjt/eCUN+41K7sD7GVmxZBAF ILnBDmTGJmLkrU0KuuIpj8lI/E6Z6NnmuP2+RAQSHsfBQi6sssnXMo4HOW5gtPO7 gDrUpVXID++1P4XndkoKn7Svw5n0zS9fv1hxBcYIHPPQUze2u30bAQt0n0iIyRLz aWuhtpAtd7ffwEbASgzB7E+NGF4tpV37e8KiA2xiGSRqT5ndu28fgpOY87gD3ArZ DctZvvTCfHdAS5kEO3gnGGeZEVLDmfEsv8TGJa3AljVa5E40IQDsUXpQLi8G+UC4 1DWZu8EVT4rnYaCw1VX7ShOR1PNCCvjb8S8tfdudd9zhU3gEB0rxdeTy1tVbNLXW 99y90xcwr1ZIDUwM/xQ/noO8FRhm0LoPC73Ef+J4ZBdrvWwauF3zJe33d4ibxEcb 8/pz5WzFkeixYM2nsHhqHsBKw7JPouKNXRnl5IAE1eFmqDyC7G/VT7OF669xM6hb Ut5G21JE4cNK6NNucS+fzg1JPX0+3VhsYZjj7D5uljRvQXrJ8iHgr/M6j2oLHvTA I2MLdq2qjZFDOCXsxBxJpbmLGBx9ow6ZerlUxzws2AWv2pk= -----END CERTIFICATE----- rfc3161ng-2.1.3/data/redwax-interop-ca.crt000066400000000000000000000020211374702250600201040ustar00rootroot000000000000000 0oU}+`ʌK0  *H 0Z1?0=U6Redwax Interop Testing Root Certificate Authority 204010U Redwax Project0 200211163856Z 400206163856Z0Z1?0=U6Redwax Interop Testing Root Certificate Authority 204010U Redwax Project0"0  *H 0  '#_Dp}$FS_t^#lY2 3›=̀ vdLCJe4`Gjzn/ೡ̇ͻJv܀h#~x_ >C׆ĔV@lL)( ;tUCp諕ЂxLhHv & 5ڿo %N) 3Vb鉙$YY.Po$yL Ȓѣm0㖤A>1]!Z00Uu5÷bօ@7 Nprfc3161ng-2.1.3/rfc3161ng/000077500000000000000000000000001374702250600145475ustar00rootroot00000000000000rfc3161ng-2.1.3/rfc3161ng/__init__.py000066400000000000000000000022371374702250600166640ustar00rootroot00000000000000from .types import ( TimeStampReq, MessageImprint, PKIFreeText, PKIStatus, PKIFailureInfo, PKIStatusInfo, TimeStampResp, Accuracy, AnotherName, GeneralName, TimeStampToken, TSTInfo, ) from .constants import ( id_kp_timeStamping, id_sha1, id_sha256, id_sha384, id_sha512, id_ct_TSTInfo, oid_to_hash, ) from .api import ( RemoteTimestamper, check_timestamp, get_hash_oid, TimestampingError, get_timestamp, make_timestamp_request, encode_timestamp_request, encode_timestamp_response, decode_timestamp_request, decode_timestamp_response, ) __all__ = ( 'VERSION', 'TimeStampReq', 'MessageImprint', 'PKIFreeText', 'PKIStatus', 'PKIFailureInfo', 'PKIStatusInfo', 'TimeStampResp', 'Accuracy', 'AnotherName', 'GeneralName', 'TimeStampToken', 'TSTInfo', 'id_kp_timeStamping', 'id_sha1', 'id_sha256', 'id_sha384', 'id_sha512', 'id_ct_TSTInfo', 'oid_to_hash', 'RemoteTimestamper', 'check_timestamp', 'get_hash_oid', 'TimestampingError', 'get_timestamp', 'make_timestamp_request', 'encode_timestamp_request', 'encode_timestamp_response', 'decode_timestamp_request', 'decode_timestamp_response', ) VERSION = '2.1.3' rfc3161ng-2.1.3/rfc3161ng/api.py000066400000000000000000000306641374702250600157030ustar00rootroot00000000000000import hashlib import requests import base64 import re import datetime import dateutil.relativedelta import dateutil.tz from pyasn1.codec.der import encoder, decoder from pyasn1_modules import rfc2459 from pyasn1.type import univ from pyasn1.error import PyAsn1Error from cryptography import x509 from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import hashes import rfc3161ng __all__ = ( 'RemoteTimestamper', 'check_timestamp', 'get_hash_oid', 'TimestampingError', 'get_timestamp', 'make_timestamp_request', 'encode_timestamp_request', 'encode_timestamp_response', 'decode_timestamp_request', 'decode_timestamp_response', ) id_attribute_messageDigest = univ.ObjectIdentifier((1, 2, 840, 113549, 1, 9, 4)) def get_hash_oid(hashname): return rfc3161ng.__dict__['id_' + hashname] def get_hash_from_oid(oid): h = rfc3161ng.oid_to_hash.get(oid) if h is None: raise ValueError('unsupported hash algorithm', oid) return h def get_hash_class_from_oid(oid): h = get_hash_from_oid(oid) return getattr(hashlib, h) class TimestampingError(RuntimeError): pass def generalizedtime_to_utc_datetime(gt, naive=True): m = re.match(r'(?P\d{4})(?P\d{2})(?P\d{2})(?P\d{2})(?:(?P\d{2})(?:(?P\d{2})(?:[.,](?P\d*))?)?)?(?PZ|[+-]\d{2}(?:\d{2})?)?', gt) if not m: raise ValueError("not an ASN.1 generalizedTime: '%s'" % (gt,)) d = m.groupdict() dt = datetime.datetime( int(d['year']), int(d['month']), int(d['day']), int(d['hour']), int(d['minutes'] or 0), int(d['seconds'] or 0), int(float('0.' + d['fractions']) * 1000000 if d['fractions'] else 0) ) if naive: if d['tz'] and d['tz'][0] in ('+', '-'): diff = dateutil.relativedelta.relativedelta( hours=int(d['tz'][1:3]), minutes=int(d['tz'][3:5]) if len(d['tz']) > 3 else 0 ) if d['tz'][0] == '+': dt -= diff else: dt += diff return dt if d['tz'] and re.match(r'^[+\-]\d*[^0]\d*$', d['tz']): diff = datetime.timedelta( hours=int(d['tz'][1:3]), minutes=int(d['tz'][3:5]) if len(d['tz']) > 3 else 0 ).total_seconds() name = d['tz'][0:3] if len(d['tz']) > 3: name += ':' + d['tz'][3:5] dt = dt.replace(tzinfo=dateutil.tz.tzoffset(name, diff if d['tz'][0] == '+' else -diff)) else: dt = dt.replace(tzinfo=dateutil.tz.tzutc()) return dt def get_timestamp(tst, naive=True): try: if not isinstance(tst, rfc3161ng.TimeStampToken): tst, substrate = decoder.decode(tst, asn1Spec=rfc3161ng.TimeStampToken()) if substrate: raise ValueError("extra data after tst") tstinfo = tst.getComponentByName('content').getComponentByPosition(2).getComponentByPosition(1) tstinfo, substrate = decoder.decode(tstinfo, asn1Spec=univ.OctetString()) if substrate: raise ValueError("extra data after tst") tstinfo, substrate = decoder.decode(tstinfo, asn1Spec=rfc3161ng.TSTInfo()) if substrate: raise ValueError("extra data after tst") genTime = tstinfo.getComponentByName('genTime') return generalizedtime_to_utc_datetime(str(genTime), naive) except PyAsn1Error as exc: raise ValueError('not a valid TimeStampToken', exc) def load_certificate(signed_data, certificate=b""): backend = default_backend() if certificate == b"": try: certificate = signed_data['certificates'][0][0] except (KeyError, IndexError, TypeError): raise AttributeError("missing certificate") data = encoder.encode(certificate) return x509.load_der_x509_certificate(data, backend) if b'-----BEGIN CERTIFICATE-----' in certificate: return x509.load_pem_x509_certificate(certificate, backend) return x509.load_der_x509_certificate(certificate, backend) def check_timestamp(tst, certificate=None, data=None, digest=None, hashname=None, nonce=None): hashname = hashname or 'sha1' hashobj = hashlib.new(hashname) if digest is None: if not data: raise ValueError("check_timestamp requires data or digest argument") hashobj.update(data) digest = hashobj.digest() if not isinstance(tst, rfc3161ng.TimeStampToken): tst, substrate = decoder.decode(tst, asn1Spec=rfc3161ng.TimeStampToken()) if substrate: raise ValueError("extra data after tst") signed_data = tst.content certificate = load_certificate(signed_data, certificate) if nonce is not None and int(tst.tst_info['nonce']) != int(nonce): raise ValueError('nonce is different or missing') # check message imprint with respect to locally computed digest message_imprint = tst.tst_info.message_imprint if message_imprint.hash_algorithm[0] != get_hash_oid(hashname) or bytes(message_imprint.hashed_message) != digest: raise ValueError('Message imprint mismatch') if not len(signed_data['signerInfos']): raise ValueError('No signature') # We validate only one signature signer_info = signed_data['signerInfos'][0] # check content type if tst.content['contentInfo']['contentType'] != rfc3161ng.id_ct_TSTInfo: raise ValueError("Signed content type is wrong: %s != %s" % ( tst.content['contentInfo']['contentType'], rfc3161ng.id_ct_TSTInfo )) # check signed data digest content = bytes(decoder.decode(bytes(tst.content['contentInfo']['content']), asn1Spec=univ.OctetString())[0]) # if there is authenticated attributes, they must contain the message # digest and they are the signed data otherwise the content is the # signed data if len(signer_info['authenticatedAttributes']): authenticated_attributes = signer_info['authenticatedAttributes'] signer_digest_algorithm = signer_info['digestAlgorithm']['algorithm'] signer_hash_class = get_hash_class_from_oid(signer_digest_algorithm) signer_hash_name = get_hash_from_oid(signer_digest_algorithm) content_digest = signer_hash_class(content).digest() for authenticated_attribute in authenticated_attributes: if authenticated_attribute[0] == id_attribute_messageDigest: try: signed_digest = bytes(decoder.decode(bytes(authenticated_attribute[1][0]), asn1Spec=univ.OctetString())[0]) if signed_digest != content_digest: raise ValueError('Content digest != signed digest') s = univ.SetOf() for i, x in enumerate(authenticated_attributes): s.setComponentByPosition(i, x) signed_data = encoder.encode(s) break except PyAsn1Error: raise else: raise ValueError('No signed digest') else: signed_data = content # check signature signature = signer_info['encryptedDigest'] public_key = certificate.public_key() hash_family = getattr(hashes, signer_hash_name.upper()) public_key.verify( bytes(signature), signed_data, padding.PKCS1v15(), hash_family(), ) return True class RemoteTimestamper(object): def __init__(self, url, certificate=None, capath=None, cafile=None, username=None, password=None, hashname=None, include_tsa_certificate=False, timeout=10, tsa_policy_id=None): self.url = url self.certificate = certificate self.capath = capath self.cafile = cafile self.username = username self.password = password self.hashname = hashname or 'sha1' self.include_tsa_certificate = include_tsa_certificate self.timeout = timeout self.tsa_policy_id = tsa_policy_id def check_response(self, response, digest, nonce=None): ''' Check validity of a TimeStampResponse ''' tst = response.time_stamp_token if self.certificate: return self.check(tst, digest=digest, nonce=nonce) return tst def check(self, tst, data=None, digest=None, nonce=None): return check_timestamp( tst, digest=digest, data=data, nonce=nonce, certificate=self.certificate, hashname=self.hashname, ) def timestamp(self, data=None, digest=None, include_tsa_certificate=None, nonce=None, tsa_policy_id=None): return self( data=data, digest=digest, include_tsa_certificate=include_tsa_certificate, nonce=nonce, tsa_policy_id=tsa_policy_id, ) def __call__(self, data=None, digest=None, include_tsa_certificate=None, nonce=None, return_tsr=False, tsa_policy_id=None): if data: digest = data_to_digest(data, self.hashname) request = make_timestamp_request( data=data, digest=digest, hashname=self.hashname, include_tsa_certificate=include_tsa_certificate if include_tsa_certificate is not None else self.include_tsa_certificate, nonce=nonce, tsa_policy_id=tsa_policy_id if tsa_policy_id is not None else self.tsa_policy_id ) binary_request = encode_timestamp_request(request) headers = {'Content-Type': 'application/timestamp-query'} if self.username is not None: username = self.username.encode() if not isinstance(self.username, bytes) else self.username password = self.password.encode() if not isinstance(self.password, bytes) else self.password base64string = base64.standard_b64encode(b'%s:%s' % (username, password)) if isinstance(base64string, bytes): base64string = base64string.decode() headers['Authorization'] = "Basic %s" % base64string try: response = requests.post( self.url, data=binary_request, timeout=self.timeout, headers=headers, ) response.raise_for_status() except requests.RequestException as exc: raise TimestampingError('Unable to send the request to %r' % self.url, exc) tsr = decode_timestamp_response(response.content) self.check_response(tsr, digest, nonce=nonce) if return_tsr: return tsr return encoder.encode(tsr.time_stamp_token) def data_to_digest(data, hashname='sha1'): hashobj = hashlib.new(hashname) if not isinstance(data, bytes): data = data.encode() hashobj.update(data) return hashobj.digest() def make_timestamp_request(data=None, digest=None, hashname='sha1', include_tsa_certificate=False, nonce=None, tsa_policy_id=None): algorithm_identifier = rfc2459.AlgorithmIdentifier() algorithm_identifier.setComponentByPosition(0, get_hash_oid(hashname)) message_imprint = rfc3161ng.MessageImprint() message_imprint.setComponentByPosition(0, algorithm_identifier) hashobj = hashlib.new(hashname) if digest: assert len(digest) == hashobj.digest_size, 'digest length is wrong %s != %s' % (len(digest), hashobj.digest_size) elif data: digest = data_to_digest(data) else: raise ValueError('You must pass some data to digest, or the digest') message_imprint.setComponentByPosition(1, digest) tsq = rfc3161ng.TimeStampReq() tsq.setComponentByPosition(0, 'v1') tsq.setComponentByPosition(1, message_imprint) if tsa_policy_id: tsq.setComponentByPosition(2, rfc3161ng.types.TSAPolicyId(tsa_policy_id)) if nonce is not None: tsq.setComponentByPosition(3, int(nonce)) tsq.setComponentByPosition(4, include_tsa_certificate) return tsq def encode_timestamp_request(request): return encoder.encode(request) def encode_timestamp_response(response): return encoder.encode(response) def decode_timestamp_request(request): tsq, substrate = decoder.decode(request, asn1Spec=rfc3161ng.TimeStampReq()) if substrate: raise ValueError('Extra data returned') return tsq def decode_timestamp_response(response): tsr, substrate = decoder.decode(response, asn1Spec=rfc3161ng.TimeStampResp()) if substrate: raise ValueError('Extra data returned') return tsr rfc3161ng-2.1.3/rfc3161ng/constants.py000066400000000000000000000012451374702250600171370ustar00rootroot00000000000000from pyasn1.type import univ __all__ = ( 'id_kp_timeStamping', 'id_sha1', 'id_sha256', 'id_sha384', 'id_sha512', 'id_ct_TSTInfo', 'oid_to_hash', ) id_kp_timeStamping = univ.ObjectIdentifier((1, 3, 6, 1, 5, 5, 7, 3, 8)) id_sha1 = univ.ObjectIdentifier((1, 3, 14, 3, 2, 26)) id_sha256 = univ.ObjectIdentifier((2, 16, 840, 1, 101, 3, 4, 2, 1)) id_sha384 = univ.ObjectIdentifier((2, 16, 840, 1, 101, 3, 4, 2, 2)) id_sha512 = univ.ObjectIdentifier((2, 16, 840, 1, 101, 3, 4, 2, 3)) id_ct_TSTInfo = univ.ObjectIdentifier((1, 2, 840, 113549, 1, 9, 16, 1, 4)) oid_to_hash = { id_sha1: 'sha1', id_sha256: 'sha256', id_sha384: 'sha384', id_sha512: 'sha512', } rfc3161ng-2.1.3/rfc3161ng/types.py000066400000000000000000000170351374702250600162730ustar00rootroot00000000000000from pyasn1.type import univ, namedtype, tag, namedval, constraint, char, useful from pyasn1_modules.rfc2459 import AlgorithmIdentifier, Extensions, MAX, Name from pyasn1_modules.rfc2315 import ContentInfo, signedData, SignedData from pyasn1.codec.ber import decoder __all__ = ( 'TimeStampReq', 'MessageImprint', 'PKIFreeText', 'PKIStatus', 'PKIFailureInfo', 'PKIStatusInfo', 'TimeStampResp', 'Accuracy', 'AnotherName', 'GeneralName', 'TimeStampToken', 'TSTInfo', ) # Request class TSAPolicyId(univ.ObjectIdentifier): pass class MessageImprint(univ.Sequence): componentType = namedtype.NamedTypes( namedtype.NamedType('hashAlgorithm', AlgorithmIdentifier()), namedtype.NamedType('hashedMessage', univ.OctetString()), ) @property def hash_algorithm(self): return self[0] @property def hashed_message(self): return self[1] class TimeStampReq(univ.Sequence): componentType = namedtype.NamedTypes( namedtype.NamedType('version', univ.Integer(namedValues=namedval.NamedValues(('v1', 1)))), namedtype.NamedType('messageImprint', MessageImprint()), namedtype.OptionalNamedType('reqPolicy', TSAPolicyId()), namedtype.OptionalNamedType('nonce', univ.Integer()), namedtype.DefaultedNamedType('certReq', univ.Boolean(False)), namedtype.OptionalNamedType('extensions', Extensions().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))) ) # Reponse class PKIFreeText(univ.SequenceOf): componentType = char.UTF8String() sizeSpec = univ.SequenceOf.sizeSpec + constraint.ValueSizeConstraint(1, MAX) class PKIStatus(univ.Integer): namedValues = namedval.NamedValues( ('granted', 0), # -- when the PKIStatus contains the value zero a TimeStampToken, as # requested, is present. ('grantedWithMods', 1), # -- when the PKIStatus contains the value one a TimeStampToken, # with modifications, is present. ('rejection', 2), ('waiting', 3), ('revocationWarning', 4), # -- this message contains a warning that a revocation is # -- imminent ('revocationNotification', 5), ) class PKIFailureInfo(univ.BitString): namedValues = namedval.NamedValues( ('badAlg', 0), # -- unrecognized or unsupported Algorithm Identifier ('badRequest', 2), # -- transaction not permitted or supported ('badDataFormat', 5), # -- the data submitted has the wrong format ('timeNotAvailable', 14), # -- the TSA's time source is not available ('unacceptedPolicy', 15), # -- the requested TSA policy is not supported by the TSA ('unacceptedExtension', 16), # -- the requested extension is not supported by the TSA ('addInfoNotAvailable', 17), # -- the additional information requested could not be understood # -- or is not available ('systemFailure', 25), # -- the request cannot be handled due to system failure } ) class PKIStatusInfo(univ.Sequence): componentType = namedtype.NamedTypes( namedtype.NamedType('status', PKIStatus()), namedtype.OptionalNamedType('statusString', PKIFreeText()), namedtype.OptionalNamedType('failInfo', PKIFailureInfo()), ) class TimeStampToken(ContentInfo): componentType = namedtype.NamedTypes( namedtype.NamedType('contentType', signedData), namedtype.OptionalNamedType('content', SignedData().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatConstructed, 0))), ) @property def content(self): return self[1] @property def tst_info(self): x, substrate = decoder.decode(bytes(self.content['contentInfo']['content'])) if substrate: raise ValueError('Incomplete decoding') x, substrate = decoder.decode(bytes(x), asn1Spec=TSTInfo()) if substrate: raise ValueError('Incomplete decoding') return x class TimeStampResp(univ.Sequence): componentType = namedtype.NamedTypes( namedtype.NamedType('status', PKIStatusInfo()), namedtype.OptionalNamedType('timeStampToken', TimeStampToken()) ) @property def status(self): return self[0] @property def time_stamp_token(self): return self[1] class Accuracy(univ.Sequence): componentType = namedtype.NamedTypes( namedtype.OptionalNamedType('seconds', univ.Integer()), namedtype.OptionalNamedType('millis', univ.Integer().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), namedtype.OptionalNamedType('micros', univ.Integer().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), ) # import from class AnotherName(univ.Sequence): componentType = namedtype.NamedTypes( namedtype.OptionalNamedType('type-id', univ.ObjectIdentifier()), namedtype.OptionalNamedType('value', univ.Any().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), ) class GeneralName(univ.Choice): componentType = namedtype.NamedTypes( namedtype.NamedType('rfc822Name', char.IA5String().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), # namedtype.NamedType('dNSName', univ.Any().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 2))), # namedtype.NamedType('x400Address', univ.Any().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 3))), namedtype.NamedType('directoryName', Name().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 4))), # namedtype.NamedType('ediPartyName', univ.Any().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 5))), # namedtype.NamedType('uniformResourceIdentifier', char.IA5String().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 6))), # namedtype.NamedType('iPAddress', univ.OctetString().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 7))), namedtype.NamedType('registeredID', univ.ObjectIdentifier().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 8)))) class TSTInfo(univ.Sequence): componentType = namedtype.NamedTypes( namedtype.NamedType('version', univ.Integer(namedValues=namedval.NamedValues(('v1', 1)))), namedtype.OptionalNamedType('policy', TSAPolicyId()), namedtype.NamedType('messageImprint', MessageImprint()), # -- MUST have the same value as the similar field in # -- TimeStampReq namedtype.NamedType('serialNumber', univ.Integer()), # -- Time-Stamping users MUST be ready to accommodate integers # -- up to 160 bits. namedtype.NamedType('genTime', useful.GeneralizedTime()), namedtype.OptionalNamedType('accuracy', Accuracy()), namedtype.DefaultedNamedType('ordering', univ.Boolean(False)), namedtype.OptionalNamedType('nonce', univ.Integer()), # -- MUST be present if the similar field was present # -- in TimeStampReq. In that case it MUST have the same value. namedtype.OptionalNamedType('tsa', GeneralName().subtype(explicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 0))), namedtype.OptionalNamedType('extensions', Extensions().subtype(implicitTag=tag.Tag(tag.tagClassContext, tag.tagFormatSimple, 1))), ) @property def version(self): return self[0] @property def policy(self): return self[1] @property def message_imprint(self): return self[2] rfc3161ng-2.1.3/setup.cfg000066400000000000000000000004051374702250600147550ustar00rootroot00000000000000[bdist_wheel] universal = 1 [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 [flake8] ignore = E265,W391 max-line-length = 256 exclude = .tox,.git,__pycache__,docs/source/conf.py,old,build,dist max-complexity = 20 [pycodestyle] max-line-length = 256 rfc3161ng-2.1.3/setup.py000077500000000000000000000023631374702250600146560ustar00rootroot00000000000000#!/usr/bin/python from setuptools import setup with open('README.rst', 'r') as f: long_description = f.read() setup( name='rfc3161ng', version='2.1.3', license='MIT', url='https://dev.entrouvert.org/projects/python-rfc3161', description='Python implementation of the RFC3161 specification, using pyasn1', long_description=long_description, long_description_content_type='text/x-rst', author='Benjamin Dauvergne', author_email='bdauvergne@entrouvert.com', maintainer='trbs', maintainer_email='trbs@trbs.net', platforms=['any'], classifiers=[ 'Environment :: Console', 'Intended Audience :: Developers', 'Intended Audience :: Information Technology', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3.6', 'Topic :: Communications', 'Topic :: Software Development :: Libraries :: Python Modules', ], packages=['rfc3161ng'], install_requires=[ 'pyasn1', 'python-dateutil', 'pyasn1_modules', 'requests', 'cryptography', ] ) rfc3161ng-2.1.3/spec/000077500000000000000000000000001374702250600140675ustar00rootroot00000000000000rfc3161ng-2.1.3/spec/schema.asn88000066400000000000000000000115551374702250600162210ustar00rootroot00000000000000PKIXTSP {iso(1) identified-organization(3) dod(6) internet(1) security(5) mechanisms(5) pkix(7) id-mod(0) id-mod-tsp(13)} DEFINITIONS IMPLICIT TAGS ::= BEGIN -- EXPORTS ALL -- IMPORTS Extensions, AlgorithmIdentifier FROM PKIX1Explicit88 {iso(1) identified-organization(3) dod(6) internet(1) security(5) mechanisms(5) pkix(7) id-mod(0) id-pkix1-explicit-88(1)} GeneralName FROM PKIX1Implicit88 {iso(1) identified-organization(3) dod(6) internet(1) security(5) mechanisms(5) pkix(7) id-mod(0) id-pkix1-implicit-88(2)} ContentInfo FROM CryptographicMessageSyntax {iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) modules(0) cms(1)} PKIFreeText FROM PKIXCMP {iso(1) identified-organization(3) dod(6) internet(1) security(5) mechanisms(5) pkix(7) id-mod(0) id-mod-cmp(9)} ; -- Locally defined OIDs -- -- eContentType for a time-stamp token id-ct-TSTInfo OBJECT IDENTIFIER ::= { iso(1) member-body(2) us(840) rsadsi(113549) pkcs(1) pkcs-9(9) smime(16) ct(1) 4} -- 2.4.1 TimeStampReq ::= SEQUENCE { version INTEGER { v1(1) }, messageImprint MessageImprint, --a hash algorithm OID and the hash value of the data to be --time-stamped reqPolicy TSAPolicyId OPTIONAL, nonce INTEGER OPTIONAL, certReq BOOLEAN DEFAULT FALSE, extensions [0] IMPLICIT Extensions OPTIONAL } MessageImprint ::= SEQUENCE { hashAlgorithm AlgorithmIdentifier, hashedMessage OCTET STRING } TSAPolicyId ::= OBJECT IDENTIFIER -- 2.4.2 TimeStampResp ::= SEQUENCE { status PKIStatusInfo, timeStampToken TimeStampToken OPTIONAL } -- The status is based on the definition of status -- in section 3.2.3 of [RFC2510] PKIStatusInfo ::= SEQUENCE { status PKIStatus, statusString PKIFreeText OPTIONAL, failInfo PKIFailureInfo OPTIONAL } PKIStatus ::= INTEGER { granted (0), -- when the PKIStatus contains the value zero a TimeStampToken, as -- requested, is present. grantedWithMods (1), -- when the PKIStatus contains the value one a TimeStampToken, -- with modifications, is present. rejection (2), waiting (3), revocationWarning (4), -- this message contains a warning that a revocation is -- imminent revocationNotification (5) } -- notification that a revocation has occurred -- When the TimeStampToken is not present -- failInfo indicates the reason why the -- time-stamp request was rejected and -- may be one of the following values. PKIFailureInfo ::= BIT STRING { badAlg (0), -- unrecognized or unsupported Algorithm Identifier badRequest (2), -- transaction not permitted or supported badDataFormat (5), -- the data submitted has the wrong format timeNotAvailable (14), -- the TSA's time source is not available unacceptedPolicy (15), -- the requested TSA policy is not supported by the TSA. unacceptedExtension (16), -- the requested extension is not supported by the TSA. addInfoNotAvailable (17), -- the additional information requested could not be understood -- or is not available systemFailure (25) -- the request cannot be handled due to system failure } TimeStampToken ::= ContentInfo -- contentType is id-signedData as defined in [CMS] -- content is SignedData as defined in([CMS]) -- eContentType within SignedData is id-ct-TSTInfo -- eContent within SignedData is TSTInfo TSTInfo ::= SEQUENCE { version INTEGER { v1(1) }, policy TSAPolicyId, messageImprint MessageImprint, -- MUST have the same value as the similar field in -- TimeStampReq serialNumber INTEGER, -- Time-Stamping users MUST be ready to accommodate integers -- up to 160 bits. genTime GeneralizedTime, accuracy Accuracy OPTIONAL, ordering BOOLEAN DEFAULT FALSE, nonce INTEGER OPTIONAL, -- MUST be present if the similar field was present -- in TimeStampReq. In that case it MUST have the same value. tsa [0] GeneralName OPTIONAL, extensions [1] IMPLICIT Extensions OPTIONAL } Accuracy ::= SEQUENCE { seconds INTEGER OPTIONAL, millis [0] INTEGER (1..999) OPTIONAL, micros [1] INTEGER (1..999) OPTIONAL } END rfc3161ng-2.1.3/stdeb.cfg000066400000000000000000000001111374702250600147100ustar00rootroot00000000000000[DEFAULT] Depends: python-pyasn1, python-pyasn1-modules, python-m2crypto rfc3161ng-2.1.3/tests/000077500000000000000000000000001374702250600142775ustar00rootroot00000000000000rfc3161ng-2.1.3/tests/test_api.py000077500000000000000000000144461374702250600164750ustar00rootroot00000000000000import os.path import datetime import dateutil.tz import subprocess import pytest from tempfile import NamedTemporaryFile from pyasn1.codec.der import encoder import rfc3161ng def _default_test(tsa_server, certificate=None, username=None, password=None, data='xx', nonce=None, **kwargs): if certificate: with open(certificate, 'rb') as f: certificate_data = f.read() kwargs.update({ 'certificate': certificate_data, }) if username and password: kwargs.update({ 'username': username, 'password': password, }) timestamper = rfc3161ng.RemoteTimestamper(tsa_server, **kwargs) kwargs = {} if nonce: kwargs['nonce'] = nonce value = timestamper(data=data, **kwargs) assert value is not False assert isinstance(rfc3161ng.get_timestamp(value), datetime.datetime) assert value is not None def test_verify_timestamp_response_with_openssl(): with open(os.path.join(os.path.dirname(__file__), '../data/freetsa.crt'), 'rb') as f: certificate_data = f.read() timestamper = rfc3161ng.RemoteTimestamper('http://freetsa.org/tsr', certificate=certificate_data) with NamedTemporaryFile() as data_f, NamedTemporaryFile() as tsr_f: data_f.write(b"Hello World from rfc3161ng\n") data_f.flush() data_f.seek(0) tsr = timestamper(data=data_f.read(), return_tsr=True) tsr_f.write(encoder.encode(tsr)) tsr_f.flush() args = ["openssl", "ts", "-verify", "-data", data_f.name, "-in", tsr_f.name, "-CAfile", "data/freetsa_cacert.pem", "-untrusted", "data/freetsa.crt"] subprocess.check_call(args) def test_time_certum_pl(): _default_test( 'http://time.certum.pl', os.path.join(os.path.dirname(__file__), '../data/certum_certificate.crt'), ) @pytest.mark.xfail def test_redwax_eu(): # https://interop.redwax.eu/rs/timestamp/ # CA: https://interop.redwax.eu/test/simple/ca.der # Server: 'https://interop.redwax.eu/test/timestamp _default_test( 'https://interop.redwax.eu/test/timestamp', certificate=os.path.join('data/redwax-interop-ca.crt'), data=b'The RedWax Project', hashname='sha256', ) @pytest.mark.xfail def test_redwax_eu_no_certificate(): # https://interop.redwax.eu/rs/timestamp/ # CA: https://interop.redwax.eu/test/simple/ca.der # Server: 'https://interop.redwax.eu/test/timestamp _default_test( 'https://interop.redwax.eu/test/timestamp', data=b'The RedWax Project', hashname='sha256', ) def test_freetsa_org(): _default_test( 'http://freetsa.org/tsr', os.path.join('data/freetsa.crt'), ) def test_teszt_e_szigno_hu(): data = '{"comment": "Envoi en Commission", "to": "Benjamin Dauvergne", "filetype": "Arr\u00eat CC", "from": "Benjamin Dauvergne", "files": [{"name": "affectations_ange1d.xlsx", "digest": "ce57e4ba353107dddaab91b9ad26c0569ffe0f94", "size": 16279}]}' _default_test( 'https://teszt.e-szigno.hu:440/tsa', username='teszt', password='teszt', certificate=os.path.join(os.path.dirname(__file__), '../data/e_szigno_test_tsa2.crt'), data=data, hashname='sha256', ) def test_teszt_e_szigno_hu_with_nonce(): data = '{"comment": "Envoi en Commission", "to": "Benjamin Dauvergne", "filetype": "Arr\u00eat CC", "from": "Benjamin Dauvergne", "files": [{"name": "affectations_ange1d.xlsx", "digest": "ce57e4ba353107dddaab91b9ad26c0569ffe0f94", "size": 16279}]}' _default_test( 'https://teszt.e-szigno.hu:440/tsa', username='teszt', password='teszt', certificate=os.path.join(os.path.dirname(__file__), '../data/e_szigno_test_tsa2.crt'), data=data, nonce=2, hashname='sha256', ) def test_encode_decode_timestamp_request(): tsq = rfc3161ng.make_timestamp_request(data="test") pretty_print_str = "TimeStampReq:\n version=v1\n messageImprint=MessageImprint:\n hashAlgorithm=AlgorithmIdentifier:\n algorithm=1.3.14.3.2.26\n\n hashedMessage=0xa94a8fe5ccb19ba61c4c0873d391e987982fbbd3\n\n certReq=False\n" # Some versions of prettyPrint() include quotes, others do not. # Hide the difference by removing the quotes. assert tsq.prettyPrint().replace("'", "") == pretty_print_str bin_tsq = rfc3161ng.encode_timestamp_request(tsq) assert bin_tsq == b'0$\x02\x01\x010\x1f0\x07\x06\x05+\x0e\x03\x02\x1a\x04\x14\xa9J\x8f\xe5\xcc\xb1\x9b\xa6\x1cL\x08s\xd3\x91\xe9\x87\x98/\xbb\xd3' tsq2 = rfc3161ng.decode_timestamp_request(bin_tsq) assert tsq2.getComponentByPosition(1).getComponentByPosition(1) == tsq.getComponentByPosition(1).getComponentByPosition(1) # This test is probably still incomplete def test_generalized_time_decoding(): from rfc3161ng.api import generalizedtime_to_utc_datetime # generalizedTime string, naive == expected datetime assert generalizedtime_to_utc_datetime('20180208181004,948468', True) == datetime.datetime(2018, 2, 8, 18, 10, 4, 948468) assert generalizedtime_to_utc_datetime('20180208181004', True) == datetime.datetime(2018, 2, 8, 18, 10, 4, 0) assert generalizedtime_to_utc_datetime('201802081810', True) == datetime.datetime(2018, 2, 8, 18, 10, 0, 0) assert generalizedtime_to_utc_datetime('2018020818', True) == datetime.datetime(2018, 2, 8, 18, 0, 0, 0) assert generalizedtime_to_utc_datetime('20180208181004.948468Z', True) == datetime.datetime(2018, 2, 8, 18, 10, 4, 948468) assert generalizedtime_to_utc_datetime('20180208181004.948468+01', True) == datetime.datetime(2018, 2, 8, 17, 10, 4, 948468) assert generalizedtime_to_utc_datetime('20180208181004.948468-01', True) == datetime.datetime(2018, 2, 8, 19, 10, 4, 948468) assert generalizedtime_to_utc_datetime('20180208181004.948468+0130', True) == datetime.datetime(2018, 2, 8, 16, 40, 4, 948468) assert generalizedtime_to_utc_datetime('20180208181004.948468Z', False) == datetime.datetime(2018, 2, 8, 18, 10, 4, 948468, tzinfo=dateutil.tz.tzutc()) assert generalizedtime_to_utc_datetime('20180208181004.948468-01', False) == datetime.datetime(2018, 2, 8, 19, 10, 4, 948468, tzinfo=dateutil.tz.tzutc()) assert generalizedtime_to_utc_datetime('20180208181004.948468+0130', False) == datetime.datetime(2018, 2, 8, 16, 40, 4, 948468, tzinfo=dateutil.tz.tzutc()) rfc3161ng-2.1.3/tox.ini000066400000000000000000000003311374702250600144450ustar00rootroot00000000000000[tox] envlist = py38 [gh-actions] python = 3.8: py38 [testenv] setenv = deps = flake8 pytest-cov pytest commands = flake8 rfc3161ng tests pip install -e . py.test -ra -v {posargs:tests/}