Skip to content

Commit 8b7ff11

Browse files
authored
Merge pull request #5 from Freedom-Club-Sec/refactor/strandlock-protocol-support
Refactor/strandlock protocol support
2 parents c9c12bb + 690747f commit 8b7ff11

File tree

6 files changed

+28
-114
lines changed

6 files changed

+28
-114
lines changed

app/logic/message.py

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,17 @@
11
from app.db.sqlite import get_db, check_user_exists
22
from app.db.redis import get_redis, get_redis_list
3-
from app.utils.helper_utils import valid_b64
4-
from base64 import b64decode
53
import json
64
import logging
75

86
redis_client = get_redis()
97

10-
def otp_batch_processor(user_id: str, recipient_id: str, otp_hashchain_ciphertext: str, otp_hashchain_signature: str) -> None:
8+
def message_processor(user_id: str, recipient_id: str, ciphertext_blob: str) -> None:
119
if not check_user_exists(recipient_id):
1210
raise ValueError("Recipient_id does not exist")
1311

1412
payload = {
1513
"sender": user_id,
16-
"msg_type": "new_otp_batch",
17-
"otp_hashchain_ciphertext": otp_hashchain_ciphertext,
18-
"otp_hashchain_signature": otp_hashchain_signature,
19-
"data_type": "message"
20-
}
21-
22-
redis_client.rpush(recipient_id, json.dumps(payload))
23-
24-
25-
def otp_message_processor(user_id: str, recipient_id: str, message_encrypted: str) -> None:
26-
if not check_user_exists(recipient_id):
27-
raise ValueError("Recipient_id does not exist")
28-
29-
payload = {
30-
"sender": user_id,
31-
"msg_type": "new_message",
32-
"message_encrypted": message_encrypted,
14+
"ciphertext_blob": ciphertext_blob,
3315
"data_type": "message"
3416
}
3517

app/logic/pfs.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
redis_client = get_redis()
88

99

10-
def ephemeral_keys_processor(user_id: str, recipient_id: str, publickeys_hashchain: str, hashchain_signature: str, pfs_type: str) -> None:
10+
def ephemeral_keys_processor(user_id: str, recipient_id: str, ciphertext_blob: str) -> None:
1111
if not check_user_exists(recipient_id):
1212
raise ValueError("Recipient_id does not exist")
1313

@@ -17,10 +17,8 @@ def ephemeral_keys_processor(user_id: str, recipient_id: str, publickeys_hashcha
1717

1818
payload = {
1919
"sender": user_id,
20-
"publickeys_hashchain": publickeys_hashchain,
21-
"hashchain_signature": hashchain_signature,
20+
"ciphertext_blob": ciphertext_blob,
2221
"data_type": "pfs",
23-
"pfs_type": pfs_type
2422
}
2523

2624
redis_client.rpush(recipient_id, json.dumps(payload))

app/routes/message.py

Lines changed: 12 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
from fastapi import APIRouter, Request, HTTPException, Response, Depends
22
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
33
from pydantic import BaseModel, validator
4-
from base64 import b64encode, b64decode
5-
from app.core.crypto import verify_signature
6-
from app.logic.message import otp_batch_processor, otp_message_processor
4+
from app.logic.message import message_processor
75
from app.utils.helper_utils import valid_b64
86
from app.utils.jwt import verify_jwt_token
97
from app.core.constants import (
@@ -17,71 +15,30 @@
1715

1816
router = APIRouter()
1917

20-
class PadsPayload(BaseModel):
21-
otp_hashchain_ciphertext: str
22-
otp_hashchain_signature : str
23-
recipient : str
18+
class SendPayload(BaseModel):
19+
ciphertext_blob: str
20+
recipient : str
2421

25-
class SendMessagePayload(BaseModel):
26-
message_encrypted: str
27-
recipient : str
28-
29-
@router.post("/messages/send_pads")
30-
async def message_send_pads(payload: PadsPayload, response: Response, user=Depends(verify_jwt_token)):
31-
otp_hashchain_ciphertext = payload.otp_hashchain_ciphertext
32-
otp_hashchain_signature = payload.otp_hashchain_signature
33-
recipient = payload.recipient
22+
@router.post("/messages/send")
23+
async def message_send(payload: SendPayload, response: Response, user=Depends(verify_jwt_token)):
24+
ciphertext_blob = payload.ciphertext_blob
25+
recipient = payload.recipient
3426

3527
user_id = user["id"]
36-
37-
38-
# ML-KEM-1024 ciphertext is always 1568 bytes, and Classic McEliece8192128 is always 208 bytes,
39-
# and since our default One-Time-Pad size is around 11 kilobytes (11264)
40-
# We can be confident that the decoded ciphertext_blob size must match 551936 bytes
41-
#
42-
# 11264 / 32 = 352
43-
# 352 x 1568 = 551936
44-
# 352 x 208 = 73216
45-
# size to match is 551936 + 73216 = 625152
46-
47-
print(len(b64decode(otp_hashchain_ciphertext)))
48-
if (not valid_b64(otp_hashchain_ciphertext)) or len(b64decode(otp_hashchain_ciphertext)) != (OTP_PAD_SIZE // 32) * (ML_KEM_1024_CT_LEN + CLASSIC_MCELIECE_8_F_CT_LEN):
49-
raise HTTPException(status_code=400, detail="Malformed otp_hashchain_ciphertext")
50-
51-
# Dilithium5 signature is always 4595
52-
if (not valid_b64(otp_hashchain_signature)) or len(b64decode(otp_hashchain_signature)) != ML_DSA_87_SIGN_LEN:
53-
raise HTTPException(status_code=400, detail="Malformed otp_hashchain_signature")
5428

5529
if (not recipient.isdigit()) or len(recipient) != 16:
5630
raise HTTPException(status_code=400, detail="Invalid recipient")
5731

58-
try:
59-
await asyncio.to_thread(otp_batch_processor, user_id, recipient, otp_hashchain_ciphertext, otp_hashchain_signature)
60-
except ValueError as e:
61-
raise HTTPException(status_code=400, detail=e)
62-
63-
return {"status": "success"}
64-
65-
66-
@router.post("/messages/send_message")
67-
async def message_send_message(payload: SendMessagePayload, response: Response, user=Depends(verify_jwt_token)):
68-
message_encrypted = payload.message_encrypted
69-
recipient = payload.recipient
70-
71-
user_id = user["id"]
32+
33+
if (not valid_b64(ciphertext_blob)):
34+
raise HTTPException(status_code=400, detail="Malformed ciphertext_blob")
7235

73-
if (not recipient.isdigit()) or len(recipient) != 16:
74-
raise HTTPException(status_code=400, detail="Invalid recipient")
7536

76-
# 64 is the hash chain output calculated using sha3_512, and 2 is for the padding length field and 1 character is bare minimum for a message
77-
if len(message_encrypted) < (64 + 2 + 1):
78-
raise HTTPException(status_code=400, detail="Your message is malformed")
7937

8038
try:
81-
await asyncio.to_thread(otp_message_processor, user_id, recipient, message_encrypted)
39+
await asyncio.to_thread(message_processor, user_id, recipient, ciphertext_blob)
8240
except ValueError as e:
8341
raise HTTPException(status_code=400, detail=e)
8442

8543
return {"status": "success"}
8644

87-

app/routes/pfs.py

Lines changed: 9 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -19,52 +19,28 @@
1919

2020

2121
class SendKeysPFS(BaseModel):
22-
publickeys_hashchain: str
23-
hashchain_signature: str
24-
pfs_type : str
25-
recipient : str
22+
ciphertext_blob: str
23+
recipient : str
2624

2725

2826

2927
@router.post("/pfs/send_keys")
3028
async def pfs_send_keys(payload: SendKeysPFS, response: Response, user=Depends(verify_jwt_token)):
31-
publickeys_hashchain = payload.publickeys_hashchain
32-
hashchain_signature = payload.hashchain_signature
33-
pfs_type = payload.pfs_type
34-
recipient = payload.recipient
29+
ciphertext_blob = payload.ciphertext_blob
30+
recipient = payload.recipient
3531

3632
user_id = user["id"]
3733

34+
if (not recipient.isdigit()) or len(recipient) != 16:
35+
raise HTTPException(status_code=400, detail="Invalid recipient")
3836

39-
if not valid_b64(publickeys_hashchain):
40-
raise HTTPException(status_code=400, detail="Malformed public_key base64 encoding")
41-
42-
43-
# ML-KEM-1024 public-key size is always exactly 1568 bytes according to spec
44-
# And 64 bytes for our SHA3-512 hash-chain
45-
if pfs_type == "partial":
46-
if len(b64decode(publickeys_hashchain)) != ML_KEM_1024_PK_LEN + KEYS_HASH_CHAIN_LEN:
47-
raise HTTPException(status_code=400, detail="Malformed public_keys")
4837

49-
# Classic McEliece8192128 public-key size is always exactly 1357824 bytes according to spec
50-
# And 64 bytes for our SHA3-512 hash-chain
51-
elif pfs_type == "full":
52-
if len(b64decode(publickeys_hashchain)) != CLASSIC_MCELIECE_8_F_PK_LEN + ML_KEM_1024_PK_LEN + KEYS_HASH_CHAIN_LEN:
53-
raise HTTPException(status_code=400, detail="Malformed public_keys")
54-
55-
else:
56-
raise HTTPException(status_code=400, detail="Malformed pfs_type")
38+
if (not valid_b64(ciphertext_blob)):
39+
raise HTTPException(status_code=400, detail="Malformed ciphertext_blob")
5740

5841

59-
if (not valid_b64(hashchain_signature)) or len(b64decode(hashchain_signature)) != ML_DSA_87_SIGN_LEN:
60-
raise HTTPException(status_code=400, detail="Malformed signature")
61-
62-
63-
if (not recipient.isdigit()) or len(recipient) != 16:
64-
raise HTTPException(status_code=400, detail="Invalid recipient")
65-
6642
try:
67-
await asyncio.to_thread(ephemeral_keys_processor, user_id, recipient, publickeys_hashchain, hashchain_signature, pfs_type)
43+
await asyncio.to_thread(ephemeral_keys_processor, user_id, recipient, ciphertext_blob)
6844
except ValueError as e:
6945
return {"status": "failure", "error": e}
7046

app/routes/smp.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,8 +68,6 @@ async def smp_step(payload: SMP_Step, response: Response, user=Depends(verify_jw
6868
if (not recipient.isdigit()) or len(recipient) != 16:
6969
raise HTTPException(status_code=400, detail="Invalid recipient")
7070

71-
72-
7371

7472
if (not valid_b64(ciphertext_blob)):
7573
raise HTTPException(status_code=400, detail="Malformed ciphertext_blob")

app/utils/helper_utils.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55

66
def valid_b64(s: str) -> bool:
7+
if not s.strip():
8+
return False
9+
710
try:
811
b64decode(s, validate=True)
912
return True

0 commit comments

Comments
 (0)