0
# Utilities and Type Checking
1
2
Utility functions for working with STIX objects including type checking, timestamp handling, object deduplication, and specification version detection.
3
4
## Capabilities
5
6
### Type Checking Functions
7
8
Functions to identify and validate STIX object types.
9
10
```python { .api }
11
def is_sdo(value, stix_version="2.1"):
12
"""
13
Check if value is a STIX Domain Object.
14
15
Parameters:
16
- value: Object to check (STIX object, dict, or string)
17
- stix_version (str): STIX specification version ("2.0" or "2.1")
18
19
Returns:
20
bool: True if value is an SDO, False otherwise
21
"""
22
23
def is_sco(value, stix_version="2.1"):
24
"""
25
Check if value is a STIX Cyber Observable Object.
26
27
Parameters:
28
- value: Object to check (STIX object, dict, or string)
29
- stix_version (str): STIX specification version ("2.0" or "2.1")
30
31
Returns:
32
bool: True if value is an SCO, False otherwise
33
"""
34
35
def is_sro(value, stix_version="2.1"):
36
"""
37
Check if value is a STIX Relationship Object.
38
39
Parameters:
40
- value: Object to check (STIX object, dict, or string)
41
- stix_version (str): STIX specification version ("2.0" or "2.1")
42
43
Returns:
44
bool: True if value is an SRO, False otherwise
45
"""
46
47
def is_object(value, stix_version="2.1"):
48
"""
49
Check if value is any STIX object (SDO, SRO, or marking).
50
51
Parameters:
52
- value: Object to check (STIX object, dict, or string)
53
- stix_version (str): STIX specification version ("2.0" or "2.1")
54
55
Returns:
56
bool: True if value is a STIX object, False otherwise
57
"""
58
59
def is_marking(value, stix_version="2.1"):
60
"""
61
Check if value is a STIX marking definition.
62
63
Parameters:
64
- value: Object to check (STIX object, dict, or string)
65
- stix_version (str): STIX specification version ("2.0" or "2.1")
66
67
Returns:
68
bool: True if value is a marking definition, False otherwise
69
"""
70
71
def is_stix_type(value, stix_version="2.1", *types):
72
"""
73
Check if value is one of the specified STIX types.
74
75
Parameters:
76
- value: Object to check
77
- stix_version (str): STIX specification version
78
- *types: Variable number of STIXTypeClass values to check against
79
80
Returns:
81
bool: True if value matches any of the specified types
82
"""
83
```
84
85
Usage examples:
86
87
```python
88
from stix2 import is_sdo, is_sco, is_sro, is_object, is_marking
89
from stix2 import Indicator, Malware, File, Relationship, MarkingDefinition
90
91
# Create test objects
92
indicator = Indicator(
93
name="Test Indicator",
94
indicator_types=["malicious-activity"],
95
pattern_type="stix",
96
pattern="[file:hashes.MD5 = 'abc123']"
97
)
98
99
malware = Malware(
100
name="Test Malware",
101
malware_types=["trojan"]
102
)
103
104
file_obj = File(
105
hashes={"MD5": "abc123"},
106
name="test.exe"
107
)
108
109
relationship = Relationship(
110
relationship_type="indicates",
111
source_ref=indicator.id,
112
target_ref=malware.id
113
)
114
115
marking = MarkingDefinition(
116
definition_type="statement",
117
definition={"statement": "Internal Use Only"}
118
)
119
120
# Type checking
121
print(f"Indicator is SDO: {is_sdo(indicator)}") # True
122
print(f"Malware is SDO: {is_sdo(malware)}") # True
123
print(f"File is SDO: {is_sdo(file_obj)}") # False
124
125
print(f"File is SCO: {is_sco(file_obj)}") # True
126
print(f"Indicator is SCO: {is_sco(indicator)}") # False
127
128
print(f"Relationship is SRO: {is_sro(relationship)}") # True
129
print(f"Indicator is SRO: {is_sro(indicator)}") # False
130
131
print(f"Marking is marking: {is_marking(marking)}") # True
132
print(f"Indicator is marking: {is_marking(indicator)}")# False
133
134
# Check any STIX object
135
print(f"Indicator is STIX object: {is_object(indicator)}") # True
136
print(f"File is STIX object: {is_object(file_obj)}") # True
137
print(f"Relationship is STIX object: {is_object(relationship)}")# True
138
139
# Check with dictionaries
140
indicator_dict = {
141
"type": "indicator",
142
"id": "indicator--12345678-1234-1234-1234-123456789012",
143
"name": "Test Indicator"
144
}
145
print(f"Dict is SDO: {is_sdo(indicator_dict)}") # True
146
147
# Check with type strings
148
print(f"'indicator' is SDO: {is_sdo('indicator')}") # True
149
print(f"'file' is SCO: {is_sco('file')}") # True
150
print(f"'relationship' is SRO: {is_sro('relationship')}")# True
151
```
152
153
### Timestamp Utilities
154
155
Functions for working with STIX timestamps and datetime objects.
156
157
```python { .api }
158
def get_timestamp():
159
"""
160
Get current timestamp in STIX format.
161
162
Returns:
163
str: Current timestamp in STIX format (ISO 8601)
164
"""
165
166
def format_datetime(dttm):
167
"""
168
Format datetime object to STIX timestamp string.
169
170
Parameters:
171
- dttm (datetime): Datetime object to format
172
173
Returns:
174
str: Formatted timestamp string
175
"""
176
177
def parse_into_datetime(value, precision=None, allow_none=False):
178
"""
179
Parse string into datetime object with STIX-specific handling.
180
181
Parameters:
182
- value: String or datetime to parse
183
- precision (Precision): Timestamp precision level
184
- allow_none (bool): Allow None values
185
186
Returns:
187
STIXdatetime: Parsed datetime object
188
189
Raises:
190
ValueError: If timestamp format is invalid
191
"""
192
193
class STIXdatetime:
194
"""
195
STIX-specific datetime class with precision handling.
196
197
Inherits from datetime.datetime with additional STIX-specific
198
formatting and precision methods.
199
"""
200
201
class Precision:
202
"""
203
Enumeration of timestamp precision levels.
204
205
Values:
206
- YEAR
207
- MONTH
208
- DAY
209
- HOUR
210
- MINUTE
211
- SECOND
212
- MILLISECOND
213
"""
214
```
215
216
Usage examples:
217
218
```python
219
from stix2 import get_timestamp, format_datetime, parse_into_datetime, STIXdatetime
220
from datetime import datetime
221
222
# Get current timestamp in STIX format
223
current_time = get_timestamp()
224
print(f"Current STIX timestamp: {current_time}")
225
# Output: "2021-04-23T10:30:45.123Z"
226
227
# Format datetime object
228
dt = datetime(2021, 4, 23, 10, 30, 45, 123000)
229
stix_formatted = format_datetime(dt)
230
print(f"Formatted datetime: {stix_formatted}")
231
# Output: "2021-04-23T10:30:45.123Z"
232
233
# Parse timestamp strings
234
timestamp_str = "2021-04-23T10:30:45.123Z"
235
parsed_dt = parse_into_datetime(timestamp_str)
236
print(f"Parsed datetime: {parsed_dt}")
237
print(f"Type: {type(parsed_dt)}") # STIXdatetime
238
239
# Parse with different precision
240
from stix2.utils import Precision
241
242
year_precision = parse_into_datetime("2021", precision=Precision.YEAR)
243
day_precision = parse_into_datetime("2021-04-23", precision=Precision.DAY)
244
245
print(f"Year precision: {year_precision}") # 2021-01-01T00:00:00.000Z
246
print(f"Day precision: {day_precision}") # 2021-04-23T00:00:00.000Z
247
248
# STIX datetime usage
249
stix_dt = STIXdatetime(2021, 4, 23, 10, 30, 45, 123000)
250
print(f"STIX datetime: {stix_dt}")
251
252
# Parse various timestamp formats
253
timestamps = [
254
"2021-04-23T10:30:45Z",
255
"2021-04-23T10:30:45.123Z",
256
"2021-04-23T10:30:45+00:00",
257
"2021-04-23 10:30:45"
258
]
259
260
for ts in timestamps:
261
try:
262
parsed = parse_into_datetime(ts)
263
print(f"{ts} -> {parsed}")
264
except ValueError as e:
265
print(f"{ts} -> Error: {e}")
266
```
267
268
### Object Utilities
269
270
Utilities for working with STIX objects and their properties.
271
272
```python { .api }
273
def deduplicate(stix_obj_list):
274
"""
275
Remove duplicate STIX objects from a list.
276
277
Parameters:
278
- stix_obj_list (list): List of STIX objects
279
280
Returns:
281
list: List with duplicates removed (keeps most recent version)
282
"""
283
284
def get_class_hierarchy_names(obj):
285
"""
286
Get class hierarchy names for a STIX object.
287
288
Parameters:
289
- obj: STIX object
290
291
Returns:
292
list: List of class names in inheritance hierarchy
293
"""
294
295
def get_type_from_id(stix_id):
296
"""
297
Extract STIX type from STIX identifier.
298
299
Parameters:
300
- stix_id (str): STIX object identifier
301
302
Returns:
303
str: STIX object type or None if invalid ID
304
"""
305
306
def detect_spec_version(stix_dict):
307
"""
308
Detect STIX specification version from object dictionary.
309
310
Parameters:
311
- stix_dict (dict): STIX object as dictionary
312
313
Returns:
314
str: Detected STIX version ("2.0" or "2.1")
315
"""
316
317
def to_enum(value, enum_type, enum_default=None):
318
"""
319
Convert value to enumeration type with default fallback.
320
321
Parameters:
322
- value: Value to convert
323
- enum_type: Target enumeration type
324
- enum_default: Default value if conversion fails
325
326
Returns:
327
Enum value or default
328
"""
329
```
330
331
Usage examples:
332
333
```python
334
from stix2 import deduplicate, get_type_from_id, detect_spec_version
335
from stix2 import Indicator, new_version
336
337
# Create objects with duplicates
338
indicator_v1 = Indicator(
339
name="Test Indicator",
340
indicator_types=["malicious-activity"],
341
pattern_type="stix",
342
pattern="[file:hashes.MD5 = 'abc123']"
343
)
344
345
indicator_v2 = new_version(indicator_v1, confidence=85)
346
indicator_v3 = new_version(indicator_v2, confidence=95)
347
348
# Different indicator
349
other_indicator = Indicator(
350
name="Other Indicator",
351
indicator_types=["malicious-activity"],
352
pattern_type="stix",
353
pattern="[ip-addr:value = '192.168.1.1']"
354
)
355
356
# List with duplicates (multiple versions of same object)
357
object_list = [indicator_v1, indicator_v2, indicator_v3, other_indicator, indicator_v1]
358
359
# Remove duplicates (keeps most recent version)
360
deduplicated = deduplicate(object_list)
361
print(f"Original list: {len(object_list)} objects")
362
print(f"Deduplicated: {len(deduplicated)} objects")
363
364
# Should contain indicator_v3 (most recent) and other_indicator
365
for obj in deduplicated:
366
if obj.id == indicator_v1.id:
367
print(f"Kept version modified: {obj.modified}") # Should be v3
368
369
# Extract type from STIX ID
370
stix_id = "indicator--12345678-1234-1234-1234-123456789012"
371
obj_type = get_type_from_id(stix_id)
372
print(f"Type from ID: {obj_type}") # "indicator"
373
374
# Test various ID formats
375
test_ids = [
376
"indicator--12345678-1234-1234-1234-123456789012",
377
"malware--abcdef12-3456-7890-abcd-ef1234567890",
378
"relationship--fedcba98-7654-3210-fedc-ba9876543210",
379
"invalid-id-format"
380
]
381
382
for test_id in test_ids:
383
obj_type = get_type_from_id(test_id)
384
print(f"{test_id} -> {obj_type}")
385
386
# Detect STIX specification version
387
stix_2_0_dict = {
388
"type": "indicator",
389
"id": "indicator--12345678-1234-1234-1234-123456789012",
390
"created": "2021-04-23T10:30:00.000Z",
391
"modified": "2021-04-23T10:30:00.000Z",
392
"labels": ["malicious-activity"],
393
"pattern": "[file:hashes.MD5 = 'abc123']"
394
}
395
396
stix_2_1_dict = {
397
"type": "indicator",
398
"spec_version": "2.1",
399
"id": "indicator--12345678-1234-1234-1234-123456789012",
400
"created": "2021-04-23T10:30:00.000Z",
401
"modified": "2021-04-23T10:30:00.000Z",
402
"indicator_types": ["malicious-activity"],
403
"pattern_type": "stix",
404
"pattern": "[file:hashes.MD5 = 'abc123']"
405
}
406
407
version_2_0 = detect_spec_version(stix_2_0_dict)
408
version_2_1 = detect_spec_version(stix_2_1_dict)
409
410
print(f"First dict version: {version_2_0}") # "2.0"
411
print(f"Second dict version: {version_2_1}") # "2.1"
412
413
# Class hierarchy inspection
414
hierarchy = get_class_hierarchy_names(indicator_v1)
415
print(f"Indicator class hierarchy: {hierarchy}")
416
```
417
418
### Advanced Type Checking
419
420
More sophisticated type checking and classification utilities.
421
422
```python
423
from stix2.utils import STIXTypeClass, is_stix_type
424
425
# STIXTypeClass enumeration values
426
print("Available STIX type classes:")
427
for type_class in STIXTypeClass:
428
print(f" - {type_class.name}: {type_class.value}")
429
430
# Check specific type classes
431
print(f"Indicator is SDO: {is_stix_type(indicator, '2.1', STIXTypeClass.SDO)}")
432
print(f"File is SCO: {is_stix_type(file_obj, '2.1', STIXTypeClass.SCO)}")
433
print(f"Relationship is SRO: {is_stix_type(relationship, '2.1', STIXTypeClass.SRO)}")
434
435
# Check multiple type classes
436
is_domain_or_relationship = is_stix_type(
437
indicator,
438
'2.1',
439
STIXTypeClass.SDO,
440
STIXTypeClass.SRO
441
)
442
print(f"Indicator is SDO or SRO: {is_domain_or_relationship}") # True
443
444
# Custom type classification function
445
def classify_stix_object(obj, version="2.1"):
446
"""Classify STIX object into detailed categories."""
447
if is_sdo(obj, version):
448
obj_type = obj.type if hasattr(obj, 'type') else str(obj)
449
450
threat_objects = ['threat-actor', 'intrusion-set', 'campaign']
451
malware_objects = ['malware', 'tool']
452
indicator_objects = ['indicator', 'observed-data']
453
target_objects = ['identity', 'location', 'infrastructure']
454
context_objects = ['attack-pattern', 'course-of-action', 'vulnerability']
455
456
if obj_type in threat_objects:
457
return "Threat Intelligence"
458
elif obj_type in malware_objects:
459
return "Malware/Tools"
460
elif obj_type in indicator_objects:
461
return "Indicators/Observables"
462
elif obj_type in target_objects:
463
return "Targets/Assets"
464
elif obj_type in context_objects:
465
return "Context/Mitigation"
466
else:
467
return "Other SDO"
468
469
elif is_sco(obj, version):
470
return "Cyber Observable"
471
elif is_sro(obj, version):
472
return "Relationship"
473
elif is_marking(obj, version):
474
return "Marking Definition"
475
else:
476
return "Unknown"
477
478
# Classify objects
479
test_objects = [indicator, malware, file_obj, relationship, marking]
480
for obj in test_objects:
481
classification = classify_stix_object(obj)
482
obj_name = getattr(obj, 'name', getattr(obj, 'type', str(obj)))
483
print(f"{obj_name}: {classification}")
484
```
485
486
### Confidence Scales
487
488
Functions for converting between different confidence scale representations. STIX 2.1 supports multiple confidence scales, and these functions provide standardized conversions between common scale formats.
489
490
```python { .api }
491
def none_low_med_high_to_value(scale_value):
492
"""
493
Transform string value from None/Low/Med/High scale to integer.
494
495
Parameters:
496
- scale_value (str): Scale value ("None", "Low", "Med", "High")
497
498
Returns:
499
int: Confidence value (0=None, 15=Low, 50=Med, 85=High)
500
501
Raises:
502
ValueError: If scale_value is not recognized
503
"""
504
505
def value_to_none_low_medium_high(confidence_value):
506
"""
507
Transform integer confidence value to None/Low/Med/High scale.
508
509
Parameters:
510
- confidence_value (int): Integer value between 0-100
511
512
Returns:
513
str: Scale string (0="None", 1-29="Low", 30-69="Med", 70-100="High")
514
515
Raises:
516
ValueError: If confidence_value is out of bounds
517
"""
518
519
def zero_ten_to_value(scale_value):
520
"""
521
Transform string value from 0-10 scale to confidence integer.
522
523
Parameters:
524
- scale_value (str): Scale value from "0" to "10"
525
526
Returns:
527
int: Confidence value (0-100, mapped from 0-10 scale)
528
529
Raises:
530
ValueError: If scale_value is not valid 0-10 string
531
"""
532
533
def value_to_zero_ten(confidence_value):
534
"""
535
Transform integer confidence value to 0-10 scale.
536
537
Parameters:
538
- confidence_value (int): Integer value between 0-100
539
540
Returns:
541
str: Scale string from "0" to "10"
542
543
Raises:
544
ValueError: If confidence_value is out of bounds
545
"""
546
547
def admiralty_credibility_to_value(scale_value):
548
"""
549
Transform Admiralty Credibility scale to confidence integer.
550
551
Parameters:
552
- scale_value (str): Admiralty scale ("A" through "F")
553
554
Returns:
555
int: Confidence value mapped from Admiralty scale
556
557
Raises:
558
ValueError: If scale_value is not valid Admiralty scale
559
"""
560
561
def value_to_admiralty_credibility(confidence_value):
562
"""
563
Transform confidence integer to Admiralty Credibility scale.
564
565
Parameters:
566
- confidence_value (int): Integer value between 0-100
567
568
Returns:
569
str: Admiralty scale value ("A" through "F")
570
571
Raises:
572
ValueError: If confidence_value is out of bounds
573
"""
574
```
575
576
Usage examples:
577
578
```python
579
from stix2.confidence.scales import (
580
none_low_med_high_to_value, value_to_none_low_medium_high,
581
zero_ten_to_value, value_to_zero_ten,
582
admiralty_credibility_to_value, value_to_admiralty_credibility
583
)
584
585
# None/Low/Med/High scale conversions
586
print("None/Low/Med/High Scale:")
587
scales = ["None", "Low", "Med", "High"]
588
for scale in scales:
589
value = none_low_med_high_to_value(scale)
590
print(f" {scale} -> {value}")
591
592
# Convert confidence values back to scale
593
confidence_values = [0, 15, 35, 50, 75, 85, 100]
594
print("\nConfidence to None/Low/Med/High:")
595
for conf in confidence_values:
596
try:
597
scale = value_to_none_low_medium_high(conf)
598
print(f" {conf} -> {scale}")
599
except ValueError as e:
600
print(f" {conf} -> Error: {e}")
601
602
# 0-10 scale conversions
603
print("\n0-10 Scale:")
604
for i in range(11):
605
str_scale = str(i)
606
value = zero_ten_to_value(str_scale)
607
print(f" {str_scale} -> {value}")
608
609
# Convert back to 0-10 scale
610
print("\nConfidence to 0-10 Scale:")
611
test_values = [0, 25, 50, 75, 100]
612
for conf in test_values:
613
try:
614
scale = value_to_zero_ten(conf)
615
print(f" {conf} -> {scale}")
616
except ValueError as e:
617
print(f" {conf} -> Error: {e}")
618
619
# Admiralty Credibility scale
620
print("\nAdmiralty Credibility Scale:")
621
admiralty_scales = ["A", "B", "C", "D", "E", "F"]
622
for scale in admiralty_scales:
623
try:
624
value = admiralty_credibility_to_value(scale)
625
print(f" {scale} -> {value}")
626
except ValueError as e:
627
print(f" {scale} -> Error: {e}")
628
629
# Practical usage in STIX objects
630
from stix2 import Indicator
631
632
# Create indicators with confidence from different scales
633
high_confidence = none_low_med_high_to_value("High") # 85
634
medium_confidence = zero_ten_to_value("5") # 50
635
low_confidence = value_to_none_low_medium_high(20) # "Low"
636
637
indicator_high = Indicator(
638
name="High Confidence Indicator",
639
indicator_types=["malicious-activity"],
640
pattern_type="stix",
641
pattern="[file:hashes.md5 = 'abc123']",
642
confidence=high_confidence
643
)
644
645
indicator_medium = Indicator(
646
name="Medium Confidence Indicator",
647
indicator_types=["malicious-activity"],
648
pattern_type="stix",
649
pattern="[ip-addr:value = '192.168.1.1']",
650
confidence=medium_confidence
651
)
652
653
print(f"\nHigh confidence indicator: {indicator_high.confidence}")
654
print(f"Medium confidence indicator: {indicator_medium.confidence}")
655
656
# Convert indicator confidence back to human-readable scales
657
high_scale = value_to_none_low_medium_high(indicator_high.confidence)
658
medium_scale = value_to_zero_ten(indicator_medium.confidence)
659
660
print(f"High confidence as scale: {high_scale}") # "High"
661
print(f"Medium confidence as 0-10: {medium_scale}") # "5"
662
663
# Error handling for invalid inputs
664
try:
665
invalid_scale = none_low_med_high_to_value("Invalid")
666
except ValueError as e:
667
print(f"Invalid scale error: {e}")
668
669
try:
670
invalid_confidence = value_to_none_low_medium_high(150)
671
except ValueError as e:
672
print(f"Invalid confidence error: {e}")
673
```
674
675
### Error Handling Utilities
676
677
Utilities for handling and validating STIX objects with error checking.
678
679
```python
680
from stix2.exceptions import STIXError
681
682
def safe_type_check(obj, check_func, default=False):
683
"""Safely perform type check with error handling."""
684
try:
685
return check_func(obj)
686
except (STIXError, AttributeError, TypeError) as e:
687
print(f"Type check error: {e}")
688
return default
689
690
def validate_stix_object(obj):
691
"""Validate STIX object structure and properties."""
692
validation_results = {
693
'valid': True,
694
'errors': [],
695
'warnings': []
696
}
697
698
# Check if it's a recognized STIX object
699
if not is_object(obj):
700
validation_results['valid'] = False
701
validation_results['errors'].append("Not a valid STIX object")
702
return validation_results
703
704
# Check required properties
705
if hasattr(obj, 'id'):
706
obj_type = get_type_from_id(obj.id)
707
if obj_type != getattr(obj, 'type', None):
708
validation_results['warnings'].append(
709
f"ID type '{obj_type}' doesn't match object type '{obj.type}'"
710
)
711
else:
712
validation_results['errors'].append("Missing required 'id' property")
713
validation_results['valid'] = False
714
715
# Check timestamps
716
if hasattr(obj, 'created') and hasattr(obj, 'modified'):
717
try:
718
created = parse_into_datetime(obj.created)
719
modified = parse_into_datetime(obj.modified)
720
if modified < created:
721
validation_results['warnings'].append(
722
"Modified timestamp is before created timestamp"
723
)
724
except ValueError as e:
725
validation_results['errors'].append(f"Invalid timestamp format: {e}")
726
727
# Type-specific validations
728
if is_sdo(obj):
729
validation_results['warnings'].append("SDO validation passed")
730
elif is_sco(obj):
731
validation_results['warnings'].append("SCO validation passed")
732
elif is_sro(obj):
733
# Check relationship references
734
if hasattr(obj, 'source_ref') and hasattr(obj, 'target_ref'):
735
source_type = get_type_from_id(obj.source_ref)
736
target_type = get_type_from_id(obj.target_ref)
737
if not source_type or not target_type:
738
validation_results['warnings'].append(
739
"Invalid source_ref or target_ref format"
740
)
741
742
return validation_results
743
744
# Test validation
745
validation_result = validate_stix_object(indicator)
746
print(f"Validation result: {validation_result}")
747
748
# Safe type checking
749
unknown_object = {"type": "unknown-type"}
750
is_sdo_safe = safe_type_check(unknown_object, is_sdo, default=False)
751
print(f"Unknown object is SDO: {is_sdo_safe}") # False
752
```