stidantic 0.1.3__py3-none-any.whl

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.

Potentially problematic release.


This version of stidantic might be problematic. Click here for more details.

stidantic/sco.py ADDED
@@ -0,0 +1,1526 @@
1
+ import ipaddress
2
+ from datetime import datetime
3
+ from annotated_types import Ge, Le
4
+ from typing_extensions import Annotated, TypedDict
5
+ from typing import Any, Literal, Self
6
+
7
+ from pydantic.functional_serializers import SerializeAsAny
8
+ from pydantic.functional_validators import model_validator
9
+ from pydantic.types import JsonValue
10
+ from pydantic import Field
11
+
12
+ from stidantic.types import (
13
+ Extension,
14
+ StixCore,
15
+ StixObservable,
16
+ StixBinary,
17
+ StixUrl,
18
+ Hashes,
19
+ Identifier,
20
+ )
21
+ from stidantic.vocab import (
22
+ EncryptionAlgorithm,
23
+ NetworkSocketAddressFamily,
24
+ NetworkSocketType,
25
+ WindowsIntegrityLevel,
26
+ WindowsServiceStartType,
27
+ WindowsServiceStatus,
28
+ WindowsServiceType,
29
+ WindowsRegistryDatatype,
30
+ )
31
+
32
+
33
+ # 6.1 Artifact Object
34
+ class Artifact(StixObservable):
35
+ """
36
+ The Artifact Object permits capturing an array of bytes (8-bits),
37
+ as a base64-encoded string string, or linking to a file-like payload.
38
+
39
+ It is incumbent on sharing communities to ensure that the URL is accessible for downstream consumers.
40
+ """
41
+
42
+ type: Literal["artifact"] = "artifact" # pyright: ignore[reportIncompatibleVariableOverride]
43
+ # Whenever feasible, this value SHOULD be one of the values defined in the Template column in the IANA media type
44
+ # registry [Media Types]. Maintaining a comprehensive universal catalog of all extant file types is obviously
45
+ # not possible. When specifying a MIME Type not included in the IANA registry, implementers should use their best
46
+ # judgement so as to facilitate interoperability.
47
+ mime_type: str | None = None
48
+ # Specifies the binary data contained in the artifact as a base64-encoded string.
49
+ payload_bin: StixBinary | None = None
50
+ # The value of this property MUST be a valid URL that resolves to the unencoded content.
51
+ url: StixUrl | None = None
52
+ # Specifies a dictionary of hashes for the contents of the url or the payload_bin.
53
+ hashes: Hashes | None = None
54
+ # If the artifact is encrypted, specifies the type of encryption algorithm the binary data
55
+ # (either via payload_bin or url) is encoded in.
56
+ # If both mime_type and encryption_algorithm are included, this signifies that the artifact represents an
57
+ # encrypted archive.
58
+ encryption_algorithm: EncryptionAlgorithm | None = None
59
+ # Specifies the decryption key for the encrypted binary data (either via payload_bin or url). For example,
60
+ # this may be useful in cases of sharing malware samples, which are often encoded in an encrypted archive.
61
+ decryption_key: str | None = None
62
+
63
+ @model_validator(mode="after")
64
+ def at_least_one_of(self) -> Self:
65
+ """
66
+ One of payload_bin or url MUST be provided.
67
+ """
68
+ if self.payload_bin or self.hashes:
69
+ return self
70
+ raise ValueError("Missing at least hashes or payload_bin property.")
71
+
72
+ @model_validator(mode="after")
73
+ def url_must_not_be_present_if_payload_bin_provided(self) -> Self:
74
+ """
75
+ The url property MUST NOT be present if payload_bin is provided.
76
+ """
77
+ if self.payload_bin and self.url:
78
+ raise ValueError(
79
+ "The url property MUST NOT be present if payload_bin is provided"
80
+ )
81
+ return self
82
+
83
+ @model_validator(mode="after")
84
+ def hashes_must_be_present_if_url_provided(self) -> Self:
85
+ """
86
+ The hashes property MUST be present when the url property is present.
87
+ """
88
+ if self.url and not self.hashes:
89
+ raise ValueError("The hashes property MUST be present if url is provided")
90
+ return self
91
+
92
+ @model_validator(mode="after")
93
+ def decryption_key_must_not_be_present_if_encryption_algorithm_absent(self) -> Self:
94
+ """
95
+ The decryption_key property MUST NOT be present when the encryption_algorithm property is absent.
96
+ """
97
+ if not self.encryption_algorithm and self.decryption_key:
98
+ raise ValueError(
99
+ "The decryption_key MUST NOT be present when the encryption_algorithm property is absent"
100
+ )
101
+ return self
102
+
103
+ class Config:
104
+ json_schema_extra: dict[str, list[str]] = {
105
+ "id_contributing_properties": ["hashes", "payload_bin"]
106
+ }
107
+
108
+
109
+ # 6.2 Autonomous System
110
+ class AutonomousSystem(StixObservable):
111
+ """
112
+ The AS object represents the properties of an Autonomous Systems (AS).
113
+ """
114
+
115
+ type: Literal["autonomous-system"] = "autonomous-system" # pyright: ignore[reportIncompatibleVariableOverride]
116
+ # Specifies the number assigned to the AS. Such assignments a
117
+ # re typically performed by a Regional Internet Registry (RIR).
118
+ number: int
119
+ # Specifies the name of the AS.
120
+ name: str | None = None
121
+ # Specifies the name of the Regional Internet Registry (RIR) that assigned the number to the AS.
122
+ rir: str | None = None
123
+
124
+ class Config:
125
+ json_schema_extra: dict[str, list[str]] = {
126
+ "id_contributing_properties": ["number"]
127
+ }
128
+
129
+
130
+ # 6.3 Directory Object
131
+ class Directory(StixObservable):
132
+ """
133
+ The Directory object represents the properties common to a file system directory.
134
+ """
135
+
136
+ type: Literal["directory"] = "directory" # pyright: ignore[reportIncompatibleVariableOverride]
137
+ # Specifies the path, as originally observed, to the directory on the file system.
138
+ path: str
139
+ # Specifies the observed encoding for the path. The value MUST be specified if the path is stored in a
140
+ # non-Unicode encoding. This value MUST be specified using the corresponding name from the 2013-12-20
141
+ # revision of the IANA character set registry [Character Sets]. If the preferred MIME name for a character
142
+ # set is defined, this value MUST be used; if it is not defined, then the Name value from the registry
143
+ # MUST be used instead.
144
+ path_enc: str | None = None
145
+ # Specifies the date/time the directory was created.
146
+ ctime: datetime | None = None
147
+ # Specifies the date/time the directory was last written to/modified.
148
+ mtime: datetime | None = None
149
+ # Specifies the date/time the directory was last accessed.
150
+ atime: datetime | None = None
151
+ # Specifies a list of references to other File and/or Directory objects contained within the directory.
152
+ # The objects referenced in this list MUST be of type file or directory.
153
+ contains_refs: list[Identifier] | None = None
154
+
155
+ class Config:
156
+ json_schema_extra: dict[str, list[str]] = {
157
+ "id_contributing_properties": ["path"]
158
+ }
159
+
160
+
161
+ # 6.4 Domain Name Object
162
+ class DomainName(StixObservable):
163
+ """
164
+ The Domain Name object represents the properties of a network domain name.
165
+ """
166
+
167
+ # NOTE: As for the validation of the value field, a complex regular expression could be used to validate the
168
+ # domain name structure. However, fully compliant regex for domain names is notoriously difficult and prone to
169
+ # errors, especially with internationalized domain names (IDNs). A more robust solution would be to use a dedicated
170
+ # library for domain name parsing and validation.
171
+
172
+ type: Literal["domain-name"] = "domain-name" # pyright: ignore[reportIncompatibleVariableOverride]
173
+ # Specifies the value of the domain name. The value of this property MUST conform to [RFC1034], and each
174
+ # domain and sub-domain contained within the domain name MUST conform to [RFC5890].
175
+ value: str
176
+ # Specifies a list of references to one or more IP addresses or domain names that the domain name resolves to.
177
+ # The objects referenced in this list MUST be of type ipv4-addr or ipv6-addr or domain-name
178
+ # (for cases such as CNAME records).
179
+ resolves_to_refs: list[Identifier] | None = None
180
+
181
+ class Config:
182
+ json_schema_extra: dict[str, list[str]] = {
183
+ "id_contributing_properties": ["value"]
184
+ }
185
+
186
+
187
+ # 6.5 Email Address Object
188
+ class EmailAddress(StixObservable):
189
+ """
190
+ The Email Address object represents a single email address.
191
+ """
192
+
193
+ type: Literal["email-addr"] = "email-addr" # pyright: ignore[reportIncompatibleVariableOverride]
194
+ # Specifies the value of the email address. This MUST NOT include the display name.
195
+ # This property corresponds to the addr-spec construction in section 3.4 of [RFC5322].
196
+ value: str
197
+ # Specifies a single email display name, i.e., the name that is displayed to the human user of a mail application.
198
+ # This property corresponds to the display-name construction in section 3.4 of [RFC5322].
199
+ display_name: str | None = None
200
+ # Specifies the user account that the email address belongs to, as a reference to a User Account object.
201
+ # The object referenced in this property MUST be of type user-account.
202
+ belongs_to_ref: Identifier | None = None
203
+
204
+ class Config:
205
+ json_schema_extra: dict[str, list[str]] = {
206
+ "id_contributing_properties": ["value"]
207
+ }
208
+
209
+
210
+ # 6.6.2 Email MIME Component Type
211
+ class EmailMimeComponent(StixCore):
212
+ """
213
+ Specifies one component of a multi-part email body.
214
+
215
+ There is no property to capture the value of the "Content-Transfer-Encoding" header field, since the body MUST be
216
+ decoded before being represented in the body property.
217
+ """
218
+
219
+ # Specifies the contents of the MIME part if the content_type is not provided or starts with text/
220
+ # (e.g., in the case of plain text or HTML email).
221
+ # For inclusion in this property, the contents MUST be decoded to Unicode. Note that the charset provided in
222
+ # content_type is for informational usage and not for decoding of this property.
223
+ body: str | None = None
224
+ # Specifies the contents of non-textual MIME parts, that is those whose content_type does not start with text/,
225
+ # as a reference to an Artifact object or File object.
226
+ # The object referenced in this property MUST be of type artifact or file.
227
+ # For use cases where conveying the actual data contained in the MIME part is of primary importance,
228
+ # artifact SHOULD be used. Otherwise, for use cases where conveying metadata about the file-like properties of the
229
+ # MIME part is of primary importance, file SHOULD be used.
230
+ body_raw_ref: Identifier | None = None
231
+ # Specifies the value of the "Content-Type" header field of the MIME part.
232
+ # Any additional "Content-Type" header field parameters such as charset SHOULD be included in this property.
233
+ content_type: str | None = None
234
+ # Specifies the value of the "Content-Disposition" header field of the MIME part.
235
+ content_disposition: str | None = None
236
+
237
+ @model_validator(mode="after")
238
+ def validate_body_or_body_raw_ref(self) -> Self:
239
+ """
240
+ One of body OR body_raw_ref MUST be included.
241
+ """
242
+ if self.body is None and self.body_raw_ref is None:
243
+ raise ValueError("One of body or body_raw_ref MUST be included")
244
+ return self
245
+
246
+
247
+ # 6.6 Email Message Object
248
+ class EmailMessage(StixObservable):
249
+ """
250
+ The Email Message object represents an instance of an email message, corresponding to the internet message format
251
+ described in [RFC5322] and related RFCs.
252
+
253
+ Header field values that have been encoded as described in section 2 of [RFC2047] MUST be decoded before inclusion
254
+ in Email Message object properties. For example, this is some text MUST be used instead of
255
+ =?iso-8859-1?q?this=20is=20some=20text?=. Any characters in the encoded value which cannot be decoded into Unicode
256
+ SHOULD be replaced with the 'REPLACEMENT CHARACTER' (U+FFFD). If it is necessary to capture the header value as
257
+ observed, this can be achieved by referencing an Artifact object through the raw_email_ref property.
258
+ """
259
+
260
+ type: Literal["email-message"] = "email-message" # pyright: ignore[reportIncompatibleVariableOverride]
261
+ # Indicates whether the email body contains multiple MIME parts.
262
+ is_multipart: bool
263
+ # Specifies the date/time that the email message was sent.
264
+ date: datetime | None = None
265
+ # Specifies the value of the "Content-Type" header of the email message.
266
+ content_type: str | None = None
267
+ # Specifies the value of the "From:" header of the email message.
268
+ # The "From:" field specifies the author of the message, that is, the mailbox(es) of the person or system
269
+ # responsible for the writing of the message.
270
+ from_ref: Identifier | None = None
271
+ # Specifies the value of the "Sender" field of the email message.
272
+ # The "Sender:" field specifies the mailbox of the agent responsible for the actual transmission of the message.
273
+ sender_ref: Identifier | None = None
274
+ # Specifies the mailboxes that are "To:" recipients of the email message.
275
+ to_refs: list[Identifier] | None = None
276
+ # Specifies the mailboxes that are "CC:" recipients of the email message.
277
+ cc_refs: list[Identifier] | None = None
278
+ # Specifies the mailboxes that are "BCC:" recipients of the email message.
279
+ # As per [RFC5322], the absence of this property should not be interpreted as semantically equivalent to an absent
280
+ # BCC header on the message being characterized.
281
+ bcc_refs: list[Identifier] | None = None
282
+ # Specifies the Message-ID field of the email message.
283
+ message_id: str | None = None
284
+ # Specifies the subject of the email message.
285
+ subject: str | None = None
286
+ # Specifies one or more "Received" header fields that may be included in the email headers.
287
+ # List values MUST appear in the same order as present in the email message.
288
+ received_lines: list[str] | None = None
289
+ # Specifies any other header fields found in the email message, as a dictionary.
290
+ # Each key/value pair in the dictionary represents the name/value of a single header field or names/values of a
291
+ # header field that occurs more than once. Each dictionary key SHOULD be a case-preserved version of the header
292
+ # field name. The corresponding value for each dictionary key MUST always be a list of type string to support when
293
+ # a header field is repeated.
294
+ additional_header_fields: dict[str, list[str]] | None = None
295
+ # Specifies a string containing the email body.
296
+ body: str | None = None
297
+ # Specifies a list of the MIME parts that make up the email body.
298
+ body_multipart: list[EmailMimeComponent] | None = None
299
+ # Specifies the raw binary contents of the email message, including both the headers and body, as a reference
300
+ # to an Artifact object. The object referenced in this property MUST be of type artifact.
301
+ raw_email_ref: Identifier | None = None
302
+
303
+ @model_validator(mode="after")
304
+ def validate_body(self) -> Self:
305
+ """
306
+ The property body MUST NOT be used if is_multipart is true.
307
+ The property body_multipart MUST NOT be used if is_multipart is false.
308
+ """
309
+ if self.is_multipart and self.body is not None:
310
+ raise ValueError("body MUST NOT be used if is_multipart is true")
311
+ if not self.is_multipart and self.body_multipart is not None:
312
+ raise ValueError("body_multipart MUST NOT be used if is_multipart is false")
313
+ return self
314
+
315
+ class Config:
316
+ json_schema_extra: dict[str, list[str]] = {
317
+ "id_contributing_properties": ["from_ref", "subject", "body"]
318
+ }
319
+
320
+
321
+ # 6.7.2 Archive File Extension
322
+ class ArchiveFileExtension(Extension):
323
+ """
324
+ The Archive File extension specifies a default extension for capturing properties specific to archive files.
325
+ The key for this extension when used in the extensions dictionary MUST be archive-ext.
326
+ Note that this predefined extension does not use the extension facility described in section 7.3.
327
+ """
328
+
329
+ # This property specifies the files that are contained in the archive. It MUST contain references to one or more
330
+ # File objects. The objects referenced in this list MUST be of type file or directory.
331
+ contains_refs: list[Identifier]
332
+ # Specifies a comment included as part of the archive file.
333
+ comment: str | None = None
334
+
335
+
336
+ # 6.7.3.2 Alternate Data Stream Type
337
+ class AlternateDataStream(StixCore):
338
+ """
339
+ The Alternate Data Stream type represents an NTFS alternate data stream.
340
+ """
341
+
342
+ # Specifies the name of the alternate data stream.
343
+ name: str
344
+ # Specifies a dictionary of hashes for the data contained in the alternate data stream.
345
+ # Dictionary keys MUST come from the hash-algorithm-ov open vocabulary.
346
+ hashes: Hashes | None = None
347
+ # Specifies the size of the alternate data stream, in bytes. The value of this property MUST NOT be negative.
348
+ size: Annotated[int, Ge(0)] | None = None
349
+
350
+
351
+ # 6.7.3 NTFS File Extension
352
+ class NTFSFileExtension(Extension):
353
+ """
354
+ The NTFS File extension specifies a set of properties specific to files stored on NTFS file systems.
355
+
356
+ The key for this extension when used in the extensions dictionary MUST be ntfs-ext. Note that this predefined
357
+ extension does not use the extension facility described in section 7.3.
358
+ """
359
+
360
+ # Specifies the security ID (SID) value assigned to the file.
361
+ sid: str | None = None
362
+ # Specifies a list of NTFS alternate data streams that exist for the file.
363
+ alternate_data_streams: list[AlternateDataStream] | None = None
364
+
365
+ @model_validator(mode="after")
366
+ def at_least_one_of(self) -> Self:
367
+ """
368
+ An object using the NTFS File Extension MUST contain at least one property from this extension.
369
+ """
370
+ if self.sid is None and self.alternate_data_streams is None:
371
+ raise ValueError("At least one property must be present")
372
+ return self
373
+
374
+
375
+ # 6.7.4 PDF File Extension
376
+ class PDFFileExtension(Extension):
377
+ """
378
+ The PDF file extension specifies a default extension for capturing properties specific to PDF files.
379
+
380
+ The key for this extension when used in the extensions dictionary MUST be pdf-ext.
381
+
382
+ Note that this predefined extension does not use the extension facility described in section 7.3.
383
+ """
384
+
385
+ # Specifies the decimal version number of the string from the PDF header that specifies the version of the PDF
386
+ # specification to which the PDF file conforms. E.g 1.4
387
+ version: str | None = None
388
+ # Specifies whether the PDF file has been optimized.
389
+ is_optimized: bool | None = None
390
+ # Specifies details of the PDF document information dictionary (DID), which includes properties like the document
391
+ # creation date and producer, as a dictionary. Each key in the dictionary SHOULD be a case-preserved version of
392
+ # the corresponding entry in the document information dictionary without the prepended forward slash, e.g., Title.
393
+ # The corresponding value for the key MUST be the value specified for the document information dictionary entry,
394
+ # as a string.
395
+ document_info_dict: dict[str, str] | None = None
396
+ # Specifies the first file identifier found for the PDF file.
397
+ pdfid0: str | None = None
398
+ # Specifies the second file identifier found for the PDF file.
399
+ pdfid1: str | None = None
400
+
401
+ @model_validator(mode="after")
402
+ def at_least_one_of(self) -> Self:
403
+ """
404
+ An object using the PDF File Extension MUST contain at least one property from this extension.
405
+ """
406
+ if (
407
+ self.version is None
408
+ and self.is_optimized is None
409
+ and self.document_info_dict is None
410
+ and self.pdfid0 is None
411
+ and self.pdfid1 is None
412
+ ):
413
+ raise ValueError("At least one property must be present")
414
+ return self
415
+
416
+
417
+ # 6.7.5 Raster Image File Extension
418
+ class RasterImageFileExtension(Extension):
419
+ """
420
+ The Raster Image file extension specifies a default extension for capturing properties specific to raster image
421
+ files.
422
+
423
+ The key for this extension when used in the extensions dictionary MUST be raster-image-ext. Note that this
424
+ predefined extension does not use the extension facility described in section 7.3.
425
+ """
426
+
427
+ # Specifies the height of the image in the image file, in pixels.
428
+ image_height: int | None = None
429
+ # Specifies the width of the image in the image file, in pixels.
430
+ image_width: int | None = None
431
+ # Specifies the sum of bits used for each color channel in the image file, and thus the total number of pixels
432
+ # used for expressing the color depth of the image.
433
+ bits_per_pixel: int | None = None
434
+ # Specifies the set of EXIF tags found in the image file, as a dictionary. Each key/value pair in the dictionary
435
+ # represents the name/value of a single EXIF tag. Accordingly, each dictionary key MUST be a case-preserved
436
+ # version of the EXIF tag name, e.g., XResolution. Each dictionary value MUST be either an integer
437
+ # (for int* EXIF datatypes) or a string (for all other EXIF datatypes).
438
+ exif_tags: dict[str, int | str] | None = None
439
+
440
+ @model_validator(mode="after")
441
+ def at_least_one_of(self) -> Self:
442
+ """
443
+ An object using the Raster Image File Extension MUST contain at least one property from this extension.
444
+ """
445
+ if (
446
+ self.image_height is None
447
+ and self.image_width is None
448
+ and self.bits_per_pixel is None
449
+ and self.exif_tags is None
450
+ ):
451
+ raise ValueError("At least one property must be present")
452
+ return self
453
+
454
+
455
+ # 6.7.6.3 Windows PE Section Type
456
+ class WindowsPESection(StixCore):
457
+ """
458
+ The Windows PE Section type specifies metadata about a PE file section.
459
+ """
460
+
461
+ # Specifies the name of the section.
462
+ name: str
463
+ # Specifies the size of the section, in bytes. The value of this property MUST NOT be negative.
464
+ size: Annotated[int, Ge(0)] | None = None
465
+ # Specifies the calculated entropy for the section, as calculated using the Shannon algorithm [Shannon Entropy].
466
+ # The size of each input character is defined as a byte, resulting in a possible range of 0 through 8.
467
+ entropy: float | None = None
468
+ # Specifies any hashes computed over the section.
469
+ hashes: Hashes | None = None
470
+
471
+
472
+ # 6.7.6.2 Windows PE Optional Header Type
473
+ class WindowsPEOptionalHeader(StixCore):
474
+ """
475
+ The Windows PE Optional Header type represents the properties of the PE optional header.
476
+ An object using the Windows PE Optional Header Type MUST contain at least one property from this type.
477
+ """
478
+
479
+ # Specifies the hex value that indicates the type of the PE binary.
480
+ magic_hex: str | None = None
481
+ # Specifies the linker major version number.
482
+ major_linker_version: int | None = None
483
+ # Specifies the linker minor version number.
484
+ minor_linker_version: int | None = None
485
+ # Specifies the size of the code (text) section.
486
+ # If there are multiple such sections, this refers to the sum of the sizes of each section.
487
+ size_of_code: Annotated[int, Ge(0)] | None = None
488
+ # Specifies the size of the initialized data section.
489
+ # If there are multiple such sections, this refers to the sum of the sizes of each section.
490
+ size_of_initialized_data: Annotated[int, Ge(0)] | None = None
491
+ # Specifies the size of the uninitialized data section.
492
+ # If there are multiple such sections, this refers to the sum of the sizes of each section.
493
+ size_of_uninitialized_data: Annotated[int, Ge(0)] | None = None
494
+ # Specifies the address of the entry point relative to the image base when the executable is loaded into memory.
495
+ address_of_entry_point: int | None = None
496
+ # Specifies the address that is relative to the image base of the beginning-of-code section when it is loaded
497
+ # into memory.
498
+ base_of_code: int | None = None
499
+ # Specifies the address that is relative to the image base of the beginning-of-data section when it is loaded
500
+ # into memory.
501
+ base_of_data: int | None = None
502
+ # Specifies the preferred address of the first byte of the image when loaded into memory.
503
+ image_base: int | None = None
504
+ # Specifies the alignment (in bytes) of PE sections when they are loaded into memory.
505
+ section_alignment: int | None = None
506
+ # Specifies the factor (in bytes) that is used to align the raw data of sections in the image file.
507
+ file_alignment: int | None = None
508
+ # Specifies the major version number of the required operating system.
509
+ major_os_version: int | None = None
510
+ # Specifies the minor version number of the required operating system.
511
+ minor_os_version: int | None = None
512
+ # Specifies the major version number of the image.
513
+ major_image_version: int | None = None
514
+ # Specifies the minor version number of the image.
515
+ minor_image_version: int | None = None
516
+ # Specifies the major version number of the subsystem.
517
+ major_subsystem_version: int | None = None
518
+ # Specifies the minor version number of the subsystem.
519
+ minor_subsystem_version: int | None = None
520
+ # Specifies the reserved win32 version value.
521
+ win32_version_value_hex: str | None = None
522
+ # Specifies the size of the image in bytes, including all headers, as the image is loaded in memory.
523
+ size_of_image: Annotated[int, Ge(0)] | None = None
524
+ # Specifies the combined size of the MS-DOS, PE header, and section headers, rounded up to a multiple of the
525
+ # value specified in the file_alignment header.
526
+ size_of_headers: Annotated[int, Ge(0)] | None = None
527
+ # Specifies the checksum of the PE binary.
528
+ checksum_hex: str | None = None
529
+ # Specifies the subsystem (e.g., GUI, device driver, etc.) that is required to run this image.
530
+ subsystem_hex: str | None = None
531
+ # Specifies the flags that characterize the PE binary.
532
+ dll_characteristics_hex: str | None = None
533
+ # Specifies the size of the stack to reserve, in bytes.
534
+ size_of_stack_reserve: Annotated[int, Ge(0)] | None = None
535
+ # Specifies the size of the stack to commit, in bytes.
536
+ size_of_stack_commit: Annotated[int, Ge(0)] | None = None
537
+ # Specifies the size of the local heap space to reserve, in bytes.
538
+ size_of_heap_reserve: Annotated[int, Ge(0)] | None = None
539
+ # Specifies the size of the local heap space to commit, in bytes.
540
+ size_of_heap_commit: Annotated[int, Ge(0)] | None = None
541
+ # Specifies the reserved loader flags.
542
+ loader_flags_hex: str | None = None
543
+ # Specifies the number of data-directory entries in the remainder of the optional header.
544
+ number_of_rva_and_sizes: int | None = None
545
+ # Specifies any hashes that were computed for the optional header.
546
+ hashes: Hashes | None = None
547
+
548
+ @model_validator(mode="before")
549
+ @classmethod
550
+ def at_least_one(cls, data: Any) -> Any: # pyright: ignore[reportExplicitAny, reportAny]
551
+ """
552
+ An object using the Windows PE Optional Header Type MUST contain at least one property from this type.
553
+ """
554
+ if isinstance(data, dict):
555
+ for key, value in data.items(): # pyright: ignore[reportUnknownVariableType]
556
+ if key != "type" and value is not None:
557
+ return data # pyright: ignore[reportUnknownVariableType]
558
+ raise ValueError("At least one property must be present")
559
+ raise TypeError("Input data must be a dictionary")
560
+
561
+
562
+ # 6.7.6 Windows PE Binary File Extension
563
+ class WindowsPEBinaryExtension(Extension):
564
+ """
565
+ The Windows™ PE Binary File extension specifies a default extension for capturing properties specific to
566
+ Windows portable executable (PE) files.
567
+
568
+ The key for this extension when used in the extensions dictionary MUST be windows-pebinary-ext.
569
+ Note that this predefined extension does not use the extension facility described in section 7.3.
570
+
571
+ An object using the Windows™ PE Binary File Extension MUST contain at least one property other than the
572
+ required pe_type property from this extension.
573
+ """
574
+
575
+ # Specifies the type of the PE binary.
576
+ # This is an open vocabulary and values SHOULD come from the windows-pebinary-type-ov open vocabulary.
577
+ pe_type: str
578
+ # Specifies the special import hash, or 'imphash', calculated for the PE Binary based on its imported
579
+ # libraries and functions.
580
+ imphash: str | None = None
581
+ # Specifies the type of target machine.
582
+ machine_hex: str | None = None
583
+ # Specifies the number of sections in the PE binary, as a non-negative integer.
584
+ number_of_sections: Annotated[int, Ge(0)] | None = None
585
+ # Specifies the time when the PE binary was created.
586
+ # The timestamp value MUST be precise to the second.
587
+ time_date_stamp: datetime | None = None
588
+ # Specifies the file offset of the COFF symbol table.
589
+ pointer_to_symbol_table_hex: str | None = None
590
+ # Specifies the number of entries in the symbol table of the PE binary, as a non-negative integer.
591
+ number_of_symbols: Annotated[int, Ge(0)] | None = None
592
+ # Specifies the size of the optional header of the PE binary.
593
+ size_of_optional_header: Annotated[int, Ge(0)] | None = None
594
+ # Specifies the flags that indicate the file’s characteristics.
595
+ characteristics_hex: str | None = None
596
+ # Specifies any hashes that were computed for the file header.
597
+ file_header_hashes: Hashes | None = None
598
+ # Specifies the PE optional header of the PE binary.
599
+ optional_header: WindowsPEOptionalHeader | None = None
600
+ # Specifies metadata about the sections in the PE file.
601
+ sections: list[WindowsPESection] | None = None
602
+
603
+
604
+ FileExtensions = TypedDict(
605
+ "FileExtensions",
606
+ {
607
+ "archive-ext": ArchiveFileExtension,
608
+ "ntfs-ext": NTFSFileExtension,
609
+ "pdf-ext": PDFFileExtension,
610
+ "raster-image-ext": RasterImageFileExtension,
611
+ "windows-pebinary-ext": WindowsPEBinaryExtension,
612
+ },
613
+ total=False,
614
+ extra_items=SerializeAsAny[Extension],
615
+ )
616
+
617
+
618
+ # 6.7 File Object
619
+ class File(StixObservable):
620
+ """
621
+ The File object represents the properties of a file.
622
+ """
623
+
624
+ type: Literal["file"] = "file" # pyright: ignore[reportIncompatibleVariableOverride]
625
+ # The File object defines the following extensions. In addition to these, producers MAY create their own.
626
+ # Dictionary keys MUST use the specification defined name (examples above) or be the id of a STIX Extension object,
627
+ # depending on the type of extension being used.
628
+ # The corresponding dictionary values MUST contain the contents of the extension instance.
629
+ extensions: FileExtensions | None = None # pyright: ignore[reportIncompatibleVariableOverride]
630
+ # Specifies a dictionary of hashes for the file.
631
+ # When used with the Archive File Extension, this refers to the hash of the entire archive file, not its contents.
632
+ hashes: Hashes | None = None
633
+ # Specifies the size of the file, in bytes.
634
+ size: Annotated[int, Ge(0)] | None = None
635
+ # Specifies the name of the file.
636
+ name: str | None = None
637
+ # Specifies the observed encoding for the name of the file. This value MUST be specified using the corresponding
638
+ # name from the 2013-12-20 revision of the IANA character set registry [Character Sets]. If the value from the
639
+ # Preferred MIME Name column for a character set is defined, this value MUST be used; if it is not defined, then
640
+ # the value from the Name column in the registry MUST be used instead.
641
+ # This property allows for the capture of the original text encoding for the file name, which may be forensically
642
+ # relevant; for example, a file on an NTFS volume whose name was created using the windows-1251 encoding, commonly
643
+ # used for languages based on Cyrillic script.
644
+ name_enc: str | None = None
645
+ # Specifies the hexadecimal constant ("magic number") associated with a specific file format that corresponds
646
+ # to the file, if applicable.
647
+ magic_number_hex: str | None = None
648
+ # Specifies the MIME type name specified for the file, e.g., application/msword.
649
+ # Whenever feasible, this value SHOULD be one of the values defined in the Template column in the IANA media type
650
+ # registry [Media Types].
651
+ # Maintaining a comprehensive universal catalog of all extant file types is obviously not possible. When specifying
652
+ # a MIME Type not included in the IANA registry, implementers should use their best judgement so as to facilitate
653
+ # interoperability.
654
+ mime_type: str | None = None
655
+ # Specifies the date/time the file was created.
656
+ ctime: datetime | None = None
657
+ # Specifies the date/time the file was last written to/modified.
658
+ mtime: datetime | None = None
659
+ # Specifies the date/time the file was last accessed.
660
+ atime: datetime | None = None
661
+ # Specifies the parent directory of the file, as a reference to a Directory object.
662
+ parent_directory_ref: Identifier | None = None
663
+ # Specifies a list of references to other Cyber-observable Objects contained within the file, such as another file
664
+ # that is appended to the end of the file, or an IP address that is contained somewhere in the file.
665
+ # This is intended for use cases other than those targeted by the Archive extension.
666
+ contains_refs: list[Identifier] | None = None
667
+ # Specifies the content of the file, represented as an Artifact object.
668
+ content_ref: Identifier | None = None
669
+
670
+ @model_validator(mode="after")
671
+ def at_least_one_of(self) -> Self:
672
+ """
673
+ A File object MUST contain at least one of hashes or name.
674
+ """
675
+ if self.hashes is None and self.name is None:
676
+ raise ValueError("At least one of hashes or name must be present.")
677
+ return self
678
+
679
+ class Config:
680
+ json_schema_extra: dict[str, list[str]] = {
681
+ "id_contributing_properties": [
682
+ "hashes",
683
+ "name",
684
+ "extensions",
685
+ "parent_directory_ref",
686
+ ]
687
+ }
688
+
689
+
690
+ # 6.8 IPv4 Address Object
691
+ class IPv4Address(StixObservable):
692
+ """
693
+ The IPv4 Address object represents one or more IPv4 addresses expressed using CIDR notation.
694
+ """
695
+
696
+ type: Literal["ipv4-addr"] = "ipv4-addr" # pyright: ignore[reportIncompatibleVariableOverride]
697
+ # Specifies the values of one or more IPv4 addresses expressed using CIDR notation.
698
+ # If a given IPv4 Address object represents a single IPv4 address, the CIDR /32 suffix MAY be omitted.
699
+ value: ipaddress.IPv4Address | ipaddress.IPv4Network
700
+ # Specifies a list of references to one or more Layer 2 Media Access Control (MAC) addresses that the IPv4
701
+ # address resolves to.
702
+ resolves_to_refs: list[Identifier] | None = None
703
+ # Specifies a list of references to one or more autonomous systems (AS) that the IPv4 address belongs to.
704
+ belongs_to_refs: list[Identifier] | None = None
705
+
706
+ class Config:
707
+ json_schema_extra: dict[str, list[str]] = {
708
+ "id_contributing_properties": ["value"]
709
+ }
710
+
711
+
712
+ # 6.9 IPv6 Address Object
713
+ class IPv6Address(StixObservable):
714
+ """
715
+ The IPv6 Address object represents one or more IPv6 addresses expressed using CIDR notation.
716
+ """
717
+
718
+ type: Literal["ipv6-addr"] = "ipv6-addr" # pyright: ignore[reportIncompatibleVariableOverride]
719
+ # Specifies the values of one or more IPv6 addresses expressed using CIDR notation.
720
+ # If a given IPv6 Address object represents a single IPv6 address, the CIDR /128 suffix MAY be omitted.
721
+ value: ipaddress.IPv6Address | ipaddress.IPv6Network
722
+ # Specifies a list of references to one or more Layer 2 Media Access Control (MAC) addresses that the IPv6
723
+ # address resolves to.
724
+ resolves_to_refs: list[Identifier] | None = None
725
+ # Specifies a list of references to one or more autonomous systems (AS) that the IPv6 address belongs to.
726
+ belongs_to_refs: list[Identifier] | None = None
727
+
728
+ class Config:
729
+ json_schema_extra: dict[str, list[str]] = {
730
+ "id_contributing_properties": ["value"]
731
+ }
732
+
733
+
734
+ # 6.10 MAC Address Object
735
+ class MACAddress(StixObservable):
736
+ """
737
+ The MAC Address object represents a single Media Access Control (MAC) address.
738
+ """
739
+
740
+ type: Literal["mac-addr"] = "mac-addr" # pyright: ignore[reportIncompatibleVariableOverride]
741
+ # Specifies the value of a single MAC address.
742
+ # The MAC address value MUST be represented as a single colon-delimited, lowercase MAC-48 address, which MUST
743
+ # include leading zeros for each octet.
744
+ # TODO: check the following regex: ^([0-9a-f]{2}:){5}[0-9a-f]{2}$
745
+ value: str
746
+
747
+ class Config:
748
+ json_schema_extra: dict[str, list[str]] = {
749
+ "id_contributing_properties": ["value"]
750
+ }
751
+
752
+
753
+ # 6.11 Mutex Object
754
+ class Mutex(StixObservable):
755
+ """
756
+ The Mutex object represents the properties of a mutual exclusion (mutex) object.
757
+ """
758
+
759
+ type: Literal["mutex"] = "mutex" # pyright: ignore[reportIncompatibleVariableOverride]
760
+ # Specifies the name of the mutex object.
761
+ name: str
762
+
763
+ class Config:
764
+ json_schema_extra: dict[str, list[str]] = {
765
+ "id_contributing_properties": ["name"]
766
+ }
767
+
768
+
769
+ # 6.12.2 HTTP Request Extension
770
+ class HTTPRequestExtension(Extension):
771
+ """
772
+ The HTTP request extension specifies a default extension for capturing network traffic properties specific to
773
+ HTTP requests.
774
+
775
+ The key for this extension when used in the extensions dictionary MUST be http-request-ext.
776
+ Note that this predefined extension does not use the extension facility described in section 7.3.
777
+ The corresponding protocol value for this extension is http.
778
+ """
779
+
780
+ # Specifies the HTTP method portion of the HTTP request line, as a lowercase string.
781
+ request_method: str
782
+ # Specifies the value (typically a resource path) portion of the HTTP request line.
783
+ request_value: str
784
+ # Specifies the HTTP version portion of the HTTP request line, as a lowercase string.
785
+ request_version: str | None = None
786
+ # Specifies all of the HTTP header fields that may be found in the HTTP client request, as a dictionary.
787
+ request_header: dict[str, list[str]] | None = None
788
+ # Specifies the length of the HTTP message body, if included, in bytes.
789
+ message_body_length: int | None = None
790
+ # Specifies the data contained in the HTTP message body, if included.
791
+ message_body_data_ref: Identifier | None = None
792
+
793
+
794
+ # 6.12.3 ICMP Extension
795
+ class ICMPExtension(Extension):
796
+ """
797
+ The ICMP extension specifies a default extension for capturing network traffic properties specific to ICMP.
798
+
799
+ The key for this extension when used in the extensions dictionary MUST be icmp-ext.
800
+ Note that this predefined extension does not use the extension facility described in section 7.3.
801
+ The corresponding protocol value for this extension is icmp.
802
+ """
803
+
804
+ # Specifies the ICMP type byte.
805
+ icmp_type_hex: str
806
+ # Specifies the ICMP code byte.
807
+ icmp_code_hex: str
808
+
809
+
810
+ # 6.12.4 Network Socket Extension
811
+ class NetworkSocketExtension(Extension):
812
+ """
813
+ The Network Socket extension specifies a default extension for capturing network traffic properties associated
814
+ with network sockets.
815
+
816
+ The key for this extension when used in the extensions dictionary MUST be socket-ext.
817
+ Note that this predefined extension does not use the extension facility described in section 7.3.
818
+ """
819
+
820
+ # Specifies the address family (AF_*) that the socket is configured for.
821
+ address_family: NetworkSocketAddressFamily
822
+ # Specifies whether the socket is in blocking mode.
823
+ is_blocking: bool | None = None
824
+ # Specifies whether the socket is in listening mode.
825
+ is_listening: bool | None = None
826
+ # Specifies any options (e.g., SO_*) that may be used by the socket, as a dictionary.
827
+ # Each key in the dictionary SHOULD be a case-preserved version of the option name, e.g., SO_ACCEPTCONN.
828
+ # Each key value in the dictionary MUST be the value for the corresponding options key.
829
+ # Each dictionary value MUST be an integer. For SO_RCVTIMEO, SO_SNDTIMEO and SO_LINGER the value represents
830
+ # the number of milliseconds. If the SO_LINGER key is present, it indicates that the SO_LINGER option is active.
831
+ options: dict[str | int, int] | None = None
832
+ # Specifies the type of the socket.
833
+ socket_type: NetworkSocketType | None = None
834
+ # Specifies the socket file descriptor value associated with the socket, as a non-negative integer.
835
+ socket_descriptor: int | None = None
836
+ # Specifies the handle or inode value associated with the socket.
837
+ socket_handle: int | None = None
838
+
839
+
840
+ # 6.12.5 TCP Extension
841
+ class TCPExtension(Extension):
842
+ """
843
+ The TCP extension specifies a default extension for capturing network traffic properties specific to TCP.
844
+ An object using the TCP Extension MUST contain at least one property from this extension.
845
+ """
846
+
847
+ # Specifies the source TCP flags, as the union of all TCP flags observed between the start of the traffic
848
+ # (as defined by the start property) and the end of the traffic (as defined by the end property).
849
+ # If the start and end times of the traffic are not specified, this property SHOULD be interpreted as the union
850
+ # of all TCP flags observed over the entirety of the network traffic being reported upon.
851
+ src_flags_hex: str | None = None
852
+ # Specifies the destination TCP flags, as the union of all TCP flags observed between the start of the traffic
853
+ # (as defined by the start property) and the end of the traffic (as defined by the end property).
854
+ # If the start and end times of the traffic are not specified, this property SHOULD be interpreted as the union
855
+ # of all TCP flags observed over the entirety of the network traffic being reported upon.
856
+ dst_flags_hex: str | None = None
857
+
858
+ @model_validator(mode="after")
859
+ def at_least_one_of(self) -> Self:
860
+ """
861
+ An object using the TCP Extension MUST contain at least one property from this extension.
862
+ """
863
+ if self.src_flags_hex is None and self.dst_flags_hex is None:
864
+ raise ValueError("At least one property must be present")
865
+ return self
866
+
867
+
868
+ NetworkTrafficExtensions = TypedDict(
869
+ "NetworkTrafficExtensions",
870
+ {
871
+ "http-request-ext": HTTPRequestExtension,
872
+ "icmp-ext": ICMPExtension,
873
+ "socket-ext": NetworkSocketExtension,
874
+ "tcp-ext": TCPExtension,
875
+ },
876
+ total=False,
877
+ extra_items=SerializeAsAny[Extension],
878
+ )
879
+
880
+
881
+ # 6.12 Network Traffic Object
882
+ class NetworkTraffic(StixObservable):
883
+ """
884
+ The Network Traffic object represents arbitrary network traffic that originates from a source and is addressed to
885
+ a destination. The network traffic MAY or MAY NOT constitute a valid unicast, multicast, or broadcast network
886
+ connection. This MAY also include traffic that is not established, such as a SYN flood.
887
+
888
+ To allow for use cases where a source or destination address may be sensitive and not suitable for sharing,
889
+ such as addresses that are internal to an organization's network, the source and destination properties
890
+ (src_ref and dst_ref, respectively) are defined as optional in the properties table below.
891
+ """
892
+
893
+ type: Literal["network-traffic"] = "network-traffic" # pyright: ignore[reportIncompatibleVariableOverride]
894
+ # The Network Traffic object defines the following extensions. In addition to these, producers MAY create their own.
895
+ # Dictionary keys MUST use the specification defined name (examples above) or be the id of a STIX Extension object,
896
+ # depending on the type of extension being used.
897
+ # The corresponding dictionary values MUST contain the contents of the extension instance.
898
+ extensions: NetworkTrafficExtensions | None = None # pyright: ignore[reportIncompatibleVariableOverride]
899
+ # Specifies the date/time the network traffic was initiated, if known.
900
+ start: datetime | None = None
901
+ # Specifies the date/time the network traffic ended, if known.
902
+ end: datetime | None = None
903
+ # Indicates whether the network traffic is still ongoing.
904
+ is_active: bool | None = None
905
+ # Specifies the source of the network traffic, as a reference to a Cyber-observable Object.
906
+ # The object referenced MUST be of type ipv4-addr, ipv6-addr, mac-addr, or domain-name
907
+ # (for cases where the IP address for a domain name is unknown).
908
+ src_ref: Identifier | None = None
909
+ # Specifies the destination of the network traffic, as a reference to a Cyber-observable Object.
910
+ # The object referenced MUST be of type ipv4-addr, ipv6-addr, mac-addr, or domain-name
911
+ # (for cases where the IP address for a domain name is unknown).
912
+ dst_ref: Identifier | None = None
913
+ # Specifies the source port used in the network traffic, as an integer.
914
+ src_port: Annotated[int, Ge(0), Le(65535)] | None = None
915
+ # Specifies the destination port used in the network traffic, as an integer.
916
+ dst_port: Annotated[int, Ge(0), Le(65535)] | None = None
917
+ # Specifies the protocols observed in the network traffic, along with their corresponding state.
918
+ # Protocols MUST be listed in low to high order, from outer to inner in terms of packet encapsulation.
919
+ # That is, the protocols in the outer level of the packet, such as IP, MUST be listed first.
920
+ # The protocol names SHOULD come from the service names defined in the Service Name column of the
921
+ # IANA Service Name and Port Number Registry [Port Numbers].
922
+ # In cases where there is variance in the name of a network protocol not included in the IANA Registry,
923
+ # content producers should exercise their best judgement, and it is recommended that lowercase names be used for
924
+ # consistency with the IANA registry.
925
+ # If the protocol extension is present, the corresponding protocol value for that extension
926
+ # SHOULD be listed in this property.
927
+ protocols: list[str]
928
+ # Specifies the number of bytes, as a positive integer, sent from the source to the destination.
929
+ src_byte_count: Annotated[int, Ge(0)] | None = None
930
+ # Specifies the number of bytes, as a positive integer, sent from the destination to the source.
931
+ dst_byte_count: Annotated[int, Ge(0)] | None = None
932
+ # Specifies the number of packets, as a positive integer, sent from the source to the destination.
933
+ src_packets: Annotated[int, Ge(0)] | None = None
934
+ # Specifies the number of packets, as a positive integer, sent from the destination to the source.
935
+ dst_packets: Annotated[int, Ge(0)] | None = None
936
+ # Specifies any IP Flow Information Export [IPFIX] data for the traffic, as a dictionary.
937
+ # Each key/value pair in the dictionary represents the name/value of a single IPFIX element.
938
+ # Accordingly, each dictionary key SHOULD be a case-preserved version of the IPFIX element name,
939
+ # e.g., octetDeltaCount. Each dictionary value MUST be either an integer or a string,
940
+ # as well as a valid IPFIX property.
941
+ ipfix: dict[str, int | str] | None = None
942
+ # Specifies the bytes sent from the source to the destination.
943
+ src_payload_ref: Identifier | None = None
944
+ # Specifies the bytes sent from the destination to the source.
945
+ dst_payload_ref: Identifier | None = None
946
+ # Links to other network-traffic objects encapsulated by this network-traffic object.
947
+ encapsulates_refs: list[Identifier] | None = None
948
+ # Links to another network-traffic object which encapsulates this object.
949
+ encapsulated_by_ref: Identifier | None = None
950
+
951
+ @model_validator(mode="after")
952
+ def validate_end(self) -> Self:
953
+ """
954
+ If the end property and the start property are both defined, then this property
955
+ MUST be greater than or equal to the timestamp in the start property.
956
+ If the is_active property is true, then the end property MUST NOT be included.
957
+ """
958
+ if self.is_active and self.end is not None:
959
+ raise ValueError(
960
+ "The end property MUST NOT be included if is_active is true"
961
+ )
962
+ if self.start and self.end and self.start > self.end:
963
+ raise ValueError("The end property MUST be greater than or equal to start")
964
+ return self
965
+
966
+ @model_validator(mode="after")
967
+ def at_least_one_of(self) -> Self:
968
+ """
969
+ A Network Traffic object MUST contain the protocols property and at least one of the src_ref or dst_ref
970
+ properties and SHOULD contain the src_port and dst_port properties.
971
+ """
972
+ if self.src_ref is None and self.dst_ref is None:
973
+ raise ValueError("At least one of src_ref or dst_ref must be present")
974
+ return self
975
+
976
+ class Config:
977
+ json_schema_extra: dict[str, list[str]] = {
978
+ "id_contributing_properties": [
979
+ "start",
980
+ "end",
981
+ "src_ref",
982
+ "dst_ref",
983
+ "src_port",
984
+ "dst_port",
985
+ "protocols",
986
+ "extensions",
987
+ ]
988
+ }
989
+
990
+
991
+ # 6.13.2 Windows Process Extension
992
+ class WindowsProcessExtension(Extension):
993
+ """
994
+ The Windows Process extension specifies a default extension for capturing properties specific to Windows processes.
995
+
996
+ The key for this extension when used in the extensions dictionary MUST be windows-process-ext.
997
+ Note that this predefined extension does not use the extension facility described in section 7.3.
998
+ """
999
+
1000
+ # Specifies whether Address Space Layout Randomization (ASLR) is enabled for the process.
1001
+ aslr_enabled: bool | None = None
1002
+ # Specifies whether Data Execution Prevention (DEP) is enabled for the process.
1003
+ dep_enabled: bool | None = None
1004
+ # Specifies the current priority class of the process in Windows.
1005
+ # This value SHOULD be a string that ends in _CLASS.
1006
+ priority: str | None = None
1007
+ # Specifies the Security ID (SID) value of the owner of the process.
1008
+ owner_sid: str | None = None
1009
+ # Specifies the title of the main window of the process.
1010
+ window_title: str | None = None
1011
+ # Specifies the STARTUP_INFO struct used by the process, as a dictionary.
1012
+ # Each name/value pair in the struct MUST be represented as a key/value pair in the dictionary,
1013
+ # where each key MUST be a case-preserved version of the original name.
1014
+ # For example, given a name of "lpDesktop" the corresponding key would be lpDesktop.
1015
+ startup_info: dict[str, JsonValue] | None = None
1016
+ # Specifies the Windows integrity level, or trustworthiness, of the process.
1017
+ integrity_level: WindowsIntegrityLevel | None = None
1018
+
1019
+ @model_validator(mode="before")
1020
+ @classmethod
1021
+ def at_least_one(cls, data: Any) -> Any: # pyright: ignore[reportExplicitAny, reportAny]
1022
+ """
1023
+ An object using the Windows Process Extension MUST contain at least one property from this extension.
1024
+ """
1025
+ if isinstance(data, dict):
1026
+ for key, value in data.items(): # pyright: ignore[reportUnknownVariableType]
1027
+ if key != "type" and value is not None:
1028
+ return data # pyright: ignore[reportUnknownVariableType]
1029
+ raise ValueError("At least one property must be present")
1030
+ raise TypeError("Input data must be a dictionary")
1031
+
1032
+
1033
+ # 6.13.3 Windows Service Extension
1034
+ class WindowsServiceExtension(Extension):
1035
+ """
1036
+ The Windows Service extension specifies a default extension for capturing properties specific to Windows services.
1037
+
1038
+ The key for this extension when used in the extensions dictionary MUST be windows-service-ext.
1039
+ Note that this predefined extension does not use the extension facility described in section 7.3.
1040
+ """
1041
+
1042
+ # Specifies the name of the service.
1043
+ service_name: str | None = None
1044
+ # Specifies the descriptions defined for the service.
1045
+ descriptions: list[str] | None = None
1046
+ # Specifies the display name of the service in Windows GUI controls.
1047
+ display_name: str | None = None
1048
+ # Specifies the name of the load ordering group of which the service is a member.
1049
+ group_name: str | None = None
1050
+ # Specifies the start options defined for the service.
1051
+ start_type: WindowsServiceStartType | None = None
1052
+ # Specifies the DLLs loaded by the service, as a reference to one or more File objects.
1053
+ service_dll_refs: list[Identifier] | None = None
1054
+ # Specifies the type of the service.
1055
+ service_type: WindowsServiceType | None = None
1056
+ # Specifies the current status of the service.
1057
+ service_status: WindowsServiceStatus | None = None
1058
+
1059
+ @model_validator(mode="before")
1060
+ @classmethod
1061
+ def at_least_one(cls, data: Any) -> Any: # pyright: ignore[reportExplicitAny, reportAny]
1062
+ """
1063
+ As all properties of this extension are optional, at least one of the properties defined below MUST be
1064
+ included when using this extension.
1065
+ """
1066
+ if isinstance(data, dict):
1067
+ for key, value in data.items(): # pyright: ignore[reportUnknownVariableType]
1068
+ if key != "type" and value is not None:
1069
+ return data # pyright: ignore[reportUnknownVariableType]
1070
+ raise ValueError("At least one property must be present")
1071
+ raise TypeError("Input data must be a dictionary")
1072
+
1073
+
1074
+ ProcessExtensions = TypedDict(
1075
+ "ProcessExtensions",
1076
+ {
1077
+ "windows-service-ext": WindowsServiceExtension,
1078
+ "windows-process-ext": WindowsProcessExtension,
1079
+ },
1080
+ total=False,
1081
+ extra_items=SerializeAsAny[Extension],
1082
+ )
1083
+
1084
+
1085
+ # 6.13 Process Object
1086
+ class Process(StixObservable):
1087
+ """
1088
+ The Process object represents common properties of an instance of a computer program as executed on an
1089
+ operating system. A Process object MUST contain at least one property (other than type) from this object
1090
+ (or one of its extensions).
1091
+ """
1092
+
1093
+ type: Literal["process"] = "process" # pyright: ignore[reportIncompatibleVariableOverride]
1094
+ # The Process object defines the following extensions. In addition to these, producers MAY create their own.
1095
+ # Dictionary keys MUST use the specification defined name (examples above) or be the id of a STIX Extension object,
1096
+ # depending on the type of extension being used.
1097
+ # The corresponding dictionary values MUST contain the contents of the extension instance.
1098
+ extensions: ProcessExtensions | None = None # pyright: ignore[reportIncompatibleVariableOverride]
1099
+ # Specifies whether the process is hidden.
1100
+ is_hidden: bool | None = None
1101
+ # Specifies the Process ID, or PID, of the process.
1102
+ pid: int | None = None
1103
+ # Specifies the date/time at which the process was created.
1104
+ created_time: datetime | None = None
1105
+ # Specifies the current working directory of the process.
1106
+ cwd: str | None = None
1107
+ # Specifies the full command line used in executing the process, including the process name (which may be
1108
+ # specified individually via the image_ref.name property) and any arguments.
1109
+ command_line: str | None = None
1110
+ # Specifies the list of environment variables associated with the process as a dictionary.
1111
+ # Each key in the dictionary MUST be a case preserved version of the name of the environment variable,
1112
+ # and each corresponding value MUST be the environment variable value as a string.
1113
+ environment_variables: dict[str, str] | None = None
1114
+ # Specifies the list of network connections opened by the process, as a reference to one or more
1115
+ # Network Traffic objects.
1116
+ opened_connection_refs: list[Identifier] | None = None
1117
+ # Specifies the user that created the process, as a reference to a User Account object.
1118
+ creator_user_ref: Identifier | None = None
1119
+ # Specifies the executable binary that was executed as the process image, as a reference to a File object.
1120
+ image_ref: Identifier | None = None
1121
+ # Specifies the other process that spawned (i.e. is the parent of) this one, as a reference to a Process object.
1122
+ parent_ref: Identifier | None = None
1123
+ # Specifies the other processes that were spawned by (i.e. children of) this process, as a reference to one
1124
+ # or more other Process objects.
1125
+ child_refs: list[Identifier] | None = None
1126
+
1127
+ @model_validator(mode="before")
1128
+ @classmethod
1129
+ def at_least_one(cls, data: Any) -> Any: # pyright: ignore[reportExplicitAny, reportAny]
1130
+ """
1131
+ A Process object MUST contain at least one property (other than type)
1132
+ from this object (or one of its extensions).
1133
+ """
1134
+ if isinstance(data, dict):
1135
+ for key, value in data.items(): # pyright: ignore[reportUnknownVariableType]
1136
+ if key != "type" and value is not None:
1137
+ return data # pyright: ignore[reportUnknownVariableType]
1138
+ raise ValueError("At least one property must be present")
1139
+ raise TypeError("Input data must be a dictionary")
1140
+
1141
+
1142
+ # 6.14 Software Object
1143
+ class Software(StixObservable):
1144
+ """
1145
+ The Software object represents high-level properties associated with software, including software products.
1146
+ """
1147
+
1148
+ type: Literal["software"] = "software" # pyright: ignore[reportIncompatibleVariableOverride]
1149
+ # Specifies the name of the software.
1150
+ name: str
1151
+ # Specifies the Common Platform Enumeration (CPE) entry for the software, if available.
1152
+ # The value for this property MUST be a CPE v2.3 entry from the official NVD CPE Dictionary [NVD].
1153
+ # While the CPE dictionary does not contain entries for all software, whenever it does contain an identifier
1154
+ # for a given instance of software, this property SHOULD be present.
1155
+ cpe: str | None = None
1156
+ # Specifies the Software Identification (SWID) Tags [SWID] entry for the software, if available. The tag attribute,
1157
+ # tagId, a globally unique identifier, SHOULD be used as a proxy identifier of the tagged product.
1158
+ swid: str | None = None
1159
+ # Specifies the languages supported by the software. The value of each list member MUST be a language code
1160
+ # conformant to [RFC5646].
1161
+ languages: list[str] | None = None
1162
+ # Specifies the name of the vendor of the software.
1163
+ vendor: str | None = None
1164
+ # Specifies the version of the software.
1165
+ version: str | None = None
1166
+
1167
+ class Config:
1168
+ json_schema_extra: dict[str, list[str]] = {
1169
+ "id_contributing_properties": ["name", "cpe", "swid", "vendor", "version"]
1170
+ }
1171
+
1172
+
1173
+ # 6.15 URL Object
1174
+ class URL(StixObservable):
1175
+ """
1176
+ The URL object represents the properties of a uniform resource locator (URL).
1177
+ """
1178
+
1179
+ type: Literal["url"] = "url" # pyright: ignore[reportIncompatibleVariableOverride]
1180
+ # Specifies the value of the URL. The value of this property MUST conform to [RFC3986], more specifically
1181
+ # section 1.1.3 with reference to the definition for "Uniform Resource Locator".
1182
+ value: StixUrl
1183
+
1184
+ class Config:
1185
+ json_schema_extra: dict[str, list[str]] = {
1186
+ "id_contributing_properties": ["value"]
1187
+ }
1188
+
1189
+
1190
+ # 6.16.2 UNIX Account Extension
1191
+ class UnixAccountExtension(Extension):
1192
+ """
1193
+ The UNIX account extension specifies a default extension for capturing the additional information for an account
1194
+ on a UNIX system. The key for this extension when used in the extensions dictionary MUST be unix-account-ext.
1195
+
1196
+ Note that this predefined extension does not use the extension facility described in Section 7.3.
1197
+ """
1198
+
1199
+ # Specifies the primary group ID of the account.
1200
+ gid: int | None = None
1201
+ # Specifies a list of names of groups that the account is a member of.
1202
+ groups: list[str] | None = None
1203
+ # Specifies the home directory of the account.
1204
+ home_dir: str | None = None
1205
+ # Specifies the account’s command shell.
1206
+ shell: str | None = None
1207
+
1208
+ @model_validator(mode="before")
1209
+ @classmethod
1210
+ def at_least_one(cls, data: Any) -> Any: # pyright: ignore[reportExplicitAny, reportAny]
1211
+ """
1212
+ An object using the UNIX Account Extension MUST contain at least one property from this extension.
1213
+ """
1214
+ if isinstance(data, dict):
1215
+ for key, value in data.items(): # pyright: ignore[reportUnknownVariableType]
1216
+ if key != "type" and value is not None:
1217
+ return data # pyright: ignore[reportUnknownVariableType]
1218
+ raise ValueError("At least one property must be present")
1219
+ raise TypeError("Input data must be a dictionary")
1220
+
1221
+
1222
+ UserAccountExtensions = TypedDict(
1223
+ "UserAccountExtensions",
1224
+ {"unix-account-ext": UnixAccountExtension},
1225
+ total=False,
1226
+ extra_items=SerializeAsAny[Extension],
1227
+ )
1228
+
1229
+
1230
+ # 6.16 User Account Object
1231
+ class UserAccount(StixObservable):
1232
+ """
1233
+ The User Account object represents an instance of any type of user account, including but not limited to
1234
+ operating system, device, messaging service, and social media platform accounts.
1235
+ """
1236
+
1237
+ type: Literal["user-account"] = "user-account" # pyright: ignore[reportIncompatibleVariableOverride]
1238
+ # The User Account object defines the following extensions. In addition to these, producers MAY create their own.
1239
+ # Dictionary keys MUST use the specification defined name (examples above) or be the id of a STIX Extension object,
1240
+ # depending on the type of extension being used.
1241
+ # The corresponding dictionary values MUST contain the contents of the extension instance.
1242
+ extensions: UserAccountExtensions | None = None # pyright: ignore[reportIncompatibleVariableOverride]
1243
+ # Specifies the identifier of the account. The format of the identifier depends on the system the user account is
1244
+ # maintained in, and may be a numeric ID, a GUID, an account name, an email address, etc. The user_id property
1245
+ # should be populated with whatever field is the unique identifier for the system the account is a member of.
1246
+ # For example, on UNIX systems it would be populated with the UID.
1247
+ user_id: str | None = None
1248
+ # Specifies a cleartext credential. This is only intended to be used in capturing metadata from malware analysis
1249
+ # (e.g., a hard-coded domain administrator password that the malware attempts to use for lateral movement) and
1250
+ # SHOULD NOT be used for sharing of PII.
1251
+ credential: str | None = None
1252
+ # Specifies the account login string, used in cases where the user_id property specifies something other than what
1253
+ # a user would type when they login.
1254
+ # For example, in the case of a Unix account with user_id 0, the account_login might be "root".
1255
+ account_login: str | None = None
1256
+ # Specifies the type of the account.
1257
+ # This is an open vocabulary and values SHOULD come from the account-type-ov open vocabulary.
1258
+ account_type: str | None = None
1259
+ # Specifies the display name of the account, to be shown in user interfaces, if applicable.
1260
+ # On Unix, this is equivalent to the GECOS field.
1261
+ display_name: str | None = None
1262
+ # Indicates that the account is associated with a network service or system process (daemon), not a
1263
+ # specific individual.
1264
+ is_service_account: bool | None = None
1265
+ # Specifies that the account has elevated privileges
1266
+ # (i.e., in the case of root on Unix or the Windows Administrator account).
1267
+ is_privileged: bool | None = None
1268
+ # Specifies that the account has the ability to escalate privileges
1269
+ # (i.e., in the case of sudo on Unix or a Windows Domain Admin account)
1270
+ can_escalate_privs: bool | None = None
1271
+ # Specifies if the account is disabled.
1272
+ is_disabled: bool | None = None
1273
+ # Specifies when the account was created.
1274
+ account_created: datetime | None = None
1275
+ # Specifies the expiration date of the account.
1276
+ account_expires: datetime | None = None
1277
+ # Specifies when the account credential was last changed.
1278
+ credential_last_changed: datetime | None = None
1279
+ # Specifies when the account was first accessed.
1280
+ account_first_login: datetime | None = None
1281
+ # Specifies when the account was last accessed.
1282
+ account_last_login: datetime | None = None
1283
+
1284
+ @model_validator(mode="before")
1285
+ @classmethod
1286
+ def at_least_one(cls, data: Any) -> Any: # pyright: ignore[reportExplicitAny, reportAny]
1287
+ """
1288
+ As all properties of this object are optional, at least one of the properties defined below
1289
+ MUST be included when using this object.
1290
+ """
1291
+ if isinstance(data, dict):
1292
+ for key, value in data.items(): # pyright: ignore[reportUnknownVariableType]
1293
+ if key != "type" and value is not None:
1294
+ return data # pyright: ignore[reportUnknownVariableType]
1295
+ raise ValueError("At least one property must be present")
1296
+ raise TypeError("Input data must be a dictionary")
1297
+
1298
+ class Config:
1299
+ json_schema_extra: dict[str, list[str]] = {
1300
+ "id_contributing_properties": ["account_type", "user_id", "account_login"]
1301
+ }
1302
+
1303
+
1304
+ # 6.17.2 Windows Registry Value Type
1305
+ class WindowsRegistryValueType(StixCore):
1306
+ """
1307
+ The Windows Registry Value type captures the properties of a Windows Registry Key Value.
1308
+ """
1309
+
1310
+ # Specifies the name of the registry value. For specifying the default value in a registry key,
1311
+ # an empty string MUST be used.
1312
+ name: str | None = None
1313
+ # Specifies the data contained in the registry value.
1314
+ data: str | None = None
1315
+ # Specifies the registry (REG_*) data type used in the registry value.
1316
+ data_type: WindowsRegistryDatatype | None = None
1317
+
1318
+ @model_validator(mode="before")
1319
+ @classmethod
1320
+ def at_least_one(cls, data: Any) -> Any: # pyright: ignore[reportExplicitAny, reportAny]
1321
+ """
1322
+ As all properties of this object are optional, at least one of the properties defined below
1323
+ MUST be included when using this object.
1324
+ """
1325
+ if isinstance(data, dict):
1326
+ for key, value in data.items(): # pyright: ignore[reportUnknownVariableType]
1327
+ if key != "type" and value is not None:
1328
+ return data # pyright: ignore[reportUnknownVariableType]
1329
+ raise ValueError("At least one property must be present")
1330
+ raise TypeError("Input data must be a dictionary")
1331
+
1332
+
1333
+ # 6.17 Windows Registry Key Object
1334
+ class WindowsRegistryKey(StixObservable):
1335
+ """
1336
+ The Registry Key object represents the properties of a Windows registry key.
1337
+ """
1338
+
1339
+ type: Literal["windows-registry-key"] = "windows-registry-key" # pyright: ignore[reportIncompatibleVariableOverride]
1340
+ # Specifies the full registry key including the hive.
1341
+ key: str | None = None
1342
+ # Specifies the values found under the registry key.
1343
+ # The value of the key, including the hive portion, SHOULD be case-preserved. The hive portion of the key MUST be
1344
+ # fully expanded and not truncated; e.g., HKEY_LOCAL_MACHINE must be used instead of HKLM.
1345
+ values: list[WindowsRegistryValueType] | None = None
1346
+ # Specifies the last date/time that the registry key was modified.
1347
+ modified_time: datetime | None = None
1348
+ # Specifies a reference to the user account that created the registry key.
1349
+ # The object referenced in this property MUST be of type user-account.
1350
+ creator_user_ref: Identifier | None = None
1351
+ # Specifies the number of subkeys contained under the registry key.
1352
+ number_of_subkeys: int | None = None
1353
+
1354
+ @model_validator(mode="before")
1355
+ @classmethod
1356
+ def at_least_one(cls, data: Any) -> Any: # pyright: ignore[reportExplicitAny, reportAny]
1357
+ """
1358
+ As all properties of this object are optional, at least one of the properties defined below
1359
+ MUST be included when using this object.
1360
+ """
1361
+ if isinstance(data, dict):
1362
+ for key, value in data.items(): # pyright: ignore[reportUnknownVariableType]
1363
+ if key != "type" and value is not None:
1364
+ return data # pyright: ignore[reportUnknownVariableType]
1365
+ raise ValueError("At least one property must be present")
1366
+ raise TypeError("Input data must be a dictionary")
1367
+
1368
+ class Config:
1369
+ json_schema_extra: dict[str, list[str]] = {
1370
+ "id_contributing_properties": ["key", "values"]
1371
+ }
1372
+
1373
+
1374
+ # 6.18.2 X.509 v3 Extensions Type
1375
+ class X509v3ExtensionsType(Extension):
1376
+ """
1377
+ The X.509 v3 Extensions type captures properties associated with X.509 v3 extensions, which serve as a mechanism
1378
+ for specifying additional information such as alternative subject names.
1379
+
1380
+ Note that the use of the term "extensions" in this context refers to the X.509 v3 Extensions type and is not a
1381
+ STIX Cyber Observables extension. Therefore, it is a type that describes X.509 extensions.
1382
+ """
1383
+
1384
+ # Specifies a multi-valued extension which indicates whether a certificate is a CA certificate.
1385
+ # The first (mandatory) name is CA followed by TRUE or FALSE. If CA is TRUE, then an optional pathlen name
1386
+ # followed by a non-negative value can be included. Also equivalent to the object ID (OID) value of 2.5.29.19.
1387
+ basic_constraints: str | None = None
1388
+ # Specifies a namespace within which all subject names in subsequent certificates in a certification path
1389
+ # MUST be located. Also equivalent to the object ID (OID) value of 2.5.29.30.
1390
+ name_constraints: str | None = None
1391
+ # Specifies any constraints on path validation for certificates issued to CAs.
1392
+ # Also equivalent to the object ID (OID) value of 2.5.29.36.
1393
+ policy_constraints: str | None = None
1394
+ # Specifies a multi-valued extension consisting of a list of names of the permitted key usages.
1395
+ # Also equivalent to the object ID (OID) value of 2.5.29.15.
1396
+ key_usage: str | None = None
1397
+ # Specifies a list of usages indicating purposes for which the certificate public key can be used for.
1398
+ # Also equivalent to the object ID (OID) value of 2.5.29.37.
1399
+ extended_key_usage: str | None = None
1400
+ # Specifies the identifier that provides a means of identifying certificates that contain a particular public key.
1401
+ # Also equivalent to the object ID (OID) value of 2.5.29.14.
1402
+ subject_key_identifier: str | None = None
1403
+ # Specifies the identifier that provides a means of identifying the public key corresponding to the private key
1404
+ # used to sign a certificate. Also equivalent to the object ID (OID) value of 2.5.29.35.
1405
+ authority_key_identifier: str | None = None
1406
+ # Specifies the additional identities to be bound to the subject of the certificate.
1407
+ # Also equivalent to the object ID (OID) value of 2.5.29.17.
1408
+ subject_alternative_name: str | None = None
1409
+ # Specifies the additional identities to be bound to the issuer of the certificate.
1410
+ # Also equivalent to the object ID (OID) value of 2.5.29.18.
1411
+ issuer_alternative_name: str | None = None
1412
+ # Specifies the identification attributes (e.g., nationality) of the subject.
1413
+ # Also equivalent to the object ID (OID) value of 2.5.29.9.
1414
+ subject_directory_attributes: str | None = None
1415
+ # Specifies how CRL information is obtained.
1416
+ # Also equivalent to the object ID (OID) value of 2.5.29.31.
1417
+ crl_distribution_points: str | None = None
1418
+ # Specifies the number of additional certificates that may appear in the path before anyPolicy is no longer
1419
+ # permitted. Also equivalent to the object ID (OID) value of 2.5.29.54.
1420
+ inhibit_any_policy: str | None = None
1421
+ # Specifies the date on which the validity period begins for the private key, if it is different from the
1422
+ # validity period of the certificate.
1423
+ private_key_usage_period_not_before: datetime | None = None
1424
+ # Specifies the date on which the validity period ends for the private key, if it is different from the
1425
+ # validity period of the certificate.
1426
+ private_key_usage_period_not_after: datetime | None = None
1427
+ # Specifies a sequence of one or more policy information terms, each of which consists of an object identifier
1428
+ # (OID) and optional qualifiers. Also equivalent to the object ID (OID) value of 2.5.29.32.
1429
+ certificate_policies: str | None = None
1430
+ # Specifies one or more pairs of OIDs; each pair includes an issuerDomainPolicy and a subjectDomainPolicy.
1431
+ # The pairing indicates whether the issuing CA considers its issuerDomainPolicy equivalent to the subject
1432
+ # CA’s subjectDomainPolicy. Also equivalent to the object ID (OID) value of 2.5.29.33.
1433
+ policy_mappings: str | None = None
1434
+
1435
+ @classmethod
1436
+ def at_least_one(cls, data: Any) -> Any: # pyright: ignore[reportExplicitAny, reportAny]
1437
+ """
1438
+ An object using the X.509 v3 Extensions type MUST contain at least one property from this type.
1439
+ """
1440
+ if isinstance(data, dict):
1441
+ for key, value in data.items(): # pyright: ignore[reportUnknownVariableType]
1442
+ if key != "type" and value is not None:
1443
+ return data # pyright: ignore[reportUnknownVariableType]
1444
+ raise ValueError("At least one property must be present")
1445
+ raise TypeError("Input data must be a dictionary")
1446
+
1447
+
1448
+ # 6.18 X.509 Certificate Object
1449
+ class X509Certificate(StixObservable):
1450
+ """
1451
+ The X.509 Certificate object represents the properties of an X.509 certificate, as defined by ITU recommendation
1452
+ X.509 [X509].
1453
+ """
1454
+
1455
+ type: Literal["x509-certificate"] = "x509-certificate" # pyright: ignore[reportIncompatibleVariableOverride]
1456
+ # Specifies whether the certificate is self-signed, i.e., whether it is signed by the same entity whose
1457
+ # identity it certifies.
1458
+ is_self_signed: bool | None = None
1459
+ # Specifies any hashes that were calculated for the entire contents of the certificate.
1460
+ hashes: Hashes | None = None
1461
+ # Specifies the version of the encoded certificate.
1462
+ version: str | None = None
1463
+ # Specifies the unique identifier for the certificate, as issued by a specific Certificate Authority.
1464
+ serial_number: str | None = None
1465
+ # Specifies the name of the algorithm used to sign the certificate.
1466
+ signature_algorithm: str | None = None
1467
+ # Specifies the name of the Certificate Authority that issued the certificate.
1468
+ issuer: str | None = None
1469
+ # Specifies the date on which the certificate validity period begins.
1470
+ validity_not_before: datetime | None = None
1471
+ # Specifies the date on which the certificate validity period ends.
1472
+ validity_not_after: datetime | None = None
1473
+ # Specifies the name of the entity associated with the public key stored in the subject public key field of the
1474
+ # certificate.
1475
+ subject: str | None = None
1476
+ # Specifies the name of the algorithm with which to encrypt data being sent to the subject.
1477
+ subject_public_key_algorithm: str | None = None
1478
+ # Specifies the modulus portion of the subject's public RSA key.
1479
+ subject_public_key_modulus: str | None = None
1480
+ # Specifies the exponent portion of the subject's public RSA key, as an integer.
1481
+ subject_public_key_exponent: int | None = None
1482
+ # Specifies any standard X.509 v3 extensions that may be used in the certificate.
1483
+ x509_v3_extensions: X509v3ExtensionsType | None = None
1484
+
1485
+ @classmethod
1486
+ def at_least_one(cls, data: Any) -> Any: # pyright: ignore[reportExplicitAny, reportAny]
1487
+ """
1488
+ An X.509 Certificate object MUST contain at least one object specific property (other than type)
1489
+ from this object.
1490
+ """
1491
+ if isinstance(data, dict):
1492
+ for key, value in data.items(): # pyright: ignore[reportUnknownVariableType]
1493
+ if key != "type" and value is not None:
1494
+ return data # pyright: ignore[reportUnknownVariableType]
1495
+ raise ValueError("At least one property must be present")
1496
+ raise TypeError("Input data must be a dictionary")
1497
+
1498
+ class Config:
1499
+ json_schema_extra: dict[str, list[str]] = {
1500
+ "id_contributing_properties": ["hashes", "serial_number"]
1501
+ }
1502
+
1503
+
1504
+ SCOs = Annotated[
1505
+ (
1506
+ Artifact
1507
+ | AutonomousSystem
1508
+ | Directory
1509
+ | DomainName
1510
+ | EmailAddress
1511
+ | EmailMessage
1512
+ | File
1513
+ | IPv4Address
1514
+ | IPv6Address
1515
+ | MACAddress
1516
+ | Mutex
1517
+ | NetworkTraffic
1518
+ | Process
1519
+ | Software
1520
+ | URL
1521
+ | UserAccount
1522
+ | WindowsRegistryKey
1523
+ | X509Certificate
1524
+ ),
1525
+ Field(discriminator="type"),
1526
+ ]