Skip to content

Commit

Permalink
Merge pull request #4 from OpSecId/pstlouis/fix-statuslist
Browse files Browse the repository at this point in the history
Pstlouis/fix statuslist
  • Loading branch information
PatStLouis authored Mar 28, 2024
2 parents a0133b2 + 645043b commit 76d0633
Show file tree
Hide file tree
Showing 17 changed files with 727 additions and 432 deletions.
19 changes: 19 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Traceability Controller
TRACEABILITY_CONTROLLER_DOMAIN=''

# Traction
TRACTION_API_URL='https://traction-sandbox-tenant-proxy.apps.silver.devops.gov.bc.ca'
TRACTION_TENANT_ID=''
TRACTION_API_KEY=''

# Temporary verifier agent endpoint
# until next release (Aca-py admin v0.11)
VERIFIER_ENDPOINT='http://verifier-agent:8020'

# Postgres
POSTGRES_URI='postgres://postgres:postgres@postgres:5432'
POSTGRES_USER='postgres'
POSTGRES_PASSWORD='postgres'

# Letsencrypt
LETSENCRYPT_EMAIL=''
70 changes: 70 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
---
version: '3'
services:

agent:
image: bcgovimages/aries-cloudagent:py3.9-indy-1.16.0_0.12.0rc2
ports:
- 8020:8020
entrypoint: ["aca-py", "start"]
command: [
'--no-ledger',
'--admin', '0.0.0.0', '8020',
'--admin-insecure-mode',
'--endpoint', 'http://agent:8021',
'--outbound-transport', 'http',
'--inbound-transport', 'http', '0.0.0.0', '8021'
]

postgres:
image: postgres:16-alpine
ports:
- 5432:5432
volumes:
- postgres_data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}

traceability-api:
image: patstlouis91/traceability-controller:interop-0.0.1
# build:
# context: ./traceability-controller
ports:
- 8000:8000
entrypoint: ["python", "main.py"]
environment:
POSTGRES_URI: ${POSTGRES_URI}
TRACTION_API_KEY: ${TRACTION_API_KEY}
TRACTION_TENANT_ID: ${TRACTION_TENANT_ID}
TRACTION_API_ENDPOINT: ${TRACTION_API_ENDPOINT}
VERIFIER_ENDPOINT: ${VERIFIER_ENDPOINT}
TRACEABILITY_CONTROLLER_DOMAIN: ${TRACEABILITY_CONTROLLER_DOMAIN}
labels:
- traefik.enable=true
- traefik.http.routers.traceability-api.rule=Host(`${TRACEABILITY_CONTROLLER_DOMAIN}`)
- traefik.http.routers.traceability-api.entrypoints=websecure
- traefik.http.routers.traceability-api.tls.certresolver=myresolver
- traefik.http.services.traceability-api.loadbalancer.server.port=8000

traefik:
image: traefik:v2.10
restart: always
security_opt:
- no-new-privileges:true
command:
- --providers.docker=true
- --providers.docker.exposedbydefault=false
- --entrypoints.websecure.address=:443
- --certificatesresolvers.myresolver.acme.tlschallenge=true
- --certificatesresolvers.myresolver.acme.email=${LETSENCRYPT_EMAIL}
- --certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json
ports:
- 443:443
volumes:
- letsencrypt:/letsencrypt
- /var/run/docker.sock:/var/run/docker.sock:ro

volumes:
postgres_data:
letsencrypt:
17 changes: 17 additions & 0 deletions traceability-controller/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
FROM python:3.11-slim

WORKDIR /traceability-controller

ENV VIRTUAL_ENV=/opt/venv
RUN python3 -m venv $VIRTUAL_ENV
ENV PATH="$VIRTUAL_ENV/bin:$PATH"

COPY requirements.txt ./

RUN pip install --upgrade pip
RUN pip install -r requirements.txt

COPY app ./app
COPY app config.py main.py ./

CMD [ "python", "main.py" ]
111 changes: 39 additions & 72 deletions traceability-controller/app/controllers/askar.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,83 +3,50 @@
from config import settings
import time
from app.validations import ValidationException


def holderClientHashesDataKey(did_label):
"""OAuth client who will send presentations `/presentations`"""
return f"holderClientHashes"


def issuerClientHashesDataKey():
"""OAuth client who will issue credentials `/credentials/issue`"""
return f"issuerClientHashes"


def didDocumentDataKey(did_label):
"""Controller documents for web DIDs"""
return f"didDocuments:{did_label}"


def statusEntriesDataKey(did_label, statusCredentialId):
"""List of registered indexes in a status list"""
return f"statusEntries:{did_label}:{statusCredentialId}"


def statusCredentialDataKey(did_label, statusCredentialId):
"""Status list credential maintained by an issuer"""
return f"statusCredentials:{did_label}:{statusCredentialId}"


def issuedCredentialDataKey(did_label, credentialId):
"""Issued credentials of an issuer"""
return f"issuedCredentials:{did_label}:{credentialId}"


def recievedCredentialDataKey(did_label, credentialId):
"""Credentials recieved through a presentation exchange"""
return f"storedCredentials:{did_label}:{credentialId}"


async def provision_public_store():
await Store.provision(
settings.ASKAR_PUBLIC_STORE,
"raw",
settings.ASKAR_PUBLIC_STORE_KEY,
recreate=False,
)


async def open_store(key):
return await Store.open(settings.ASKAR_PUBLIC_STORE, "raw", key)


async def store_data(storeKey, dataKey, data):
store = await open_store(storeKey)
async with store.session() as session:
await session.insert(
"seq",
dataKey.lower(),
json.dumps(data),
{"~plaintag": "a", "enctag": "b"},
from aries_askar.bindings import generate_raw_key

class AskarController:

def __init__(self, db=settings.ASKAR_DEFAULT_DB):
self.db = f'{settings.POSTGRES_URI}/{db}'
self.key = generate_raw_key(settings.TRACTION_API_KEY)

async def provision(self):
await Store.provision(
self.db,
"raw",
self.key,
recreate=False,
)

async def open(self):
return await Store.open(self.db, "raw", self.key)

async def fetch_data(storeKey, dataKey):
store = await open_store(storeKey)
async with store.session() as session:
data = await session.fetch("seq", dataKey.lower())
return json.loads(data.value)
async def fetch(self, data_key):
store = await self.open()
async with store.session() as session:
data = await session.fetch("seq", data_key)
return json.loads(data.value)

async def store(self, data_key, data):
store = await self.open()
async with store.session() as session:
await session.insert(
"seq",
data_key,
json.dumps(data),
{"~plaintag": "a", "enctag": "b"},
)

async def update_data(storeKey, dataKey, new_data):
store = await open_store(storeKey)
async with store.session() as session:
await session.replace(
"seq",
dataKey.lower(),
json.dumps(new_data),
{"~plaintag": "a", "enctag": "b"},
)
async def update(self, data_key, data):
store = await self.open()
async with store.session() as session:
await session.replace(
"seq",
data_key,
json.dumps(data),
{"~plaintag": "a", "enctag": "b"},
)


async def find_api_key(storeKey, apiKeyHash):
Expand Down
48 changes: 26 additions & 22 deletions traceability-controller/app/controllers/auth.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
from app.controllers import askar
from app.controllers.askar import AskarController
from config import settings
import uuid
import hashlib
import secrets
from app.validations import ValidationException
from app.auth.handler import decodeJWT
from app.utils import did_from_label


def can_issue(credential, did_label):
"""Function to check if the issuer field matches the organization's instance"""
did = (
credential["issuer"]
if isinstance(credential["issuer"], str)
else credential["issuer"]["id"]
)
if did != did_from_label(did_label):
raise ValidationException(
status_code=400, content={"message": "Invalid issuer"}
)
return did


async def verify_client_hash(client_id, client_secret):
try:
client_secret_hash = hashlib.sha256(client_secret.encode('utf-8')).hexdigest()
data_key = f'issuerClients:{client_id}'
client_data = await askar.fetch_data(settings.ASKAR_PUBLIC_STORE_KEY, data_key)
if client_data['client_secret_hash'] != client_secret_hash:
client_hash = uuid.uuid5(uuid.UUID(client_id), client_secret)
if await AskarController().fetch(f'clientHash:{client_id}') != str(client_hash):
raise ValidationException(
status_code=400,
content={"message": "Invalid client"},
Expand All @@ -26,26 +39,17 @@ async def verify_client_hash(client_id, client_secret):

async def is_authorized(did_label, request):
token = request.headers.get("Authorization").replace("Bearer ", "")
client_id = decodeJWT(token)["client_id"]
data_key = f'issuerClients:{client_id}'
client_data = await askar.fetch_data(settings.ASKAR_PUBLIC_STORE_KEY, data_key)
if client_data['did_label'] != did_label:
request_client_id = decodeJWT(token)["client_id"]
stored_client_id = await AskarController(db=did_label).fetch('clientId')
if request_client_id != stored_client_id:
raise ValidationException(status_code=401, content={"message": "Unauthorized"})


async def new_issuer_client(did_label):
client_id = uuid.uuid4()
client_secret = secrets.token_urlsafe(24)
client_secret_hash = hashlib.sha256(client_secret.encode('utf-8')).hexdigest()
client_data = {
'did_label': did_label,
'client_secret_hash': client_secret_hash
}
data_key = f'issuerClients:{client_id}'
await askar.store_data(settings.ASKAR_PUBLIC_STORE_KEY, data_key, client_data)

# Provision private store
# askarKey = generate_askar_key(client_secret)
# await askar.provision_store(askarKey)

return client_id, client_secret
client_hash = uuid.uuid5(client_id, client_secret)
await AskarController().store(f'clientHash:{str(client_id)}', str(client_hash))
await AskarController(db=did_label).store('clientId', str(client_id))

return str(client_id), client_secret
50 changes: 50 additions & 0 deletions traceability-controller/app/controllers/did_document.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import json
from aries_askar import Store, error
from config import settings
import time
from app.validations import ValidationException
from app.utils import did_from_label
from app.controllers.askar import AskarController



class DidDocumentController:

def __init__(self, did_label):
self.did_label = did_label
self.id = did_from_label(did_label)
self.context = [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/v2",
"https://w3id.org/traceability/v1"
]
self.verification_method = []
self.authentication = []
self.assertion_method = []
service = {
'id': f'{self.id}#traceability-api',
'type': ["TraceabilityAPI"],
'serviceEndpoint': f'{settings.HTTPS_BASE}/{settings.DID_NAMESPACE}/{did_label}'
}
self.service = [service]

async def add_verkey(self):
verification_method = {
'id': f'{self.id}#verkey',
'type': 'Ed25519VerificationKey2018',
'controller': self.id,
'publicKeyBase58': await AskarController(self.did_label).fetch('verkey')
}
self.verification_method = [verification_method]
self.authentication = [verification_method['id']]
self.assertion_method = [verification_method['id']]

def as_json(self):
return {
'@context': self.context,
'id': self.id,
'verificationMethod': self.verification_method,
'authentication': self.authentication,
'assertionMethod': self.assertion_method,
'service': self.service,
}
Loading

0 comments on commit 76d0633

Please sign in to comment.