├── testresources ├── crashesCollectionAdd │ ├── 4a0a19218e082a343a1b17e5333409af9d98f0f5 │ ├── 2ad2f27dbf67dc7f7d4e3bf9b5443262727769c5 │ ├── 9e86a464a9b06148ffc9ae9996702def6993927b │ ├── e74eb7ab46535f74e89cfb9bf511f7cc24dced39 │ ├── 73bc3d59bff1b404ec8caaf1776227b9101ac25d │ ├── 5b495522d7b28f30bfca7f6a48f566cd4a9cbd41 │ ├── 4c2a9fea1d5e599fd5768628e0ed724d7dfd4d95 │ ├── 2b609187db868f6ddc65cadea8b448cf05eeaf9a │ ├── 903fd6292e92cb5d3e35d04a75823c4cbc8447aa │ ├── 90ecc958b37b415352a1a38d7d3bf2ec68396dfe │ ├── a9cb24e41c2b7b8164c8f163b1c20be13766ef4d │ ├── c88eced809a82e217cee4c54c540419dd68c070b │ ├── 280c70497ac5ea11fdeb12989706a4cf31f265a3 │ ├── 2d8c993e1244423bd6496a8825e939b9cef9ad38 │ ├── 0f40f3730365a0a37570562c8319b8fc38b2193e │ ├── 52cd5a4de55ae0290ab45462eff6cfe1d7cdd5c0 │ ├── a859c4443f7b6de6728bb1c57f70b10472bc1cb5 │ ├── 564cbed382a71becf1263f3be737de1cce1e00fa │ ├── 4548ef313e98eaab42aad32586498d5a8b6df7b0 │ ├── 6686f33a530243baa72f42c7a33a4333cc5b7630 │ ├── 3daecaffffa67fd33432d27187384eafb72fadf7 │ └── e1c892f503235cdbc85963459136e400a28f5cc8 └── examples │ ├── indicator-for-c2-ip-address.json │ ├── indicator-to-campaign-relationship.json │ ├── LICENSE │ ├── malicious-email-indicator-with-attachment.json │ ├── infrastructure.json │ ├── README.md │ └── indicators-for-C2-with-COA.json ├── .github ├── FUNDING.yml └── workflows │ ├── build.yml │ └── tag.yml ├── go.mod ├── .gitignore ├── CONTRIBUTING.md ├── internal └── jsoncanonicalizer │ ├── LICENSE │ ├── README.md │ └── es6numfmt.go ├── error.go ├── mutex.go ├── mac.go ├── url.go ├── crash_test.go ├── incident.go ├── LICENSE ├── as.go ├── bundle.go ├── object_test.go ├── bundle_test.go ├── dns.go ├── vulnerability.go ├── go.sum ├── note.go ├── directory.go ├── doc.go ├── mutex_test.go ├── url_test.go ├── mac_test.go ├── software.go ├── as_test.go ├── directory_test.go ├── incident_test.go ├── README.md ├── course-of-action.go ├── grouping.go ├── relationship_test.go ├── schema_test.go ├── attack.go ├── vulnerability_test.go ├── dns_test.go ├── software_test.go ├── observed-data_test.go ├── report.go ├── note_test.go ├── grouping_test.go ├── identity_test.go ├── report_test.go ├── ip.go ├── regkey_test.go ├── artifact.go ├── opinion.go ├── campaign.go ├── location_test.go ├── sighting_test.go ├── artifact_test.go ├── observed-data.go ├── user_test.go ├── opinion_test.go └── x509_test.go /testresources/crashesCollectionAdd/4a0a19218e082a343a1b17e5333409af9d98f0f5: -------------------------------------------------------------------------------- 1 | f -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [TcM1911] 4 | -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/2ad2f27dbf67dc7f7d4e3bf9b5443262727769c5: -------------------------------------------------------------------------------- 1 | {"objects":[{"type":"campaign"},{}]} -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/9e86a464a9b06148ffc9ae9996702def6993927b: -------------------------------------------------------------------------------- 1 | {"objects":[{"type":"malware"},{}]} -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/e74eb7ab46535f74e89cfb9bf511f7cc24dced39: -------------------------------------------------------------------------------- 1 | {"objects":[{"type":"indicator"},{}]} -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/73bc3d59bff1b404ec8caaf1776227b9101ac25d: -------------------------------------------------------------------------------- 1 | {"objects":[{"type":"relationship"},{}]} -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/5b495522d7b28f30bfca7f6a48f566cd4a9cbd41: -------------------------------------------------------------------------------- 1 | {"objects":[{"type":"course-of-action"},{}]} -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/4c2a9fea1d5e599fd5768628e0ed724d7dfd4d95: -------------------------------------------------------------------------------- 1 | {"objects":[{"type":"infrastructure"},{"id":"0"}]} -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/2b609187db868f6ddc65cadea8b448cf05eeaf9a: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TcM1911/stix2/HEAD/testresources/crashesCollectionAdd/2b609187db868f6ddc65cadea8b448cf05eeaf9a -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/903fd6292e92cb5d3e35d04a75823c4cbc8447aa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TcM1911/stix2/HEAD/testresources/crashesCollectionAdd/903fd6292e92cb5d3e35d04a75823c4cbc8447aa -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/90ecc958b37b415352a1a38d7d3bf2ec68396dfe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TcM1911/stix2/HEAD/testresources/crashesCollectionAdd/90ecc958b37b415352a1a38d7d3bf2ec68396dfe -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/a9cb24e41c2b7b8164c8f163b1c20be13766ef4d: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TcM1911/stix2/HEAD/testresources/crashesCollectionAdd/a9cb24e41c2b7b8164c8f163b1c20be13766ef4d -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/c88eced809a82e217cee4c54c540419dd68c070b: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TcM1911/stix2/HEAD/testresources/crashesCollectionAdd/c88eced809a82e217cee4c54c540419dd68c070b -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/280c70497ac5ea11fdeb12989706a4cf31f265a3: -------------------------------------------------------------------------------- 1 | [{"type":"marking-definition","id":"marking-definition--00000000-0000-0000-0000-000000000000"},{"id":"--00000000-0000-0000-0000-000000000000"}] -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/2d8c993e1244423bd6496a8825e939b9cef9ad38: -------------------------------------------------------------------------------- 1 | [{"type":"language-content","id":"language-content--00000000-0000-0000-0000-000000000000"},{"id":"--00000000-0000-0000-0000-000000000000"}] -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/TcM1911/stix2 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/google/uuid v1.3.0 7 | github.com/stretchr/testify v1.4.0 8 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect 9 | github.com/xeipuuv/gojsonschema v1.2.0 10 | ) 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Want to contribute? 2 | 3 | We welcome tickets, pull requests, feature suggestions. 4 | 5 | When developing, please try to comply to the general code style that we try to 6 | maintain across the project. When introducing new features or fixing 7 | significant bugs, please provide tests and also include some concise 8 | information. 9 | 10 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v2 18 | with: 19 | go-version: 1.16 20 | 21 | - name: Test 22 | run: go test -race -coverprofile=coverage.txt -covermode=atomic -tags "long_test" 23 | 24 | - name: Codecov 25 | uses: codecov/codecov-action@v1.5.2 26 | -------------------------------------------------------------------------------- /.github/workflows/tag.yml: -------------------------------------------------------------------------------- 1 | name: Create a tag 2 | 3 | on: 4 | workflow_dispatch: 5 | inputs: 6 | ver: 7 | description: 'Tag name' 8 | required: true 9 | 10 | jobs: 11 | release_tagging: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | 16 | - name: Setup git 17 | run: git config user.name "GitHub Actions Bot" && git config user.email "<>" 18 | 19 | - name: Tag commit 20 | run: git tag ${{ github.event.inputs.ver }} 21 | 22 | - name: Push tag 23 | run: git push --tags 24 | -------------------------------------------------------------------------------- /internal/jsoncanonicalizer/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2018 Anders Rundgren 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | https://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | 15 | -------------------------------------------------------------------------------- /error.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import "errors" 7 | 8 | var ( 9 | // ErrPropertyMissing is returned if not at least one of the required 10 | // properties are missing 11 | ErrPropertyMissing = errors.New("at least one of the description, url, or externalID properties MUST be present") 12 | // ErrInvalidProperty is returned if the value for a property is invalid. 13 | ErrInvalidProperty = errors.New("invalid value for the property") 14 | // ErrInvalidParameter is returned if function is called with an invalid 15 | // function parameter. 16 | ErrInvalidParameter = errors.New("invalid parameter") 17 | ) 18 | -------------------------------------------------------------------------------- /testresources/examples/indicator-for-c2-ip-address.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "bundle", 3 | "id": "bundle--44af6c39-c09b-49c5-9de2-394224b04982", 4 | "objects": [ 5 | { 6 | "type": "indicator", 7 | "spec_version": "2.1", 8 | "pattern_type": "stix", 9 | "id": "indicator--33fe3b22-0201-47cf-85d0-97c02164528d", 10 | "created": "2014-05-08T09:00:00.000Z", 11 | "modified": "2014-05-08T09:00:00.000Z", 12 | "name": "IP Address for known C2 channel", 13 | "description": "Test description C2 channel.", 14 | "indicator_types": [ 15 | "malicious-activity" 16 | ], 17 | "pattern": "[ipv4-addr:value = '10.0.0.0']", 18 | "valid_from": "2014-05-08T09:00:00.000000Z" 19 | } 20 | ] 21 | } 22 | -------------------------------------------------------------------------------- /mutex.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import "fmt" 7 | 8 | // Mutex represents the properties of a mutual exclusion (mutex) object. 9 | type Mutex struct { 10 | STIXCyberObservableObject 11 | // Name specifies the name of the mutex object. 12 | Name string `json:"name"` 13 | } 14 | 15 | func (o *Mutex) MarshalJSON() ([]byte, error) { 16 | return marshalToJSONHelper(o) 17 | } 18 | 19 | // NewMutex creates a new Mutex object. 20 | func NewMutex(value string, opts ...STIXOption) (*Mutex, error) { 21 | if value == "" { 22 | return nil, ErrInvalidParameter 23 | } 24 | base := newSTIXCyberObservableObject(TypeMutex) 25 | obj := &Mutex{ 26 | STIXCyberObservableObject: base, 27 | Name: value, 28 | } 29 | 30 | err := applyOptions(obj, opts) 31 | obj.ID = NewObservableIdentifier(fmt.Sprintf("[\"%s\"]", value), TypeMutex) 32 | return obj, err 33 | } 34 | -------------------------------------------------------------------------------- /mac.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import "fmt" 7 | 8 | // MACAddress represents a single Media Access Control (MAC) address. 9 | type MACAddress struct { 10 | STIXCyberObservableObject 11 | // Value specifies the value of a single MAC address. 12 | Value string `json:"value"` 13 | } 14 | 15 | func (o *MACAddress) MarshalJSON() ([]byte, error) { 16 | return marshalToJSONHelper(o) 17 | } 18 | 19 | // NewMACAddress creates a new MACAddress object. 20 | func NewMACAddress(value string, opts ...STIXOption) (*MACAddress, error) { 21 | if value == "" { 22 | return nil, ErrInvalidParameter 23 | } 24 | base := newSTIXCyberObservableObject(TypeMACAddress) 25 | obj := &MACAddress{ 26 | STIXCyberObservableObject: base, 27 | Value: value, 28 | } 29 | 30 | err := applyOptions(obj, opts) 31 | obj.ID = NewObservableIdentifier(fmt.Sprintf("[\"%s\"]", value), TypeMACAddress) 32 | return obj, err 33 | } 34 | -------------------------------------------------------------------------------- /internal/jsoncanonicalizer/README.md: -------------------------------------------------------------------------------- 1 | ## JSON Canonicalizer for Go 2 | 3 | The [jsoncanonicalizer](src/webpki.org/jsoncanonicalizer) 4 | folder contains the source code for a 5 | JCS (RFC 8785) compliant JSON canonicalizer written in Go. 6 | 7 | ### Building and testing 8 | 9 | - Set GOPATH to this directory. 10 | 11 | - For running `verify-numbers.go` you need to download a 3Gb+ file with test 12 | data described in the root directory [testdata](../testdata). This file can be stored in 13 | any directory and requires updating the file path in `verify-numbers.go`. 14 | 15 | - Perform the commands: 16 | ```code 17 | $ cd test 18 | $ go build webpki.org/jsoncanonicalizer 19 | $ go run verify-canonicalization.go 20 | $ go run verify-numbers.go 21 | ``` 22 | 23 | 24 | ### Using the JSON canonicalizer 25 | 26 | ```go 27 | import "webpki.org/jsoncanonicalizer" 28 | 29 | func Transform(jsonData []byte) (result []byte, e error) 30 | ``` 31 | Note that both the input and the result is assumed to be in UTF-8 format. 32 | 33 | ### Constraints 34 | The JSON canonicalizer only accepts a JSON _Object_ or _Array_ as the top level data type. 35 | 36 | -------------------------------------------------------------------------------- /url.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import "fmt" 7 | 8 | // URL object represents the properties of a uniform resource locator (URL). 9 | type URL struct { 10 | STIXCyberObservableObject 11 | // Value specifies the value of the URL. The value of this property MUST 12 | // conform to RFC3986, more specifically section 1.1.3 with reference to 13 | // the definition for "Uniform Resource Locator". 14 | Value string `json:"value"` 15 | } 16 | 17 | func (o *URL) MarshalJSON() ([]byte, error) { 18 | return marshalToJSONHelper(o) 19 | } 20 | 21 | // NewURL creates a new URL object. 22 | func NewURL(value string, opts ...STIXOption) (*URL, error) { 23 | if value == "" { 24 | return nil, ErrInvalidParameter 25 | } 26 | base := newSTIXCyberObservableObject(TypeURL) 27 | obj := &URL{ 28 | STIXCyberObservableObject: base, 29 | Value: value, 30 | } 31 | 32 | err := applyOptions(obj, opts) 33 | obj.ID = NewObservableIdentifier(fmt.Sprintf("[\"%s\"]", value), TypeURL) 34 | return obj, err 35 | } 36 | -------------------------------------------------------------------------------- /crash_test.go: -------------------------------------------------------------------------------- 1 | package stix2_test 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path/filepath" 7 | "testing" 8 | 9 | "github.com/TcM1911/stix2" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestCrashCollectionAdd(t *testing.T) { 14 | runFolderTest(t, "crashesCollectionAdd", func(a *assert.Assertions, testData []byte) { 15 | a.NotPanics(func() { 16 | col, err := stix2.FromJSON(testData, stix2.DropCustomOption()) 17 | a.Error(err) 18 | a.Nil(col) 19 | }) 20 | }) 21 | } 22 | 23 | type testFunc func(a *assert.Assertions, testData []byte) 24 | 25 | func runFolderTest(t *testing.T, path string, fn testFunc) { 26 | assert := assert.New(t) 27 | pth, err := filepath.Abs(filepath.Join("testresources", path)) 28 | if err != nil { 29 | t.Fatalf("Error when resolving abs path to resource files: %s\n", err) 30 | } 31 | info, err := ioutil.ReadDir(pth) 32 | if err != nil { 33 | t.Fatalf("Error when loading resource files: %s\n", err) 34 | } 35 | for _, f := range info { 36 | if f.IsDir() { 37 | continue 38 | } 39 | fr, err := os.OpenFile(filepath.Join(pth, f.Name()), os.O_RDONLY, 0600) 40 | if err != nil { 41 | t.Fatalf("Error when opening the file: %s\n", err) 42 | } 43 | inData, err := ioutil.ReadAll(fr) 44 | assert.NoError(err) 45 | fr.Close() 46 | 47 | fn(assert, inData) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/0f40f3730365a0a37570562c8319b8fc38b2193e: -------------------------------------------------------------------------------- 1 | {"":"","":"","objects":[{ 2 | "type": "campaign", 3 | "spec_version": "2.1", 4 | "id": "campaign--b549a58c-afd9-4847-85c3-5be13d56d3cc", 5 | "created": "2014-09-09T19:58:39.609Z", 6 | "modified": "2014-09-09T19:58:39.609Z", 7 | "name": "Operation Omega" 8 | }, 9 | { 10 | "typ": "indicator", 11 | "spec_version": "2.1", 12 | "pattern_type": "stix", 13 | "name": "test_name", 14 | "description": "Test description.", 15 | "id": "indicator--c43a0a05-e8d2-4f64-ae37-3f3fb153f8d9", 16 | "created": "2014-09-09T19:58:39.609Z", 17 | "modified": "2014-09-09T19:58:39.609Z", 18 | "indicator_types": [ 19 | "malicious-activity" 20 | ], 21 | "pattern": "[ ipv4-addr:value = '10.0.0.0' ]", 22 | "valid_from": "2014-09-09T19:58:39.609000Z" 23 | }, 24 | { 25 | "type": "relationship", 26 | "spec_version": "2.1", 27 | "id": "relationship--eca24e47-2259-4850-9705-fd1065c77236", 28 | "relationship_type": "indicates", 29 | "created": "2014-09-09T19:58:39.609Z", 30 | "modified": "2014-09-09T19:58:39.609Z", 31 | "source_ref": "indicator--c43a0a05-e8d2-4f64-ae37-3f3fb153f8d9", 32 | "target_ref": "campaign--b549a58c-afd9-4847-85c3-5be13d56d3cc" 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/52cd5a4de55ae0290ab45462eff6cfe1d7cdd5c0: -------------------------------------------------------------------------------- 1 | {"":"","":"","objects":[{"":"","":"","":"","":"","":"","":"","":""},{"":"","":"","":"","":"","":"","":"","":true,"":"","":[""],"":""},{"":"","":"","":"","":"","":"","":"","":"","":[""],"":"","":"","":"","":""},{"type":"indicator","":"","":"","":"","":"","":"","":[""],"":"","":"","":"","":"","":""},{"":"","":"","id":"f","":"","":"","":"","":"","":""},{"":"","":"","":"","":"","":"","":"","":"","":"","":""},{"":"","":"","":"","":"","":"", "modified": "2017-01-27T13:49:53.997Z", 2 | "relationship_type": "indicates", 3 | "source_ref": "indicator--a932fcc6-e032-476c-826f-cb970a5a1ade", 4 | "target_ref": "malware--fdd60b30-b67c-41e3-b0b9-f01faf20d111" 5 | 6 | }, 7 | { 8 | "type": "relationship", 9 | "spec_version": "2.1", 10 | "id": "relationship--9606dac3-965a-47d3-b270-8b17431ba0e4", 11 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 12 | "created": "2014-05-08T09:00:00.000Z", 13 | "modified": "2014-05-08T09:00:00.000Z", 14 | "relationship_type": "indicates", 15 | "source_ref": "indicator--53fe3b22-0201-47cf-85d0-97c02164528d", 16 | "target_ref": "malware--fdd60b30-b67c-41e3-b0b9-f01faf20d111" 17 | 18 | } 19 | ] 20 | } -------------------------------------------------------------------------------- /incident.go: -------------------------------------------------------------------------------- 1 | // Copyright 2022 Patrick Bédat. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | // The Incident object in STIX 2.1 is a stub. 7 | // It is included to support basic use cases but does not contain properties 8 | // to represent metadata about incidents. Future STIX 2 releases will expand 9 | // it to include these capabilities. 10 | // It is suggested that it is used as an extension point for an Incident object 11 | // defined using the extension facility described in section 7.3. 12 | type Incident struct { 13 | STIXDomainObject 14 | // Name used to identify the Incident. 15 | Name string `json:"name"` 16 | // A description that provides more details and context about the Incident, 17 | // potentially including its purpose and its key characteristics. 18 | Description string `json:"description,omitempty"` 19 | } 20 | 21 | func (o *Incident) MarshalJSON() ([]byte, error) { 22 | return marshalToJSONHelper(o) 23 | } 24 | 25 | // NewCampaign creates a new Campaign object. 26 | func NewIncident(name string, opts ...STIXOption) (*Incident, error) { 27 | if name == "" { 28 | return nil, ErrPropertyMissing 29 | } 30 | base := newSTIXDomainObject(TypeIncident) 31 | obj := &Incident{STIXDomainObject: base, Name: name} 32 | 33 | err := applyOptions(obj, opts) 34 | 35 | return obj, err 36 | } 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2020, TcM1911 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /as.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import "fmt" 7 | 8 | // AutonomousSystem object represents the properties of an Autonomous System (AS). 9 | type AutonomousSystem struct { 10 | STIXCyberObservableObject 11 | // Number specifies the number assigned to the AS. Such assignments are 12 | // typically performed by a Regional Internet Registry (RIR). 13 | Number int64 `json:"number"` 14 | // Name specifies the name of the AS. 15 | Name string `json:"name,omitempty"` 16 | // RIR specifies the name of the Regional Internet Registry (RIR) that 17 | // assigned the number to the AS. 18 | RIR string `json:"rir,omitempty"` 19 | } 20 | 21 | func (o *AutonomousSystem) MarshalJSON() ([]byte, error) { 22 | return marshalToJSONHelper(o) 23 | } 24 | 25 | // NewAutonomousSystem creates a new AutonomousSystem object. 26 | func NewAutonomousSystem(number int64, opts ...STIXOption) (*AutonomousSystem, error) { 27 | if number == 0 { 28 | return nil, ErrInvalidParameter 29 | } 30 | base := newSTIXCyberObservableObject(TypeAutonomousSystem) 31 | obj := &AutonomousSystem{ 32 | STIXCyberObservableObject: base, 33 | Number: number, 34 | } 35 | 36 | err := applyOptions(obj, opts) 37 | obj.ID = NewObservableIdentifier(fmt.Sprintf("[%d]", number), TypeAutonomousSystem) 38 | return obj, err 39 | } 40 | -------------------------------------------------------------------------------- /testresources/examples/indicator-to-campaign-relationship.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "bundle", 3 | "id": "bundle--44af6c39-c09b-49c5-9de2-394224b04982", 4 | "objects": [ 5 | { 6 | "type": "campaign", 7 | "spec_version": "2.1", 8 | "id": "campaign--b549a58c-afd9-4847-85c3-5be13d56d3cc", 9 | "created": "2014-09-09T19:58:39.609Z", 10 | "modified": "2014-09-09T19:58:39.609Z", 11 | "name": "Operation Omega" 12 | }, 13 | { 14 | "type": "indicator", 15 | "spec_version": "2.1", 16 | "pattern_type": "stix", 17 | "name": "test_name", 18 | "description": "Test description.", 19 | "id": "indicator--c43a0a05-e8d2-4f64-ae37-3f3fb153f8d9", 20 | "created": "2014-09-09T19:58:39.609Z", 21 | "modified": "2014-09-09T19:58:39.609Z", 22 | "indicator_types": [ 23 | "malicious-activity" 24 | ], 25 | "pattern": "[ ipv4-addr:value = '10.0.0.0' ]", 26 | "valid_from": "2014-09-09T19:58:39.609000Z" 27 | }, 28 | { 29 | "type": "relationship", 30 | "spec_version": "2.1", 31 | "id": "relationship--eca24e47-2259-4850-9705-fd1065c77236", 32 | "relationship_type": "indicates", 33 | "created": "2014-09-09T19:58:39.609Z", 34 | "modified": "2014-09-09T19:58:39.609Z", 35 | "source_ref": "indicator--c43a0a05-e8d2-4f64-ae37-3f3fb153f8d9", 36 | "target_ref": "campaign--b549a58c-afd9-4847-85c3-5be13d56d3cc" 37 | } 38 | ] 39 | } 40 | -------------------------------------------------------------------------------- /bundle.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "fmt" 9 | ) 10 | 11 | // Bundle is a collection of arbitrary STIX Objects grouped together in a 12 | // single container. A Bundle does not have any semantic meaning and the 13 | // objects contained within the Bundle are not considered related by virtue of 14 | // being in the same Bundle. 15 | type Bundle struct { 16 | // Type property identifies the type of object. 17 | Type STIXType `json:"type"` 18 | // ID is an identifier for this Bundle. The id property for the Bundle is 19 | // designed to help tools that may need it for processing, but tools are 20 | // not required to store or track it. Tools that consume STIX should not 21 | // rely on the ability to refer to bundles by ID. 22 | ID Identifier `json:"id"` 23 | // Objects specifies a set of one or more STIX Objects. 24 | Objects []json.RawMessage `json:"objects,omitempty"` 25 | } 26 | 27 | // NewBundle creates a new STIX Bundle. 28 | func NewBundle(objs ...STIXObject) (*Bundle, error) { 29 | b := &Bundle{Type: TypeBundle, ID: NewIdentifier(TypeBundle)} 30 | a := make([]json.RawMessage, 0, len(objs)) 31 | for _, v := range objs { 32 | data, err := json.Marshal(v) 33 | if err != nil { 34 | return nil, fmt.Errorf("error when encoding %s to JSON: %w", v.GetID(), err) 35 | } 36 | a = append(a, data) 37 | } 38 | b.Objects = a 39 | return b, nil 40 | } 41 | -------------------------------------------------------------------------------- /testresources/examples/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) [2016], OASIS Open 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the copyright holder nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /object_test.go: -------------------------------------------------------------------------------- 1 | package stix2 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | "github.com/stretchr/testify/require" 8 | ) 9 | 10 | func TestCommonRelationships(t *testing.T) { 11 | assert := assert.New(t) 12 | require := require.New(t) 13 | 14 | sdo, err := NewAttackPattern("test") 15 | require.NoError(err) 16 | sco, err := NewDomainName("example.com") 17 | require.NoError(err) 18 | 19 | rel, err := sdo.AddDerivedFrom(sco.ID) 20 | require.NoError(err) 21 | assert.Equal(RelationshipTypeDerivedFrom, rel.RelationshipType) 22 | assert.Equal(sdo.ID, rel.Source) 23 | assert.Equal(sco.ID, rel.Target) 24 | 25 | rel, err = sdo.AddDuplicateOf(sco.ID) 26 | require.NoError(err) 27 | assert.Equal(RelationshipTypeDuplicateOf, rel.RelationshipType) 28 | assert.Equal(sdo.ID, rel.Source) 29 | assert.Equal(sco.ID, rel.Target) 30 | 31 | rel, err = sdo.AddRelatedTo(sco.ID) 32 | require.NoError(err) 33 | assert.Equal(RelationshipTypeRelatedTo, rel.RelationshipType) 34 | assert.Equal(sdo.ID, rel.Source) 35 | assert.Equal(sco.ID, rel.Target) 36 | 37 | rel, err = sco.AddDerivedFrom(sdo.ID) 38 | require.NoError(err) 39 | assert.Equal(RelationshipTypeDerivedFrom, rel.RelationshipType) 40 | assert.Equal(sco.ID, rel.Source) 41 | assert.Equal(sdo.ID, rel.Target) 42 | 43 | rel, err = sco.AddDuplicateOf(sdo.ID) 44 | require.NoError(err) 45 | assert.Equal(RelationshipTypeDuplicateOf, rel.RelationshipType) 46 | assert.Equal(sco.ID, rel.Source) 47 | assert.Equal(sdo.ID, rel.Target) 48 | 49 | rel, err = sco.AddRelatedTo(sdo.ID) 50 | require.NoError(err) 51 | assert.Equal(RelationshipTypeRelatedTo, rel.RelationshipType) 52 | assert.Equal(sco.ID, rel.Source) 53 | assert.Equal(sdo.ID, rel.Target) 54 | } 55 | -------------------------------------------------------------------------------- /bundle_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestBundle(t *testing.T) { 14 | assert := assert.New(t) 15 | data := []byte( 16 | ` 17 | { 18 | "type": "bundle", 19 | "id": "bundle--5d0092c5-5f74-4287-9642-33f4c354e56d", 20 | "objects": [ 21 | { 22 | "type": "indicator", 23 | "spec_version": "2.1", 24 | "id": "indicator--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", 25 | "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", 26 | "created": "2016-04-29T14:09:00.000Z", 27 | "modified": "2016-04-29T14:09:00.000Z", 28 | "object_marking_refs": ["marking-definition--089a6ecb-cc15-43cc-9494-767639779123"], 29 | "name": "Poison Ivy Malware", 30 | "description": "This file is part of Poison Ivy", 31 | "pattern": "[file:hashes.'SHA-256' = 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f']" 32 | } 33 | ] 34 | } 35 | `) 36 | var bundle Bundle 37 | err := json.Unmarshal(data, &bundle) 38 | 39 | assert.NoError(err) 40 | assert.Len(bundle.Objects, 1) 41 | 42 | var typ Indicator 43 | err = json.Unmarshal(bundle.Objects[0], &typ) 44 | assert.NoError(err) 45 | assert.Equal("Poison Ivy Malware", typ.Name) 46 | } 47 | 48 | func TestCreateBundle(t *testing.T) { 49 | assert := assert.New(t) 50 | ipStr := "10.0.0.1" 51 | ip, err := NewIPv4Address(ipStr) 52 | assert.NoError(err) 53 | 54 | b, err := NewBundle(ip) 55 | assert.NoError(err) 56 | assert.NotNil(b) 57 | 58 | data, err := json.Marshal(b) 59 | assert.NoError(err) 60 | assert.Contains(string(data), `"id":"ipv4-addr--8e9dc7c8-b845-5cfb-9c37-cff3a18e08d6","spec_version":"2.1","type":"ipv4-addr","value":"10.0.0.1"`) 61 | } 62 | -------------------------------------------------------------------------------- /dns.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import "fmt" 7 | 8 | // DomainName object represents the properties of a network domain name. 9 | type DomainName struct { 10 | STIXCyberObservableObject 11 | // Value specifies the value of the domain name. The value of this property 12 | // MUST conform to RFC1034, and each domain and sub-domain contained within 13 | // the domain name MUST conform to RFC5890. 14 | Value string `json:"value"` 15 | // ResolvesTo specifies a list of references to one or more IP addresses or 16 | // domain names that the domain name resolves to. The objects referenced in 17 | // this list MUST be of type ipv4-addr or ipv6-addr or domain-name (for 18 | // cases such as CNAME records). 19 | ResolvesTo []Identifier `json:"resolves_to_refs,omitempty"` 20 | } 21 | 22 | func (o *DomainName) MarshalJSON() ([]byte, error) { 23 | return marshalToJSONHelper(o) 24 | } 25 | 26 | // AddResolvesTo describes that this Domain Name resolves to one or more IP 27 | // addresses or domain names. 28 | func (c *DomainName) AddResolvesTo(id Identifier, opts ...STIXOption) (*Relationship, error) { 29 | if !IsValidIdentifier(id) || !id.ForTypes(TypeDomainName, TypeIPv4Addr, TypeIPv6Addr) { 30 | return nil, ErrInvalidParameter 31 | } 32 | return NewRelationship(RelationshipTypeResolvesTo, c.ID, id, opts...) 33 | } 34 | 35 | // NewDomainName creates a new DomainName object. 36 | func NewDomainName(value string, opts ...STIXOption) (*DomainName, error) { 37 | if value == "" { 38 | return nil, ErrInvalidParameter 39 | } 40 | base := newSTIXCyberObservableObject(TypeDomainName) 41 | obj := &DomainName{ 42 | STIXCyberObservableObject: base, 43 | Value: value, 44 | } 45 | 46 | err := applyOptions(obj, opts) 47 | obj.ID = NewObservableIdentifier(fmt.Sprintf("[\"%s\"]", value), TypeDomainName) 48 | return obj, err 49 | } 50 | -------------------------------------------------------------------------------- /vulnerability.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | // Vulnerability is "a mistake in software that can be directly used by a 7 | // hacker to gain access to a system or network". For example, if a piece of 8 | // malware exploits CVE-2015-12345, a Malware object could be linked to a 9 | // Vulnerability object that references CVE-2015-12345. CVE is a list of 10 | // information security vulnerabilities and exposures that provides common 11 | // names for publicly known problems. 12 | // 13 | // The Vulnerability SDO is primarily used to link to external definitions of 14 | // vulnerabilities or to describe 0-day vulnerabilities that do not yet have an 15 | // external definition. Typically, other SDOs assert relationships to 16 | // Vulnerability objects when a specific vulnerability is targeted and 17 | // exploited as part of malicious cyber activity. As such, Vulnerability 18 | // objects can be used as a linkage to the asset management and compliance 19 | // process. 20 | type Vulnerability struct { 21 | STIXDomainObject 22 | // Name is used to identify the Vulnerability. 23 | Name string `json:"name"` 24 | // Description provides more details and context about the Vulnerability, 25 | // potentially including its purpose and its key characteristics. 26 | Description string `json:"description,omitempty"` 27 | } 28 | 29 | func (o *Vulnerability) MarshalJSON() ([]byte, error) { 30 | return marshalToJSONHelper(o) 31 | } 32 | 33 | // NewVulnerability creates a new Vulnerability object. 34 | func NewVulnerability(name string, opts ...STIXOption) (*Vulnerability, error) { 35 | if name == "" { 36 | return nil, ErrPropertyMissing 37 | } 38 | base := newSTIXDomainObject(TypeVulnerability) 39 | obj := &Vulnerability{ 40 | STIXDomainObject: base, 41 | Name: name, 42 | } 43 | 44 | err := applyOptions(obj, opts) 45 | return obj, err 46 | } 47 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= 4 | github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 5 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 8 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 9 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 10 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 11 | github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 12 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= 13 | github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= 14 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= 15 | github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= 16 | github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= 17 | github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= 18 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 19 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 20 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 21 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 22 | -------------------------------------------------------------------------------- /note.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | // Note is intended to convey informative text to provide further context 7 | // and/or to provide additional analysis not contained in the STIX Objects, 8 | // Marking Definition objects, or Language Content objects which the Note 9 | // relates to. Notes can be created by anyone (not just the original object 10 | // creator). For example, an analyst may add a Note to a Campaign object 11 | // created by another organization indicating that they've seen posts related 12 | // to that Campaign on a hacker forum. Because Notes are typically (though not 13 | // always) created by human analysts and are comprised of human-oriented text, 14 | // they contain an additional property to capture the analyst(s) that created 15 | // the Note. This is distinct from the created_by_ref property, which is meant 16 | // to capture the organization that created the object. 17 | type Note struct { 18 | STIXDomainObject 19 | // Abstract is a brief summary of the note content. 20 | Abstract string `json:"abstract,omitempty"` 21 | // Content is the content of the note. 22 | Content string `json:"content"` 23 | // Authors is/are the name of the author(s) of this note (e.g., the 24 | // analyst(s) that created it). 25 | Authors []string `json:"authors,omitempty"` 26 | // Objects are the STIX Objects that the note is being applied to. 27 | Objects []Identifier `json:"object_refs"` 28 | } 29 | 30 | func (o *Note) MarshalJSON() ([]byte, error) { 31 | return marshalToJSONHelper(o) 32 | } 33 | 34 | // NewNote creates a new Note object. 35 | func NewNote(content string, objects []Identifier, opts ...STIXOption) (*Note, error) { 36 | if len(objects) == 0 { 37 | return nil, ErrPropertyMissing 38 | } 39 | base := newSTIXDomainObject(TypeNote) 40 | obj := &Note{ 41 | STIXDomainObject: base, 42 | Content: content, 43 | Objects: objects, 44 | } 45 | 46 | err := applyOptions(obj, opts) 47 | return obj, err 48 | } 49 | -------------------------------------------------------------------------------- /directory.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import "fmt" 7 | 8 | // Directory object represents the properties common to a file system 9 | // directory. 10 | type Directory struct { 11 | STIXCyberObservableObject 12 | // Path specifies the path, as originally observed, to the directory on the 13 | // file system. 14 | Path string `json:"path"` 15 | // PathEnc specifies the observed encoding for the path. The value MUST be 16 | // specified if the path is stored in a non-Unicode encoding. This value 17 | // MUST be specified using the corresponding name from the 2013-12-20 18 | // revision of the IANA character set registry. If the preferred MIME name 19 | // for a character set is defined, this value MUST be used; if it is not 20 | // defined, then the Name value from the registry MUST be used instead. 21 | PathEnc string `json:"path_enc,omitempty"` 22 | // Ctime specifies the date/time the directory was created. 23 | Ctime *Timestamp `json:"ctime,omitempty"` 24 | // Mtime specifies the date/time the directory was last written 25 | // to/modified. 26 | Mtime *Timestamp `json:"mtime,omitempty"` 27 | // Atime specifies the date/time the directory was last accessed. 28 | Atime *Timestamp `json:"atime,omitempty"` 29 | // Contains specifies a list of references to other File and/or Directory 30 | // objects contained within the directory. 31 | Contains []Identifier `json:"contains_refs,omitempty"` 32 | } 33 | 34 | func (o *Directory) MarshalJSON() ([]byte, error) { 35 | return marshalToJSONHelper(o) 36 | } 37 | 38 | // NewDirectory creates a new Directory object. 39 | func NewDirectory(path string, opts ...STIXOption) (*Directory, error) { 40 | if path == "" { 41 | return nil, ErrInvalidParameter 42 | } 43 | base := newSTIXCyberObservableObject(TypeDirectory) 44 | obj := &Directory{ 45 | STIXCyberObservableObject: base, 46 | Path: path, 47 | } 48 | 49 | err := applyOptions(obj, opts) 50 | obj.ID = NewObservableIdentifier(fmt.Sprintf("[\"%s\"]", path), TypeDirectory) 51 | return obj, err 52 | } 53 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | /* 5 | Package stix2 is a pure Go library for working with Structured Threat 6 | Information Expression (STIX™) version 2.x data. 7 | 8 | Parsing STIX JSON data: 9 | 10 | collection, err := stix2.FromJSON(jsonData) 11 | 12 | Creating a STIX Bundle, is as easy as creating a set of STIX objects and add 13 | them to the Collection. The Bundle can be created by calling the `ToBundle` 14 | method on the Collection object. The Bundle can be serialized to `JSON` 15 | using the `JSON` encoder in the standard library. 16 | 17 | c := stix2.New() 18 | ip, err := stix2.NewIPv4Address("10.0.0.1") 19 | c.Add(ip) 20 | ip, err = stix2.NewIPv4Address("10.0.0.2") 21 | c.Add(ip) 22 | b, err := c.ToBundle() 23 | data, err := json.Marshal(b) 24 | 25 | Example of a malware using an infrastructure. Taken from: 26 | https://docs.oasis-open.org/cti/stix/v2.1/csprd02/stix-v2.1-csprd02.html#_Toc26789941 27 | 28 | collection := stix2.New() 29 | domain, err := stix2.NewDomainName("example.com") 30 | collection.Add(domain) 31 | 32 | mal, err := stix2.NewMalware( 33 | false, 34 | stix2.OptionName("IMDDOS"), 35 | stix2.OptionTypes([]string{stix2.MalwareTypeBot}), 36 | ) 37 | collection.Add(mal) 38 | 39 | infra, err := stix2.NewInfrastructure( 40 | "Example Target List Host", 41 | []string{stix2.InfrastructureTypeHostingTargetLists}, 42 | ) 43 | collection.Add(infra) 44 | 45 | ref, err := mal.AddUses(infra.ID) 46 | collection.Add(ref) 47 | 48 | ref, err = infra.AddConsistsOf(domain.ID) 49 | collection.Add(ref) 50 | 51 | b, err := collection.ToBundle() 52 | data, err := json.MarshalIndent(b, "", "\t") 53 | 54 | Extensions and Customization 55 | 56 | With the release of version 2.1 of the specification custom properties 57 | has been deprecated. Instead, `property-extension` functionality should 58 | be used. This library supports parsing objects with old custom properties 59 | for backwards compatibility. The fields can be accessed via the 60 | `GetExtendedTopLevelProperties` method. 61 | 62 | See the examples on how to work with extensions. 63 | */ 64 | package stix2 65 | -------------------------------------------------------------------------------- /mutex_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestMutex(t *testing.T) { 14 | assert := assert.New(t) 15 | 16 | val := "__CLEANSWEEP__" 17 | 18 | t.Run("missing_property", func(t *testing.T) { 19 | obj, err := NewMutex("") 20 | assert.Nil(obj) 21 | assert.Equal(ErrInvalidParameter, err) 22 | }) 23 | 24 | t.Run("with_property", func(t *testing.T) { 25 | obj, err := NewMutex(val, nil) 26 | assert.NotNil(obj) 27 | assert.NoError(err) 28 | }) 29 | 30 | t.Run("payload_with_options", func(t *testing.T) { 31 | marking := make([]*GranularMarking, 0) 32 | objmark := []Identifier{Identifier("id")} 33 | specVer := "2.0" 34 | 35 | opts := []STIXOption{ 36 | OptionGranularMarking(marking), 37 | OptionObjectMarking(objmark), 38 | OptionSpecVersion(specVer), 39 | OptionDefanged(true), 40 | } 41 | obj, err := NewMutex(val, opts...) 42 | assert.NotNil(obj) 43 | assert.NoError(err) 44 | assert.Equal(marking, obj.GranularMarking) 45 | assert.Equal(objmark, obj.ObjectMarking) 46 | assert.Equal(specVer, obj.SpecVersion) 47 | assert.True(obj.Defanged) 48 | 49 | assert.Equal(val, obj.Name) 50 | }) 51 | 52 | t.Run("id-generation", func(t *testing.T) { 53 | tests := []struct { 54 | name string 55 | id string 56 | }{ 57 | {val, "mutex--840b4dcd-0db1-5190-89c1-1b664c5ab0ea"}, 58 | } 59 | for _, test := range tests { 60 | obj, err := NewMutex(test.name) 61 | assert.NoError(err) 62 | assert.Equal(Identifier(test.id), obj.ID) 63 | } 64 | }) 65 | 66 | t.Run("parse_json", func(t *testing.T) { 67 | data := []byte(`{ 68 | "type": "mutex", 69 | "spec_version": "2.1", 70 | "id": "mutex--eba44954-d4e4-5d3b-814c-2b17dd8de300", 71 | "name": "__CLEANSWEEP__" 72 | }`) 73 | var obj *Mutex 74 | err := json.Unmarshal(data, &obj) 75 | assert.NoError(err) 76 | assert.Equal(Identifier("mutex--eba44954-d4e4-5d3b-814c-2b17dd8de300"), obj.ID) 77 | assert.Equal("2.1", obj.SpecVersion) 78 | assert.Equal(TypeMutex, obj.Type) 79 | assert.Equal("__CLEANSWEEP__", obj.Name) 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /url_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestURL(t *testing.T) { 14 | assert := assert.New(t) 15 | 16 | val := "https://example.com/research/index.html" 17 | 18 | t.Run("missing_property", func(t *testing.T) { 19 | obj, err := NewURL("") 20 | assert.Nil(obj) 21 | assert.Equal(ErrInvalidParameter, err) 22 | }) 23 | 24 | t.Run("with_property", func(t *testing.T) { 25 | obj, err := NewURL(val, nil) 26 | assert.NotNil(obj) 27 | assert.NoError(err) 28 | }) 29 | 30 | t.Run("payload_with_options", func(t *testing.T) { 31 | marking := make([]*GranularMarking, 0) 32 | objmark := []Identifier{Identifier("id")} 33 | specVer := "2.0" 34 | 35 | opts := []STIXOption{ 36 | OptionGranularMarking(marking), 37 | OptionObjectMarking(objmark), 38 | OptionSpecVersion(specVer), 39 | OptionDefanged(true), 40 | } 41 | obj, err := NewURL(val, opts...) 42 | assert.NotNil(obj) 43 | assert.NoError(err) 44 | assert.Equal(marking, obj.GranularMarking) 45 | assert.Equal(objmark, obj.ObjectMarking) 46 | assert.Equal(specVer, obj.SpecVersion) 47 | assert.True(obj.Defanged) 48 | 49 | assert.Equal(val, obj.Value) 50 | }) 51 | 52 | t.Run("id-generation", func(t *testing.T) { 53 | tests := []struct { 54 | name string 55 | id string 56 | }{ 57 | {val, "url--19cabd28-b056-5306-bf6c-9997c8ff59a7"}, 58 | } 59 | for _, test := range tests { 60 | obj, err := NewURL(test.name) 61 | assert.NoError(err) 62 | assert.Equal(Identifier(test.id), obj.ID) 63 | } 64 | }) 65 | 66 | t.Run("parse_json", func(t *testing.T) { 67 | data := []byte(`{ 68 | "type": "url", 69 | "spec_version": "2.1", 70 | "id": "url--c1477287-23ac-5971-a010-5c287877fa60", 71 | "value": "https://example.com/research/index.html" 72 | }`) 73 | var obj *URL 74 | err := json.Unmarshal(data, &obj) 75 | assert.NoError(err) 76 | assert.Equal(Identifier("url--c1477287-23ac-5971-a010-5c287877fa60"), obj.ID) 77 | assert.Equal("2.1", obj.SpecVersion) 78 | assert.Equal(TypeURL, obj.Type) 79 | assert.Equal(val, obj.Value) 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /mac_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestMACAddress(t *testing.T) { 14 | assert := assert.New(t) 15 | 16 | val := "d2:fb:49:24:37:18" 17 | 18 | t.Run("missing_property", func(t *testing.T) { 19 | obj, err := NewMACAddress("") 20 | assert.Nil(obj) 21 | assert.Equal(ErrInvalidParameter, err) 22 | }) 23 | 24 | t.Run("with_property", func(t *testing.T) { 25 | obj, err := NewMACAddress(val, nil) 26 | assert.NotNil(obj) 27 | assert.NoError(err) 28 | }) 29 | 30 | t.Run("payload_with_options", func(t *testing.T) { 31 | marking := make([]*GranularMarking, 0) 32 | objmark := []Identifier{Identifier("id")} 33 | specVer := "2.0" 34 | 35 | opts := []STIXOption{ 36 | OptionGranularMarking(marking), 37 | OptionObjectMarking(objmark), 38 | OptionSpecVersion(specVer), 39 | OptionDefanged(true), 40 | } 41 | obj, err := NewMACAddress(val, opts...) 42 | assert.NotNil(obj) 43 | assert.NoError(err) 44 | assert.Equal(marking, obj.GranularMarking) 45 | assert.Equal(objmark, obj.ObjectMarking) 46 | assert.Equal(specVer, obj.SpecVersion) 47 | assert.True(obj.Defanged) 48 | 49 | assert.Equal(val, obj.Value) 50 | }) 51 | 52 | t.Run("id-generation", func(t *testing.T) { 53 | tests := []struct { 54 | name string 55 | id string 56 | }{ 57 | {val, "mac-addr--08900593-0265-52fc-93c0-5b4a942f5887"}, 58 | } 59 | for _, test := range tests { 60 | obj, err := NewMACAddress(test.name) 61 | assert.NoError(err) 62 | assert.Equal(Identifier(test.id), obj.ID) 63 | } 64 | }) 65 | 66 | t.Run("parse_json", func(t *testing.T) { 67 | data := []byte(`{ 68 | "type": "mac-addr", 69 | "spec_version": "2.1", 70 | "id": "mac-addr--65cfcf98-8a6e-5a1b-8f61-379ac4f92d00", 71 | "value": "d2:fb:49:24:37:18" 72 | }`) 73 | var obj *MACAddress 74 | err := json.Unmarshal(data, &obj) 75 | assert.NoError(err) 76 | assert.Equal(Identifier("mac-addr--65cfcf98-8a6e-5a1b-8f61-379ac4f92d00"), obj.ID) 77 | assert.Equal("2.1", obj.SpecVersion) 78 | assert.Equal(TypeMACAddress, obj.Type) 79 | assert.Equal("d2:fb:49:24:37:18", obj.Value) 80 | }) 81 | } 82 | -------------------------------------------------------------------------------- /internal/jsoncanonicalizer/es6numfmt.go: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright 2006-2019 WebPKI.org (http://webpki.org). 3 | // 4 | // Licensed under the Apache License, Version 2.0 (the "License"); 5 | // you may not use this file except in compliance with the License. 6 | // You may obtain a copy of the License at 7 | // 8 | // https://www.apache.org/licenses/LICENSE-2.0 9 | // 10 | // Unless required by applicable law or agreed to in writing, software 11 | // distributed under the License is distributed on an "AS IS" BASIS, 12 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | // See the License for the specific language governing permissions and 14 | // limitations under the License. 15 | // 16 | 17 | // This package converts numbers in IEEE-754 double precision into the 18 | // format specified for JSON in EcmaScript Version 6 and forward. 19 | // The core application for this is canonicalization: 20 | // https://tools.ietf.org/html/draft-rundgren-json-canonicalization-scheme-02 21 | 22 | package jsoncanonicalizer 23 | 24 | import ( 25 | "errors" 26 | "math" 27 | "strconv" 28 | "strings" 29 | ) 30 | 31 | const invalidPattern uint64 = 0x7ff0000000000000 32 | 33 | func NumberToJSON(ieeeF64 float64) (res string, err error) { 34 | ieeeU64 := math.Float64bits(ieeeF64) 35 | 36 | // Special case: NaN and Infinity are invalid in JSON 37 | if (ieeeU64 & invalidPattern) == invalidPattern { 38 | return "null", errors.New("Invalid JSON number: " + strconv.FormatUint(ieeeU64, 16)) 39 | } 40 | 41 | // Special case: eliminate "-0" as mandated by the ES6-JSON/JCS specifications 42 | if ieeeF64 == 0 { // Right, this line takes both -0 and 0 43 | return "0", nil 44 | } 45 | 46 | // Deal with the sign separately 47 | var sign string = "" 48 | if ieeeF64 < 0 { 49 | ieeeF64 = -ieeeF64 50 | sign = "-" 51 | } 52 | 53 | // ES6 has a unique "g" format 54 | var format byte = 'e' 55 | if ieeeF64 < 1e+21 && ieeeF64 >= 1e-6 { 56 | format = 'f' 57 | } 58 | 59 | // The following should do the trick: 60 | es6Formatted := strconv.FormatFloat(ieeeF64, format, -1, 64) 61 | 62 | // Minor cleanup 63 | exponent := strings.IndexByte(es6Formatted, 'e') 64 | if exponent > 0 { 65 | // Go outputs "1e+09" which must be rewritten as "1e+9" 66 | if es6Formatted[exponent+2] == '0' { 67 | es6Formatted = es6Formatted[:exponent+2] + es6Formatted[exponent+3:] 68 | } 69 | } 70 | return sign + es6Formatted, nil 71 | } 72 | -------------------------------------------------------------------------------- /software.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "fmt" 8 | "strings" 9 | ) 10 | 11 | // Software object represents high-level properties associated with software, 12 | // including software products. 13 | type Software struct { 14 | STIXCyberObservableObject 15 | // Name specifies the name of the software. 16 | Name string `json:"name"` 17 | // CPE specifies the Common Platform Enumeration (CPE) entry for the 18 | // software, if available. The value for this property MUST be a CPE v2.3 19 | // entry from the official NVD CPE Dictionary. 20 | CPE string `json:"cpe,omitempty"` 21 | // SWID specifies the Software Identification (SWID) Tags entry for the 22 | // software, if available. The tag attribute, tagId, a globally unique 23 | // identifier, SHOULD be used as a proxy identifier of the tagged product. 24 | SWID string `json:"swid,omitempty"` 25 | // Languages specifies the languages supported by the software. The value 26 | // of each list member MUST be an ISO 639-2 language code. 27 | Languages []string `json:"languages,omitempty"` 28 | // Vendor specifies the name of the vendor of the software. 29 | Vendor string `json:"vendor,omitempty"` 30 | // Version specifies the version of the software. 31 | Version string `json:"version,omitempty"` 32 | } 33 | 34 | func (o *Software) MarshalJSON() ([]byte, error) { 35 | return marshalToJSONHelper(o) 36 | } 37 | 38 | // NewSoftware creates a new Software object. A Software object MUST contain at least one 39 | // of hashes or name. 40 | func NewSoftware(name string, opts ...STIXOption) (*Software, error) { 41 | if name == "" { 42 | return nil, ErrInvalidParameter 43 | } 44 | base := newSTIXCyberObservableObject(TypeSoftware) 45 | obj := &Software{ 46 | STIXCyberObservableObject: base, 47 | Name: name, 48 | } 49 | 50 | err := applyOptions(obj, opts) 51 | idContri := make([]string, 0, 4) 52 | idContri = append(idContri, fmt.Sprintf(`"%s"`, obj.Name)) 53 | if obj.CPE != "" { 54 | idContri = append(idContri, fmt.Sprintf(`"%s"`, obj.CPE)) 55 | } 56 | if obj.Vendor != "" { 57 | idContri = append(idContri, fmt.Sprintf(`"%s"`, obj.Vendor)) 58 | } 59 | if obj.Version != "" { 60 | idContri = append(idContri, fmt.Sprintf(`"%s"`, obj.Version)) 61 | } 62 | obj.ID = NewObservableIdentifier(fmt.Sprintf("[%s]", strings.Join(idContri, ",")), TypeSoftware) 63 | return obj, err 64 | } 65 | -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/a859c4443f7b6de6728bb1c57f70b10472bc1cb5: -------------------------------------------------------------------------------- 1 | {"":"","":"","objects":[{"":"","":"","":"","":"","":"","":"","":"","":[""]},{"type":"attack-pattern","":"","":"","":"","":"","":"","":"","":[{"":"","":""}]},{"":"","":"","_":"","":"","":"","":"-10-31T15:52:13.127Z", 2 | "modified": "2014-10-31T15:52:13.127Z", 3 | "description": "", 4 | "name": "Malicious E-mail", 5 | "indicator_types": [ 6 | "malicious-activity" 7 | ], 8 | "pattern": "[email-message:subject MATCHES '^[IMPORTANT] Please Review Before']", 9 | "valid_from": "2014-10-31T15:52:13.127931Z" 10 | }, 11 | { 12 | "type": "indicator", 13 | "spec_version": "2.1", 14 | "pattern_type": "stix", 15 | "id": "indicator--2e17f6fe-3a4d-438a-911a-e509ba1b9933", 16 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 17 | "created": "2014-10-31T15:52:13.127Z", 18 | "modified": "2014-10-31T15:52:13.127Z", 19 | "name": "Malicious Email Attachment", 20 | "description": "", 21 | "indicator_types": [ 22 | "malicious-activity" 23 | ], 24 | "valid_from": "2014-10-31T15:52:13.127931Z", 25 | "pattern": "[email-message:body_multipart[*].body_raw_ref.name MATCHES '^Final Report.+\\\\.exe$']" 26 | }, 27 | { 28 | "type": "relationship", 29 | "spec_version": "2.1", 30 | "id": "relationship--c3fa00e6-1d31-4137-98f5-32a1ec0d0e92", 31 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 32 | "created": "2014-10-31T15:52:13.127Z", 33 | "modified": "2014-10-31T15:52:13.127Z", 34 | "relationship_type": "indicates", 35 | "source_ref": "indicator--8cf9236f-1b96-493d-98be-0c1c1e8b62d7", 36 | "target_ref": "attack-pattern--d7b066aa-4091-4276-a142-29d5d81c3484" 37 | }, 38 | { 39 | "type": "relationship", 40 | "spec_version": "2.1", 41 | "id": "relationship--8e231463-6b3e-4be6-9c44-56999d8c1d80", 42 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 43 | "created": "2014-10-31T15:52:13.127Z", 44 | "modified": "2014-10-31T15:52:13.127Z", 45 | "relationship_type": "indicates", 46 | "source_ref": "indicator--2e17f6fe-3a4d-438a-911a-e509ba1b9933", 47 | "target_ref": "attack-pattern--d7b066aa-4091-4276-a142-29d5d81c3484" 48 | } 49 | ] 50 | } -------------------------------------------------------------------------------- /as_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestAS(t *testing.T) { 14 | assert := assert.New(t) 15 | 16 | num := int64(42) 17 | name := "System 12" 18 | rir := "RIPE" 19 | 20 | t.Run("missing_property", func(t *testing.T) { 21 | obj, err := NewAutonomousSystem(0) 22 | assert.Nil(obj) 23 | assert.Equal(ErrInvalidParameter, err) 24 | }) 25 | 26 | t.Run("with_property", func(t *testing.T) { 27 | obj, err := NewAutonomousSystem(num, nil) 28 | assert.NotNil(obj) 29 | assert.NoError(err) 30 | }) 31 | 32 | t.Run("payload_with_options", func(t *testing.T) { 33 | marking := make([]*GranularMarking, 0) 34 | objmark := []Identifier{Identifier("id")} 35 | specVer := "2.0" 36 | 37 | opts := []STIXOption{ 38 | OptionGranularMarking(marking), 39 | OptionObjectMarking(objmark), 40 | OptionSpecVersion(specVer), 41 | OptionDefanged(true), 42 | // 43 | OptionName(name), 44 | OptionRIR(rir), 45 | } 46 | obj, err := NewAutonomousSystem(num, opts...) 47 | assert.NotNil(obj) 48 | assert.NoError(err) 49 | assert.Equal(marking, obj.GranularMarking) 50 | assert.Equal(objmark, obj.ObjectMarking) 51 | assert.Equal(specVer, obj.SpecVersion) 52 | assert.True(obj.Defanged) 53 | 54 | assert.Equal(num, obj.Number) 55 | assert.Equal(name, obj.Name) 56 | assert.Equal(rir, obj.RIR) 57 | }) 58 | 59 | t.Run("id-generation", func(t *testing.T) { 60 | tests := []struct { 61 | number int64 62 | id string 63 | }{ 64 | {int64(100), "autonomous-system--0a68995b-d4b2-5f3e-810d-1aeeeb0d4b88"}, 65 | } 66 | for _, test := range tests { 67 | obj, err := NewAutonomousSystem(test.number) 68 | assert.NoError(err) 69 | assert.Equal(Identifier(test.id), obj.ID) 70 | } 71 | }) 72 | 73 | t.Run("parse_json", func(t *testing.T) { 74 | data := []byte(`{ 75 | "type": "autonomous-system", 76 | "spec_version": "2.1", 77 | "id": "autonomous-system--f720c34b-98ae-597f-ade5-27dc241e8c74", 78 | "number": 15139, 79 | "name": "Slime Industries", 80 | "rir": "ARIN" 81 | }`) 82 | var obj *AutonomousSystem 83 | err := json.Unmarshal(data, &obj) 84 | assert.NoError(err) 85 | assert.Equal(Identifier("autonomous-system--f720c34b-98ae-597f-ade5-27dc241e8c74"), obj.ID) 86 | assert.Equal("2.1", obj.SpecVersion) 87 | assert.Equal(TypeAutonomousSystem, obj.Type) 88 | assert.Equal(int64(15139), obj.Number) 89 | assert.Equal("Slime Industries", obj.Name) 90 | assert.Equal("ARIN", obj.RIR) 91 | }) 92 | } 93 | -------------------------------------------------------------------------------- /directory_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestDirectory(t *testing.T) { 15 | assert := assert.New(t) 16 | 17 | pth := "/root" 18 | pthEnc := "ascii" 19 | ts := &Timestamp{time.Now()} 20 | contains := []Identifier{Identifier("some")} 21 | 22 | t.Run("missing_property", func(t *testing.T) { 23 | obj, err := NewDirectory("") 24 | assert.Nil(obj) 25 | assert.Equal(ErrInvalidParameter, err) 26 | }) 27 | 28 | t.Run("with_property", func(t *testing.T) { 29 | obj, err := NewDirectory(pth, nil) 30 | assert.NotNil(obj) 31 | assert.NoError(err) 32 | }) 33 | 34 | t.Run("payload_with_options", func(t *testing.T) { 35 | marking := make([]*GranularMarking, 0) 36 | objmark := []Identifier{Identifier("id")} 37 | specVer := "2.0" 38 | 39 | opts := []STIXOption{ 40 | OptionGranularMarking(marking), 41 | OptionObjectMarking(objmark), 42 | OptionSpecVersion(specVer), 43 | OptionDefanged(true), 44 | // 45 | OptionPathEncoding(pthEnc), 46 | OptionCtime(ts), 47 | OptionMtime(ts), 48 | OptionAtime(ts), 49 | OptionContains(contains), 50 | } 51 | obj, err := NewDirectory(pth, opts...) 52 | assert.NotNil(obj) 53 | assert.NoError(err) 54 | assert.Equal(marking, obj.GranularMarking) 55 | assert.Equal(objmark, obj.ObjectMarking) 56 | assert.Equal(specVer, obj.SpecVersion) 57 | assert.True(obj.Defanged) 58 | 59 | assert.Equal(pth, obj.Path) 60 | assert.Equal(pthEnc, obj.PathEnc) 61 | assert.Equal(ts, obj.Ctime) 62 | assert.Equal(ts, obj.Mtime) 63 | assert.Equal(ts, obj.Atime) 64 | assert.Equal(contains, obj.Contains) 65 | }) 66 | 67 | t.Run("id-generation", func(t *testing.T) { 68 | tests := []struct { 69 | path string 70 | id string 71 | }{ 72 | {`C:\\Windows\\System32`, "directory--4ba604fb-ee79-5983-bc76-49018d75c428"}, 73 | } 74 | for _, test := range tests { 75 | obj, err := NewDirectory(test.path) 76 | assert.NoError(err) 77 | assert.Equal(Identifier(test.id), obj.ID) 78 | } 79 | }) 80 | 81 | t.Run("parse_json", func(t *testing.T) { 82 | data := []byte(`{ 83 | "type": "directory", 84 | "spec_version": "2.1", 85 | "id": "directory--93c0a9b0-520d-545d-9094-1a08ddf46b05", 86 | "path": "C:\\Windows\\System32" 87 | }`) 88 | var obj *Directory 89 | err := json.Unmarshal(data, &obj) 90 | assert.NoError(err) 91 | assert.Equal(Identifier("directory--93c0a9b0-520d-545d-9094-1a08ddf46b05"), obj.ID) 92 | assert.Equal("2.1", obj.SpecVersion) 93 | assert.Equal(TypeDirectory, obj.Type) 94 | assert.Equal("C:\\Windows\\System32", obj.Path) 95 | }) 96 | } 97 | -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/564cbed382a71becf1263f3be737de1cce1e00fa: -------------------------------------------------------------------------------- 1 | {"":"","":"","objects":[{"":"","":"","":"","":"","":"","":"","":"","":[""]},{"":"","":"","":"","":"","":"","":"","":"","":[{"":"","":""}]},{"":"","":"", "pattern_type": "stix", 2 | "id": "indicator--8cf9236f-1b96-493d-98be-0c1c1e8b62d7", 3 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 4 | "created": "2014-10-31T15:52:13.127Z", 5 | "modified": "2014-10-31T15:52:13.127Z", 6 | "description": "", 7 | "name": "Malicious E-mail", 8 | "indicator_types": [ 9 | "malicious-activity" 10 | ], 11 | "pattern": "[email-message:subject MATCHES '^[IMPORTANT] Please Review Before']", 12 | "valid_from": "2014-10-31T15:52:13.127931Z" 13 | }, 14 | { 15 | "type": "indicator", 16 | "spec_version": "2.1", 17 | "pattern_type": "stix", 18 | "id": "indicator--2e17f6fe-3a4d-438a-911a-e509ba1b9933", 19 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 20 | "created": "2014-10-31T15:52:13.127Z", 21 | "modified": "2014-10-31T15:52:13.127Z", 22 | "name": "Malicious Email Attachment", 23 | "description": "", 24 | "indicator_types": [ 25 | "malicious-activity" 26 | ], 27 | "valid_from": "2014-10-31T15:52:13.127931Z", 28 | "pattern": "[email-message:body_multipart[*].body_raw_ref.name MATCHES '^Final Report.+\\\\.exe$']" 29 | }, 30 | { 31 | "type": "relationship", 32 | "spec_version": "2.1", 33 | "id": "relationship--c3fa00e6-1d31-4137-98f5-32a1ec0d0e92", 34 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 35 | "created": "2014-10-31T15:52:13.127Z", 36 | "modified": "2014-10-31T15:52:13.127Z", 37 | "relationship_type": "indicates", 38 | "source_ref": "indicator--8cf9236f-1b96-493d-98be-0c1c1e8b62d7", 39 | "target_ref": "attack-pattern--d7b066aa-4091-4276-a142-29d5d81c3484" 40 | }, 41 | { 42 | "typ": "relationship", 43 | "spec_version": "2.1", 44 | "id": "relationship--8e231463-6b3e-4be6-9c44-56999d8c1d80", 45 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 46 | "created": "2014-10-31T15:52:13.127Z", 47 | "modified": "2014-10-31T15:52:13.127Z", 48 | "relationship_type": "indicates", 49 | "source_ref": "indicator--2e17f6fe-3a4d-438a-911a-e509ba1b9933", 50 | "target_ref": "attack-pattern--d7b066aa-4091-4276-a142-29d5d81c3484" 51 | } 52 | ] 53 | } -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/4548ef313e98eaab42aad32586498d5a8b6df7b0: -------------------------------------------------------------------------------- 1 | {"":"","objects":[{"":"","":"","":[""],"":[""],"":"","":"","":"","":""},{"":"","":"","":"","":"","":"","":"","":"","":""},{"":"","":"","":true,"":[""],"":"","":"","":[""],"":"","":""},{"":"","":"8a2-9c3e-b0aaa60266ef", 2 | "modified": "2016-05-09T08:17:27.000Z", 3 | "relationship_type": "consists-of", 4 | "source_ref": "infrastructure--38c47d93-d984-4fd9-b87b-d69d0841628d", 5 | "target_ref": "ipv4-addr--1e4ffad7-0b88-41d4-ba4d-77d27490b956", 6 | "type": "relationship", 7 | "spec_version": "2.1" 8 | }, 9 | { 10 | "id": "ipv4-addr--1e4ffad7-0b88-41d4-ba4d-77d27490b956", 11 | "type": "ipv4-addr", 12 | "value": "198.51.100.3" 13 | }, 14 | { 15 | "created": "2016-11-22T09:22:30.000Z", 16 | "id": "infrastructure--d09c50cf-5bab-465e-9e2d-543912148b73", 17 | "infrastructure_types": [ 18 | "hosting-target-lists" 19 | ], 20 | "labels": [ 21 | "target-list-hosting" 22 | ], 23 | "modified": "2016-11-22T09:22:30.000Z", 24 | "name": "Example target-list-hosting", 25 | "typ": "infrastructure", 26 | "spec_version": "2.1" 27 | }, 28 | { 29 | "created": "2016-11-23T08:17:27.000Z", 30 | "id": "relationship--37ac0c8d-f86d-4e56-aee9-914343959a4c", 31 | "modified": "2016-11-23T08:17:27.000Z", 32 | "relationship_type": "uses", 33 | "source_ref": "malware--3a41e552-999b-4ad3-bedc-332b6d9ff80c", 34 | "target_ref": "infrastructure--d09c50cf-5bab-465e-9e2d-543912148b73", 35 | "type": "relationship", 36 | "spec_version": "2.1" 37 | }, 38 | { 39 | "created": "2016-11-12T14:31:09.000Z", 40 | "id": "malware--3a41e552-999b-4ad3-bedc-332b6d9ff80c", 41 | "is_family": true, 42 | "modified": "2016-11-12T14:31:09.000Z", 43 | "name": "IMDDOS", 44 | "type": "malware", 45 | "malware_types":[ 46 | "bot" 47 | ], 48 | 49 | "spec_version": "2.1" 50 | }, 51 | { 52 | "created": "2016-11-23T10:42:39.000Z", 53 | "id": "relationship--81f12913-1372-4c96-85ec-E9034ac98aba", 54 | "modified": "2016-11-23T10:42:39.000Z", 55 | "relationship_type": "consists-of", 56 | "source_ref": "infrastructure--d09c50cf-5bab-465e-9e2d-543912148b73", 57 | "target_ref": "domain-name--ecb120bf-2694-4902-a737-62b74539a41b", 58 | "type": "relationship", 59 | "spec_version": "2.1" 60 | }, 61 | { 62 | "id": "domain-name--ecb120bf-2694-4902-a737-62b74539a41b", 63 | "type": "domain-name", 64 | "value": "example.com" 65 | } 66 | ], 67 | "type": "bundle" 68 | } -------------------------------------------------------------------------------- /incident_test.go: -------------------------------------------------------------------------------- 1 | package stix2 2 | 3 | import ( 4 | "encoding/json" 5 | "testing" 6 | "time" 7 | 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestIncident(t *testing.T) { 12 | assert := assert.New(t) 13 | 14 | name := "New Incident" 15 | 16 | t.Run("missing_property", func(t *testing.T) { 17 | obj, err := NewIncident("") 18 | assert.Nil(obj) 19 | assert.Equal(ErrPropertyMissing, err) 20 | }) 21 | 22 | t.Run("no_optional", func(t *testing.T) { 23 | obj, err := NewIncident(name, nil) 24 | assert.NotNil(obj) 25 | assert.NoError(err) 26 | }) 27 | 28 | t.Run("with_options", func(t *testing.T) { 29 | conf := 50 30 | desc := "My description" 31 | ts := &Timestamp{time.Now()} 32 | createdBy := NewIdentifier(TypeIdentity) 33 | ref := &ExternalReference{} 34 | marking := make([]*GranularMarking, 0) 35 | labels := []string{"tag1", "tag2"} 36 | lang := "en" 37 | objmark := []Identifier{Identifier("id")} 38 | specVer := "2.0" 39 | 40 | opts := []STIXOption{ 41 | OptionConfidence(conf), 42 | OptionCreated(ts), 43 | OptionModified(ts), 44 | OptionCreatedBy(createdBy), 45 | OptionExternalReferences([]*ExternalReference{ref}), 46 | OptionGranularMarking(marking), 47 | OptionLabels(labels), 48 | OptionLang(lang), 49 | OptionObjectMarking(objmark), 50 | OptionRevoked(true), 51 | OptionSpecVersion(specVer), 52 | // 53 | OptionDescription(desc), 54 | } 55 | obj, err := NewIncident(name, opts...) 56 | assert.NotNil(obj) 57 | assert.NoError(err) 58 | assert.Equal(conf, obj.Confidence) 59 | assert.Equal(ts, obj.Created) 60 | assert.Equal(ts, obj.Modified) 61 | assert.Equal(createdBy, obj.CreatedBy) 62 | assert.Contains(obj.ExternalReferences, ref) 63 | assert.Equal(marking, obj.GranularMarking) 64 | assert.Equal(labels, obj.Labels) 65 | assert.Equal(lang, obj.Lang) 66 | assert.Equal(objmark, obj.ObjectMarking) 67 | assert.True(obj.Revoked) 68 | assert.Equal(specVer, obj.SpecVersion) 69 | 70 | assert.Equal(name, obj.Name) 71 | assert.Equal(desc, obj.Description) 72 | }) 73 | 74 | t.Run("parse_json", func(t *testing.T) { 75 | data := []byte(`{ 76 | "type": "incident", 77 | "spec_version": "2.1", 78 | "id": "incident--023d105b-752e-4e3c-941c-7d3f3cb15e9e", 79 | "created_by_ref": "incident--f431f809-377b-45e0-aa1c-6a4751cae5ff", 80 | "created": "2016-04-06T20:03:00.000Z", 81 | "modified": "2016-04-06T20:03:00.000Z", 82 | "name": "2016 DNC Hack by APT28" 83 | }`) 84 | ts, err := time.Parse(time.RFC3339Nano, "2016-04-06T20:03:00.000Z") 85 | assert.NoError(err) 86 | var obj *Identity 87 | err = json.Unmarshal(data, &obj) 88 | assert.NoError(err) 89 | assert.Equal(Identifier("incident--023d105b-752e-4e3c-941c-7d3f3cb15e9e"), obj.ID) 90 | assert.Equal("2.1", obj.SpecVersion) 91 | assert.Equal(TypeIncident, obj.Type) 92 | assert.Equal("2016 DNC Hack by APT28", obj.Name) 93 | assert.Equal(ts, obj.Created.Time) 94 | assert.Equal(ts, obj.Modified.Time) 95 | }) 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build](https://github.com/TcM1911/stix2/actions/workflows/build.yml/badge.svg)](https://github.com/TcM1911/stix2/actions/workflows/build.yml) 2 | [![Go Report Card](https://goreportcard.com/badge/github.com/TcM1911/stix2)](https://goreportcard.com/report/github.com/TcM1911/stix2) 3 | ![GitHub tag (latest SemVer)](https://img.shields.io/github/v/tag/TcM1911/stix2?label=Latest) 4 | ![GitHub go.mod Go version](https://img.shields.io/github/go-mod/go-version/TcM1911/stix2) 5 | [![Go Reference](https://pkg.go.dev/badge/github.com/TcM1911/stix2.svg)](https://pkg.go.dev/github.com/TcM1911/stix2) 6 | [![codecov](https://codecov.io/gh/TcM1911/stix2/branch/master/graph/badge.svg)](https://codecov.io/gh/TcM1911/stix2) 7 | 8 | # stix2 9 | A pure Go library for working with Structured Threat Information Expression 10 | (STIX™) version 2.x data. 11 | 12 | ## Parsing STIX JSON data 13 | 14 | The library provides a helper function to parse STIX JSON. It can handle 15 | both the bundle object and JSON objects as a JSON array. The function returns 16 | a `Collection` object that holds all the extracted STIX objects. 17 | 18 | ```go 19 | collection, err := stix2.FromJSON(jsonData) 20 | ``` 21 | 22 | ## Creating a STIX Bundle 23 | 24 | Creating a STIX Bundle, is as easy as creating a set of STIX objects and add 25 | them to the Collection. The Bundle can be created by calling the `ToBundle` 26 | method on the Collection object. The Bundle can be serialized to `JSON` 27 | using the `JSON` encoder in the standard library. 28 | 29 | ```go 30 | c := stix2.New() 31 | ip, err := stix2.NewIPv4Address("10.0.0.1") 32 | c.Add(ip) 33 | ip, err = stix2.NewIPv4Address("10.0.0.2") 34 | c.Add(ip) 35 | b, err := c.ToBundle() 36 | data, err := json.Marshal(b) 37 | ``` 38 | 39 | 40 | ## Example of a malware using an infrastructure 41 | Taken from: https://docs.oasis-open.org/cti/stix/v2.1/csprd02/stix-v2.1-csprd02.html#_Toc26789941 42 | 43 | ```go 44 | collection := stix2.New() 45 | domain, err := stix2.NewDomainName("example.com") 46 | collection.Add(domain) 47 | 48 | mal, err := stix2.NewMalware( 49 | false, 50 | stix2.OptionName("IMDDOS"), 51 | stix2.OptionTypes([]string{stix2.MalwareTypeBot}), 52 | ) 53 | collection.Add(mal) 54 | 55 | infra, err := stix2.NewInfrastructure( 56 | "Example Target List Host", 57 | []string{stix2.InfrastructureTypeHostingTargetLists}, 58 | ) 59 | collection.Add(infra) 60 | 61 | ref, err := mal.AddUses(infra.ID) 62 | collection.Add(ref) 63 | 64 | ref, err = infra.AddConsistsOf(domain.ID) 65 | collection.Add(ref) 66 | 67 | b, err := collection.ToBundle() 68 | data, err := json.MarshalIndent(b, "", "\t") 69 | ``` 70 | 71 | ## Extensions and Customization 72 | 73 | With the release of version 2.1 of the specification custom properties 74 | has been deprecated. Instead, `property-extension` functionality should 75 | be used. This library supports parsing objects with old custom properties 76 | for backwards compatibility. The fields can be accessed via the 77 | `GetExtendedTopLevelProperties` method. 78 | 79 | See the examples in the documentation on how to work with extensions. -------------------------------------------------------------------------------- /course-of-action.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | // CourseOfAction (CoA) is a recommendation from a producer of intelligence to 7 | // a consumer on the actions that they might take in response to that 8 | // intelligence. The CoA may be preventative to deter exploitation or 9 | // corrective to counter its potential impact. The CoA may describe automatable 10 | // actions (applying patches, configuring firewalls, etc.), manual processes, 11 | // or a combination of the two. For example, a CoA that describes how to 12 | // remediate a vulnerability could describe how to apply the patch that removes 13 | // that vulnerability. 14 | type CourseOfAction struct { 15 | STIXDomainObject 16 | // Name used to identify the Course of Action. 17 | Name string `json:"name"` 18 | // Description provides more details and context about the Course of 19 | // Action, potentially including its purpose and its key characteristics. 20 | // In some cases, this property may contain the actual course of action in 21 | // prose text. 22 | Description string `json:"description,omitempty"` 23 | } 24 | 25 | func (o *CourseOfAction) MarshalJSON() ([]byte, error) { 26 | return marshalToJSONHelper(o) 27 | } 28 | 29 | // AddInvestigates creates an investigate relationship between the course of 30 | // action and an indicator. 31 | func (c *CourseOfAction) AddInvestigates(id Identifier, opts ...STIXOption) (*Relationship, error) { 32 | if !IsValidIdentifier(id) || !id.ForType(TypeIndicator) { 33 | return nil, ErrInvalidParameter 34 | } 35 | return NewRelationship(RelationshipTypeInvestigates, c.ID, id, opts...) 36 | } 37 | 38 | // AddMitigates creates a relationship to an attack pattern, indicator, 39 | // malware, tool, or vulnerability that are mitigated by the object. 40 | func (c *CourseOfAction) AddMitigates(id Identifier, opts ...STIXOption) (*Relationship, error) { 41 | if !IsValidIdentifier(id) || (!id.ForType(TypeAttackPattern) && !id.ForType(TypeIndicator) && !id.ForType(TypeMalware) && !id.ForType(TypeTool) && !id.ForType(TypeVulnerability)) { 42 | return nil, ErrInvalidParameter 43 | } 44 | return NewRelationship(RelationshipTypeMitigates, c.ID, id, opts...) 45 | } 46 | 47 | // AddRemediates creates a relationship to a malware or a vulnerability that 48 | // are remediated by the object. 49 | func (c *CourseOfAction) AddRemediates(id Identifier, opts ...STIXOption) (*Relationship, error) { 50 | if !IsValidIdentifier(id) || (!id.ForType(TypeMalware) && !id.ForType(TypeVulnerability)) { 51 | return nil, ErrInvalidParameter 52 | } 53 | return NewRelationship(RelationshipTypeRemediates, c.ID, id, opts...) 54 | } 55 | 56 | // NewCourseOfAction creates a new CourseOfAction object. 57 | func NewCourseOfAction(name string, opts ...STIXOption) (*CourseOfAction, error) { 58 | if name == "" { 59 | return nil, ErrPropertyMissing 60 | } 61 | base := newSTIXDomainObject(TypeCourseOfAction) 62 | obj := &CourseOfAction{STIXDomainObject: base, Name: name} 63 | 64 | err := applyOptions(obj, opts) 65 | return obj, err 66 | } 67 | -------------------------------------------------------------------------------- /grouping.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | // Grouping object explicitly asserts that the referenced STIX Objects have a 7 | // shared context, unlike a STIX Bundle (which explicitly conveys no context). 8 | // A Grouping object should not be confused with an intelligence product, which 9 | // should be conveyed via a STIX Report. 10 | // 11 | // A STIX Grouping object might represent a set of data that, in time, given 12 | // sufficient analysis, would mature to convey an incident or threat report as 13 | // a STIX Report object. For example, a Grouping could be used to characterize 14 | // an ongoing investigation into a security event or incident. A Grouping 15 | // object could also be used to assert that the referenced STIX Objects are 16 | // related to an ongoing analysis process, such as when a threat analyst is 17 | // collaborating with others in their trust community to examine a series of 18 | // Campaigns and Indicators. The Grouping SDO contains a list of references to 19 | // SDOs, SCOs, and SROs, along with an explicit statement of the context shared 20 | // by the content, a textual description, and the name of the grouping. 21 | type Grouping struct { 22 | STIXDomainObject 23 | // Name is used to identify the Grouping. 24 | Name string `json:"name,omitempty"` 25 | // Description provides more details and context about the Grouping, 26 | // potentially including its purpose and its key characteristics. 27 | Description string `json:"description,omitempty"` 28 | // Context provides a short descriptor of the particular context shared by 29 | // the content referenced by the Grouping. This is an open vocabulary and 30 | // values SHOULD come from the GroupingContext constants. 31 | Context string `json:"context"` 32 | // Objects specifies the STIX Objects that are referred to by this 33 | // Grouping. 34 | Objects []Identifier `json:"object_refs"` 35 | } 36 | 37 | func (o *Grouping) MarshalJSON() ([]byte, error) { 38 | return marshalToJSONHelper(o) 39 | } 40 | 41 | // NewGrouping creates a new Grouping object. 42 | func NewGrouping(context string, objects []Identifier, opts ...STIXOption) (*Grouping, error) { 43 | if context == "" || len(objects) < 1 { 44 | return nil, ErrPropertyMissing 45 | } 46 | base := newSTIXDomainObject(TypeGrouping) 47 | obj := &Grouping{STIXDomainObject: base, Context: context, Objects: objects} 48 | 49 | err := applyOptions(obj, opts) 50 | return obj, err 51 | } 52 | 53 | const ( 54 | // GroupingContextSuspiciousActivity is a et of STIX content related to a 55 | // particular suspicious activity event. 56 | GroupingContextSuspiciousActivity = "suspicious-activity" 57 | // GroupingContextMalwareAnalysis is a set of STIX content related to a 58 | // particular malware instance or family. 59 | GroupingContextMalwareAnalysis = "malware-analysis" 60 | // GroupingContextUnspecified is a set of STIX content contextually related 61 | // but without any precise characterization of the contextual relationship 62 | // between the objects. 63 | GroupingContextUnspecified = "unspecified" 64 | ) 65 | -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/6686f33a530243baa72f42c7a33a4333cc5b7630: -------------------------------------------------------------------------------- 1 | {"":"","objects":[{"":"","":"","":[""],"":[""],"":"","":"","":"","":""},{"":"","":"","":"","":"","":"","":"","":"","":""},{"":"","":"","":true,"":[""],"":"","":"","es":[ 2 | "remote-access-trojan" 3 | ], 4 | "type": "malware", 5 | "spec_version": "2.1" 6 | }, 7 | { 8 | "created": "2016-05-09T08:17:27.000Z", 9 | "id": "relationship--7aebe2f0-28d6-48a2-9c3e-b0aaa60266ef", 10 | "modified": "2016-05-09T08:17:27.000Z", 11 | "relationship_type": "consists-of", 12 | "source_ref": "infrastructure--38c47d93-d984-4fd9-b87b-d69d0841628d", 13 | "target_ref": "ipv4-addr--1e4ffad7-0b88-41d4-ba4d-77d27490b956", 14 | "type": "relationship", 15 | "spec_version": "2.1" 16 | }, 17 | { 18 | "id": "ipv4-addr--1e4ffad7-0b88-41d4-ba4d-77d27490b956", 19 | "type": "ipv4-addr", 20 | "value": "198.51.100.3" 21 | }, 22 | { 23 | "created": "2016-11-22T09:22:30.000Z", 24 | "id": "infrastructure--d09c50cf-5bab-465e-9e2d-543912148b73", 25 | "infrastructure_types": [ 26 | "hosting-target-lists" 27 | ], 28 | "labels": [ 29 | "target-list-hosting" 30 | ], 31 | "modified": "2016-11-22T09:22:30.000Z", 32 | "name": "Example target-list-hosting", 33 | "type": "infrastructure", 34 | "spec_version": "2.1" 35 | }, 36 | { 37 | "created": "2016-11-23T08:17:27.000Z", 38 | "id": "relationship--37ac0c8d-f86d-4e56-aee9-914343959a4c", 39 | "modified": "2016-11-23T08:17:27.000Z", 40 | "relationship_type": "uses", 41 | "source_ref": "malware--3a41e552-999b-4ad3-bedc-332b6d9ff80c", 42 | "target_ref": "infrastructure--d09c50cf-5bab-465e-9e2d-543912148b73", 43 | "typ": "relationship", 44 | "spec_version": "2.1" 45 | }, 46 | { 47 | "created": "2016-11-12T14:31:09.000Z", 48 | "id": "malware--3a41e552-999b-4ad3-bedc-332b6d9ff80c", 49 | "is_family": true, 50 | "modified": "2016-11-12T14:31:09.000Z", 51 | "name": "IMDDOS", 52 | "type": "malware", 53 | "malware_types":[ 54 | "bot" 55 | ], 56 | 57 | "spec_version": "2.1" 58 | }, 59 | { 60 | "created": "2016-11-23T10:42:39.000Z", 61 | "id": "relationship--81f12913-1372-4c96-85ec-E9034ac98aba", 62 | "modified": "2016-11-23T10:42:39.000Z", 63 | "relationship_type": "consists-of", 64 | "source_ref": "infrastructure--d09c50cf-5bab-465e-9e2d-543912148b73", 65 | "target_ref": "domain-name--ecb120bf-2694-4902-a737-62b74539a41b", 66 | "type": "relationship", 67 | "spec_version": "2.1" 68 | }, 69 | { 70 | "id": "domain-name--ecb120bf-2694-4902-a737-62b74539a41b", 71 | "type": "domain-name", 72 | "value": "example.com" 73 | } 74 | ], 75 | "type": "bundle" 76 | } -------------------------------------------------------------------------------- /relationship_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestRelationship(t *testing.T) { 15 | assert := assert.New(t) 16 | 17 | source := NewIdentifier(TypeIPv4Addr) 18 | target := NewIdentifier(TypeIndicator) 19 | typ := RelationshipTypeRelatedTo 20 | 21 | t.Run("missing_property", func(t *testing.T) { 22 | r, err := NewRelationship("", "", "", nil) 23 | assert.Nil(r) 24 | assert.Equal(ErrPropertyMissing, err) 25 | }) 26 | 27 | t.Run("no_optional", func(t *testing.T) { 28 | r, err := NewRelationship(typ, source, target, nil) 29 | assert.NotNil(r) 30 | assert.NoError(err) 31 | }) 32 | 33 | t.Run("with_options", func(t *testing.T) { 34 | conf := 50 35 | desc := "My description" 36 | ts := &Timestamp{time.Now()} 37 | createdBy := NewIdentifier(TypeIdentity) 38 | ref := &ExternalReference{} 39 | marking := make([]*GranularMarking, 0) 40 | labels := []string{"tag1", "tag2"} 41 | lang := "en" 42 | objmark := []Identifier{Identifier("id")} 43 | specVer := "2.0" 44 | 45 | opts := []STIXOption{ 46 | OptionConfidence(conf), 47 | OptionDescription(desc), 48 | OptionCreated(ts), 49 | OptionModified(ts), 50 | OptionCreatedBy(createdBy), 51 | OptionExternalReferences([]*ExternalReference{ref}), 52 | OptionGranularMarking(marking), 53 | OptionLabels(labels), 54 | OptionLang(lang), 55 | OptionObjectMarking(objmark), 56 | OptionRevoked(true), 57 | OptionSpecVersion(specVer), 58 | OptionStartTime(ts), 59 | OptionStopTime(ts), 60 | } 61 | r, err := NewRelationship(typ, source, target, opts...) 62 | assert.NotNil(r) 63 | assert.NoError(err) 64 | assert.Equal(conf, r.Confidence) 65 | assert.Equal(desc, r.Description) 66 | assert.Equal(ts, r.Created) 67 | assert.Equal(ts, r.Modified) 68 | assert.Equal(ts, r.StartTime) 69 | assert.Equal(ts, r.StopTime) 70 | assert.Equal(createdBy, r.CreatedBy) 71 | assert.Contains(r.ExternalReferences, ref) 72 | assert.Equal(marking, r.GranularMarking) 73 | assert.Equal(labels, r.Labels) 74 | assert.Equal(lang, r.Lang) 75 | assert.Equal(objmark, r.ObjectMarking) 76 | assert.True(r.Revoked) 77 | assert.Equal(specVer, r.SpecVersion) 78 | }) 79 | 80 | t.Run("parse-JSON", func(t *testing.T) { 81 | data := []byte( 82 | ` 83 | { 84 | "type": "relationship", 85 | "id": "relationship--6598bf44-1c10-4218-af9f-75b5b71c23a7", 86 | "created": "2015-05-15T09:00:00.000Z", 87 | "modified": "2015-05-15T09:00:00.000Z", 88 | "object_marking_refs": [ 89 | "marking-definition--3444e29e-2aa6-46f7-a01c-1c174820fa67" 90 | ], 91 | "relationship_type": "uses", 92 | "source_ref": "threat-actor--6d179234-61fc-40c4-ae86-3d53308d8e65", 93 | "target_ref": "malware--2485b844-4efe-4343-84c8-eb33312dd56f" 94 | } 95 | `) 96 | var rel Relationship 97 | err := json.Unmarshal(data, &rel) 98 | assert.NoError(err) 99 | assert.Equal(RelationshipTypeUses, rel.RelationshipType) 100 | }) 101 | } 102 | -------------------------------------------------------------------------------- /schema_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | //+build long_test 5 | 6 | package stix2_test 7 | 8 | import ( 9 | "encoding/json" 10 | "fmt" 11 | "io/ioutil" 12 | "os" 13 | "path/filepath" 14 | "strings" 15 | "testing" 16 | 17 | "github.com/TcM1911/stix2" 18 | "github.com/stretchr/testify/assert" 19 | "github.com/xeipuuv/gojsonschema" 20 | ) 21 | 22 | func TestCollectionToBundle(t *testing.T) { 23 | sloader := gojsonschema.NewReferenceLoader("http://raw.githubusercontent.com/oasis-open/cti-stix2-json-schemas/stix2.1/schemas/common/bundle.json") 24 | schema, err := gojsonschema.NewSchema(sloader) 25 | if err != nil { 26 | t.Fatalf("Failed to load schema: %s\n", err) 27 | } 28 | 29 | t.Run("create_from_collection", func(t *testing.T) { 30 | assert := assert.New(t) 31 | c := &stix2.Collection{} 32 | ip, err := stix2.NewIPv4Address("10.0.0.1") 33 | assert.NoError(err) 34 | c.Add(ip) 35 | ip, err = stix2.NewIPv4Address("10.0.0.2") 36 | assert.NoError(err) 37 | c.Add(ip) 38 | 39 | b, err := c.ToBundle() 40 | assert.NoError(err) 41 | assert.NotNil(b) 42 | 43 | data, err := json.Marshal(b) 44 | assert.NoError(err) 45 | assert.NotNil(data) 46 | 47 | docloader := gojsonschema.NewBytesLoader(data) 48 | result, err := schema.Validate(docloader) 49 | assert.NoError(err) 50 | assert.True(result.Valid()) 51 | }) 52 | 53 | t.Run("examples", func(t *testing.T) { 54 | runFolder(t, schema, "examples") 55 | }) 56 | 57 | t.Run("threat-reports", func(t *testing.T) { 58 | runFolder(t, schema, filepath.Join("examples", "threat-reports")) 59 | }) 60 | } 61 | 62 | func runFolder(t *testing.T, schema *gojsonschema.Schema, path string) { 63 | assert := assert.New(t) 64 | pth, err := filepath.Abs(filepath.Join("testresources", path)) 65 | if err != nil { 66 | t.Fatalf("Error when resolving abs path to resource files: %s\n", err) 67 | } 68 | info, err := ioutil.ReadDir(pth) 69 | if err != nil { 70 | t.Fatalf("Error when loading resource files: %s\n", err) 71 | } 72 | for _, f := range info { 73 | if f.IsDir() || !strings.HasSuffix(f.Name(), ".json") { 74 | continue 75 | } 76 | fr, err := os.OpenFile(filepath.Join(pth, f.Name()), os.O_RDONLY, 0600) 77 | if err != nil { 78 | t.Fatalf("Error when opening the file: %s\n", err) 79 | } 80 | inData, err := ioutil.ReadAll(fr) 81 | assert.NoError(err) 82 | fr.Close() 83 | 84 | collection, err := stix2.FromJSON(inData) 85 | assert.NoError(err) 86 | assert.NotNil(collection) 87 | 88 | bundle, err := collection.ToBundle() 89 | assert.NoError(err) 90 | assert.NotNil(bundle) 91 | 92 | outData, err := json.Marshal(bundle) 93 | assert.NoError(err) 94 | assert.NotNil(outData) 95 | 96 | docloader := gojsonschema.NewBytesLoader(outData) 97 | result, err := schema.Validate(docloader) 98 | assert.NoError(err) 99 | assert.True(result.Valid(), f.Name()+" failed to validate") 100 | if !result.Valid() { 101 | for _, desc := range result.Errors() { 102 | fmt.Printf("- %s\n", desc) 103 | } 104 | // Dump the output data 105 | fmt.Println(string(outData)) 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /attack.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | // AttackPattern is a type of TTP that describe ways that adversaries 7 | // attempt to compromise targets. Attack Patterns are used to help categorize 8 | // attacks, generalize specific attacks to the patterns that they follow, and 9 | // provide detailed information about how attacks are performed. An example of 10 | // an attack pattern is "spear phishing": a common type of attack where an 11 | // attacker sends a carefully crafted e-mail message to a party with the intent 12 | // of getting them to click a link or open an attachment to deliver malware. 13 | // Attack Patterns can also be more specific; spear phishing as practiced by a 14 | // particular threat actor (e.g., they might generally say that the target won 15 | // a contest) can also be an Attack Pattern. The Attack Pattern SDO contains 16 | // textual descriptions of the pattern along with references to 17 | // externally-defined taxonomies of attacks such as CAPEC. 18 | type AttackPattern struct { 19 | STIXDomainObject 20 | // Name is used to identify the Attack Pattern. 21 | Name string `json:"name"` 22 | // Description provides more details and context about the Attack Pattern, 23 | // potentially including its purpose and its key characteristics. 24 | Description string `json:"description,omitempty"` 25 | // Aliases are alternative names used to identify this Attack Pattern. 26 | Aliases []string `json:"aliases,omitempty"` 27 | // KillChainPhase is a list of Kill Chain Phases for which this Attack 28 | // Pattern is used. 29 | KillChainPhase []*KillChainPhase `json:"kill_chain_phases,omitempty"` 30 | } 31 | 32 | func (o *AttackPattern) MarshalJSON() ([]byte, error) { 33 | return marshalToJSONHelper(o) 34 | } 35 | 36 | // AddDelivers creates a relationship to a malware delivered by this object. 37 | func (a *AttackPattern) AddDelivers(id Identifier, opts ...STIXOption) (*Relationship, error) { 38 | if !IsValidIdentifier(id) || !id.ForType(TypeMalware) { 39 | return nil, ErrInvalidParameter 40 | } 41 | return NewRelationship(RelationshipTypeDelivers, a.ID, id, opts...) 42 | } 43 | 44 | // AddTargets creates a relationship to either an identity, location, or 45 | // vulnerability that is targeted by this attack pattern. 46 | func (a *AttackPattern) AddTargets(id Identifier, opts ...STIXOption) (*Relationship, error) { 47 | if !IsValidIdentifier(id) || (!id.ForType(TypeLocation) && 48 | !id.ForType(TypeIdentity)) && !id.ForType(TypeVulnerability) { 49 | return nil, ErrInvalidParameter 50 | } 51 | return NewRelationship(RelationshipTypeTargets, a.ID, id, opts...) 52 | } 53 | 54 | // AddUses creates a relationship to either a malware or tool that is used by 55 | // the attack pattern. 56 | func (a *AttackPattern) AddUses(id Identifier, opts ...STIXOption) (*Relationship, error) { 57 | if !IsValidIdentifier(id) || (!id.ForType(TypeMalware) && !id.ForType(TypeTool)) { 58 | return nil, ErrInvalidParameter 59 | } 60 | return NewRelationship(RelationshipTypeTargets, a.ID, id, opts...) 61 | } 62 | 63 | // NewAttackPattern creates a new AttackPattern object. 64 | func NewAttackPattern(name string, opts ...STIXOption) (*AttackPattern, error) { 65 | if name == "" { 66 | return nil, ErrPropertyMissing 67 | } 68 | base := newSTIXDomainObject(TypeAttackPattern) 69 | obj := &AttackPattern{STIXDomainObject: base, Name: name} 70 | 71 | err := applyOptions(obj, opts) 72 | return obj, err 73 | } 74 | -------------------------------------------------------------------------------- /vulnerability_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestVulnerability(t *testing.T) { 15 | assert := assert.New(t) 16 | 17 | ts := &Timestamp{time.Now()} 18 | desc := "Vulnerability content" 19 | name := "CVE-2015-12345" 20 | 21 | t.Run("missing_property", func(t *testing.T) { 22 | obj, err := NewVulnerability("", nil) 23 | assert.Nil(obj) 24 | assert.Equal(ErrPropertyMissing, err) 25 | }) 26 | 27 | t.Run("no_optional", func(t *testing.T) { 28 | obj, err := NewVulnerability(name, nil) 29 | assert.NotNil(obj) 30 | assert.NoError(err) 31 | }) 32 | 33 | t.Run("with_options", func(t *testing.T) { 34 | conf := 50 35 | createdBy := NewIdentifier(TypeIdentity) 36 | ref := &ExternalReference{} 37 | marking := make([]*GranularMarking, 0) 38 | labels := []string{"tag1", "tag2"} 39 | lang := "en" 40 | objmark := []Identifier{Identifier("id")} 41 | specVer := "2.0" 42 | 43 | opts := []STIXOption{ 44 | OptionConfidence(conf), 45 | OptionCreated(ts), 46 | OptionModified(ts), 47 | OptionCreatedBy(createdBy), 48 | OptionExternalReferences([]*ExternalReference{ref}), 49 | OptionGranularMarking(marking), 50 | OptionLabels(labels), 51 | OptionLang(lang), 52 | OptionObjectMarking(objmark), 53 | OptionRevoked(true), 54 | OptionSpecVersion(specVer), 55 | // 56 | OptionDescription(desc), 57 | } 58 | obj, err := NewVulnerability(name, opts...) 59 | assert.NotNil(obj) 60 | assert.NoError(err) 61 | assert.Equal(conf, obj.Confidence) 62 | assert.Equal(ts, obj.Created) 63 | assert.Equal(ts, obj.Modified) 64 | assert.Equal(createdBy, obj.CreatedBy) 65 | assert.Contains(obj.ExternalReferences, ref) 66 | assert.Equal(marking, obj.GranularMarking) 67 | assert.Equal(labels, obj.Labels) 68 | assert.Equal(lang, obj.Lang) 69 | assert.Equal(objmark, obj.ObjectMarking) 70 | assert.True(obj.Revoked) 71 | assert.Equal(specVer, obj.SpecVersion) 72 | 73 | assert.Equal(desc, obj.Description) 74 | assert.Equal(name, obj.Name) 75 | }) 76 | 77 | t.Run("parse_json", func(t *testing.T) { 78 | data := []byte(`{ 79 | "type": "vulnerability", 80 | "spec_version": "2.1", 81 | "id": "vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061", 82 | "created": "2016-05-12T08:17:27.000Z", 83 | "modified": "2016-05-12T08:17:27.000Z", 84 | "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", 85 | "name": "CVE-2016-1234", 86 | "external_references": [ 87 | { 88 | "source_name": "cve", 89 | "external_id": "CVE-2016-1234" 90 | } 91 | ] 92 | }`) 93 | ts, err := time.Parse(time.RFC3339Nano, "2016-05-12T08:17:27.000Z") 94 | assert.NoError(err) 95 | var obj *Vulnerability 96 | err = json.Unmarshal(data, &obj) 97 | assert.NoError(err) 98 | assert.Equal(Identifier("vulnerability--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061"), obj.ID) 99 | assert.Equal("2.1", obj.SpecVersion) 100 | assert.Equal(TypeVulnerability, obj.Type) 101 | assert.Equal(ts, obj.Created.Time) 102 | assert.Equal(ts, obj.Modified.Time) 103 | assert.Equal("CVE-2016-1234", obj.Name) 104 | assert.Len(obj.ExternalReferences, 1) 105 | assert.Equal("cve", obj.ExternalReferences[0].Name) 106 | assert.Equal("CVE-2016-1234", obj.ExternalReferences[0].ExternalID) 107 | }) 108 | } 109 | -------------------------------------------------------------------------------- /dns_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestDomain(t *testing.T) { 14 | assert := assert.New(t) 15 | 16 | name := "example.com" 17 | resolve := Identifier("test") 18 | 19 | t.Run("missing_property", func(t *testing.T) { 20 | obj, err := NewDomainName("") 21 | assert.Nil(obj) 22 | assert.Equal(ErrInvalidParameter, err) 23 | }) 24 | 25 | t.Run("with_property", func(t *testing.T) { 26 | obj, err := NewDomainName(name, nil) 27 | assert.NotNil(obj) 28 | assert.NoError(err) 29 | }) 30 | 31 | t.Run("payload_with_options", func(t *testing.T) { 32 | marking := make([]*GranularMarking, 0) 33 | objmark := []Identifier{Identifier("id")} 34 | specVer := "2.0" 35 | 36 | opts := []STIXOption{ 37 | OptionGranularMarking(marking), 38 | OptionObjectMarking(objmark), 39 | OptionSpecVersion(specVer), 40 | OptionDefanged(true), 41 | OptionResolvesTo([]Identifier{resolve}), 42 | } 43 | obj, err := NewDomainName(name, opts...) 44 | assert.NotNil(obj) 45 | assert.NoError(err) 46 | assert.Equal(marking, obj.GranularMarking) 47 | assert.Equal(objmark, obj.ObjectMarking) 48 | assert.Equal(specVer, obj.SpecVersion) 49 | assert.True(obj.Defanged) 50 | 51 | assert.Equal(name, obj.Value) 52 | assert.Contains(obj.ResolvesTo, resolve) 53 | }) 54 | 55 | t.Run("id-generation", func(t *testing.T) { 56 | tests := []struct { 57 | name string 58 | id string 59 | }{ 60 | {"example.com", "domain-name--220a2699-5ebf-5b57-bf02-424964bb19c0"}, 61 | } 62 | for _, test := range tests { 63 | obj, err := NewDomainName(test.name) 64 | assert.NoError(err) 65 | assert.Equal(Identifier(test.id), obj.ID) 66 | } 67 | }) 68 | 69 | t.Run("parse_json", func(t *testing.T) { 70 | data := []byte(`{ 71 | "type": "domain-name", 72 | "spec_version": "2.1", 73 | "id": "domain-name--3c10e93f-798e-5a26-a0c1-08156efab7f5", 74 | "value": "example.com" 75 | }`) 76 | var obj *DomainName 77 | err := json.Unmarshal(data, &obj) 78 | assert.NoError(err) 79 | assert.Equal(Identifier("domain-name--3c10e93f-798e-5a26-a0c1-08156efab7f5"), obj.ID) 80 | assert.Equal("2.1", obj.SpecVersion) 81 | assert.Equal(TypeDomainName, obj.Type) 82 | assert.Equal("example.com", obj.Value) 83 | }) 84 | } 85 | 86 | func TestDomainResolvesTo(t *testing.T) { 87 | assert := assert.New(t) 88 | val := "example.com" 89 | 90 | t.Run("domain", func(t *testing.T) { 91 | obj, err := NewDomainName(val) 92 | assert.NoError(err) 93 | id := NewIdentifier(TypeDomainName) 94 | rel, err := obj.AddResolvesTo(id) 95 | assert.NoError(err) 96 | assert.Equal(id, rel.Target) 97 | assert.Equal(obj.ID, rel.Source) 98 | }) 99 | 100 | t.Run("ip-v4", func(t *testing.T) { 101 | obj, err := NewDomainName(val) 102 | assert.NoError(err) 103 | id := NewIdentifier(TypeIPv4Addr) 104 | rel, err := obj.AddResolvesTo(id) 105 | assert.NoError(err) 106 | assert.Equal(id, rel.Target) 107 | assert.Equal(obj.ID, rel.Source) 108 | }) 109 | 110 | t.Run("ip-v6", func(t *testing.T) { 111 | obj, err := NewDomainName(val) 112 | assert.NoError(err) 113 | id := NewIdentifier(TypeIPv6Addr) 114 | rel, err := obj.AddResolvesTo(id) 115 | assert.NoError(err) 116 | assert.Equal(id, rel.Target) 117 | assert.Equal(obj.ID, rel.Source) 118 | }) 119 | 120 | t.Run("invalid_type", func(t *testing.T) { 121 | obj, err := NewDomainName(val) 122 | assert.NoError(err) 123 | id := NewIdentifier(TypeMalware) 124 | rel, err := obj.AddResolvesTo(id) 125 | assert.Equal(err, ErrInvalidParameter) 126 | assert.Nil(rel) 127 | }) 128 | } 129 | -------------------------------------------------------------------------------- /software_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestSoftware(t *testing.T) { 13 | assert := assert.New(t) 14 | 15 | val := "Software name" 16 | testStr := "Test string" 17 | 18 | t.Run("missing_property", func(t *testing.T) { 19 | obj, err := NewSoftware("", nil) 20 | assert.Nil(obj) 21 | assert.Equal(ErrInvalidParameter, err) 22 | }) 23 | 24 | t.Run("with_property", func(t *testing.T) { 25 | obj, err := NewSoftware(val, nil) 26 | assert.NotNil(obj) 27 | assert.NoError(err) 28 | }) 29 | 30 | t.Run("payload_with_options", func(t *testing.T) { 31 | marking := make([]*GranularMarking, 0) 32 | objmark := []Identifier{Identifier("id")} 33 | specVer := "2.0" 34 | 35 | opts := []STIXOption{ 36 | OptionGranularMarking(marking), 37 | OptionObjectMarking(objmark), 38 | OptionSpecVersion(specVer), 39 | OptionDefanged(true), 40 | // 41 | OptionCPE(testStr), 42 | OptionSWID(testStr), 43 | OptionLanguages([]string{testStr}), 44 | OptionVendor(testStr), 45 | OptionVersion(testStr), 46 | } 47 | obj, err := NewSoftware(val, opts...) 48 | assert.NotNil(obj) 49 | assert.NoError(err) 50 | assert.Equal(marking, obj.GranularMarking) 51 | assert.Equal(objmark, obj.ObjectMarking) 52 | assert.Equal(specVer, obj.SpecVersion) 53 | assert.True(obj.Defanged) 54 | 55 | assert.Equal(val, obj.Name) 56 | assert.Equal(testStr, obj.CPE) 57 | assert.Equal(testStr, obj.SWID) 58 | assert.Equal([]string{testStr}, obj.Languages) 59 | assert.Equal(testStr, obj.Vendor) 60 | assert.Equal(testStr, obj.Version) 61 | }) 62 | 63 | t.Run("id-generation", func(t *testing.T) { 64 | tests := []struct { 65 | name string 66 | cpe string 67 | vendor string 68 | version string 69 | id string 70 | }{ 71 | { 72 | "Word", 73 | "", 74 | "", 75 | "", 76 | "software--b2ad0d37-3ded-5b98-96f6-8fbb994ba540", 77 | }, 78 | { 79 | "Word", 80 | "cpe:2.3:a:microsoft:word:2000:*:*:*:*:*:*:*", 81 | "", 82 | "", 83 | "software--2a04f5b2-6a03-5762-bb36-5fd126e20d6c", 84 | }, 85 | { 86 | "Word", 87 | "cpe:2.3:a:microsoft:word:2000:*:*:*:*:*:*:*", 88 | "Microsoft", 89 | "", 90 | "software--cfdedf86-ec9c-5769-ae75-2062f68c3313", 91 | }, 92 | { 93 | "Word", 94 | "cpe:2.3:a:microsoft:word:2000:*:*:*:*:*:*:*", 95 | "Microsoft", 96 | "2002", 97 | "software--c9ce663b-b8f8-5256-8cd9-3c0db09808dd", 98 | }, 99 | } 100 | for _, test := range tests { 101 | opts := make([]STIXOption, 0, 5) 102 | if test.cpe != "" { 103 | opts = append(opts, OptionCPE(test.cpe)) 104 | } 105 | if test.vendor != "" { 106 | opts = append(opts, OptionVendor(test.vendor)) 107 | } 108 | if test.version != "" { 109 | opts = append(opts, OptionVersion(test.version)) 110 | } 111 | obj, err := NewSoftware(test.name, opts...) 112 | assert.NoError(err) 113 | assert.Equal(Identifier(test.id), obj.ID) 114 | } 115 | }) 116 | 117 | // t.Run("parse_json", func(t *testing.T) { 118 | // data := []byte(``) 119 | // var obj *Software 120 | // err := json.Unmarshal(data, &obj) 121 | // assert.NoError(err) 122 | // assert.Equal(Identifier("network-traffic--2568d22a-8998-58eb-99ec-3c8ca74f527d"), obj.ID) 123 | // assert.Equal("2.1", obj.SpecVersion) 124 | // assert.Equal(TypeSoftware, obj.Type) 125 | // assert.Equal(Identifier("ipv4-addr--4d22aae0-2bf9-5427-8819-e4f6abf20a53"), obj.Src) 126 | // assert.Equal(Identifier("ipv4-addr--ff26c055-6336-5bc5-b98d-13d6226742dd"), obj.Dst) 127 | // assert.Equal("tcp", obj.Protocols[0]) 128 | // }) 129 | } 130 | -------------------------------------------------------------------------------- /testresources/examples/malicious-email-indicator-with-attachment.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "bundle", 3 | "id": "bundle--8b8ed1c1-f01d-4393-ac65-97017ed15876", 4 | "objects": [ 5 | { 6 | "type": "identity", 7 | "spec_version": "2.1", 8 | "id": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 9 | "created": "2016-08-08T15:50:10.983Z", 10 | "modified": "2016-08-08T15:50:10.983Z", 11 | "name": "The MITRE Corporation - DHS Support Team", 12 | "identity_class": "organization", 13 | "object_marking_refs": ["marking-definition--34098fce-860f-48ae-8e50-ebd3cc5e41da"] 14 | }, 15 | { 16 | "type": "attack-pattern", 17 | "spec_version": "2.1", 18 | "id": "attack-pattern--d7b066aa-4091-4276-a142-29d5d81c3484", 19 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 20 | "created": "2014-10-31T15:52:13.126Z", 21 | "modified": "2014-10-31T15:52:13.126Z", 22 | "name": "Phishing", 23 | "external_references": [ 24 | { 25 | "source_name": "capec", 26 | "external_id": "CAPEC-98" 27 | } 28 | ] 29 | }, 30 | { 31 | "type": "indicator", 32 | "spec_version": "2.1", 33 | "pattern_type": "stix", 34 | "id": "indicator--8cf9236f-1b96-493d-98be-0c1c1e8b62d7", 35 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 36 | "created": "2014-10-31T15:52:13.127Z", 37 | "modified": "2014-10-31T15:52:13.127Z", 38 | "description": "", 39 | "name": "Malicious E-mail", 40 | "indicator_types": [ 41 | "malicious-activity" 42 | ], 43 | "pattern": "[email-message:subject MATCHES '^[IMPORTANT] Please Review Before']", 44 | "valid_from": "2014-10-31T15:52:13.127931Z" 45 | }, 46 | { 47 | "type": "indicator", 48 | "spec_version": "2.1", 49 | "pattern_type": "stix", 50 | "id": "indicator--2e17f6fe-3a4d-438a-911a-e509ba1b9933", 51 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 52 | "created": "2014-10-31T15:52:13.127Z", 53 | "modified": "2014-10-31T15:52:13.127Z", 54 | "name": "Malicious Email Attachment", 55 | "description": "", 56 | "indicator_types": [ 57 | "malicious-activity" 58 | ], 59 | "valid_from": "2014-10-31T15:52:13.127931Z", 60 | "pattern": "[email-message:body_multipart[*].body_raw_ref.name MATCHES '^Final Report.+\\\\.exe$']" 61 | }, 62 | { 63 | "type": "relationship", 64 | "spec_version": "2.1", 65 | "id": "relationship--c3fa00e6-1d31-4137-98f5-32a1ec0d0e92", 66 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 67 | "created": "2014-10-31T15:52:13.127Z", 68 | "modified": "2014-10-31T15:52:13.127Z", 69 | "relationship_type": "indicates", 70 | "source_ref": "indicator--8cf9236f-1b96-493d-98be-0c1c1e8b62d7", 71 | "target_ref": "attack-pattern--d7b066aa-4091-4276-a142-29d5d81c3484" 72 | }, 73 | { 74 | "type": "relationship", 75 | "spec_version": "2.1", 76 | "id": "relationship--8e231463-6b3e-4be6-9c44-56999d8c1d80", 77 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 78 | "created": "2014-10-31T15:52:13.127Z", 79 | "modified": "2014-10-31T15:52:13.127Z", 80 | "relationship_type": "indicates", 81 | "source_ref": "indicator--2e17f6fe-3a4d-438a-911a-e509ba1b9933", 82 | "target_ref": "attack-pattern--d7b066aa-4091-4276-a142-29d5d81c3484" 83 | } 84 | ] 85 | } 86 | -------------------------------------------------------------------------------- /observed-data_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestObservedData(t *testing.T) { 15 | assert := assert.New(t) 16 | 17 | first := &Timestamp{} 18 | last := &Timestamp{} 19 | count := int64(10) 20 | objs := []Identifier{Identifier("1"), Identifier("2")} 21 | 22 | t.Run("missing_property", func(t *testing.T) { 23 | obj, err := NewObservedData(nil, nil, int64(0), []Identifier{}, nil) 24 | assert.Nil(obj) 25 | assert.Equal(ErrPropertyMissing, err) 26 | }) 27 | 28 | t.Run("no_optional", func(t *testing.T) { 29 | obj, err := NewObservedData(first, last, count, objs, nil) 30 | assert.NotNil(obj) 31 | assert.NoError(err) 32 | }) 33 | 34 | t.Run("with_options", func(t *testing.T) { 35 | conf := 50 36 | ts := &Timestamp{time.Now()} 37 | createdBy := NewIdentifier(TypeIdentity) 38 | ref := &ExternalReference{} 39 | marking := make([]*GranularMarking, 0) 40 | labels := []string{"tag1", "tag2"} 41 | lang := "en" 42 | objmark := []Identifier{Identifier("id")} 43 | specVer := "2.0" 44 | 45 | opts := []STIXOption{ 46 | OptionConfidence(conf), 47 | OptionCreated(ts), 48 | OptionModified(ts), 49 | OptionCreatedBy(createdBy), 50 | OptionExternalReferences([]*ExternalReference{ref}), 51 | OptionGranularMarking(marking), 52 | OptionLabels(labels), 53 | OptionLang(lang), 54 | OptionObjectMarking(objmark), 55 | OptionRevoked(true), 56 | OptionSpecVersion(specVer), 57 | } 58 | obj, err := NewObservedData(first, last, count, objs, opts...) 59 | assert.NotNil(obj) 60 | assert.NoError(err) 61 | assert.Equal(conf, obj.Confidence) 62 | assert.Equal(ts, obj.Created) 63 | assert.Equal(ts, obj.Modified) 64 | assert.Equal(createdBy, obj.CreatedBy) 65 | assert.Contains(obj.ExternalReferences, ref) 66 | assert.Equal(marking, obj.GranularMarking) 67 | assert.Equal(labels, obj.Labels) 68 | assert.Equal(lang, obj.Lang) 69 | assert.Equal(objmark, obj.ObjectMarking) 70 | assert.True(obj.Revoked) 71 | assert.Equal(specVer, obj.SpecVersion) 72 | 73 | assert.Equal(first, obj.FirstObserved) 74 | assert.Equal(last, obj.LastObserved) 75 | assert.Equal(count, obj.NumberObserved) 76 | assert.Equal(objs, obj.ObjectRefs) 77 | }) 78 | 79 | t.Run("parse_json", func(t *testing.T) { 80 | data := []byte(`{ 81 | "type": "observed-data", 82 | "spec_version": "2.1", 83 | "id": "observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf", 84 | "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", 85 | "created": "2016-04-06T19:58:16.000Z", 86 | "modified": "2016-04-06T19:58:16.000Z", 87 | "first_observed": "2015-12-21T19:00:00Z", 88 | "last_observed": "2015-12-21T19:00:00Z", 89 | "number_observed": 50, 90 | "object_refs": [ 91 | "ipv4-address--efcd5e80-570d-4131-b213-62cb18eaa6a8", 92 | "domain-name--ecb120bf-2694-4902-a737-62b74539a41b" 93 | ] 94 | }`) 95 | ts, err := time.Parse(time.RFC3339Nano, "2016-04-06T19:58:16.000Z") 96 | assert.NoError(err) 97 | first, _ := time.Parse(time.RFC3339Nano, "2015-12-21T19:00:00Z") 98 | last, _ := time.Parse(time.RFC3339Nano, "2015-12-21T19:00:00Z") 99 | var obj *ObservedData 100 | err = json.Unmarshal(data, &obj) 101 | assert.NoError(err) 102 | assert.Equal(Identifier("observed-data--b67d30ff-02ac-498a-92f9-32f845f448cf"), obj.ID) 103 | assert.Equal("2.1", obj.SpecVersion) 104 | assert.Equal(TypeObservedData, obj.Type) 105 | assert.Equal(ts, obj.Created.Time) 106 | assert.Equal(ts, obj.Modified.Time) 107 | assert.Equal(first, obj.FirstObserved.Time) 108 | assert.Equal(last, obj.LastObserved.Time) 109 | assert.Contains(obj.ObjectRefs, Identifier("ipv4-address--efcd5e80-570d-4131-b213-62cb18eaa6a8")) 110 | assert.Contains(obj.ObjectRefs, Identifier("domain-name--ecb120bf-2694-4902-a737-62b74539a41b")) 111 | assert.Equal(int64(50), obj.NumberObserved) 112 | }) 113 | } 114 | -------------------------------------------------------------------------------- /report.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | // Report is a collection of threat intelligence focused on one or more 7 | // topics, such as a description of a threat actor, malware, or attack 8 | // technique, including context and related details. They are used to group 9 | // related threat intelligence together so that it can be published as a 10 | // comprehensive cyber threat story. 11 | // 12 | // The Report SDO contains a list of references to STIX Objects (the CTI 13 | // objects included in the report) along with a textual description and the 14 | // name of the report. 15 | // 16 | // For example, a threat report produced by ACME Defense Corp. discussing the 17 | // Glass Gazelle campaign should be represented using Report. The Report itself 18 | // would contain the narrative of the report while the Campaign SDO and any 19 | // related SDOs (e.g., Indicators for the Campaign, Malware it uses, and the 20 | // associated Relationships) would be referenced in the report contents. 21 | type Report struct { 22 | STIXDomainObject 23 | // Name is used to identify the Report. 24 | Name string `json:"name"` 25 | // Description that provides more details and context about the Report, 26 | // potentially including its purpose and its key characteristics. 27 | Description string `json:"description,omitempty"` 28 | // Types is an open vocabulary that specifies the primary subject(s) of 29 | // this report. 30 | Types []string `json:"report_types,omitempty"` 31 | // Published is the date that this Report object was officially published 32 | // by the creator of this report. 33 | Published *Timestamp `json:"published"` 34 | // Objects specifies the STIX Objects that are referred to by this Report. 35 | Objects []Identifier `json:"object_refs"` 36 | } 37 | 38 | func (o *Report) MarshalJSON() ([]byte, error) { 39 | return marshalToJSONHelper(o) 40 | } 41 | 42 | // NewReport creates a new Report object. 43 | func NewReport(name string, published *Timestamp, objects []Identifier, opts ...STIXOption) (*Report, error) { 44 | if name == "" || published == nil || len(objects) == 0 { 45 | return nil, ErrPropertyMissing 46 | } 47 | base := newSTIXDomainObject(TypeReport) 48 | obj := &Report{ 49 | STIXDomainObject: base, 50 | Name: name, 51 | Published: published, 52 | Objects: objects, 53 | } 54 | 55 | err := applyOptions(obj, opts) 56 | return obj, err 57 | } 58 | 59 | const ( 60 | // ReportTypeAttackPattern subject is a characterization of one or more 61 | // attack patterns and related information. 62 | ReportTypeAttackPattern = "attack-pattern" 63 | // ReportTypeCampaign subject is a characterization of one or more 64 | // campaigns and related information. 65 | ReportTypeCampaign = "campaign" 66 | // ReportTypeIdentity subject is a characterization of one or more 67 | // identities and related information. 68 | ReportTypeIdentity = "identity" 69 | // ReportTypeIndicator subject is a characterization of one or more 70 | // indicators and related information. 71 | ReportTypeIndicator = "indicator" 72 | // ReportTypeIntrusionSet subject is a characterization of one or more 73 | // intrusion sets and related information. 74 | ReportTypeIntrusionSet = "intrusion-set" 75 | // ReportTypeMalware subject is a characterization of one or more malware 76 | // instances and related information. 77 | ReportTypeMalware = "malware" 78 | // ReportTypeObservedData subject is a characterization of observed data 79 | // and related information. 80 | ReportTypeObservedData = "observed-data" 81 | // ReportTypeThreatActor subject is a characterization of one or more 82 | // threat actors and related information. 83 | ReportTypeThreatActor = "threat-actor" 84 | // ReportTypeThreatReport subject is a broad characterization of a threat 85 | // across multiple facets. 86 | ReportTypeThreatReport = "threat-report" 87 | // ReportTypeTool subject is a characterization of one or more tools and 88 | // related information. 89 | ReportTypeTool = "tool" 90 | // ReportTypeVulnerability subject is a characterization of one or more 91 | // vulnerabilities and related information. 92 | ReportTypeVulnerability = "vulnerability" 93 | ) 94 | -------------------------------------------------------------------------------- /note_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestNote(t *testing.T) { 15 | assert := assert.New(t) 16 | 17 | content := "Note content" 18 | authors := []string{"Author 1", "Author 2"} 19 | abstract := "Note abstract" 20 | ip := NewIdentifier(TypeIPv4Addr) 21 | objects := []Identifier{ip} 22 | 23 | t.Run("missing_property", func(t *testing.T) { 24 | obj, err := NewNote("", []Identifier{}, nil) 25 | assert.Nil(obj) 26 | assert.Equal(ErrPropertyMissing, err) 27 | }) 28 | 29 | t.Run("no_optional", func(t *testing.T) { 30 | obj, err := NewNote(content, objects, nil) 31 | assert.NotNil(obj) 32 | assert.NoError(err) 33 | }) 34 | 35 | t.Run("with_options", func(t *testing.T) { 36 | conf := 50 37 | ts := &Timestamp{time.Now()} 38 | createdBy := NewIdentifier(TypeIdentity) 39 | ref := &ExternalReference{} 40 | marking := make([]*GranularMarking, 0) 41 | labels := []string{"tag1", "tag2"} 42 | lang := "en" 43 | objmark := []Identifier{Identifier("id")} 44 | specVer := "2.0" 45 | 46 | opts := []STIXOption{ 47 | OptionConfidence(conf), 48 | OptionCreated(ts), 49 | OptionModified(ts), 50 | OptionCreatedBy(createdBy), 51 | OptionExternalReferences([]*ExternalReference{ref}), 52 | OptionGranularMarking(marking), 53 | OptionLabels(labels), 54 | OptionLang(lang), 55 | OptionObjectMarking(objmark), 56 | OptionRevoked(true), 57 | OptionSpecVersion(specVer), 58 | // 59 | OptionAbstract(abstract), 60 | OptionAuthors(authors), 61 | } 62 | obj, err := NewNote(content, objects, opts...) 63 | assert.NotNil(obj) 64 | assert.NoError(err) 65 | assert.Equal(conf, obj.Confidence) 66 | assert.Equal(ts, obj.Created) 67 | assert.Equal(ts, obj.Modified) 68 | assert.Equal(createdBy, obj.CreatedBy) 69 | assert.Contains(obj.ExternalReferences, ref) 70 | assert.Equal(marking, obj.GranularMarking) 71 | assert.Equal(labels, obj.Labels) 72 | assert.Equal(lang, obj.Lang) 73 | assert.Equal(objmark, obj.ObjectMarking) 74 | assert.True(obj.Revoked) 75 | assert.Equal(specVer, obj.SpecVersion) 76 | 77 | assert.Equal(abstract, obj.Abstract) 78 | assert.Equal(authors, obj.Authors) 79 | }) 80 | 81 | t.Run("parse_json", func(t *testing.T) { 82 | data := []byte(`{ 83 | "type": "note", 84 | "spec_version": "2.1", 85 | "id": "note--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061", 86 | "created": "2016-05-12T08:17:27.000Z", 87 | "modified": "2016-05-12T08:17:27.000Z", 88 | "external_references": [ 89 | { 90 | "source_name": "job-tracker", 91 | "external_id": "job-id-1234" 92 | } 93 | ], 94 | "abstract": "Tracking Team Note#1", 95 | "content": "This note indicates the various steps taken by the threat analyst team to investigate this specific campaign. Step 1) Do a scan 2) Review scanned results for identified hosts not known by external intel….etc", 96 | "authors": ["John Doe"], 97 | "object_refs": ["campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f"] 98 | }`) 99 | ts, err := time.Parse(time.RFC3339Nano, "2016-05-12T08:17:27.000Z") 100 | assert.NoError(err) 101 | var obj *Note 102 | err = json.Unmarshal(data, &obj) 103 | assert.NoError(err) 104 | assert.Equal(Identifier("note--0c7b5b88-8ff7-4a4d-aa9d-feb398cd0061"), obj.ID) 105 | assert.Equal("2.1", obj.SpecVersion) 106 | assert.Equal(TypeNote, obj.Type) 107 | assert.Equal(ts, obj.Created.Time) 108 | assert.Equal(ts, obj.Modified.Time) 109 | assert.Equal("Tracking Team Note#1", obj.Abstract) 110 | assert.Equal("This note indicates the various steps taken by the threat analyst team to investigate this specific campaign. Step 1) Do a scan 2) Review scanned results for identified hosts not known by external intel….etc", obj.Content) 111 | assert.Contains(obj.Authors, "John Doe") 112 | assert.Contains(obj.Objects, Identifier("campaign--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f")) 113 | assert.Len(obj.ExternalReferences, 1) 114 | assert.Equal("job-tracker", obj.ExternalReferences[0].Name) 115 | assert.Equal("job-id-1234", obj.ExternalReferences[0].ExternalID) 116 | }) 117 | } 118 | -------------------------------------------------------------------------------- /grouping_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestGrouping(t *testing.T) { 15 | assert := assert.New(t) 16 | 17 | context := "New campaign" 18 | objects := []Identifier{Identifier("1")} 19 | 20 | t.Run("missing_property", func(t *testing.T) { 21 | obj, err := NewGrouping("", []Identifier{}) 22 | assert.Nil(obj) 23 | assert.Equal(ErrPropertyMissing, err) 24 | }) 25 | 26 | t.Run("no_optional", func(t *testing.T) { 27 | obj, err := NewGrouping(context, objects, nil) 28 | assert.NotNil(obj) 29 | assert.NoError(err) 30 | }) 31 | 32 | t.Run("with_options", func(t *testing.T) { 33 | conf := 50 34 | ts := &Timestamp{time.Now()} 35 | createdBy := NewIdentifier(TypeIdentity) 36 | ref := &ExternalReference{} 37 | marking := make([]*GranularMarking, 0) 38 | labels := []string{"tag1", "tag2"} 39 | lang := "en" 40 | objmark := []Identifier{Identifier("id")} 41 | specVer := "2.0" 42 | 43 | desc := "My description" 44 | name := "Group 1" 45 | 46 | opts := []STIXOption{ 47 | OptionConfidence(conf), 48 | OptionCreated(ts), 49 | OptionModified(ts), 50 | OptionCreatedBy(createdBy), 51 | OptionExternalReferences([]*ExternalReference{ref}), 52 | OptionGranularMarking(marking), 53 | OptionLabels(labels), 54 | OptionLang(lang), 55 | OptionObjectMarking(objmark), 56 | OptionRevoked(true), 57 | OptionSpecVersion(specVer), 58 | // 59 | OptionDescription(desc), 60 | OptionName(name), 61 | } 62 | obj, err := NewGrouping(context, objects, opts...) 63 | assert.NotNil(obj) 64 | assert.NoError(err) 65 | assert.Equal(conf, obj.Confidence) 66 | assert.Equal(ts, obj.Created) 67 | assert.Equal(ts, obj.Modified) 68 | assert.Equal(createdBy, obj.CreatedBy) 69 | assert.Contains(obj.ExternalReferences, ref) 70 | assert.Equal(marking, obj.GranularMarking) 71 | assert.Equal(labels, obj.Labels) 72 | assert.Equal(lang, obj.Lang) 73 | assert.Equal(objmark, obj.ObjectMarking) 74 | assert.True(obj.Revoked) 75 | assert.Equal(specVer, obj.SpecVersion) 76 | 77 | assert.Equal(desc, obj.Description) 78 | assert.Equal(name, obj.Name) 79 | }) 80 | 81 | t.Run("parse_json", func(t *testing.T) { 82 | data := []byte(`{ 83 | "type": "grouping", 84 | "spec_version": "2.1", 85 | "id": "grouping--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3", 86 | "created_by_ref": "identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283", 87 | "created": "2015-12-21T19:59:11.000Z", 88 | "modified": "2015-12-21T19:59:11.000Z", 89 | "name": "The Black Vine Cyberespionage Group", 90 | "description": "A simple collection of Black Vine Cyberespionage Group attributed intel", 91 | "context": "suspicious-activity", 92 | "object_refs": [ 93 | "indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2", 94 | "campaign--83422c77-904c-4dc1-aff5-5c38f3a2c55c", 95 | "relationship--f82356ae-fe6c-437c-9c24-6b64314ae68a", 96 | "file--0203b5c8-f8b6-4ddb-9ad0-527d727f968b" 97 | ] 98 | }`) 99 | ts, err := time.Parse(time.RFC3339Nano, "2015-12-21T19:59:11.000Z") 100 | assert.NoError(err) 101 | var obj *Grouping 102 | err = json.Unmarshal(data, &obj) 103 | assert.NoError(err) 104 | assert.Equal(Identifier("grouping--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3"), obj.ID) 105 | assert.Equal("2.1", obj.SpecVersion) 106 | assert.Equal(TypeGrouping, obj.Type) 107 | assert.Equal("The Black Vine Cyberespionage Group", obj.Name) 108 | assert.Equal("A simple collection of Black Vine Cyberespionage Group attributed intel", obj.Description) 109 | assert.Equal(ts, obj.Created.Time) 110 | assert.Equal(ts, obj.Modified.Time) 111 | assert.Equal(GroupingContextSuspiciousActivity, obj.Context) 112 | assert.Len(obj.Objects, 4) 113 | assert.Contains(obj.Objects, Identifier("indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2")) 114 | assert.Contains(obj.Objects, Identifier("campaign--83422c77-904c-4dc1-aff5-5c38f3a2c55c")) 115 | assert.Contains(obj.Objects, Identifier("relationship--f82356ae-fe6c-437c-9c24-6b64314ae68a")) 116 | assert.Contains(obj.Objects, Identifier("file--0203b5c8-f8b6-4ddb-9ad0-527d727f968b")) 117 | }) 118 | } 119 | -------------------------------------------------------------------------------- /identity_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestIdentity(t *testing.T) { 15 | assert := assert.New(t) 16 | 17 | name := "New campaign" 18 | 19 | t.Run("missing_property", func(t *testing.T) { 20 | obj, err := NewIdentity("") 21 | assert.Nil(obj) 22 | assert.Equal(ErrPropertyMissing, err) 23 | }) 24 | 25 | t.Run("no_optional", func(t *testing.T) { 26 | obj, err := NewIdentity(name, nil) 27 | assert.NotNil(obj) 28 | assert.NoError(err) 29 | }) 30 | 31 | t.Run("with_options", func(t *testing.T) { 32 | conf := 50 33 | desc := "My description" 34 | ts := &Timestamp{time.Now()} 35 | createdBy := NewIdentifier(TypeIdentity) 36 | ref := &ExternalReference{} 37 | marking := make([]*GranularMarking, 0) 38 | labels := []string{"tag1", "tag2"} 39 | lang := "en" 40 | objmark := []Identifier{Identifier("id")} 41 | specVer := "2.0" 42 | 43 | roles := []string{"CEO", "Retailer"} 44 | class := IdentityClassUnknown 45 | sectors := []string{IdentitySectorDefence, IdentitySectorEntertainment} 46 | contact := "123 Main ST" 47 | 48 | opts := []STIXOption{ 49 | OptionConfidence(conf), 50 | OptionCreated(ts), 51 | OptionModified(ts), 52 | OptionCreatedBy(createdBy), 53 | OptionExternalReferences([]*ExternalReference{ref}), 54 | OptionGranularMarking(marking), 55 | OptionLabels(labels), 56 | OptionLang(lang), 57 | OptionObjectMarking(objmark), 58 | OptionRevoked(true), 59 | OptionSpecVersion(specVer), 60 | // 61 | OptionDescription(desc), 62 | OptionClass(class), 63 | OptionRoles(roles), 64 | OptionSectors(sectors), 65 | OptionContactInformation(contact), 66 | } 67 | obj, err := NewIdentity(name, opts...) 68 | assert.NotNil(obj) 69 | assert.NoError(err) 70 | assert.Equal(conf, obj.Confidence) 71 | assert.Equal(ts, obj.Created) 72 | assert.Equal(ts, obj.Modified) 73 | assert.Equal(createdBy, obj.CreatedBy) 74 | assert.Contains(obj.ExternalReferences, ref) 75 | assert.Equal(marking, obj.GranularMarking) 76 | assert.Equal(labels, obj.Labels) 77 | assert.Equal(lang, obj.Lang) 78 | assert.Equal(objmark, obj.ObjectMarking) 79 | assert.True(obj.Revoked) 80 | assert.Equal(specVer, obj.SpecVersion) 81 | 82 | assert.Equal(name, obj.Name) 83 | assert.Equal(class, obj.Class) 84 | assert.Equal(desc, obj.Description) 85 | assert.Equal(roles, obj.Roles) 86 | assert.Equal(sectors, obj.Sectors) 87 | assert.Equal(contact, obj.ContactInformation) 88 | }) 89 | 90 | t.Run("parse_json", func(t *testing.T) { 91 | data := []byte(`{ 92 | "type": "identity", 93 | "spec_version": "2.1", 94 | "id": "identity--023d105b-752e-4e3c-941c-7d3f3cb15e9e", 95 | "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", 96 | "created": "2016-04-06T20:03:00.000Z", 97 | "modified": "2016-04-06T20:03:00.000Z", 98 | "name": "John Smith", 99 | "identity_class": "individual" 100 | }`) 101 | ts, err := time.Parse(time.RFC3339Nano, "2016-04-06T20:03:00.000Z") 102 | assert.NoError(err) 103 | var obj *Identity 104 | err = json.Unmarshal(data, &obj) 105 | assert.NoError(err) 106 | assert.Equal(Identifier("identity--023d105b-752e-4e3c-941c-7d3f3cb15e9e"), obj.ID) 107 | assert.Equal("2.1", obj.SpecVersion) 108 | assert.Equal(TypeIdentity, obj.Type) 109 | assert.Equal("John Smith", obj.Name) 110 | assert.Equal(ts, obj.Created.Time) 111 | assert.Equal(ts, obj.Modified.Time) 112 | assert.Equal(IdentityClassIndividual, obj.Class) 113 | }) 114 | } 115 | 116 | func TestIdentityMitigates(t *testing.T) { 117 | assert := assert.New(t) 118 | 119 | t.Run("vulnerability", func(t *testing.T) { 120 | obj, err := NewIdentity("name") 121 | assert.NoError(err) 122 | id := NewIdentifier(TypeLocation) 123 | rel, err := obj.AddLocatedAt(id) 124 | assert.NoError(err) 125 | assert.Equal(id, rel.Target) 126 | assert.Equal(obj.ID, rel.Source) 127 | }) 128 | 129 | t.Run("invalid_type", func(t *testing.T) { 130 | obj, err := NewIdentity("name") 131 | assert.NoError(err) 132 | id := NewIdentifier(TypeIPv4Addr) 133 | rel, err := obj.AddLocatedAt(id) 134 | assert.Equal(err, ErrInvalidParameter) 135 | assert.Nil(rel) 136 | }) 137 | } 138 | -------------------------------------------------------------------------------- /report_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestReport(t *testing.T) { 15 | assert := assert.New(t) 16 | 17 | ts := &Timestamp{time.Now()} 18 | desc := "Report content" 19 | objects := []Identifier{Identifier("something")} 20 | name := "Report name" 21 | 22 | t.Run("missing_property", func(t *testing.T) { 23 | obj, err := NewReport("", nil, []Identifier{}, nil) 24 | assert.Nil(obj) 25 | assert.Equal(ErrPropertyMissing, err) 26 | }) 27 | 28 | t.Run("no_optional", func(t *testing.T) { 29 | obj, err := NewReport(name, ts, objects, nil) 30 | assert.NotNil(obj) 31 | assert.NoError(err) 32 | }) 33 | 34 | t.Run("with_options", func(t *testing.T) { 35 | conf := 50 36 | createdBy := NewIdentifier(TypeIdentity) 37 | ref := &ExternalReference{} 38 | marking := make([]*GranularMarking, 0) 39 | labels := []string{"tag1", "tag2"} 40 | lang := "en" 41 | objmark := []Identifier{Identifier("id")} 42 | specVer := "2.0" 43 | 44 | typs := []string{ReportTypeAttackPattern} 45 | 46 | opts := []STIXOption{ 47 | OptionConfidence(conf), 48 | OptionCreated(ts), 49 | OptionModified(ts), 50 | OptionCreatedBy(createdBy), 51 | OptionExternalReferences([]*ExternalReference{ref}), 52 | OptionGranularMarking(marking), 53 | OptionLabels(labels), 54 | OptionLang(lang), 55 | OptionObjectMarking(objmark), 56 | OptionRevoked(true), 57 | OptionSpecVersion(specVer), 58 | // 59 | OptionDescription(desc), 60 | OptionTypes(typs), 61 | } 62 | obj, err := NewReport(name, ts, objects, opts...) 63 | assert.NotNil(obj) 64 | assert.NoError(err) 65 | assert.Equal(conf, obj.Confidence) 66 | assert.Equal(ts, obj.Created) 67 | assert.Equal(ts, obj.Modified) 68 | assert.Equal(createdBy, obj.CreatedBy) 69 | assert.Contains(obj.ExternalReferences, ref) 70 | assert.Equal(marking, obj.GranularMarking) 71 | assert.Equal(labels, obj.Labels) 72 | assert.Equal(lang, obj.Lang) 73 | assert.Equal(objmark, obj.ObjectMarking) 74 | assert.True(obj.Revoked) 75 | assert.Equal(specVer, obj.SpecVersion) 76 | 77 | assert.Equal(desc, obj.Description) 78 | assert.Equal(name, obj.Name) 79 | assert.Equal(typs, obj.Types) 80 | assert.Equal(objects, obj.Objects) 81 | assert.Equal(ts, obj.Published) 82 | }) 83 | 84 | t.Run("parse_json", func(t *testing.T) { 85 | data := []byte(`{ 86 | "type": "report", 87 | "spec_version": "2.1", 88 | "id": "report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3", 89 | "created_by_ref": "identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283", 90 | "created": "2015-12-21T19:59:11.000Z", 91 | "modified": "2015-12-21T19:59:11.000Z", 92 | "name": "The Black Vine Cyberespionage Group", 93 | "description": "A simple report with an indicator and campaign", 94 | "published": "2016-01-20T17:00:00.000Z", 95 | "report_types": ["campaign"], 96 | "object_refs": [ 97 | "indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2", 98 | "campaign--83422c77-904c-4dc1-aff5-5c38f3a2c55c", 99 | "relationship--f82356ae-fe6c-437c-9c24-6b64314ae68a" 100 | ] 101 | }`) 102 | ts, err := time.Parse(time.RFC3339Nano, "2015-12-21T19:59:11.000Z") 103 | assert.NoError(err) 104 | pub, err := time.Parse(time.RFC3339Nano, "2016-01-20T17:00:00.000Z") 105 | assert.NoError(err) 106 | var obj *Report 107 | err = json.Unmarshal(data, &obj) 108 | assert.NoError(err) 109 | assert.Equal(Identifier("report--84e4d88f-44ea-4bcd-bbf3-b2c1c320bcb3"), obj.ID) 110 | assert.Equal("2.1", obj.SpecVersion) 111 | assert.Equal(TypeReport, obj.Type) 112 | assert.Equal(ts, obj.Created.Time) 113 | assert.Equal(ts, obj.Modified.Time) 114 | assert.Equal(pub, obj.Published.Time) 115 | assert.Equal([]string{ReportTypeCampaign}, obj.Types) 116 | assert.Equal("A simple report with an indicator and campaign", obj.Description) 117 | assert.Equal("The Black Vine Cyberespionage Group", obj.Name) 118 | assert.Contains(obj.Objects, Identifier("indicator--26ffb872-1dd9-446e-b6f5-d58527e5b5d2")) 119 | assert.Contains(obj.Objects, Identifier("campaign--83422c77-904c-4dc1-aff5-5c38f3a2c55c")) 120 | assert.Contains(obj.Objects, Identifier("relationship--f82356ae-fe6c-437c-9c24-6b64314ae68a")) 121 | assert.Equal(Identifier("identity--a463ffb3-1bd9-4d94-b02d-74e4f1658283"), obj.CreatedBy) 122 | }) 123 | } 124 | -------------------------------------------------------------------------------- /testresources/examples/infrastructure.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "bundle--5330e3e3-8f58-4d21-9f4b-793a054c0d33", 3 | "objects": [ 4 | { 5 | "created": "2016-05-07T11:22:30.000Z", 6 | "id": "infrastructure--38c47d93-d984-4fd9-b87b-d69d0841628d", 7 | "infrastructure_types": [ 8 | "command-and-control" 9 | ], 10 | "labels": [ 11 | "command-and-control" 12 | ], 13 | "modified": "2016-05-07T11:22:30.000Z", 14 | "name": "Poison Ivy C2", 15 | "type": "infrastructure", 16 | "spec_version": "2.1" 17 | }, 18 | { 19 | "created": "2016-05-09T08:17:27.000Z", 20 | "id": "relationship--7aebe2f0-28d6-48a2-9c3e-b0aaa60266ed", 21 | "modified": "2016-05-09T08:17:27.000Z", 22 | "relationship_type": "controls", 23 | "source_ref": "infrastructure--38c47d93-d984-4fd9-b87b-d69d0841628d", 24 | "target_ref": "malware--16f4f3f9-1b68-4abb-bb66-7639d49f1e30", 25 | "type": "relationship", 26 | "spec_version": "2.1" 27 | }, 28 | { 29 | "created": "2016-05-08T14:31:09.000Z", 30 | "id": "malware--16f4f3f9-1b68-4abb-bb66-7639d49f1e30", 31 | "is_family": true, 32 | "labels": [ 33 | "rat" 34 | ], 35 | "modified": "2016-05-08T14:31:09.000Z", 36 | "name": "Poison Ivy", 37 | "malware_types":[ 38 | "remote-access-trojan" 39 | ], 40 | "type": "malware", 41 | "spec_version": "2.1" 42 | }, 43 | { 44 | "created": "2016-05-09T08:17:27.000Z", 45 | "id": "relationship--7aebe2f0-28d6-48a2-9c3e-b0aaa60266ef", 46 | "modified": "2016-05-09T08:17:27.000Z", 47 | "relationship_type": "consists-of", 48 | "source_ref": "infrastructure--38c47d93-d984-4fd9-b87b-d69d0841628d", 49 | "target_ref": "ipv4-addr--1e4ffad7-0b88-41d4-ba4d-77d27490b956", 50 | "type": "relationship", 51 | "spec_version": "2.1" 52 | }, 53 | { 54 | "id": "ipv4-addr--1e4ffad7-0b88-41d4-ba4d-77d27490b956", 55 | "type": "ipv4-addr", 56 | "value": "198.51.100.3" 57 | }, 58 | { 59 | "created": "2016-11-22T09:22:30.000Z", 60 | "id": "infrastructure--d09c50cf-5bab-465e-9e2d-543912148b73", 61 | "infrastructure_types": [ 62 | "hosting-target-lists" 63 | ], 64 | "labels": [ 65 | "target-list-hosting" 66 | ], 67 | "modified": "2016-11-22T09:22:30.000Z", 68 | "name": "Example target-list-hosting", 69 | "type": "infrastructure", 70 | "spec_version": "2.1" 71 | }, 72 | { 73 | "created": "2016-11-23T08:17:27.000Z", 74 | "id": "relationship--37ac0c8d-f86d-4e56-aee9-914343959a4c", 75 | "modified": "2016-11-23T08:17:27.000Z", 76 | "relationship_type": "uses", 77 | "source_ref": "malware--3a41e552-999b-4ad3-bedc-332b6d9ff80c", 78 | "target_ref": "infrastructure--d09c50cf-5bab-465e-9e2d-543912148b73", 79 | "type": "relationship", 80 | "spec_version": "2.1" 81 | }, 82 | { 83 | "created": "2016-11-12T14:31:09.000Z", 84 | "id": "malware--3a41e552-999b-4ad3-bedc-332b6d9ff80c", 85 | "is_family": true, 86 | "modified": "2016-11-12T14:31:09.000Z", 87 | "name": "IMDDOS", 88 | "type": "malware", 89 | "malware_types":[ 90 | "bot" 91 | ], 92 | 93 | "spec_version": "2.1" 94 | }, 95 | { 96 | "created": "2016-11-23T10:42:39.000Z", 97 | "id": "relationship--81f12913-1372-4c96-85ec-E9034ac98aba", 98 | "modified": "2016-11-23T10:42:39.000Z", 99 | "relationship_type": "consists-of", 100 | "source_ref": "infrastructure--d09c50cf-5bab-465e-9e2d-543912148b73", 101 | "target_ref": "domain-name--ecb120bf-2694-4902-a737-62b74539a41b", 102 | "type": "relationship", 103 | "spec_version": "2.1" 104 | }, 105 | { 106 | "id": "domain-name--ecb120bf-2694-4902-a737-62b74539a41b", 107 | "type": "domain-name", 108 | "value": "example.com" 109 | } 110 | ], 111 | "type": "bundle" 112 | } 113 | -------------------------------------------------------------------------------- /testresources/examples/README.md: -------------------------------------------------------------------------------- 1 | # Notes on STIX 2.0 Examples 2 | 3 | ## General Comments 4 | * Most of these were manually converted from the old [STIX 1.2 XML Idioms](http://stixproject.github.io/documentation/idioms/), and have been updated to STIX 2.0 JSON. This means many values are the same but some have been added based on required properties from the [STIX 2.0 Specification](https://docs.google.com/document/d/1yvqWaPPnPW-2NiVCLqzRszcx91ffMowfT5MmE9Nsy_w/edit#heading=h.8bbhgdisbmt). 5 | * Some of the STIX 1.x idioms were scrapped when transitioning to STIX 2.0 since certain concepts did not translate well. 6 | * All of these examples are placed within a STIX Bundle object. 7 | * There is a lot of repetition since all SDOs and SROs require common properties like: id, created, modified etc. 8 | * The majority of these have indicators or specifically focus on indicators since this object was one of the main use cases for 2.0. 9 | * Some older examples were taken out until 2.1 is finished. Examples for objects like Incidents, Infrastructure and Assets will return with the release of 2.1. 10 | * In many cases, only required properties of objects were used unless the objective was to highlight specific objects as a whole. In those instances, most optional properties were also included. 11 | * SROs in each of these examples are located at the bottom of the Bundle, after the SDOs. 12 | 13 | 14 | ## Comments for Individual Examples 15 | * Detailed write-ups and analyses of these examples on the STIX 2.0 CTI-documentation site under the [Examples page](https://oasis-open.github.io/cti-documentation/stix/examples.html). 16 | 17 | ### Campaigns and Threat Actors 18 | *Write-up on the documentation site to come soon* 19 | * Completely rewritten from 1.x "Defining Campaigns vs. Threat Actors Idiom". 20 | * Now includes Attack Pattern and multiple Identity objects. 21 | 22 | ### Identifying a Threat Actor Profile 23 | * Description of this example can be seen on the CTI-documentation site [here](https://oasis-open.github.io/cti-documentation/examples/identifying-a-threat-actor-profile). 24 | * Has been expanded from the 1.x idiom--added optional Threat Actor properties new to 2.0 such as aliases, roles, goals, motivation, and sophistication. 25 | 26 | ### Indicator for C2 IP Address 27 | *Write-up on the documentation site to come soon* 28 | * Needs to be expanded for 2.0--currently only contains one Indicator object representing the IP address. 29 | 30 | ### Indicator for Malicious URL 31 | * Description of this example can be seen on the CTI-documentation site [here](https://oasis-open.github.io/cti-documentation/examples/indicator-for-malicious-url). 32 | * This example was expanded upon for 2.0--a Malware SDO was added along with a relationship connecting it to the Indicator SDO. 33 | 34 | ### Indicator, Campaign, and Intrusion Set 35 | *Write-up on the documentation site to come soon* 36 | * Formerly titled Indictor to Campaign Relationship--expanded for 2.0 to include Intrusion Set SDO. 37 | 38 | ### Malicious E-mail Indicator With Attachment 39 | *Write-up on the documentation site to come soon* 40 | * Includes Attack Pattern and Identity objects not present in 1.x. 41 | 42 | ### Malware Indicator for File Hash 43 | * Description of this example can be seen on the CTI-documentation site [here](https://oasis-open.github.io/cti-documentation/examples/malware-indicator-for-file-hash). 44 | * Malware SDO is just a stub in 2.0, this example will be improved when 2.1 is released. 45 | 46 | ### Sighting of an Indicator 47 | *Completely New Example Written for 2.0* 48 | * Description of this example can be seen on the CTI-documentation site [here](https://oasis-open.github.io/cti-documentation/examples/sighting-of-an-indicator). 49 | 50 | ### Threat Actor Leveraging Attack Patterns and Malware 51 | * Description of this example can be seen on the CTI-documentation site [here](https://oasis-open.github.io/cti-documentation/examples/threat-actor-leveraging-attack-patterns-and-malware). 52 | * Expanded to demonstrate the use of Kill chains in STIX 2.0 within the Attack Pattern and Malware SDOs. 53 | 54 | ### Using Granular Markings 55 | *Completely New Example Written for 2.0* 56 | 57 | *Write-up on the documentation site to come soon* 58 | 59 | ### Using Marking Definitions 60 | *Completely New Example Written for 2.0* 61 | * Description of this example can be seen on the CTI-documentation site [here](https://oasis-open.github.io/cti-documentation/examples/using-marking-definitions). 62 | * Demonstrates both specification-designed marking definition types: Statement and TLP. 63 | 64 | ### Infrastructure Examples 65 | *Completely New Example Written for 2.1* 66 | * This example contains the bundled form of the infrastructure examples from STIX 2.1 WD04. 67 | * Demonstrates how to use the new Infrastructure object. 68 | -------------------------------------------------------------------------------- /ip.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import "fmt" 7 | 8 | // IPv4Address represents one or more IPv4 addresses expressed using CIDR 9 | // notation. 10 | type IPv4Address struct { 11 | STIXCyberObservableObject 12 | // Value specifies the values of one or more IPv4 addresses expressed using 13 | // CIDR notation. If a given IPv4Address object represents a single IPv4 14 | // address, the CIDR /32 suffix MAY be omitted. Example: 10.2.4.5/24 15 | Value string `json:"value"` 16 | // ResolvesTo specifies a list of references to one or more Layer 2 Media 17 | // Access Control (MAC) addresses that the IPv4 address resolves to. 18 | ResolvesTo []Identifier `json:"resolves_to_refs,omitempty"` 19 | // BelongsTo specifies a list of references to one or more autonomous 20 | // systems (AS) that the IPv4 address belongs to. 21 | BelongsTo []Identifier `json:"belongs_to_refs,omitempty"` 22 | } 23 | 24 | func (o *IPv4Address) MarshalJSON() ([]byte, error) { 25 | return marshalToJSONHelper(o) 26 | } 27 | 28 | // AddResolvesTo describes that this IPv4Address resolves to one or more Layer 29 | // 2 Media Access Control (MAC) addresses. 30 | func (c *IPv4Address) AddResolvesTo(id Identifier, opts ...STIXOption) (*Relationship, error) { 31 | if !IsValidIdentifier(id) || !id.ForType(TypeMACAddress) { 32 | return nil, ErrInvalidParameter 33 | } 34 | return NewRelationship(RelationshipTypeResolvesTo, c.ID, id, opts...) 35 | } 36 | 37 | // AddBelongsTo describes that this IPv4 Address belongs to one or more 38 | // autonomous systems (AS). 39 | func (c *IPv4Address) AddBelongsTo(id Identifier, opts ...STIXOption) (*Relationship, error) { 40 | if !IsValidIdentifier(id) || !id.ForType(TypeAutonomousSystem) { 41 | return nil, ErrInvalidParameter 42 | } 43 | return NewRelationship(RelationshipTypeBelongsTo, c.ID, id, opts...) 44 | } 45 | 46 | // NewIPv4Address creates a new IPv4Address object. 47 | func NewIPv4Address(value string, opts ...STIXOption) (*IPv4Address, error) { 48 | if value == "" { 49 | return nil, ErrInvalidParameter 50 | } 51 | base := newSTIXCyberObservableObject(TypeIPv4Addr) 52 | obj := &IPv4Address{ 53 | STIXCyberObservableObject: base, 54 | Value: value, 55 | } 56 | 57 | err := applyOptions(obj, opts) 58 | obj.ID = NewObservableIdentifier(fmt.Sprintf("[\"%s\"]", value), TypeIPv4Addr) 59 | return obj, err 60 | } 61 | 62 | // IPv6Address represents one or more IPv6 addresses expressed using CIDR 63 | // notation. 64 | type IPv6Address struct { 65 | STIXCyberObservableObject 66 | // Value specifies the values of one or more IPv6 addresses expressed using 67 | // CIDR notation. If a given IPv6Address object represents a single IPv6 68 | // address, the CIDR /128 suffix MAY be omitted. 69 | Value string `json:"value"` 70 | // ResolvesTo specifies a list of references to one or more Layer 2 Media 71 | // Access Control (MAC) addresses that the IPv6 address resolves to. 72 | ResolvesTo []Identifier `json:"resolves_to_refs,omitempty"` 73 | // BelongsTo specifies a list of references to one or more autonomous 74 | // systems (AS) that the IPv6 address belongs to. 75 | BelongsTo []Identifier `json:"belongs_to_refs,omitempty"` 76 | } 77 | 78 | func (o *IPv6Address) MarshalJSON() ([]byte, error) { 79 | return marshalToJSONHelper(o) 80 | } 81 | 82 | // AddResolvesTo describes that this IPv6Address resolves to one or more Layer 83 | // 2 Media Access Control (MAC) addresses. 84 | func (c *IPv6Address) AddResolvesTo(id Identifier, opts ...STIXOption) (*Relationship, error) { 85 | if !IsValidIdentifier(id) || !id.ForType(TypeMACAddress) { 86 | return nil, ErrInvalidParameter 87 | } 88 | return NewRelationship(RelationshipTypeResolvesTo, c.ID, id, opts...) 89 | } 90 | 91 | // AddBelongsTo describes that this IPv6 Address belongs to one or more 92 | // autonomous systems (AS). 93 | func (c *IPv6Address) AddBelongsTo(id Identifier, opts ...STIXOption) (*Relationship, error) { 94 | if !IsValidIdentifier(id) || !id.ForType(TypeAutonomousSystem) { 95 | return nil, ErrInvalidParameter 96 | } 97 | return NewRelationship(RelationshipTypeBelongsTo, c.ID, id, opts...) 98 | } 99 | 100 | // NewIPv6Address creates a new IPv6Address object. 101 | func NewIPv6Address(value string, opts ...STIXOption) (*IPv6Address, error) { 102 | if value == "" { 103 | return nil, ErrInvalidParameter 104 | } 105 | base := newSTIXCyberObservableObject(TypeIPv6Addr) 106 | obj := &IPv6Address{ 107 | STIXCyberObservableObject: base, 108 | Value: value, 109 | } 110 | 111 | err := applyOptions(obj, opts) 112 | obj.ID = NewObservableIdentifier(fmt.Sprintf("[\"%s\"]", value), TypeIPv6Addr) 113 | return obj, err 114 | } 115 | -------------------------------------------------------------------------------- /regkey_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestRegistryKey(t *testing.T) { 15 | assert := assert.New(t) 16 | 17 | testStr := "test string" 18 | val := []*RegistryValue{{Name: "name", Data: "data", DataType: RegSz}} 19 | ts := &Timestamp{time.Now()} 20 | ref := Identifier("ref") 21 | num := int64(42) 22 | 23 | t.Run("missing_property", func(t *testing.T) { 24 | obj, err := NewRegistryKey() 25 | assert.Nil(obj) 26 | assert.Equal(ErrPropertyMissing, err) 27 | }) 28 | 29 | t.Run("with_property", func(t *testing.T) { 30 | obj, err := NewRegistryKey(nil) 31 | assert.NotNil(obj) 32 | assert.NoError(err) 33 | }) 34 | 35 | t.Run("payload_with_options", func(t *testing.T) { 36 | marking := make([]*GranularMarking, 0) 37 | objmark := []Identifier{Identifier("id")} 38 | specVer := "2.0" 39 | 40 | opts := []STIXOption{ 41 | OptionGranularMarking(marking), 42 | OptionObjectMarking(objmark), 43 | OptionSpecVersion(specVer), 44 | OptionDefanged(true), 45 | // 46 | OptionKey(testStr), 47 | OptionValues(val), 48 | OptionModifiedTime(ts), 49 | OptionCreatorUser(ref), 50 | OptionNumberOfSubkeys(num), 51 | } 52 | obj, err := NewRegistryKey(opts...) 53 | assert.NotNil(obj) 54 | assert.NoError(err) 55 | assert.Equal(marking, obj.GranularMarking) 56 | assert.Equal(objmark, obj.ObjectMarking) 57 | assert.Equal(specVer, obj.SpecVersion) 58 | assert.True(obj.Defanged) 59 | 60 | assert.Equal(testStr, obj.Key) 61 | assert.Equal(val, obj.Values) 62 | assert.Equal(ts, obj.ModifiedTime) 63 | assert.Equal(ref, obj.CreatorUser) 64 | assert.Equal(num, obj.NumberOfSubkeys) 65 | }) 66 | 67 | t.Run("id-generation", func(t *testing.T) { 68 | tests := []struct { 69 | key string 70 | values []*RegistryValue 71 | id string 72 | }{ 73 | { 74 | testStr, 75 | nil, 76 | "windows-registry-key--95c4c7a8-d804-505a-be56-e48cf0907412", 77 | }, 78 | { 79 | testStr, 80 | val, 81 | "windows-registry-key--a292f379-2da1-5606-a5ae-36124b43ef1b", 82 | }, 83 | } 84 | for _, test := range tests { 85 | opts := make([]STIXOption, 0, 3) 86 | if test.key != "" { 87 | opts = append(opts, OptionKey(test.key)) 88 | } 89 | if test.values != nil { 90 | opts = append(opts, OptionValues(test.values)) 91 | } 92 | obj, err := NewRegistryKey(opts...) 93 | assert.NoError(err) 94 | assert.Equal(Identifier(test.id), obj.ID) 95 | } 96 | }) 97 | 98 | t.Run("parse_json", func(t *testing.T) { 99 | data := []byte(` 100 | { 101 | "type": "windows-registry-key", 102 | "spec_version": "2.1", 103 | "id": "windows-registry-key--2ba37ae7-2745-5082-9dfd-9486dad41016", 104 | "key": "hkey_local_machine\\system\\bar\\foo", 105 | "values": [ 106 | { 107 | "name": "Foo", 108 | "data": "qwerty", 109 | "data_type": "REG_SZ" 110 | }, 111 | { 112 | "name": "Bar", 113 | "data": "42", 114 | "data_type": "REG_DWORD" 115 | } 116 | ] 117 | } 118 | `) 119 | var obj *RegistryKey 120 | err := json.Unmarshal(data, &obj) 121 | assert.NoError(err) 122 | assert.Equal(Identifier("windows-registry-key--2ba37ae7-2745-5082-9dfd-9486dad41016"), obj.ID) 123 | assert.Equal("2.1", obj.SpecVersion) 124 | assert.Equal(TypeRegistryKey, obj.Type) 125 | assert.Equal("hkey_local_machine\\system\\bar\\foo", obj.Key) 126 | assert.Len(obj.Values, 2) 127 | assert.Equal("Foo", obj.Values[0].Name) 128 | assert.Equal("qwerty", obj.Values[0].Data) 129 | assert.Equal(RegSz, obj.Values[0].DataType) 130 | assert.Equal("Bar", obj.Values[1].Name) 131 | assert.Equal("42", obj.Values[1].Data) 132 | assert.Equal(RegDword, obj.Values[1].DataType) 133 | }) 134 | } 135 | 136 | func TestRegistryDataType(t *testing.T) { 137 | assert := assert.New(t) 138 | t.Run("valid", func(t *testing.T) { 139 | data, err := RegBinary.MarshalJSON() 140 | assert.NoError(err) 141 | assert.Equal(`"REG_BINARY"`, string(data)) 142 | 143 | var actual RegistryDataType 144 | ptr := &actual 145 | err = ptr.UnmarshalJSON([]byte(`"REG_BINARY"`)) 146 | assert.NoError(err) 147 | assert.Equal(RegBinary, actual) 148 | }) 149 | t.Run("invalid", func(t *testing.T) { 150 | var actual RegistryDataType 151 | ptr := &actual 152 | err := ptr.UnmarshalJSON([]byte(`"AAAAAA"`)) 153 | assert.NoError(err) 154 | assert.Equal(RegUnknownValue, actual) 155 | }) 156 | t.Run("invalid-short", func(t *testing.T) { 157 | var actual RegistryDataType 158 | ptr := &actual 159 | err := ptr.UnmarshalJSON([]byte(`A`)) 160 | assert.NoError(err) 161 | assert.Equal(RegUnknownValue, actual) 162 | }) 163 | } 164 | -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/3daecaffffa67fd33432d27187384eafb72fadf7: -------------------------------------------------------------------------------- 1 | {"":"","":"","objects":[{ "type": "identity", 2 | "spec_version": "2.1", 3 | "id": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 4 | "created": "2016-08-08T15:50:10.983Z", 5 | "modified": "2016-08-08T15:50:10.983Z", 6 | "name": "The MITRE Corporation - DHS Support Team", 7 | "identity_class": "organization" 8 | 9 | }, 10 | { 11 | "typ": "malware", 12 | "spec_version": "2.1", 13 | "id": "malware--fdd60b30-b67c-41e3-b0b9-f01faf20d111", 14 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 15 | "created": "2017-01-27T13:49:53.997Z", 16 | "modified": "2017-01-27T13:49:53.997Z", 17 | "is_family": true, 18 | "description": "variant", 19 | "malware_types": [ 20 | "remote-access-trojan" 21 | ], 22 | "name": "Poison Ivy" 23 | }, 24 | { 25 | "type": "indicator", 26 | "spec_version": "2.1", 27 | "pattern_type": "stix", 28 | "id": "indicator--53fe3b22-0201-47cf-85d0-97c02164528d", 29 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 30 | "created": "2014-05-08T09:00:00.000Z", 31 | "modified": "2014-05-08T09:00:00.000Z", 32 | "indicator_types": [ 33 | "malicious-activity" 34 | ], 35 | "name": "IP Address for known C2 channel", 36 | "description": "Test description.", 37 | "pattern": "[ipv4-addr:value = '10.0.0.0']", 38 | "valid_from": "2014-05-08T09:00:00.000000Z" 39 | }, 40 | { 41 | "type": "indicator", 42 | "spec_version": "2.1", 43 | "pattern_type": "stix", 44 | "id": "indicator--a932fcc6-e032-476c-826f-cb970a5a1ade", 45 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 46 | "created": "2017-01-27T13:49:53.997Z", 47 | "indicator_types": [ 48 | "malicious-activity" 49 | ], 50 | "modified": "2017-01-27T13:49:53.997Z", 51 | "name": "File hash for Poison Ivy variant", 52 | "description": "Test description.", 53 | "pattern": "[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']", 54 | "valid_from": "2014-05-08T09:00:00.000000Z" 55 | }, 56 | { 57 | "type": "course-of-action", 58 | "spec_version": "2.1", 59 | "id": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", 60 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 61 | "created": "2016-04-06T20:03:48.000Z", 62 | "modified": "2016-04-06T20:03:48.000Z", 63 | "name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter", 64 | "description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..." 65 | }, 66 | { 67 | "type": "relationship", 68 | "spec_version": "2.1", 69 | "id": "relationship--44298a74-ba52-4f0c-87a3-1824e67d7fad", 70 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 71 | "created": "2016-04-06T20:06:37.000Z", 72 | "modified": "2016-04-06T20:06:37.000Z", 73 | "source_ref": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", 74 | "target_ref": "malware--fdd60b30-b67c-41e3-b0b9-f01faf20d111", 75 | "relationship_type": "mitigates" 76 | }, 77 | { 78 | "type": "relationship", 79 | "spec_version": "2.1", 80 | "id": "relationship--2f9a9aa9-108a-4333-83e2-4fb25add0463", 81 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 82 | "created": "2017-01-27T13:49:53.997Z", 83 | "modified": "2017-01-27T13:49:53.997Z", 84 | "relationship_type": "indicates", 85 | "source_ref": "indicator--a932fcc6-e032-476c-826f-cb970a5a1ade", 86 | "target_ref": "malware--fdd60b30-b67c-41e3-b0b9-f01faf20d111" 87 | 88 | }, 89 | { 90 | "type": "relationship", 91 | "spec_version": "2.1", 92 | "id": "relationship--9606dac3-965a-47d3-b270-8b17431ba0e4", 93 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 94 | "created": "2014-05-08T09:00:00.000Z", 95 | "modified": "2014-05-08T09:00:00.000Z", 96 | "relationship_type": "indicates", 97 | "source_ref": "indicator--53fe3b22-0201-47cf-85d0-97c02164528d", 98 | "target_ref": "malware--fdd60b30-b67c-41e3-b0b9-f01faf20d111" 99 | 100 | } 101 | ] 102 | } -------------------------------------------------------------------------------- /testresources/crashesCollectionAdd/e1c892f503235cdbc85963459136e400a28f5cc8: -------------------------------------------------------------------------------- 1 | {"":"","":"528d", 2 | "objects": [ 3 | { 4 | "type": "identity", 5 | "spec_version": "2.1", 6 | "id": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 7 | "created": "2016-08-08T15:50:10.983Z", 8 | "modified": "2016-08-08T15:50:10.983Z", 9 | "name": "The MITRE Corporation - DHS Support Team", 10 | "identity_class": "organization" 11 | 12 | }, 13 | { 14 | "tye": "malware", 15 | "spec_version": "2.1", 16 | "id": "malware--fdd60b30-b67c-41e3-b0b9-f01faf20d111", 17 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 18 | "created": "2017-01-27T13:49:53.997Z", 19 | "modified": "2017-01-27T13:49:53.997Z", 20 | "is_family": true, 21 | "description": "variant", 22 | "malware_types": [ 23 | "remote-access-trojan" 24 | ], 25 | "name": "Poison Ivy" 26 | }, 27 | { 28 | "type": "indicator", 29 | "spec_version": "2.1", 30 | "pattern_type": "stix", 31 | "id": "indicator--53fe3b22-0201-47cf-85d0-97c02164528d", 32 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 33 | "created": "2014-05-08T09:00:00.000Z", 34 | "modified": "2014-05-08T09:00:00.000Z", 35 | "indicator_types": [ 36 | "malicious-activity" 37 | ], 38 | "name": "IP Address for known C2 channel", 39 | "description": "Test description.", 40 | "pattern": "[ipv4-addr:value = '10.0.0.0']", 41 | "valid_from": "2014-05-08T09:00:00.000000Z" 42 | }, 43 | { 44 | "type": "indicator", 45 | "spec_version": "2.1", 46 | "pattern_type": "stix", 47 | "id": "indicator--a932fcc6-e032-476c-826f-cb970a5a1ade", 48 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 49 | "created": "2017-01-27T13:49:53.997Z", 50 | "indicator_types": [ 51 | "malicious-activity" 52 | ], 53 | "modified": "2017-01-27T13:49:53.997Z", 54 | "name": "File hash for Poison Ivy variant", 55 | "description": "Test description.", 56 | "pattern": "[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']", 57 | "valid_from": "2014-05-08T09:00:00.000000Z" 58 | }, 59 | { 60 | "type": "course-of-action", 61 | "spec_version": "2.1", 62 | "id": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", 63 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 64 | "created": "2016-04-06T20:03:48.000Z", 65 | "modified": "2016-04-06T20:03:48.000Z", 66 | "name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter", 67 | "description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..." 68 | }, 69 | { 70 | "type": "relationship", 71 | "spec_version": "2.1", 72 | "id": "relationship--44298a74-ba52-4f0c-87a3-1824e67d7fad", 73 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 74 | "created": "2016-04-06T20:06:37.000Z", 75 | "modified": "2016-04-06T20:06:37.000Z", 76 | "source_ref": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", 77 | "target_ref": "malware--fdd60b30-b67c-41e3-b0b9-f01faf20d111", 78 | "relationship_type": "mitigates" 79 | }, 80 | { 81 | "type": "relationship", 82 | "spec_version": "2.1", 83 | "id": "relationship--2f9a9aa9-108a-4333-83e2-4fb25add0463", 84 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 85 | "created": "2017-01-27T13:49:53.997Z", 86 | "modified": "2017-01-27T13:49:53.997Z", 87 | "relationship_type": "indicates", 88 | "source_ref": "indicator--a932fcc6-e032-476c-826f-cb970a5a1ade", 89 | "target_ref": "malware--fdd60b30-b67c-41e3-b0b9-f01faf20d111" 90 | 91 | }, 92 | { 93 | "type": "relationship", 94 | "spec_version": "2.1", 95 | "id": "relationship--9606dac3-965a-47d3-b270-8b17431ba0e4", 96 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 97 | "created": "2014-05-08T09:00:00.000Z", 98 | "modified": "2014-05-08T09:00:00.000Z", 99 | "relationship_type": "indicates", 100 | "source_ref": "indicator--53fe3b22-0201-47cf-85d0-97c02164528d", 101 | "target_ref": "malware--fdd60b30-b67c-41e3-b0b9-f01faf20d111" 102 | 103 | } 104 | ] 105 | } -------------------------------------------------------------------------------- /artifact.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "strings" 9 | ) 10 | 11 | // Artifact object permits capturing an array of bytes (8-bits), as a 12 | // base64-encoded string, or linking to a file-like payload. One of payload_bin 13 | // or url MUST be provided. It is incumbent on object creators to ensure that 14 | // the URL is accessible for downstream consumers. 15 | type Artifact struct { 16 | STIXCyberObservableObject 17 | // MimeType should, whenever feasible, be one of the values defined in the 18 | // Template column in the IANA media type registry. Maintaining a 19 | // comprehensive universal catalog of all extant file types is obviously 20 | // not possible. When specifying a MIME Type not included in the IANA 21 | // registry, implementers should use their best judgement so as to 22 | // facilitate interoperability. 23 | MimeType string `json:"mime_type,omitempty"` 24 | // Payload specifies the binary data contained in the artifact. This 25 | // property MUST NOT be present if url is provided. 26 | Payload Binary `json:"payload_bin,omitempty"` 27 | // URL a valid URL that resolves to the unencoded content. This property 28 | // MUST NOT be present if Payload is provided. 29 | URL string `json:"url,omitempty"` 30 | // Hashes are hashes for the contents of the URL or the Payload. 31 | // This property MUST be present when the url property is present. 32 | Hashes Hashes `json:"hashes,omitempty"` 33 | // Encryption is used if the artifact is encrypted, specifies the type of 34 | // encryption algorithm the binary data is encoded in. 35 | Encryption EncryptionAlgorithm `json:"encryption_algorithm,omitempty"` 36 | // Key specifies the decryption key for the encrypted binary data. For 37 | // example, this may be useful in cases of sharing malware samples, which 38 | // are often encoded in an encrypted archive. 39 | Key string `json:"decryption_key,omitempty"` 40 | } 41 | 42 | func (o *Artifact) MarshalJSON() ([]byte, error) { 43 | return marshalToJSONHelper(o) 44 | } 45 | 46 | // NewArtifact creates a new Artifact object. 47 | func NewArtifact(opts ...STIXOption) (*Artifact, error) { 48 | base := newSTIXCyberObservableObject(TypeArtifact) 49 | obj := &Artifact{ 50 | STIXCyberObservableObject: base, 51 | } 52 | 53 | err := applyOptions(obj, opts) 54 | if err != nil { 55 | return nil, err 56 | } 57 | 58 | if obj.Payload == nil && obj.URL == "" { 59 | return nil, ErrPropertyMissing 60 | } 61 | if obj.Payload != nil && obj.URL != "" { 62 | return nil, ErrInvalidParameter 63 | } 64 | if obj.URL != "" && obj.Hashes == nil { 65 | return nil, ErrPropertyMissing 66 | } 67 | 68 | contriStr := []string{} 69 | if len(obj.Hashes) != 0 { 70 | contriStr = append(contriStr, obj.Hashes.getIDContribution()) 71 | } 72 | if len(obj.Payload) != 0 { 73 | contriStr = append(contriStr, `"`+obj.Payload.String()+`"`) 74 | } 75 | obj.ID = NewObservableIdentifier("["+strings.Join(contriStr, ",")+"]", TypeArtifact) 76 | return obj, nil 77 | } 78 | 79 | // EncryptionAlgorithm is the encryption algorithms used for sharing defanged 80 | // and/or confidential artifacts. 81 | type EncryptionAlgorithm uint8 82 | 83 | // String returns the string representation of the type. 84 | func (typ EncryptionAlgorithm) String() string { 85 | return encAlgMap[typ] 86 | } 87 | 88 | // MarshalJSON converts the enum type to the JSON string. 89 | func (typ EncryptionAlgorithm) MarshalJSON() ([]byte, error) { 90 | return json.Marshal(typ.String()) 91 | } 92 | 93 | // UnmarshalJSON extracts the encryption algorithm from the json data. 94 | func (typ *EncryptionAlgorithm) UnmarshalJSON(b []byte) error { 95 | t := string(b[1 : len(b)-1]) 96 | for k, v := range encAlgMap { 97 | if v == t { 98 | *typ = k 99 | return nil 100 | } 101 | } 102 | *typ = EncryptionAlgorithmNone 103 | return nil 104 | } 105 | 106 | const ( 107 | // EncryptionAlgorithmNone no encryption is used. 108 | EncryptionAlgorithmNone EncryptionAlgorithm = iota 109 | // EncryptionAlgorithmAES256GCM the AES-256-GCM cipher. 110 | EncryptionAlgorithmAES256GCM 111 | // EncryptionAlgorithmChaCha20Poly1305 the ChaCha20-Poly1305 stream 112 | // cipher. 113 | EncryptionAlgorithmChaCha20Poly1305 114 | // EncryptionAlgorithmMimeTypeIndicated mean encryption algorithm is 115 | // self-defined by the artifact's data. The specified mime-type tells you 116 | // which format it is, e.g., Word Doc or GPG. This is intended for formats 117 | // like Zip files and Word files which take a simple password, or GPG 118 | // armored files that contain the key blob along with the file. 119 | EncryptionAlgorithmMimeTypeIndicated 120 | ) 121 | 122 | var encAlgMap = map[EncryptionAlgorithm]string{ 123 | EncryptionAlgorithmNone: "", 124 | EncryptionAlgorithmAES256GCM: "AES-256-GCM", 125 | EncryptionAlgorithmChaCha20Poly1305: "ChaCha20-Poly1305", 126 | EncryptionAlgorithmMimeTypeIndicated: "mime-type-indicated", 127 | } 128 | -------------------------------------------------------------------------------- /opinion.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import "fmt" 7 | 8 | // Opinion is an assessment of the correctness of the information in a STIX 9 | // Object produced by a different entity. The primary property is the opinion 10 | // property, which captures the level of agreement or disagreement using a 11 | // fixed scale. That fixed scale also supports a numeric mapping to allow for 12 | // consistent statistical operations across opinions. 13 | // 14 | // For example, an analyst from a consuming organization might say that they 15 | // "strongly disagree" with a Campaign object and provide an explanation about 16 | // why. In a more automated workflow, a SOC operator might give an Indicator 17 | // "one star" in their TIP (expressing "strongly disagree") because it is 18 | // considered to be a false positive within their environment. Opinions are 19 | // subjective, and the specification does not address how best to interpret 20 | // them. Sharing communities are encouraged to provide clear guidelines to 21 | // their constituents regarding best practice for the use of Opinion objects 22 | // within the community. 23 | // 24 | // Because Opinions are typically (though not always) created by human analysts 25 | // and are comprised of human-oriented text, they contain an additional 26 | // property to capture the analyst(s) that created the Opinion. This is 27 | // distinct from the CreatedBy property, which is meant to capture the 28 | // organization that created the object. 29 | type Opinion struct { 30 | STIXDomainObject 31 | // Explanation is an explanation of why the producer has this Opinion. For 32 | // example, if an Opinion of strongly-disagree is given, the explanation 33 | // can contain an explanation of why the Opinion producer disagrees and 34 | // what evidence they have for their disagreement. 35 | Explanation string `json:"explanation,omitempty"` 36 | // Authors is the name of the author(s) of this Opinion (e.g., the 37 | // analyst(s) that created it). 38 | Authors []string `json:"authors,omitempty"` 39 | // Value is the opinion that the producer has about all of the STIX 40 | // Object(s) listed in the Objects property. 41 | Value OpinionValue `json:"opinion"` 42 | // Objects is the STIX Objects that the Opinion is being applied to. 43 | Objects []Identifier `json:"object_refs"` 44 | } 45 | 46 | func (o *Opinion) MarshalJSON() ([]byte, error) { 47 | return marshalToJSONHelper(o) 48 | } 49 | 50 | // NewOpinion creates a new Opinion object. 51 | func NewOpinion(val OpinionValue, objects []Identifier, opts ...STIXOption) (*Opinion, error) { 52 | if len(objects) == 0 { 53 | return nil, ErrPropertyMissing 54 | } 55 | base := newSTIXDomainObject(TypeOpinion) 56 | obj := &Opinion{ 57 | STIXDomainObject: base, 58 | Value: val, 59 | Objects: objects, 60 | } 61 | 62 | err := applyOptions(obj, opts) 63 | return obj, err 64 | } 65 | 66 | // OpinionValue aptures a degree of agreement with the information in a STIX 67 | // Object. It is an ordered enumeration, with the earlier terms representing 68 | // disagreement, the middle term neutral, and the later terms representing 69 | // agreement. 70 | type OpinionValue byte 71 | 72 | const ( 73 | // OpinionStronglyDisagree means the creator strongly disagrees with the 74 | // information and believes it is inaccurate or incorrect. 75 | OpinionStronglyDisagree OpinionValue = iota + 1 76 | // OpinionDisagree means the creator disagrees with the information and 77 | // believes it is inaccurate or incorrect. 78 | OpinionDisagree 79 | // OpinionNeutral means the creator is neutral about the accuracy or 80 | // correctness of the information. 81 | OpinionNeutral 82 | // OpinionAgree means the creator agrees with the information and believes 83 | // that it is accurate and correct. 84 | OpinionAgree 85 | // OpinionStronglyAgree means the creator strongly agrees with the 86 | // information and believes that it is accurate and correct. 87 | OpinionStronglyAgree 88 | ) 89 | 90 | var opinionValueMap = map[OpinionValue]string{ 91 | OpinionStronglyDisagree: "strongly-disagree", 92 | OpinionDisagree: "disagree", 93 | OpinionNeutral: "neutral", 94 | OpinionAgree: "agree", 95 | OpinionStronglyAgree: "strongly-agree", 96 | } 97 | 98 | // String returns the string representation of the OpinionValue. 99 | func (typ OpinionValue) String() string { 100 | val, ok := opinionValueMap[typ] 101 | if !ok { 102 | return "" 103 | } 104 | return val 105 | } 106 | 107 | func (typ OpinionValue) MarshalJSON() ([]byte, error) { 108 | return []byte(fmt.Sprintf(`"%s"`, typ.String())), nil 109 | } 110 | 111 | // UnmarshalJSON extracts the OpinionValue from the json data. 112 | func (typ *OpinionValue) UnmarshalJSON(b []byte) error { 113 | t := string(b[1 : len(b)-1]) 114 | for k, v := range opinionValueMap { 115 | if v == t { 116 | *typ = k 117 | return nil 118 | } 119 | } 120 | *typ = OpinionValue(0) 121 | return nil 122 | } 123 | -------------------------------------------------------------------------------- /testresources/examples/indicators-for-C2-with-COA.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "bundle", 3 | "id": "bundle--63fe3b22-0201-47cf-85d0-97c02164528d", 4 | "objects": [ 5 | { 6 | "type": "identity", 7 | "spec_version": "2.1", 8 | "id": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 9 | "created": "2016-08-08T15:50:10.983Z", 10 | "modified": "2016-08-08T15:50:10.983Z", 11 | "name": "The MITRE Corporation - DHS Support Team", 12 | "identity_class": "organization" 13 | 14 | }, 15 | { 16 | "type": "malware", 17 | "spec_version": "2.1", 18 | "id": "malware--fdd60b30-b67c-41e3-b0b9-f01faf20d111", 19 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 20 | "created": "2017-01-27T13:49:53.997Z", 21 | "modified": "2017-01-27T13:49:53.997Z", 22 | "is_family": true, 23 | "description": "variant", 24 | "malware_types": [ 25 | "remote-access-trojan" 26 | ], 27 | "name": "Poison Ivy" 28 | }, 29 | { 30 | "type": "indicator", 31 | "spec_version": "2.1", 32 | "pattern_type": "stix", 33 | "id": "indicator--53fe3b22-0201-47cf-85d0-97c02164528d", 34 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 35 | "created": "2014-05-08T09:00:00.000Z", 36 | "modified": "2014-05-08T09:00:00.000Z", 37 | "indicator_types": [ 38 | "malicious-activity" 39 | ], 40 | "name": "IP Address for known C2 channel", 41 | "description": "Test description.", 42 | "pattern": "[ipv4-addr:value = '10.0.0.0']", 43 | "valid_from": "2014-05-08T09:00:00.000000Z" 44 | }, 45 | { 46 | "type": "indicator", 47 | "spec_version": "2.1", 48 | "pattern_type": "stix", 49 | "id": "indicator--a932fcc6-e032-476c-826f-cb970a5a1ade", 50 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 51 | "created": "2017-01-27T13:49:53.997Z", 52 | "indicator_types": [ 53 | "malicious-activity" 54 | ], 55 | "modified": "2017-01-27T13:49:53.997Z", 56 | "name": "File hash for Poison Ivy variant", 57 | "description": "Test description.", 58 | "pattern": "[file:hashes.'SHA-256' = 'ef537f25c895bfa782526529a9b63d97aa631564d5d789c2b765448c8635fb6c']", 59 | "valid_from": "2014-05-08T09:00:00.000000Z" 60 | }, 61 | { 62 | "type": "course-of-action", 63 | "spec_version": "2.1", 64 | "id": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", 65 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 66 | "created": "2016-04-06T20:03:48.000Z", 67 | "modified": "2016-04-06T20:03:48.000Z", 68 | "name": "Add TCP port 80 Filter Rule to the existing Block UDP 1434 Filter", 69 | "description": "This is how to add a filter rule to block inbound access to TCP port 80 to the existing UDP 1434 filter ..." 70 | }, 71 | { 72 | "type": "relationship", 73 | "spec_version": "2.1", 74 | "id": "relationship--44298a74-ba52-4f0c-87a3-1824e67d7fad", 75 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 76 | "created": "2016-04-06T20:06:37.000Z", 77 | "modified": "2016-04-06T20:06:37.000Z", 78 | "source_ref": "course-of-action--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f", 79 | "target_ref": "malware--fdd60b30-b67c-41e3-b0b9-f01faf20d111", 80 | "relationship_type": "mitigates" 81 | }, 82 | { 83 | "type": "relationship", 84 | "spec_version": "2.1", 85 | "id": "relationship--2f9a9aa9-108a-4333-83e2-4fb25add0463", 86 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 87 | "created": "2017-01-27T13:49:53.997Z", 88 | "modified": "2017-01-27T13:49:53.997Z", 89 | "relationship_type": "indicates", 90 | "source_ref": "indicator--a932fcc6-e032-476c-826f-cb970a5a1ade", 91 | "target_ref": "malware--fdd60b30-b67c-41e3-b0b9-f01faf20d111" 92 | 93 | }, 94 | { 95 | "type": "relationship", 96 | "spec_version": "2.1", 97 | "id": "relationship--9606dac3-965a-47d3-b270-8b17431ba0e4", 98 | "created_by_ref": "identity--f690c992-8e7d-4b9a-9303-3312616c0220", 99 | "created": "2014-05-08T09:00:00.000Z", 100 | "modified": "2014-05-08T09:00:00.000Z", 101 | "relationship_type": "indicates", 102 | "source_ref": "indicator--53fe3b22-0201-47cf-85d0-97c02164528d", 103 | "target_ref": "malware--fdd60b30-b67c-41e3-b0b9-f01faf20d111" 104 | 105 | } 106 | ] 107 | } 108 | -------------------------------------------------------------------------------- /campaign.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import "fmt" 7 | 8 | // Campaign is a grouping of adversarial behaviors that describes a set of 9 | // malicious activities or attacks (sometimes called waves) that occur over a 10 | // period of time against a specific set of targets. Campaigns usually have 11 | // well defined objectives and may be part of an Intrusion Set. Campaigns are 12 | // often attributed to an intrusion set and threat actors. The threat actors 13 | // may reuse known infrastructure from the intrusion set or may set up new 14 | // infrastructure specific for conducting that campaign. Campaigns can be 15 | // characterized by their objectives and the incidents they cause, people or 16 | // resources they target, and the resources (infrastructure, intelligence, 17 | // Malware, Tools, etc.) they use. For example, a Campaign could be used to 18 | // describe a crime syndicate's attack using a specific variant of malware and 19 | // new C2 servers against the executives of ACME Bank during the summer of 2016 20 | // in order to gain secret information about an upcoming merger with another 21 | // bank. 22 | type Campaign struct { 23 | STIXDomainObject 24 | // Name used to identify the Campaign. 25 | Name string `json:"name"` 26 | // Description provides more details and context about the Campaign, 27 | // potentially including its purpose and its key characteristics. 28 | Description string `json:"description,omitempty"` 29 | // Aliases are a lternative names used to identify this Campaign 30 | Aliases []string `json:"aliases,omitempty"` 31 | // FirstSeen is the time that this Campaign was first seen. 32 | FirstSeen *Timestamp `json:"first_seen,omitempty"` 33 | // LastSeen is the time that this Campaign was last seen. 34 | LastSeen *Timestamp `json:"last_seen,omitempty"` 35 | // Objective defines the Campaign’s primary goal, objective, desired 36 | // outcome, or intended effect — what the Threat Actor or Intrusion Set 37 | // hopes to accomplish with this Campaign. 38 | Objective string `json:"objective,omitempty"` 39 | } 40 | 41 | func (o *Campaign) MarshalJSON() ([]byte, error) { 42 | return marshalToJSONHelper(o) 43 | } 44 | 45 | // AddTargets creates a relationship to either an identity, location, or 46 | // vulnerability that is targeted by this campaign. 47 | func (c *Campaign) AddTargets(id Identifier, opts ...STIXOption) (*Relationship, error) { 48 | if !IsValidIdentifier(id) || (!id.ForType(TypeLocation) && 49 | !id.ForType(TypeIdentity)) && !id.ForType(TypeVulnerability) { 50 | return nil, ErrInvalidParameter 51 | } 52 | return NewRelationship(RelationshipTypeTargets, c.ID, id, opts...) 53 | } 54 | 55 | // AddUses creates a relationship to either a malware or tool that is used by 56 | // the campaign 57 | func (c *Campaign) AddUses(id Identifier, opts ...STIXOption) (*Relationship, error) { 58 | if !IsValidIdentifier(id) || (!id.ForType(TypeAttackPattern) && !id.ForType(TypeInfrastructure) && !id.ForType(TypeMalware) && !id.ForType(TypeTool)) { 59 | return nil, ErrInvalidParameter 60 | } 61 | return NewRelationship(RelationshipTypeUses, c.ID, id, opts...) 62 | } 63 | 64 | // AddAttributedTo creates a relationship to either an intrusion set or a 65 | // threat actor that is attributed to the campaign. 66 | func (c *Campaign) AddAttributedTo(id Identifier, opts ...STIXOption) (*Relationship, error) { 67 | if !IsValidIdentifier(id) || (!id.ForType(TypeIntrusionSet) && !id.ForType(TypeThreatActor)) { 68 | return nil, ErrInvalidParameter 69 | } 70 | return NewRelationship(RelationshipTypeAttributedTo, c.ID, id, opts...) 71 | } 72 | 73 | // AddCompromises creates a relationship to an infrastructure that is 74 | // compromised as part of the campaign. 75 | func (c *Campaign) AddCompromises(id Identifier, opts ...STIXOption) (*Relationship, error) { 76 | if !IsValidIdentifier(id) || !id.ForType(TypeInfrastructure) { 77 | return nil, ErrInvalidParameter 78 | } 79 | return NewRelationship(RelationshipTypeCompromises, c.ID, id, opts...) 80 | } 81 | 82 | // AddOriginatesFrom creates a relationship to a location that the campaign 83 | // originates from the related location. 84 | func (c *Campaign) AddOriginatesFrom(id Identifier, opts ...STIXOption) (*Relationship, error) { 85 | if !IsValidIdentifier(id) || !id.ForType(TypeLocation) { 86 | return nil, ErrInvalidParameter 87 | } 88 | return NewRelationship(RelationshipTypeOriginatesFrom, c.ID, id, opts...) 89 | } 90 | 91 | // NewCampaign creates a new Campaign object. 92 | func NewCampaign(name string, opts ...STIXOption) (*Campaign, error) { 93 | if name == "" { 94 | return nil, ErrPropertyMissing 95 | } 96 | base := newSTIXDomainObject(TypeCampaign) 97 | obj := &Campaign{STIXDomainObject: base, Name: name} 98 | 99 | err := applyOptions(obj, opts) 100 | 101 | if (obj.FirstSeen != nil && obj.LastSeen != nil) && obj.FirstSeen.After(obj.LastSeen.Time) { 102 | return nil, fmt.Errorf("%w: Last seen (%s) is before first seen (%s)", ErrInvalidProperty, obj.LastSeen, obj.FirstSeen) 103 | } 104 | 105 | return obj, err 106 | } 107 | -------------------------------------------------------------------------------- /location_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestLocation(t *testing.T) { 15 | assert := assert.New(t) 16 | 17 | region := RegionNorthernAmerica 18 | country := "us" 19 | lat := float64(1) 20 | long := float64(-15) 21 | 22 | t.Run("missing_property", func(t *testing.T) { 23 | obj, err := NewLocation("", "", float64(0), float64(0)) 24 | assert.Nil(obj) 25 | assert.Equal(ErrPropertyMissing, err) 26 | }) 27 | 28 | t.Run("no_optional", func(t *testing.T) { 29 | obj, err := NewLocation(region, country, lat, long, nil) 30 | assert.NotNil(obj) 31 | assert.NoError(err) 32 | }) 33 | 34 | t.Run("with_options", func(t *testing.T) { 35 | conf := 50 36 | desc := "My description" 37 | ts := &Timestamp{time.Now()} 38 | createdBy := NewIdentifier(TypeIdentity) 39 | ref := &ExternalReference{} 40 | marking := make([]*GranularMarking, 0) 41 | labels := []string{"tag1", "tag2"} 42 | lang := "en" 43 | objmark := []Identifier{Identifier("id")} 44 | specVer := "2.0" 45 | 46 | name := "new name" 47 | precision := float64(100.2) 48 | adminArea := "XX" 49 | city := "City" 50 | address := "123 Main street" 51 | postal := "12346" 52 | 53 | opts := []STIXOption{ 54 | OptionConfidence(conf), 55 | OptionCreated(ts), 56 | OptionModified(ts), 57 | OptionCreatedBy(createdBy), 58 | OptionExternalReferences([]*ExternalReference{ref}), 59 | OptionGranularMarking(marking), 60 | OptionLabels(labels), 61 | OptionLang(lang), 62 | OptionObjectMarking(objmark), 63 | OptionRevoked(true), 64 | OptionSpecVersion(specVer), 65 | // 66 | OptionDescription(desc), 67 | OptionName(name), 68 | OptionPrecision(precision), 69 | OptionAdministrativeArea(adminArea), 70 | OptionCity(city), 71 | OptionStreetAddress(address), 72 | OptionPostalCode(postal), 73 | } 74 | obj, err := NewLocation(region, country, lat, long, opts...) 75 | assert.NotNil(obj) 76 | assert.NoError(err) 77 | assert.Equal(conf, obj.Confidence) 78 | assert.Equal(ts, obj.Created) 79 | assert.Equal(ts, obj.Modified) 80 | assert.Equal(createdBy, obj.CreatedBy) 81 | assert.Contains(obj.ExternalReferences, ref) 82 | assert.Equal(marking, obj.GranularMarking) 83 | assert.Equal(labels, obj.Labels) 84 | assert.Equal(lang, obj.Lang) 85 | assert.Equal(objmark, obj.ObjectMarking) 86 | assert.True(obj.Revoked) 87 | assert.Equal(specVer, obj.SpecVersion) 88 | 89 | assert.Equal(desc, obj.Description) 90 | assert.Equal(name, obj.Name) 91 | assert.Equal(precision, obj.Precision) 92 | assert.Equal(adminArea, obj.AdministrativeArea) 93 | assert.Equal(city, obj.City) 94 | assert.Equal(address, obj.StreetAddress) 95 | assert.Equal(postal, obj.PostalCode) 96 | }) 97 | 98 | t.Run("lat-long-precision-validation", func(t *testing.T) { 99 | tests := []struct { 100 | lat float64 101 | long float64 102 | precision float64 103 | err error 104 | }{ 105 | {float64(0), float64(0), float64(0), nil}, 106 | {float64(10), float64(0), float64(0), ErrInvalidProperty}, 107 | {float64(0), float64(10), float64(0), ErrInvalidProperty}, 108 | {float64(-91), float64(10), float64(0), ErrInvalidProperty}, 109 | {float64(10), float64(-181), float64(0), ErrInvalidProperty}, 110 | {float64(91), float64(10), float64(0), ErrInvalidProperty}, 111 | {float64(10), float64(181), float64(0), ErrInvalidProperty}, 112 | {float64(90), float64(180), float64(0), nil}, 113 | {float64(-90), float64(-180), float64(0), nil}, 114 | {float64(0), float64(180), float64(10.1), ErrInvalidProperty}, 115 | {float64(90), float64(0), float64(10.1), ErrInvalidProperty}, 116 | {float64(0), float64(0), float64(10.1), ErrInvalidProperty}, 117 | } 118 | for _, test := range tests { 119 | _, err := NewLocation(region, country, test.lat, test.long, OptionPrecision(test.precision)) 120 | assert.Equal(test.err, err) 121 | } 122 | }) 123 | 124 | t.Run("parse_json", func(t *testing.T) { 125 | data := []byte(`{ 126 | "type": "location", 127 | "spec_version": "2.1", 128 | "id": "location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64", 129 | "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", 130 | "created": "2016-04-06T20:03:00.000Z", 131 | "modified": "2016-04-06T20:03:00.000Z", 132 | "latitude": 48.8566, 133 | "longitude": 2.3522 134 | }`) 135 | ts, err := time.Parse(time.RFC3339Nano, "2016-04-06T20:03:00.000Z") 136 | assert.NoError(err) 137 | var obj *Location 138 | err = json.Unmarshal(data, &obj) 139 | assert.NoError(err) 140 | assert.Equal(Identifier("location--a6e9345f-5a15-4c29-8bb3-7dcc5d168d64"), obj.ID) 141 | assert.Equal("2.1", obj.SpecVersion) 142 | assert.Equal(TypeLocation, obj.Type) 143 | assert.Equal(ts, obj.Created.Time) 144 | assert.Equal(ts, obj.Modified.Time) 145 | assert.Equal(float64(48.8566), obj.Latitude) 146 | assert.Equal(float64(2.3522), obj.Longitude) 147 | }) 148 | } 149 | -------------------------------------------------------------------------------- /sighting_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestSighting(t *testing.T) { 15 | assert := assert.New(t) 16 | 17 | data := NewIdentifier(TypeIPv4Addr) 18 | indicator := NewIdentifier(TypeIndicator) 19 | 20 | t.Run("missing_property", func(t *testing.T) { 21 | r, err := NewSighting("") 22 | assert.Nil(r) 23 | assert.Equal(ErrPropertyMissing, err) 24 | }) 25 | 26 | t.Run("no_optional", func(t *testing.T) { 27 | r, err := NewSighting(indicator, nil) 28 | assert.NotNil(r) 29 | assert.NoError(err) 30 | }) 31 | 32 | t.Run("with_options", func(t *testing.T) { 33 | conf := 50 34 | desc := "My description" 35 | ts := &Timestamp{time.Now()} 36 | createdBy := NewIdentifier(TypeIdentity) 37 | ref := &ExternalReference{} 38 | marking := make([]*GranularMarking, 0) 39 | labels := []string{"tag1", "tag2"} 40 | lang := "en" 41 | objmark := []Identifier{Identifier("id")} 42 | specVer := "2.0" 43 | 44 | count := int64(50) 45 | obsData := []Identifier{data} 46 | ws := []Identifier{createdBy} 47 | 48 | opts := []STIXOption{ 49 | OptionConfidence(conf), 50 | OptionDescription(desc), 51 | OptionCreated(ts), 52 | OptionModified(ts), 53 | OptionCreatedBy(createdBy), 54 | OptionExternalReferences([]*ExternalReference{ref}), 55 | OptionGranularMarking(marking), 56 | OptionLabels(labels), 57 | OptionLang(lang), 58 | OptionObjectMarking(objmark), 59 | OptionRevoked(true), 60 | OptionSpecVersion(specVer), 61 | OptionFirstSeen(ts), 62 | OptionLastSeen(ts), 63 | OptionCount(count), 64 | OptionObservedData(obsData), 65 | OptionWhereSighted(ws), 66 | OptionSummary(true), 67 | } 68 | r, err := NewSighting(indicator, opts...) 69 | assert.NotNil(r) 70 | assert.NoError(err) 71 | assert.Equal(conf, r.Confidence) 72 | assert.Equal(desc, r.Description) 73 | assert.Equal(ts, r.Created) 74 | assert.Equal(ts, r.Modified) 75 | assert.Equal(ts, r.FirstSeen) 76 | assert.Equal(ts, r.LastSeen) 77 | assert.Equal(createdBy, r.CreatedBy) 78 | assert.Contains(r.ExternalReferences, ref) 79 | assert.Equal(marking, r.GranularMarking) 80 | assert.Equal(labels, r.Labels) 81 | assert.Equal(lang, r.Lang) 82 | assert.Equal(objmark, r.ObjectMarking) 83 | assert.True(r.Revoked) 84 | assert.Equal(specVer, r.SpecVersion) 85 | 86 | assert.Equal(count, r.Count) 87 | assert.Equal(obsData, r.ObservedData) 88 | assert.Equal(ws, r.WhereSighted) 89 | assert.True(r.Summary) 90 | }) 91 | 92 | t.Run("validate_count", func(t *testing.T) { 93 | tests := []struct { 94 | count int64 95 | err bool 96 | }{ 97 | {int64(-1), true}, 98 | {int64(0), false}, 99 | {int64(100), false}, 100 | {int64(999999999), false}, 101 | {int64(999999999 + 1), true}, 102 | } 103 | for _, test := range tests { 104 | obj, err := NewSighting(indicator, OptionCount(test.count)) 105 | if test.err { 106 | assert.Error(err) 107 | assert.Nil(obj) 108 | } else { 109 | assert.NoError(err) 110 | assert.NotNil(obj) 111 | } 112 | } 113 | }) 114 | 115 | t.Run("validate_first_last_seen", func(t *testing.T) { 116 | early := &Timestamp{time.Now()} 117 | later := &Timestamp{early.Add(10 * time.Second)} 118 | tests := []struct { 119 | before *Timestamp 120 | after *Timestamp 121 | err bool 122 | }{ 123 | {early, later, false}, 124 | {early, early, false}, 125 | {later, early, true}, 126 | } 127 | for _, test := range tests { 128 | obj, err := NewSighting(indicator, OptionFirstSeen(test.before), OptionLastSeen(test.after)) 129 | if test.err { 130 | assert.Error(err) 131 | assert.Nil(obj) 132 | } else { 133 | assert.NoError(err) 134 | assert.NotNil(obj) 135 | } 136 | } 137 | }) 138 | 139 | t.Run("json_parsing", func(t *testing.T) { 140 | ct, err := time.Parse(time.RFC3339Nano, "2016-04-06T20:08:31.000Z") 141 | assert.NoError(err) 142 | mt, err := time.Parse(time.RFC3339Nano, "2016-04-06T20:08:31.000Z") 143 | assert.NoError(err) 144 | data := []byte(`{ 145 | "type": "sighting", "spec_version": "2.1", "id": 146 | "sighting--ee20065d-2555-424f-ad9e-0f8428623c75", "created_by_ref": 147 | "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", "created": 148 | "2016-04-06T20:08:31.000Z", "modified": "2016-04-06T20:08:31.000Z", 149 | "sighting_of_ref": "indicator--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f"}`) 150 | var obj *Sighting 151 | err = json.Unmarshal(data, &obj) 152 | assert.NoError(err) 153 | assert.Equal(TypeSighting, obj.Type) 154 | assert.Equal("2.1", obj.SpecVersion) 155 | assert.Equal(Identifier("sighting--ee20065d-2555-424f-ad9e-0f8428623c75"), obj.ID) 156 | assert.Equal(Identifier("identity--f431f809-377b-45e0-aa1c-6a4751cae5ff"), obj.CreatedBy) 157 | assert.Equal(ct, obj.Created.Time) 158 | assert.Equal(mt, obj.Modified.Time) 159 | assert.Equal(Identifier("indicator--8e2e2d2b-17d4-4cbf-938f-98ee46b3cd3f"), obj.SightingOf) 160 | }) 161 | } 162 | -------------------------------------------------------------------------------- /artifact_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func TestArtifact(t *testing.T) { 14 | assert := assert.New(t) 15 | 16 | payload := Binary([]byte("Hello World")) 17 | mime := "mime/type" 18 | url := "url://" 19 | hashes := Hashes{"SHA-256": "2cbb138d4097f05fffeb968b34a4e62884fc4755d7d043e2d3760950f1e1a9ee", "MD5": "0da609bd9ec46237557373f1c5cfcae9"} 20 | key := "key" 21 | 22 | t.Run("missing_property", func(t *testing.T) { 23 | obj, err := NewArtifact(nil) 24 | assert.Nil(obj) 25 | assert.Equal(ErrPropertyMissing, err) 26 | }) 27 | 28 | t.Run("url_and_payload", func(t *testing.T) { 29 | obj, err := NewArtifact(OptionURL(url), OptionPayload(payload)) 30 | assert.Nil(obj) 31 | assert.Equal(err, ErrInvalidParameter) 32 | }) 33 | 34 | t.Run("url_and_no_hash", func(t *testing.T) { 35 | obj, err := NewArtifact(OptionURL(url)) 36 | assert.Nil(obj) 37 | assert.Equal(err, ErrPropertyMissing) 38 | }) 39 | 40 | t.Run("url_and_hash", func(t *testing.T) { 41 | obj, err := NewArtifact(OptionURL(url), OptionHashes(hashes)) 42 | assert.NoError(err) 43 | assert.Equal(url, obj.URL) 44 | }) 45 | 46 | t.Run("payload_with_options", func(t *testing.T) { 47 | marking := make([]*GranularMarking, 0) 48 | objmark := []Identifier{Identifier("id")} 49 | specVer := "2.0" 50 | 51 | opts := []STIXOption{ 52 | OptionGranularMarking(marking), 53 | OptionObjectMarking(objmark), 54 | OptionSpecVersion(specVer), 55 | OptionDefanged(true), 56 | // 57 | OptionMimeType(mime), 58 | OptionPayload(payload), 59 | OptionHashes(hashes), 60 | OptionEncryption(EncryptionAlgorithmAES256GCM), 61 | OptionKey(key), 62 | } 63 | obj, err := NewArtifact(opts...) 64 | assert.NotNil(obj) 65 | assert.NoError(err) 66 | assert.Equal(marking, obj.GranularMarking) 67 | assert.Equal(objmark, obj.ObjectMarking) 68 | assert.Equal(specVer, obj.SpecVersion) 69 | assert.True(obj.Defanged) 70 | 71 | assert.Equal(mime, obj.MimeType) 72 | assert.Equal(payload, obj.Payload) 73 | assert.Equal(hashes, obj.Hashes) 74 | assert.Equal(EncryptionAlgorithmAES256GCM, obj.Encryption) 75 | assert.Equal(key, obj.Key) 76 | }) 77 | 78 | t.Run("id-generation", func(t *testing.T) { 79 | tests := []struct { 80 | payload Binary 81 | hashes Hashes 82 | id string 83 | }{ 84 | {payload, nil, "artifact--2640de4e-9baf-5a92-8f14-dcc7628ec983"}, 85 | {payload, hashes, "artifact--ead91fe9-8a76-5413-9feb-94292e1622ea"}, 86 | {nil, hashes, "artifact--e9ad4fc1-2f44-538d-98c5-5e226ea95501"}, 87 | } 88 | for _, test := range tests { 89 | var arg STIXOption 90 | if test.payload == nil { 91 | arg = OptionURL(url) 92 | } else { 93 | arg = OptionPayload(test.payload) 94 | } 95 | obj, err := NewArtifact( 96 | arg, 97 | OptionHashes(test.hashes), 98 | ) 99 | assert.NoError(err) 100 | assert.Equal(Identifier(test.id), obj.ID) 101 | } 102 | }) 103 | 104 | t.Run("parse_json", func(t *testing.T) { 105 | data := []byte(`{ 106 | "type": "artifact", 107 | "spec_version": "2.1", 108 | "id": "artifact--6f437177-6e48-5cf8-9d9e-872a2bddd641", 109 | "mime_type": "application/zip", 110 | "payload_bin": "SGVsbG8gV29ybGQ=", 111 | "encryption_algorithm": "mime-type-indicated", 112 | "decryption_key": "My voice is my passport" 113 | }`) 114 | var obj *Artifact 115 | err := json.Unmarshal(data, &obj) 116 | assert.NoError(err) 117 | assert.Equal(Identifier("artifact--6f437177-6e48-5cf8-9d9e-872a2bddd641"), obj.ID) 118 | assert.Equal("2.1", obj.SpecVersion) 119 | assert.Equal(TypeArtifact, obj.Type) 120 | assert.Equal("application/zip", obj.MimeType) 121 | assert.Equal("SGVsbG8gV29ybGQ=", obj.Payload.String()) 122 | assert.Equal(EncryptionAlgorithmMimeTypeIndicated, obj.Encryption) 123 | assert.Equal("My voice is my passport", obj.Key) 124 | }) 125 | } 126 | 127 | func TestEncryptionAlgorithm(t *testing.T) { 128 | assert := assert.New(t) 129 | 130 | t.Run("stringer", func(t *testing.T) { 131 | tests := []struct { 132 | expected string 133 | alg EncryptionAlgorithm 134 | }{ 135 | {"AES-256-GCM", EncryptionAlgorithmAES256GCM}, 136 | {"ChaCha20-Poly1305", EncryptionAlgorithmChaCha20Poly1305}, 137 | {"mime-type-indicated", EncryptionAlgorithmMimeTypeIndicated}, 138 | } 139 | 140 | for _, test := range tests { 141 | assert.Equal(test.expected, test.alg.String()) 142 | } 143 | }) 144 | 145 | t.Run("unmarshalJSON", func(t *testing.T) { 146 | data := []byte(`{ 147 | "type": "artifact", 148 | "spec_version": "2.1", 149 | "encryption_algorithm": "something not accepted" 150 | }`) 151 | var obj *Artifact 152 | err := json.Unmarshal(data, &obj) 153 | assert.NoError(err) 154 | assert.Equal(EncryptionAlgorithmNone, obj.Encryption) 155 | }) 156 | 157 | t.Run("marshalJSON", func(t *testing.T) { 158 | a, err := NewArtifact( 159 | OptionHashes(Hashes{"SHA-256": "2cbb138d4097f05fffeb968b34a4e62884fc4755d7d043e2d3760950f1e1a9ee", "MD5": "0da609bd9ec46237557373f1c5cfcae9"}), 160 | OptionKey("key"), 161 | OptionPayload(Binary("Hello World")), 162 | OptionEncryption(EncryptionAlgorithmChaCha20Poly1305), 163 | ) 164 | assert.NoError(err) 165 | 166 | data, err := json.Marshal(a) 167 | assert.NoError(err) 168 | assert.Contains(string(data), EncryptionAlgorithmChaCha20Poly1305.String()) 169 | }) 170 | } 171 | -------------------------------------------------------------------------------- /observed-data.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | // ObservedData conveys information about cyber security related entities such 7 | // as files, systems, and networks using the STIX Cyber-observable Objects 8 | // (SCOs). For example, ObservedData can capture information about an IP 9 | // address, a network connection, a file, or a registry key. ObservedData is 10 | // not an intelligence assertion, it is simply the raw information without any 11 | // context for what it means. 12 | // 13 | // ObservedData can capture that a piece of information was seen one or more 14 | // times. Meaning, it can capture both a single observation of a single entity 15 | // (file, network connection) as well as the aggregation of multiple 16 | // observations of an entity. When the NumberObserved property is 1 the 17 | // ObservedData represents a single entity. When the NumberObserved property is 18 | // greater than 1, the ObservedData represents several instances of an entity 19 | // potentially collected over a period of time. If a time window is known, that 20 | // can be captured using the FirstObserved and LastObserved properties. When 21 | // used to collect aggregate data, it is likely that some properties in the SCO 22 | // (e.g., timestamp properties) will be omitted because they would differ for 23 | // each of the individual observations. 24 | // 25 | // ObservedData may be used by itself (without relationships) to convey raw 26 | // data collected from any source including analyst reports, sandboxes, and 27 | // network and host-based detection tools. An intelligence producer conveying 28 | // ObservedData SHOULD include as much context (e.g. SCOs) as possible that 29 | // supports the use of the observed data set in systems expecting to utilize 30 | // the ObservedData for improved security. This includes all SCOs that matched 31 | // on an Indicator pattern and are represented in the collected observed event 32 | // (or events) being conveyed in the ObservedData object. For example, a 33 | // firewall could emit a single ObservedData instance containing a single 34 | // Network Traffic object for each connection it sees. The firewall could also 35 | // aggregate data and instead send out an ObservedData instance every ten 36 | // minutes with an IP address and an appropriate NumberObserved value to 37 | // indicate the number of times that IP address was observed in that window. A 38 | // sandbox could emit an ObservedData instance containing a file hash that it 39 | // discovered. 40 | // 41 | // ObservedData may also be related to other SDOs to represent raw data that is 42 | // relevant to those objects. For example, the Sighting Relationship object, 43 | // can relate an Indicator, Malware, or other SDO to a specific ObservedData to 44 | // represent the raw information that led to the creation of the Sighting 45 | // (e.g., what was actually seen that suggested that a particular instance of 46 | // malware was active). 47 | // 48 | // To support backwards compatibility, related SCOs can still be specified 49 | // using the Objects properties, Either the objects property or the ObjectRefs 50 | // property MUST be provided, but both MUST NOT be present at the same time. 51 | type ObservedData struct { 52 | STIXDomainObject 53 | // FirstObserved is the beginning of the time window during which the data 54 | // was seen. 55 | FirstObserved *Timestamp `json:"first_observed"` 56 | // LastObserved is the end of the time window during which the data was 57 | // seen. 58 | LastObserved *Timestamp `json:"last_observed"` 59 | // NumberObserved is the number of times that each Cyber-observable object 60 | // represented in the objects or object_ref property was seen. If present, 61 | // this MUST be an integer between 1 and 999,999,999 inclusive. 62 | NumberObserved int64 `json:"number_observed"` 63 | // Objects is a map of SCO representing the observation. The dictionary 64 | // MUST contain at least one object. The cyber observable content MAY 65 | // include multiple objects if those objects are related as part of a 66 | // single observation. Multiple objects not related to each other via cyber 67 | // observable Relationships MUST NOT be contained within the same 68 | // ObservedData instance. This property MUST NOT be present if ObjectRefs 69 | // is provided. For example, a Network Traffic object and two IPv4 Address 70 | // objects related via the src_ref and dst_ref properties can be contained 71 | // in the same Observed Data because they are all related and used to 72 | // characterize that single entity. 73 | // 74 | // NOTE: this property is now deprecated in favor of ObjectRefs and will be 75 | // removed in a future version. 76 | Objects map[string]*STIXCyberObservableObject `json:"objects,omitempty"` 77 | // ObjectRefs is a list of SCOs and SROs representing the observation. The 78 | // ObjectRefs MUST contain at least one SCO reference if defined. 79 | ObjectRefs []Identifier `json:"object_refs,omitempty"` 80 | } 81 | 82 | func (o *ObservedData) MarshalJSON() ([]byte, error) { 83 | return marshalToJSONHelper(o) 84 | } 85 | 86 | // NewObservedData creates a new ObservedData object. 87 | func NewObservedData(firstObserved, lastObserved *Timestamp, numberObserved int64, objectsRef []Identifier, opts ...STIXOption) (*ObservedData, error) { 88 | if len(objectsRef) == 0 || firstObserved == nil || lastObserved == nil || numberObserved < 1 { 89 | return nil, ErrPropertyMissing 90 | } 91 | base := newSTIXDomainObject(TypeObservedData) 92 | obj := &ObservedData{ 93 | STIXDomainObject: base, 94 | FirstObserved: firstObserved, 95 | LastObserved: lastObserved, 96 | NumberObserved: numberObserved, 97 | ObjectRefs: objectsRef, 98 | } 99 | 100 | err := applyOptions(obj, opts) 101 | return obj, err 102 | } 103 | -------------------------------------------------------------------------------- /user_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestUserAccount(t *testing.T) { 15 | assert := assert.New(t) 16 | 17 | testStr := "test string" 18 | ts := &Timestamp{time.Now()} 19 | 20 | t.Run("missing_property", func(t *testing.T) { 21 | obj, err := NewUserAccount() 22 | assert.Nil(obj) 23 | assert.Equal(ErrPropertyMissing, err) 24 | }) 25 | 26 | t.Run("with_property", func(t *testing.T) { 27 | obj, err := NewUserAccount(nil) 28 | assert.NotNil(obj) 29 | assert.NoError(err) 30 | }) 31 | 32 | t.Run("payload_with_options", func(t *testing.T) { 33 | marking := make([]*GranularMarking, 0) 34 | objmark := []Identifier{Identifier("id")} 35 | specVer := "2.0" 36 | 37 | opts := []STIXOption{ 38 | OptionGranularMarking(marking), 39 | OptionObjectMarking(objmark), 40 | OptionSpecVersion(specVer), 41 | OptionDefanged(true), 42 | // 43 | OptionUserID(testStr), 44 | OptionCredential(testStr), 45 | OptionAccountLogin(testStr), 46 | OptionAccountType(AccountWindowsLocal), 47 | OptionDisplayName(testStr), 48 | OptionIsServiceAccount(true), 49 | OptionIsPrivileged(true), 50 | OptionCanEscalatePrivs(true), 51 | OptionIsDisabled(true), 52 | OptionAccountCreated(ts), 53 | OptionAccountExpires(ts), 54 | OptionCredentialLastChanged(ts), 55 | OptionAccountFirstLogin(ts), 56 | OptionAccountLastLogin(ts), 57 | } 58 | obj, err := NewUserAccount(opts...) 59 | assert.NotNil(obj) 60 | assert.NoError(err) 61 | assert.Equal(marking, obj.GranularMarking) 62 | assert.Equal(objmark, obj.ObjectMarking) 63 | assert.Equal(specVer, obj.SpecVersion) 64 | assert.True(obj.Defanged) 65 | 66 | assert.Equal(testStr, obj.UserID) 67 | assert.Equal(testStr, obj.Credential) 68 | assert.Equal(testStr, obj.AccountLogin) 69 | assert.Equal(AccountWindowsLocal, obj.AccountType) 70 | assert.Equal(testStr, obj.DisplayName) 71 | assert.True(obj.IsServiceAccount) 72 | assert.True(obj.IsPrivileged) 73 | assert.True(obj.CanEscalatePrivs) 74 | assert.True(obj.IsDisabled) 75 | assert.Equal(ts, obj.AccountCreated) 76 | assert.Equal(ts, obj.AccountExpires) 77 | assert.Equal(ts, obj.CredentialLastChanged) 78 | assert.Equal(ts, obj.AccountFirstLogin) 79 | assert.Equal(ts, obj.AccountLastLogin) 80 | }) 81 | 82 | t.Run("id-generation", func(t *testing.T) { 83 | tests := []struct { 84 | typ string 85 | uid string 86 | login string 87 | id string 88 | }{ 89 | { 90 | AccountUnix, 91 | "", 92 | "", 93 | "user-account--5e8f515f-35db-5c9b-b413-65cc81740e1b", 94 | }, 95 | { 96 | AccountUnix, 97 | "1001", 98 | "", 99 | "user-account--d7498183-59da-5cbd-98a3-d9c95ea4276c", 100 | }, 101 | { 102 | AccountUnix, 103 | "1001", 104 | "jdoe", 105 | "user-account--ac9fed0c-94ad-5651-8630-7ac1f6ea0c67", 106 | }, 107 | } 108 | for _, test := range tests { 109 | opts := make([]STIXOption, 0, 3) 110 | if test.uid != "" { 111 | opts = append(opts, OptionUserID(test.uid)) 112 | } 113 | if test.typ != "" { 114 | opts = append(opts, OptionAccountType(test.typ)) 115 | } 116 | if test.login != "" { 117 | opts = append(opts, OptionAccountLogin(test.login)) 118 | } 119 | obj, err := NewUserAccount(opts...) 120 | assert.NoError(err) 121 | assert.Equal(Identifier(test.id), obj.ID) 122 | } 123 | }) 124 | 125 | t.Run("unix-extension", func(t *testing.T) { 126 | ext := &UNIXAccountExtension{GID: int64(1)} 127 | f, _ := NewUserAccount(OptionExtension(ExtUnixAccount, ext)) 128 | assert.Len(f.Extensions, 1) 129 | stored := f.UNIXAccountExtension() 130 | assert.Equal(ext, stored) 131 | }) 132 | 133 | t.Run("unix-extension-nil", func(t *testing.T) { 134 | f, _ := NewUserAccount(nil) 135 | assert.Len(f.Extensions, 0) 136 | stored := f.UNIXAccountExtension() 137 | assert.Nil(stored) 138 | }) 139 | 140 | t.Run("parse_json", func(t *testing.T) { 141 | data := []byte(` 142 | { 143 | "type": "user-account", 144 | "spec_version": "2.1", 145 | "id": "user-account--9bd3afcf-deee-54f9-83e2-520653cb6bba", 146 | "user_id": "thegrugq_ebooks", 147 | "account_login": "thegrugq_ebooks", 148 | "account_type": "twitter", 149 | "display_name": "the grugq" 150 | }`) 151 | var obj *UserAccount 152 | err := json.Unmarshal(data, &obj) 153 | assert.NoError(err) 154 | assert.Equal(Identifier("user-account--9bd3afcf-deee-54f9-83e2-520653cb6bba"), obj.ID) 155 | assert.Equal("2.1", obj.SpecVersion) 156 | assert.Equal(TypeUserAccount, obj.Type) 157 | assert.Equal("thegrugq_ebooks", obj.UserID) 158 | assert.Equal("thegrugq_ebooks", obj.AccountLogin) 159 | assert.Equal("the grugq", obj.DisplayName) 160 | assert.Equal(AccountTwitter, obj.AccountType) 161 | }) 162 | 163 | t.Run("parse-unix-user", func(t *testing.T) { 164 | data := []byte(`{ 165 | "type": "user-account", 166 | "spec_version": "2.1", 167 | "id": "user-account--0d5b424b-93b8-5cd8-ac36-306e1789d63c", 168 | "user_id": "1001", 169 | "account_login": "jdoe", 170 | "account_type": "unix", 171 | "display_name": "John Doe", 172 | "is_service_account": false, 173 | "is_privileged": false, 174 | "can_escalate_privs": true, 175 | "extensions": { 176 | "unix-account-ext": { 177 | "gid": 1001, 178 | "groups": ["wheel"], 179 | "home_dir": "/home/jdoe", 180 | "shell": "/bin/bash" 181 | } 182 | } 183 | } 184 | `) 185 | 186 | var obj *UserAccount 187 | err := json.Unmarshal(data, &obj) 188 | assert.NoError(err) 189 | 190 | ext := obj.UNIXAccountExtension() 191 | assert.NotNil(ext) 192 | assert.Equal(int64(1001), ext.GID) 193 | }) 194 | } 195 | -------------------------------------------------------------------------------- /opinion_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestOpinion(t *testing.T) { 15 | assert := assert.New(t) 16 | 17 | expl := "Opinion content" 18 | authors := []string{"Author 1", "Author 2"} 19 | objects := []Identifier{Identifier("something")} 20 | val := OpinionAgree 21 | 22 | t.Run("missing_property", func(t *testing.T) { 23 | obj, err := NewOpinion(OpinionValue(0), []Identifier{}, nil) 24 | assert.Nil(obj) 25 | assert.Equal(ErrPropertyMissing, err) 26 | }) 27 | 28 | t.Run("no_optional", func(t *testing.T) { 29 | obj, err := NewOpinion(val, objects, nil) 30 | assert.NotNil(obj) 31 | assert.NoError(err) 32 | }) 33 | 34 | t.Run("with_options", func(t *testing.T) { 35 | conf := 50 36 | ts := &Timestamp{time.Now()} 37 | createdBy := NewIdentifier(TypeIdentity) 38 | ref := &ExternalReference{} 39 | marking := make([]*GranularMarking, 0) 40 | labels := []string{"tag1", "tag2"} 41 | lang := "en" 42 | objmark := []Identifier{Identifier("id")} 43 | specVer := "2.0" 44 | 45 | opts := []STIXOption{ 46 | OptionConfidence(conf), 47 | OptionCreated(ts), 48 | OptionModified(ts), 49 | OptionCreatedBy(createdBy), 50 | OptionExternalReferences([]*ExternalReference{ref}), 51 | OptionGranularMarking(marking), 52 | OptionLabels(labels), 53 | OptionLang(lang), 54 | OptionObjectMarking(objmark), 55 | OptionRevoked(true), 56 | OptionSpecVersion(specVer), 57 | // 58 | OptionExplanation(expl), 59 | OptionAuthors(authors), 60 | } 61 | obj, err := NewOpinion(val, objects, opts...) 62 | assert.NotNil(obj) 63 | assert.NoError(err) 64 | assert.Equal(conf, obj.Confidence) 65 | assert.Equal(ts, obj.Created) 66 | assert.Equal(ts, obj.Modified) 67 | assert.Equal(createdBy, obj.CreatedBy) 68 | assert.Contains(obj.ExternalReferences, ref) 69 | assert.Equal(marking, obj.GranularMarking) 70 | assert.Equal(labels, obj.Labels) 71 | assert.Equal(lang, obj.Lang) 72 | assert.Equal(objmark, obj.ObjectMarking) 73 | assert.True(obj.Revoked) 74 | assert.Equal(specVer, obj.SpecVersion) 75 | 76 | assert.Equal(expl, obj.Explanation) 77 | assert.Equal(authors, obj.Authors) 78 | assert.Equal(val, obj.Value) 79 | assert.Equal(objects, obj.Objects) 80 | }) 81 | 82 | t.Run("parse_json", func(t *testing.T) { 83 | data := []byte(`{ 84 | "type": "opinion", 85 | "spec_version": "2.1", 86 | "id": "opinion--b01efc25-77b4-4003-b18b-f6e24b5cd9f7", 87 | "created_by_ref": "identity--f431f809-377b-45e0-aa1c-6a4751cae5ff", 88 | "created": "2016-05-12T08:17:27.000Z", 89 | "modified": "2016-05-12T08:17:27.000Z", 90 | "object_refs": ["relationship--16d2358f-3b0d-4c88-b047-0da2f7ed4471"], 91 | "opinion": "strongly-disagree", 92 | "explanation": "This doesn't seem like it is feasible. We've seen how PandaCat has attacked Spanish infrastructure over the last 3 years, so this change in targeting seems too great to be viable. The methods used are more commonly associated with the FlameDragonCrew." 93 | }`) 94 | ts, err := time.Parse(time.RFC3339Nano, "2016-05-12T08:17:27.000Z") 95 | assert.NoError(err) 96 | var obj *Opinion 97 | err = json.Unmarshal(data, &obj) 98 | assert.NoError(err) 99 | assert.Equal(Identifier("opinion--b01efc25-77b4-4003-b18b-f6e24b5cd9f7"), obj.ID) 100 | assert.Equal("2.1", obj.SpecVersion) 101 | assert.Equal(TypeOpinion, obj.Type) 102 | assert.Equal(ts, obj.Created.Time) 103 | assert.Equal(ts, obj.Modified.Time) 104 | assert.Equal(OpinionStronglyDisagree, obj.Value) 105 | assert.Equal("This doesn't seem like it is feasible. We've seen how PandaCat has attacked Spanish infrastructure over the last 3 years, so this change in targeting seems too great to be viable. The methods used are more commonly associated with the FlameDragonCrew.", obj.Explanation) 106 | assert.Contains(obj.Objects, Identifier("relationship--16d2358f-3b0d-4c88-b047-0da2f7ed4471")) 107 | assert.Equal(Identifier("identity--f431f809-377b-45e0-aa1c-6a4751cae5ff"), obj.CreatedBy) 108 | }) 109 | } 110 | 111 | func TestOpinionValue(t *testing.T) { 112 | assert := assert.New(t) 113 | 114 | t.Run("stringer", func(t *testing.T) { 115 | tests := []struct { 116 | op OpinionValue 117 | str string 118 | }{ 119 | {OpinionStronglyDisagree, "strongly-disagree"}, 120 | {OpinionDisagree, "disagree"}, 121 | {OpinionNeutral, "neutral"}, 122 | {OpinionAgree, "agree"}, 123 | {OpinionStronglyAgree, "strongly-agree"}, 124 | {OpinionValue(0), ""}, 125 | } 126 | for _, test := range tests { 127 | assert.Equal(test.str, test.op.String()) 128 | } 129 | }) 130 | 131 | t.Run("marshalJSON", func(t *testing.T) { 132 | tests := []struct { 133 | op OpinionValue 134 | str string 135 | }{ 136 | {OpinionStronglyDisagree, `"strongly-disagree"`}, 137 | {OpinionDisagree, `"disagree"`}, 138 | {OpinionNeutral, `"neutral"`}, 139 | {OpinionAgree, `"agree"`}, 140 | {OpinionStronglyAgree, `"strongly-agree"`}, 141 | {OpinionValue(0), `""`}, 142 | } 143 | for _, test := range tests { 144 | j, err := test.op.MarshalJSON() 145 | assert.NoError(err) 146 | assert.Equal(test.str, string(j)) 147 | } 148 | }) 149 | 150 | t.Run("unmarshalJSON", func(t *testing.T) { 151 | tests := []struct { 152 | op OpinionValue 153 | str []byte 154 | }{ 155 | {OpinionStronglyDisagree, []byte("\"strongly-disagree\"")}, 156 | {OpinionDisagree, []byte("\"disagree\"")}, 157 | {OpinionNeutral, []byte("\"neutral\"")}, 158 | {OpinionAgree, []byte("\"agree\"")}, 159 | {OpinionStronglyAgree, []byte("\"strongly-agree\"")}, 160 | {OpinionValue(0), []byte("\"\"")}, 161 | } 162 | for _, test := range tests { 163 | var val OpinionValue 164 | vp := &val 165 | err := vp.UnmarshalJSON(test.str) 166 | assert.NoError(err) 167 | assert.Equal(test.op, val) 168 | } 169 | }) 170 | } 171 | -------------------------------------------------------------------------------- /x509_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 Joakim Kennedy. All rights reserved. Use of 2 | // this source code is governed by the included BSD license. 3 | 4 | package stix2 5 | 6 | import ( 7 | "encoding/json" 8 | "testing" 9 | "time" 10 | 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestX509Certificate(t *testing.T) { 15 | assert := assert.New(t) 16 | 17 | testStr := "test string" 18 | hashes := Hashes{SHA1: "0f01ed56a1e32a05e5ef96e4d779f34784af9a96"} 19 | ts := &Timestamp{time.Now()} 20 | v3 := X509v3Extension{SubjectAltName: "alt name"} 21 | num := int64(42) 22 | 23 | t.Run("missing_property", func(t *testing.T) { 24 | obj, err := NewX509Certificate() 25 | assert.Nil(obj) 26 | assert.Equal(ErrPropertyMissing, err) 27 | }) 28 | 29 | t.Run("with_property", func(t *testing.T) { 30 | obj, err := NewX509Certificate(nil) 31 | assert.NotNil(obj) 32 | assert.NoError(err) 33 | }) 34 | 35 | t.Run("payload_with_options", func(t *testing.T) { 36 | marking := make([]*GranularMarking, 0) 37 | objmark := []Identifier{Identifier("id")} 38 | specVer := "2.0" 39 | 40 | opts := []STIXOption{ 41 | OptionGranularMarking(marking), 42 | OptionObjectMarking(objmark), 43 | OptionSpecVersion(specVer), 44 | OptionDefanged(true), 45 | // 46 | OptionSelfSigned(true), 47 | OptionHashes(hashes), 48 | OptionSerialNumber(testStr), 49 | OptionVersion(testStr), 50 | OptionSignatureAlgorithm(testStr), 51 | OptionIssuer(testStr), 52 | OptionValidityNotBefore(ts), 53 | OptionValidityNotAfter(ts), 54 | OptionSubject(testStr), 55 | OptionSubjectPublicKeyAlgorithm(testStr), 56 | OptionSubjectPublicKeyModulus(testStr), 57 | OptionSubjectPublicKeyExponent(num), 58 | OptionV3Extensions(v3), 59 | } 60 | obj, err := NewX509Certificate(opts...) 61 | assert.NotNil(obj) 62 | assert.NoError(err) 63 | assert.Equal(marking, obj.GranularMarking) 64 | assert.Equal(objmark, obj.ObjectMarking) 65 | assert.Equal(specVer, obj.SpecVersion) 66 | assert.True(obj.Defanged) 67 | 68 | assert.True(obj.SelfSigned) 69 | assert.Equal(testStr, obj.SerialNumber) 70 | assert.Equal(hashes, obj.Hashes) 71 | assert.Equal(testStr, obj.Version) 72 | assert.Equal(testStr, obj.SignatureAlgorithm) 73 | assert.Equal(testStr, obj.Issuer) 74 | assert.Equal(ts, obj.ValidityNotBefore) 75 | assert.Equal(ts, obj.ValidityNotAfter) 76 | assert.Equal(testStr, obj.Subject) 77 | assert.Equal(testStr, obj.SubjectPublicKeyAlgorithm) 78 | assert.Equal(testStr, obj.SubjectPublicKeyModulus) 79 | assert.Equal(num, obj.SubjectPublicKeyExponent) 80 | assert.Equal(v3, obj.X509v3Extensions) 81 | }) 82 | 83 | t.Run("id-generation", func(t *testing.T) { 84 | tests := []struct { 85 | serial string 86 | hashes Hashes 87 | id string 88 | }{ 89 | { 90 | testStr, 91 | nil, 92 | "x509-certificate--95c4c7a8-d804-505a-be56-e48cf0907412", 93 | }, 94 | { 95 | testStr, 96 | hashes, 97 | "x509-certificate--72619740-49d8-56c5-a082-639ff2c8f0a6", 98 | }, 99 | } 100 | for _, test := range tests { 101 | opts := make([]STIXOption, 0, 3) 102 | if test.serial != "" { 103 | opts = append(opts, OptionSerialNumber(test.serial)) 104 | } 105 | if test.hashes != nil { 106 | opts = append(opts, OptionHashes(test.hashes)) 107 | } 108 | obj, err := NewX509Certificate(opts...) 109 | assert.NoError(err) 110 | assert.Equal(Identifier(test.id), obj.ID) 111 | } 112 | }) 113 | 114 | t.Run("parse_json", func(t *testing.T) { 115 | data := []byte(` 116 | { 117 | "type":"x509-certificate", 118 | "spec_version": "2.1", 119 | "id": "x509-certificate--b595eaf0-0b28-5dad-9e8e-0fab9c1facc9", 120 | "issuer":"C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com", 121 | "validity_not_before":"2016-03-12T12:00:00Z", 122 | "validity_not_after":"2016-08-21T12:00:00Z", 123 | "subject":"C=US, ST=Maryland, L=Pasadena, O=Brent Baccala, OU=FreeSoft, CN=www.freesoft.org/emailAddress=baccala@freesoft.org", 124 | "serial_number": "02:08:87:83:f2:13:58:1f:79:52:1e:66:90:0a:02:24:c9:6b:c7:dc", 125 | "x509_v3_extensions":{ 126 | "basic_constraints":"critical,CA:TRUE, pathlen:0", 127 | "name_constraints":"permitted;IP:192.168.0.0/255.255.0.0", 128 | "policy_contraints":"requireExplicitPolicy:3", 129 | "key_usage":"critical, keyCertSign", 130 | "extended_key_usage":"critical,codeSigning,1.2.3.4", 131 | "subject_key_identifier":"hash", 132 | "authority_key_identifier":"keyid,issuer", 133 | "subject_alternative_name":"email:my@other.address,RID:1.2.3.4", 134 | "issuer_alternative_name":"issuer:copy", 135 | "crl_distribution_points":"URI:http://myhost.com/myca.crl", 136 | "inhibit_any_policy":"2", 137 | "private_key_usage_period_not_before":"2016-03-12T12:00:00Z", 138 | "private_key_usage_period_not_after":"2018-03-12T12:00:00Z", 139 | "certificate_policies":"1.2.4.5, 1.1.3.4" 140 | } 141 | } 142 | `) 143 | var obj *X509Certificate 144 | err := json.Unmarshal(data, &obj) 145 | assert.NoError(err) 146 | assert.Equal(Identifier("x509-certificate--b595eaf0-0b28-5dad-9e8e-0fab9c1facc9"), obj.ID) 147 | assert.Equal("2.1", obj.SpecVersion) 148 | assert.Equal(TypeX509Certificate, obj.Type) 149 | assert.Equal("C=ZA, ST=Western Cape, L=Cape Town, O=Thawte Consulting cc, OU=Certification Services Division, CN=Thawte Server CA/emailAddress=server-certs@thawte.com", obj.Issuer) 150 | assert.Equal("C=US, ST=Maryland, L=Pasadena, O=Brent Baccala, OU=FreeSoft, CN=www.freesoft.org/emailAddress=baccala@freesoft.org", obj.Subject) 151 | assert.Equal("02:08:87:83:f2:13:58:1f:79:52:1e:66:90:0a:02:24:c9:6b:c7:dc", obj.SerialNumber) 152 | assert.NotNil(obj.X509v3Extensions) 153 | assert.Equal("critical,CA:TRUE, pathlen:0", obj.X509v3Extensions.BasicConstraints) 154 | assert.Equal("critical,codeSigning,1.2.3.4", obj.X509v3Extensions.ExtendedKeyUsage) 155 | assert.Equal("email:my@other.address,RID:1.2.3.4", obj.X509v3Extensions.SubjectAltName) 156 | }) 157 | } 158 | --------------------------------------------------------------------------------