omniauth_openid_federation 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of omniauth_openid_federation might be problematic. Click here for more details.
- checksums.yaml +7 -0
- data/CHANGELOG.md +36 -0
- data/LICENSE.md +22 -0
- data/README.md +922 -0
- data/SECURITY.md +28 -0
- data/examples/README_INTEGRATION_TESTING.md +399 -0
- data/examples/README_MOCK_OP.md +243 -0
- data/examples/app/controllers/users/omniauth_callbacks_controller.rb.example +37 -0
- data/examples/app/jobs/jwks_rotation_job.rb.example +60 -0
- data/examples/app/models/user.rb.example +39 -0
- data/examples/config/initializers/devise.rb.example +131 -0
- data/examples/config/initializers/federation_endpoint.rb.example +206 -0
- data/examples/config/mock_op.yml.example +83 -0
- data/examples/config/open_id_connect_config.rb.example +210 -0
- data/examples/config/routes.rb.example +12 -0
- data/examples/db/migrate/add_omniauth_to_users.rb.example +16 -0
- data/examples/integration_test_flow.rb +1334 -0
- data/examples/jobs/README.md +194 -0
- data/examples/jobs/federation_cache_refresh_job.rb.example +78 -0
- data/examples/jobs/federation_files_generation_job.rb.example +87 -0
- data/examples/mock_op_server.rb +775 -0
- data/examples/mock_rp_server.rb +435 -0
- data/lib/omniauth_openid_federation/access_token.rb +504 -0
- data/lib/omniauth_openid_federation/cache.rb +39 -0
- data/lib/omniauth_openid_federation/cache_adapter.rb +173 -0
- data/lib/omniauth_openid_federation/configuration.rb +135 -0
- data/lib/omniauth_openid_federation/constants.rb +13 -0
- data/lib/omniauth_openid_federation/endpoint_resolver.rb +168 -0
- data/lib/omniauth_openid_federation/engine.rb +21 -0
- data/lib/omniauth_openid_federation/entity_statement_reader.rb +129 -0
- data/lib/omniauth_openid_federation/errors.rb +52 -0
- data/lib/omniauth_openid_federation/federation/entity_statement.rb +331 -0
- data/lib/omniauth_openid_federation/federation/entity_statement_builder.rb +188 -0
- data/lib/omniauth_openid_federation/federation/entity_statement_fetcher.rb +142 -0
- data/lib/omniauth_openid_federation/federation/entity_statement_helper.rb +87 -0
- data/lib/omniauth_openid_federation/federation/entity_statement_parser.rb +198 -0
- data/lib/omniauth_openid_federation/federation/entity_statement_validator.rb +502 -0
- data/lib/omniauth_openid_federation/federation/metadata_policy_merger.rb +276 -0
- data/lib/omniauth_openid_federation/federation/signed_jwks.rb +210 -0
- data/lib/omniauth_openid_federation/federation/trust_chain_resolver.rb +225 -0
- data/lib/omniauth_openid_federation/federation_endpoint.rb +949 -0
- data/lib/omniauth_openid_federation/http_client.rb +70 -0
- data/lib/omniauth_openid_federation/instrumentation.rb +399 -0
- data/lib/omniauth_openid_federation/jwks/cache.rb +76 -0
- data/lib/omniauth_openid_federation/jwks/decode.rb +175 -0
- data/lib/omniauth_openid_federation/jwks/fetch.rb +153 -0
- data/lib/omniauth_openid_federation/jwks/normalizer.rb +49 -0
- data/lib/omniauth_openid_federation/jwks/rotate.rb +97 -0
- data/lib/omniauth_openid_federation/jwks/selector.rb +101 -0
- data/lib/omniauth_openid_federation/jws.rb +410 -0
- data/lib/omniauth_openid_federation/key_extractor.rb +173 -0
- data/lib/omniauth_openid_federation/logger.rb +99 -0
- data/lib/omniauth_openid_federation/rack_endpoint.rb +187 -0
- data/lib/omniauth_openid_federation/railtie.rb +15 -0
- data/lib/omniauth_openid_federation/rate_limiter.rb +55 -0
- data/lib/omniauth_openid_federation/strategy.rb +2114 -0
- data/lib/omniauth_openid_federation/string_helpers.rb +30 -0
- data/lib/omniauth_openid_federation/tasks_helper.rb +428 -0
- data/lib/omniauth_openid_federation/utils.rb +168 -0
- data/lib/omniauth_openid_federation/validators.rb +126 -0
- data/lib/omniauth_openid_federation/version.rb +3 -0
- data/lib/omniauth_openid_federation.rb +99 -0
- data/lib/tasks/omniauth_openid_federation.rake +376 -0
- data/sig/federation.rbs +218 -0
- data/sig/jwks.rbs +63 -0
- data/sig/omniauth_openid_federation.rbs +254 -0
- data/sig/strategy.rbs +60 -0
- metadata +359 -0
data/SECURITY.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# SECURITY
|
|
2
|
+
|
|
3
|
+
## Reporting a Vulnerability
|
|
4
|
+
|
|
5
|
+
**Do NOT** open a public GitHub issue for security vulnerabilities.
|
|
6
|
+
|
|
7
|
+
Email security details to: **security@kiskolabs.com**
|
|
8
|
+
|
|
9
|
+
Include: description, steps to reproduce, potential impact, and suggested fix (if available).
|
|
10
|
+
|
|
11
|
+
### Response Timeline
|
|
12
|
+
|
|
13
|
+
- We will acknowledge receipt of your report
|
|
14
|
+
- We will provide an initial assessment
|
|
15
|
+
- We will keep you informed of our progress and resolution timeline
|
|
16
|
+
|
|
17
|
+
### Disclosure Policy
|
|
18
|
+
|
|
19
|
+
- We will work with you to understand and resolve the issue
|
|
20
|
+
- We will credit you for the discovery (unless you prefer to remain anonymous)
|
|
21
|
+
- We will publish a security advisory after the vulnerability is patched
|
|
22
|
+
- We will coordinate public disclosure with you
|
|
23
|
+
|
|
24
|
+
## Automation Security
|
|
25
|
+
|
|
26
|
+
* **Context Isolation:** It is strictly forbidden to include production credentials, API keys, or Personally Identifiable Information (PII) in prompts sent to third-party LLMs or automation services.
|
|
27
|
+
|
|
28
|
+
* **Supply Chain:** All automated dependencies must be verified.
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# OpenID Federation Integration Testing
|
|
2
|
+
|
|
3
|
+
This directory contains comprehensive mock servers and integration tests for the complete OpenID Federation flow.
|
|
4
|
+
|
|
5
|
+
## Architecture
|
|
6
|
+
|
|
7
|
+
The integration test setup consists of:
|
|
8
|
+
|
|
9
|
+
1. **Mock OP Server** (`mock_op_server.rb`) - Simulates an OpenID Provider
|
|
10
|
+
2. **Mock RP Server** (`mock_rp_server.rb`) - Simulates a Relying Party (Client)
|
|
11
|
+
3. **Integration Test Flow** (`integration_test_flow.rb`) - Automated tests for the complete flow
|
|
12
|
+
|
|
13
|
+
## Complete OpenID Federation Flow
|
|
14
|
+
|
|
15
|
+
### 1. Provider Exposes Entity Statement with JWKS
|
|
16
|
+
|
|
17
|
+
The OP server exposes its entity configuration at:
|
|
18
|
+
```
|
|
19
|
+
GET /.well-known/openid-federation
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Returns a signed JWT containing:
|
|
23
|
+
- Provider metadata (authorization_endpoint, token_endpoint, etc.)
|
|
24
|
+
- JWKS (public keys for signing/encryption)
|
|
25
|
+
- Authority hints (if subordinate to a Trust Anchor)
|
|
26
|
+
|
|
27
|
+
### 2. Client Exposes Entity Statement with JWKS
|
|
28
|
+
|
|
29
|
+
The RP server exposes its entity configuration at:
|
|
30
|
+
```
|
|
31
|
+
GET /.well-known/openid-federation
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Returns a signed JWT containing:
|
|
35
|
+
- RP metadata (redirect_uris, client_name, etc.)
|
|
36
|
+
- JWKS (public keys for signing request objects)
|
|
37
|
+
- Authority hints (if subordinate to a Trust Anchor)
|
|
38
|
+
|
|
39
|
+
### 3. Client Fetches Provider Statement with Keys
|
|
40
|
+
|
|
41
|
+
When the RP initiates login:
|
|
42
|
+
1. RP fetches OP's entity statement from `/.well-known/openid-federation`
|
|
43
|
+
2. RP validates the entity statement signature
|
|
44
|
+
3. RP extracts OP's JWKS for future token validation
|
|
45
|
+
4. RP extracts OP's metadata (endpoints, capabilities)
|
|
46
|
+
|
|
47
|
+
### 4. Client Sends Login Request
|
|
48
|
+
|
|
49
|
+
RP generates a signed request object (JWT) containing:
|
|
50
|
+
- `client_id`: RP's Entity ID
|
|
51
|
+
- `redirect_uri`: Callback URL
|
|
52
|
+
- `scope`: Requested scopes
|
|
53
|
+
- `state`: CSRF protection
|
|
54
|
+
- `nonce`: Replay protection
|
|
55
|
+
- `aud`: OP's Entity ID
|
|
56
|
+
|
|
57
|
+
The request object is:
|
|
58
|
+
- **Always signed** with RP's private key (RFC 9101 requirement)
|
|
59
|
+
- **Optionally encrypted** if OP requires encryption
|
|
60
|
+
|
|
61
|
+
RP redirects to OP's authorization endpoint:
|
|
62
|
+
```
|
|
63
|
+
GET /auth?request=<signed_jwt>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 5. Provider Fetches Client Statement and Keys
|
|
67
|
+
|
|
68
|
+
When OP receives the authorization request:
|
|
69
|
+
1. OP extracts `client_id` from the request object
|
|
70
|
+
2. OP fetches RP's entity statement from `/.well-known/openid-federation`
|
|
71
|
+
3. OP validates RP's entity statement signature
|
|
72
|
+
4. OP extracts RP's JWKS from the entity statement
|
|
73
|
+
5. OP validates the request object signature using RP's public key
|
|
74
|
+
6. OP resolves RP's trust chain (if trust anchors configured)
|
|
75
|
+
7. OP applies metadata policies from trust chain
|
|
76
|
+
8. OP validates `redirect_uri` against RP's allowed redirect URIs
|
|
77
|
+
|
|
78
|
+
### 6. Exchange and Authenticated Login
|
|
79
|
+
|
|
80
|
+
After user authorization:
|
|
81
|
+
1. OP redirects back to RP with authorization code
|
|
82
|
+
2. RP exchanges code for tokens at OP's token endpoint
|
|
83
|
+
3. OP returns ID token (signed with OP's private key)
|
|
84
|
+
4. RP validates ID token signature using OP's JWKS
|
|
85
|
+
5. RP validates ID token claims (iss, aud, exp, nonce)
|
|
86
|
+
6. User is authenticated
|
|
87
|
+
|
|
88
|
+
## Error Scenarios Supported
|
|
89
|
+
|
|
90
|
+
The mock servers support error injection via `?error_mode=<mode>` parameter:
|
|
91
|
+
|
|
92
|
+
### Invalid Statement
|
|
93
|
+
```
|
|
94
|
+
GET /.well-known/openid-federation?error_mode=invalid_statement
|
|
95
|
+
```
|
|
96
|
+
Returns malformed JWT to test error handling.
|
|
97
|
+
|
|
98
|
+
### Wrong Keys
|
|
99
|
+
```
|
|
100
|
+
GET /.well-known/jwks.json?error_mode=wrong_keys
|
|
101
|
+
GET /.well-known/openid-federation?error_mode=wrong_keys
|
|
102
|
+
```
|
|
103
|
+
Returns JWKS with keys that don't match the signing key.
|
|
104
|
+
|
|
105
|
+
### Invalid Request
|
|
106
|
+
```
|
|
107
|
+
GET /auth?error_mode=invalid_request
|
|
108
|
+
```
|
|
109
|
+
Rejects request object validation to test error handling.
|
|
110
|
+
|
|
111
|
+
### Invalid Signature
|
|
112
|
+
```
|
|
113
|
+
GET /.well-known/signed-jwks.json?error_mode=invalid_signature
|
|
114
|
+
```
|
|
115
|
+
Returns signed JWKS with invalid signature.
|
|
116
|
+
|
|
117
|
+
### Expired Statement
|
|
118
|
+
```
|
|
119
|
+
GET /.well-known/openid-federation?error_mode=expired_statement
|
|
120
|
+
```
|
|
121
|
+
Returns expired entity statement to test expiration handling.
|
|
122
|
+
|
|
123
|
+
### Missing Metadata
|
|
124
|
+
```
|
|
125
|
+
GET /.well-known/openid-federation?error_mode=missing_metadata
|
|
126
|
+
```
|
|
127
|
+
Returns entity statement without metadata to test validation.
|
|
128
|
+
|
|
129
|
+
## Usage
|
|
130
|
+
|
|
131
|
+
### Quick Start (Automated - Recommended)
|
|
132
|
+
|
|
133
|
+
The integration test flow can automatically start servers, generate keys, and run all tests:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Single command - fully automated
|
|
137
|
+
ruby examples/integration_test_flow.rb
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
This will:
|
|
141
|
+
1. Create temporary directories for keys and configs
|
|
142
|
+
2. Generate RSA keys for both OP and RP (using rake task logic)
|
|
143
|
+
3. Configure and start both servers automatically
|
|
144
|
+
4. Wait for servers to be ready
|
|
145
|
+
5. Run all integration tests
|
|
146
|
+
6. Clean up (kill servers, remove tmp dirs) on exit
|
|
147
|
+
|
|
148
|
+
### Manual Start (For Debugging)
|
|
149
|
+
|
|
150
|
+
If you want to start servers manually for debugging:
|
|
151
|
+
|
|
152
|
+
**Terminal 1 - OP Server:**
|
|
153
|
+
```bash
|
|
154
|
+
ruby examples/mock_op_server.rb
|
|
155
|
+
# Server runs on http://localhost:9292
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
**Terminal 2 - RP Server:**
|
|
159
|
+
```bash
|
|
160
|
+
ruby examples/mock_rp_server.rb
|
|
161
|
+
# Server runs on http://localhost:9293
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Terminal 3 - Integration Tests:**
|
|
165
|
+
```bash
|
|
166
|
+
# Disable auto-start to use manually started servers
|
|
167
|
+
AUTO_START_SERVERS=false ruby examples/integration_test_flow.rb
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Environment Variables
|
|
171
|
+
|
|
172
|
+
The integration test flow supports the following environment variables:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Server URLs
|
|
176
|
+
OP_URL=http://localhost:9292 # OP server URL
|
|
177
|
+
RP_URL=http://localhost:9293 # RP server URL
|
|
178
|
+
|
|
179
|
+
# Server Ports
|
|
180
|
+
OP_PORT=9292 # OP server port
|
|
181
|
+
RP_PORT=9293 # RP server port
|
|
182
|
+
|
|
183
|
+
# Entity IDs
|
|
184
|
+
OP_ENTITY_ID=https://op.example.com # OP entity identifier
|
|
185
|
+
RP_ENTITY_ID=https://rp.example.com # RP entity identifier
|
|
186
|
+
|
|
187
|
+
# Temporary Directory
|
|
188
|
+
TMP_DIR=tmp/integration_test # Directory for keys/configs (default: tmp/integration_test)
|
|
189
|
+
|
|
190
|
+
# Auto-start servers (true/false)
|
|
191
|
+
AUTO_START_SERVERS=true # Auto-start servers (default: true)
|
|
192
|
+
|
|
193
|
+
# Cleanup on exit (true/false)
|
|
194
|
+
CLEANUP_ON_EXIT=true # Clean up tmp dirs on exit (default: true)
|
|
195
|
+
|
|
196
|
+
# Key Type
|
|
197
|
+
KEY_TYPE=separate # 'single' or 'separate' (default: separate)
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
**Example with custom configuration:**
|
|
201
|
+
```bash
|
|
202
|
+
KEY_TYPE=separate \
|
|
203
|
+
TMP_DIR=/tmp/my_test \
|
|
204
|
+
ruby examples/integration_test_flow.rb
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Note**: By default, the integration test uses localhost URLs for complete isolation:
|
|
208
|
+
- No DNS resolution required
|
|
209
|
+
- No external network dependencies
|
|
210
|
+
- All communication happens on localhost
|
|
211
|
+
- Entity IDs default to `http://localhost:9292` (OP) and `http://localhost:9293` (RP)
|
|
212
|
+
|
|
213
|
+
This ensures the tests work in any environment without network configuration.
|
|
214
|
+
|
|
215
|
+
### Manual Testing
|
|
216
|
+
|
|
217
|
+
**1. Test Provider Entity Statement:**
|
|
218
|
+
```bash
|
|
219
|
+
curl http://localhost:9292/.well-known/openid-federation
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
**2. Test Client Entity Statement:**
|
|
223
|
+
```bash
|
|
224
|
+
curl http://localhost:9293/.well-known/openid-federation
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
**3. Test Login Flow:**
|
|
228
|
+
```bash
|
|
229
|
+
# Initiate login (will redirect to OP)
|
|
230
|
+
curl -L "http://localhost:9293/login?provider=https://op.example.com"
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
**4. Test Error Scenarios:**
|
|
234
|
+
```bash
|
|
235
|
+
# Invalid statement
|
|
236
|
+
curl "http://localhost:9292/.well-known/openid-federation?error_mode=invalid_statement"
|
|
237
|
+
|
|
238
|
+
# Wrong keys
|
|
239
|
+
curl "http://localhost:9292/.well-known/jwks.json?error_mode=wrong_keys"
|
|
240
|
+
|
|
241
|
+
# Expired statement
|
|
242
|
+
curl "http://localhost:9292/.well-known/openid-federation?error_mode=expired_statement"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
## Configuration
|
|
246
|
+
|
|
247
|
+
### OP Server Configuration
|
|
248
|
+
|
|
249
|
+
Create `examples/config/mock_op.yml`:
|
|
250
|
+
|
|
251
|
+
```yaml
|
|
252
|
+
entity_id: "https://op.example.com"
|
|
253
|
+
server_host: "localhost:9292"
|
|
254
|
+
signing_key: |
|
|
255
|
+
-----BEGIN RSA PRIVATE KEY-----
|
|
256
|
+
...
|
|
257
|
+
-----END RSA PRIVATE KEY-----
|
|
258
|
+
encryption_key: | # Optional, defaults to signing_key
|
|
259
|
+
-----BEGIN RSA PRIVATE KEY-----
|
|
260
|
+
...
|
|
261
|
+
-----END RSA PRIVATE KEY-----
|
|
262
|
+
trust_anchors:
|
|
263
|
+
- entity_id: "https://ta.example.com"
|
|
264
|
+
jwks:
|
|
265
|
+
keys:
|
|
266
|
+
- kty: "RSA"
|
|
267
|
+
kid: "ta-key-1"
|
|
268
|
+
use: "sig"
|
|
269
|
+
n: "..."
|
|
270
|
+
e: "AQAB"
|
|
271
|
+
authority_hints: # Optional, if OP is subordinate
|
|
272
|
+
- "https://ta.example.com"
|
|
273
|
+
op_metadata:
|
|
274
|
+
issuer: "https://op.example.com"
|
|
275
|
+
authorization_endpoint: "https://op.example.com/auth"
|
|
276
|
+
token_endpoint: "https://op.example.com/token"
|
|
277
|
+
request_object_encryption_alg_values_supported: ["RSA-OAEP"]
|
|
278
|
+
request_object_encryption_enc_values_supported: ["A128CBC-HS256"]
|
|
279
|
+
require_request_encryption: false # Set to true to require encryption
|
|
280
|
+
validate_request_objects: true # Set to false to skip validation
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### RP Server Configuration
|
|
284
|
+
|
|
285
|
+
Create `examples/config/mock_rp.yml`:
|
|
286
|
+
|
|
287
|
+
```yaml
|
|
288
|
+
entity_id: "https://rp.example.com"
|
|
289
|
+
server_host: "localhost:9293"
|
|
290
|
+
signing_key: |
|
|
291
|
+
-----BEGIN RSA PRIVATE KEY-----
|
|
292
|
+
...
|
|
293
|
+
-----END RSA PRIVATE KEY-----
|
|
294
|
+
encryption_key: | # Optional, for decrypting encrypted ID tokens
|
|
295
|
+
-----BEGIN RSA PRIVATE KEY-----
|
|
296
|
+
...
|
|
297
|
+
-----END RSA PRIVATE KEY-----
|
|
298
|
+
trust_anchors:
|
|
299
|
+
- entity_id: "https://ta.example.com"
|
|
300
|
+
jwks:
|
|
301
|
+
keys: [...]
|
|
302
|
+
authority_hints: # Optional, if RP is subordinate
|
|
303
|
+
- "https://ta.example.com"
|
|
304
|
+
redirect_uris:
|
|
305
|
+
- "https://rp.example.com/callback"
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
## Testing Scenarios
|
|
309
|
+
|
|
310
|
+
### Happy Path
|
|
311
|
+
|
|
312
|
+
1. Start both servers
|
|
313
|
+
2. Run integration tests: `ruby examples/integration_test_flow.rb`
|
|
314
|
+
3. All tests should pass
|
|
315
|
+
|
|
316
|
+
### Error Scenarios
|
|
317
|
+
|
|
318
|
+
1. Test invalid entity statement:
|
|
319
|
+
```bash
|
|
320
|
+
curl "http://localhost:9292/.well-known/openid-federation?error_mode=invalid_statement"
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
2. Test wrong keys:
|
|
324
|
+
```bash
|
|
325
|
+
curl "http://localhost:9292/.well-known/jwks.json?error_mode=wrong_keys"
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
3. Test expired statement:
|
|
329
|
+
```bash
|
|
330
|
+
curl "http://localhost:9292/.well-known/openid-federation?error_mode=expired_statement"
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
### Request Object Validation
|
|
334
|
+
|
|
335
|
+
1. Test with valid signed request object:
|
|
336
|
+
```bash
|
|
337
|
+
curl "http://localhost:9293/login?provider=https://op.example.com"
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
2. Test with invalid request object:
|
|
341
|
+
```bash
|
|
342
|
+
curl "http://localhost:9292/auth?error_mode=invalid_request&request=invalid.jwt"
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Trust Chain Resolution
|
|
346
|
+
|
|
347
|
+
1. Configure trust anchors in both servers
|
|
348
|
+
2. Ensure RP has authority_hints pointing to trust anchor
|
|
349
|
+
3. OP will resolve RP's trust chain during authorization
|
|
350
|
+
4. OP will apply metadata policies from trust chain
|
|
351
|
+
|
|
352
|
+
## Security Testing
|
|
353
|
+
|
|
354
|
+
The mock servers support comprehensive security testing:
|
|
355
|
+
|
|
356
|
+
- **Algorithm Confusion**: Test rejection of non-RS256 algorithms
|
|
357
|
+
- **Key Confusion**: Test rejection of wrong keys
|
|
358
|
+
- **Signature Verification**: Test rejection of tampered signatures
|
|
359
|
+
- **Path Traversal**: Test rejection of malicious paths
|
|
360
|
+
- **Replay Attacks**: Test nonce validation
|
|
361
|
+
- **Request Object Validation**: Test signature and encryption validation
|
|
362
|
+
- **Trust Chain Validation**: Test authority hints and metadata policy enforcement
|
|
363
|
+
|
|
364
|
+
## Troubleshooting
|
|
365
|
+
|
|
366
|
+
### Server Won't Start
|
|
367
|
+
|
|
368
|
+
- Check if ports 9292 (OP) and 9293 (RP) are available
|
|
369
|
+
- Verify all dependencies are installed: `bundle install`
|
|
370
|
+
- Check configuration files exist and are valid YAML
|
|
371
|
+
|
|
372
|
+
### Entity Statements Not Validating
|
|
373
|
+
|
|
374
|
+
- Verify keys are correctly configured
|
|
375
|
+
- Check entity IDs match between servers
|
|
376
|
+
- Ensure trust anchors are configured if using trust chains
|
|
377
|
+
|
|
378
|
+
### Request Objects Rejected
|
|
379
|
+
|
|
380
|
+
- Verify RP's entity statement is accessible
|
|
381
|
+
- Check RP's JWKS contains the signing key
|
|
382
|
+
- Ensure request object is properly signed
|
|
383
|
+
- Verify encryption if required by OP
|
|
384
|
+
|
|
385
|
+
### Trust Chain Resolution Fails
|
|
386
|
+
|
|
387
|
+
- Verify trust anchors are configured
|
|
388
|
+
- Check authority_hints are correct
|
|
389
|
+
- Ensure all entity statements in chain are valid
|
|
390
|
+
- Verify subordinate statements are available
|
|
391
|
+
|
|
392
|
+
## Next Steps
|
|
393
|
+
|
|
394
|
+
1. **Add More Error Scenarios**: Extend error injection modes
|
|
395
|
+
2. **Performance Testing**: Add load testing scenarios
|
|
396
|
+
3. **Security Testing**: Add fuzzing and penetration tests
|
|
397
|
+
4. **Trust Mark Testing**: Add trust mark validation
|
|
398
|
+
5. **Metadata Policy Testing**: Add comprehensive policy merging tests
|
|
399
|
+
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# Mock OpenID Provider (OP) Server
|
|
2
|
+
|
|
3
|
+
A standalone Rack/Sinatra application for testing OpenID Federation flows.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- ✅ Entity Configuration endpoint (`/.well-known/openid-federation`)
|
|
8
|
+
- ✅ Fetch Endpoint (`/.well-known/openid-federation/fetch`) for Subordinate Statements
|
|
9
|
+
- ✅ Authorization Endpoint (`/auth`) with trust chain resolution
|
|
10
|
+
- ✅ Token Endpoint (`/token`) with ID Token signing
|
|
11
|
+
- ✅ JWKS endpoints (standard and signed)
|
|
12
|
+
- ✅ UserInfo endpoint (mock)
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
### 1. Install Dependencies
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
bundle install
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2. Configure
|
|
23
|
+
|
|
24
|
+
Copy the example configuration:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
cp examples/config/mock_op.yml.example examples/config/mock_op.yml
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Edit `examples/config/mock_op.yml` with your settings:
|
|
31
|
+
|
|
32
|
+
```yaml
|
|
33
|
+
entity_id: "https://op.example.com"
|
|
34
|
+
server_host: "localhost:9292"
|
|
35
|
+
signing_key: |
|
|
36
|
+
-----BEGIN RSA PRIVATE KEY-----
|
|
37
|
+
...
|
|
38
|
+
-----END RSA PRIVATE KEY-----
|
|
39
|
+
trust_anchors:
|
|
40
|
+
- entity_id: "https://ta.example.com"
|
|
41
|
+
jwks:
|
|
42
|
+
keys: [...]
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 3. Generate Keys (if needed)
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
# Generate OP signing key
|
|
49
|
+
openssl genrsa -out op-private-key.pem 2048
|
|
50
|
+
openssl rsa -in op-private-key.pem -pubout -out op-public-key.pem
|
|
51
|
+
|
|
52
|
+
# Extract JWKS from public key (or use the rake task)
|
|
53
|
+
rake openid_federation:prepare_client_keys
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### 4. Run Server
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
# Option 1: Direct Ruby execution
|
|
60
|
+
ruby examples/mock_op_server.rb
|
|
61
|
+
|
|
62
|
+
# Option 2: Using Rack
|
|
63
|
+
rackup examples/mock_op_server.ru
|
|
64
|
+
|
|
65
|
+
# Option 3: With specific port
|
|
66
|
+
rackup -p 9292 examples/mock_op_server.ru
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Configuration Options
|
|
70
|
+
|
|
71
|
+
### Environment Variables
|
|
72
|
+
|
|
73
|
+
Instead of YAML, you can use environment variables:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
export OP_ENTITY_ID="https://op.example.com"
|
|
77
|
+
export OP_SERVER_HOST="localhost:9292"
|
|
78
|
+
export OP_SIGNING_KEY="$(cat op-private-key.pem)"
|
|
79
|
+
export OP_TRUST_ANCHORS='[{"entity_id":"https://ta.example.com","jwks":{"keys":[...]}}]'
|
|
80
|
+
export OP_AUTHORITY_HINTS="https://federation.example.com"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### YAML Configuration
|
|
84
|
+
|
|
85
|
+
See `examples/config/mock_op.yml.example` for full configuration options.
|
|
86
|
+
|
|
87
|
+
## Testing Scenarios
|
|
88
|
+
|
|
89
|
+
### 1. Direct Entity Statement (No Trust Chain)
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# Fetch OP's Entity Configuration
|
|
93
|
+
curl http://localhost:9292/.well-known/openid-federation
|
|
94
|
+
|
|
95
|
+
# Use in RP configuration
|
|
96
|
+
config.omniauth :openid_federation,
|
|
97
|
+
issuer: "https://op.example.com",
|
|
98
|
+
entity_statement_url: "http://localhost:9292/.well-known/openid-federation",
|
|
99
|
+
client_options: { ... }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
### 2. Trust Chain Resolution
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Configure trust anchors in mock_op.yml
|
|
106
|
+
trust_anchors:
|
|
107
|
+
- entity_id: "https://ta.example.com"
|
|
108
|
+
jwks: {...}
|
|
109
|
+
|
|
110
|
+
# RP with trust chain
|
|
111
|
+
# The OP will resolve the RP's trust chain automatically
|
|
112
|
+
curl "http://localhost:9292/auth?client_id=https://rp.example.com&redirect_uri=https://rp.example.com/callback"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### 3. Subordinate Statements
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
# Configure subordinate statements in mock_op.yml
|
|
119
|
+
subordinate_statements:
|
|
120
|
+
"https://rp.example.com":
|
|
121
|
+
metadata: {...}
|
|
122
|
+
metadata_policy: {...}
|
|
123
|
+
|
|
124
|
+
# Fetch Subordinate Statement
|
|
125
|
+
curl "http://localhost:9292/.well-known/openid-federation/fetch?sub=https://rp.example.com"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Endpoints
|
|
129
|
+
|
|
130
|
+
| Endpoint | Method | Description |
|
|
131
|
+
|----------|--------|-------------|
|
|
132
|
+
| `/.well-known/openid-federation` | GET | Entity Configuration (JWT) |
|
|
133
|
+
| `/.well-known/openid-federation/fetch` | GET | Fetch Subordinate Statement (requires `sub` parameter) |
|
|
134
|
+
| `/.well-known/jwks.json` | GET | Standard JWKS (JSON) |
|
|
135
|
+
| `/.well-known/signed-jwks.json` | GET | Signed JWKS (JWT) |
|
|
136
|
+
| `/auth` | GET | Authorization Endpoint (requires `client_id`, `redirect_uri`) |
|
|
137
|
+
| `/token` | POST | Token Endpoint (requires `code`, `grant_type=authorization_code`) |
|
|
138
|
+
| `/userinfo` | GET | UserInfo Endpoint (mock data) |
|
|
139
|
+
| `/` | GET | Health check and endpoint list |
|
|
140
|
+
|
|
141
|
+
## Example Flow
|
|
142
|
+
|
|
143
|
+
### 1. RP Discovers OP
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
# RP fetches OP's Entity Configuration
|
|
147
|
+
curl http://localhost:9292/.well-known/openid-federation
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 2. RP Initiates Authentication
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# RP redirects user to authorization endpoint
|
|
154
|
+
# client_id is the RP's Entity ID (for automatic registration)
|
|
155
|
+
curl "http://localhost:9292/auth?client_id=https://rp.example.com&redirect_uri=https://rp.example.com/callback&state=xyz&nonce=abc"
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### 3. OP Resolves RP's Trust Chain
|
|
159
|
+
|
|
160
|
+
The OP automatically:
|
|
161
|
+
- Resolves RP's trust chain using `TrustChainResolver`
|
|
162
|
+
- Merges metadata policies using `MetadataPolicyMerger`
|
|
163
|
+
- Validates RP's effective metadata
|
|
164
|
+
- Redirects back to RP with authorization code
|
|
165
|
+
|
|
166
|
+
### 4. RP Exchanges Code for Tokens
|
|
167
|
+
|
|
168
|
+
```bash
|
|
169
|
+
curl -X POST http://localhost:9292/token \
|
|
170
|
+
-d "grant_type=authorization_code" \
|
|
171
|
+
-d "code=<authorization_code>" \
|
|
172
|
+
-d "redirect_uri=https://rp.example.com/callback" \
|
|
173
|
+
-d "client_id=https://rp.example.com"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
### 5. RP Validates ID Token
|
|
177
|
+
|
|
178
|
+
The RP validates the ID Token using the OP's JWKS from the effective metadata.
|
|
179
|
+
|
|
180
|
+
## Integration with Real RP
|
|
181
|
+
|
|
182
|
+
To test with a real RP application:
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
# In RP's config/initializers/devise.rb
|
|
186
|
+
config.omniauth :openid_federation,
|
|
187
|
+
issuer: "http://localhost:9292",
|
|
188
|
+
entity_statement_url: "http://localhost:9292/.well-known/openid-federation",
|
|
189
|
+
trust_anchors: [
|
|
190
|
+
{
|
|
191
|
+
entity_id: "https://ta.example.com",
|
|
192
|
+
jwks: trust_anchor_jwks
|
|
193
|
+
}
|
|
194
|
+
],
|
|
195
|
+
client_options: {
|
|
196
|
+
identifier: "https://rp.example.com", # RP's Entity ID
|
|
197
|
+
redirect_uri: "http://localhost:3000/users/auth/openid_federation/callback",
|
|
198
|
+
private_key: rp_private_key
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Limitations
|
|
203
|
+
|
|
204
|
+
This is a **mock server for testing only**:
|
|
205
|
+
|
|
206
|
+
- ⚠️ No real user authentication (always returns mock user)
|
|
207
|
+
- ⚠️ Authorization codes stored in memory (lost on restart)
|
|
208
|
+
- ⚠️ No database persistence
|
|
209
|
+
- ⚠️ No production security hardening
|
|
210
|
+
- ⚠️ ID Tokens contain mock user data
|
|
211
|
+
|
|
212
|
+
## Production Considerations
|
|
213
|
+
|
|
214
|
+
For production use, you would need:
|
|
215
|
+
|
|
216
|
+
- Real user authentication system
|
|
217
|
+
- Database for authorization codes and tokens
|
|
218
|
+
- Proper session management
|
|
219
|
+
- Security hardening (rate limiting, CSRF protection, etc.)
|
|
220
|
+
- Real user data in ID Tokens
|
|
221
|
+
- Proper error handling and logging
|
|
222
|
+
|
|
223
|
+
## Troubleshooting
|
|
224
|
+
|
|
225
|
+
**"Federation endpoint not configured"**
|
|
226
|
+
- Ensure `signing_key` is provided in config
|
|
227
|
+
- Check that `entity_id` is set
|
|
228
|
+
|
|
229
|
+
**"Trust chain resolution failed"**
|
|
230
|
+
- Verify `trust_anchors` are correctly configured
|
|
231
|
+
- Ensure trust anchor JWKS are valid
|
|
232
|
+
- Check that RP's Entity ID is resolvable
|
|
233
|
+
|
|
234
|
+
**"Subordinate Statement not found"**
|
|
235
|
+
- Configure `subordinate_statements` in `mock_op.yml`
|
|
236
|
+
- Ensure subject Entity ID matches exactly
|
|
237
|
+
|
|
238
|
+
## See Also
|
|
239
|
+
|
|
240
|
+
- [OpenID Federation 1.0 Specification](https://openid.net/specs/openid-federation-1_0.html)
|
|
241
|
+
- [Main README](../README.md)
|
|
242
|
+
- [Federation Endpoint Documentation](../README.md#publishing-federation-endpoint)
|
|
243
|
+
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Example OmniAuth callback controller for Devise
|
|
2
|
+
# Copy this to app/controllers/users/omniauth_callbacks_controller.rb
|
|
3
|
+
|
|
4
|
+
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|
5
|
+
# Skip Rails CSRF protection for OAuth callbacks
|
|
6
|
+
# OAuth callbacks from external providers cannot include Rails CSRF tokens
|
|
7
|
+
# CSRF protection is handled by OAuth state parameter validation in the strategy
|
|
8
|
+
skip_before_action :verify_authenticity_token, only: [:openid_federation, :failure]
|
|
9
|
+
skip_before_action :authenticate_user!, only: [:openid_federation, :failure]
|
|
10
|
+
|
|
11
|
+
def openid_federation
|
|
12
|
+
auth = request.env["omniauth.auth"]
|
|
13
|
+
|
|
14
|
+
@user = User.find_or_create_from_omniauth(auth)
|
|
15
|
+
|
|
16
|
+
if @user&.persisted?
|
|
17
|
+
sign_in_and_redirect @user, event: :authentication
|
|
18
|
+
else
|
|
19
|
+
redirect_to root_path, alert: "Authentication failed"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def failure
|
|
24
|
+
error_type = request.env["omniauth.error.type"] || :unknown
|
|
25
|
+
error_message = request.env["omniauth.error"]&.message || "Authentication failed"
|
|
26
|
+
|
|
27
|
+
Rails.logger.error({
|
|
28
|
+
message: "OmniAuth authentication failure",
|
|
29
|
+
error_type: error_type,
|
|
30
|
+
error_message: error_message,
|
|
31
|
+
strategy: request.env["omniauth.strategy"]&.name
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
redirect_to root_path, alert: "Authentication failed: #{error_type}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|