Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
190 changes: 189 additions & 1 deletion poetry.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ dependencies = [
"pyyaml>=6.0.2,<7.0.0",
"dependency-injector>=4.48.1,<5.0.0",
"click>=8.1.0,<9.0.0",
"rich>=13.7.0,<14.0.0"
"rich>=13.7.0,<14.0.0",
"cryptography>=46.0.3,<47.0.0"
]

[project.urls]
Expand Down
12 changes: 10 additions & 2 deletions src/omnibase_core/models/security/model_chain_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@
with structured metric fields.
"""

from pydantic import BaseModel, Field
from pydantic import BaseModel, ConfigDict, Field


class ModelChainMetrics(BaseModel):
"""Metrics for signature chain operations."""
"""Metrics for signature chain operations.

Note:
This model uses frozen=True for immutability and from_attributes=True
to support pytest-xdist parallel execution where class identity may
differ between workers.
"""

model_config = ConfigDict(frozen=True, from_attributes=True)

total_signatures: int = Field(default=0, description="Total signatures in chain")
valid_signatures: int = Field(default=0, description="Number of valid signatures")
Expand Down
12 changes: 10 additions & 2 deletions src/omnibase_core/models/security/model_chain_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,19 @@

from uuid import UUID

from pydantic import BaseModel, Field
from pydantic import BaseModel, ConfigDict, Field


class ModelChainValidation(BaseModel):
"""Chain validation details."""
"""Chain validation details.

Note:
This model uses frozen=True for immutability and from_attributes=True
to support pytest-xdist parallel execution where class identity may
differ between workers.
"""

model_config = ConfigDict(frozen=True, from_attributes=True)

chain_id: UUID = Field(default=..., description="Chain identifier")
envelope_id: UUID = Field(default=..., description="Envelope identifier")
Expand Down
12 changes: 10 additions & 2 deletions src/omnibase_core/models/security/model_compliance_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,19 @@
Compliance and regulatory metadata for secure event envelopes.
"""

from pydantic import BaseModel, Field
from pydantic import BaseModel, ConfigDict, Field


class ModelComplianceMetadata(BaseModel):
"""Compliance and regulatory metadata."""
"""Compliance and regulatory metadata.

Note:
This model uses frozen=True for immutability and from_attributes=True
to support pytest-xdist parallel execution where class identity may
differ between workers.
"""

model_config = ConfigDict(frozen=True, from_attributes=True)

frameworks: list[str] = Field(
default_factory=list,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,12 +87,13 @@ def validate_algorithm_name(cls, v: str) -> str:
from omnibase_core.enums.enum_core_error_code import EnumCoreErrorCode
from omnibase_core.errors import ModelOnexError

msg = f"Unsupported encryption algorithm: {v}"
raise ModelOnexError(
msg,
error_code=EnumCoreErrorCode.VALIDATION_FAILED,
component="encryption_algorithm",
operation="validate_algorithm_name",
message=f"Unsupported encryption algorithm: {v}",
error_code=EnumCoreErrorCode.VALIDATION_ERROR,
context={
"algorithm": v,
"supported_algorithms": sorted(known_algorithms),
},
)

return v
Expand Down
73 changes: 70 additions & 3 deletions src/omnibase_core/models/security/model_encryption_metadata.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,89 @@
"""Encryption Metadata Model.

Metadata for encrypted envelope payloads.
Metadata for encrypted envelope payloads including cryptographic parameters,
key identifiers, and integrity verification data.

This module provides the ModelEncryptionMetadata class used to store all
cryptographic metadata required to decrypt an encrypted payload in a
ModelSecureEventEnvelope.

Example:
>>> from uuid import uuid4
>>> metadata = ModelEncryptionMetadata(
... algorithm="AES-256-GCM",
... key_id=uuid4(),
... iv="base64_encoded_iv==",
... auth_tag="base64_encoded_tag==",
... )
"""

from uuid import UUID

from pydantic import BaseModel, Field
from pydantic import BaseModel, ConfigDict, Field


class ModelEncryptionMetadata(BaseModel):
"""Metadata for encrypted envelope payloads."""
"""Metadata for encrypted envelope payloads.

Stores all cryptographic parameters required for decryption of encrypted
payloads in ModelSecureEventEnvelope. This includes the algorithm used,
key identifiers, initialization vector, authentication tag, and optional
fields for asymmetric encryption scenarios.

The model supports both symmetric encryption (single key) and asymmetric
encryption with multiple recipients (per-recipient encrypted keys).

Attributes:
algorithm: The encryption algorithm used (e.g., "AES-256-GCM").
Currently only AES-256-GCM is supported for encryption.
key_id: Unique identifier for the encryption key. For PBKDF2-derived
keys, this UUID's bytes are used as the salt.
iv: Base64-encoded initialization vector (nonce). For AES-GCM,
this is typically 12 bytes (96 bits).
auth_tag: Base64-encoded authentication tag from AES-GCM. This
16-byte tag ensures ciphertext integrity and authenticity.
aad_hash: SHA-256 hash of the Additional Authenticated Data (AAD)
used during encryption. AAD binds the ciphertext to envelope
metadata to prevent ciphertext transplantation attacks.
encrypted_key: Base64-encoded encrypted symmetric key for asymmetric
encryption scenarios where the data key is encrypted with a
public key.
recipient_keys: Mapping of recipient identifiers to their individually
encrypted copies of the symmetric key, enabling multi-recipient
encryption.

Example:
>>> from uuid import uuid4
>>> # Create metadata for AES-256-GCM encryption
>>> metadata = ModelEncryptionMetadata(
... algorithm="AES-256-GCM",
... key_id=uuid4(),
... iv="SGVsbG9Xb3JsZA==",
... auth_tag="dGVzdF9hdXRoX3RhZw==",
... aad_hash="abc123...",
... )
>>> metadata.algorithm
'AES-256-GCM'

Note:
This model uses from_attributes=True to support pytest-xdist parallel
execution where class identity may differ between workers.
"""

model_config = ConfigDict(frozen=True, extra="ignore", from_attributes=True)

algorithm: str = Field(
default=..., description="Encryption algorithm (AES-256-GCM, etc.)"
)
key_id: UUID = Field(default=..., description="Encryption key identifier")
iv: str = Field(default=..., description="Base64-encoded initialization vector")
auth_tag: str = Field(default=..., description="Base64-encoded authentication tag")
aad_hash: str | None = Field(
default=None,
description="SHA-256 hash of Additional Authenticated Data (AAD) used in encryption. "
"AAD binds ciphertext to envelope metadata (envelope_id, source_node_id, timestamp) "
"to prevent ciphertext transplantation attacks.",
)
encrypted_key: str | None = Field(
default=None,
description="Encrypted symmetric key (for asymmetric)",
Expand Down
31 changes: 20 additions & 11 deletions src/omnibase_core/models/security/model_nodesignature.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from pydantic import BaseModel, ConfigDict, Field, field_validator

from omnibase_core.constants import TIMEOUT_LONG_MS
from omnibase_core.enums.enum_core_error_code import EnumCoreErrorCode
from omnibase_core.enums.enum_node_operation import EnumNodeOperation
from omnibase_core.enums.enum_signature_algorithm import EnumSignatureAlgorithm
from omnibase_core.errors import ModelOnexError
Expand All @@ -30,7 +31,9 @@ class ModelNodeSignature(BaseModel):
through PKI-based digital signatures.
"""

model_config = ConfigDict(validate_assignment=True, extra="forbid")
model_config = ConfigDict(
validate_assignment=True, extra="forbid", from_attributes=True
)
MAX_HOP_INDEX: ClassVar[int] = 1000
MAX_PROCESSING_TIME_MS: ClassVar[int] = TIMEOUT_LONG_MS
MAX_SIGNATURE_TIME_MS: ClassVar[int] = 60000
Expand Down Expand Up @@ -114,7 +117,8 @@ def validate_hop_index(cls, v: int) -> int:
if v > cls.MAX_HOP_INDEX:
raise ModelOnexError(
message=f"Hop index too large - possible routing loop (max: {cls.MAX_HOP_INDEX})",
error_code="ONEX_NODE_SIGNATURE_VALIDATION_ERROR",
error_code=EnumCoreErrorCode.VALIDATION_ERROR,
context={"hop_index": v, "max_hop_index": cls.MAX_HOP_INDEX},
)
return v

Expand All @@ -124,11 +128,12 @@ def validate_signature_format(cls, v: str) -> str:
"""Validate signature is properly base64 encoded."""
try:
base64.b64decode(v, validate=True)
except Exception as e:
except ValueError as e:
raise ModelOnexError(
message=f"Signature must be valid base64 encoding: {e!s}",
error_code="ONEX_NODE_SIGNATURE_VALIDATION_ERROR",
)
error_code=EnumCoreErrorCode.VALIDATION_ERROR,
context={"signature_preview": v[:20] + "..." if len(v) > 20 else v},
) from e
return v

@field_validator("envelope_state_hash")
Expand All @@ -138,15 +143,17 @@ def validate_hash_format(cls, v: str) -> str:
if len(v) != 64:
raise ModelOnexError(
message="Envelope state hash must be 64 characters (SHA-256)",
error_code="ONEX_NODE_SIGNATURE_VALIDATION_ERROR",
error_code=EnumCoreErrorCode.VALIDATION_ERROR,
context={"hash_length": len(v), "expected_length": 64},
)
try:
int(v, 16)
except ValueError:
except ValueError as e:
raise ModelOnexError(
message="Envelope state hash must be hexadecimal",
error_code="ONEX_NODE_SIGNATURE_VALIDATION_ERROR",
)
error_code=EnumCoreErrorCode.VALIDATION_ERROR,
context={"hash_preview": v[:20] + "..." if len(v) > 20 else v},
) from e
return v

@classmethod
Expand Down Expand Up @@ -187,7 +194,8 @@ def create_routing_signature(
if hop_index <= 0:
raise ModelOnexError(
message="Routing signature must have hop_index > 0",
error_code="ONEX_NODE_SIGNATURE_ERROR",
error_code=EnumCoreErrorCode.VALIDATION_ERROR,
context={"hop_index": hop_index, "operation": "routing"},
)
return cls(
node_id=node_id,
Expand Down Expand Up @@ -217,7 +225,8 @@ def create_destination_signature(
if hop_index <= 0:
raise ModelOnexError(
message="Destination signature must have hop_index > 0",
error_code="ONEX_NODE_SIGNATURE_ERROR",
error_code=EnumCoreErrorCode.VALIDATION_ERROR,
context={"hop_index": hop_index, "operation": "destination"},
)
return cls(
node_id=node_id,
Expand Down
Loading