Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send enrollment key as JWK #34

Merged
merged 1 commit into from
Dec 17, 2024
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
9 changes: 5 additions & 4 deletions nodeman/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,10 +152,11 @@ def command_create(args: argparse.Namespace) -> NodeBootstrapInformation:

create_response = response.json()
name = create_response["name"]
secret = create_response["secret"]
logging.info("Created node with name=%s secret=%s", name, secret)

return NodeBootstrapInformation(name=name, secret=secret)
logging.debug("Response: %s", json.dumps(create_response, indent=4))
logging.info("Created node with name=%s", name)

return NodeBootstrapInformation(name=name, key=create_response["key"])


def command_delete(args: argparse.Namespace) -> None:
Expand Down Expand Up @@ -209,7 +210,7 @@ def command_enroll(args: argparse.Namespace) -> NodeConfiguration:
if args.create:
node_bootstrap_information = command_create(args)
name = node_bootstrap_information.name
secret = node_bootstrap_information.secret
secret = node_bootstrap_information.key.k
else:
name = args.name
secret = args.secret
Expand Down
8 changes: 8 additions & 0 deletions nodeman/jose.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ class PublicOKP(BaseJWK):
x: Base64UrlString


class PrivateSymmetric(BaseJWK):
"""JWK: Private symmetric key"""

kty: Annotated[str, StringConstraints(pattern=r"^oct$")]
k: Base64UrlString
alg: str | None = None
jschlyter marked this conversation as resolved.
Show resolved Hide resolved


PublicJwk = PublicRSA | PublicEC | PublicOKP


Expand Down
4 changes: 2 additions & 2 deletions nodeman/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from pydantic.types import AwareDatetime

from .db_models import TapirNode
from .jose import PublicJwk, public_key_factory
from .jose import PrivateSymmetric, PublicJwk, public_key_factory
from .settings import MqttUrl

MAX_REQUEST_AGE = 300
Expand Down Expand Up @@ -65,7 +65,7 @@ class NodeCollection(BaseModel):

class NodeBootstrapInformation(BaseModel):
name: str = Field(title="Node name")
secret: str = Field(title="Enrollment secret")
key: PrivateSymmetric = Field(title="Enrollment JWK")


class NodeCertificate(BaseModel):
Expand Down
10 changes: 7 additions & 3 deletions nodeman/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,14 @@ def find_node(name: str) -> TapirNode:
status.HTTP_201_CREATED: {"model": NodeBootstrapInformation},
},
tags=["backend"],
response_model_exclude_none=True,
)
async def create_node(
request: Request, username: Annotated[str, Depends(get_current_username)], name: str | None = None
) -> NodeBootstrapInformation:
secret = JWK.generate(kty="oct", size=256).k
"""Create node"""

node_enrollment_key = JWK.generate(kty="oct", size=256, alg="HS256")
jschlyter marked this conversation as resolved.
Show resolved Hide resolved
domain = request.app.settings.nodes.domain

if name is None:
Expand All @@ -71,12 +74,13 @@ async def create_node(
logging.warning("Explicit node name %s not acceptable", name, extra={"nodename": name})
raise HTTPException(status.HTTP_400_BAD_REQUEST, detail="Invalid node name")

node_secret = TapirNodeSecret(name=node.name, secret=secret).save()
TapirNodeSecret(name=node.name, secret=node_enrollment_key.k).save()

nodes_created.add(1, {"creator": username})

logging.info("%s created node %s", username, node.name, extra={"username": username, "nodename": node.name})
return NodeBootstrapInformation(name=node.name, secret=node_secret.secret)

return NodeBootstrapInformation(name=node.name, key=node_enrollment_key.export(as_dict=True, private_key=True))


@router.get(
Expand Down
19 changes: 11 additions & 8 deletions tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ def _test_enroll(data_key: JWK, x509_key: PrivateKey, requested_name: str | None
assert response.status_code == status.HTTP_201_CREATED
create_response = response.json()
name = create_response["name"]
secret = create_response["secret"]
secret = create_response["key"]["k"]
if requested_name:
assert name == requested_name
logging.info("Got name=%s secret=%s", name, secret)
Expand All @@ -99,8 +99,8 @@ def _test_enroll(data_key: JWK, x509_key: PrivateKey, requested_name: str | None
#####################
# Enroll created node

hmac_key = JWK(kty="oct", k=secret)
hmac_alg = "HS256"
hmac_key = JWK(**create_response["key"])
hmac_alg = hmac_key.alg

data_alg = jwk_to_alg(data_key)

Expand Down Expand Up @@ -276,8 +276,10 @@ def test_enroll_bad_hmac_signature() -> None:
create_response = response.json()
name = create_response["name"]

hmac_key = JWK.generate(kty="oct")
hmac_alg = "HS256"
hmac_key = JWK.generate(kty="oct", size=256, alg="HS256")
hmac_alg = hmac_key.alg

assert hmac_alg == "HS256"

data_key = JWK.generate(kty=kty, crv=crv)
data_alg = jwk_to_alg(data_key)
Expand Down Expand Up @@ -321,10 +323,11 @@ def test_enroll_bad_data_signature() -> None:
assert response.status_code == status.HTTP_201_CREATED
create_response = response.json()
name = create_response["name"]
secret = create_response["secret"]

hmac_key = JWK(kty="oct", k=secret)
hmac_alg = "HS256"
hmac_key = JWK(**create_response["key"])
hmac_alg = hmac_key.alg

assert hmac_alg == "HS256"

data_key = JWK.generate(kty=kty, crv=crv)
bad_data_key = JWK.generate(kty=kty, crv=crv)
Expand Down
Loading