Skip to content

Commit

Permalink
Merge branch 'sd-jwt' into p2
Browse files Browse the repository at this point in the history
  • Loading branch information
peppelinux committed Nov 21, 2024
2 parents 3af829f + 1c54446 commit 6abec75
Show file tree
Hide file tree
Showing 36 changed files with 575 additions and 599 deletions.
49 changes: 25 additions & 24 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,9 +47,9 @@ jobs:
sudo apt install python3-dev python3-pip
- name: Install MongoDB
run: |
sudo apt-get install -y gnupg wget
sudo wget -qO - https://www.mongodb.org/static/pgp/server-4.4.asc | sudo apt-key add -
sudo echo "deb http://repo.mongodb.org/apt/debian buster/mongodb-org/4.4 main" | sudo tee /etc/apt/sources.list.d/mongodb-org-4.4.list
sudo apt-get install -y gnupg curl
sudo curl -fsSL https://pgp.mongodb.com/server-7.0.asc | sudo gpg -o /usr/share/keyrings/mongodb-server-7.0.gpg --dearmor
sudo echo "deb [ arch=amd64,arm64 signed-by=/usr/share/keyrings/mongodb-server-7.0.gpg ] https://repo.mongodb.org/apt/ubuntu jammy/mongodb-org/7.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-7.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
- name: Start MongoDB
Expand All @@ -65,34 +65,35 @@ jobs:
python -m pip install "Pillow>=10.0.0,<10.1" "device_detector>=5.0,<6" "satosa>=8.4,<8.6" "jinja2>=3.0,<4" "pymongo>=4.4.1,<4.5" aiohttp
python -m pip install git+https://github.com/peppelinux/pyMDOC-CBOR.git
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 pyeudiw --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 pyeudiw --count --exit-zero --statistics --max-line-length 160
- name: Tests
run: |
# pytest --cov=pyeudiw --cov-fail-under=90 pyeudiw
pytest --cov=pyeudiw pyeudiw
coverage report -m --skip-covered
# - name: Integration Tests
# run: |
# sudo apt-get install xmlsec1
# cd example/satosa/integration_test/
# python -m pip install -r requirements_test.txt
# python cross_device_integration_test.py
# python same_device_integration_test.py
- name: Bandit Security Scan
run: |
bandit -r -x pyeudiw/tests* pyeudiw/*
- name: Lint with html linter
run: |
echo -e '\nHTML:'
readarray -d '' array < <(find $SRC example -name "*.html" -print0)
echo "Running linter on (${#array[@]}): "
printf '\t- %s\n' "${array[@]}"
echo "Linter output:"
- name: Lint with flake8
run: |
# stop the build if there are Python syntax errors or undefined names
flake8 pyeudiw --count --select=E9,F63,F7,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
flake8 pyeudiw --count --exit-zero --statistics --max-line-length 160
- name: Tests
working-directory: ./pyeudiw
run: |
# pytest --cov=pyeudiw --cov-fail-under=90
pytest --cov=pyeudiw
coverage report -m --skip-covered
- name: Bandit Security Scan
run: |
bandit -r -x pyeudiw/tests* pyeudiw/*
- name: Lint with html linter
run: |
echo -e '\nHTML:'
readarray -d '' array < <(find $SRC example -name "*.html" -print0)
echo "Running linter on (${#array[@]}): "
printf '\t- %s\n' "${array[@]}"
echo "Linter output:"
for file in "${array[@]}"
do
Expand Down
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -169,4 +169,8 @@ env

.DS_Store

docs/source
docs/source

# VSCode
# VSCode specific settings
.vscode/
180 changes: 119 additions & 61 deletions docs/SD-JWT.md
Original file line number Diff line number Diff line change
@@ -1,92 +1,150 @@
# SD-JWT Documentation
# sd-jwt-python Fork with cryptojwt

## Introduction
This document explains how to create and verify a Self-Contained JWT (SD-JWT) using the EUDI Wallet IT Python library. It also covers how to validate proof of possession.

## Creating an SD-JWT
This module is a fork of [sd-jwt-python](https://github.com/openwallet-foundation-labs/sd-jwt-python) project. It has been adapted to use the [`cryptojwt`](https://github.com/IdentityPython/JWTConnect-Python-CryptoJWT) library as the core JWT implementation.

### Step 1: Import Necessary Modules
To get started, you need to import the necessary modules from the EUDI Wallet IT Python library.

```python
from pyeudiw.sd_jwt.issuer import SDJWTIssuer
from pyeudiw.jwk import JWK
from pyeudiw.sd_jwt.exceptions import UnknownCurveNistName
from pyeudiw.sd_jwt.verifier import SDJWTVerifier
from json import dumps, loads
```
If you're familiar with the original `sd-jwt-python` library, this fork retains similar functionality with minimal API changes, if needed.

### Step 2: Prepare User Claims
Define the claims that you want to include in your SD-JWT.
---

```python
user_claims = {
"iss": "issuer_identifier", # The identifier for the issuer
"sub": "subject_identifier", # The identifier for the subject
"exp": 1234567890, # Expiration time (in seconds)
"iat": 1234567890, # Issued at time (in seconds)
# Add other claims as needed
}
## Features

- **SD-JWT Support**: Implements the Selective Disclosure JWT standard.
- **`cryptojwt` Integration**: Leverages a mature and feature-rich library for JWT operations.
- **Backward Compatibility**: Minimal changes required for existing users of `sd-jwt-python`.
- **Improved Flexibility**: Extensible for custom SD-JWT use cases.

---

# SD-JWT Library Usage Documentation

## Introduction

This library provides an implementation of the SD-JWT (Selective Disclosure for JWT) standard. This document explains how to create and verify a Selected-Disclosure JWT (SD-JWT) using the EUDI Wallet IT Python library. It also covers how to validate proof of possession enabling three key operations:
1. **Issuer**: Generate an SD-JWT with selective disclosure capabilities.
2. **Holder**: Select claims to disclose and create a presentation.
3. **Verifier**: Validate the SD-JWT and verify the disclosed claims.

### Requirements
- Python version as configured in the CI of this project.
- Install the library via `pip`:
```bash
pip install pyeudiw
```

### Step 3: Create Keys
Generate or load your JSON Web Keys (JWKs).
- **Key Requirements**:
- All keys must be in JWK (JSON Web Key) format, conforming to [RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517).
- You can use a library like `cryptojwt` to generate or manage JWKs. Example:

```python
issuer_key = JWK(key_type='RSA') # Example for RSA key
holder_key = JWK(key_type='RSA') # Example for RSA key
```bash
from cryptojwt.jwk.ec import new_ec_key

# Generate an EC key pair
issuer_private_key = new_ec_key('P-256')

# Serialize the keys
issuer_keys = [issuer_private_key.serialize(private=True)] # List of private keys
public_key = issuer_private_key.serialize() # Public key
```
---

### Step 4: Issue SD-JWT
Create an instance of `SDJWTIssuer` and generate the JWT.
## 1. Issuer: Generating an SD-JWT

```python
sd_jwt_issuer = SDJWTIssuer(
The Issuer creates an SD-JWT using the user's claims (`user_claims`) and a private key in JWK format to sign the token.

### Example

```bash
from pyeudiw.sd_jwt.issuer import SDJWTIssuer

# User claims
user_claims = {
"sub": "john_doe_42",
"given_name": "John",
"family_name": "Doe",
"email": "[email protected]",
}

# Generate private keys
issuer_private_key = new_ec_key('P-256')
issuer_keys = [issuer_private_key.serialize(private=True)] # List of private JWKs
holder_key = new_ec_key('P-256').serialize(private=True) # Holder private key (optional)

# Create SD-JWT
sdjwt_issuer = SDJWTIssuer(
user_claims=user_claims,
issuer_key=issuer_key,
holder_key=holder_key,
sign_alg='RS256', # Example signing algorithm
issuer_keys=issuer_keys, # List of private JWKs
holder_key=holder_key, # Holder key (optional)
add_decoy_claims=True, # Add decoy claims for privacy
serialization_format="compact" # Compact JWS format
)

sd_jwt = sd_jwt_issuer.serialize() # Get the serialized SD-JWT
print("Serialized SD-JWT:", sd_jwt)
# Output SD-JWT and disclosures
print("SD-JWT Issuance:", sdjwt_issuer.sd_jwt_issuance)
```

## Verifying an SD-JWT
---

### Step 1: Prepare the JWT
Receive the SD-JWT that you want to verify.
## 2. Holder: Creating a Selective Disclosure Presentation

```python
received_sd_jwt = sd_jwt # The JWT you want to verify
```
The Holder receives the SD-JWT from the Issuer and selects which claims to disclose to the Verifier.

### Step 2: Create Verifier Instance
Use the `SDJWTVerifier` to verify the JWT.
### Example

```python
sd_jwt_verifier = SDJWTVerifier(
received_sd_jwt,
issuer_key=issuer_key,
holder_key=holder_key,
```bash
from pyeudiw.sd_jwt.holder import SDJWTHolder

# Claims to disclose
holder_disclosed_claims = {
"given_name": True,
"family_name": True
}

# Initialize Holder
sdjwt_holder = SDJWTHolder(sdjwt_issuer.sd_jwt_issuance)

# Create presentation with selected claims
sdjwt_holder.create_presentation(
disclosed_claims=holder_disclosed_claims,
nonce=None, # Optional: Used for key binding
verifier=None, # Optional: Verifier identifier for key binding
holder_key=holder_key # Optional: Holder private key for key binding
)

verified_claims = sd_jwt_verifier.verify() # Get the verified claims
print("Verified Claims:", verified_claims)
# Output the presentation
print("SD-JWT Presentation:", sdjwt_holder.sd_jwt_presentation)
```

## Proof of Possession
## 3. Verifier: Verifying an SD-JWT

The Verifier validates the SD-JWT and checks the disclosed claims.

To verify proof of possession, ensure that the holder key matches the expected public key during verification. This process should be included in your verification logic.
### Example

```python
if holder_key.verify(verified_claims):
print("Proof of possession is valid.")
else:
print("Invalid proof of possession.")
```
from pyeudiw.sd_jwt.verifier import SDJWTVerifier

# Callback to retrieve Issuer's public key
def get_issuer_public_key(issuer, header_parameters):
# Return the public key(s) in JWK format
return [issuer_private_key.serialize()]

# Initialize Verifier
sdjwt_verifier = SDJWTVerifier(
sdjwt_presentation=sdjwt_holder.sd_jwt_presentation,
cb_get_issuer_key=get_issuer_public_key
)

# Verify and retrieve payload
verified_payload = sdjwt_verifier.get_verified_payload()

# Verified claims
print("Verified Claims:", verified_payload)
```


---

**Note:**
For more specific implementation details read more on [SD-JWT](../pyeudiw/sd_jwt/SD-JWT.md).
```
32 changes: 29 additions & 3 deletions example/satosa/integration_test/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,41 @@ This integration test will verify a full authentication flow of a simulated IT-W
### Environment

An up an running Openid4VP Relying Party is a requirement of this project.
The intended Relying Party of this integration test is the example one provided in the repostiory [https://github.com/italia/Satosa-Saml2Spid](https://github.com/italia/Satosa-Saml2Spid).
The intended Relying Party of this integration test is the example one provided in the repository [https://github.com/italia/Satosa-Saml2Spid](https://github.com/italia/Satosa-Saml2Spid).
That project will provide full instruction on how to setup such an environment with Docker.

Before starting, make sure that the `pyeudiw_backend.yaml` is properly configured and included in the file `proxy_conf.yaml` that is running in your Docker environemnt.
Before starting, make sure that the `pyeudiw_backend.yaml` is properly configured and included in the file `proxy_conf.yaml` that is running in your Docker environment.
This project folder always provide up to date example of the pyeudiw plugin configuration in the file [pyeudiw_backend.yaml](./pyeudiw_backend.yaml), as well as other configuration file of the module in [static](./static/) and [template](./template/) folders.

#### MongoDB Configuration for Tests

The MongoDB connection is configured dynamically using the environment variable `PYEUDIW_MONGO_TEST_AUTH_INLINE`.

#### How It Works
- The value of `PYEUDIW_MONGO_TEST_AUTH_INLINE` should be in the format `username:password@`.
- If the variable is not set, the configuration defaults to:
- **Authentication**: Defaults to empty string.
- **MongoDB URL**: `mongodb://localhost:27017/?timeoutMS=2000`.

#### Example Usage
1. **With Authentication**:
Set the environment variable:
```bash
export PYEUDIW_MONGO_TEST_AUTH_INLINE="satosa:thatpassword@"
```

or just using `.env` file

#### Custom Behavior
You can override the default credentials by setting the environment variable:

```bash
export PYEUDIW_MONGO_TEST_AUTH_INLINE="customuser:custompassword@"
```

### Dependencies

Requirements eexclusive to the integration test can be installed with
Requirements exclusive to the integration test can be installed with

pip install -r requirements_test.txt

Expand Down
Loading

0 comments on commit 6abec75

Please sign in to comment.