or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

association-management.mdconsumer-auth.mddiscovery.mdextensions.mdindex.mdmessage-processing.mdserver-implementation.mdstorage-backends.mdutilities.md

association-management.mddocs/

0

# Association Management

1

2

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.

3

4

## Capabilities

5

6

### Association Objects

7

8

Represents shared secrets between OpenID consumers and servers with cryptographic operations.

9

10

```python { .api }

11

class Association:

12

"""Shared secret association between consumer and server."""

13

14

def __init__(self, handle, secret, issued, lifetime, assoc_type):

15

"""

16

Initialize association with cryptographic parameters.

17

18

Parameters:

19

- handle: str, unique association handle

20

- secret: bytes, shared secret for signing

21

- issued: int, timestamp when association was created

22

- lifetime: int, association lifetime in seconds

23

- assoc_type: str, association type ('HMAC-SHA1' or 'HMAC-SHA256')

24

"""

25

26

@classmethod

27

def fromExpiresIn(cls, expires_in, handle, secret, assoc_type):

28

"""

29

Create association from expiration time.

30

31

Parameters:

32

- expires_in: int, seconds until expiration

33

- handle: str, association handle

34

- secret: bytes, shared secret

35

- assoc_type: str, association type

36

37

Returns:

38

Association object

39

"""

40

41

def serialize(self):

42

"""

43

Serialize association to string format for storage.

44

45

Returns:

46

str, serialized association data

47

"""

48

49

@classmethod

50

def deserialize(cls, assoc_s):

51

"""

52

Deserialize association from string format.

53

54

Parameters:

55

- assoc_s: str, serialized association data

56

57

Returns:

58

Association object

59

60

Raises:

61

ValueError: if deserialization fails

62

"""

63

64

def sign(self, pairs):

65

"""

66

Sign list of key-value pairs.

67

68

Parameters:

69

- pairs: list, [(key, value), ...] tuples to sign

70

71

Returns:

72

str, base64-encoded signature

73

"""

74

75

def getMessageSignature(self, message):

76

"""

77

Generate signature for OpenID message.

78

79

Parameters:

80

- message: Message object to sign

81

82

Returns:

83

str, base64-encoded message signature

84

"""

85

86

def signMessage(self, message):

87

"""

88

Add signature to OpenID message.

89

90

Parameters:

91

- message: Message object to sign (modified in-place)

92

"""

93

94

def checkMessageSignature(self, message):

95

"""

96

Verify message signature against this association.

97

98

Parameters:

99

- message: Message object to verify

100

101

Returns:

102

bool, True if signature is valid

103

"""

104

105

@property

106

def expiresIn(self):

107

"""

108

Get seconds until association expires.

109

110

Returns:

111

int, seconds until expiration (0 if expired)

112

"""

113

```

114

115

### Session Negotiation

116

117

Manages allowed association and session types for establishing shared secrets.

118

119

```python { .api }

120

class SessionNegotiator:

121

"""Manages allowed association and session type combinations."""

122

123

def __init__(self, allowed_types):

124

"""

125

Initialize negotiator with allowed type combinations.

126

127

Parameters:

128

- allowed_types: list, [(assoc_type, session_type), ...] tuples

129

"""

130

131

def copy(self):

132

"""

133

Create copy of this negotiator.

134

135

Returns:

136

SessionNegotiator, copy of this negotiator

137

"""

138

139

def setAllowedTypes(self, allowed_types):

140

"""

141

Set allowed association and session type combinations.

142

143

Parameters:

144

- allowed_types: list, [(assoc_type, session_type), ...] tuples

145

"""

146

147

def addAllowedType(self, assoc_type, session_type=None):

148

"""

149

Add allowed association and session type combination.

150

151

Parameters:

152

- assoc_type: str, association type ('HMAC-SHA1' or 'HMAC-SHA256')

153

- session_type: str, session type (None for all compatible types)

154

"""

155

156

def isAllowed(self, assoc_type, session_type):

157

"""

158

Check if association and session type combination is allowed.

159

160

Parameters:

161

- assoc_type: str, association type

162

- session_type: str, session type

163

164

Returns:

165

bool, True if combination is allowed

166

"""

167

168

def getAllowedType(self):

169

"""

170

Get preferred allowed association and session type combination.

171

172

Returns:

173

tuple, (assoc_type, session_type) or None if no types allowed

174

"""

175

```

176

177

### Association Utilities

178

179

Utility functions for working with association types and session compatibility.

180

181

```python { .api }

182

def getSessionTypes(assoc_type):

183

"""

184

Get compatible session types for association type.

185

186

Parameters:

187

- assoc_type: str, association type

188

189

Returns:

190

list, compatible session type names

191

"""

192

193

def checkSessionType(assoc_type, session_type):

194

"""

195

Validate that session type is compatible with association type.

196

197

Parameters:

198

- assoc_type: str, association type

199

- session_type: str, session type

200

201

Raises:

202

ValueError: if combination is invalid

203

"""

204

205

def getSecretSize(assoc_type):

206

"""

207

Get required secret size for association type.

208

209

Parameters:

210

- assoc_type: str, association type

211

212

Returns:

213

int, required secret size in bytes

214

"""

215

```

216

217

### Pre-configured Negotiators

218

219

Pre-configured session negotiators for common use cases.

220

221

```python { .api }

222

# Default negotiator allowing all standard combinations

223

default_negotiator = SessionNegotiator([

224

('HMAC-SHA1', 'DH-SHA1'),

225

('HMAC-SHA1', 'no-encryption'),

226

('HMAC-SHA256', 'DH-SHA256'),

227

('HMAC-SHA256', 'no-encryption')

228

])

229

230

# Encrypted-only negotiator (no plain-text sessions)

231

encrypted_negotiator = SessionNegotiator([

232

('HMAC-SHA1', 'DH-SHA1'),

233

('HMAC-SHA256', 'DH-SHA256')

234

])

235

```

236

237

## Usage Examples

238

239

### Basic Association Usage

240

241

```python

242

from openid.association import Association

243

import time

244

245

# Create association

246

handle = 'association_handle_123'

247

secret = b'shared_secret_bytes'

248

issued = int(time.time())

249

lifetime = 3600 # 1 hour

250

assoc_type = 'HMAC-SHA1'

251

252

association = Association(handle, secret, issued, lifetime, assoc_type)

253

254

# Check expiration

255

if association.expiresIn > 0:

256

print(f"Association expires in {association.expiresIn} seconds")

257

else:

258

print("Association has expired")

259

260

# Serialize for storage

261

serialized = association.serialize()

262

print(f"Serialized: {serialized}")

263

264

# Deserialize from storage

265

restored = Association.deserialize(serialized)

266

print(f"Restored handle: {restored.handle}")

267

```

268

269

### Message Signing

270

271

```python

272

from openid.message import Message

273

from openid.association import Association

274

275

# Create message

276

message = Message()

277

message.setArg('openid.ns', 'http://specs.openid.net/auth/2.0')

278

message.setArg('openid.mode', 'id_res')

279

message.setArg('openid.identity', 'https://user.example.com')

280

message.setArg('openid.return_to', 'https://consumer.example.com/return')

281

282

# Sign message with association

283

association.signMessage(message)

284

285

# Verify signature

286

is_valid = association.checkMessageSignature(message)

287

print(f"Signature valid: {is_valid}")

288

289

# Manual signing of key-value pairs

290

pairs = [

291

('openid.mode', 'id_res'),

292

('openid.identity', 'https://user.example.com'),

293

('openid.return_to', 'https://consumer.example.com/return')

294

]

295

signature = association.sign(pairs)

296

print(f"Manual signature: {signature}")

297

```

298

299

### Session Negotiator Usage

300

301

```python

302

from openid.association import SessionNegotiator

303

304

# Create custom negotiator

305

negotiator = SessionNegotiator([

306

('HMAC-SHA256', 'DH-SHA256'), # Prefer SHA256 with DH

307

('HMAC-SHA1', 'DH-SHA1') # Fallback to SHA1 with DH

308

])

309

310

# Check if combination is allowed

311

if negotiator.isAllowed('HMAC-SHA256', 'DH-SHA256'):

312

print("SHA256 with DH is allowed")

313

314

# Get preferred type

315

preferred = negotiator.getAllowedType()

316

if preferred:

317

assoc_type, session_type = preferred

318

print(f"Preferred: {assoc_type} with {session_type}")

319

320

# Add new allowed type

321

negotiator.addAllowedType('HMAC-SHA256', 'no-encryption')

322

323

# Use pre-configured negotiators

324

from openid.association import default_negotiator, encrypted_negotiator

325

326

# Default allows plain-text sessions

327

if default_negotiator.isAllowed('HMAC-SHA1', 'no-encryption'):

328

print("Plain-text sessions allowed in default negotiator")

329

330

# Encrypted-only does not allow plain-text

331

if not encrypted_negotiator.isAllowed('HMAC-SHA1', 'no-encryption'):

332

print("Plain-text sessions not allowed in encrypted negotiator")

333

```

334

335

### Consumer Association Management

336

337

```python

338

from openid.consumer import consumer

339

from openid.association import encrypted_negotiator

340

341

# Create consumer with encrypted-only associations

342

openid_consumer = consumer.Consumer({}, store)

343

openid_consumer.setAssociationPreference([

344

('HMAC-SHA256', 'DH-SHA256'),

345

('HMAC-SHA1', 'DH-SHA1')

346

])

347

348

# Start authentication (will use preferred association types)

349

auth_request = openid_consumer.begin(user_url)

350

```

351

352

### Server Association Creation

353

354

```python

355

from openid.server.server import Signatory

356

from openid.association import getSecretSize

357

import os

358

359

# Create signatory for association management

360

signatory = Signatory(store)

361

362

# Create association with specific type

363

assoc_type = 'HMAC-SHA256'

364

secret_size = getSecretSize(assoc_type)

365

secret = os.urandom(secret_size)

366

367

association = signatory.createAssociation(

368

dumb=False, # Smart mode association

369

assoc_type=assoc_type

370

)

371

372

print(f"Created association: {association.handle}")

373

print(f"Expires in: {association.expiresIn} seconds")

374

375

# Store association

376

store.storeAssociation('https://consumer.example.com', association)

377

378

# Later, retrieve for verification

379

retrieved = store.getAssociation('https://consumer.example.com', association.handle)

380

if retrieved:

381

print(f"Retrieved association: {retrieved.handle}")

382

```

383

384

### Association Validation

385

386

```python

387

from openid.association import checkSessionType, getSessionTypes

388

389

# Validate session type compatibility

390

try:

391

checkSessionType('HMAC-SHA256', 'DH-SHA256')

392

print("Valid combination")

393

except ValueError as e:

394

print(f"Invalid combination: {e}")

395

396

# Get compatible session types

397

session_types = getSessionTypes('HMAC-SHA1')

398

print(f"Compatible session types for HMAC-SHA1: {session_types}")

399

400

# Check secret size requirements

401

secret_size_sha1 = getSecretSize('HMAC-SHA1')

402

secret_size_sha256 = getSecretSize('HMAC-SHA256')

403

print(f"HMAC-SHA1 requires {secret_size_sha1} byte secret")

404

print(f"HMAC-SHA256 requires {secret_size_sha256} byte secret")

405

```

406

407

## Types

408

409

```python { .api }

410

# Association types

411

all_association_types = ['HMAC-SHA1', 'HMAC-SHA256']

412

413

# Session types

414

SESSION_TYPE_NO_ENCRYPTION = 'no-encryption'

415

SESSION_TYPE_DH_SHA1 = 'DH-SHA1'

416

SESSION_TYPE_DH_SHA256 = 'DH-SHA256'

417

418

# Secret sizes (in bytes)

419

SECRET_SIZE = {

420

'HMAC-SHA1': 20,

421

'HMAC-SHA256': 32

422

}

423

424

# Default association lifetime

425

DEFAULT_LIFETIME = 14 * 24 * 60 * 60 # 14 days in seconds

426

427

# Association handle format

428

ASSOCIATION_HANDLE_LENGTH = 255 # Maximum length

429

430

# Serialization format version

431

ASSOCIATION_SERIALIZATION_VERSION = 2

432

```