Merge pull request #42 from oasis-open/bundles Parse bundles correctly
commit
cb5c4ad080
248
README.md
248
README.md
|
@ -1,248 +0,0 @@
|
||||||
[](https://travis-ci.org/oasis-open/cti-python-stix2)
|
|
||||||
[](https://codecov.io/gh/oasis-open/cti-python-stix2)
|
|
||||||
|
|
||||||
# cti-python-stix2
|
|
||||||
|
|
||||||
*This is an [OASIS Open Repository](https://www.oasis-open.org/resources/open-repositories/). See the [Governance](#governance) section for more information.*
|
|
||||||
|
|
||||||
This repository provides Python APIs for serializing and de-serializing STIX 2 JSON content, along with higher-level APIs for common tasks, including data markings, versioning, and for resolving STIX IDs across multiple data sources.
|
|
||||||
|
|
||||||
For more information, see [the documentation](https://stix2.readthedocs.io/en/latest/) on ReadTheDocs.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Install with [`pip`](https://pip.pypa.io/en/stable/):
|
|
||||||
|
|
||||||
```
|
|
||||||
pip install stix2
|
|
||||||
```
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
### Creating STIX Domain Objects
|
|
||||||
|
|
||||||
To create a STIX object, provide keyword arguments to the type's constructor:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from stix2 import Indicator
|
|
||||||
|
|
||||||
indicator = Indicator(name="File hash for malware variant",
|
|
||||||
labels=['malicious-activity'],
|
|
||||||
pattern='file:hashes.md5 = "d41d8cd98f00b204e9800998ecf8427e"')
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
Certain required attributes of all objects will be set automatically if not
|
|
||||||
provided as keyword arguments:
|
|
||||||
|
|
||||||
- If not provided, `type` will be set automatically to the correct type.
|
|
||||||
You can also provide the type explicitly, but this is not necessary:
|
|
||||||
|
|
||||||
```python
|
|
||||||
indicator = Indicator(type='indicator', ...)
|
|
||||||
```
|
|
||||||
|
|
||||||
Passing a value for `type` that does not match the class being constructed
|
|
||||||
will cause an error:
|
|
||||||
|
|
||||||
```python
|
|
||||||
>>> indicator = Indicator(type='xxx', ...)
|
|
||||||
stix2.exceptions.InvalidValueError: Invalid value for Indicator 'type': must equal 'indicator'.
|
|
||||||
```
|
|
||||||
|
|
||||||
- If not provided, `id` will be generated randomly. If you provide an `id`
|
|
||||||
argument, it must begin with the correct prefix:
|
|
||||||
|
|
||||||
```python
|
|
||||||
>>> indicator = Indicator(id="campaign--63ce9068-b5ab-47fa-a2cf-a602ea01f21a")
|
|
||||||
stix2.exceptions.InvalidValueError: Invalid value for Indicator 'id': must start with 'indicator--'.
|
|
||||||
```
|
|
||||||
|
|
||||||
- If not provided, `created` and `modified` will be set to the (same) current
|
|
||||||
time.
|
|
||||||
|
|
||||||
For indicators, `labels` and `pattern` are required and cannot be set
|
|
||||||
automatically. Trying to create an indicator that is missing one of these
|
|
||||||
properties will result in an error:
|
|
||||||
|
|
||||||
```python
|
|
||||||
>>> indicator = Indicator()
|
|
||||||
stix2.exceptions.MissingPropertiesError: No values for required properties for Indicator: (labels, pattern).
|
|
||||||
```
|
|
||||||
|
|
||||||
However, the required `valid_from` attribute on Indicators will be set to the
|
|
||||||
current time if not provided as a keyword argument.
|
|
||||||
|
|
||||||
Once created, the object acts like a frozen dictionary. Properties can be
|
|
||||||
accessed using the standard Python dictionary syntax:
|
|
||||||
|
|
||||||
```python
|
|
||||||
>>> indicator['name']
|
|
||||||
'File hash for malware variant'
|
|
||||||
```
|
|
||||||
|
|
||||||
TBD: Should we allow property access using the standard Python attribute syntax?
|
|
||||||
|
|
||||||
```python
|
|
||||||
>>> indicator.name
|
|
||||||
'File hash for malware variant'
|
|
||||||
```
|
|
||||||
|
|
||||||
Attempting to modify any attributes will raise an error:
|
|
||||||
|
|
||||||
```python
|
|
||||||
>>> indicator['name'] = "This is a revised name"
|
|
||||||
TypeError: 'Indicator' object does not support item assignment
|
|
||||||
>>> indicator.name = "This is a revised name"
|
|
||||||
stix2.exceptions.ImmutableError: Cannot modify properties after creation.
|
|
||||||
```
|
|
||||||
|
|
||||||
To update the properties of an object, see [Versioning](#versioning) below.
|
|
||||||
|
|
||||||
Creating a Malware object follows the same pattern:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from stix2 import Malware
|
|
||||||
|
|
||||||
malware = Malware(name="Poison Ivy",
|
|
||||||
labels=['remote-access-trojan'])
|
|
||||||
```
|
|
||||||
|
|
||||||
As with indicators, the `type`, `id`, `created`, and `modified` properties will
|
|
||||||
be set automatically if not provided. For Malware objects, the `labels` and
|
|
||||||
`name` properties must be provided.
|
|
||||||
|
|
||||||
### Creating Relationships
|
|
||||||
|
|
||||||
STIX 2 Relationships are separate objects, not properties of the object on
|
|
||||||
either side of the relationship. They are constructed similarly to other STIX
|
|
||||||
objects. The `type`, `id`, `created`, and `modified` properties are added
|
|
||||||
automatically if not provided. Callers must provide the `relationship_type`,
|
|
||||||
`source_ref`, and `target_ref` properties.
|
|
||||||
|
|
||||||
```python
|
|
||||||
from stix2 import Relationship
|
|
||||||
|
|
||||||
relationship = Relationship(relationship_type='indicates',
|
|
||||||
source_ref=indicator.id,
|
|
||||||
target_ref=malware.id)
|
|
||||||
```
|
|
||||||
|
|
||||||
The `source_ref` and `target_ref` properties can be either the ID's of other
|
|
||||||
STIX objects, or the STIX objects themselves. For readability, Relationship
|
|
||||||
objects can also be constructed with the `source_ref`, `relationship_type`, and
|
|
||||||
`target_ref` as positional (non-keyword) arguments:
|
|
||||||
|
|
||||||
```python
|
|
||||||
relationship = Relationship(indicator, 'indicates', malware)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Creating Bundles
|
|
||||||
|
|
||||||
STIX Bundles can be created by passing objects as arguments to the Bundle
|
|
||||||
constructor. All required properties (`type`, `id`, and `spec_version`) will be
|
|
||||||
set automatically if not provided, or can be provided as keyword arguments:
|
|
||||||
|
|
||||||
```python
|
|
||||||
from stix2 import bundle
|
|
||||||
|
|
||||||
bundle = Bundle(indicator, malware, relationship)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Serializing STIX objects
|
|
||||||
|
|
||||||
The string representation of all STIX classes is a valid STIX JSON object.
|
|
||||||
|
|
||||||
```python
|
|
||||||
indicator = Indicator(...)
|
|
||||||
|
|
||||||
print(str(indicator))
|
|
||||||
```
|
|
||||||
|
|
||||||
### Versioning
|
|
||||||
|
|
||||||
TBD
|
|
||||||
|
|
||||||
|
|
||||||
## Governance
|
|
||||||
|
|
||||||
This GitHub public repository (
|
|
||||||
**<https://github.com/oasis-open/cti-python-stix2>** ) was [proposed](https://lists.oasis-open.org/archives/cti/201702/msg00008.html)
|
|
||||||
and
|
|
||||||
[approved](https://www.oasis-open.org/committees/download.php/60009/)
|
|
||||||
\[[bis](https://issues.oasis-open.org/browse/TCADMIN-2549)\] by the [OASIS Cyber Threat Intelligence (CTI)
|
|
||||||
TC](https://www.oasis-open.org/committees/cti/) as an [OASIS Open
|
|
||||||
Repository](https://www.oasis-open.org/resources/open-repositories/) to
|
|
||||||
support development of open source resources related to Technical
|
|
||||||
Committee work.
|
|
||||||
|
|
||||||
While this Open Repository remains associated with the sponsor TC, its
|
|
||||||
development priorities, leadership, intellectual property terms,
|
|
||||||
participation rules, and other matters of governance are [separate and
|
|
||||||
distinct](https://github.com/oasis-open/cti-python-stix2/blob/master/CONTRIBUTING.md#governance-distinct-from-oasis-tc-process)
|
|
||||||
from the OASIS TC Process and related policies.
|
|
||||||
|
|
||||||
All contributions made to this Open Repository are subject to open
|
|
||||||
source license terms expressed in the [BSD-3-Clause
|
|
||||||
License](https://www.oasis-open.org/sites/www.oasis-open.org/files/BSD-3-Clause.txt).
|
|
||||||
That license was selected as the declared ["Applicable
|
|
||||||
License"](https://www.oasis-open.org/resources/open-repositories/licenses)
|
|
||||||
when the Open Repository was created.
|
|
||||||
|
|
||||||
As documented in ["Public Participation
|
|
||||||
Invited](https://github.com/oasis-open/cti-python-stix2/blob/master/CONTRIBUTING.md#public-participation-invited)",
|
|
||||||
contributions to this OASIS Open Repository are invited from all
|
|
||||||
parties, whether affiliated with OASIS or not. Participants must have a
|
|
||||||
GitHub account, but no fees or OASIS membership obligations are
|
|
||||||
required. Participation is expected to be consistent with the [OASIS
|
|
||||||
Open Repository Guidelines and
|
|
||||||
Procedures](https://www.oasis-open.org/policies-guidelines/open-repositories),
|
|
||||||
the open source
|
|
||||||
[LICENSE](https://github.com/oasis-open/cti-python-stix2/blob/master/LICENSE)
|
|
||||||
designated for this particular repository, and the requirement for an
|
|
||||||
[Individual Contributor License
|
|
||||||
Agreement](https://www.oasis-open.org/resources/open-repositories/cla/individual-cla)
|
|
||||||
that governs intellectual property.
|
|
||||||
|
|
||||||
|
|
||||||
### <a id="maintainers">Maintainers</a>
|
|
||||||
|
|
||||||
Open Repository
|
|
||||||
[Maintainers](https://www.oasis-open.org/resources/open-repositories/maintainers-guide)
|
|
||||||
are responsible for oversight of this project's community development
|
|
||||||
activities, including evaluation of GitHub [pull
|
|
||||||
requests](https://github.com/oasis-open/cti-python-stix2/blob/master/CONTRIBUTING.md#fork-and-pull-collaboration-model)
|
|
||||||
and
|
|
||||||
[preserving](https://www.oasis-open.org/policies-guidelines/open-repositories#repositoryManagement)
|
|
||||||
open source principles of openness and fairness. Maintainers are
|
|
||||||
recognized and trusted experts who serve to implement community goals
|
|
||||||
and consensus design preferences.
|
|
||||||
|
|
||||||
Initially, the associated TC members have designated one or more persons
|
|
||||||
to serve as Maintainer(s); subsequently, participating community members
|
|
||||||
may select additional or substitute Maintainers, per [consensus
|
|
||||||
agreements](https://www.oasis-open.org/resources/open-repositories/maintainers-guide#additionalMaintainers).
|
|
||||||
|
|
||||||
**<a id="currentMaintainers">Current Maintainers of this Open Repository</a>**
|
|
||||||
|
|
||||||
* [Greg Back](mailto:gback@mitre.org); GitHub ID: <https://github.com/gtback/>; WWW: [MITRE Corporation](http://www.mitre.org/)
|
|
||||||
* [Chris Lenk](mailto:clenk@mitre.org); GitHub ID: <https://github.com/clenk/>; WWW: [MITRE Corporation](http://www.mitre.org/)
|
|
||||||
|
|
||||||
## <a id="aboutOpenRepos">About OASIS Open Repositories</a>
|
|
||||||
|
|
||||||
* [Open Repositories: Overview and Resources](https://www.oasis-open.org/resources/open-repositories/)
|
|
||||||
* [Frequently Asked Questions](https://www.oasis-open.org/resources/open-repositories/faq)
|
|
||||||
* [Open Source Licenses](https://www.oasis-open.org/resources/open-repositories/licenses)
|
|
||||||
* [Contributor License Agreements (CLAs)](https://www.oasis-open.org/resources/open-repositories/cla)
|
|
||||||
* [Maintainers' Guidelines and Agreement](https://www.oasis-open.org/resources/open-repositories/maintainers-guide)
|
|
||||||
|
|
||||||
|
|
||||||
## <a id="feedback">Feedback</a>
|
|
||||||
|
|
||||||
Questions or comments about this Open Repository's activities should be
|
|
||||||
composed as GitHub issues or comments. If use of an issue/comment is not
|
|
||||||
possible or appropriate, questions may be directed by email to the
|
|
||||||
Maintainer(s) [listed above](#currentMaintainers). Please send general
|
|
||||||
questions about Open Repository participation to OASIS Staff at
|
|
||||||
<repository-admin@oasis-open.org> and any specific CLA-related questions
|
|
||||||
to <repository-cla@oasis-open.org>.
|
|
|
@ -0,0 +1,280 @@
|
||||||
|
|Build Status| |codecov|
|
||||||
|
|
||||||
|
cti-python-stix2
|
||||||
|
================
|
||||||
|
|
||||||
|
This is an `OASIS Open
|
||||||
|
Repository <https://www.oasis-open.org/resources/open-repositories/>`__.
|
||||||
|
See the `Governance <#governance>`__ section for more information.
|
||||||
|
|
||||||
|
This repository provides Python APIs for serializing and de-serializing
|
||||||
|
STIX 2 JSON content, along with higher-level APIs for common tasks,
|
||||||
|
including data markings, versioning, and for resolving STIX IDs across
|
||||||
|
multiple data sources.
|
||||||
|
|
||||||
|
For more information, see `the
|
||||||
|
documentation <https://stix2.readthedocs.io/en/latest/>`__ on
|
||||||
|
ReadTheDocs.
|
||||||
|
|
||||||
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
|
Install with `pip <https://pip.pypa.io/en/stable/>`__:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
pip install stix2
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
Creating STIX Domain Objects
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
To create a STIX object, provide keyword arguments to the type's
|
||||||
|
constructor:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from stix2 import Indicator
|
||||||
|
|
||||||
|
indicator = Indicator(name="File hash for malware variant",
|
||||||
|
labels=['malicious-activity'],
|
||||||
|
pattern='file:hashes.md5 = "d41d8cd98f00b204e9800998ecf8427e"')
|
||||||
|
|
||||||
|
Certain required attributes of all objects will be set automatically if
|
||||||
|
not provided as keyword arguments:
|
||||||
|
|
||||||
|
- If not provided, ``type`` will be set automatically to the correct
|
||||||
|
type. You can also provide the type explicitly, but this is not
|
||||||
|
necessary:
|
||||||
|
|
||||||
|
``python indicator = Indicator(type='indicator', ...)``
|
||||||
|
|
||||||
|
Passing a value for ``type`` that does not match the class being
|
||||||
|
constructed will cause an error:
|
||||||
|
|
||||||
|
``python >>> indicator = Indicator(type='xxx', ...) stix2.exceptions.InvalidValueError: Invalid value for Indicator 'type': must equal 'indicator'.``
|
||||||
|
|
||||||
|
- If not provided, ``id`` will be generated randomly. If you provide an
|
||||||
|
``id`` argument, it must begin with the correct prefix:
|
||||||
|
|
||||||
|
``python >>> indicator = Indicator(id="campaign--63ce9068-b5ab-47fa-a2cf-a602ea01f21a") stix2.exceptions.InvalidValueError: Invalid value for Indicator 'id': must start with 'indicator--'.``
|
||||||
|
|
||||||
|
- If not provided, ``created`` and ``modified`` will be set to the
|
||||||
|
(same) current time.
|
||||||
|
|
||||||
|
For indicators, ``labels`` and ``pattern`` are required and cannot be
|
||||||
|
set automatically. Trying to create an indicator that is missing one of
|
||||||
|
these properties will result in an error:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
>>> indicator = Indicator()
|
||||||
|
stix2.exceptions.MissingPropertiesError: No values for required properties for Indicator: (labels, pattern).
|
||||||
|
|
||||||
|
However, the required ``valid_from`` attribute on Indicators will be set
|
||||||
|
to the current time if not provided as a keyword argument.
|
||||||
|
|
||||||
|
Once created, the object acts like a frozen dictionary. Properties can
|
||||||
|
be accessed using the standard Python dictionary syntax:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
>>> indicator['name']
|
||||||
|
'File hash for malware variant'
|
||||||
|
|
||||||
|
TBD: Should we allow property access using the standard Python attribute
|
||||||
|
syntax?
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
>>> indicator.name
|
||||||
|
'File hash for malware variant'
|
||||||
|
|
||||||
|
Attempting to modify any attributes will raise an error:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
>>> indicator['name'] = "This is a revised name"
|
||||||
|
TypeError: 'Indicator' object does not support item assignment
|
||||||
|
>>> indicator.name = "This is a revised name"
|
||||||
|
stix2.exceptions.ImmutableError: Cannot modify properties after creation.
|
||||||
|
|
||||||
|
To update the properties of an object, see `Versioning <#versioning>`__
|
||||||
|
below.
|
||||||
|
|
||||||
|
Creating a Malware object follows the same pattern:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from stix2 import Malware
|
||||||
|
|
||||||
|
malware = Malware(name="Poison Ivy",
|
||||||
|
labels=['remote-access-trojan'])
|
||||||
|
|
||||||
|
As with indicators, the ``type``, ``id``, ``created``, and ``modified``
|
||||||
|
properties will be set automatically if not provided. For Malware
|
||||||
|
objects, the ``labels`` and ``name`` properties must be provided.
|
||||||
|
|
||||||
|
Creating Relationships
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
STIX 2 Relationships are separate objects, not properties of the object
|
||||||
|
on either side of the relationship. They are constructed similarly to
|
||||||
|
other STIX objects. The ``type``, ``id``, ``created``, and ``modified``
|
||||||
|
properties are added automatically if not provided. Callers must provide
|
||||||
|
the ``relationship_type``, ``source_ref``, and ``target_ref``
|
||||||
|
properties.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from stix2 import Relationship
|
||||||
|
|
||||||
|
relationship = Relationship(relationship_type='indicates',
|
||||||
|
source_ref=indicator.id,
|
||||||
|
target_ref=malware.id)
|
||||||
|
|
||||||
|
The ``source_ref`` and ``target_ref`` properties can be either the ID's
|
||||||
|
of other STIX objects, or the STIX objects themselves. For readability,
|
||||||
|
Relationship objects can also be constructed with the ``source_ref``,
|
||||||
|
``relationship_type``, and ``target_ref`` as positional (non-keyword)
|
||||||
|
arguments:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
relationship = Relationship(indicator, 'indicates', malware)
|
||||||
|
|
||||||
|
Creating Bundles
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
STIX Bundles can be created by passing objects as arguments to the
|
||||||
|
Bundle constructor. All required properties (``type``, ``id``, and
|
||||||
|
``spec_version``) will be set automatically if not provided, or can be
|
||||||
|
provided as keyword arguments:
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
from stix2 import bundle
|
||||||
|
|
||||||
|
bundle = Bundle(indicator, malware, relationship)
|
||||||
|
|
||||||
|
Serializing STIX objects
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The string representation of all STIX classes is a valid STIX JSON
|
||||||
|
object.
|
||||||
|
|
||||||
|
.. code:: python
|
||||||
|
|
||||||
|
indicator = Indicator(...)
|
||||||
|
|
||||||
|
print(str(indicator))
|
||||||
|
|
||||||
|
Versioning
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
TBD
|
||||||
|
|
||||||
|
Governance
|
||||||
|
----------
|
||||||
|
|
||||||
|
This GitHub public repository (
|
||||||
|
**https://github.com/oasis-open/cti-python-stix2** ) was
|
||||||
|
`proposed <https://lists.oasis-open.org/archives/cti/201702/msg00008.html>`__
|
||||||
|
and
|
||||||
|
`approved <https://www.oasis-open.org/committees/download.php/60009/>`__
|
||||||
|
[`bis <https://issues.oasis-open.org/browse/TCADMIN-2549>`__] by the
|
||||||
|
`OASIS Cyber Threat Intelligence (CTI)
|
||||||
|
TC <https://www.oasis-open.org/committees/cti/>`__ as an `OASIS Open
|
||||||
|
Repository <https://www.oasis-open.org/resources/open-repositories/>`__
|
||||||
|
to support development of open source resources related to Technical
|
||||||
|
Committee work.
|
||||||
|
|
||||||
|
While this Open Repository remains associated with the sponsor TC, its
|
||||||
|
development priorities, leadership, intellectual property terms,
|
||||||
|
participation rules, and other matters of governance are `separate and
|
||||||
|
distinct <https://github.com/oasis-open/cti-python-stix2/blob/master/CONTRIBUTING.md#governance-distinct-from-oasis-tc-process>`__
|
||||||
|
from the OASIS TC Process and related policies.
|
||||||
|
|
||||||
|
All contributions made to this Open Repository are subject to open
|
||||||
|
source license terms expressed in the `BSD-3-Clause
|
||||||
|
License <https://www.oasis-open.org/sites/www.oasis-open.org/files/BSD-3-Clause.txt>`__.
|
||||||
|
That license was selected as the declared `"Applicable
|
||||||
|
License" <https://www.oasis-open.org/resources/open-repositories/licenses>`__
|
||||||
|
when the Open Repository was created.
|
||||||
|
|
||||||
|
As documented in `"Public Participation
|
||||||
|
Invited <https://github.com/oasis-open/cti-python-stix2/blob/master/CONTRIBUTING.md#public-participation-invited>`__",
|
||||||
|
contributions to this OASIS Open Repository are invited from all
|
||||||
|
parties, whether affiliated with OASIS or not. Participants must have a
|
||||||
|
GitHub account, but no fees or OASIS membership obligations are
|
||||||
|
required. Participation is expected to be consistent with the `OASIS
|
||||||
|
Open Repository Guidelines and
|
||||||
|
Procedures <https://www.oasis-open.org/policies-guidelines/open-repositories>`__,
|
||||||
|
the open source
|
||||||
|
`LICENSE <https://github.com/oasis-open/cti-python-stix2/blob/master/LICENSE>`__
|
||||||
|
designated for this particular repository, and the requirement for an
|
||||||
|
`Individual Contributor License
|
||||||
|
Agreement <https://www.oasis-open.org/resources/open-repositories/cla/individual-cla>`__
|
||||||
|
that governs intellectual property.
|
||||||
|
|
||||||
|
Maintainers
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
Open Repository
|
||||||
|
`Maintainers <https://www.oasis-open.org/resources/open-repositories/maintainers-guide>`__
|
||||||
|
are responsible for oversight of this project's community development
|
||||||
|
activities, including evaluation of GitHub `pull
|
||||||
|
requests <https://github.com/oasis-open/cti-python-stix2/blob/master/CONTRIBUTING.md#fork-and-pull-collaboration-model>`__
|
||||||
|
and
|
||||||
|
`preserving <https://www.oasis-open.org/policies-guidelines/open-repositories#repositoryManagement>`__
|
||||||
|
open source principles of openness and fairness. Maintainers are
|
||||||
|
recognized and trusted experts who serve to implement community goals
|
||||||
|
and consensus design preferences.
|
||||||
|
|
||||||
|
Initially, the associated TC members have designated one or more persons
|
||||||
|
to serve as Maintainer(s); subsequently, participating community members
|
||||||
|
may select additional or substitute Maintainers, per `consensus
|
||||||
|
agreements <https://www.oasis-open.org/resources/open-repositories/maintainers-guide#additionalMaintainers>`__.
|
||||||
|
|
||||||
|
.. _currentMaintainers:
|
||||||
|
|
||||||
|
**Current Maintainers of this Open Repository**
|
||||||
|
|
||||||
|
- `Greg Back <mailto:gback@mitre.org>`__; GitHub ID:
|
||||||
|
https://github.com/gtback/; WWW: `MITRE
|
||||||
|
Corporation <http://www.mitre.org/>`__
|
||||||
|
- `Chris Lenk <mailto:clenk@mitre.org>`__; GitHub ID:
|
||||||
|
https://github.com/clenk/; WWW: `MITRE
|
||||||
|
Corporation <http://www.mitre.org/>`__
|
||||||
|
|
||||||
|
About OASIS Open Repositories
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
- `Open Repositories: Overview and
|
||||||
|
Resources <https://www.oasis-open.org/resources/open-repositories/>`__
|
||||||
|
- `Frequently Asked
|
||||||
|
Questions <https://www.oasis-open.org/resources/open-repositories/faq>`__
|
||||||
|
- `Open Source
|
||||||
|
Licenses <https://www.oasis-open.org/resources/open-repositories/licenses>`__
|
||||||
|
- `Contributor License Agreements
|
||||||
|
(CLAs) <https://www.oasis-open.org/resources/open-repositories/cla>`__
|
||||||
|
- `Maintainers' Guidelines and
|
||||||
|
Agreement <https://www.oasis-open.org/resources/open-repositories/maintainers-guide>`__
|
||||||
|
|
||||||
|
Feedback
|
||||||
|
--------
|
||||||
|
|
||||||
|
Questions or comments about this Open Repository's activities should be
|
||||||
|
composed as GitHub issues or comments. If use of an issue/comment is not
|
||||||
|
possible or appropriate, questions may be directed by email to the
|
||||||
|
Maintainer(s) `listed above <#currentmaintainers>`__. Please send
|
||||||
|
general questions about Open Repository participation to OASIS Staff at
|
||||||
|
repository-admin@oasis-open.org and any specific CLA-related questions
|
||||||
|
to repository-cla@oasis-open.org.
|
||||||
|
|
||||||
|
.. |Build Status| image:: https://travis-ci.org/oasis-open/cti-python-stix2.svg?branch=master
|
||||||
|
:target: https://travis-ci.org/oasis-open/cti-python-stix2
|
||||||
|
.. |codecov| image:: https://codecov.io/gh/oasis-open/cti-python-stix2/branch/master/graph/badge.svg
|
||||||
|
:target: https://codecov.io/gh/oasis-open/cti-python-stix2
|
|
@ -3,7 +3,7 @@ current_version = 0.2.0
|
||||||
commit = True
|
commit = True
|
||||||
tag = True
|
tag = True
|
||||||
|
|
||||||
[bumpversion:file:setup.py]
|
[bumpversion:file:stix2/version.py]
|
||||||
|
|
||||||
[bumpversion:file:docs/conf.py]
|
[bumpversion:file:docs/conf.py]
|
||||||
|
|
||||||
|
|
51
setup.py
51
setup.py
|
@ -1,9 +1,14 @@
|
||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
|
from codecs import open
|
||||||
|
import os.path
|
||||||
|
|
||||||
from setuptools import find_packages, setup
|
from setuptools import find_packages, setup
|
||||||
|
|
||||||
|
here = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
|
||||||
def get_version():
|
def get_version():
|
||||||
with open('stix2/version.py') as f:
|
with open('stix2/version.py', encoding="utf-8") as f:
|
||||||
for line in f.readlines():
|
for line in f.readlines():
|
||||||
if line.startswith("__version__"):
|
if line.startswith("__version__"):
|
||||||
version = line.split()[-1].strip('"')
|
version = line.split()[-1].strip('"')
|
||||||
|
@ -11,19 +16,41 @@ def get_version():
|
||||||
raise AttributeError("Package does not have a __version__")
|
raise AttributeError("Package does not have a __version__")
|
||||||
|
|
||||||
|
|
||||||
install_requires = [
|
with open(os.path.join(here, 'README.rst'), encoding='utf-8') as f:
|
||||||
|
long_description = f.read()
|
||||||
|
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name='stix2',
|
||||||
|
version=get_version(),
|
||||||
|
description='Produce and consume STIX 2 JSON content',
|
||||||
|
long_description=long_description,
|
||||||
|
url='https://github.com/oasis-open/cti-python-stix2',
|
||||||
|
author='OASIS Cyber Threat Intelligence Technical Committee',
|
||||||
|
author_email='cti-users@lists.oasis-open.org',
|
||||||
|
maintainer='Greg Back',
|
||||||
|
maintainer_email='gback@mitre.org',
|
||||||
|
license='BSD',
|
||||||
|
classifiers=[
|
||||||
|
'Development Status :: 4 - Beta',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'Topic :: Security',
|
||||||
|
'License :: OSI Approved :: BSD License',
|
||||||
|
'Programming Language :: Python :: 2',
|
||||||
|
'Programming Language :: Python :: 2.7',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Programming Language :: Python :: 3.3',
|
||||||
|
'Programming Language :: Python :: 3.4',
|
||||||
|
'Programming Language :: Python :: 3.5',
|
||||||
|
'Programming Language :: Python :: 3.6',
|
||||||
|
],
|
||||||
|
keywords="stix stix2 json cti cyber threat intelligence",
|
||||||
|
packages=find_packages(),
|
||||||
|
install_requires=[
|
||||||
'pytz',
|
'pytz',
|
||||||
'six',
|
'six',
|
||||||
'python-dateutil',
|
'python-dateutil',
|
||||||
'requests',
|
'requests',
|
||||||
'simplejson',
|
'simplejson'
|
||||||
]
|
],
|
||||||
|
|
||||||
setup(
|
|
||||||
name='stix2',
|
|
||||||
description="Produce and consume STIX 2 JSON content",
|
|
||||||
version=get_version(),
|
|
||||||
packages=find_packages(),
|
|
||||||
install_requires=install_requires,
|
|
||||||
keywords="stix stix2 json cti cyber threat intelligence",
|
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,10 @@
|
||||||
# flake8: noqa
|
# flake8: noqa
|
||||||
|
|
||||||
from . import exceptions
|
from . import exceptions
|
||||||
from .bundle import Bundle
|
from .common import (TLP_AMBER, TLP_GREEN, TLP_RED, TLP_WHITE,
|
||||||
|
ExternalReference, GranularMarking, KillChainPhase,
|
||||||
|
MarkingDefinition, StatementMarking, TLPMarking)
|
||||||
|
from .core import Bundle, _register_type, parse
|
||||||
from .environment import ObjectFactory
|
from .environment import ObjectFactory
|
||||||
from .observables import (URL, AlternateDataStream, ArchiveExt, Artifact,
|
from .observables import (URL, AlternateDataStream, ArchiveExt, Artifact,
|
||||||
AutonomousSystem, CustomObservable, Directory,
|
AutonomousSystem, CustomObservable, Directory,
|
||||||
|
@ -18,9 +21,6 @@ from .observables import (URL, AlternateDataStream, ArchiveExt, Artifact,
|
||||||
WindowsRegistryValueType, WindowsServiceExt,
|
WindowsRegistryValueType, WindowsServiceExt,
|
||||||
X509Certificate, X509V3ExtenstionsType,
|
X509Certificate, X509V3ExtenstionsType,
|
||||||
parse_observable)
|
parse_observable)
|
||||||
from .other import (TLP_AMBER, TLP_GREEN, TLP_RED, TLP_WHITE,
|
|
||||||
ExternalReference, GranularMarking, KillChainPhase,
|
|
||||||
MarkingDefinition, StatementMarking, TLPMarking)
|
|
||||||
from .patterns import (AndBooleanExpression, AndObservationExpression,
|
from .patterns import (AndBooleanExpression, AndObservationExpression,
|
||||||
BasicObjectPathComponent, EqualityComparisonExpression,
|
BasicObjectPathComponent, EqualityComparisonExpression,
|
||||||
FloatConstant, FollowedByObservationExpression,
|
FloatConstant, FollowedByObservationExpression,
|
||||||
|
@ -44,51 +44,3 @@ from .sdo import (AttackPattern, Campaign, CourseOfAction, CustomObject,
|
||||||
from .sro import Relationship, Sighting
|
from .sro import Relationship, Sighting
|
||||||
from .utils import get_dict
|
from .utils import get_dict
|
||||||
from .version import __version__
|
from .version import __version__
|
||||||
|
|
||||||
OBJ_MAP = {
|
|
||||||
'attack-pattern': AttackPattern,
|
|
||||||
'campaign': Campaign,
|
|
||||||
'course-of-action': CourseOfAction,
|
|
||||||
'identity': Identity,
|
|
||||||
'indicator': Indicator,
|
|
||||||
'intrusion-set': IntrusionSet,
|
|
||||||
'malware': Malware,
|
|
||||||
'marking-definition': MarkingDefinition,
|
|
||||||
'observed-data': ObservedData,
|
|
||||||
'report': Report,
|
|
||||||
'relationship': Relationship,
|
|
||||||
'threat-actor': ThreatActor,
|
|
||||||
'tool': Tool,
|
|
||||||
'sighting': Sighting,
|
|
||||||
'vulnerability': Vulnerability,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def parse(data, allow_custom=False):
|
|
||||||
"""Deserialize a string or file-like object into a STIX object.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
data: The STIX 2 string to be parsed.
|
|
||||||
allow_custom (bool): Whether to allow custom properties or not. Default: False.
|
|
||||||
|
|
||||||
Returns:
|
|
||||||
An instantiated Python STIX object.
|
|
||||||
"""
|
|
||||||
|
|
||||||
obj = get_dict(data)
|
|
||||||
|
|
||||||
if 'type' not in obj:
|
|
||||||
raise exceptions.ParseError("Can't parse object with no 'type' property: %s" % str(obj))
|
|
||||||
|
|
||||||
try:
|
|
||||||
obj_class = OBJ_MAP[obj['type']]
|
|
||||||
except KeyError:
|
|
||||||
raise exceptions.ParseError("Can't parse unknown object type '%s'! For custom types, use the CustomObject decorator." % obj['type'])
|
|
||||||
return obj_class(allow_custom=allow_custom, **obj)
|
|
||||||
|
|
||||||
|
|
||||||
def _register_type(new_type):
|
|
||||||
"""Register a custom STIX Object type.
|
|
||||||
"""
|
|
||||||
|
|
||||||
OBJ_MAP[new_type._type] = new_type
|
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
"""STIX 2 Bundle object"""
|
|
||||||
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
from .base import _STIXBase
|
|
||||||
from .properties import IDProperty, Property, TypeProperty
|
|
||||||
|
|
||||||
|
|
||||||
class Bundle(_STIXBase):
|
|
||||||
|
|
||||||
_type = 'bundle'
|
|
||||||
_properties = OrderedDict()
|
|
||||||
_properties.update([
|
|
||||||
('type', TypeProperty(_type)),
|
|
||||||
('id', IDProperty(_type)),
|
|
||||||
('spec_version', Property(fixed="2.0")),
|
|
||||||
('objects', Property()),
|
|
||||||
])
|
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
# Add any positional arguments to the 'objects' kwarg.
|
|
||||||
if args:
|
|
||||||
if isinstance(args[0], list):
|
|
||||||
kwargs['objects'] = args[0] + list(args[1:]) + kwargs.get('objects', [])
|
|
||||||
else:
|
|
||||||
kwargs['objects'] = list(args) + kwargs.get('objects', [])
|
|
||||||
|
|
||||||
super(Bundle, self).__init__(**kwargs)
|
|
147
stix2/common.py
147
stix2/common.py
|
@ -2,10 +2,149 @@
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from .other import ExternalReference, GranularMarking
|
from .base import _STIXBase
|
||||||
from .properties import (BooleanProperty, ListProperty, ReferenceProperty,
|
from .properties import (BooleanProperty, HashesProperty, IDProperty,
|
||||||
StringProperty, TimestampProperty)
|
ListProperty, Property, ReferenceProperty,
|
||||||
from .utils import NOW
|
SelectorProperty, StringProperty, TimestampProperty,
|
||||||
|
TypeProperty)
|
||||||
|
from .utils import NOW, get_dict
|
||||||
|
|
||||||
|
|
||||||
|
class ExternalReference(_STIXBase):
|
||||||
|
_properties = OrderedDict()
|
||||||
|
_properties.update([
|
||||||
|
('source_name', StringProperty(required=True)),
|
||||||
|
('description', StringProperty()),
|
||||||
|
('url', StringProperty()),
|
||||||
|
('hashes', HashesProperty()),
|
||||||
|
('external_id', StringProperty()),
|
||||||
|
])
|
||||||
|
|
||||||
|
def _check_object_constraints(self):
|
||||||
|
super(ExternalReference, self)._check_object_constraints()
|
||||||
|
self._check_at_least_one_property(["description", "external_id", "url"])
|
||||||
|
|
||||||
|
|
||||||
|
class KillChainPhase(_STIXBase):
|
||||||
|
_properties = OrderedDict()
|
||||||
|
_properties.update([
|
||||||
|
('kill_chain_name', StringProperty(required=True)),
|
||||||
|
('phase_name', StringProperty(required=True)),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class GranularMarking(_STIXBase):
|
||||||
|
_properties = OrderedDict()
|
||||||
|
_properties.update([
|
||||||
|
('marking_ref', ReferenceProperty(required=True, type="marking-definition")),
|
||||||
|
('selectors', ListProperty(SelectorProperty, required=True)),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class TLPMarking(_STIXBase):
|
||||||
|
# TODO: don't allow the creation of any other TLPMarkings than the ones below
|
||||||
|
_type = 'tlp'
|
||||||
|
_properties = OrderedDict()
|
||||||
|
_properties.update([
|
||||||
|
('tlp', Property(required=True))
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
class StatementMarking(_STIXBase):
|
||||||
|
_type = 'statement'
|
||||||
|
_properties = OrderedDict()
|
||||||
|
_properties.update([
|
||||||
|
('statement', StringProperty(required=True))
|
||||||
|
])
|
||||||
|
|
||||||
|
def __init__(self, statement=None, **kwargs):
|
||||||
|
# Allow statement as positional args.
|
||||||
|
if statement and not kwargs.get('statement'):
|
||||||
|
kwargs['statement'] = statement
|
||||||
|
|
||||||
|
super(StatementMarking, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class MarkingProperty(Property):
|
||||||
|
"""Represent the marking objects in the `definition` property of
|
||||||
|
marking-definition objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def clean(self, value):
|
||||||
|
if type(value) in OBJ_MAP_MARKING.values():
|
||||||
|
return value
|
||||||
|
else:
|
||||||
|
raise ValueError("must be a Statement, TLP Marking or a registered marking.")
|
||||||
|
|
||||||
|
|
||||||
|
class MarkingDefinition(_STIXBase):
|
||||||
|
_type = 'marking-definition'
|
||||||
|
_properties = OrderedDict()
|
||||||
|
_properties.update([
|
||||||
|
('type', TypeProperty(_type)),
|
||||||
|
('id', IDProperty(_type)),
|
||||||
|
('created_by_ref', ReferenceProperty(type="identity")),
|
||||||
|
('created', TimestampProperty(default=lambda: NOW)),
|
||||||
|
('external_references', ListProperty(ExternalReference)),
|
||||||
|
('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))),
|
||||||
|
('granular_markings', ListProperty(GranularMarking)),
|
||||||
|
('definition_type', StringProperty(required=True)),
|
||||||
|
('definition', MarkingProperty(required=True)),
|
||||||
|
])
|
||||||
|
|
||||||
|
def __init__(self, **kwargs):
|
||||||
|
if set(('definition_type', 'definition')).issubset(kwargs.keys()):
|
||||||
|
# Create correct marking type object
|
||||||
|
try:
|
||||||
|
marking_type = OBJ_MAP_MARKING[kwargs['definition_type']]
|
||||||
|
except KeyError:
|
||||||
|
raise ValueError("definition_type must be a valid marking type")
|
||||||
|
|
||||||
|
if not isinstance(kwargs['definition'], marking_type):
|
||||||
|
defn = get_dict(kwargs['definition'])
|
||||||
|
kwargs['definition'] = marking_type(**defn)
|
||||||
|
|
||||||
|
super(MarkingDefinition, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def register_marking(new_marking):
|
||||||
|
"""Register a custom STIX Marking Definition type.
|
||||||
|
"""
|
||||||
|
OBJ_MAP_MARKING[new_marking._type] = new_marking
|
||||||
|
|
||||||
|
|
||||||
|
OBJ_MAP_MARKING = {
|
||||||
|
'tlp': TLPMarking,
|
||||||
|
'statement': StatementMarking,
|
||||||
|
}
|
||||||
|
|
||||||
|
TLP_WHITE = MarkingDefinition(
|
||||||
|
id="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
||||||
|
created="2017-01-20T00:00:00.000Z",
|
||||||
|
definition_type="tlp",
|
||||||
|
definition=TLPMarking(tlp="white")
|
||||||
|
)
|
||||||
|
|
||||||
|
TLP_GREEN = MarkingDefinition(
|
||||||
|
id="marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da",
|
||||||
|
created="2017-01-20T00:00:00.000Z",
|
||||||
|
definition_type="tlp",
|
||||||
|
definition=TLPMarking(tlp="green")
|
||||||
|
)
|
||||||
|
|
||||||
|
TLP_AMBER = MarkingDefinition(
|
||||||
|
id="marking-definition--f88d31f6-486f-44da-b317-01333bde0b82",
|
||||||
|
created="2017-01-20T00:00:00.000Z",
|
||||||
|
definition_type="tlp",
|
||||||
|
definition=TLPMarking(tlp="amber")
|
||||||
|
)
|
||||||
|
|
||||||
|
TLP_RED = MarkingDefinition(
|
||||||
|
id="marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed",
|
||||||
|
created="2017-01-20T00:00:00.000Z",
|
||||||
|
definition_type="tlp",
|
||||||
|
definition=TLPMarking(tlp="red")
|
||||||
|
)
|
||||||
|
|
||||||
COMMON_PROPERTIES = OrderedDict()
|
COMMON_PROPERTIES = OrderedDict()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
"""STIX 2.0 Objects that are neither SDOs nor SROs"""
|
||||||
|
|
||||||
|
|
||||||
|
from . import exceptions
|
||||||
|
from .base import _STIXBase
|
||||||
|
from .common import MarkingDefinition
|
||||||
|
from .properties import IDProperty, ListProperty, Property, TypeProperty
|
||||||
|
from .sdo import (AttackPattern, Campaign, CourseOfAction, Identity, Indicator,
|
||||||
|
IntrusionSet, Malware, ObservedData, Report, ThreatActor,
|
||||||
|
Tool, Vulnerability)
|
||||||
|
from .sro import Relationship, Sighting
|
||||||
|
from .utils import get_dict
|
||||||
|
|
||||||
|
|
||||||
|
class STIXObjectProperty(Property):
|
||||||
|
|
||||||
|
def clean(self, value):
|
||||||
|
try:
|
||||||
|
dictified = get_dict(value)
|
||||||
|
except ValueError:
|
||||||
|
raise ValueError("This property may only contain a dictionary or object")
|
||||||
|
if dictified == {}:
|
||||||
|
raise ValueError("This property may only contain a non-empty dictionary or object")
|
||||||
|
if 'type' in dictified and dictified['type'] == 'bundle':
|
||||||
|
raise ValueError('This property may not contain a Bundle object')
|
||||||
|
|
||||||
|
parsed_obj = parse(dictified)
|
||||||
|
return parsed_obj
|
||||||
|
|
||||||
|
|
||||||
|
class Bundle(_STIXBase):
|
||||||
|
|
||||||
|
_type = 'bundle'
|
||||||
|
_properties = {
|
||||||
|
'type': TypeProperty(_type),
|
||||||
|
'id': IDProperty(_type),
|
||||||
|
'spec_version': Property(fixed="2.0"),
|
||||||
|
'objects': ListProperty(STIXObjectProperty),
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
# Add any positional arguments to the 'objects' kwarg.
|
||||||
|
if args:
|
||||||
|
if isinstance(args[0], list):
|
||||||
|
kwargs['objects'] = args[0] + list(args[1:]) + kwargs.get('objects', [])
|
||||||
|
else:
|
||||||
|
kwargs['objects'] = list(args) + kwargs.get('objects', [])
|
||||||
|
|
||||||
|
super(Bundle, self).__init__(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
OBJ_MAP = {
|
||||||
|
'attack-pattern': AttackPattern,
|
||||||
|
'bundle': Bundle,
|
||||||
|
'campaign': Campaign,
|
||||||
|
'course-of-action': CourseOfAction,
|
||||||
|
'identity': Identity,
|
||||||
|
'indicator': Indicator,
|
||||||
|
'intrusion-set': IntrusionSet,
|
||||||
|
'malware': Malware,
|
||||||
|
'marking-definition': MarkingDefinition,
|
||||||
|
'observed-data': ObservedData,
|
||||||
|
'report': Report,
|
||||||
|
'relationship': Relationship,
|
||||||
|
'threat-actor': ThreatActor,
|
||||||
|
'tool': Tool,
|
||||||
|
'sighting': Sighting,
|
||||||
|
'vulnerability': Vulnerability,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse(data, allow_custom=False):
|
||||||
|
"""Deserialize a string or file-like object into a STIX object.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data: The STIX 2 string to be parsed.
|
||||||
|
allow_custom (bool): Whether to allow custom properties or not. Default: False.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An instantiated Python STIX object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
obj = get_dict(data)
|
||||||
|
|
||||||
|
if 'type' not in obj:
|
||||||
|
raise exceptions.ParseError("Can't parse object with no 'type' property: %s" % str(obj))
|
||||||
|
|
||||||
|
try:
|
||||||
|
obj_class = OBJ_MAP[obj['type']]
|
||||||
|
except KeyError:
|
||||||
|
raise exceptions.ParseError("Can't parse unknown object type '%s'! For custom types, use the CustomObject decorator." % obj['type'])
|
||||||
|
return obj_class(allow_custom=allow_custom, **obj)
|
||||||
|
|
||||||
|
|
||||||
|
def _register_type(new_type):
|
||||||
|
"""Register a custom STIX Object type.
|
||||||
|
"""
|
||||||
|
|
||||||
|
OBJ_MAP[new_type._type] = new_type
|
145
stix2/other.py
145
stix2/other.py
|
@ -1,145 +0,0 @@
|
||||||
"""STIX 2.0 Objects that are neither SDOs nor SROs"""
|
|
||||||
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
from .base import _STIXBase
|
|
||||||
from .properties import (IDProperty, HashesProperty, ListProperty, Property,
|
|
||||||
ReferenceProperty, SelectorProperty, StringProperty,
|
|
||||||
TimestampProperty, TypeProperty)
|
|
||||||
from .utils import NOW, get_dict
|
|
||||||
|
|
||||||
|
|
||||||
class ExternalReference(_STIXBase):
|
|
||||||
_properties = OrderedDict()
|
|
||||||
_properties.update([
|
|
||||||
('source_name', StringProperty(required=True)),
|
|
||||||
('description', StringProperty()),
|
|
||||||
('url', StringProperty()),
|
|
||||||
('hashes', HashesProperty()),
|
|
||||||
('external_id', StringProperty()),
|
|
||||||
])
|
|
||||||
|
|
||||||
def _check_object_constraints(self):
|
|
||||||
super(ExternalReference, self)._check_object_constraints()
|
|
||||||
self._check_at_least_one_property(["description", "external_id", "url"])
|
|
||||||
|
|
||||||
|
|
||||||
class KillChainPhase(_STIXBase):
|
|
||||||
_properties = OrderedDict()
|
|
||||||
_properties.update([
|
|
||||||
('kill_chain_name', StringProperty(required=True)),
|
|
||||||
('phase_name', StringProperty(required=True)),
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
class GranularMarking(_STIXBase):
|
|
||||||
_properties = OrderedDict()
|
|
||||||
_properties.update([
|
|
||||||
('marking_ref', ReferenceProperty(required=True, type="marking-definition")),
|
|
||||||
('selectors', ListProperty(SelectorProperty, required=True)),
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
class TLPMarking(_STIXBase):
|
|
||||||
_type = 'tlp'
|
|
||||||
_properties = OrderedDict()
|
|
||||||
_properties.update([
|
|
||||||
('tlp', Property(required=True))
|
|
||||||
])
|
|
||||||
|
|
||||||
|
|
||||||
class StatementMarking(_STIXBase):
|
|
||||||
_type = 'statement'
|
|
||||||
_properties = OrderedDict()
|
|
||||||
_properties.update([
|
|
||||||
('statement', StringProperty(required=True))
|
|
||||||
])
|
|
||||||
|
|
||||||
def __init__(self, statement=None, **kwargs):
|
|
||||||
# Allow statement as positional args.
|
|
||||||
if statement and not kwargs.get('statement'):
|
|
||||||
kwargs['statement'] = statement
|
|
||||||
|
|
||||||
super(StatementMarking, self).__init__(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
class MarkingProperty(Property):
|
|
||||||
"""Represent the marking objects in the `definition` property of
|
|
||||||
marking-definition objects.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def clean(self, value):
|
|
||||||
if type(value) in OBJ_MAP_MARKING.values():
|
|
||||||
return value
|
|
||||||
else:
|
|
||||||
raise ValueError("must be a Statement, TLP Marking or a registered marking.")
|
|
||||||
|
|
||||||
|
|
||||||
class MarkingDefinition(_STIXBase):
|
|
||||||
_type = 'marking-definition'
|
|
||||||
_properties = OrderedDict()
|
|
||||||
_properties.update([
|
|
||||||
('type', TypeProperty(_type)),
|
|
||||||
('id', IDProperty(_type)),
|
|
||||||
('created_by_ref', ReferenceProperty(type="identity")),
|
|
||||||
('created', TimestampProperty(default=lambda: NOW)),
|
|
||||||
('external_references', ListProperty(ExternalReference)),
|
|
||||||
('object_marking_refs', ListProperty(ReferenceProperty(type="marking-definition"))),
|
|
||||||
('granular_markings', ListProperty(GranularMarking)),
|
|
||||||
('definition_type', StringProperty(required=True)),
|
|
||||||
('definition', MarkingProperty(required=True)),
|
|
||||||
])
|
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
|
||||||
if set(('definition_type', 'definition')).issubset(kwargs.keys()):
|
|
||||||
# Create correct marking type object
|
|
||||||
try:
|
|
||||||
marking_type = OBJ_MAP_MARKING[kwargs['definition_type']]
|
|
||||||
except KeyError:
|
|
||||||
raise ValueError("definition_type must be a valid marking type")
|
|
||||||
|
|
||||||
if not isinstance(kwargs['definition'], marking_type):
|
|
||||||
defn = get_dict(kwargs['definition'])
|
|
||||||
kwargs['definition'] = marking_type(**defn)
|
|
||||||
|
|
||||||
super(MarkingDefinition, self).__init__(**kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
def register_marking(new_marking):
|
|
||||||
"""Register a custom STIX Marking Definition type.
|
|
||||||
"""
|
|
||||||
OBJ_MAP_MARKING[new_marking._type] = new_marking
|
|
||||||
|
|
||||||
|
|
||||||
OBJ_MAP_MARKING = {
|
|
||||||
'tlp': TLPMarking,
|
|
||||||
'statement': StatementMarking,
|
|
||||||
}
|
|
||||||
|
|
||||||
TLP_WHITE = MarkingDefinition(
|
|
||||||
id="marking-definition--613f2e26-407d-48c7-9eca-b8e91df99dc9",
|
|
||||||
created="2017-01-20T00:00:00.000Z",
|
|
||||||
definition_type="tlp",
|
|
||||||
definition=TLPMarking(tlp="white")
|
|
||||||
)
|
|
||||||
|
|
||||||
TLP_GREEN = MarkingDefinition(
|
|
||||||
id="marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da",
|
|
||||||
created="2017-01-20T00:00:00.000Z",
|
|
||||||
definition_type="tlp",
|
|
||||||
definition=TLPMarking(tlp="green")
|
|
||||||
)
|
|
||||||
|
|
||||||
TLP_AMBER = MarkingDefinition(
|
|
||||||
id="marking-definition--f88d31f6-486f-44da-b317-01333bde0b82",
|
|
||||||
created="2017-01-20T00:00:00.000Z",
|
|
||||||
definition_type="tlp",
|
|
||||||
definition=TLPMarking(tlp="amber")
|
|
||||||
)
|
|
||||||
|
|
||||||
TLP_RED = MarkingDefinition(
|
|
||||||
id="marking-definition--5e57c739-391a-4eb3-b6be-7d15ca92d5ed",
|
|
||||||
created="2017-01-20T00:00:00.000Z",
|
|
||||||
definition_type="tlp",
|
|
||||||
definition=TLPMarking(tlp="red")
|
|
||||||
)
|
|
|
@ -118,6 +118,9 @@ class ListProperty(Property):
|
||||||
|
|
||||||
if type(self.contained) is EmbeddedObjectProperty:
|
if type(self.contained) is EmbeddedObjectProperty:
|
||||||
obj_type = self.contained.type
|
obj_type = self.contained.type
|
||||||
|
elif type(self.contained).__name__ is 'STIXObjectProperty':
|
||||||
|
# ^ this way of checking doesn't require a circular import
|
||||||
|
obj_type = type(valid)
|
||||||
else:
|
else:
|
||||||
obj_type = self.contained
|
obj_type = self.contained
|
||||||
|
|
||||||
|
|
|
@ -5,8 +5,8 @@ from collections import OrderedDict
|
||||||
import stix2
|
import stix2
|
||||||
|
|
||||||
from .base import _STIXBase
|
from .base import _STIXBase
|
||||||
|
from .common import ExternalReference, GranularMarking, KillChainPhase
|
||||||
from .observables import ObservableProperty
|
from .observables import ObservableProperty
|
||||||
from .other import ExternalReference, GranularMarking, KillChainPhase
|
|
||||||
from .properties import (BooleanProperty, IDProperty, IntegerProperty,
|
from .properties import (BooleanProperty, IDProperty, IntegerProperty,
|
||||||
ListProperty, ReferenceProperty, StringProperty,
|
ListProperty, ReferenceProperty, StringProperty,
|
||||||
TimestampProperty, TypeProperty)
|
TimestampProperty, TypeProperty)
|
||||||
|
|
|
@ -116,3 +116,15 @@ def test_create_bundle_with_arg_listarg_and_kwarg(indicator, malware, relationsh
|
||||||
bundle = stix2.Bundle([indicator], malware, objects=[relationship])
|
bundle = stix2.Bundle([indicator], malware, objects=[relationship])
|
||||||
|
|
||||||
assert str(bundle) == EXPECTED_BUNDLE
|
assert str(bundle) == EXPECTED_BUNDLE
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse_bundle():
|
||||||
|
bundle = stix2.parse(EXPECTED_BUNDLE)
|
||||||
|
|
||||||
|
assert bundle.type == "bundle"
|
||||||
|
assert bundle.id.startswith("bundle--")
|
||||||
|
assert bundle.spec_version == "2.0"
|
||||||
|
assert type(bundle.objects[0]) is stix2.Indicator
|
||||||
|
assert bundle.objects[0].type == 'indicator'
|
||||||
|
assert bundle.objects[1].type == 'malware'
|
||||||
|
assert bundle.objects[2].type == 'relationship'
|
||||||
|
|
Loading…
Reference in New Issue