OpenID support for modern servers and consumers with comprehensive authentication functionality.
—
Handles shared secrets between consumers and servers including creation, serialization, message signing, and expiration management. Associations enable efficient message signing without requiring the consumer to contact the server for each verification.
Represents shared secrets between OpenID consumers and servers with cryptographic operations.
class Association:
"""Shared secret association between consumer and server."""
def __init__(self, handle, secret, issued, lifetime, assoc_type):
"""
Initialize association with cryptographic parameters.
Parameters:
- handle: str, unique association handle
- secret: bytes, shared secret for signing
- issued: int, timestamp when association was created
- lifetime: int, association lifetime in seconds
- assoc_type: str, association type ('HMAC-SHA1' or 'HMAC-SHA256')
"""
@classmethod
def fromExpiresIn(cls, expires_in, handle, secret, assoc_type):
"""
Create association from expiration time.
Parameters:
- expires_in: int, seconds until expiration
- handle: str, association handle
- secret: bytes, shared secret
- assoc_type: str, association type
Returns:
Association object
"""
def serialize(self):
"""
Serialize association to string format for storage.
Returns:
str, serialized association data
"""
@classmethod
def deserialize(cls, assoc_s):
"""
Deserialize association from string format.
Parameters:
- assoc_s: str, serialized association data
Returns:
Association object
Raises:
ValueError: if deserialization fails
"""
def sign(self, pairs):
"""
Sign list of key-value pairs.
Parameters:
- pairs: list, [(key, value), ...] tuples to sign
Returns:
str, base64-encoded signature
"""
def getMessageSignature(self, message):
"""
Generate signature for OpenID message.
Parameters:
- message: Message object to sign
Returns:
str, base64-encoded message signature
"""
def signMessage(self, message):
"""
Add signature to OpenID message.
Parameters:
- message: Message object to sign (modified in-place)
"""
def checkMessageSignature(self, message):
"""
Verify message signature against this association.
Parameters:
- message: Message object to verify
Returns:
bool, True if signature is valid
"""
@property
def expiresIn(self):
"""
Get seconds until association expires.
Returns:
int, seconds until expiration (0 if expired)
"""Manages allowed association and session types for establishing shared secrets.
class SessionNegotiator:
"""Manages allowed association and session type combinations."""
def __init__(self, allowed_types):
"""
Initialize negotiator with allowed type combinations.
Parameters:
- allowed_types: list, [(assoc_type, session_type), ...] tuples
"""
def copy(self):
"""
Create copy of this negotiator.
Returns:
SessionNegotiator, copy of this negotiator
"""
def setAllowedTypes(self, allowed_types):
"""
Set allowed association and session type combinations.
Parameters:
- allowed_types: list, [(assoc_type, session_type), ...] tuples
"""
def addAllowedType(self, assoc_type, session_type=None):
"""
Add allowed association and session type combination.
Parameters:
- assoc_type: str, association type ('HMAC-SHA1' or 'HMAC-SHA256')
- session_type: str, session type (None for all compatible types)
"""
def isAllowed(self, assoc_type, session_type):
"""
Check if association and session type combination is allowed.
Parameters:
- assoc_type: str, association type
- session_type: str, session type
Returns:
bool, True if combination is allowed
"""
def getAllowedType(self):
"""
Get preferred allowed association and session type combination.
Returns:
tuple, (assoc_type, session_type) or None if no types allowed
"""Utility functions for working with association types and session compatibility.
def getSessionTypes(assoc_type):
"""
Get compatible session types for association type.
Parameters:
- assoc_type: str, association type
Returns:
list, compatible session type names
"""
def checkSessionType(assoc_type, session_type):
"""
Validate that session type is compatible with association type.
Parameters:
- assoc_type: str, association type
- session_type: str, session type
Raises:
ValueError: if combination is invalid
"""
def getSecretSize(assoc_type):
"""
Get required secret size for association type.
Parameters:
- assoc_type: str, association type
Returns:
int, required secret size in bytes
"""Pre-configured session negotiators for common use cases.
# Default negotiator allowing all standard combinations
default_negotiator = SessionNegotiator([
('HMAC-SHA1', 'DH-SHA1'),
('HMAC-SHA1', 'no-encryption'),
('HMAC-SHA256', 'DH-SHA256'),
('HMAC-SHA256', 'no-encryption')
])
# Encrypted-only negotiator (no plain-text sessions)
encrypted_negotiator = SessionNegotiator([
('HMAC-SHA1', 'DH-SHA1'),
('HMAC-SHA256', 'DH-SHA256')
])from openid.association import Association
import time
# Create association
handle = 'association_handle_123'
secret = b'shared_secret_bytes'
issued = int(time.time())
lifetime = 3600 # 1 hour
assoc_type = 'HMAC-SHA1'
association = Association(handle, secret, issued, lifetime, assoc_type)
# Check expiration
if association.expiresIn > 0:
print(f"Association expires in {association.expiresIn} seconds")
else:
print("Association has expired")
# Serialize for storage
serialized = association.serialize()
print(f"Serialized: {serialized}")
# Deserialize from storage
restored = Association.deserialize(serialized)
print(f"Restored handle: {restored.handle}")from openid.message import Message
from openid.association import Association
# Create message
message = Message()
message.setArg('openid.ns', 'http://specs.openid.net/auth/2.0')
message.setArg('openid.mode', 'id_res')
message.setArg('openid.identity', 'https://user.example.com')
message.setArg('openid.return_to', 'https://consumer.example.com/return')
# Sign message with association
association.signMessage(message)
# Verify signature
is_valid = association.checkMessageSignature(message)
print(f"Signature valid: {is_valid}")
# Manual signing of key-value pairs
pairs = [
('openid.mode', 'id_res'),
('openid.identity', 'https://user.example.com'),
('openid.return_to', 'https://consumer.example.com/return')
]
signature = association.sign(pairs)
print(f"Manual signature: {signature}")from openid.association import SessionNegotiator
# Create custom negotiator
negotiator = SessionNegotiator([
('HMAC-SHA256', 'DH-SHA256'), # Prefer SHA256 with DH
('HMAC-SHA1', 'DH-SHA1') # Fallback to SHA1 with DH
])
# Check if combination is allowed
if negotiator.isAllowed('HMAC-SHA256', 'DH-SHA256'):
print("SHA256 with DH is allowed")
# Get preferred type
preferred = negotiator.getAllowedType()
if preferred:
assoc_type, session_type = preferred
print(f"Preferred: {assoc_type} with {session_type}")
# Add new allowed type
negotiator.addAllowedType('HMAC-SHA256', 'no-encryption')
# Use pre-configured negotiators
from openid.association import default_negotiator, encrypted_negotiator
# Default allows plain-text sessions
if default_negotiator.isAllowed('HMAC-SHA1', 'no-encryption'):
print("Plain-text sessions allowed in default negotiator")
# Encrypted-only does not allow plain-text
if not encrypted_negotiator.isAllowed('HMAC-SHA1', 'no-encryption'):
print("Plain-text sessions not allowed in encrypted negotiator")from openid.consumer import consumer
from openid.association import encrypted_negotiator
# Create consumer with encrypted-only associations
openid_consumer = consumer.Consumer({}, store)
openid_consumer.setAssociationPreference([
('HMAC-SHA256', 'DH-SHA256'),
('HMAC-SHA1', 'DH-SHA1')
])
# Start authentication (will use preferred association types)
auth_request = openid_consumer.begin(user_url)from openid.server.server import Signatory
from openid.association import getSecretSize
import os
# Create signatory for association management
signatory = Signatory(store)
# Create association with specific type
assoc_type = 'HMAC-SHA256'
secret_size = getSecretSize(assoc_type)
secret = os.urandom(secret_size)
association = signatory.createAssociation(
dumb=False, # Smart mode association
assoc_type=assoc_type
)
print(f"Created association: {association.handle}")
print(f"Expires in: {association.expiresIn} seconds")
# Store association
store.storeAssociation('https://consumer.example.com', association)
# Later, retrieve for verification
retrieved = store.getAssociation('https://consumer.example.com', association.handle)
if retrieved:
print(f"Retrieved association: {retrieved.handle}")from openid.association import checkSessionType, getSessionTypes
# Validate session type compatibility
try:
checkSessionType('HMAC-SHA256', 'DH-SHA256')
print("Valid combination")
except ValueError as e:
print(f"Invalid combination: {e}")
# Get compatible session types
session_types = getSessionTypes('HMAC-SHA1')
print(f"Compatible session types for HMAC-SHA1: {session_types}")
# Check secret size requirements
secret_size_sha1 = getSecretSize('HMAC-SHA1')
secret_size_sha256 = getSecretSize('HMAC-SHA256')
print(f"HMAC-SHA1 requires {secret_size_sha1} byte secret")
print(f"HMAC-SHA256 requires {secret_size_sha256} byte secret")# Association types
all_association_types = ['HMAC-SHA1', 'HMAC-SHA256']
# Session types
SESSION_TYPE_NO_ENCRYPTION = 'no-encryption'
SESSION_TYPE_DH_SHA1 = 'DH-SHA1'
SESSION_TYPE_DH_SHA256 = 'DH-SHA256'
# Secret sizes (in bytes)
SECRET_SIZE = {
'HMAC-SHA1': 20,
'HMAC-SHA256': 32
}
# Default association lifetime
DEFAULT_LIFETIME = 14 * 24 * 60 * 60 # 14 days in seconds
# Association handle format
ASSOCIATION_HANDLE_LENGTH = 255 # Maximum length
# Serialization format version
ASSOCIATION_SERIALIZATION_VERSION = 2Install with Tessl CLI
npx tessl i tessl/pypi-python3-openid