fix: Handle both plain and base64-encoded public keys in API responses

## Issue
Even after storing keys as base64, the API was returning plain "p:g:h" format
for existing elections that had keys stored as plain UTF-8 bytes, causing:
- Client receives: "23:5:13" (plain text)
- Client tries to decode as base64 (btoa call)
- Results in: "Invalid base64: 23:5:13... - String contains an invalid character"

## Root Cause
1. Old elections have public_key stored as plain UTF-8: b'23:5:13'
2. New elections store as base64: b'MjM6NToxMw=='
3. Both were decoded to string before return, exposing wrong format
4. Also fixed ElGamal class name typo: ElGamal() → ElGamalEncryption()

## Fix
1. Detect public key format before returning:
   - If plain "p:g:h" format (contains ':'), encode to base64
   - If already base64 (starts with 'MjM6'), return as-is
2. Always return base64-encoded string to client
3. Updated both /setup and /public-keys endpoints in votes.py
4. Updated /init-keys endpoint in admin.py
5. Fixed class name in setup_election function

## Files Changed
- backend/routes/votes.py: Lines 502, 509-518, 560-569
- backend/routes/admin.py: Lines 179-197

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Alexis Bruneteau 2025-11-11 19:36:39 +01:00
parent adfec105d8
commit 78f0140342
2 changed files with 37 additions and 4 deletions

View File

@ -176,6 +176,17 @@ async def init_election_keys(election_id: int, db: Session = Depends(get_db)):
else:
logger.info(f"Election {election_id} already has valid public key")
# Ensure public key is base64-encoded for client
pubkey_to_return = election.public_key
if isinstance(pubkey_to_return, bytes):
pubkey_str = pubkey_to_return.decode('utf-8')
# If it's plain "p:g:h" format, encode it to base64
if ':' in pubkey_str and not pubkey_str.startswith('MjM6'): # Not already base64
pubkey_to_return = base64.b64encode(pubkey_str.encode('utf-8')).decode('ascii')
else:
# Already base64, just decode to string
pubkey_to_return = pubkey_str
return {
"status": "success",
"election_id": election_id,
@ -183,7 +194,7 @@ async def init_election_keys(election_id: int, db: Session = Depends(get_db)):
"elgamal_p": election.elgamal_p,
"elgamal_g": election.elgamal_g,
"public_key_generated": True,
"public_key": election.public_key.decode('ascii') if isinstance(election.public_key, bytes) else election.public_key
"public_key": pubkey_to_return
}
except HTTPException:

View File

@ -499,18 +499,29 @@ async def setup_election(
# Générer les clés ElGamal si nécessaire
if not election.public_key:
elgamal = ElGamal()
elgamal = ElGamalEncryption()
# Store as base64-encoded bytes (database column is LargeBinary)
# public_key_bytes returns UTF-8 "p:g:h", then encode to base64
election.public_key = base64.b64encode(elgamal.public_key_bytes)
db.add(election)
db.commit()
# Ensure public key is base64-encoded for client
pubkey_to_return = election.public_key
if isinstance(pubkey_to_return, bytes):
pubkey_str = pubkey_to_return.decode('utf-8')
# If it's plain "p:g:h" format, encode it to base64
if ':' in pubkey_str and not pubkey_str.startswith('MjM6'): # Not already base64
pubkey_to_return = base64.b64encode(pubkey_str.encode('utf-8')).decode('ascii')
else:
# Already base64, just decode to string
pubkey_to_return = pubkey_str
return {
"status": "initialized",
"election_id": election_id,
"public_keys": {
"elgamal_pubkey": election.public_key.decode('ascii') if isinstance(election.public_key, bytes) else election.public_key
"elgamal_pubkey": pubkey_to_return
},
"blockchain_blocks": blockchain.get_block_count()
}
@ -546,8 +557,19 @@ async def get_public_keys(
detail="Election keys not initialized. Call /setup first."
)
# Ensure public key is base64-encoded for client
pubkey_to_return = election.public_key
if isinstance(pubkey_to_return, bytes):
pubkey_str = pubkey_to_return.decode('utf-8')
# If it's plain "p:g:h" format, encode it to base64
if ':' in pubkey_str and not pubkey_str.startswith('MjM6'): # Not already base64
pubkey_to_return = base64.b64encode(pubkey_str.encode('utf-8')).decode('ascii')
else:
# Already base64, just decode to string
pubkey_to_return = pubkey_str
return {
"elgamal_pubkey": election.public_key.decode('ascii') if isinstance(election.public_key, bytes) else election.public_key
"elgamal_pubkey": pubkey_to_return
}