dnsid 0.26.0__py3-none-any.whl
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.
- dnsid/__init__.py +387 -0
- dnsid/_crypto.py +249 -0
- dnsid/_default_resolver.py +251 -0
- dnsid/_https_client.py +363 -0
- dnsid/_utils.py +283 -0
- dnsid/_verification_budget.py +170 -0
- dnsid/agent_status.py +25 -0
- dnsid/aws_kms_key_provider.py +611 -0
- dnsid/c2sp_tlog/__init__.py +297 -0
- dnsid/c2sp_tlog/canonical.py +152 -0
- dnsid/c2sp_tlog/checkpoint.py +92 -0
- dnsid/c2sp_tlog/checkpoint_store.py +127 -0
- dnsid/c2sp_tlog/errors.py +137 -0
- dnsid/c2sp_tlog/event_codec.py +472 -0
- dnsid/c2sp_tlog/lr.py +185 -0
- dnsid/c2sp_tlog/managed_verification_registry.py +176 -0
- dnsid/c2sp_tlog/merkle.py +111 -0
- dnsid/c2sp_tlog/policy.py +408 -0
- dnsid/c2sp_tlog/prepared.py +589 -0
- dnsid/c2sp_tlog/proof.py +95 -0
- dnsid/c2sp_tlog/reader.py +1486 -0
- dnsid/c2sp_tlog/resource_fetcher.py +299 -0
- dnsid/c2sp_tlog/signed_note.py +159 -0
- dnsid/c2sp_tlog/sqlite_checkpoint_store.py +220 -0
- dnsid/c2sp_tlog/stream_bundle.py +759 -0
- dnsid/c2sp_tlog/stream_source.py +336 -0
- dnsid/c2sp_tlog/stream_verifier.py +674 -0
- dnsid/c2sp_tlog/tiles.py +83 -0
- dnsid/c2sp_tlog/trust_profile.py +154 -0
- dnsid/c2sp_tlog/verification_registry.py +277 -0
- dnsid/config_loading.py +742 -0
- dnsid/conformance.py +51 -0
- dnsid/enums.py +149 -0
- dnsid/exceptions.py +190 -0
- dnsid/http_signatures.py +889 -0
- dnsid/interfaces.py +585 -0
- dnsid/jose.py +509 -0
- dnsid/local_key_provider.py +616 -0
- dnsid/manager.py +2422 -0
- dnsid/models.py +2315 -0
- dnsid/oidc.py +1029 -0
- dnsid/py.typed +0 -0
- dnsid/registry.py +69 -0
- dnsid/registry_client.py +1759 -0
- dnsid/retry.py +86 -0
- dnsid/safe_transport.py +385 -0
- dnsid/wba_signer.py +435 -0
- dnsid/wba_verifier.py +447 -0
- dnsid/web_bot_auth.py +625 -0
- dnsid-0.26.0.dist-info/METADATA +827 -0
- dnsid-0.26.0.dist-info/RECORD +53 -0
- dnsid-0.26.0.dist-info/WHEEL +4 -0
- dnsid-0.26.0.dist-info/licenses/LICENSE.txt +202 -0
dnsid/__init__.py
ADDED
|
@@ -0,0 +1,387 @@
|
|
|
1
|
+
"""DNSid Python SDK.
|
|
2
|
+
|
|
3
|
+
Implements the DNSid Protocol for identity management and verification.
|
|
4
|
+
|
|
5
|
+
Protocol layer (spec-defined):
|
|
6
|
+
IdentityManager — primary entry point; all protocol operations flow through it
|
|
7
|
+
IdentityManagerDependencies — optional dependency bundle for IdentityManager
|
|
8
|
+
DnsidConfig — core configuration: identity, verification, transport
|
|
9
|
+
IdentityConfig — local identity publication settings
|
|
10
|
+
VerificationConfig — verification policy and counterparty acceptance
|
|
11
|
+
TrustedEntity — one counterparty allowlist entry (gi + optional key pins)
|
|
12
|
+
TransportConfig — optional deployment/runtime DNS and TLS controls
|
|
13
|
+
RegistryConfig — optional registry control-plane configuration
|
|
14
|
+
DnsIdTxtRecord — _dnsid TXT record parsing, validation, serialization
|
|
15
|
+
JWKS / JWK — key set wrapper and individual key type
|
|
16
|
+
check_ek_ku_distinctness — RFC 7638 thumbprint distinctness check (ek vs ku key sets)
|
|
17
|
+
AgentStatus — status endpoint response
|
|
18
|
+
VerifiedDomain — result of a successful verify_domain call
|
|
19
|
+
DomainLog — verified event history for a domain
|
|
20
|
+
DomainSnapshot — materialized domain state at a point in time
|
|
21
|
+
LogRegistry — factory registry for log method implementations
|
|
22
|
+
RegistryClient — operator-side registry workflows
|
|
23
|
+
|
|
24
|
+
Application profiles (not part of the DNSid protocol):
|
|
25
|
+
JoseProfile / JoseConfig — JWT and JWS helpers (dnsid.jose)
|
|
26
|
+
HttpSignatureProfile / HttpMessageSignatureConfig
|
|
27
|
+
— RFC 9421 HTTP Message Signatures (dnsid.http_signatures)
|
|
28
|
+
OIDCProfile / OIDCConfig — OIDC token minting and verification (dnsid.oidc)
|
|
29
|
+
|
|
30
|
+
Key management:
|
|
31
|
+
LocalKeyProvider — file-backed EdDSA/ES256 KeyProvider with key rotation lifecycle
|
|
32
|
+
AwsKmsKeyProvider — AWS KMS-backed KeyProvider (optional dep: boto3)
|
|
33
|
+
AwsKmsConfig — configuration for AwsKmsKeyProvider
|
|
34
|
+
AwsKmsKeyState — mutable lifecycle state for AwsKmsKeyProvider
|
|
35
|
+
BotoKmsFacade — boto3 adapter for AwsKmsKeyProvider
|
|
36
|
+
AwsKmsFacade — abstract facade for testing without AWS
|
|
37
|
+
|
|
38
|
+
Configuration loading (loaders parse; constructors default):
|
|
39
|
+
load_environment / load_file / load_cli_directory — one loader per source → LoadedConfig
|
|
40
|
+
LoadedConfig, LogTrust, KeySource — partial configuration shape shared by every source
|
|
41
|
+
merge_loaded_config — field-wise merge of partial configurations (later wins)
|
|
42
|
+
construct_identity_manager — LoadedConfig + caller deps → IdentityManager
|
|
43
|
+
identity_manager_from_environment / identity_manager_from_dnsid / identity_manager_from_file
|
|
44
|
+
registry_client_from_environment — RegistryClient from DNSID_REGISTRY_URL / DNSID_API_KEY
|
|
45
|
+
|
|
46
|
+
Interfaces (implement to integrate your own backends):
|
|
47
|
+
IdentityResolver — minimal protocol satisfied by IdentityManager; accepted by profiles
|
|
48
|
+
KeyProvider — key management (local files, KMS, HSM)
|
|
49
|
+
Log — log write interface
|
|
50
|
+
LogReader — log read/verify interface
|
|
51
|
+
IdentityCache — verified domain cache
|
|
52
|
+
DNSResolver — DNS resolver
|
|
53
|
+
|
|
54
|
+
Errors:
|
|
55
|
+
ParseError — structurally malformed TXT record
|
|
56
|
+
ValidationError — semantic constraint violation
|
|
57
|
+
VerificationError — live verification failure (carries VerificationCode)
|
|
58
|
+
ArgumentError — invalid caller-supplied argument
|
|
59
|
+
|
|
60
|
+
Enumerations:
|
|
61
|
+
DNSSECState, DNSSECMode, VerificationCode, AgentState, RevocationReason,
|
|
62
|
+
RegistryRevocationReason, EventType
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
from ._verification_budget import remaining_seconds, verification_budget
|
|
66
|
+
from .agent_status import active_status_document
|
|
67
|
+
from .aws_kms_key_provider import (
|
|
68
|
+
AwsKmsConfig as AwsKmsConfig,
|
|
69
|
+
)
|
|
70
|
+
from .aws_kms_key_provider import (
|
|
71
|
+
AwsKmsFacade as AwsKmsFacade,
|
|
72
|
+
)
|
|
73
|
+
from .aws_kms_key_provider import (
|
|
74
|
+
AwsKmsKeyProvider as AwsKmsKeyProvider,
|
|
75
|
+
)
|
|
76
|
+
from .aws_kms_key_provider import (
|
|
77
|
+
AwsKmsKeyState as AwsKmsKeyState,
|
|
78
|
+
)
|
|
79
|
+
from .aws_kms_key_provider import (
|
|
80
|
+
BotoKmsFacade as BotoKmsFacade,
|
|
81
|
+
)
|
|
82
|
+
from .config_loading import (
|
|
83
|
+
KeySource,
|
|
84
|
+
LoadedConfig,
|
|
85
|
+
LogTrust,
|
|
86
|
+
construct_identity_manager,
|
|
87
|
+
identity_manager_from_dnsid,
|
|
88
|
+
identity_manager_from_environment,
|
|
89
|
+
identity_manager_from_file,
|
|
90
|
+
load_cli_directory,
|
|
91
|
+
load_environment,
|
|
92
|
+
load_file,
|
|
93
|
+
merge_loaded_config,
|
|
94
|
+
registry_client_from_environment,
|
|
95
|
+
)
|
|
96
|
+
from .conformance import SDK_CONFORMANCE, SDKConformance
|
|
97
|
+
from .enums import (
|
|
98
|
+
AgentState,
|
|
99
|
+
DNSSECMode,
|
|
100
|
+
DNSSECState,
|
|
101
|
+
EventType,
|
|
102
|
+
LifecycleErrorCategory,
|
|
103
|
+
LogSignerRole,
|
|
104
|
+
RegistryRevocationReason,
|
|
105
|
+
RevocationReason,
|
|
106
|
+
VerificationCode,
|
|
107
|
+
)
|
|
108
|
+
from .exceptions import (
|
|
109
|
+
ArgumentError,
|
|
110
|
+
DNSidError,
|
|
111
|
+
LifecycleVerificationError,
|
|
112
|
+
ManagedKeyRotationActivationError,
|
|
113
|
+
ManagedKeyRotationSubmissionError,
|
|
114
|
+
ParseError,
|
|
115
|
+
RegistryRequestError,
|
|
116
|
+
ValidationError,
|
|
117
|
+
VerificationError,
|
|
118
|
+
)
|
|
119
|
+
from .http_signatures import HttpMessageSignatureConfig, HttpSignatureProfile
|
|
120
|
+
from .interfaces import (
|
|
121
|
+
AbstractRegistryClient,
|
|
122
|
+
DNSResolver,
|
|
123
|
+
HTTPSFetcher,
|
|
124
|
+
IdentityCache,
|
|
125
|
+
IdentityResolver,
|
|
126
|
+
KeyProvider,
|
|
127
|
+
Log,
|
|
128
|
+
LogReader,
|
|
129
|
+
NoopLogReader,
|
|
130
|
+
)
|
|
131
|
+
from .jose import JoseConfig, JoseProfile
|
|
132
|
+
from .local_key_provider import LocalKeyProvider
|
|
133
|
+
from .manager import (
|
|
134
|
+
ApplicationSigningPauseHook,
|
|
135
|
+
IdentityManager,
|
|
136
|
+
IdentityManagerDependencies,
|
|
137
|
+
KeyRotationPersistenceHook,
|
|
138
|
+
sign_event_with_provider,
|
|
139
|
+
)
|
|
140
|
+
from .models import (
|
|
141
|
+
DEFAULT_REGISTRY_URL,
|
|
142
|
+
IDENTITY_RECORD_VERSION,
|
|
143
|
+
JWK,
|
|
144
|
+
JWKS,
|
|
145
|
+
AgentRegistration,
|
|
146
|
+
AgentRegistrationInput,
|
|
147
|
+
AgentStatus,
|
|
148
|
+
AnyLogEvent,
|
|
149
|
+
CanonicalRecordContentResponse,
|
|
150
|
+
DelegationEvent,
|
|
151
|
+
DnsidConfig,
|
|
152
|
+
DnsIdTxtRecord,
|
|
153
|
+
DomainLog,
|
|
154
|
+
DomainSnapshot,
|
|
155
|
+
HttpRequest,
|
|
156
|
+
HttpSigningOptions,
|
|
157
|
+
HttpVerificationOptions,
|
|
158
|
+
IdentityConfig,
|
|
159
|
+
IssuanceEvent,
|
|
160
|
+
JWTOptions,
|
|
161
|
+
KeyRotationEvent,
|
|
162
|
+
KeyRotationPreparationRequest,
|
|
163
|
+
KeyRotationResult,
|
|
164
|
+
LifecycleResult,
|
|
165
|
+
LiveAgentRegistrationInput,
|
|
166
|
+
LiveChallengeTranscript,
|
|
167
|
+
LiveProofReissueRequest,
|
|
168
|
+
LiveProofReissueResponse,
|
|
169
|
+
LiveProofRequest,
|
|
170
|
+
LiveProofResponse,
|
|
171
|
+
LiveProvisioningResponse,
|
|
172
|
+
LogEvent,
|
|
173
|
+
LoggedStateEvidence,
|
|
174
|
+
LogRef,
|
|
175
|
+
MigrationEvent,
|
|
176
|
+
PreparedRegistryEvent,
|
|
177
|
+
PublicationConfig,
|
|
178
|
+
PublishedRecord,
|
|
179
|
+
RegistryAgentStatus,
|
|
180
|
+
RegistryConfig,
|
|
181
|
+
RetirementEvent,
|
|
182
|
+
RevocationEvent,
|
|
183
|
+
SignatureParams,
|
|
184
|
+
SubmissionResult,
|
|
185
|
+
TLSCertificate,
|
|
186
|
+
TransportConfig,
|
|
187
|
+
TrustedEntity,
|
|
188
|
+
TXTRecord,
|
|
189
|
+
VerificationConfig,
|
|
190
|
+
VerifiedCutoffHistory,
|
|
191
|
+
VerifiedDomain,
|
|
192
|
+
check_ek_ku_distinctness,
|
|
193
|
+
publish_allowed_version,
|
|
194
|
+
)
|
|
195
|
+
from .oidc import (
|
|
196
|
+
NetworkError,
|
|
197
|
+
OAuthError,
|
|
198
|
+
OIDCAssertionOptions,
|
|
199
|
+
OIDCConfig,
|
|
200
|
+
OIDCDiscoveryDocument,
|
|
201
|
+
OIDCProfile,
|
|
202
|
+
OIDCTokenExchangeOptions,
|
|
203
|
+
OIDCTokenResponse,
|
|
204
|
+
VerifiedOIDCSubject,
|
|
205
|
+
VerifyOIDCTokenOptions,
|
|
206
|
+
)
|
|
207
|
+
from .registry import LogRegistry
|
|
208
|
+
from .registry_client import RegistryClient
|
|
209
|
+
from .retry import async_retry_transient, retry_transient
|
|
210
|
+
from .wba_signer import (
|
|
211
|
+
WBADirectoryResponse,
|
|
212
|
+
WBAHttpSigner,
|
|
213
|
+
WBAVariant,
|
|
214
|
+
WebBotAuthConfig,
|
|
215
|
+
WebBotAuthSigningOptions,
|
|
216
|
+
serve_http_message_signatures_directory,
|
|
217
|
+
)
|
|
218
|
+
from .wba_verifier import WBAHttpVerifier, WebBotAuthVerificationOptions
|
|
219
|
+
from .web_bot_auth import (
|
|
220
|
+
BotAuthConfig,
|
|
221
|
+
BotIdentity,
|
|
222
|
+
VerifiedBotRequest,
|
|
223
|
+
WebBotAuthProfile,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
PROTOCOL_VERSION: str = IDENTITY_RECORD_VERSION
|
|
227
|
+
"""Wire version string for the DNSid protocol implemented by this SDK.
|
|
228
|
+
|
|
229
|
+
Alias of IDENTITY_RECORD_VERSION — the current numbered publish profile.
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
__all__ = [
|
|
233
|
+
# Core
|
|
234
|
+
"IdentityManager",
|
|
235
|
+
"IdentityManagerDependencies",
|
|
236
|
+
# Config
|
|
237
|
+
"DnsidConfig",
|
|
238
|
+
"IdentityConfig",
|
|
239
|
+
"VerificationConfig",
|
|
240
|
+
"TrustedEntity",
|
|
241
|
+
"TransportConfig",
|
|
242
|
+
"RegistryConfig",
|
|
243
|
+
# DNS record
|
|
244
|
+
"DnsIdTxtRecord",
|
|
245
|
+
"TXTRecord",
|
|
246
|
+
# Keys
|
|
247
|
+
"JWK",
|
|
248
|
+
"JWKS",
|
|
249
|
+
"check_ek_ku_distinctness",
|
|
250
|
+
# Status / TLS
|
|
251
|
+
"AgentStatus",
|
|
252
|
+
"TLSCertificate",
|
|
253
|
+
# Verification result
|
|
254
|
+
"VerifiedDomain",
|
|
255
|
+
# Log
|
|
256
|
+
"DomainLog",
|
|
257
|
+
"DomainSnapshot",
|
|
258
|
+
"LoggedStateEvidence",
|
|
259
|
+
"VerifiedCutoffHistory",
|
|
260
|
+
"LogRef",
|
|
261
|
+
"LogEvent",
|
|
262
|
+
"AnyLogEvent",
|
|
263
|
+
"IssuanceEvent",
|
|
264
|
+
"KeyRotationEvent",
|
|
265
|
+
"RevocationEvent",
|
|
266
|
+
"RetirementEvent",
|
|
267
|
+
"MigrationEvent",
|
|
268
|
+
"DelegationEvent",
|
|
269
|
+
"LifecycleErrorCategory",
|
|
270
|
+
"LifecycleVerificationError",
|
|
271
|
+
"ManagedKeyRotationActivationError",
|
|
272
|
+
"ManagedKeyRotationSubmissionError",
|
|
273
|
+
"LogSignerRole",
|
|
274
|
+
"sign_event_with_provider",
|
|
275
|
+
# Registry
|
|
276
|
+
"LogRegistry",
|
|
277
|
+
"RegistryClient",
|
|
278
|
+
"AgentRegistrationInput",
|
|
279
|
+
"AgentRegistration",
|
|
280
|
+
"LifecycleResult",
|
|
281
|
+
"LiveAgentRegistrationInput",
|
|
282
|
+
"LiveChallengeTranscript",
|
|
283
|
+
"LiveProvisioningResponse",
|
|
284
|
+
"LiveProofRequest",
|
|
285
|
+
"LiveProofResponse",
|
|
286
|
+
"LiveProofReissueRequest",
|
|
287
|
+
"LiveProofReissueResponse",
|
|
288
|
+
"RegistryAgentStatus",
|
|
289
|
+
"CanonicalRecordContentResponse",
|
|
290
|
+
"PublishedRecord",
|
|
291
|
+
"PreparedRegistryEvent",
|
|
292
|
+
"KeyRotationPreparationRequest",
|
|
293
|
+
"KeyRotationResult",
|
|
294
|
+
"KeyRotationPersistenceHook",
|
|
295
|
+
"ApplicationSigningPauseHook",
|
|
296
|
+
"SubmissionResult",
|
|
297
|
+
# Application-layer option types
|
|
298
|
+
"JWTOptions",
|
|
299
|
+
"HttpRequest",
|
|
300
|
+
"HttpSigningOptions",
|
|
301
|
+
"HttpVerificationOptions",
|
|
302
|
+
"SignatureParams",
|
|
303
|
+
# JOSE / HTTP signatures profiles
|
|
304
|
+
"JoseProfile",
|
|
305
|
+
"JoseConfig",
|
|
306
|
+
"HttpSignatureProfile",
|
|
307
|
+
"HttpMessageSignatureConfig",
|
|
308
|
+
# OIDC profile
|
|
309
|
+
"OIDCProfile",
|
|
310
|
+
"OIDCConfig",
|
|
311
|
+
"OIDCAssertionOptions",
|
|
312
|
+
"OIDCTokenExchangeOptions",
|
|
313
|
+
"OIDCTokenResponse",
|
|
314
|
+
"OIDCDiscoveryDocument",
|
|
315
|
+
"VerifyOIDCTokenOptions",
|
|
316
|
+
"VerifiedOIDCSubject",
|
|
317
|
+
# Key management
|
|
318
|
+
"LocalKeyProvider",
|
|
319
|
+
# Configuration loading
|
|
320
|
+
"LoadedConfig",
|
|
321
|
+
"LogTrust",
|
|
322
|
+
"KeySource",
|
|
323
|
+
"load_environment",
|
|
324
|
+
"load_file",
|
|
325
|
+
"load_cli_directory",
|
|
326
|
+
"merge_loaded_config",
|
|
327
|
+
"construct_identity_manager",
|
|
328
|
+
"identity_manager_from_environment",
|
|
329
|
+
"identity_manager_from_dnsid",
|
|
330
|
+
"identity_manager_from_file",
|
|
331
|
+
"registry_client_from_environment",
|
|
332
|
+
# Agent status helpers
|
|
333
|
+
"active_status_document",
|
|
334
|
+
# Interfaces
|
|
335
|
+
"AbstractRegistryClient",
|
|
336
|
+
"IdentityResolver",
|
|
337
|
+
"KeyProvider",
|
|
338
|
+
"Log",
|
|
339
|
+
"LogReader",
|
|
340
|
+
"NoopLogReader",
|
|
341
|
+
"IdentityCache",
|
|
342
|
+
"DNSResolver",
|
|
343
|
+
"HTTPSFetcher",
|
|
344
|
+
# Retry helpers
|
|
345
|
+
"retry_transient",
|
|
346
|
+
"async_retry_transient",
|
|
347
|
+
# Conformance
|
|
348
|
+
"SDKConformance",
|
|
349
|
+
"SDK_CONFORMANCE",
|
|
350
|
+
"verification_budget",
|
|
351
|
+
"remaining_seconds",
|
|
352
|
+
"PROTOCOL_VERSION",
|
|
353
|
+
"PublicationConfig",
|
|
354
|
+
"DEFAULT_REGISTRY_URL",
|
|
355
|
+
"IDENTITY_RECORD_VERSION",
|
|
356
|
+
"publish_allowed_version",
|
|
357
|
+
# Web Bot Auth profile
|
|
358
|
+
"WBAHttpSigner",
|
|
359
|
+
"WBAHttpVerifier",
|
|
360
|
+
"WebBotAuthVerificationOptions",
|
|
361
|
+
"WBAVariant",
|
|
362
|
+
"WBADirectoryResponse",
|
|
363
|
+
"WebBotAuthConfig",
|
|
364
|
+
"WebBotAuthSigningOptions",
|
|
365
|
+
"serve_http_message_signatures_directory",
|
|
366
|
+
"WebBotAuthProfile",
|
|
367
|
+
"BotAuthConfig",
|
|
368
|
+
"BotIdentity",
|
|
369
|
+
"VerifiedBotRequest",
|
|
370
|
+
# Errors
|
|
371
|
+
"DNSidError",
|
|
372
|
+
"ParseError",
|
|
373
|
+
"ValidationError",
|
|
374
|
+
"RegistryRequestError",
|
|
375
|
+
"VerificationError",
|
|
376
|
+
"ArgumentError",
|
|
377
|
+
"NetworkError",
|
|
378
|
+
"OAuthError",
|
|
379
|
+
# Enumerations
|
|
380
|
+
"DNSSECState",
|
|
381
|
+
"DNSSECMode",
|
|
382
|
+
"VerificationCode",
|
|
383
|
+
"AgentState",
|
|
384
|
+
"RevocationReason",
|
|
385
|
+
"RegistryRevocationReason",
|
|
386
|
+
"EventType",
|
|
387
|
+
]
|
dnsid/_crypto.py
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""Cryptographic operations: JWK thumbprint, signature verification, and key parsing."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import hashlib
|
|
6
|
+
import json
|
|
7
|
+
from typing import TYPE_CHECKING, Any
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from .models import JWK
|
|
11
|
+
|
|
12
|
+
from cryptography.exceptions import InvalidSignature
|
|
13
|
+
from cryptography.hazmat.primitives import hashes
|
|
14
|
+
from cryptography.hazmat.primitives.asymmetric import ec, ed25519, padding, rsa
|
|
15
|
+
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
|
|
16
|
+
|
|
17
|
+
from ._utils import b64url_decode, b64url_encode
|
|
18
|
+
|
|
19
|
+
# ---------------------------------------------------------------------------
|
|
20
|
+
# RFC 7638 thumbprint
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
|
|
23
|
+
# Required public members per key type (alphabetical — the spec mandates this order).
|
|
24
|
+
_THUMBPRINT_MEMBERS: dict[str, list[str]] = {
|
|
25
|
+
"EC": ["crv", "kty", "x", "y"],
|
|
26
|
+
"RSA": ["e", "kty", "n"],
|
|
27
|
+
"OKP": ["crv", "kty", "x"],
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def compute_thumbprint(raw: dict[str, Any]) -> str:
|
|
32
|
+
"""Return the RFC 7638 JWK thumbprint (SHA-256, unpadded base64url).
|
|
33
|
+
|
|
34
|
+
Raises ValueError if the key type is unsupported or required members are missing.
|
|
35
|
+
"""
|
|
36
|
+
kty = raw.get("kty", "")
|
|
37
|
+
members = _THUMBPRINT_MEMBERS.get(kty)
|
|
38
|
+
if members is None:
|
|
39
|
+
raise ValueError(f"unsupported kty for thumbprint: {kty!r}")
|
|
40
|
+
|
|
41
|
+
missing = [k for k in members if k not in raw]
|
|
42
|
+
if missing:
|
|
43
|
+
raise ValueError(f"JWK missing required thumbprint members: {missing}")
|
|
44
|
+
|
|
45
|
+
canonical = {k: raw[k] for k in members}
|
|
46
|
+
canonical_json = json.dumps(canonical, separators=(",", ":"), sort_keys=True).encode("ascii")
|
|
47
|
+
digest = hashlib.sha256(canonical_json).digest()
|
|
48
|
+
return b64url_encode(digest)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
# ---------------------------------------------------------------------------
|
|
52
|
+
# Signature verification
|
|
53
|
+
# ---------------------------------------------------------------------------
|
|
54
|
+
|
|
55
|
+
_EC_CURVES: dict[str, ec.EllipticCurve] = {
|
|
56
|
+
"P-256": ec.SECP256R1(),
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
# Number of bytes per integer component (r or s) — ES256 / P-256 only.
|
|
60
|
+
_EC_COORD_BYTES: dict[str, int] = {
|
|
61
|
+
"ES256": 32,
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_EC_HASH: dict[str, type[hashes.HashAlgorithm]] = {
|
|
65
|
+
"ES256": hashes.SHA256,
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
_RSA_HASH: dict[str, type[hashes.HashAlgorithm]] = {
|
|
69
|
+
"RS256": hashes.SHA256,
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
# Supported kty/crv → JOSE alg mappings (EdDSA and ES256 only per DNSid protocol).
|
|
73
|
+
_KTY_CRV_TO_ALG: dict[tuple[str, str], str] = {
|
|
74
|
+
("OKP", "Ed25519"): "EdDSA",
|
|
75
|
+
("EC", "P-256"): "ES256",
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def signature_alg_from_raw(raw: dict[str, Any]) -> str:
|
|
80
|
+
"""Derive JOSE signature algorithm from kty/crv.
|
|
81
|
+
|
|
82
|
+
Raises ValueError if the kty/crv combination is not supported by the DNSid protocol.
|
|
83
|
+
"""
|
|
84
|
+
kty = raw.get("kty", "")
|
|
85
|
+
crv = raw.get("crv", "")
|
|
86
|
+
alg = _KTY_CRV_TO_ALG.get((kty, crv))
|
|
87
|
+
if alg is None:
|
|
88
|
+
raise ValueError(
|
|
89
|
+
f"unsupported kty/crv for DNSid signature algorithm: kty={kty!r}, crv={crv!r}"
|
|
90
|
+
)
|
|
91
|
+
return alg
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
_ALLOWED_ALGS: frozenset[str] = frozenset({"EdDSA", "ES256", "RS256"})
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def verify_signature(raw: dict[str, Any], payload: bytes, signature: bytes) -> bool:
|
|
98
|
+
"""Return True if *signature* over *payload* is valid for the public key in *raw*.
|
|
99
|
+
|
|
100
|
+
Returns False for an invalid signature; never raises for a bad sig value.
|
|
101
|
+
When the JWK has no alg field, the algorithm is derived from kty/crv.
|
|
102
|
+
Only EdDSA and ES256 are accepted; any other algorithm returns False.
|
|
103
|
+
"""
|
|
104
|
+
kty = raw.get("kty", "")
|
|
105
|
+
alg = raw.get("alg", "")
|
|
106
|
+
|
|
107
|
+
# Derive alg from kty/crv when absent (alg is optional per DNSid design).
|
|
108
|
+
if not alg:
|
|
109
|
+
try:
|
|
110
|
+
alg = signature_alg_from_raw(raw)
|
|
111
|
+
except ValueError:
|
|
112
|
+
return False
|
|
113
|
+
|
|
114
|
+
if alg not in _ALLOWED_ALGS:
|
|
115
|
+
return False
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
if kty == "EC":
|
|
119
|
+
return _verify_ec(raw, alg, payload, signature)
|
|
120
|
+
if kty == "OKP":
|
|
121
|
+
return _verify_okp(raw, payload, signature)
|
|
122
|
+
if kty == "RSA":
|
|
123
|
+
return _verify_rsa(raw, alg, payload, signature)
|
|
124
|
+
return False
|
|
125
|
+
except InvalidSignature:
|
|
126
|
+
return False
|
|
127
|
+
except Exception:
|
|
128
|
+
return False
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _verify_ec(raw: dict[str, Any], alg: str, payload: bytes, sig: bytes) -> bool:
|
|
132
|
+
crv = raw.get("crv", "")
|
|
133
|
+
curve = _EC_CURVES.get(crv)
|
|
134
|
+
if not curve:
|
|
135
|
+
raise ValueError(f"unsupported EC curve: {crv!r}")
|
|
136
|
+
|
|
137
|
+
hash_cls = _EC_HASH.get(alg)
|
|
138
|
+
if not hash_cls:
|
|
139
|
+
raise ValueError(f"unsupported EC alg: {alg!r}")
|
|
140
|
+
|
|
141
|
+
x = int.from_bytes(b64url_decode(raw["x"]), "big")
|
|
142
|
+
y = int.from_bytes(b64url_decode(raw["y"]), "big")
|
|
143
|
+
pub_key = ec.EllipticCurvePublicNumbers(x=x, y=y, curve=curve).public_key()
|
|
144
|
+
|
|
145
|
+
# JWT/JWS/RFC 9421 use IEEE P1363 (r || s); cryptography expects DER.
|
|
146
|
+
coord = _EC_COORD_BYTES.get(alg)
|
|
147
|
+
if coord and len(sig) == 2 * coord:
|
|
148
|
+
r = int.from_bytes(sig[:coord], "big")
|
|
149
|
+
s = int.from_bytes(sig[coord:], "big")
|
|
150
|
+
der_sig = encode_dss_signature(r, s)
|
|
151
|
+
else:
|
|
152
|
+
der_sig = sig # fall through and let the library reject it
|
|
153
|
+
|
|
154
|
+
pub_key.verify(der_sig, payload, ec.ECDSA(hash_cls()))
|
|
155
|
+
return True
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _verify_okp(raw: dict[str, Any], payload: bytes, sig: bytes) -> bool:
|
|
159
|
+
crv = raw.get("crv", "")
|
|
160
|
+
if crv != "Ed25519":
|
|
161
|
+
raise ValueError(f"unsupported OKP curve: {crv!r}")
|
|
162
|
+
pub_key = ed25519.Ed25519PublicKey.from_public_bytes(b64url_decode(raw["x"]))
|
|
163
|
+
pub_key.verify(sig, payload)
|
|
164
|
+
return True
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
def _verify_rsa(raw: dict[str, Any], alg: str, payload: bytes, sig: bytes) -> bool:
|
|
168
|
+
hash_cls = _RSA_HASH.get(alg)
|
|
169
|
+
if not hash_cls:
|
|
170
|
+
raise ValueError(f"unsupported RSA alg: {alg!r}")
|
|
171
|
+
n = int.from_bytes(b64url_decode(raw["n"]), "big")
|
|
172
|
+
e = int.from_bytes(b64url_decode(raw["e"]), "big")
|
|
173
|
+
pub_key = rsa.RSAPublicNumbers(e=e, n=n).public_key()
|
|
174
|
+
pub_key.verify(sig, payload, padding.PKCS1v15(), hash_cls())
|
|
175
|
+
return True
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# ---------------------------------------------------------------------------
|
|
179
|
+
# JWK construction from wire format
|
|
180
|
+
# ---------------------------------------------------------------------------
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def jwk_from_dict(d: dict[str, Any]) -> JWK:
|
|
184
|
+
"""Build a JWK dataclass from a raw dict (e.g. from a parsed JWKS JSON response)."""
|
|
185
|
+
from .models import JWK # lazy import — models imports _utils, not _crypto
|
|
186
|
+
|
|
187
|
+
return JWK(
|
|
188
|
+
kty=d.get("kty", ""),
|
|
189
|
+
alg=d.get("alg", ""),
|
|
190
|
+
kid=d.get("kid", ""),
|
|
191
|
+
use=d.get("use", ""),
|
|
192
|
+
_raw=dict(d),
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
# ---------------------------------------------------------------------------
|
|
197
|
+
# Helpers for building a JWK dict from a cryptography private/public key
|
|
198
|
+
# (used in tests to produce valid _raw dicts)
|
|
199
|
+
# ---------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def ec_public_jwk_dict(
|
|
203
|
+
private_key: ec.EllipticCurvePrivateKey, kid: str, alg: str
|
|
204
|
+
) -> dict[str, Any]:
|
|
205
|
+
"""Produce a public JWK dict from a cryptography EC private key (ES256/P-256 only)."""
|
|
206
|
+
pub = private_key.public_key()
|
|
207
|
+
numbers = pub.public_numbers()
|
|
208
|
+
crv = {"secp256r1": "P-256"}[pub.curve.name]
|
|
209
|
+
coord_len = _EC_COORD_BYTES[alg]
|
|
210
|
+
return {
|
|
211
|
+
"kty": "EC",
|
|
212
|
+
"kid": kid,
|
|
213
|
+
"alg": alg,
|
|
214
|
+
"use": "sig",
|
|
215
|
+
"crv": crv,
|
|
216
|
+
"x": b64url_encode(numbers.x.to_bytes(coord_len, "big")),
|
|
217
|
+
"y": b64url_encode(numbers.y.to_bytes(coord_len, "big")),
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def ed25519_public_jwk_dict(private_key: ed25519.Ed25519PrivateKey, kid: str) -> dict[str, Any]:
|
|
222
|
+
"""Produce a public JWK dict from a cryptography Ed25519 private key."""
|
|
223
|
+
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
|
224
|
+
|
|
225
|
+
pub_bytes = private_key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
|
|
226
|
+
return {
|
|
227
|
+
"kty": "OKP",
|
|
228
|
+
"kid": kid,
|
|
229
|
+
"alg": "EdDSA",
|
|
230
|
+
"use": "sig",
|
|
231
|
+
"crv": "Ed25519",
|
|
232
|
+
"x": b64url_encode(pub_bytes),
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def ec_sign(private_key: ec.EllipticCurvePrivateKey, alg: str, payload: bytes) -> bytes:
|
|
237
|
+
"""Sign *payload* with an EC private key; return IEEE P1363 (r||s) bytes."""
|
|
238
|
+
from cryptography.hazmat.primitives.asymmetric.utils import decode_dss_signature
|
|
239
|
+
|
|
240
|
+
hash_cls = _EC_HASH[alg]
|
|
241
|
+
der_sig = private_key.sign(payload, ec.ECDSA(hash_cls()))
|
|
242
|
+
r, s = decode_dss_signature(der_sig)
|
|
243
|
+
coord = _EC_COORD_BYTES[alg]
|
|
244
|
+
return r.to_bytes(coord, "big") + s.to_bytes(coord, "big")
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def ed25519_sign(private_key: ed25519.Ed25519PrivateKey, payload: bytes) -> bytes:
|
|
248
|
+
"""Sign *payload* with an Ed25519 private key."""
|
|
249
|
+
return private_key.sign(payload)
|