Skip to content

Commit

Permalink
Problem: Solana wallet couln't be used to control the VM
Browse files Browse the repository at this point in the history
  • Loading branch information
olethanh committed Sep 18, 2024
1 parent 4838652 commit 664d790
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 6 deletions.
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ dependencies = [
"aiohttp_cors~=0.7.0",
"pyroute2==0.7.12",
"jwcrypto==1.5.6",
"python-cpuid==0.1.0"
"python-cpuid==0.1.0",
"solathon==1.0.2",
]

[project.urls]
Expand Down
22 changes: 18 additions & 4 deletions src/aleph/vm/orchestrator/views/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from eth_account.messages import encode_defunct
from jwcrypto import jwk
from jwcrypto.jwa import JWA
from nacl.exceptions import BadSignatureError
from pydantic import BaseModel, ValidationError, root_validator, validator

from aleph.vm.conf import settings
Expand Down Expand Up @@ -55,6 +56,7 @@ class SignedPubKeyPayload(BaseModel):
# alg: Literal["ECDSA"]
address: str
expires: str
chain: Literal["ETH"] | Literal["SOL"] = "ETH"

@property
def json_web_key(self) -> jwk.JWK:
Expand Down Expand Up @@ -89,12 +91,23 @@ def check_expiry(cls, values) -> dict[str, bytes]:
@root_validator(pre=False, skip_on_failure=True)
def check_signature(cls, values) -> dict[str, bytes]:
"""Check that the signature is valid"""
signature: bytes = values["signature"]
signature: list = values["signature"]
payload: bytes = values["payload"]
content = SignedPubKeyPayload.parse_raw(payload)
if not verify_wallet_signature(signature, payload.hex(), content.address):
msg = "Invalid signature"
raise ValueError(msg)
if content.chain == "SOL":
from solathon.utils import verify_signature

try:
verify_signature(content.address, signature, payload.hex())
except BadSignatureError:
msg = "Invalid signature"
raise ValueError(msg)
elif content.chain == "ETH":
if not verify_wallet_signature(signature, payload.hex(), content.address):
msg = "Invalid signature"
raise ValueError(msg)
else:
raise ValueError("Unsupported chain")
return values

@property
Expand Down Expand Up @@ -208,6 +221,7 @@ def verify_signed_operation(signed_operation: SignedOperation, signed_pubkey: Si
async def authenticate_jwk(request: web.Request) -> str:
"""Authenticate a request using the X-SignedPubKey and X-SignedOperation headers."""
signed_pubkey = get_signed_pubkey(request)

signed_operation = get_signed_operation(request)
if signed_operation.content.domain != settings.DOMAIN_NAME:
logger.debug(f"Invalid domain '{signed_operation.content.domain}' != '{settings.DOMAIN_NAME}'")
Expand Down
74 changes: 73 additions & 1 deletion tests/supervisor/test_authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import eth_account.messages
import pytest
import solathon
from aiohttp import web
from eth_account.datastructures import SignedMessage
from jwcrypto import jwk, jws
Expand All @@ -14,7 +15,6 @@
)
from aleph.vm.utils.test_helpers import (
generate_signer_and_signed_headers_for_operation,
patch_datetime_now,
to_0x_hex,
)

Expand Down Expand Up @@ -238,6 +238,78 @@ async def view(request, authenticated_sender):
assert "ok" == r


async def generate_sol_signer_and_signed_headers_for_operation(
patch_datetime_now, operation_payload: dict
) -> tuple[solathon.Keypair, dict]:
"""Generate a temporary eth_account for testing and sign the operation with it"""

kp = solathon.Keypair()
key = jwk.JWK.generate(
kty="EC",
crv="P-256",
# key_ops=["verify"],
)

pubkey = {
"pubkey": json.loads(key.export_public()),
"alg": "ECDSA",
"domain": "localhost",
"address": str(kp.public_key),
"expires": (patch_datetime_now.FAKE_TIME + datetime.timedelta(days=1)).isoformat() + "Z",
"chain": "SOL",
}
pubkey_payload = json.dumps(pubkey).encode("utf-8").hex()
import nacl.signing

signed_message: nacl.signing.SignedMessage = kp.sign(pubkey_payload)
pubkey_signature = to_0x_hex(signed_message.signature)
pubkey_signature_header = json.dumps(
{
"payload": pubkey_payload,
"signature": pubkey_signature,
}
)
payload_as_bytes = json.dumps(operation_payload).encode("utf-8")
from jwcrypto.jwa import JWA

payload_signature = JWA.signing_alg("ES256").sign(key, payload_as_bytes)
headers = {
"X-SignedPubKey": pubkey_signature_header,
"X-SignedOperation": json.dumps(
{
"payload": payload_as_bytes.hex(),
"signature": payload_signature.hex(),
}
),
}
return kp, headers


@pytest.mark.asyncio
async def test_require_jwk_authentication_good_key_solana(aiohttp_client, patch_datetime_now):
"""An HTTP request to a view decorated by `@require_jwk_authentication`
auth correctly a temporary key signed by a wallet and an operation signed by that key"""

app = web.Application()
payload = {"time": "2010-12-25T17:05:55Z", "method": "GET", "path": "/", "domain": "localhost"}

signer_account, headers = await generate_sol_signer_and_signed_headers_for_operation(patch_datetime_now, payload)

@require_jwk_authentication
async def view(request, authenticated_sender):
assert authenticated_sender == str(signer_account.public_key)
return web.Response(text="ok")

app.router.add_get("", view)
client = await aiohttp_client(app)

resp = await client.get("/", headers=headers)
assert resp.status == 200, await resp.text()

r = await resp.text()
assert "ok" == r


@pytest.fixture
def valid_jwk_headers(mocker):
mocker.patch("aleph.vm.orchestrator.views.authentication.is_token_still_valid", lambda timestamp: True)
Expand Down

0 comments on commit 664d790

Please sign in to comment.