Skip to content

Commit ed7225d

Browse files
authored
Refactoring of SecOC Layer and implementation for SecOC over CANFD (#4459)
* Refactoring of SecOC Layer and implementation for SecOC over CANFD * fix unit test
1 parent 836e4d5 commit ed7225d

File tree

6 files changed

+271
-98
lines changed

6 files changed

+271
-98
lines changed

scapy/contrib/automotive/autosar/pdu.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ def extract_padding(self, s):
3636
class PDUTransport(Packet):
3737
"""
3838
Packet representing PDUTransport containing multiple PDUs
39-
FIXME: Support CAN messages as well.
4039
"""
4140
name = 'PDUTransport'
4241
fields_desc = [

scapy/contrib/automotive/autosar/secoc.py

Lines changed: 22 additions & 96 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,11 @@
44
# Copyright (C) Nils Weiss <[email protected]>
55

66
# scapy.contrib.description = AUTOSAR Secure On-Board Communication
7-
# scapy.contrib.status = loads
7+
# scapy.contrib.status = library
88

99
"""
1010
SecOC
1111
"""
12-
import struct
13-
1412
from scapy.config import conf
1513
from scapy.error import log_loading
1614

@@ -21,74 +19,35 @@
2119
log_loading.info("Can't import python-cryptography v1.7+. "
2220
"Disabled SecOC calculate_cmac.")
2321

24-
from scapy.base_classes import Packet_metaclass
2522
from scapy.config import conf
26-
from scapy.contrib.automotive.autosar.pdu import PDU
27-
from scapy.fields import (XByteField, XIntField, PacketListField,
28-
FieldLenField, PacketLenField, XStrFixedLenField)
23+
from scapy.fields import PacketLenField
2924
from scapy.packet import Packet, Raw
3025

3126
# Typing imports
3227
from typing import (
33-
Any,
3428
Callable,
3529
Dict,
3630
Optional,
3731
Set,
38-
Tuple,
3932
Type,
4033
)
4134

4235

43-
class PduPayloadField(PacketLenField):
44-
45-
__slots__ = ["guess_pkt_cls"]
46-
47-
def __init__(self,
48-
name, # type: str
49-
default, # type: Packet
50-
guess_pkt_cls, # type: Callable[[Packet, bytes], Packet] # noqa: E501
51-
length_from=None # type: Optional[Callable[[Packet], int]] # noqa: E501
52-
):
53-
# type: (...) -> None
54-
super(PacketLenField, self).__init__(name, default, Raw)
55-
self.length_from = length_from or (lambda x: 0)
56-
self.guess_pkt_cls = guess_pkt_cls
57-
58-
def m2i(self, pkt, m): # type: ignore
59-
# type: (Optional[Packet], bytes) -> Packet
60-
return self.guess_pkt_cls(pkt, m)
61-
62-
63-
class SecOC_PDU(Packet):
64-
name = 'SecOC_PDU'
65-
fields_desc = [
66-
XIntField('pdu_id', 0),
67-
FieldLenField('pdu_payload_len', None,
68-
fmt="I",
69-
length_of="pdu_payload",
70-
adjust=lambda pkt, x: x + 4),
71-
PduPayloadField('pdu_payload',
72-
Raw(),
73-
guess_pkt_cls=lambda pkt, data: SecOC_PDU.get_pdu_payload_cls(pkt, data), # noqa: E501
74-
length_from=lambda pkt: pkt.pdu_payload_len - 4),
75-
XByteField("tfv", 0), # truncated freshness value
76-
XStrFixedLenField("tmac", None, length=3)] # truncated message authentication code # noqa: E501
36+
class SecOCMixin:
7737

7838
pdu_payload_cls_by_identifier: Dict[int, Type[Packet]] = dict()
7939
secoc_protected_pdus_by_identifier: Set[int] = set()
8040

8141
def secoc_authenticate(self) -> None:
82-
self.tfv = struct.unpack(">B", self.get_secoc_freshness_value()[-1:])[0]
83-
self.tmac = self.get_message_authentication_code()[0:3]
42+
raise NotImplementedError
8443

8544
def secoc_verify(self) -> bool:
86-
return self.get_message_authentication_code()[0:3] == self.tmac
45+
raise NotImplementedError
8746

8847
def get_secoc_payload(self) -> bytes:
8948
"""Override this method for customization
9049
"""
91-
return self.pdu_payload
50+
raise NotImplementedError
9251

9352
def get_secoc_key(self) -> bytes:
9453
"""Override this method for customization
@@ -123,56 +82,23 @@ def register_secoc_protected_pdu(cls,
12382
@classmethod
12483
def unregister_secoc_protected_pdu(cls, pdu_id: int) -> None:
12584
cls.secoc_protected_pdus_by_identifier.remove(pdu_id)
126-
del cls.secret_keys_by_identifier[pdu_id]
85+
del cls.pdu_payload_cls_by_identifier[pdu_id]
12786

128-
@classmethod
129-
def dispatch_hook(cls, s=None, *_args, **_kwds):
130-
# type: (Optional[bytes], Any, Any) -> Packet_metaclass
131-
"""dispatch_hook determines if PDU is protected by SecOC.
132-
If PDU is protected, SecOC_PDU will be returned, otherwise AutoSAR PDU
133-
will be returned.
134-
"""
135-
if s is None:
136-
return SecOC_PDU
137-
if len(s) < 4:
138-
return Raw
139-
identifier = struct.unpack('>I', s[0:4])[0]
140-
if identifier in cls.secoc_protected_pdus_by_identifier:
141-
return SecOC_PDU
142-
else:
143-
return PDU
14487

145-
@staticmethod
146-
def get_pdu_payload_cls(pkt: Packet,
147-
data: bytes
148-
) -> Packet:
149-
try:
150-
cls = SecOC_PDU.pdu_payload_cls_by_identifier[pkt.pdu_id]
151-
except KeyError:
152-
cls = conf.raw_layer
153-
return cls(data, _parent=pkt)
154-
155-
def extract_padding(self, s):
156-
# type: (bytes) -> Tuple[bytes, Optional[bytes]]
157-
return b"", s
158-
159-
160-
class SecOC_PDUTransport(Packet):
161-
"""
162-
Packet representing SecOC_PDUTransport containing multiple PDUs
163-
"""
164-
165-
name = 'SecOC_PDUTransport'
166-
fields_desc = [
167-
PacketListField("pdus", [SecOC_PDU()], pkt_cls=SecOC_PDU)
168-
]
88+
class PduPayloadField(PacketLenField):
89+
__slots__ = ["guess_pkt_cls"]
16990

170-
@staticmethod
171-
def register_secoc_protected_pdu(pdu_id: int,
172-
pdu_payload_cls: Type[Packet] = Raw
173-
) -> None:
174-
SecOC_PDU.register_secoc_protected_pdu(pdu_id, pdu_payload_cls)
91+
def __init__(self,
92+
name, # type: str
93+
default, # type: Packet
94+
guess_pkt_cls, # type: Callable[[Packet, bytes], Packet] # noqa: E501
95+
length_from=None # type: Optional[Callable[[Packet], int]] # noqa: E501
96+
):
97+
# type: (...) -> None
98+
super(PacketLenField, self).__init__(name, default, Raw)
99+
self.length_from = length_from or (lambda x: 0)
100+
self.guess_pkt_cls = guess_pkt_cls
175101

176-
@staticmethod
177-
def unregister_secoc_protected_pdu(pdu_id: int) -> None:
178-
SecOC_PDU.unregister_secoc_protected_pdu(pdu_id)
102+
def m2i(self, pkt, m): # type: ignore
103+
# type: (Optional[Packet], bytes) -> Packet
104+
return self.guess_pkt_cls(pkt, m)
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
# This file is part of Scapy
3+
# See https://scapy.net/ for more information
4+
# Copyright (C) Nils Weiss <[email protected]>
5+
6+
# scapy.contrib.description = AUTOSAR Secure On-Board Communication PDUs
7+
# scapy.contrib.status = loads
8+
9+
"""
10+
SecOC PDU
11+
"""
12+
import struct
13+
14+
from scapy.config import conf
15+
from scapy.contrib.automotive.autosar.secoc import SecOCMixin, PduPayloadField
16+
from scapy.base_classes import Packet_metaclass
17+
from scapy.fields import (XByteField, FieldLenField, XStrFixedLenField,
18+
FlagsField, XBitField, ShortField)
19+
from scapy.layers.can import CANFD
20+
from scapy.packet import Raw, Packet
21+
22+
# Typing imports
23+
from typing import (
24+
Any,
25+
Optional,
26+
Tuple,
27+
)
28+
29+
30+
class SecOC_CANFD(CANFD, SecOCMixin):
31+
name = 'SecOC_CANFD'
32+
fields_desc = [
33+
FlagsField('flags', 0, 3, ['error',
34+
'remote_transmission_request',
35+
'extended']),
36+
XBitField('identifier', 0, 29),
37+
FieldLenField('length', None, length_of='pdu_payload',
38+
fmt='B', adjust=lambda pkt, x: x + 4),
39+
FlagsField('fd_flags', 4, 8, [
40+
'bit_rate_switch', 'error_state_indicator', 'fd_frame']),
41+
ShortField('reserved', 0),
42+
PduPayloadField('pdu_payload',
43+
Raw(),
44+
guess_pkt_cls=lambda pkt, data: SecOC_CANFD.get_pdu_payload_cls(pkt, data), # noqa: E501
45+
length_from=lambda pkt: pkt.length - 4),
46+
XByteField("tfv", 0), # truncated freshness value
47+
XStrFixedLenField("tmac", None, length=3)] # truncated message authentication code # noqa: E501
48+
49+
def secoc_authenticate(self) -> None:
50+
self.tfv = struct.unpack(">B", self.get_secoc_freshness_value()[-1:])[0]
51+
self.tmac = self.get_message_authentication_code()[0:3]
52+
53+
def secoc_verify(self) -> bool:
54+
return self.get_message_authentication_code()[0:3] == self.tmac
55+
56+
def get_secoc_payload(self) -> bytes:
57+
"""Override this method for customization
58+
"""
59+
return bytes(self.pdu_payload)
60+
61+
@classmethod
62+
def dispatch_hook(cls, s=None, *_args, **_kwds):
63+
# type: (Optional[bytes], Any, Any) -> Packet_metaclass
64+
"""dispatch_hook determines if PDU is protected by SecOC.
65+
If PDU is protected, SecOC_PDU will be returned, otherwise AutoSAR PDU
66+
will be returned.
67+
"""
68+
if s is None:
69+
return SecOC_CANFD
70+
if len(s) < 4:
71+
return Raw
72+
identifier = struct.unpack('>I', s[0:4])[0] & 0x1FFFFFFF
73+
if identifier in cls.secoc_protected_pdus_by_identifier:
74+
return SecOC_CANFD
75+
else:
76+
return CANFD
77+
78+
@classmethod
79+
def get_pdu_payload_cls(cls,
80+
pkt: Packet,
81+
data: bytes
82+
) -> Packet:
83+
try:
84+
klass = cls.pdu_payload_cls_by_identifier[pkt.identifier]
85+
except KeyError:
86+
klass = conf.raw_layer
87+
return klass(data, _parent=pkt)
88+
89+
def extract_padding(self, s):
90+
# type: (bytes) -> Tuple[bytes, Optional[bytes]]
91+
return b"", s
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
# SPDX-License-Identifier: GPL-2.0-only
2+
# This file is part of Scapy
3+
# See https://scapy.net/ for more information
4+
# Copyright (C) Nils Weiss <[email protected]>
5+
6+
# scapy.contrib.description = AUTOSAR Secure On-Board Communication PDUs
7+
# scapy.contrib.status = loads
8+
9+
"""
10+
SecOC PDU
11+
"""
12+
import struct
13+
14+
from scapy.config import conf
15+
from scapy.contrib.automotive.autosar.secoc import SecOCMixin, PduPayloadField
16+
from scapy.base_classes import Packet_metaclass
17+
from scapy.contrib.automotive.autosar.pdu import PDU
18+
from scapy.fields import (XByteField, XIntField, PacketListField,
19+
FieldLenField, XStrFixedLenField)
20+
from scapy.packet import Packet, Raw
21+
22+
# Typing imports
23+
from typing import (
24+
Any,
25+
Optional,
26+
Tuple,
27+
Type,
28+
)
29+
30+
31+
class SecOC_PDU(Packet, SecOCMixin):
32+
name = 'SecOC_PDU'
33+
fields_desc = [
34+
XIntField('pdu_id', 0),
35+
FieldLenField('pdu_payload_len', None,
36+
fmt="I",
37+
length_of="pdu_payload",
38+
adjust=lambda pkt, x: x + 4),
39+
PduPayloadField('pdu_payload',
40+
Raw(),
41+
guess_pkt_cls=lambda pkt, data: SecOC_PDU.get_pdu_payload_cls(pkt, data), # noqa: E501
42+
length_from=lambda pkt: pkt.pdu_payload_len - 4),
43+
XByteField("tfv", 0), # truncated freshness value
44+
XStrFixedLenField("tmac", None, length=3)] # truncated message authentication code # noqa: E501
45+
46+
def secoc_authenticate(self) -> None:
47+
self.tfv = struct.unpack(">B", self.get_secoc_freshness_value()[-1:])[0]
48+
self.tmac = self.get_message_authentication_code()[0:3]
49+
50+
def secoc_verify(self) -> bool:
51+
return self.get_message_authentication_code()[0:3] == self.tmac
52+
53+
def get_secoc_payload(self) -> bytes:
54+
"""Override this method for customization
55+
"""
56+
return self.pdu_payload
57+
58+
@classmethod
59+
def dispatch_hook(cls, s=None, *_args, **_kwds):
60+
# type: (Optional[bytes], Any, Any) -> Packet_metaclass
61+
"""dispatch_hook determines if PDU is protected by SecOC.
62+
If PDU is protected, SecOC_PDU will be returned, otherwise AutoSAR PDU
63+
will be returned.
64+
"""
65+
if s is None:
66+
return SecOC_PDU
67+
if len(s) < 4:
68+
return Raw
69+
identifier = struct.unpack('>I', s[0:4])[0]
70+
if identifier in cls.secoc_protected_pdus_by_identifier:
71+
return SecOC_PDU
72+
else:
73+
return PDU
74+
75+
@classmethod
76+
def get_pdu_payload_cls(cls,
77+
pkt: Packet,
78+
data: bytes
79+
) -> Packet:
80+
try:
81+
klass = cls.pdu_payload_cls_by_identifier[pkt.pdu_id]
82+
except KeyError:
83+
klass = conf.raw_layer
84+
return klass(data, _parent=pkt)
85+
86+
def extract_padding(self, s):
87+
# type: (bytes) -> Tuple[bytes, Optional[bytes]]
88+
return b"", s
89+
90+
91+
class SecOC_PDUTransport(Packet):
92+
"""
93+
Packet representing SecOC_PDUTransport containing multiple PDUs
94+
"""
95+
96+
name = 'SecOC_PDUTransport'
97+
fields_desc = [
98+
PacketListField("pdus", [SecOC_PDU()], pkt_cls=SecOC_PDU)
99+
]
100+
101+
@staticmethod
102+
def register_secoc_protected_pdu(pdu_id: int,
103+
pdu_payload_cls: Type[Packet] = Raw
104+
) -> None:
105+
SecOC_PDU.register_secoc_protected_pdu(pdu_id, pdu_payload_cls)
106+
107+
@staticmethod
108+
def unregister_secoc_protected_pdu(pdu_id: int) -> None:
109+
SecOC_PDU.unregister_secoc_protected_pdu(pdu_id)

test/contrib/automotive/autosar/secoc.uts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
= Load Contrib Layer
1313

14-
load_contrib("automotive.autosar.secoc")
14+
load_contrib("automotive.autosar.secoc_pdu")
1515

1616
= Prepare SecOC keys
1717

0 commit comments

Comments
 (0)