MOSIP Claim 169 QR code identity credential generation for OpenSPP.
- Configurable Attribute Mapping: Map OpenSPP fields to Claim 169 numbered attributes
- CWT Generation: Create signed CBOR Web Tokens using COSE Sign1
- QR Code Generation: Generate compressed, Base45-encoded QR codes
- Registry Integration: Seamlessly integrate with OpenSPP registry (res.partner)
- Credential Management: Track issued credentials, expiration, and revocation
- Multi-issuer Support: Configure multiple credential issuers
MOSIP Claim 169 defines a standardized set of numbered attributes for identity credentials:
- 1: ID
- 2: Version
- 3: Language
- 4: Full Name
- 5: First Name
- 6: Middle Name
- 7: Last Name
- 8: Date of Birth (YYYYMMDD)
- 9: Gender (1=Male, 2=Female, 3=Others)
- 10: Address
- 11: Email
- 12: Phone
- 13: Nationality (ISO 3166-1)
- 14: Marital Status
- 15: Guardian
- 16: Photo (binary)
- 17: Photo Format (1=JPEG, 2=JPEG2, 3=AVIF, 4=WEBP)
- iss (1): Issuer identifier
- exp (4): Expiration timestamp
- iat (6): Issued at timestamp
-
Install dependencies:
pip install qrcode Pillow
-
Install required modules:
spp_registryspp_cbor_cosespp_key_management
-
Install this module:
odoo-bin -d <database> -i spp_claim_169
Navigate to Key Management and create or import a private key for signing credentials:
- Algorithm: ES256 (ECDSA with SHA-256)
- Format: PEM or JWK
Go to Claim 169 > Configuration > Issuer Configurations:
- Create a new issuer configuration
- Set issuer ID (e.g., DID or URI)
- Select signing key
- Configure default validity period
Go to Claim 169 > Configuration > Attribute Mappings:
- Review default mappings
- Add custom mappings as needed
- Configure transformations:
- Direct: Use field value as-is
- Date YYYYMMDD: Convert dates to integer format
- Gender Code: Map to Claim 169 codes
- Address Combined: Combine address fields
- CEL Expression: Custom transformations (advanced)
- Navigate to Registry > Partners
- Select one or more partners
- Click Action > Generate QR Credentials
- Configure wizard options:
- Select issuer
- Set validity period
- Choose generation mode:
- New Only: Skip partners with existing credentials
- Replace Expired: Replace only expired credentials
- Replace All: Replace all existing credentials
- Click Generate
Go to Claim 169 > Credentials to view all generated credentials:
- View QR code images
- Download CWT data
- Check expiration status
- Revoke credentials
Use the service API to verify credentials from QR data:
service = env["spp.claim169.service"]
result = service.verify_credential(qr_data, public_key_id)
if result["valid"]:
claims = result["claims"]
# Process claims
else:
error = result["error"]
# Handle error- Build Claims: Extract values from partner using attribute mappings
- Create CWT: Encode claims as CBOR and sign with COSE Sign1
- Compress: Apply zlib compression to reduce size
- Encode: Encode with Base45 for QR code compatibility
- Generate QR: Create QR code image
[Base45] -> [zlib decompress] -> [COSE Sign1] -> [CBOR] -> [Claims Map]
- spp.claim169.attribute.mapping: Field mappings configuration
- spp.claim169.issuer.config: Issuer configurations
- spp.claim169.credential: Stored credentials with QR codes
- spp.claim169.service: Service for credential operations (AbstractModel)
Three-tier access control:
- Claim 169 User: View credentials, generate QR codes
- Claim 169 Manager: Manage configurations, revoke credentials
- System Admin: Full access
Generate signed CWT for a partner.
Returns: (cwt_bytes, qr_data)
Encode CWT bytes for QR code (compress + Base45).
Returns: Base45 encoded string
Decode QR data to CWT bytes (Base45 decode + decompress).
Returns: CWT bytes
Verify credential from QR data.
Returns: {"valid": bool, "claims": dict, "error": str}
basespp_registryspp_cbor_cosespp_key_management
qrcode: QR code generationPillow: Image processing
./scripts/test_single_module.sh spp_claim_169Extend spp.claim169.attribute.mapping to add new transformation types:
class Claim169AttributeMapping(models.Model):
_inherit = "spp.claim169.attribute.mapping"
def _transform_value(self, value, partner):
if self.transform_type == "custom":
return self._transform_custom(value)
return super()._transform_value(value, partner)
def _transform_custom(self, value):
# Custom transformation logic
return transformed_valueLGPL-3
OpenSPP.org
- openspp-dev
- GitHub: https://github.com/OpenSPP/openspp-modules
- Documentation: https://docs.openspp.org