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.
Files changed (129) hide show
  1. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/.gitlab-ci.yml +1 -1
  2. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/CHANGELOG.rst +35 -0
  3. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/CryptoParser.egg-info/PKG-INFO +5 -3
  4. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/CryptoParser.egg-info/SOURCES.txt +7 -0
  5. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/CryptoParser.egg-info/requires.txt +3 -1
  6. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/CryptoParser.egg-info/top_level.txt +1 -2
  7. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/PKG-INFO +5 -3
  8. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/__setup__.py +1 -1
  9. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/x509.py +59 -2
  10. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/dnsrec/record.py +57 -1
  11. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ike/ikev2.py +10 -2
  12. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ike/isakmp.py +8 -8
  13. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ike/version.py +15 -21
  14. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/subprotocol.py +279 -16
  15. cryptoparser-1.3.0/cryptoparser.spec +60 -0
  16. cryptoparser-1.3.0/debian/changelog +15 -0
  17. cryptoparser-1.3.0/debian/control +31 -0
  18. cryptoparser-1.3.0/debian/copyright +16 -0
  19. cryptoparser-1.3.0/debian/rules +7 -0
  20. cryptoparser-1.3.0/debian/source/format +1 -0
  21. cryptoparser-1.3.0/debian/watch +3 -0
  22. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/docs/features.rst +3 -0
  23. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/pyproject.toml +5 -4
  24. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_x509.py +20 -0
  25. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/dnsrec/test_record.py +56 -1
  26. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/test_ikev2_sa.py +13 -1
  27. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/test_isakmp.py +13 -12
  28. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/test_version.py +24 -17
  29. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_handshake.py +184 -3
  30. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/.coveragerc +0 -0
  31. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/.flake8 +0 -0
  32. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/.gitignore +0 -0
  33. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/.gitmodules +0 -0
  34. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/.pylintrc +0 -0
  35. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/.readthedocs.yaml +0 -0
  36. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/CONTRIBUTING.rst +0 -0
  37. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/CryptoParser.egg-info/dependency_links.txt +0 -0
  38. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/LICENSE.txt +0 -0
  39. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/MANIFEST.in +0 -0
  40. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/README.rst +0 -0
  41. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/__init__.py +0 -0
  42. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/__init__.py +0 -0
  43. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/base.py +0 -0
  44. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/classes.py +0 -0
  45. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/exception.py +0 -0
  46. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/field.py +0 -0
  47. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/parse.py +0 -0
  48. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/common/utils.py +0 -0
  49. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/dnsrec/__init__.py +0 -0
  50. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/dnsrec/txt.py +0 -0
  51. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/httpx/__init__.py +0 -0
  52. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/httpx/header.py +0 -0
  53. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/httpx/parse.py +0 -0
  54. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/httpx/version.py +0 -0
  55. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ike/__init__.py +0 -0
  56. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ike/common.py +0 -0
  57. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ike/ikev1.py +0 -0
  58. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ssh/__init__.py +0 -0
  59. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ssh/key.py +0 -0
  60. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ssh/record.py +0 -0
  61. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ssh/subprotocol.py +0 -0
  62. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/ssh/version.py +0 -0
  63. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/__init__.py +0 -0
  64. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/algorithm.py +0 -0
  65. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/ciphersuite.py +0 -0
  66. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/extension.py +0 -0
  67. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/grease.py +0 -0
  68. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/ldap.py +0 -0
  69. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/mysql.py +0 -0
  70. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/openvpn.py +0 -0
  71. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/postgresql.py +0 -0
  72. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/rdp.py +0 -0
  73. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/record.py +0 -0
  74. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/cryptoparser/tls/version.py +0 -0
  75. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/docs/.gitignore +0 -0
  76. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/docs/changelog.rst +0 -0
  77. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/docs/conf.py +0 -0
  78. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/docs/development.rst +0 -0
  79. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/docs/index.rst +0 -0
  80. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/setup.cfg +0 -0
  81. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/setup.py +0 -0
  82. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/__init__.py +0 -0
  83. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/__init__.py +0 -0
  84. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/certs/ecc256.badssl.com.pem +0 -0
  85. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/certs/rsa8192.badssl.com.pem +0 -0
  86. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/certs/rsa8192.badssl.com_certificate.crt +0 -0
  87. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/certs/rsa8192.badssl.com_root_ca.crt +0 -0
  88. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/certs/snakeoil_cert.pem +0 -0
  89. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/classes.py +0 -0
  90. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_algorithm.py +0 -0
  91. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_base.py +0 -0
  92. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_classes.py +0 -0
  93. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_exception.py +0 -0
  94. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_field.py +0 -0
  95. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_parse.py +0 -0
  96. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/common/test_utils.py +0 -0
  97. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/dnsrec/__init__.py +0 -0
  98. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/dnsrec/test_txt.py +0 -0
  99. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/httpx/__init__.py +0 -0
  100. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/httpx/classes.py +0 -0
  101. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/httpx/test_header.py +0 -0
  102. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/httpx/test_version.py +0 -0
  103. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/__init__.py +0 -0
  104. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/classes.py +0 -0
  105. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/test_ikev1_payload.py +0 -0
  106. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/test_ikev1_sa.py +0 -0
  107. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/test_ikev2_notify.py +0 -0
  108. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ike/test_ikev2_payload.py +0 -0
  109. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ssh/__init__.py +0 -0
  110. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ssh/test_ciphersuites.py +0 -0
  111. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ssh/test_key.py +0 -0
  112. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ssh/test_record.py +0 -0
  113. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ssh/test_subprotocol.py +0 -0
  114. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/ssh/test_version.py +0 -0
  115. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/__init__.py +0 -0
  116. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/classes.py +0 -0
  117. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_alert.py +0 -0
  118. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_application_data.py +0 -0
  119. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_change_cipher_spec.py +0 -0
  120. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_ciphersuite.py +0 -0
  121. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_extension.py +0 -0
  122. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_grease.py +0 -0
  123. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_ldap.py +0 -0
  124. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_mysql.py +0 -0
  125. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_openvpn.py +0 -0
  126. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_postgresql.py +0 -0
  127. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_rdp.py +0 -0
  128. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_record.py +0 -0
  129. {cryptoparser-1.2.0 → cryptoparser-1.3.0}/test/tls/test_version.py +0 -0
@@ -86,7 +86,7 @@ coveralls:
86
86
 
87
87
  obs:
88
88
  image:
89
- name: coroner/python_obs:1.0.0
89
+ name: coroner/python_obs:1.2.5
90
90
  pull_policy: always
91
91
  stage: deploy
92
92
  variables:
@@ -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.2.0
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-Expression: MPL-2.0
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.2.0
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,11 +1,13 @@
1
1
  asn1crypto
2
2
  attrs
3
- cryptodatahub==1.2.0
3
+ cryptodatahub==1.3.0
4
4
  urllib3
5
5
 
6
6
  [docs]
7
7
  sphinx
8
8
  sphinx-sitemap
9
+ docutils
9
10
 
10
11
  [tests]
11
12
  pyfakefs
13
+ coverage
@@ -1,7 +1,6 @@
1
- build
2
1
  cryptoparser
2
+ debian
3
3
  dist
4
4
  docs
5
- htmlcov
6
5
  submodules
7
6
  test
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: CryptoParser
3
- Version: 1.2.0
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-Expression: MPL-2.0
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.2.0
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.deep_iterable(member_validator=attr.validators.instance_of(CtExtensions))
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 DnsRrType, DnsSecAlgorithm, DnsSecDigestType
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
- attributes, attributes_length = cls._parse_attributes(header_parser.unparsed)
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(validator=attr.validators.instance_of(int))
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 == IsakmpVersion.V1:
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 == IsakmpVersion.V2:
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 == IsakmpVersion.V1:
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 == IsakmpVersion.V2:
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 == IsakmpVersion.V1:
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 == IsakmpVersion.V2:
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 IsakmpVersion
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: IsakmpVersion = attr.ib(validator=attr.validators.instance_of(IsakmpVersion))
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 == IsakmpVersion.V1:
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 IsakmpVersion]:
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=IsakmpVersion(major_version),
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}"