stidantic 0.1.0__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/sro.py ADDED
@@ -0,0 +1,159 @@
1
+ from typing import Literal, Self, Annotated
2
+ from datetime import datetime
3
+ from pydantic import Field
4
+ from pydantic.functional_validators import model_validator
5
+ from pydantic.types import PositiveInt
6
+ from stidantic.types import StixRelationship, Identifier, StixType
7
+
8
+
9
+ # 5.1 Relationship
10
+ class Relationship(StixRelationship):
11
+ """
12
+ The Relationship object is used to link together two SDOs or SCOs in order to describe how they are related to
13
+ each other. If SDOs and SCOs are considered "nodes" or "vertices" in the graph, the Relationship Objects (SROs)
14
+ represent "edges".
15
+
16
+ STIX defines many relationship types to link together SDOs and SCOs. These relationships are contained in the
17
+ "Relationships" table under each SDO and SCO definition. Relationship types defined in the specification SHOULD
18
+ be used to ensure consistency. An example of a specification-defined relationship is that an indicator indicates
19
+ a campaign. That relationship type is listed in the Relationships section of the Indicator SDO definition.
20
+
21
+ STIX also allows relationships from any SDO or SCO to any SDO or SCO that have not been defined in this
22
+ specification. These relationships MAY use the related-to relationship type or MAY use a user-defined
23
+ relationship type. As an example, a user might want to link malware directly to a tool.
24
+ They can do so using related-to to say that the Malware is related to the Tool but not describe how,
25
+ or they could use delivered-by (a user-defined name they determined) to indicate more detail.
26
+
27
+ Note that some relationships in STIX may seem like "shortcuts". For example, an Indicator doesn't really detect
28
+ a Campaign: it detects activity (Attack Patterns, Malware, Infrastructure, etc.) that are often used by that
29
+ campaign. While some analysts might want all of the source data and think that shortcuts are misleading,
30
+ in many cases it's helpful to provide just the key points (shortcuts) and leave out the low-level details.
31
+ In other cases, the low-level analysis may not be known or sharable, while the high-level analysis is.
32
+ For these reasons, relationships that might appear to be "shortcuts" are not excluded from STIX.
33
+ """
34
+
35
+ type: Literal["relationship"] = "relationship" # pyright: ignore[reportIncompatibleVariableOverride]
36
+ # The name used to identify the type of Relationship. This value SHOULD be an exact value listed in the
37
+ # relationships for the source and target SDO, but MAY be any string.
38
+ relationship_type: StixType
39
+ # A description that provides more details and context about the Relationship,
40
+ # potentially including its purpose and its key characteristics.
41
+ description: str | None = None
42
+ # The id of the source (from) object. The value MUST be an ID reference to an SDO or SCO
43
+ # (i.e., it cannot point to an SRO, Bundle, Language Content, or Marking Definition).
44
+ source_ref: Identifier
45
+ # The id of the target (to) object. The value MUST be an ID reference to an SDO or SCO
46
+ # (i.e., it cannot point to an SRO, Bundle, Language Content, or Marking Definition).
47
+ target_ref: Identifier
48
+ # This optional timestamp represents the earliest time at which the Relationship between the objects exists.
49
+ # If this property is a future timestamp, at the time the start_time property is defined,
50
+ # then this represents an estimate by the producer of the intelligence of the earliest
51
+ # time at which relationship will be asserted to be true.
52
+ # If it is not specified, then the earliest time at which the relationship between the objects exists
53
+ # is not defined.
54
+ start_time: datetime | None = None
55
+ # The latest time at which the Relationship between the objects exists. If this property is a future timestamp,
56
+ # at the time the stop_time property is defined, then this represents an estimate by the producer of the
57
+ # intelligence of the latest time at which relationship will be asserted to be true.
58
+ # If stop_time is not specified, then the latest time at which the relationship between
59
+ # the objects exists is either not known, not disclosed, or has no defined stop time.
60
+ stop_time: datetime | None = None
61
+
62
+ @model_validator(mode="after")
63
+ def validate_start_stop_interval(self) -> Self:
64
+ """
65
+ If start_time and stop_time are both defined, then stop_time MUST be later than the start_time value.
66
+ """
67
+ if self.start_time and self.stop_time and self.start_time > self.stop_time:
68
+ raise ValueError(
69
+ "the stop_time property MUST be greater than or equal to the timestamp in the start_time property"
70
+ )
71
+ return self
72
+
73
+
74
+ # 5.2 Sighting
75
+ class Sighting(StixRelationship):
76
+ """
77
+ A Sighting denotes the belief that something in CTI (e.g., an indicator, malware, tool, threat actor, etc.)
78
+ was seen. Sightings are used to track who and what are being targeted, how attacks are carried out, and to
79
+ track trends in attack behavior.
80
+
81
+ The Sighting relationship object is a special type of SRO; it is a relationship that contains extra properties
82
+ not present on the Generic Relationship object. These extra properties are included to represent data specific
83
+ to sighting relationships (e.g., count, representing how many times something was seen), but for other purposes
84
+ a Sighting can be thought of as a Relationship with a name of "sighting-of". Sighting is captured as a relationship
85
+ because you cannot have a sighting unless you have something that has been sighted.
86
+ Sighting does not make sense without the relationship to what was sighted.
87
+
88
+ Sighting relationships relate three aspects of the sighting:
89
+ ● What was sighted, such as the Indicator, Malware, Campaign, or other SDO (sighting_of_ref)
90
+ ● Who sighted it and/or where it was sighted, represented as an Identity (where_sighted_refs)
91
+ ● What was actually seen on systems and networks, represented as Observed Data (observed_data_refs)
92
+
93
+ What was sighted is required; a sighting does not make sense unless you say what you saw. Who sighted it,
94
+ where it was sighted, and what was actually seen are optional. In many cases it is not necessary to provide
95
+ that level of detail in order to provide value.
96
+
97
+ Sightings are used whenever any SDO has been "seen". In some cases, the object creator wishes to convey very little
98
+ information about the sighting; the details might be sensitive, but the fact that they saw a malware instance or
99
+ threat actor could still be very useful. In other cases, providing the details may be helpful or even necessary;
100
+ saying exactly which of the 1000 IP addresses in an indicator were sighted is helpful when tracking which of those
101
+ IPs is still malicious.
102
+
103
+ Sighting is distinct from Observed Data in that Sighting is an intelligence assertion ("I saw this threat actor")
104
+ while Observed Data is simply information ("I saw this file"). When you combine them by including the linked
105
+ Observed Data (observed_data_refs) from a Sighting, you can say "I saw this file, and that makes me think I saw
106
+ this threat actor".
107
+ """
108
+
109
+ type: Literal["sighting"] = "sighting" # pyright: ignore[reportIncompatibleVariableOverride]
110
+ # A description that provides more details and context about the Sighting.
111
+ description: str | None = None
112
+ # The beginning of the time window during which the SDO referenced by the sighting_of_ref property was sighted.
113
+ first_seen: datetime | None = None
114
+ # The end of the time window during which the SDO referenced by the sighting_of_ref property was sighted.
115
+ last_seen: datetime | None = None
116
+ # If present, this MUST be an integer between 0 and 999,999,999 inclusive and represents the number of times the
117
+ # SDO referenced by the sighting_of_ref property was sighted.
118
+ # Observed Data has a similar property called number_observed, which refers to the number of times the data was
119
+ # observed. These counts refer to different concepts and are distinct.
120
+ # For example, a single sighting of a DDoS bot might have many millions of observations of the network traffic
121
+ # that it generates. Thus, the Sighting count would be 1 (the bot was observed once) but the Observed Data
122
+ # number_observed would be much higher.
123
+ # As another example, a sighting with a count of 0 can be used to express that an indicator was not seen at all.
124
+ count: PositiveInt | None = None
125
+ # An ID reference to the SDO that was sighted (e.g., Indicator or Malware).
126
+ # For example, if this is a Sighting of an Indicator, that Indicator’s ID would be the value of this property.
127
+ # This property MUST reference only an SDO.
128
+ sighting_of_ref: Identifier | None = None
129
+ # A list of ID references to the Observed Data objects that contain the raw cyber data for this Sighting.
130
+ # For example, a Sighting of an Indicator with an IP address could include the Observed Data for the
131
+ # network connection that the Indicator was used to detect.
132
+ # This property MUST reference only Observed Data SDOs.
133
+ observed_data_refs: list[Identifier] | None = None
134
+ # A list of ID references to the Identity or Location objects describing the entities or types of entities
135
+ # that saw the sighting.
136
+ # Omitting the where_sighted_refs property does not imply that the sighting was seen by the object creator.
137
+ # To indicate that the sighting was seen by the object creator, an Identity representing the object creator
138
+ # should be listed in where_sighted_refs.
139
+ # This property MUST reference only Identity or Location SDOs.
140
+ where_sighted_refs: list[Identifier] | None = None
141
+ # The summary property indicates whether the Sighting should be considered summary data.
142
+ # Summary data is an aggregation of previous Sightings reports and should not be considered primary source data.
143
+ # Default value is false.
144
+ summary: bool | None = False
145
+
146
+ @model_validator(mode="after")
147
+ def validate_first_last_interval(self) -> Self:
148
+ """
149
+ If this property and the first_seen property are both defined, then this property
150
+ MUST be greater than or equal to the timestamp in the first_seen property.
151
+ """
152
+ if self.first_seen and self.last_seen and self.first_seen > self.last_seen:
153
+ raise ValueError(
154
+ "the last_seen property MUST be greater than or equal to the timestamp in the first_seen property"
155
+ )
156
+ return self
157
+
158
+
159
+ SROs = Annotated[(Relationship | Sighting), Field(discriminator="type")]
stidantic/types.py ADDED
@@ -0,0 +1,409 @@
1
+ from typing import Annotated, Literal, Any, Self
2
+ from enum import Enum
3
+ from pydantic import BaseModel, ConfigDict, Field, GetCoreSchemaHandler
4
+ from pydantic.functional_validators import AfterValidator, model_validator
5
+ from pydantic.networks import AnyUrl, UrlConstraints
6
+ from pydantic.types import Base64Bytes, StringConstraints
7
+ from pydantic_core import CoreSchema, core_schema
8
+ from datetime import datetime
9
+ from re import compile
10
+
11
+ from stidantic.validators import (
12
+ validate_bin_field,
13
+ validate_hex_field,
14
+ )
15
+
16
+
17
+ # Common constraints on Stix dictionnary keys.
18
+ StixKeyPattern = compile(r"^[a-zA-Z0-9\-\_]+$")
19
+ StixKeyConstraint = StringConstraints(max_length=250, pattern=StixKeyPattern)
20
+ StixKey = Annotated[str, StixKeyConstraint]
21
+
22
+ # Common constraints on Stix type names.
23
+ StixTypePattern = compile(r"^[a-zA-Z0-9\-]+$")
24
+ StixTypeConstraint = StringConstraints(pattern=StixTypePattern)
25
+ StixType = Annotated[str, StixTypeConstraint]
26
+
27
+ # Common constraints on Stix property names.
28
+ StixPropPattern = compile(r"^[a-zA-Z0-9\_]+$")
29
+ StixPropConstraint = StringConstraints(
30
+ min_length=3, max_length=250, pattern=StixPropPattern
31
+ )
32
+ StixProp = Annotated[str, StixPropConstraint]
33
+
34
+ # 2.1 Binary
35
+ # The binary data type represents a sequence of bytes. In order to allow pattern matching on custom objects,
36
+ # for all properties that use the binary type, the property name MUST end with '_bin'.
37
+ type StixBinary = Annotated[Base64Bytes, AfterValidator(validate_bin_field)]
38
+
39
+ # 2.8 Hexadecimal
40
+ # The hex data type encodes an array of octets (8-bit bytes) as hexadecimal. The string MUST consist of an even number
41
+ # of hexadecimal characters, which are the digits '0' through '9' and the lower-case letters 'a' through 'f'. In order
42
+ # to allow pattern matching on custom objects, for all properties that use the hex type,
43
+ # the property name MUST end with '_hex'.
44
+ type StixHex = Annotated[str, AfterValidator(validate_hex_field)]
45
+
46
+ # 2.3 Dictionnary
47
+ # Dictionary keys MUST be unique in each dictionary, MUST be in ASCII, and are limited to the characters
48
+ # a-z (lowercase ASCII), A-Z (uppercase ASCII), numerals 0-9, hyphen (-), and underscore (_).
49
+ # Dictionary keys MUST be no longer than 250 ASCII characters in length and SHOULD be lowercase.
50
+ type StixDict = dict[
51
+ StixKey,
52
+ Any, # pyright: ignore[reportExplicitAny]
53
+ ]
54
+
55
+
56
+ # A URL reference to an external resource [RFC3986].
57
+ type StixUrl = Annotated[AnyUrl, UrlConstraints(preserve_empty_path=True)]
58
+
59
+
60
+ # 2.9 Identifier
61
+ class Identifier(str):
62
+ @classmethod
63
+ def __get_pydantic_core_schema__(
64
+ cls,
65
+ source_type: Any, # pyright: ignore[reportExplicitAny, reportAny]
66
+ handler: GetCoreSchemaHandler,
67
+ ) -> CoreSchema:
68
+ return core_schema.no_info_after_validator_function(cls, handler(str))
69
+
70
+ def get_type(self: str) -> str:
71
+ return self.split("--", maxsplit=1)[0]
72
+
73
+
74
+ class StixCore(BaseModel):
75
+ model_config = ConfigDict( # pyright: ignore[reportUnannotatedClassAttribute]
76
+ use_enum_values=True, validate_by_alias=True, frozen=True
77
+ )
78
+
79
+
80
+ # 2.7 Hashes
81
+ class Hashes(StixCore, extra="allow"):
82
+ """
83
+ The Hashes type represents one or more cryptographic hashes, as a special set of key/value pairs.
84
+ Accordingly, the name of each hashing algorithm MUST be specified as a key in the dictionary
85
+ and MUST identify the name of the hashing algorithm used to generate the corresponding value.
86
+ This name SHOULD come from one of the values defined in the hash-algorithm-ov open vocabulary.
87
+
88
+ To enhance compatibility, the SHA-256 hash SHOULD be used whenever possible.
89
+ """
90
+
91
+ # Specifies the MD5 message digest algorithm. The corresponding hash string for this
92
+ # value MUST be a valid MD5 message digest as defined in [RFC1321].
93
+ md5: Annotated[str | None, Field(alias="MD5")] = None
94
+ # Specifies the SHA-1 (secure-hash algorithm 1) cryptographic hash function.
95
+ # The corresponding hash string for this value MUST be a valid SHA-1 message digest as defined in [RFC3174].
96
+ sha1: Annotated[str | None, Field(alias="SHA-1")] = None
97
+ # Specifies the SHA-256 cryptographic hash function (part of the SHA2 family).
98
+ # The corresponding hash string for this value MUST be a valid SHA-256 message digest as defined in [RFC6234].
99
+ sha256: Annotated[str | None, Field(alias="SHA-256")] = None
100
+ # Specifies the SHA-512 cryptographic hash function (part of the SHA2 family).
101
+ # The corresponding hash string for this value MUST be a valid SHA-512 message digest as defined in [RFC6234].
102
+ sha512: Annotated[str | None, Field(alias="SHA-512")] = None
103
+ # Specifies the SHA3-256 cryptographic hash function. The corresponding hash string
104
+ # for this value MUST be a valid SHA3-256 message digest as defined in [FIPS202].
105
+ sha3_256: Annotated[str | None, Field(alias="SHA3-256")] = None
106
+ # Specifies the SHA3-512 cryptographic hash function. The corresponding hash string
107
+ # for this value MUST be a valid SHA3-512 message digest as defined in [FIPS202].
108
+ sha3_512: Annotated[str | None, Field(alias="SHA3-512")] = None
109
+ # Specifies the ssdeep fuzzy hashing algorithm. The corresponding hash string for this
110
+ # value MUST be a valid piecewise hash as defined in the [SSDEEP] specification.
111
+ ssdeep: Annotated[str | None, Field(alias="SSDEEP")] = None
112
+ # Specifies the TLSH fuzzy hashing algorithm. The corresponding hash string for this
113
+ # value MUST be a valid 35 byte long hash as defined in the [TLSH] specification.
114
+ tlsh: Annotated[str | None, Field(alias="TLSH")] = None
115
+
116
+ @model_validator(mode="after")
117
+ def lang_or_marking_ref(self) -> "Hashes":
118
+ """
119
+ Dictionary keys MUST be unique in each hashes property, MUST be in ASCII, and are limited to the
120
+ characters a-z (lowercase ASCII), A-Z (uppercase ASCII), numerals 0-9, hyphen (-), and underscore (_).
121
+ Dictionary keys MUST have a minimum length of 3 ASCII characters
122
+ and MUST be no longer than 250 ASCII characters in length.
123
+ The value MUST be a string in the appropriate format defined by the hash type indicated in the dictionary key.
124
+ """
125
+ if self.__pydantic_extra__ and any(
126
+ not (len(key) > 250 or len(key) < 3 or StixKeyPattern.match(key))
127
+ for key in self.__pydantic_extra__.keys()
128
+ ):
129
+ raise ValueError("Invalid extra hash key.")
130
+ return self
131
+
132
+
133
+ # 2.5 External Reference
134
+ class ExternalReference(StixCore):
135
+ """
136
+ External references are used to describe pointers to information represented outside of STIX.
137
+ For example, a Malware object could use an external reference to indicate an ID for that malware
138
+ in an external database or a report could use references to represent source material.
139
+ """
140
+
141
+ # The name of the source that the external-reference is defined within (system, registry, organization, etc.).
142
+ source_name: str
143
+ # A human readable description.
144
+ description: str | None = None
145
+ # A URL reference to an external resource [RFC3986].
146
+ url: StixUrl | None = None
147
+ # Specifies a dictionary of hashes for the contents of the url. This SHOULD be provided when the url property is
148
+ # present. Dictionary keys MUST come from one of the entries listed in the hash-algorithm-ov open vocabulary.
149
+ # As stated in Section 2.7, to ensure interoperability, a SHA-256 hash SHOULD be included whenever possible.
150
+ hashes: Hashes | None = None
151
+ # An identifier for the external reference content.
152
+ external_id: str | None = None
153
+
154
+ @model_validator(mode="before")
155
+ def at_least_one(cls, values: dict[str, str]) -> dict[str, str]:
156
+ """
157
+ In addition to the source_name property, at least one of the description, url,
158
+ or external_id properties MUST be present.
159
+ """
160
+ for value in values.values():
161
+ if value:
162
+ return values
163
+ raise ValueError("Missing at least one hash value.")
164
+
165
+
166
+ # 2.11 Kill Chain Phase
167
+ class KillChainPhase(StixCore):
168
+ """
169
+ The kill-chain-phase represents a phase in a kill chain, which describes the
170
+ various phases an attacker may undertake in order to achieve their objectives.
171
+ """
172
+
173
+ # The name of the kill chain. The value of this property SHOULD be all lowercase
174
+ # and SHOULD use hyphens instead of spaces or underscores as word separators.
175
+ kill_chain_name: str
176
+ # The name of the phase in the kill chain. The value of this property SHOULD be all
177
+ # lowercase and SHOULD use hyphens instead of spaces or underscores as word separators.
178
+ phase_name: str
179
+
180
+
181
+ # 7.2.3 Granular Markings
182
+ class GranularMarking(StixCore):
183
+ """
184
+ The granular-marking type defines how the list of marking-definition objects referenced by
185
+ the marking_refs property to apply to a set of content identified by
186
+ the list of selectors in the selectors property.
187
+ """
188
+
189
+ # The lang property identifies the language of the text identified by this marking. The value of the lang property,
190
+ # if present, MUST be an [RFC5646] language code. If the marking_ref property is not present, this property MUST
191
+ # be present. If the marking_ref property is present, this property MUST NOT be present.
192
+ lang: str | None = None
193
+ # The marking_ref property specifies the ID of the marking-definition object that describes the marking.
194
+ # If the lang property is not present, this property MUST be present. If the lang property is present,
195
+ # this property MUST NOT be present.
196
+ marking_ref: Identifier | None = None
197
+ # The selectors property specifies a list of selectors for content contained within the STIX Object in which this
198
+ # property appears. Selectors MUST conform to the syntax defined below. The marking-definition referenced in
199
+ # the marking_ref property is applied to the content selected by the selectors in this list. The [RFC5646] language
200
+ # code specified by the lang property is applied to the content selected by the selectors in this list.
201
+ selectors: list[str] | None = None
202
+ # Selector Syntax:
203
+ # Selectors contained in the selectors list are strings that consist of multiple components that MUST be separated
204
+ # by the . character. Each component MUST be one of:
205
+ # ● A property name or dictionary key, e.g., description, or;
206
+ # ● A zero-based list index, specified as a non-negative integer in square brackets, e.g., [4]
207
+ # Selectors denote path traversals: the root of each selector is the STIX Object
208
+ # that the granular_markings property appears in.
209
+ # Starting from that root, for each component in the selector, properties and list items are traversed.
210
+ # When the complete list has been traversed, the value of the content is considered selected.
211
+ # Selectors MUST refer to properties or list items that are actually present on the marked object.
212
+
213
+ @model_validator(mode="after")
214
+ def lang_or_marking_ref(self) -> "GranularMarking":
215
+ if self.lang and self.marking_ref:
216
+ raise ValueError(
217
+ "Both lang and marking_ref properties MUST NOT be present."
218
+ )
219
+ if not self.lang and not self.marking_ref:
220
+ raise ValueError("Either lang or marking_ref property MUST be present.")
221
+ return self
222
+
223
+
224
+ # 10.5 Extension Types Enumeration
225
+ class ExtensionType(Enum):
226
+ new_sdo = "new-sdo"
227
+ new_sco = "new-sco"
228
+ new_sro = "new-sro"
229
+ property_extension = "property-extension"
230
+ toplevel_property_extension = "toplevel-property-extension"
231
+
232
+
233
+ class Extension(StixCore):
234
+ extension_type: ExtensionType
235
+
236
+
237
+ # 3.2 Common Properties
238
+ class StixCommon(StixCore):
239
+ """
240
+ This section defines the common properties that MAY exist on a STIX Objects. While some STIX Objects use all
241
+ of these common properties, not all object types do.
242
+ Each type of STIX Object defines which common properties are required, which are optional,
243
+ and which are not in use.
244
+ """
245
+
246
+ # All STIX Objects and the STIX Bundle Object have an id property that uniquely identifies each instance
247
+ # of the object.
248
+ # This id MUST meet the requirements of the identifier type (see section 2.9).
249
+ # For objects that support versioning, all objects with the same id are considered different versions
250
+ # of the same object and the version of the object is identified by its modified property.
251
+ id: Identifier
252
+ # The type property identifies the type of STIX Object.
253
+ type: str
254
+ # The value of this property MUST be 2.1 for STIX Objects defined according to this specification.
255
+ spec_version: Literal["2.1"] = "2.1"
256
+ # The revoked property is only used by STIX Objects that support versioning and indicates whether the object
257
+ # has been revoked.
258
+ revoked: bool | None = None
259
+ # The labels property specifies a set of terms used to describe this object.
260
+ # Where an object has a specific property defined in the specification for characterizing
261
+ # subtypes of that object, the labels property MUST NOT be used for that purpose.
262
+ labels: list[str] | None = None
263
+ # The confidence property identifies the confidence that the creator has in the correctness of their data.
264
+ # The confidence value MUST be a number in the range of 0-100.
265
+ confidence: Annotated[int, Field(ge=0, le=100)] | None = None
266
+ # The lang property identifies the language of the text content in this object.
267
+ # When present, it MUST be a language code conformant to [RFC5646].
268
+ # RFC5646 does not enforce ISO 639-1 alpha-2 or ISO 639-3 alpha-3 formats.
269
+ lang: str | None = None
270
+ # The external_references property specifies a list of external references which refers to non-STIX information.
271
+ external_references: list[ExternalReference] | None = None
272
+ # The object_marking_refs property specifies a list of id properties of marking-definition objects that apply
273
+ # to this object.
274
+ object_marking_refs: list[Identifier] | None = None
275
+ # The granular_markings property specifies a list of granular markings applied to this object.
276
+ granular_markings: list[GranularMarking] | None = None
277
+ # Specifies any extensions of the object, as a dictionary.
278
+ # Dictionary keys SHOULD be the id of a STIX Extension object or the name of a predefined object extension found
279
+ # in this specification, depending on the type of extension being used.
280
+ # The corresponding dictionary values MUST contain the contents of the extension instance.
281
+ # Each extension dictionary MAY contain the property extension_type.
282
+ # The value of this property MUST come from the extension-type-enum enumeration.
283
+ # If the extension_type property is not present, then this is a predefined extension which
284
+ # does not use the extension facility described in section 7.3.
285
+ # When this extension facility is used the extension_type property MUST be present.
286
+ extensions: dict[str, Extension] | None = None
287
+
288
+
289
+ class StixDomain(StixCommon):
290
+ # The created_by_ref property specifies the id property of the identity object that describes the entity that
291
+ # created this object.
292
+ created_by_ref: Identifier | None = None
293
+ # The created property represents the time at which the object was originally created.
294
+ # The created property MUST NOT be changed when creating a new version of the object.
295
+ created: datetime
296
+ # The modified property is only used by STIX Objects that support versioning and represents
297
+ # the time that this particular version of the object was last modified.
298
+ # Object creators MUST set the modified property when creating a new
299
+ # version of an object if the created property was set.
300
+ modified: datetime
301
+
302
+ @model_validator(mode="after")
303
+ def validate_modified_after_created(self) -> Self:
304
+ """
305
+ If the created property is defined, then the value of the modified property for a given object version
306
+ MUST be later than or equal to the value of the created property.
307
+ """
308
+ if self.created > self.modified:
309
+ raise ValueError(
310
+ "The modified property MUST be later than or equal to the value of the created property."
311
+ )
312
+ return self
313
+
314
+
315
+ class StixObservable(StixCommon):
316
+ # This property defines whether or not the data contained within the object has been defanged.
317
+ defanged: bool | None = None
318
+
319
+
320
+ class StixRelationship(StixCommon):
321
+ type: Literal["relationship"] = "relationship" # pyright: ignore[reportIncompatibleVariableOverride]
322
+ relationship_type: str
323
+ # The created property represents the time at which the object was originally created.
324
+ # The created property MUST NOT be changed when creating a new version of the object.
325
+ created: datetime
326
+ # The modified property is only used by STIX Objects that support versioning and represents
327
+ # the time that this particular version of the object was last modified.
328
+ # Object creators MUST set the modified property when creating a new
329
+ # version of an object if the created property was set.
330
+ modified: datetime
331
+ # The created_by_ref property specifies the id property of the identity object that describes the entity that
332
+ # created this object.
333
+ created_by_ref: Identifier | None = None
334
+
335
+ @model_validator(mode="after")
336
+ def validate_modified_after_created(self) -> Self:
337
+ """
338
+ If the created property is defined, then the value of the modified property for a given object version
339
+ MUST be later than or equal to the value of the created property.
340
+ """
341
+ if self.created > self.modified:
342
+ raise ValueError(
343
+ "The modified property MUST be later than or equal to the value of the created property."
344
+ )
345
+ return self
346
+
347
+
348
+ class StixMeta(StixCommon):
349
+ type: str
350
+ # The created property represents the time at which the object was originally created.
351
+ # The created property MUST NOT be changed when creating a new version of the object.
352
+ created: datetime
353
+
354
+
355
+ class StixLanguage(StixMeta):
356
+ # The created_by_ref property specifies the id property of the identity object that describes the entity that
357
+ # created this object.
358
+ created_by_ref: Identifier | None = None
359
+ # The modified property is only used by STIX Objects that support versioning and represents
360
+ # the time that this particular version of the object was last modified.
361
+ # Object creators MUST set the modified property when creating a new
362
+ # version of an object if the created property was set.
363
+ modified: datetime
364
+
365
+ @model_validator(mode="after")
366
+ def validate_modified_after_created(self) -> Self:
367
+ """
368
+ If the created property is defined, then the value of the modified property for a given object version
369
+ MUST be later than or equal to the value of the created property.
370
+ """
371
+ if self.created > self.modified:
372
+ raise ValueError(
373
+ "The modified property MUST be later than or equal to the value of the created property."
374
+ )
375
+ return self
376
+
377
+
378
+ class StixMarking(StixMeta):
379
+ # The created_by_ref property specifies the id property of the identity object that describes the entity that
380
+ # created this object.
381
+ created_by_ref: Identifier | None = None
382
+
383
+
384
+ class StixExtension(StixMeta):
385
+ # The created_by_ref property specifies the id property of the identity object that describes the entity that
386
+ # created this object.
387
+ created_by_ref: Identifier
388
+ # The modified property is only used by STIX Objects that support versioning and represents
389
+ # the time that this particular version of the object was last modified.
390
+ # Object creators MUST set the modified property when creating a new
391
+ # version of an object if the created property was set.
392
+ modified: datetime
393
+
394
+ @model_validator(mode="after")
395
+ def validate_modified_after_created(self) -> Self:
396
+ """
397
+ If the created property is defined, then the value of the modified property for a given object version
398
+ MUST be later than or equal to the value of the created property.
399
+ """
400
+ if self.created > self.modified:
401
+ raise ValueError(
402
+ "The modified property MUST be later than or equal to the value of the created property."
403
+ )
404
+ return self
405
+
406
+
407
+ # 3.4 SCO Deterministic ID Creation
408
+ # To enable deterministic IDs for STIX Cyber-observable Objects (SCOs), each SCO defines a set of one or more properties named "ID Contributing Properties". These properties MAY be used in the default calculation of the id when creating a SCO. In some cases, additional selection of extension properties that contribute to the ID may be described in the ID Contributing Properties section listed on each SCO. The default algorithm that creates the SCO ID based on those named properties is a UUIDv5 as defined in Section 2.9, however, other algorithms for creating the SCO ID MAY be used.
409
+ # Deterministic IDs (UUIDv5) in the example SCOs contained in this specification were computed using the algorithm defined in section 2.9. Every attempt was made for these IDs to be accurate. Certain IDs which were used in reference properties of the examples did not include the actual object, and therefore it was impossible to accurately compute the appropriate UUIDv5. In these cases, a UUIDv4 was generated.
@@ -0,0 +1,20 @@
1
+ from pydantic import ValidationInfo
2
+
3
+
4
+ def validate_identifier(value: str) -> str:
5
+ _type, _uuid = value.split("--")
6
+ if not _type and not _uuid:
7
+ raise ValueError("Invalid identifier format.")
8
+ return value
9
+
10
+
11
+ def validate_bin_field(value: str, info: ValidationInfo) -> str:
12
+ if info.field_name and not info.field_name.endswith("_bin"):
13
+ raise ValueError("The property name MUST end with '_bin'.")
14
+ return value
15
+
16
+
17
+ def validate_hex_field(value: str, info: ValidationInfo) -> str:
18
+ if info.field_name and info.field_name.endswith("_hex"):
19
+ raise ValueError("The property name MUST end with '_hex'.")
20
+ return value