CryptoParser 1.2.0__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/.gitlab-ci.yml +1 -1
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/CHANGELOG.rst +35 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/CryptoParser.egg-info/PKG-INFO +5 -3
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/CryptoParser.egg-info/SOURCES.txt +7 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/CryptoParser.egg-info/requires.txt +3 -1
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/CryptoParser.egg-info/top_level.txt +1 -2
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/PKG-INFO +5 -3
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/__setup__.py +1 -1
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/x509.py +59 -2
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/dnsrec/record.py +57 -1
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ike/ikev2.py +10 -2
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ike/isakmp.py +8 -8
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ike/version.py +15 -21
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/subprotocol.py +279 -16
- cryptoparser-1.3.0/cryptoparser.spec +60 -0
- cryptoparser-1.3.0/debian/changelog +15 -0
- cryptoparser-1.3.0/debian/control +31 -0
- cryptoparser-1.3.0/debian/copyright +16 -0
- cryptoparser-1.3.0/debian/rules +7 -0
- cryptoparser-1.3.0/debian/source/format +1 -0
- cryptoparser-1.3.0/debian/watch +3 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/docs/features.rst +3 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/pyproject.toml +5 -4
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_x509.py +20 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/dnsrec/test_record.py +56 -1
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/test_ikev2_sa.py +13 -1
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/test_isakmp.py +13 -12
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/test_version.py +24 -17
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_handshake.py +184 -3
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/.coveragerc +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/.flake8 +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/.gitignore +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/.gitmodules +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/.pylintrc +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/.readthedocs.yaml +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/CONTRIBUTING.rst +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/CryptoParser.egg-info/dependency_links.txt +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/LICENSE.txt +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/MANIFEST.in +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/README.rst +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/__init__.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/__init__.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/base.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/classes.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/exception.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/field.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/parse.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/utils.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/dnsrec/__init__.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/dnsrec/txt.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/httpx/__init__.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/httpx/header.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/httpx/parse.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/httpx/version.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ike/__init__.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ike/common.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ike/ikev1.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ssh/__init__.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ssh/key.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ssh/record.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ssh/subprotocol.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ssh/version.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/__init__.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/algorithm.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/ciphersuite.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/extension.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/grease.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/ldap.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/mysql.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/openvpn.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/postgresql.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/rdp.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/record.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/version.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/docs/.gitignore +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/docs/changelog.rst +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/docs/conf.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/docs/development.rst +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/docs/index.rst +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/setup.cfg +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/setup.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/__init__.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/__init__.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/certs/ecc256.badssl.com.pem +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/certs/rsa8192.badssl.com.pem +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/certs/rsa8192.badssl.com_certificate.crt +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/certs/rsa8192.badssl.com_root_ca.crt +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/certs/snakeoil_cert.pem +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/classes.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_algorithm.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_base.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_classes.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_exception.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_field.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_parse.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_utils.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/dnsrec/__init__.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/dnsrec/test_txt.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/httpx/__init__.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/httpx/classes.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/httpx/test_header.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/httpx/test_version.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/__init__.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/classes.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/test_ikev1_payload.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/test_ikev1_sa.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/test_ikev2_notify.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/test_ikev2_payload.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ssh/__init__.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ssh/test_ciphersuites.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ssh/test_key.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ssh/test_record.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ssh/test_subprotocol.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ssh/test_version.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/__init__.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/classes.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_alert.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_application_data.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_change_cipher_spec.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_ciphersuite.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_extension.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_grease.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_ldap.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_mysql.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_openvpn.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_postgresql.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_rdp.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_record.py +0 -0
- {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_version.py +0 -0
|
@@ -2,6 +2,41 @@
|
|
|
2
2
|
Changelog
|
|
3
3
|
=========
|
|
4
4
|
|
|
5
|
+
------------------
|
|
6
|
+
1.3.0 - 2026-06-15
|
|
7
|
+
------------------
|
|
8
|
+
|
|
9
|
+
Features
|
|
10
|
+
========
|
|
11
|
+
|
|
12
|
+
- Generic
|
|
13
|
+
|
|
14
|
+
- add Debian and RPM packaging (#102)
|
|
15
|
+
|
|
16
|
+
- TLS (``tls``)
|
|
17
|
+
|
|
18
|
+
- add JA4 tag generation for the client hello message (#100)
|
|
19
|
+
- add JA4X tag generation for X.509 certificates (#101)
|
|
20
|
+
- add certificate-related messages for protocol version 1.3 (#94)
|
|
21
|
+
|
|
22
|
+
Notable fixes
|
|
23
|
+
=============
|
|
24
|
+
|
|
25
|
+
- IKE (``ike``)
|
|
26
|
+
|
|
27
|
+
- make IKEv2 transform key length optional for fixed-key ciphers (#99)
|
|
28
|
+
|
|
29
|
+
------------------
|
|
30
|
+
1.2.1 - 2026-06-02
|
|
31
|
+
------------------
|
|
32
|
+
|
|
33
|
+
Features
|
|
34
|
+
========
|
|
35
|
+
|
|
36
|
+
- DNS (``dnsrec``)
|
|
37
|
+
|
|
38
|
+
- add SSHFP DNS resource record parsing (#98)
|
|
39
|
+
|
|
5
40
|
------------------
|
|
6
41
|
1.2.0 - 2026-05-05
|
|
7
42
|
------------------
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: CryptoParser
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: An analysis oriented security protocol parser and generator
|
|
5
5
|
Author-email: Szilárd Pfeiffer <coroner@pfeifferszilard.hu>
|
|
6
6
|
Maintainer-email: Szilárd Pfeiffer <coroner@pfeifferszilard.hu>
|
|
7
|
-
License
|
|
7
|
+
License: MPL-2.0
|
|
8
8
|
Project-URL: Homepage, https://gitlab.com/coroner/cryptoparser
|
|
9
9
|
Project-URL: Changelog, https://cryptoparser.readthedocs.io/en/latest/changelog
|
|
10
10
|
Project-URL: Documentation, https://cryptoparser.readthedocs.io/en/latest/
|
|
@@ -40,13 +40,15 @@ Description-Content-Type: text/x-rst
|
|
|
40
40
|
License-File: LICENSE.txt
|
|
41
41
|
Requires-Dist: asn1crypto
|
|
42
42
|
Requires-Dist: attrs
|
|
43
|
-
Requires-Dist: cryptodatahub==1.
|
|
43
|
+
Requires-Dist: cryptodatahub==1.3.0
|
|
44
44
|
Requires-Dist: urllib3
|
|
45
45
|
Provides-Extra: tests
|
|
46
46
|
Requires-Dist: pyfakefs; extra == "tests"
|
|
47
|
+
Requires-Dist: coverage; extra == "tests"
|
|
47
48
|
Provides-Extra: docs
|
|
48
49
|
Requires-Dist: sphinx; extra == "docs"
|
|
49
50
|
Requires-Dist: sphinx-sitemap; extra == "docs"
|
|
51
|
+
Requires-Dist: docutils; extra == "docs"
|
|
50
52
|
Dynamic: license-file
|
|
51
53
|
|
|
52
54
|
.. image:: https://gitlab.com/coroner/cryptoparser/badges/master/pipeline.svg
|
|
@@ -10,6 +10,7 @@ CONTRIBUTING.rst
|
|
|
10
10
|
LICENSE.txt
|
|
11
11
|
MANIFEST.in
|
|
12
12
|
README.rst
|
|
13
|
+
cryptoparser.spec
|
|
13
14
|
pyproject.toml
|
|
14
15
|
setup.py
|
|
15
16
|
CryptoParser.egg-info/PKG-INFO
|
|
@@ -58,6 +59,12 @@ cryptoparser/tls/rdp.py
|
|
|
58
59
|
cryptoparser/tls/record.py
|
|
59
60
|
cryptoparser/tls/subprotocol.py
|
|
60
61
|
cryptoparser/tls/version.py
|
|
62
|
+
debian/changelog
|
|
63
|
+
debian/control
|
|
64
|
+
debian/copyright
|
|
65
|
+
debian/rules
|
|
66
|
+
debian/watch
|
|
67
|
+
debian/source/format
|
|
61
68
|
docs/.gitignore
|
|
62
69
|
docs/changelog.rst
|
|
63
70
|
docs/conf.py
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: CryptoParser
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: An analysis oriented security protocol parser and generator
|
|
5
5
|
Author-email: Szilárd Pfeiffer <coroner@pfeifferszilard.hu>
|
|
6
6
|
Maintainer-email: Szilárd Pfeiffer <coroner@pfeifferszilard.hu>
|
|
7
|
-
License
|
|
7
|
+
License: MPL-2.0
|
|
8
8
|
Project-URL: Homepage, https://gitlab.com/coroner/cryptoparser
|
|
9
9
|
Project-URL: Changelog, https://cryptoparser.readthedocs.io/en/latest/changelog
|
|
10
10
|
Project-URL: Documentation, https://cryptoparser.readthedocs.io/en/latest/
|
|
@@ -40,13 +40,15 @@ Description-Content-Type: text/x-rst
|
|
|
40
40
|
License-File: LICENSE.txt
|
|
41
41
|
Requires-Dist: asn1crypto
|
|
42
42
|
Requires-Dist: attrs
|
|
43
|
-
Requires-Dist: cryptodatahub==1.
|
|
43
|
+
Requires-Dist: cryptodatahub==1.3.0
|
|
44
44
|
Requires-Dist: urllib3
|
|
45
45
|
Provides-Extra: tests
|
|
46
46
|
Requires-Dist: pyfakefs; extra == "tests"
|
|
47
|
+
Requires-Dist: coverage; extra == "tests"
|
|
47
48
|
Provides-Extra: docs
|
|
48
49
|
Requires-Dist: sphinx; extra == "docs"
|
|
49
50
|
Requires-Dist: sphinx-sitemap; extra == "docs"
|
|
51
|
+
Requires-Dist: docutils; extra == "docs"
|
|
50
52
|
Dynamic: license-file
|
|
51
53
|
|
|
52
54
|
.. image:: https://gitlab.com/coroner/cryptoparser/badges/master/pipeline.svg
|
|
@@ -12,4 +12,4 @@ __description__ = metadata['Summary']
|
|
|
12
12
|
__author__ = metadata['Author']
|
|
13
13
|
__author_email__ = metadata['Author-email']
|
|
14
14
|
__url__ = 'https://gitlab.com/coroner/' + __technical_name__
|
|
15
|
-
__license__ = metadata['License']
|
|
15
|
+
__license__ = metadata.get('License-Expression') or metadata['License']
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
import collections
|
|
5
5
|
import datetime
|
|
6
6
|
import enum
|
|
7
|
+
import hashlib
|
|
7
8
|
|
|
8
9
|
import attr
|
|
9
10
|
|
|
@@ -19,7 +20,7 @@ from cryptoparser.common.base import (
|
|
|
19
20
|
VectorParamParsable,
|
|
20
21
|
VectorParsable,
|
|
21
22
|
)
|
|
22
|
-
from cryptoparser.common.parse import ComposerBinary, ParsableBase, ParserBinary
|
|
23
|
+
from cryptoparser.common.parse import ComposerBinary, ComposerText, ParsableBase, ParserBinary
|
|
23
24
|
|
|
24
25
|
from cryptoparser.tls.algorithm import TlsSignatureAndHashAlgorithm, TlsSignatureAndHashAlgorithmFactory
|
|
25
26
|
|
|
@@ -55,7 +56,7 @@ class SignedCertificateTimestamp(ParsableBase, Serializable):
|
|
|
55
56
|
)
|
|
56
57
|
timestamp = attr.ib(validator=attr.validators.instance_of(datetime.datetime))
|
|
57
58
|
extensions = attr.ib(
|
|
58
|
-
validator=attr.validators.
|
|
59
|
+
validator=attr.validators.instance_of(CtExtensions)
|
|
59
60
|
)
|
|
60
61
|
signature_algorithm = attr.ib(validator=attr.validators.in_(TlsSignatureAndHashAlgorithm))
|
|
61
62
|
signature = attr.ib(
|
|
@@ -106,7 +107,21 @@ class SignedCertificateTimestampList(VectorParsable):
|
|
|
106
107
|
)
|
|
107
108
|
|
|
108
109
|
|
|
110
|
+
@attr.s(frozen=True)
|
|
111
|
+
class TlsJA4XFingerprint(Serializable):
|
|
112
|
+
fingerprint = attr.ib(validator=attr.validators.instance_of(str))
|
|
113
|
+
fingerprint_raw = attr.ib(validator=attr.validators.instance_of(str))
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@attr.s(eq=False, init=False, frozen=True)
|
|
109
117
|
class PublicKeyX509(PublicKeyX509Base):
|
|
118
|
+
ja4x = attr.ib(init=False, default=None, metadata={'human_readable_name': 'JA4X'})
|
|
119
|
+
|
|
120
|
+
def __init__(self, certificate):
|
|
121
|
+
super().__init__(certificate)
|
|
122
|
+
|
|
123
|
+
object.__setattr__(self, 'ja4x', self._ja4x())
|
|
124
|
+
|
|
110
125
|
@property
|
|
111
126
|
def signed_certificate_timestamps(self):
|
|
112
127
|
for extension in self._certificate['tbs_certificate']['extensions']:
|
|
@@ -116,9 +131,51 @@ class PublicKeyX509(PublicKeyX509Base):
|
|
|
116
131
|
|
|
117
132
|
return SignedCertificateTimestampList([])
|
|
118
133
|
|
|
134
|
+
@staticmethod
|
|
135
|
+
def _ja4x_sha256(oid_hexes):
|
|
136
|
+
composer = ComposerText()
|
|
137
|
+
composer.compose_string_array(oid_hexes, ',')
|
|
138
|
+
return hashlib.sha256(composer.composed).hexdigest()[:12]
|
|
139
|
+
|
|
140
|
+
@staticmethod
|
|
141
|
+
def _ja4x_relative_distinguished_name_oid_hexes(name):
|
|
142
|
+
return [
|
|
143
|
+
attribute['type'].contents.hex()
|
|
144
|
+
for relative_distinguished_name in name.chosen
|
|
145
|
+
for attribute in relative_distinguished_name
|
|
146
|
+
]
|
|
147
|
+
|
|
148
|
+
def _ja4x(self):
|
|
149
|
+
tbs_certificate = self._certificate['tbs_certificate']
|
|
150
|
+
issuer_oid_hexes = self._ja4x_relative_distinguished_name_oid_hexes(tbs_certificate['issuer'])
|
|
151
|
+
subject_oid_hexes = self._ja4x_relative_distinguished_name_oid_hexes(tbs_certificate['subject'])
|
|
152
|
+
extension_oid_hexes = [
|
|
153
|
+
extension['extn_id'].contents.hex()
|
|
154
|
+
for extension in tbs_certificate['extensions']
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
fingerprint_composer = ComposerText()
|
|
158
|
+
fingerprint_composer.compose_string_array([
|
|
159
|
+
self._ja4x_sha256(issuer_oid_hexes),
|
|
160
|
+
self._ja4x_sha256(subject_oid_hexes),
|
|
161
|
+
self._ja4x_sha256(extension_oid_hexes),
|
|
162
|
+
], '_')
|
|
163
|
+
|
|
164
|
+
raw_composer = ComposerText()
|
|
165
|
+
raw_composer.compose_string_array(issuer_oid_hexes, ',')
|
|
166
|
+
for oid_hexes in (subject_oid_hexes, extension_oid_hexes):
|
|
167
|
+
raw_composer.compose_separator('_')
|
|
168
|
+
raw_composer.compose_string_array(oid_hexes, ',')
|
|
169
|
+
|
|
170
|
+
return TlsJA4XFingerprint(
|
|
171
|
+
fingerprint=fingerprint_composer.composed.decode('ascii'),
|
|
172
|
+
fingerprint_raw=raw_composer.composed.decode('ascii'),
|
|
173
|
+
)
|
|
174
|
+
|
|
119
175
|
def _asdict(self):
|
|
120
176
|
dict_value = super()._asdict()
|
|
121
177
|
|
|
122
178
|
return collections.OrderedDict(list(dict_value.items()) + [
|
|
179
|
+
('ja4x', self.ja4x),
|
|
123
180
|
('signed_certificate_timestamps', self.signed_certificate_timestamps),
|
|
124
181
|
])
|
|
@@ -18,7 +18,13 @@ from cryptodatahub.common.key import (
|
|
|
18
18
|
PublicKeyParamsRsa,
|
|
19
19
|
)
|
|
20
20
|
|
|
21
|
-
from cryptodatahub.dnsrec.algorithm import
|
|
21
|
+
from cryptodatahub.dnsrec.algorithm import (
|
|
22
|
+
DnsRrType,
|
|
23
|
+
DnsSecAlgorithm,
|
|
24
|
+
DnsSecDigestType,
|
|
25
|
+
SshFpAlgorithm,
|
|
26
|
+
SshFpFingerprintType,
|
|
27
|
+
)
|
|
22
28
|
|
|
23
29
|
from cryptoparser.common.base import NumericRangeParsableBase, OneByteEnumParsable, Serializable, TwoByteEnumParsable
|
|
24
30
|
from cryptoparser.common.exception import NotEnoughData
|
|
@@ -478,6 +484,56 @@ class DnsRecordMx(ParsableBase):
|
|
|
478
484
|
return composer.composed_bytes
|
|
479
485
|
|
|
480
486
|
|
|
487
|
+
class SshFpAlgorithmFactory(OneByteEnumParsable):
|
|
488
|
+
@classmethod
|
|
489
|
+
def get_enum_class(cls):
|
|
490
|
+
return SshFpAlgorithm
|
|
491
|
+
|
|
492
|
+
@abc.abstractmethod
|
|
493
|
+
def compose(self):
|
|
494
|
+
raise NotImplementedError()
|
|
495
|
+
|
|
496
|
+
|
|
497
|
+
class SshFpFingerprintTypeFactory(OneByteEnumParsable):
|
|
498
|
+
@classmethod
|
|
499
|
+
def get_enum_class(cls):
|
|
500
|
+
return SshFpFingerprintType
|
|
501
|
+
|
|
502
|
+
@abc.abstractmethod
|
|
503
|
+
def compose(self):
|
|
504
|
+
raise NotImplementedError()
|
|
505
|
+
|
|
506
|
+
|
|
507
|
+
@attr.s
|
|
508
|
+
class DnsRecordSshfp(ParsableBase, Serializable):
|
|
509
|
+
HEADER_SIZE = 2
|
|
510
|
+
|
|
511
|
+
algorithm = attr.ib(validator=attr.validators.instance_of(SshFpAlgorithm))
|
|
512
|
+
fingerprint_type = attr.ib(validator=attr.validators.instance_of(SshFpFingerprintType))
|
|
513
|
+
fingerprint = attr.ib(validator=attr.validators.instance_of((bytes, bytearray)))
|
|
514
|
+
|
|
515
|
+
@classmethod
|
|
516
|
+
def _parse(cls, parsable):
|
|
517
|
+
if len(parsable) < cls.HEADER_SIZE:
|
|
518
|
+
raise NotEnoughData(cls.HEADER_SIZE - len(parsable))
|
|
519
|
+
|
|
520
|
+
parser = ParserBinary(parsable)
|
|
521
|
+
parser.parse_parsable('algorithm', SshFpAlgorithmFactory)
|
|
522
|
+
parser.parse_parsable('fingerprint_type', SshFpFingerprintTypeFactory)
|
|
523
|
+
parser.parse_raw('fingerprint', parser.unparsed_length)
|
|
524
|
+
|
|
525
|
+
return cls(**parser), parser.parsed_length
|
|
526
|
+
|
|
527
|
+
def compose(self):
|
|
528
|
+
composer = ComposerBinary()
|
|
529
|
+
|
|
530
|
+
composer.compose_numeric_enum_coded(self.algorithm)
|
|
531
|
+
composer.compose_numeric_enum_coded(self.fingerprint_type)
|
|
532
|
+
composer.compose_raw(self.fingerprint)
|
|
533
|
+
|
|
534
|
+
return composer.composed_bytes
|
|
535
|
+
|
|
536
|
+
|
|
481
537
|
@attr.s
|
|
482
538
|
class DnsRecordTxt(ParsableBase):
|
|
483
539
|
HEADER_SIZE = 1
|
|
@@ -347,7 +347,8 @@ class TransformAttributes(Transform):
|
|
|
347
347
|
@classmethod
|
|
348
348
|
def _parse(cls, parsable):
|
|
349
349
|
header_parser = cls._parse_header(parsable)
|
|
350
|
-
|
|
350
|
+
attributes_size = header_parser['transform_length'] - cls.HEADER_SIZE
|
|
351
|
+
attributes, attributes_length = cls._parse_attributes(header_parser.unparsed[:attributes_size])
|
|
351
352
|
|
|
352
353
|
transform = cls(
|
|
353
354
|
transform_id=header_parser['transform_id'],
|
|
@@ -408,7 +409,10 @@ class Ikev2TransformDhGroup(TransformNoAttributes):
|
|
|
408
409
|
class Ikev2TransformEncryptionAlgorithm(TransformAttributes):
|
|
409
410
|
"""Transform payload parser for encryption algorithm."""
|
|
410
411
|
|
|
411
|
-
key_length: int = attr.ib(
|
|
412
|
+
key_length: typing.Optional[int] = attr.ib(
|
|
413
|
+
default=None,
|
|
414
|
+
validator=attr.validators.optional(attr.validators.instance_of(int)),
|
|
415
|
+
)
|
|
412
416
|
|
|
413
417
|
@classmethod
|
|
414
418
|
def get_transform_type(cls):
|
|
@@ -420,6 +424,8 @@ class Ikev2TransformEncryptionAlgorithm(TransformAttributes):
|
|
|
420
424
|
|
|
421
425
|
@classmethod
|
|
422
426
|
def _parse_attributes(cls, parsable):
|
|
427
|
+
if not parsable:
|
|
428
|
+
return {'key_length': None}, 0
|
|
423
429
|
parser = ParserBinary(parsable)
|
|
424
430
|
|
|
425
431
|
parser.parse_parsable('key_length', TransformAttributeKeyLength)
|
|
@@ -427,6 +433,8 @@ class Ikev2TransformEncryptionAlgorithm(TransformAttributes):
|
|
|
427
433
|
return {'key_length': parser['key_length'].value}, parser.parsed_length
|
|
428
434
|
|
|
429
435
|
def _get_attributes(self):
|
|
436
|
+
if self.key_length is None:
|
|
437
|
+
return []
|
|
430
438
|
return [TransformAttributeKeyLength(value=self.key_length)]
|
|
431
439
|
|
|
432
440
|
|
|
@@ -7,11 +7,11 @@ import typing
|
|
|
7
7
|
import attr
|
|
8
8
|
|
|
9
9
|
from cryptodatahub.ike.algorithm import Ikev1PayloadType, Ikev2PayloadType, Ikev1ExchangeType, Ikev2ExchangeType
|
|
10
|
+
from cryptodatahub.ike.version import IkeVersion
|
|
10
11
|
|
|
11
12
|
from cryptoparser.common.parse import ParsableBase, ParserBinary, ComposerBinary
|
|
12
13
|
from cryptoparser.common.exception import NotEnoughData, InvalidType
|
|
13
|
-
|
|
14
|
-
from cryptoparser.ike.version import IsakmpProtocolVersion, IsakmpVersion
|
|
14
|
+
from cryptoparser.ike.version import IsakmpProtocolVersion
|
|
15
15
|
from cryptoparser.ike.ikev1 import (
|
|
16
16
|
Ikev1PayloadBase,
|
|
17
17
|
IKEV1_PAYLOAD_CLASSES_BY_TYPE,
|
|
@@ -112,10 +112,10 @@ class IsakmpMessage(ParsableBase):
|
|
|
112
112
|
parser.parse_numeric('responder_spi', 8)
|
|
113
113
|
parser.parse_raw('next_payload', 1)
|
|
114
114
|
parser.parse_parsable('version', IsakmpProtocolVersion)
|
|
115
|
-
if parser['version'].major ==
|
|
115
|
+
if parser['version'].major == IkeVersion.V1:
|
|
116
116
|
next_payload_type = Ikev1PayloadType
|
|
117
117
|
parser.parse_numeric_enum_coded('exchange_type', Ikev1ExchangeType)
|
|
118
|
-
elif parser['version'].major ==
|
|
118
|
+
elif parser['version'].major == IkeVersion.V2:
|
|
119
119
|
next_payload_type = Ikev2PayloadType
|
|
120
120
|
parser.parse_numeric_enum_coded('exchange_type', Ikev2ExchangeType)
|
|
121
121
|
else:
|
|
@@ -130,10 +130,10 @@ class IsakmpMessage(ParsableBase):
|
|
|
130
130
|
parser.parse_numeric('length', 4)
|
|
131
131
|
|
|
132
132
|
version = parser['version']
|
|
133
|
-
if version.major ==
|
|
133
|
+
if version.major == IkeVersion.V1:
|
|
134
134
|
payload_classes = IKEV1_PAYLOAD_CLASSES_BY_TYPE
|
|
135
135
|
payload_none = Ikev1PayloadType.NONE
|
|
136
|
-
elif version.major ==
|
|
136
|
+
elif version.major == IkeVersion.V2:
|
|
137
137
|
payload_classes = IKEV2_PAYLOAD_CLASSES_BY_TYPE
|
|
138
138
|
payload_none = Ikev2PayloadType.NONE
|
|
139
139
|
else:
|
|
@@ -168,14 +168,14 @@ class IsakmpMessage(ParsableBase):
|
|
|
168
168
|
header_composer.compose_numeric(self.initiator_spi, 8)
|
|
169
169
|
header_composer.compose_numeric(self.responder_spi, 8)
|
|
170
170
|
|
|
171
|
-
if self.version.major ==
|
|
171
|
+
if self.version.major == IkeVersion.V1:
|
|
172
172
|
payload_none = Ikev1PayloadType.NONE
|
|
173
173
|
payload_type_security_association = (
|
|
174
174
|
self.payloads[0].get_payload_type()
|
|
175
175
|
if self.payloads
|
|
176
176
|
else payload_none
|
|
177
177
|
)
|
|
178
|
-
elif self.version.major ==
|
|
178
|
+
elif self.version.major == IkeVersion.V2:
|
|
179
179
|
payload_none = Ikev2PayloadType.NONE
|
|
180
180
|
# First payload can be a cookie notification payload
|
|
181
181
|
payload_type_security_association = self.payloads[0].get_payload_type()
|
|
@@ -2,56 +2,50 @@
|
|
|
2
2
|
"""ISAKMP version handling."""
|
|
3
3
|
|
|
4
4
|
import abc
|
|
5
|
-
import enum
|
|
6
5
|
|
|
7
6
|
import attr
|
|
8
7
|
|
|
9
8
|
from cryptodatahub.common.grade import GradeableSimple, Grade
|
|
9
|
+
from cryptodatahub.ike.version import IkeVersion
|
|
10
10
|
|
|
11
11
|
from cryptoparser.common.parse import ParsableBase, ParserBinary, ComposerBinary
|
|
12
12
|
from cryptoparser.common.exception import NotEnoughData, InvalidType
|
|
13
13
|
from cryptoparser.common.base import OneByteEnumParsable
|
|
14
14
|
|
|
15
15
|
|
|
16
|
-
class IsakmpVersion(enum.IntEnum):
|
|
17
|
-
"""ISAKMP version."""
|
|
18
|
-
V1 = 0x01 # ISAKMP v1
|
|
19
|
-
V2 = 0x02 # ISAKMP v2 (IKEv2)
|
|
20
|
-
|
|
21
|
-
@property
|
|
22
|
-
def identifier(self):
|
|
23
|
-
"""Get version identifier."""
|
|
24
|
-
return f"ikev{self.value}"
|
|
25
|
-
|
|
26
|
-
|
|
27
16
|
class IsakmpVersionFactory(OneByteEnumParsable):
|
|
28
17
|
"""ISAKMP version."""
|
|
29
18
|
@classmethod
|
|
30
19
|
def get_enum_class(cls):
|
|
31
|
-
return
|
|
20
|
+
return IkeVersion
|
|
32
21
|
|
|
33
22
|
@abc.abstractmethod
|
|
34
23
|
def compose(self):
|
|
35
24
|
raise NotImplementedError()
|
|
36
25
|
|
|
37
26
|
|
|
38
|
-
@attr.s(frozen=True)
|
|
27
|
+
@attr.s(frozen=True, order=False)
|
|
39
28
|
class IsakmpProtocolVersion(ParsableBase, GradeableSimple):
|
|
40
29
|
"""ISAKMP protocol version parser."""
|
|
41
30
|
HEADER_SIZE = 1
|
|
42
31
|
|
|
43
|
-
major
|
|
32
|
+
major = attr.ib(validator=attr.validators.instance_of(IkeVersion))
|
|
44
33
|
minor: int = attr.ib(validator=attr.validators.instance_of(int))
|
|
45
34
|
|
|
35
|
+
def __lt__(self, other):
|
|
36
|
+
if self.major.value.code != other.major.value.code:
|
|
37
|
+
return self.major.value.code < other.major.value.code
|
|
38
|
+
return self.minor < other.minor
|
|
39
|
+
|
|
46
40
|
@property
|
|
47
41
|
def grade(self):
|
|
48
|
-
if self.major ==
|
|
42
|
+
if self.major == IkeVersion.V1:
|
|
49
43
|
return Grade.DEPRECATED
|
|
50
44
|
|
|
51
45
|
return Grade.SECURE
|
|
52
46
|
|
|
53
47
|
def __str__(self):
|
|
54
|
-
return f"IKEv{self.major.value} ({self.minor})"
|
|
48
|
+
return f"IKEv{self.major.value.code} ({self.minor})"
|
|
55
49
|
|
|
56
50
|
@classmethod
|
|
57
51
|
def _parse(cls, parsable):
|
|
@@ -64,18 +58,18 @@ class IsakmpProtocolVersion(ParsableBase, GradeableSimple):
|
|
|
64
58
|
major_version = (parser['version'] >> 4) & 0x0f
|
|
65
59
|
minor_version = parser['version'] & 0x0f
|
|
66
60
|
|
|
67
|
-
if major_version not in [v.value for v in
|
|
61
|
+
if major_version not in [v.value.code for v in IkeVersion]:
|
|
68
62
|
raise InvalidType()
|
|
69
63
|
|
|
70
64
|
return cls(
|
|
71
|
-
major=
|
|
65
|
+
major=next(v for v in IkeVersion if v.value.code == major_version),
|
|
72
66
|
minor=minor_version,
|
|
73
67
|
), parser.parsed_length
|
|
74
68
|
|
|
75
69
|
def compose(self):
|
|
76
70
|
composer = ComposerBinary()
|
|
77
71
|
|
|
78
|
-
version = (self.major.value << 4) | self.minor
|
|
72
|
+
version = (self.major.value.code << 4) | self.minor
|
|
79
73
|
composer.compose_numeric(version, 1)
|
|
80
74
|
|
|
81
75
|
return composer.composed_bytes
|
|
@@ -83,4 +77,4 @@ class IsakmpProtocolVersion(ParsableBase, GradeableSimple):
|
|
|
83
77
|
@property
|
|
84
78
|
def version(self):
|
|
85
79
|
"""Get version as string."""
|
|
86
|
-
return f"{self.major.value}.{self.minor}"
|
|
80
|
+
return f"{self.major.value.code}.{self.minor}"
|