OSCORE (OSCORE v0.2.0)

Copy Markdown View Source

RFC 8613 (OSCORE) message protection for CoAP, with the crypto/protocol core implemented in Rust.

This module handles security context derivation, message protection and verification, and replay-window bookkeeping. Full CoAP message framing and OSCORE option classification (Class E/I/U, see RFC 8613 §4.1) are the caller's responsibility: inner_options is the already-serialized CoAP Class E options to encrypt, and class_i_options is the already-serialized Class I options to authenticate (unencrypted).

Only the mandatory AES-CCM-16-64-128 / HKDF-SHA-256 suite is supported.

Summary

Types

An atom describing why an OSCORE operation failed.

Functions

Derives a Security Context (RFC 8613 §3.2) from a Master Secret and the Sender/Recipient IDs.

Protects an outgoing CoAP request (RFC 8613 §8.1).

Protects an outgoing CoAP response (RFC 8613 §8.3) matching echo.

Returns the next Sender Sequence Number ctx will use to protect an outgoing message.

Verifies an incoming CoAP request (RFC 8613 §8.2): decrypts it and checks the Replay Window.

Verifies an incoming CoAP response (RFC 8613 §8.4) against the RequestEcho of the request it answers.

Types

error_reason()

@type error_reason() ::
  :sequence_number_exhausted
  | :malformed_option
  | :malformed_plaintext
  | :decryption_failed
  | :unknown_kid
  | :unknown_id_context
  | :replayed
  | :too_old
  | :id_too_long

An atom describing why an OSCORE operation failed.

Functions

derive_context(opts)

@spec derive_context(keyword()) ::
  {:ok, OSCORE.Context.t()} | {:error, error_reason()}

Derives a Security Context (RFC 8613 §3.2) from a Master Secret and the Sender/Recipient IDs.

Sender and Recipient IDs must be at most 7 bytes (the AEAD nonce length minus 6, per RFC 8613 §3.3); a longer ID returns {:error, :id_too_long}.

Options

  • :master_secret (required)
  • :sender_id (required)
  • :recipient_id (required)
  • :master_salt - defaults to <<>> (the RFC 8613 default)
  • :id_context - defaults to nil (not present)
  • :sender_seq - the initial Sender Sequence Number, defaults to 0. Restore a persisted value here to avoid AEAD nonce reuse across restarts (RFC 8613 Appendix B.1); see sender_seq/1.

protect_request(context, opts)

@spec protect_request(
  OSCORE.Context.t(),
  keyword()
) ::
  {:ok, OSCORE.Protected.t(), OSCORE.RequestEcho.t()} | {:error, error_reason()}

Protects an outgoing CoAP request (RFC 8613 §8.1).

Encrypts code, inner_options, and payload under a freshly incremented Sender Sequence Number, and returns the protected message along with a RequestEcho that must be kept around to later protect or verify the matching response.

Options

  • :code (required)
  • :inner_options - defaults to <<>>
  • :payload - defaults to <<>>

protect_response(context, echo, opts)

@spec protect_response(OSCORE.Context.t(), OSCORE.RequestEcho.t(), keyword()) ::
  {:ok, OSCORE.Protected.t()} | {:error, error_reason()}

Protects an outgoing CoAP response (RFC 8613 §8.3) matching echo.

By default reuses the request's AEAD nonce (no Partial IV is sent - the common case for a single response). Pass partial_iv: :new to generate a fresh Partial IV instead, as required for Observe notifications after the first one.

Options

  • :code (required)
  • :inner_options - defaults to <<>>
  • :payload - defaults to <<>>
  • :partial_iv - :reuse_request (default) or :new

sender_seq(context)

@spec sender_seq(OSCORE.Context.t()) :: non_neg_integer()

Returns the next Sender Sequence Number ctx will use to protect an outgoing message.

Persist this value and restore it via derive_context/1's :sender_seq option after a restart so the same AEAD nonce is never reused under the same key (RFC 8613 Appendix B.1). Persist it before the message that consumes it is sent (and consider restoring to the persisted value plus a safety margin), since a crash between using and persisting a sequence number would otherwise reuse it.

unprotect_request(context, opts)

@spec unprotect_request(
  OSCORE.Context.t(),
  keyword()
) ::
  {:ok, code :: byte(), inner_options :: binary(), payload :: binary(),
   OSCORE.RequestEcho.t()}
  | {:error, error_reason()}

Verifies an incoming CoAP request (RFC 8613 §8.2): decrypts it and checks the Replay Window.

Returns the decrypted code, inner_options, and payload, plus the RequestEcho needed to protect the matching response.

Options

  • :oscore_option (required)
  • :ciphertext (required)
  • :class_i_options - defaults to <<>>

unprotect_response(context, echo, opts)

@spec unprotect_response(OSCORE.Context.t(), OSCORE.RequestEcho.t(), keyword()) ::
  {:ok, code :: byte(), inner_options :: binary(), payload :: binary()}
  | {:error, error_reason()}

Verifies an incoming CoAP response (RFC 8613 §8.4) against the RequestEcho of the request it answers.

Options

  • :oscore_option (required)
  • :ciphertext (required)
  • :class_i_options - defaults to <<>>