aiocoap.oscore module¶
This module contains the tools to send OSCORE secured messages.
It only deals with the algorithmic parts, the security context and protection
and unprotection of messages. It does not touch on the integration of OSCORE in
the larger aiocoap stack of having a context or requests; that’s what
aiocoap.transports.osore
is for.`
- aiocoap.oscore.decode_dss_signature()¶
- aiocoap.oscore.encode_dss_signature()¶
- class aiocoap.oscore.CodeStyle(request, response)¶
Bases:
_CodeStyle
- FETCH_CONTENT = CodeStyle(request=<Request Code 5 "FETCH">, response=<Successful Response Code 69 "2.05 Content">)¶
- POST_CHANGED = CodeStyle(request=<Request Code 2 "POST">, response=<Successful Response Code 68 "2.04 Changed">)¶
- exception aiocoap.oscore.NotAProtectedMessage(message, plain_message)¶
Bases:
Error
,ValueError
Raised when verification is attempted on a non-OSCORE message
- exception aiocoap.oscore.ProtectionInvalid¶
Bases:
Error
,ValueError
Raised when verification of an OSCORE message fails
- exception aiocoap.oscore.DecodeError¶
Bases:
ProtectionInvalid
Raised when verification of an OSCORE message fails because CBOR or compressed data were erroneous
- exception aiocoap.oscore.ReplayError¶
Bases:
ProtectionInvalid
Raised when verification of an OSCORE message fails because the sequence numbers was already used
- exception aiocoap.oscore.ReplayErrorWithEcho(secctx, request_id, echo)¶
Bases:
ProtectionInvalid
,RenderableError
Raised when verification of an OSCORE message fails because the recipient replay window is uninitialized, but a 4.01 Echo can be constructed with the data in the exception that can lead to the client assisting in replay window recovery
- to_message()¶
Create a CoAP message that should be sent when this exception is rendered
Bases:
Error
,ValueError
Raised when a context is (currently or permanently) unavailable for protecting or unprotecting a message
- class aiocoap.oscore.RequestIdentifiers(kid, partial_iv, nonce, can_reuse_nonce, request_code)¶
Bases:
object
A container for details that need to be passed along from the (un)protection of a request to the (un)protection of the response; these data ensure that the request-response binding process works by passing around the request’s partial IV.
Users of this module should never create or interact with instances, but just pass them around.
- get_reusable_nonce_and_piv()¶
Return the nonce and the partial IV if can_reuse_nonce is True, and set can_reuse_nonce to False.
- class aiocoap.oscore.AeadAlgorithm¶
Bases:
object
- abstract encrypt(plaintext, aad, key, iv)¶
Return ciphertext + tag for given input data
- abstract decrypt(ciphertext_and_tag, aad, key, iv)¶
Reverse encryption. Must raise ProtectionInvalid on any error stemming from untrusted data.
- class aiocoap.oscore.AES_CCM¶
Bases:
AeadAlgorithm
AES-CCM implemented using the Python cryptography library
- classmethod encrypt(plaintext, aad, key, iv)¶
Return ciphertext + tag for given input data
- classmethod decrypt(ciphertext_and_tag, aad, key, iv)¶
Reverse encryption. Must raise ProtectionInvalid on any error stemming from untrusted data.
- class aiocoap.oscore.AES_CCM_16_64_128¶
Bases:
AES_CCM
- value = 10¶
- key_bytes = 16¶
- tag_bytes = 8¶
- iv_bytes = 13¶
- class aiocoap.oscore.AES_CCM_16_64_256¶
Bases:
AES_CCM
- value = 11¶
- key_bytes = 32¶
- tag_bytes = 8¶
- iv_bytes = 13¶
- class aiocoap.oscore.AES_CCM_64_64_128¶
Bases:
AES_CCM
- value = 12¶
- key_bytes = 16¶
- tag_bytes = 8¶
- iv_bytes = 7¶
- class aiocoap.oscore.AES_CCM_64_64_256¶
Bases:
AES_CCM
- value = 13¶
- key_bytes = 32¶
- tag_bytes = 8¶
- iv_bytes = 7¶
- class aiocoap.oscore.AES_CCM_16_128_128¶
Bases:
AES_CCM
- value = 30¶
- key_bytes = 16¶
- tag_bytes = 16¶
- iv_bytes = 13¶
- class aiocoap.oscore.AES_CCM_16_128_256¶
Bases:
AES_CCM
- value = 31¶
- key_bytes = 32¶
- tag_bytes = 16¶
- iv_bytes = 13¶
- class aiocoap.oscore.AES_CCM_64_128_128¶
Bases:
AES_CCM
- value = 32¶
- key_bytes = 16¶
- tag_bytes = 16¶
- iv_bytes = 7¶
- class aiocoap.oscore.AES_CCM_64_128_256¶
Bases:
AES_CCM
- value = 33¶
- key_bytes = 32¶
- tag_bytes = 16¶
- iv_bytes = 7¶
- class aiocoap.oscore.AES_GCM¶
Bases:
AeadAlgorithm
AES-GCM implemented using the Python cryptography library
- iv_bytes = 12¶
- classmethod encrypt(plaintext, aad, key, iv)¶
Return ciphertext + tag for given input data
- classmethod decrypt(ciphertext_and_tag, aad, key, iv)¶
Reverse encryption. Must raise ProtectionInvalid on any error stemming from untrusted data.
- class aiocoap.oscore.ChaCha20Poly1305¶
Bases:
AeadAlgorithm
- value = 24¶
- key_bytes = 32¶
- tag_bytes = 16¶
- iv_bytes = 12¶
- classmethod encrypt(plaintext, aad, key, iv)¶
Return ciphertext + tag for given input data
- classmethod decrypt(ciphertext_and_tag, aad, key, iv)¶
Reverse encryption. Must raise ProtectionInvalid on any error stemming from untrusted data.
- class aiocoap.oscore.AlgorithmCountersign¶
Bases:
object
A fully parameterized COSE countersign algorithm
An instance is able to provide all the alg_signature, par_countersign and par_countersign_key parameters taht go into the Group OSCORE algorithms field.
- abstract sign(body, external_aad, private_key)¶
Return the signature produced by the key when using CounterSignature0 as describe in draft-ietf-cose-countersign-01
- abstract verify(signature, body, external_aad, public_key)¶
Verify a signature in analogy to sign
- abstract generate()¶
Return a usable private key
- abstract public_from_private(private_key)¶
Given a private key, derive the publishable key
- abstract property signature_length¶
The length of a signature using this algorithm
- abstract property curve_number¶
Registered curve number used with this algorithm.
Only used for verification of credentials’ details
- class aiocoap.oscore.AlgorithmStaticStatic¶
Bases:
object
- abstract staticstatic(private_key, public_key)¶
Derive a shared static-static secret from a private and a public key
- class aiocoap.oscore.Ed25519¶
Bases:
AlgorithmCountersign
- sign(body, aad, private_key)¶
Return the signature produced by the key when using CounterSignature0 as describe in draft-ietf-cose-countersign-01
- verify(signature, body, aad, public_key)¶
Verify a signature in analogy to sign
- generate()¶
Return a usable private key
- public_from_private(private_key)¶
Given a private key, derive the publishable key
- value = -8¶
- curve_number = 6¶
- signature_length = 64¶
- class aiocoap.oscore.EcdhSsHkdf256¶
Bases:
AlgorithmStaticStatic
- value = -27¶
- generate()¶
Return a usable private key
- public_from_private(private_key)¶
Given a private key, derive the publishable key
- staticstatic(private_key, public_key)¶
Derive a shared static-static secret from a private and a public key
- class aiocoap.oscore.ECDSA_SHA256_P256¶
Bases:
AlgorithmCountersign
,AlgorithmStaticStatic
- from_public_parts(x: bytes, y: bytes)¶
Create a public key from its COSE values
- from_private_parts(x: bytes, y: bytes, d: bytes)¶
- sign(body, aad, private_key)¶
Return the signature produced by the key when using CounterSignature0 as describe in draft-ietf-cose-countersign-01
- verify(signature, body, aad, public_key)¶
Verify a signature in analogy to sign
- generate()¶
Return a usable private key
- public_from_private(private_key)¶
Given a private key, derive the publishable key
- staticstatic(private_key, public_key)¶
Derive a shared static-static secret from a private and a public key
- value = -7¶
- curve_number = 1¶
- signature_length = 64¶
- class aiocoap.oscore.BaseSecurityContext¶
Bases:
object
- external_aad_is_group = False¶
- authenticated_claims = []¶
- alg_aead: Optional[AeadAlgorithm]¶
- property algorithm¶
- class aiocoap.oscore.CanProtect¶
Bases:
BaseSecurityContext
- is_signing = False¶
- responses_send_kid = False¶
- protect(message, request_id=None, *, kid_context=True)¶
Given a plain CoAP message, create a protected message that contains message’s options in the inner or outer CoAP message as described in OSCOAP.
If the message is a response to a previous message, the additional data from unprotecting the request are passed in as request_id. When request data is present, its partial IV is reused if possible. The security context’s ID context is encoded in the resulting message unless kid_context is explicitly set to a False; other values for the kid_context can be passed in as byte string in the same parameter.
- new_sequence_number()¶
Return a new sequence number; the implementation is responsible for never returning the same value twice in a given security context.
May raise ContextUnavailable.
- abstract post_seqnoincrease()¶
Ensure that sender_sequence_number is stored
- context_from_response(unprotected_bag) CanUnprotect ¶
When receiving a response to a request protected with this security context, pick the security context with which to unprotect the response given the unprotected information from the Object-Security option.
This allow picking the right security context in a group response, and helps getting a new short-lived context for B.2 mode. The default behaivor is returning self.
- alg_aead: Optional[AeadAlgorithm]¶
- class aiocoap.oscore.CanUnprotect¶
Bases:
BaseSecurityContext
- unprotect(protected_message, request_id=None)¶
- context_for_response() CanProtect ¶
After processing a request with this context, with which security context should an outgoing response be protected? By default, it’s the same context.
- alg_aead: Optional[AeadAlgorithm]¶
- class aiocoap.oscore.SecurityContextUtils¶
Bases:
BaseSecurityContext
- derive_keys(master_salt, master_secret)¶
Populate sender_key, recipient_key and common_iv from the algorithm, hash function and id_context already configured beforehand, and from the passed salt and secret.
- get_oscore_context_for(unprotected)¶
Return a sutiable context (most easily self) for an incoming request if its unprotected data (COSE_KID, COSE_KID_CONTEXT) fit its description. If it doesn’t match, it returns None.
The default implementation just strictly checks for whether kid and any kid context match (not matching if a local KID context is set but none is given in the request); modes like Group OSCORE can spin up aspect objects here.
- alg_aead: Optional[AeadAlgorithm]¶
- class aiocoap.oscore.ReplayWindow(size, strike_out_callback)¶
Bases:
object
A regular replay window of a fixed size.
It is implemented as an index and a bitfield (represented by an integer) whose least significant bit represents the seqyence number of the index, and a 1 indicates that a number was seen. No shenanigans around implicit leading ones (think floating point normalization) happen.
>>> w = ReplayWindow(32, lambda: None) >>> w.initialize_empty() >>> w.strike_out(5) >>> w.is_valid(3) True >>> w.is_valid(5) False >>> w.strike_out(0) >>> w.strike_out(1) >>> w.strike_out(2) >>> w.is_valid(1) False
Jumping ahead by the window size invalidates older numbers:
>>> w.is_valid(4) True >>> w.strike_out(35) >>> w.is_valid(4) True >>> w.strike_out(36) >>> w.is_valid(4) False
Usage safety¶
For every key, the replay window can only be initielized empty once. On later uses, it needs to be persisted by storing the output of self.persist() somewhere and loaded from that persisted data.
It is acceptable to store persistance data in the strike_out_callback, but that must then ensure that the data is written (flushed to a file or committed to a database), but that is usually inefficient.
Stability¶
This class is not considered for stabilization yet and an implementation detail of the SecurityContext implementation(s).
- is_initialized()¶
- initialize_empty()¶
- initialize_from_persisted(persisted)¶
- initialize_from_freshlyseen(seen)¶
Initialize the replay window with a particular value that is just being observed in a fresh (ie. generated by the peer later than any messages processed before state was lost here) message. This marks the seen sequence number and all preceding it as invalid, and and all later ones as valid.
- is_valid(number)¶
- strike_out(number)¶
- persist()¶
Return a dict containing internal state which can be passed to init to recreated the replay window.
- class aiocoap.oscore.FilesystemSecurityContext(basedir, sequence_number_chunksize_start=10, sequence_number_chunksize_limit=10000)¶
Bases:
CanProtect
,CanUnprotect
,SecurityContextUtils
Security context stored in a directory as distinct files containing containing
Master secret, master salt, sender and recipient ID, optionally algorithm, the KDF hash function, and replay window size (settings.json and secrets.json, where the latter is typically readable only for the user)
sequence numbers and replay windows (sequence.json, the only file the process needs write access to)
The static parameters can all either be placed in settings.json or secrets.json, but must not be present in both; the presence of either file is sufficient.
Warning
Security contexts must never be copied around and used after another copy was used. They should only ever be moved, and if they are copied (eg. as a part of a system backup), restored contexts must not be used again; they need to be replaced with freshly created ones.
An additional file named lock is created to prevent the accidental use of a context by to concurrent programs.
Note that the sequence number file is updated in an atomic fashion which requires file creation privileges in the directory. If privilege separation between settings/key changes and sequence number changes is desired, one way to achieve that on Linux is giving the aiocoap process’s user group write permissions on the directory and setting the sticky bit on the directory, thus forbidding the user to remove the settings/secret files not owned by him.
Writes due to sent sequence numbers are reduced by applying a variation on the mechanism of RFC8613 Appendix B.1.1 (incrementing the persisted sender seqence number in steps of k). That value is automatically grown from sequence_number_chunksize_start up to sequence_number_chunksize_limit. At runtime, the receive window is not stored but kept indeterminate. In case of an abnormal shutdown, the server uses the mechanism described in Appendix B.1.2 to recover.
- exception LoadError¶
Bases:
ValueError
Exception raised with a descriptive message when trying to load a faulty security context
- post_seqnoincrease()¶
Ensure that sender_sequence_number is stored
- alg_aead: Optional[AeadAlgorithm]¶
- class aiocoap.oscore.GroupContext¶
Bases:
BaseSecurityContext
- is_signing = True¶
- external_aad_is_group = True¶
- responses_send_kid = True¶
- alg_signature_enc: Optional[AeadAlgorithm]¶
- alg_signature: Optional[AlgorithmCountersign]¶
- alg_pairwise_key_agreement: Optional[AlgorithmCountersign]¶
- abstract property private_key¶
Private key used to sign outgoing messages.
Contexts not designed to send messages may raise a RuntimeError here; that necessity may later go away if some more accurate class modelling is found.
- abstract property recipient_public_key¶
Public key used to verify incoming messages.
Contexts not designed to receive messages (because they’d have aspects for that) may raise a RuntimeError here; that necessity may later go away if some more accurate class modelling is found.
- class aiocoap.oscore.SimpleGroupContext(alg_aead, hashfun, alg_signature, alg_signature_enc, alg_pairwise_key_agreement, group_id, master_secret, master_salt, sender_id, private_key, sender_auth_cred, peers, group_manager_cred=None, cred_fmt=13131313)¶
Bases:
GroupContext
,CanProtect
,CanUnprotect
,SecurityContextUtils
A context for an OSCORE group
This is a non-persistable version of a group context that does not support any group manager or rekeying; it is set up statically at startup.
It is intended for experimentation and demos, but aims to be correct enough to be usable securely.
- private_key = None¶
- alg_aead: Optional[AeadAlgorithm]¶
- alg_signature: Optional[AlgorithmCountersign]¶
- alg_signature_enc: Optional[AeadAlgorithm]¶
- alg_pairwise_key_agreement: Optional[AlgorithmCountersign]¶
- sender_auth_cred = None¶
- property recipient_public_key¶
Public key used to verify incoming messages.
Contexts not designed to receive messages (because they’d have aspects for that) may raise a RuntimeError here; that necessity may later go away if some more accurate class modelling is found.
- derive_keys(master_salt, master_secret)¶
Populate sender_key, recipient_key and common_iv from the algorithm, hash function and id_context already configured beforehand, and from the passed salt and secret.
- post_seqnoincrease()¶
No-op because it’s ephemeral
- context_from_response(unprotected_bag) CanUnprotect ¶
When receiving a response to a request protected with this security context, pick the security context with which to unprotect the response given the unprotected information from the Object-Security option.
This allow picking the right security context in a group response, and helps getting a new short-lived context for B.2 mode. The default behaivor is returning self.
- get_oscore_context_for(unprotected)¶
Return a sutiable context (most easily self) for an incoming request if its unprotected data (COSE_KID, COSE_KID_CONTEXT) fit its description. If it doesn’t match, it returns None.
The default implementation just strictly checks for whether kid and any kid context match (not matching if a local KID context is set but none is given in the request); modes like Group OSCORE can spin up aspect objects here.
- pairwise_for(recipient_id)¶
- for_sending_deterministic_requests(deterministic_id, target_server: Optional[bytes])¶
- aiocoap.oscore.verify_start(message)¶
Extract the unprotected COSE options from a message for the verifier to then pick a security context to actually verify the message. (Future versions may also report fields from both unprotected and protected, if the protected bag is ever used with OSCORE.).
Call this only requests; for responses, you’ll have to know the security context anyway, and there is usually no information to be gained.