dissect.target 3.19.dev11__py3-none-any.whl → 3.19.dev12__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -7,7 +7,6 @@ from typing import Iterator
7
7
  from dissect.target import Target
8
8
  from dissect.target.exceptions import UnsupportedPluginError
9
9
  from dissect.target.helpers.fsutil import TargetPath
10
- from dissect.target.helpers.ssh import SSHPrivateKey
11
10
  from dissect.target.plugin import export
12
11
  from dissect.target.plugins.apps.ssh.ssh import (
13
12
  AuthorizedKeysRecord,
@@ -15,6 +14,7 @@ from dissect.target.plugins.apps.ssh.ssh import (
15
14
  PrivateKeyRecord,
16
15
  PublicKeyRecord,
17
16
  SSHPlugin,
17
+ SSHPrivateKey,
18
18
  calculate_fingerprints,
19
19
  )
20
20
 
@@ -1,10 +1,55 @@
1
1
  import base64
2
+ import binascii
2
3
  from hashlib import md5, sha1, sha256
3
4
 
5
+ from dissect.cstruct import cstruct
6
+
4
7
  from dissect.target.helpers.descriptor_extensions import UserRecordDescriptorExtension
5
8
  from dissect.target.helpers.record import create_extended_descriptor
6
9
  from dissect.target.plugin import NamespacePlugin
7
10
 
11
+ rfc4716_def = """
12
+ struct ssh_string {
13
+ uint32 length;
14
+ char value[length];
15
+ }
16
+
17
+ struct ssh_private_key {
18
+ char magic[15];
19
+
20
+ ssh_string cipher;
21
+ ssh_string kdf_name;
22
+ ssh_string kdf_options;
23
+
24
+ uint32 number_of_keys;
25
+
26
+ ssh_string public;
27
+ ssh_string private;
28
+ }
29
+ """
30
+
31
+ c_rfc4716 = cstruct(endian=">").load(rfc4716_def)
32
+
33
+ RFC4716_MARKER_START = b"-----BEGIN OPENSSH PRIVATE KEY-----"
34
+ RFC4716_MARKER_END = b"-----END OPENSSH PRIVATE KEY-----"
35
+ RFC4716_MAGIC = b"openssh-key-v1\x00"
36
+ RFC4716_PADDING = b"\x01\x02\x03\x04\x05\x06\x07"
37
+ RFC4716_NONE = b"none"
38
+
39
+ PKCS8_MARKER_START = b"-----BEGIN PRIVATE KEY-----"
40
+ PKCS8_MARKER_END = b"-----END PRIVATE KEY-----"
41
+ PKCS8_MARKER_START_ENCRYPTED = b"-----BEGIN ENCRYPTED PRIVATE KEY-----"
42
+ PKCS8_MARKER_END_ENCRYPTED = b"-----END ENCRYPTED PRIVATE KEY-----"
43
+
44
+ PEM_MARKER_START_RSA = b"-----BEGIN RSA PRIVATE KEY-----"
45
+ PEM_MARKER_END_RSA = b"-----END RSA PRIVATE KEY-----"
46
+ PEM_MARKER_START_DSA = b"-----BEGIN DSA PRIVATE KEY-----"
47
+ PEM_MARKER_END_DSA = b"-----END DSA PRIVATE KEY-----"
48
+ PEM_MARKER_START_EC = b"-----BEGIN EC PRIVATE KEY-----"
49
+ PEM_MARKER_END_EC = b"-----END EC PRIVATE KEY-----"
50
+ PEM_ENCRYPTED = b"ENCRYPTED"
51
+
52
+
8
53
  OpenSSHUserRecordDescriptor = create_extended_descriptor([UserRecordDescriptorExtension])
9
54
 
10
55
  COMMON_ELLEMENTS = [
@@ -96,3 +141,135 @@ def calculate_fingerprints(public_key_decoded: bytes, ssh_keygen_format: bool =
96
141
  fingerprint_sha256 = digest_sha256.hex()
97
142
 
98
143
  return digest_md5.hex(), fingerprint_sha1, fingerprint_sha256
144
+
145
+
146
+ def is_rfc4716(data: bytes) -> bool:
147
+ """Validate data is a valid looking SSH private key in the OpenSSH format."""
148
+ return data.startswith(RFC4716_MARKER_START) and data.endswith(RFC4716_MARKER_END)
149
+
150
+
151
+ def decode_rfc4716(data: bytes) -> bytes:
152
+ """Base64 decode the private key data."""
153
+ encoded_key_data = data.removeprefix(RFC4716_MARKER_START).removesuffix(RFC4716_MARKER_END)
154
+ try:
155
+ return base64.b64decode(encoded_key_data)
156
+ except binascii.Error:
157
+ raise ValueError("Error decoding RFC4716 key data")
158
+
159
+
160
+ def is_pkcs8(data: bytes) -> bool:
161
+ """Validate data is a valid looking PKCS8 SSH private key."""
162
+ return (data.startswith(PKCS8_MARKER_START) and data.endswith(PKCS8_MARKER_END)) or (
163
+ data.startswith(PKCS8_MARKER_START_ENCRYPTED) and data.endswith(PKCS8_MARKER_END_ENCRYPTED)
164
+ )
165
+
166
+
167
+ def is_pem(data: bytes) -> bool:
168
+ """Validate data is a valid looking PEM SSH private key."""
169
+ return (
170
+ (data.startswith(PEM_MARKER_START_RSA) and data.endswith(PEM_MARKER_END_RSA))
171
+ or (data.startswith(PEM_MARKER_START_DSA) and data.endswith(PEM_MARKER_END_DSA))
172
+ or (data.startswith(PEM_MARKER_START_EC) and data.endswith(PEM_MARKER_END_EC))
173
+ )
174
+
175
+
176
+ class SSHPrivateKey:
177
+ """A class to parse (OpenSSH-supported) SSH private keys.
178
+
179
+ OpenSSH supports three types of keys:
180
+ * RFC4716 (default)
181
+ * PKCS8
182
+ * PEM
183
+ """
184
+
185
+ def __init__(self, data: bytes):
186
+ self.key_type = None
187
+ self.public_key = None
188
+ self.comment = ""
189
+
190
+ if is_rfc4716(data):
191
+ self.format = "RFC4716"
192
+ self._parse_rfc4716(data)
193
+
194
+ elif is_pkcs8(data):
195
+ self.format = "PKCS8"
196
+ self.is_encrypted = data.startswith(PKCS8_MARKER_START_ENCRYPTED)
197
+
198
+ elif is_pem(data):
199
+ self.format = "PEM"
200
+ self._parse_pem(data)
201
+
202
+ else:
203
+ raise ValueError("Unsupported private key format")
204
+
205
+ def _parse_rfc4716(self, data: bytes) -> None:
206
+ """Parse OpenSSH format SSH private keys.
207
+
208
+ The format:
209
+ "openssh-key-v1"0x00 # NULL-terminated "Auth Magic" string
210
+ 32-bit length, "none" # ciphername length and string
211
+ 32-bit length, "none" # kdfname length and string
212
+ 32-bit length, nil # kdf (0 length, no kdf)
213
+ 32-bit 0x01 # number of keys, hard-coded to 1 (no length)
214
+ 32-bit length, sshpub # public key in ssh format
215
+ 32-bit length, keytype
216
+ 32-bit length, pub0
217
+ 32-bit length, pub1
218
+ 32-bit length for rnd+prv+comment+pad
219
+ 64-bit dummy checksum? # a random 32-bit int, repeated
220
+ 32-bit length, keytype # the private key (including public)
221
+ 32-bit length, pub0 # Public Key parts
222
+ 32-bit length, pub1
223
+ 32-bit length, prv0 # Private Key parts
224
+ ... # (number varies by type)
225
+ 32-bit length, comment # comment string
226
+ padding bytes 0x010203 # pad to blocksize (see notes below)
227
+
228
+ Source: https://coolaj86.com/articles/the-openssh-private-key-format/
229
+ """
230
+
231
+ key_data = decode_rfc4716(data)
232
+ private_key = c_rfc4716.ssh_private_key(key_data)
233
+
234
+ # RFC4716 only supports 1 key at the moment.
235
+ if private_key.magic != RFC4716_MAGIC or private_key.number_of_keys != 1:
236
+ raise ValueError("Unexpected number of keys for RFC4716 format private key")
237
+
238
+ self.is_encrypted = private_key.cipher.value != RFC4716_NONE
239
+
240
+ self.public_key = base64.b64encode(private_key.public.value)
241
+ public_key_type = c_rfc4716.ssh_string(private_key.public.value)
242
+ self.key_type = public_key_type.value
243
+
244
+ if not self.is_encrypted:
245
+ private_key_data = private_key.private.value.rstrip(RFC4716_PADDING)
246
+
247
+ # We skip the two dummy uint32s at the start.
248
+ private_key_index = 8
249
+
250
+ private_key_type = c_rfc4716.ssh_string(private_key_data[private_key_index:])
251
+ private_key_index += 4 + private_key_type.length
252
+ self.key_type = private_key_type.value
253
+
254
+ private_key_fields = []
255
+ while private_key_index < len(private_key_data):
256
+ field = c_rfc4716.ssh_string(private_key_data[private_key_index:])
257
+ private_key_index += 4 + field.length
258
+ private_key_fields.append(field)
259
+
260
+ # There is always a comment present (with a length field of 0 for empty comments).
261
+ self.comment = private_key_fields[-1].value
262
+
263
+ def _parse_pem(self, data: bytes) -> None:
264
+ """Detect key type and encryption of PEM keys."""
265
+ self.is_encrypted = PEM_ENCRYPTED in data
266
+
267
+ if data.startswith(PEM_MARKER_START_RSA):
268
+ self.key_type = "ssh-rsa"
269
+
270
+ elif data.startswith(PEM_MARKER_START_DSA):
271
+ self.key_type = "ssh-dss"
272
+
273
+ # This is not a valid SSH key type, but we currently do not detect the specific ecdsa variant.
274
+ else:
275
+ self.key_type = "ecdsa"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.target
3
- Version: 3.19.dev11
3
+ Version: 3.19.dev12
4
4
  Summary: This module ties all other Dissect modules together, it provides a programming API and command line tools which allow easy access to various data sources inside disk images or file collections (a.k.a. targets)
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Affero General Public License v3
@@ -65,7 +65,6 @@ dissect/target/helpers/record.py,sha256=lWl7k2Mp9Axllm0tXzPGJx2zj2zONsyY_p5g424T
65
65
  dissect/target/helpers/record_modifier.py,sha256=3I_rC5jqvl0TsW3V8OQ6Dltz_D8J4PU1uhhzbJGKm9c,3245
66
66
  dissect/target/helpers/regutil.py,sha256=kX-sSZbW8Qkg29Dn_9zYbaQrwLumrr4Y8zJ1EhHXIAM,27337
67
67
  dissect/target/helpers/shell_folder_ids.py,sha256=Behhb8oh0kMxrEk6YYKYigCDZe8Hw5QS6iK_d2hTs2Y,24978
68
- dissect/target/helpers/ssh.py,sha256=obB7sqUH0IoUo78NAmHM8TX0pgA_4GHICZ3TA3TW_0E,6324
69
68
  dissect/target/helpers/targetd.py,sha256=ELhUulzQ4OgXgHsWhsLgM14vut8Wm6btr7qTynlwKaE,1812
70
69
  dissect/target/helpers/utils.py,sha256=r36Bn0UL0E6Z8ajmQrHzC6RyUxTRdwJ1PNsd904Lmzs,4027
71
70
  dissect/target/helpers/compat/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -135,10 +134,10 @@ dissect/target/plugins/apps/remoteaccess/teamviewer.py,sha256=SiEH36HM2NvdPuCjfL
135
134
  dissect/target/plugins/apps/shell/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
136
135
  dissect/target/plugins/apps/shell/powershell.py,sha256=biPSMRWxPI6kRqP0-75yMtrw0Ti2Bzfl_xI3xbmmF48,2641
137
136
  dissect/target/plugins/apps/ssh/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
138
- dissect/target/plugins/apps/ssh/openssh.py,sha256=yt3bX93Q9wfF25_vG9APMwfZWUUqCPyLlVJdhu20syI,7250
137
+ dissect/target/plugins/apps/ssh/openssh.py,sha256=oaJeKmTvVMo4aePo4Ep7t0ludJPNuuokGEW07w4gAvQ,7216
139
138
  dissect/target/plugins/apps/ssh/opensshd.py,sha256=DaXKdgGF3GYHHA4buEvphcm6FF4C8YFjgD96Dv6rRnM,5510
140
139
  dissect/target/plugins/apps/ssh/putty.py,sha256=EmsXr2NbOB13-EWS5AkpEPMUhOkVl6FAy8JGUiaDhxk,10133
141
- dissect/target/plugins/apps/ssh/ssh.py,sha256=tTA87u0B8yY1yVCPV0VJdRUct6ggkir_pIziP-eKnVo,3009
140
+ dissect/target/plugins/apps/ssh/ssh.py,sha256=d3U8PJbtMvOV3K0wV_9KzGt2oRs-mfNQz1_Xd6SNx0Y,9320
142
141
  dissect/target/plugins/apps/vpn/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
143
142
  dissect/target/plugins/apps/vpn/openvpn.py,sha256=d-DGINTIHP_bvv3T09ZwbezHXGctvCyAhJ482m2_-a0,7654
144
143
  dissect/target/plugins/apps/vpn/wireguard.py,sha256=SoAMED_bwWJQ3nci5qEY-qV4wJKSSDZQ8K7DoJRYq0k,6521
@@ -345,10 +344,10 @@ dissect/target/volumes/luks.py,sha256=OmCMsw6rCUXG1_plnLVLTpsvE1n_6WtoRUGQbpmu1z
345
344
  dissect/target/volumes/lvm.py,sha256=wwQVR9I3G9YzmY6UxFsH2Y4MXGBcKL9aayWGCDTiWMU,2269
346
345
  dissect/target/volumes/md.py,sha256=j1K1iKmspl0C_OJFc7-Q1BMWN2OCC5EVANIgVlJ_fIE,1673
347
346
  dissect/target/volumes/vmfs.py,sha256=-LoUbn9WNwTtLi_4K34uV_-wDw2W5hgaqxZNj4UmqAQ,1730
348
- dissect.target-3.19.dev11.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
349
- dissect.target-3.19.dev11.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
350
- dissect.target-3.19.dev11.dist-info/METADATA,sha256=K__-QEn5j-2bVryeNdtzhb6et_uhqklZ7KSWkBT33pM,12719
351
- dissect.target-3.19.dev11.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
352
- dissect.target-3.19.dev11.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
353
- dissect.target-3.19.dev11.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
354
- dissect.target-3.19.dev11.dist-info/RECORD,,
347
+ dissect.target-3.19.dev12.dist-info/COPYRIGHT,sha256=m-9ih2RVhMiXHI2bf_oNSSgHgkeIvaYRVfKTwFbnJPA,301
348
+ dissect.target-3.19.dev12.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
349
+ dissect.target-3.19.dev12.dist-info/METADATA,sha256=UR_OR6Mke9csuzDPtguApwOU8mClzRRkkWmmiqNv364,12719
350
+ dissect.target-3.19.dev12.dist-info/WHEEL,sha256=Wyh-_nZ0DJYolHNn1_hMa4lM7uDedD_RGVwbmTjyItk,91
351
+ dissect.target-3.19.dev12.dist-info/entry_points.txt,sha256=tvFPa-Ap-gakjaPwRc6Fl6mxHzxEZ_arAVU-IUYeo_s,447
352
+ dissect.target-3.19.dev12.dist-info/top_level.txt,sha256=Mn-CQzEYsAbkxrUI0TnplHuXnGVKzxpDw_po_sXpvv4,8
353
+ dissect.target-3.19.dev12.dist-info/RECORD,,
@@ -1,177 +0,0 @@
1
- import base64
2
- import binascii
3
-
4
- from dissect.cstruct import cstruct
5
-
6
- rfc4716_def = """
7
- struct ssh_string {
8
- uint32 length;
9
- char value[length];
10
- }
11
-
12
- struct ssh_private_key {
13
- char magic[15];
14
-
15
- ssh_string cipher;
16
- ssh_string kdf_name;
17
- ssh_string kdf_options;
18
-
19
- uint32 number_of_keys;
20
-
21
- ssh_string public;
22
- ssh_string private;
23
- }
24
- """
25
-
26
- c_rfc4716 = cstruct(endian=">").load(rfc4716_def)
27
-
28
- RFC4716_MARKER_START = b"-----BEGIN OPENSSH PRIVATE KEY-----"
29
- RFC4716_MARKER_END = b"-----END OPENSSH PRIVATE KEY-----"
30
- RFC4716_MAGIC = b"openssh-key-v1\x00"
31
- RFC4716_PADDING = b"\x01\x02\x03\x04\x05\x06\x07"
32
- RFC4716_NONE = b"none"
33
-
34
- PKCS8_MARKER_START = b"-----BEGIN PRIVATE KEY-----"
35
- PKCS8_MARKER_END = b"-----END PRIVATE KEY-----"
36
- PKCS8_MARKER_START_ENCRYPTED = b"-----BEGIN ENCRYPTED PRIVATE KEY-----"
37
- PKCS8_MARKER_END_ENCRYPTED = b"-----END ENCRYPTED PRIVATE KEY-----"
38
-
39
- PEM_MARKER_START_RSA = b"-----BEGIN RSA PRIVATE KEY-----"
40
- PEM_MARKER_END_RSA = b"-----END RSA PRIVATE KEY-----"
41
- PEM_MARKER_START_DSA = b"-----BEGIN DSA PRIVATE KEY-----"
42
- PEM_MARKER_END_DSA = b"-----END DSA PRIVATE KEY-----"
43
- PEM_MARKER_START_EC = b"-----BEGIN EC PRIVATE KEY-----"
44
- PEM_MARKER_END_EC = b"-----END EC PRIVATE KEY-----"
45
- PEM_ENCRYPTED = b"ENCRYPTED"
46
-
47
-
48
- class SSHPrivateKey:
49
- """A class to parse (OpenSSH-supported) SSH private keys.
50
-
51
- OpenSSH supports three types of keys:
52
- * RFC4716 (default)
53
- * PKCS8
54
- * PEM
55
- """
56
-
57
- def __init__(self, data: bytes):
58
- self.key_type = None
59
- self.public_key = None
60
- self.comment = ""
61
-
62
- if is_rfc4716(data):
63
- self.format = "RFC4716"
64
- self._parse_rfc4716(data)
65
-
66
- elif is_pkcs8(data):
67
- self.format = "PKCS8"
68
- self.is_encrypted = data.startswith(PKCS8_MARKER_START_ENCRYPTED)
69
-
70
- elif is_pem(data):
71
- self.format = "PEM"
72
- self._parse_pem(data)
73
-
74
- else:
75
- raise ValueError("Unsupported private key format")
76
-
77
- def _parse_rfc4716(self, data: bytes) -> None:
78
- """Parse OpenSSH format SSH private keys.
79
-
80
- The format:
81
- "openssh-key-v1"0x00 # NULL-terminated "Auth Magic" string
82
- 32-bit length, "none" # ciphername length and string
83
- 32-bit length, "none" # kdfname length and string
84
- 32-bit length, nil # kdf (0 length, no kdf)
85
- 32-bit 0x01 # number of keys, hard-coded to 1 (no length)
86
- 32-bit length, sshpub # public key in ssh format
87
- 32-bit length, keytype
88
- 32-bit length, pub0
89
- 32-bit length, pub1
90
- 32-bit length for rnd+prv+comment+pad
91
- 64-bit dummy checksum? # a random 32-bit int, repeated
92
- 32-bit length, keytype # the private key (including public)
93
- 32-bit length, pub0 # Public Key parts
94
- 32-bit length, pub1
95
- 32-bit length, prv0 # Private Key parts
96
- ... # (number varies by type)
97
- 32-bit length, comment # comment string
98
- padding bytes 0x010203 # pad to blocksize (see notes below)
99
-
100
- Source: https://coolaj86.com/articles/the-openssh-private-key-format/
101
- """
102
-
103
- key_data = decode_rfc4716(data)
104
- private_key = c_rfc4716.ssh_private_key(key_data)
105
-
106
- # RFC4716 only supports 1 key at the moment.
107
- if private_key.magic != RFC4716_MAGIC or private_key.number_of_keys != 1:
108
- raise ValueError("Unexpected number of keys for RFC4716 format private key")
109
-
110
- self.is_encrypted = private_key.cipher.value != RFC4716_NONE
111
-
112
- self.public_key = base64.b64encode(private_key.public.value)
113
- public_key_type = c_rfc4716.ssh_string(private_key.public.value)
114
- self.key_type = public_key_type.value
115
-
116
- if not self.is_encrypted:
117
- private_key_data = private_key.private.value.rstrip(RFC4716_PADDING)
118
-
119
- # We skip the two dummy uint32s at the start.
120
- private_key_index = 8
121
-
122
- private_key_type = c_rfc4716.ssh_string(private_key_data[private_key_index:])
123
- private_key_index += 4 + private_key_type.length
124
- self.key_type = private_key_type.value
125
-
126
- private_key_fields = []
127
- while private_key_index < len(private_key_data):
128
- field = c_rfc4716.ssh_string(private_key_data[private_key_index:])
129
- private_key_index += 4 + field.length
130
- private_key_fields.append(field)
131
-
132
- # There is always a comment present (with a length field of 0 for empty comments).
133
- self.comment = private_key_fields[-1].value
134
-
135
- def _parse_pem(self, data: bytes) -> None:
136
- """Detect key type and encryption of PEM keys."""
137
- self.is_encrypted = PEM_ENCRYPTED in data
138
-
139
- if data.startswith(PEM_MARKER_START_RSA):
140
- self.key_type = "ssh-rsa"
141
-
142
- elif data.startswith(PEM_MARKER_START_DSA):
143
- self.key_type = "ssh-dss"
144
-
145
- # This is not a valid SSH key type, but we currently do not detect the specific ecdsa variant.
146
- else:
147
- self.key_type = "ecdsa"
148
-
149
-
150
- def is_rfc4716(data: bytes) -> bool:
151
- """Validate data is a valid looking SSH private key in the OpenSSH format."""
152
- return data.startswith(RFC4716_MARKER_START) and data.endswith(RFC4716_MARKER_END)
153
-
154
-
155
- def decode_rfc4716(data: bytes) -> bytes:
156
- """Base64 decode the private key data."""
157
- encoded_key_data = data.removeprefix(RFC4716_MARKER_START).removesuffix(RFC4716_MARKER_END)
158
- try:
159
- return base64.b64decode(encoded_key_data)
160
- except binascii.Error:
161
- raise ValueError("Error decoding RFC4716 key data")
162
-
163
-
164
- def is_pkcs8(data: bytes) -> bool:
165
- """Validate data is a valid looking PKCS8 SSH private key."""
166
- return (data.startswith(PKCS8_MARKER_START) and data.endswith(PKCS8_MARKER_END)) or (
167
- data.startswith(PKCS8_MARKER_START_ENCRYPTED) and data.endswith(PKCS8_MARKER_END_ENCRYPTED)
168
- )
169
-
170
-
171
- def is_pem(data: bytes) -> bool:
172
- """Validate data is a valid looking PEM SSH private key."""
173
- return (
174
- (data.startswith(PEM_MARKER_START_RSA) and data.endswith(PEM_MARKER_END_RSA))
175
- or (data.startswith(PEM_MARKER_START_DSA) and data.endswith(PEM_MARKER_END_DSA))
176
- or (data.startswith(PEM_MARKER_START_EC) and data.endswith(PEM_MARKER_END_EC))
177
- )