Skip to content

Commit 671c9fa

Browse files
committed
new: [support for signing] added
- added new class CryptographicKeys - added functions to to_feed calls to include crypto keys - added protected boolean field to misp event - updated feed generator to support signing - if the new setting is set to True signing will be attempted for protected events - protected events are now passed to the /cryptographic_keys/serverSign endpoint of misp for signing - signatures are included as a .asc file in the output directory - TODO: - currently the JSON dumping is moved from a streamed dumping to an in memory dump before saving to disk - add a check for protected events and revert to streamed dumping for non protected events - alternatively use the already saved files to request signing from MISP
1 parent 30819e1 commit 671c9fa

File tree

3 files changed

+62
-6
lines changed

3 files changed

+62
-6
lines changed

examples/feed-generator/generate.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@
55
import json
66
import os
77
from pymisp import ExpandedPyMISP
8-
from settings import url, key, ssl, outputdir, filters, valid_attribute_distribution_levels
8+
from settings import url, key, ssl, outputdir, filters, valid_attribute_distribution_levels, with_signatures
99
try:
1010
from settings import with_distribution
1111
except ImportError:
1212
with_distribution = False
1313

14+
try:
15+
from settings import with_signatures
16+
except ImportError:
17+
with_signatures = False
18+
1419
try:
1520
from settings import with_local_tags
1621
except ImportError:
@@ -39,14 +44,31 @@ def init():
3944
return ExpandedPyMISP(url, key, ssl)
4045

4146

42-
def saveEvent(event):
47+
def saveEvent(event, misp):
48+
stringified_event = json.dumps(event, indent=2)
49+
if with_signatures and event['Event'].get('protected'):
50+
signature = getSignature(stringified_event, misp)
51+
try:
52+
with open(os.path.join(outputdir, f'{event["Event"]["uuid"]}.asc'), 'w') as f:
53+
f.write(signature)
54+
except Exception as e:
55+
print(e)
56+
sys.exit('Could not create the event signature dump.')
57+
4358
try:
4459
with open(os.path.join(outputdir, f'{event["Event"]["uuid"]}.json'), 'w') as f:
45-
json.dump(event, f, indent=2)
60+
f.write(stringified_event)
4661
except Exception as e:
4762
print(e)
4863
sys.exit('Could not create the event dump.')
4964

65+
def getSignature(stringified_event, misp):
66+
try:
67+
signature = misp.direct_call('/cryptographicKeys/serverSign', stringified_event)
68+
return signature
69+
except Exception as e:
70+
print(e)
71+
sys.exit('Could not get the signature for the event from the MISP instance. Perhaps the user does not have the necessary permissions.')
5072

5173
def saveHashes(hashes):
5274
try:
@@ -79,6 +101,7 @@ def saveManifest(manifest):
79101
sys.exit("No events returned.")
80102
manifest = {}
81103
hashes = []
104+
signatures = []
82105
counter = 1
83106
total = len(events)
84107
for event in events:
@@ -97,7 +120,7 @@ def saveManifest(manifest):
97120
continue
98121
hashes += [[h, e.uuid] for h in e_feed['Event'].pop('_hashes')]
99122
manifest.update(e_feed['Event'].pop('_manifest'))
100-
saveEvent(e_feed)
123+
saveEvent(e_feed, misp)
101124
print("Event " + str(counter) + "/" + str(total) + " exported.")
102125
counter += 1
103126
saveManifest(manifest)

examples/feed-generator/settings.default.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,6 @@
5454

5555
# Include the exportable local tags along with the global tags. The default is True.
5656
with_local_tags = True
57+
58+
# Include signatures for protected events. This will allow MISP to ingest and update protected events from the feed. It requires perm_server_sign to be set to true in the user's role on MISP's side.
59+
with_signatures = False

pymisp/mispevent.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1559,7 +1559,8 @@ def __repr__(self) -> str:
15591559
class MISPEvent(AnalystDataBehaviorMixin):
15601560

15611561
_fields_for_feed: set[str] = {'uuid', 'info', 'threat_level_id', 'analysis', 'timestamp',
1562-
'publish_timestamp', 'published', 'date', 'extends_uuid'}
1562+
'publish_timestamp', 'published', 'date', 'extends_uuid',
1563+
'protected'}
15631564

15641565
_analyst_data_object_type = 'Event'
15651566

@@ -1581,6 +1582,7 @@ def __init__(self, describe_types: dict[str, Any] | None = None, strict_validati
15811582
self.EventReport: list[MISPEventReport] = []
15821583
self.Tag: list[MISPTag] = []
15831584
self.Galaxy: list[MISPGalaxy] = []
1585+
self.CryptographicKey: list[MISPCryptographicKey] = []
15841586

15851587
self.publish_timestamp: float | int | datetime
15861588
self.timestamp: float | int | datetime
@@ -1649,13 +1651,14 @@ def attributes_hashes(self, algorithm: str = 'sha512') -> list[str]:
16491651
to_return += attribute.hash_values(algorithm)
16501652
return to_return
16511653

1652-
def to_feed(self, valid_distributions: list[int] = [0, 1, 2, 3, 4, 5], with_meta: bool = False, with_distribution: bool=False, with_local_tags: bool = True, with_event_reports: bool = True) -> dict[str, Any]:
1654+
def to_feed(self, valid_distributions: list[int] = [0, 1, 2, 3, 4, 5], with_meta: bool = False, with_distribution: bool=False, with_local_tags: bool = True, with_event_reports: bool = True, with_cryptographic_keys: bool = True) -> dict[str, Any]:
16531655
""" Generate a json output for MISP Feed.
16541656
16551657
:param valid_distributions: only makes sense if the distribution key is set; i.e., the event is exported from a MISP instance.
16561658
:param with_distribution: exports distribution and Sharing Group info; otherwise all SharingGroup information is discarded (protecting privacy)
16571659
:param with_local_tags: tag export includes local exportable tags along with global exportable tags
16581660
:param with_event_reports: include event reports in the returned MISP event
1661+
:param with_cryptographic_keys: include the associated cryptographic keys in the returned protected MISP event
16591662
"""
16601663
required = ['info', 'Orgc']
16611664
for r in required:
@@ -1719,6 +1722,13 @@ def to_feed(self, valid_distributions: list[int] = [0, 1, 2, 3, 4, 5], with_meta
17191722
event_report.pop('SharingGroup', None)
17201723
event_report.pop('sharing_group_id', None)
17211724
to_return['EventReport'].append(event_report.to_dict())
1725+
1726+
if with_cryptographic_keys and self.cryptographic_keys:
1727+
to_return['CryptographicKey'] = []
1728+
for cryptographic_key in self.cryptographic_keys:
1729+
cryptographic_key.pop('parent_id', None)
1730+
cryptographic_key.pop('id', None)
1731+
to_return['CryptographicKey'].append(cryptographic_key.to_dict())
17221732

17231733
return {'Event': to_return}
17241734

@@ -1755,6 +1765,10 @@ def attributes(self, attributes: list[MISPAttribute]) -> None:
17551765
@property
17561766
def event_reports(self) -> list[MISPEventReport]:
17571767
return self.EventReport
1768+
1769+
@property
1770+
def cryptographic_keys(self) -> list[MISPCryptographicKey]:
1771+
return self.CryptographicKey
17581772

17591773
@property
17601774
def shadow_attributes(self) -> list[MISPShadowAttribute]:
@@ -1891,6 +1905,8 @@ def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def]
18911905
[self.add_galaxy(**e) for e in kwargs.pop('Galaxy')]
18921906
if kwargs.get('EventReport'):
18931907
[self.add_event_report(**e) for e in kwargs.pop('EventReport')]
1908+
if kwargs.get('CryptographicKey'):
1909+
[self.add_cryprographic_key(**e) for e in kwargs.pop('CryptographicKey')]
18941910

18951911
# All other keys
18961912
if kwargs.get('id'):
@@ -2041,6 +2057,15 @@ def add_event_report(self, name: str, content: str, **kwargs) -> MISPEventReport
20412057
self.edited = True
20422058
return event_report
20432059

2060+
def add_cryprographic_key(self, parent_type: str, key_data: str, type: str, uuid: str, fingerprint: str, timestamp: str, **kwargs) -> MISPCryptographicKey: # type: ignore[no-untyped-def]
2061+
"""Add a Cryptographic Key. parent_type, key_data, type, uuid, fingerprint, timestamp are required but you can pass all
2062+
other parameters supported by MISPEventReport"""
2063+
cryptographic_key = MISPCryptographicKey()
2064+
cryptographic_key.from_dict(parent_type=parent_type, key_data=key_data, type=type, uuid=uuid, fingerprint=fingerprint, timestamp=timestamp, **kwargs)
2065+
self.cryptographic_keys.append(cryptographic_key)
2066+
self.edited = True
2067+
return cryptographic_key
2068+
20442069
def add_galaxy(self, galaxy: MISPGalaxy | dict[str, Any] | None = None, **kwargs) -> MISPGalaxy: # type: ignore[no-untyped-def]
20452070
"""Add a galaxy and sub-clusters into an event, either by passing
20462071
a MISPGalaxy or a dictionary.
@@ -2225,6 +2250,11 @@ def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def]
22252250
kwargs = kwargs['Warninglist']
22262251
super().from_dict(**kwargs)
22272252

2253+
class MISPCryptographicKey(AbstractMISP):
2254+
def from_dict(self, **kwargs) -> None: # type: ignore[no-untyped-def]
2255+
if 'CryptographicKey' in kwargs:
2256+
kwargs = kwargs['CryptographicKey']
2257+
super().from_dict(**kwargs)
22282258

22292259
class MISPTaxonomy(AbstractMISP):
22302260

0 commit comments

Comments
 (0)