auths-python 0.1.0__cp38-abi3-win_amd64.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.
- auths/__init__.py +147 -0
- auths/__init__.pyi +486 -0
- auths/_client.py +713 -0
- auths/_errors.py +80 -0
- auths/_native.pyd +0 -0
- auths/agent.py +55 -0
- auths/artifact.py +60 -0
- auths/attestation_query.py +141 -0
- auths/audit.py +226 -0
- auths/commit.py +28 -0
- auths/devices.py +162 -0
- auths/doctor.py +109 -0
- auths/git.py +473 -0
- auths/identity.py +221 -0
- auths/jwt.py +253 -0
- auths/org.py +310 -0
- auths/pairing.py +216 -0
- auths/policy.py +382 -0
- auths/py.typed +0 -0
- auths/rotation.py +30 -0
- auths/sign.py +5 -0
- auths/trust.py +169 -0
- auths/verify.py +111 -0
- auths/witness.py +91 -0
- auths_python-0.1.0.dist-info/METADATA +152 -0
- auths_python-0.1.0.dist-info/RECORD +28 -0
- auths_python-0.1.0.dist-info/WHEEL +4 -0
- auths_python-0.1.0.dist-info/sboms/auths-python.cyclonedx.json +14418 -0
auths/_client.py
ADDED
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
"""Auths client — primary entry point for all SDK operations."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
|
|
9
|
+
from auths._native import (
|
|
10
|
+
get_token as _get_token,
|
|
11
|
+
sign_action as _sign_action,
|
|
12
|
+
sign_bytes as _sign_bytes,
|
|
13
|
+
verify_action_envelope as _verify_action_envelope,
|
|
14
|
+
verify_at_time as _verify_at_time,
|
|
15
|
+
verify_at_time_with_capability as _verify_at_time_with_capability,
|
|
16
|
+
verify_attestation as _verify_attestation,
|
|
17
|
+
verify_attestation_with_capability as _verify_attestation_with_capability,
|
|
18
|
+
verify_chain as _verify_chain,
|
|
19
|
+
verify_chain_with_capability as _verify_chain_with_capability,
|
|
20
|
+
verify_chain_with_witnesses as _verify_chain_with_witnesses,
|
|
21
|
+
verify_device_authorization as _verify_device_authorization,
|
|
22
|
+
)
|
|
23
|
+
from auths._errors import (
|
|
24
|
+
CryptoError,
|
|
25
|
+
IdentityError,
|
|
26
|
+
KeychainError,
|
|
27
|
+
NetworkError,
|
|
28
|
+
OrgError,
|
|
29
|
+
PairingError,
|
|
30
|
+
StorageError,
|
|
31
|
+
VerificationError,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from auths._native import VerificationReport, VerificationResult
|
|
36
|
+
from auths.artifact import ArtifactPublishResult, ArtifactSigningResult
|
|
37
|
+
from auths.commit import CommitSigningResult
|
|
38
|
+
from auths.verify import WitnessConfig
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
_ERROR_CODE_MAP = {
|
|
42
|
+
"AUTHS_ISSUER_SIG_FAILED": ("invalid_signature", VerificationError),
|
|
43
|
+
"AUTHS_DEVICE_SIG_FAILED": ("invalid_signature", VerificationError),
|
|
44
|
+
"AUTHS_ATTESTATION_EXPIRED": ("expired_attestation", VerificationError),
|
|
45
|
+
"AUTHS_ATTESTATION_REVOKED": ("revoked_device", VerificationError),
|
|
46
|
+
"AUTHS_TIMESTAMP_IN_FUTURE": ("future_timestamp", VerificationError),
|
|
47
|
+
"AUTHS_MISSING_CAPABILITY": ("missing_capability", VerificationError),
|
|
48
|
+
"AUTHS_CRYPTO_ERROR": ("invalid_key", CryptoError),
|
|
49
|
+
"AUTHS_DID_RESOLUTION_ERROR": ("invalid_key", CryptoError),
|
|
50
|
+
"AUTHS_INVALID_INPUT": ("invalid_signature", VerificationError),
|
|
51
|
+
"AUTHS_SERIALIZATION_ERROR": ("invalid_signature", VerificationError),
|
|
52
|
+
"AUTHS_BUNDLE_EXPIRED": ("expired_attestation", VerificationError),
|
|
53
|
+
"AUTHS_KEY_NOT_FOUND": ("key_not_found", CryptoError),
|
|
54
|
+
"AUTHS_INCORRECT_PASSPHRASE": ("signing_failed", CryptoError),
|
|
55
|
+
"AUTHS_SIGNING_FAILED": ("signing_failed", CryptoError),
|
|
56
|
+
"AUTHS_SIGNING_ERROR": ("signing_failed", CryptoError),
|
|
57
|
+
"AUTHS_INPUT_TOO_LARGE": ("invalid_signature", VerificationError),
|
|
58
|
+
"AUTHS_INTERNAL_ERROR": ("unknown", VerificationError),
|
|
59
|
+
"AUTHS_ORG_VERIFICATION_FAILED": ("invalid_signature", VerificationError),
|
|
60
|
+
"AUTHS_ORG_ATTESTATION_EXPIRED": ("expired_attestation", VerificationError),
|
|
61
|
+
"AUTHS_ORG_DID_RESOLUTION_FAILED": ("invalid_key", CryptoError),
|
|
62
|
+
"AUTHS_REGISTRY_ERROR": ("repo_not_found", StorageError),
|
|
63
|
+
"AUTHS_KEYCHAIN_ERROR": ("keychain_locked", KeychainError),
|
|
64
|
+
"AUTHS_IDENTITY_ERROR": ("identity_not_found", IdentityError),
|
|
65
|
+
"AUTHS_DEVICE_ERROR": ("unknown", IdentityError),
|
|
66
|
+
"AUTHS_ROTATION_ERROR": ("unknown", IdentityError),
|
|
67
|
+
"AUTHS_NETWORK_ERROR": ("server_error", NetworkError),
|
|
68
|
+
"AUTHS_VERIFICATION_FAILED": ("invalid_signature", VerificationError),
|
|
69
|
+
"AUTHS_ORG_ERROR": ("org_error", OrgError),
|
|
70
|
+
"AUTHS_PAIRING_ERROR": ("pairing_error", PairingError),
|
|
71
|
+
"AUTHS_PAIRING_TIMEOUT": ("timeout", PairingError),
|
|
72
|
+
"AUTHS_TRUST_ERROR": ("trust_error", StorageError),
|
|
73
|
+
"AUTHS_WITNESS_ERROR": ("witness_error", StorageError),
|
|
74
|
+
"AUTHS_AUDIT_ERROR": ("audit_error", VerificationError),
|
|
75
|
+
"AUTHS_DIAGNOSTIC_ERROR": ("diagnostic_error", VerificationError),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _map_error(exc: Exception, *, default_cls: type = VerificationError) -> Exception:
|
|
80
|
+
msg = str(exc)
|
|
81
|
+
code = None
|
|
82
|
+
if msg.startswith("[AUTHS_") and "] " in msg:
|
|
83
|
+
code = msg[1:msg.index("]")]
|
|
84
|
+
msg = msg[msg.index("] ") + 2:]
|
|
85
|
+
if code and code in _ERROR_CODE_MAP:
|
|
86
|
+
py_code, cls = _ERROR_CODE_MAP[code]
|
|
87
|
+
return cls(msg, code=py_code)
|
|
88
|
+
low = msg.lower()
|
|
89
|
+
if "public key" in low or "private key" in low or "invalid key" in low or "hex" in low:
|
|
90
|
+
return CryptoError(msg, code="invalid_key")
|
|
91
|
+
return default_cls(msg, code="unknown")
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _map_network_error(exc: Exception) -> Exception:
|
|
95
|
+
msg = str(exc)
|
|
96
|
+
if "unreachable" in msg.lower() or "connection" in msg.lower():
|
|
97
|
+
return NetworkError(msg, code="connection_failed", should_retry=True)
|
|
98
|
+
if "timeout" in msg.lower():
|
|
99
|
+
return NetworkError(msg, code="timeout", should_retry=True)
|
|
100
|
+
return NetworkError(msg, code="server_error")
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class Auths:
|
|
104
|
+
"""Auths SDK client — decentralized identity for developers.
|
|
105
|
+
|
|
106
|
+
Examples:
|
|
107
|
+
```python
|
|
108
|
+
auths = Auths()
|
|
109
|
+
result = auths.verify(attestation_json=data, issuer_key=key)
|
|
110
|
+
sig = auths.sign(b"hello", private_key=key_hex)
|
|
111
|
+
```
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
def __init__(self, repo_path: str = "~/.auths", passphrase: str | None = None):
|
|
115
|
+
self.repo_path = repo_path
|
|
116
|
+
self._passphrase = passphrase
|
|
117
|
+
|
|
118
|
+
from auths.attestation_query import AttestationService
|
|
119
|
+
from auths.audit import AuditService
|
|
120
|
+
from auths.devices import DeviceService
|
|
121
|
+
from auths.identity import IdentityService
|
|
122
|
+
from auths.org import OrgService
|
|
123
|
+
from auths.doctor import DoctorService
|
|
124
|
+
from auths.pairing import PairingService
|
|
125
|
+
from auths.trust import TrustService
|
|
126
|
+
from auths.witness import WitnessService
|
|
127
|
+
|
|
128
|
+
self.identities = IdentityService(self)
|
|
129
|
+
self.devices = DeviceService(self)
|
|
130
|
+
self.attestations = AttestationService(self)
|
|
131
|
+
self.orgs = OrgService(self)
|
|
132
|
+
self.audit = AuditService(self)
|
|
133
|
+
self.trust = TrustService(self)
|
|
134
|
+
self.witnesses = WitnessService(self)
|
|
135
|
+
self.doctor = DoctorService(self)
|
|
136
|
+
self.pairing = PairingService(self)
|
|
137
|
+
|
|
138
|
+
def verify(
|
|
139
|
+
self,
|
|
140
|
+
attestation_json: str,
|
|
141
|
+
issuer_key: str,
|
|
142
|
+
required_capability: str | None = None,
|
|
143
|
+
at: str | None = None,
|
|
144
|
+
) -> VerificationResult:
|
|
145
|
+
"""Verify a single attestation, optionally at a specific historical timestamp.
|
|
146
|
+
|
|
147
|
+
Args:
|
|
148
|
+
attestation_json: The attestation JSON string.
|
|
149
|
+
issuer_key: Issuer's public key hex.
|
|
150
|
+
required_capability: If set, also verify the attestation grants this capability.
|
|
151
|
+
at: RFC 3339 timestamp to verify against (e.g., "2024-06-15T00:00:00Z").
|
|
152
|
+
When set, checks validity at that point in time instead of now.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
VerificationResult with validity status and details.
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
VerificationError: If the attestation signature is invalid or expired.
|
|
159
|
+
CryptoError: If the issuer key is malformed.
|
|
160
|
+
|
|
161
|
+
Examples:
|
|
162
|
+
```python
|
|
163
|
+
result = auths.verify(att_json, key, at="2024-06-15T00:00:00Z",
|
|
164
|
+
required_capability="deploy:staging")
|
|
165
|
+
```
|
|
166
|
+
"""
|
|
167
|
+
try:
|
|
168
|
+
if at and required_capability:
|
|
169
|
+
return _verify_at_time_with_capability(
|
|
170
|
+
attestation_json, issuer_key, at, required_capability
|
|
171
|
+
)
|
|
172
|
+
if at:
|
|
173
|
+
return _verify_at_time(attestation_json, issuer_key, at)
|
|
174
|
+
if required_capability:
|
|
175
|
+
return _verify_attestation_with_capability(
|
|
176
|
+
attestation_json, issuer_key, required_capability
|
|
177
|
+
)
|
|
178
|
+
return _verify_attestation(attestation_json, issuer_key)
|
|
179
|
+
except (ValueError, RuntimeError) as exc:
|
|
180
|
+
raise _map_error(exc) from exc
|
|
181
|
+
|
|
182
|
+
def verify_chain(
|
|
183
|
+
self,
|
|
184
|
+
attestations: list[str],
|
|
185
|
+
root_key: str,
|
|
186
|
+
required_capability: str | None = None,
|
|
187
|
+
witnesses: WitnessConfig | None = None,
|
|
188
|
+
) -> VerificationReport:
|
|
189
|
+
"""Verify an attestation chain, optionally with witness quorum.
|
|
190
|
+
|
|
191
|
+
Args:
|
|
192
|
+
attestations: List of attestation JSON strings, ordered root-to-leaf.
|
|
193
|
+
root_key: Root identity's public key hex.
|
|
194
|
+
required_capability: If set, verify the chain grants this capability.
|
|
195
|
+
witnesses: If set, enforces witness receipt quorum.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
VerificationReport with per-link results and overall validity.
|
|
199
|
+
|
|
200
|
+
Raises:
|
|
201
|
+
VerificationError: If any link in the chain fails verification.
|
|
202
|
+
|
|
203
|
+
Examples:
|
|
204
|
+
```python
|
|
205
|
+
report = auths.verify_chain(chain, root_key, witnesses=config)
|
|
206
|
+
```
|
|
207
|
+
"""
|
|
208
|
+
try:
|
|
209
|
+
if witnesses:
|
|
210
|
+
keys_json = [
|
|
211
|
+
json.dumps({"did": k.did, "public_key_hex": k.public_key_hex})
|
|
212
|
+
for k in witnesses.keys
|
|
213
|
+
]
|
|
214
|
+
return _verify_chain_with_witnesses(
|
|
215
|
+
attestations, root_key,
|
|
216
|
+
witnesses.receipts, keys_json, witnesses.threshold,
|
|
217
|
+
)
|
|
218
|
+
if required_capability:
|
|
219
|
+
return _verify_chain_with_capability(
|
|
220
|
+
attestations, root_key, required_capability
|
|
221
|
+
)
|
|
222
|
+
return _verify_chain(attestations, root_key)
|
|
223
|
+
except (ValueError, RuntimeError) as exc:
|
|
224
|
+
raise _map_error(exc) from exc
|
|
225
|
+
|
|
226
|
+
def verify_device(
|
|
227
|
+
self,
|
|
228
|
+
identity_did: str,
|
|
229
|
+
device_did: str,
|
|
230
|
+
attestations: list[str],
|
|
231
|
+
identity_key: str,
|
|
232
|
+
) -> VerificationReport:
|
|
233
|
+
"""Verify device authorization against an identity.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
identity_did: The parent identity's DID.
|
|
237
|
+
device_did: The device DID to verify.
|
|
238
|
+
attestations: Attestation chain JSON strings.
|
|
239
|
+
identity_key: Identity's public key hex.
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
VerificationReport confirming the device is authorized.
|
|
243
|
+
|
|
244
|
+
Raises:
|
|
245
|
+
VerificationError: If the device authorization is invalid or revoked.
|
|
246
|
+
"""
|
|
247
|
+
try:
|
|
248
|
+
return _verify_device_authorization(
|
|
249
|
+
identity_did, device_did, attestations, identity_key
|
|
250
|
+
)
|
|
251
|
+
except (ValueError, RuntimeError) as exc:
|
|
252
|
+
raise _map_error(exc) from exc
|
|
253
|
+
|
|
254
|
+
def sign(self, message: bytes, private_key: str) -> str:
|
|
255
|
+
"""Sign raw bytes with a private key.
|
|
256
|
+
|
|
257
|
+
Args:
|
|
258
|
+
message: Bytes to sign.
|
|
259
|
+
private_key: Hex-encoded Ed25519 private key.
|
|
260
|
+
|
|
261
|
+
Returns:
|
|
262
|
+
Hex-encoded Ed25519 signature.
|
|
263
|
+
|
|
264
|
+
Raises:
|
|
265
|
+
CryptoError: If the private key is invalid or signing fails.
|
|
266
|
+
"""
|
|
267
|
+
try:
|
|
268
|
+
return _sign_bytes(private_key, message)
|
|
269
|
+
except (ValueError, RuntimeError) as exc:
|
|
270
|
+
raise _map_error(exc, default_cls=CryptoError) from exc
|
|
271
|
+
|
|
272
|
+
def sign_action(
|
|
273
|
+
self,
|
|
274
|
+
action_type: str,
|
|
275
|
+
payload: str,
|
|
276
|
+
identity_did: str,
|
|
277
|
+
private_key: str,
|
|
278
|
+
) -> str:
|
|
279
|
+
"""Sign an action envelope.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
action_type: Action type string.
|
|
283
|
+
payload: JSON payload string.
|
|
284
|
+
identity_did: The signer's DID.
|
|
285
|
+
private_key: Hex-encoded Ed25519 private key.
|
|
286
|
+
|
|
287
|
+
Returns:
|
|
288
|
+
JSON-serialized signed action envelope.
|
|
289
|
+
|
|
290
|
+
Raises:
|
|
291
|
+
CryptoError: If signing fails.
|
|
292
|
+
"""
|
|
293
|
+
try:
|
|
294
|
+
return _sign_action(private_key, action_type, payload, identity_did)
|
|
295
|
+
except (ValueError, RuntimeError) as exc:
|
|
296
|
+
raise _map_error(exc, default_cls=CryptoError) from exc
|
|
297
|
+
|
|
298
|
+
def verify_action(self, envelope_json: str, public_key: str) -> VerificationResult:
|
|
299
|
+
"""Verify an action envelope signature.
|
|
300
|
+
|
|
301
|
+
Args:
|
|
302
|
+
envelope_json: JSON-serialized signed action envelope.
|
|
303
|
+
public_key: Hex-encoded Ed25519 public key of the signer.
|
|
304
|
+
|
|
305
|
+
Returns:
|
|
306
|
+
VerificationResult with validity status.
|
|
307
|
+
|
|
308
|
+
Raises:
|
|
309
|
+
VerificationError: If the envelope signature is invalid.
|
|
310
|
+
"""
|
|
311
|
+
try:
|
|
312
|
+
return _verify_action_envelope(envelope_json, public_key)
|
|
313
|
+
except (ValueError, RuntimeError) as exc:
|
|
314
|
+
raise _map_error(exc) from exc
|
|
315
|
+
|
|
316
|
+
def sign_as(
|
|
317
|
+
self,
|
|
318
|
+
message: bytes,
|
|
319
|
+
identity: str,
|
|
320
|
+
passphrase: str | None = None,
|
|
321
|
+
) -> str:
|
|
322
|
+
"""Sign bytes using a keychain-stored identity key.
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
message: Bytes to sign.
|
|
326
|
+
identity: The identity DID (`did:keri:...`) whose key to use.
|
|
327
|
+
passphrase: Override passphrase (default: client passphrase or AUTHS_PASSPHRASE).
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Hex-encoded Ed25519 signature.
|
|
331
|
+
|
|
332
|
+
Raises:
|
|
333
|
+
CryptoError: If the key is not found or signing fails.
|
|
334
|
+
KeychainError: If the keychain is locked or inaccessible.
|
|
335
|
+
|
|
336
|
+
Examples:
|
|
337
|
+
```python
|
|
338
|
+
identity = auths.identities.create(label="laptop")
|
|
339
|
+
sig = auths.sign_as(b"hello", identity=identity.did)
|
|
340
|
+
```
|
|
341
|
+
"""
|
|
342
|
+
from auths._native import sign_as_identity
|
|
343
|
+
|
|
344
|
+
pp = passphrase or self._passphrase
|
|
345
|
+
try:
|
|
346
|
+
return sign_as_identity(message, identity, self.repo_path, pp)
|
|
347
|
+
except (ValueError, RuntimeError) as exc:
|
|
348
|
+
raise _map_error(exc, default_cls=CryptoError) from exc
|
|
349
|
+
|
|
350
|
+
def sign_action_as(
|
|
351
|
+
self,
|
|
352
|
+
action_type: str,
|
|
353
|
+
payload: str,
|
|
354
|
+
identity: str,
|
|
355
|
+
passphrase: str | None = None,
|
|
356
|
+
) -> str:
|
|
357
|
+
"""Sign an action envelope using a keychain-stored identity key.
|
|
358
|
+
|
|
359
|
+
Args:
|
|
360
|
+
action_type: Action type string.
|
|
361
|
+
payload: JSON payload string.
|
|
362
|
+
identity: The identity DID whose key to use.
|
|
363
|
+
passphrase: Override passphrase.
|
|
364
|
+
|
|
365
|
+
Returns:
|
|
366
|
+
JSON-serialized signed action envelope.
|
|
367
|
+
|
|
368
|
+
Raises:
|
|
369
|
+
CryptoError: If signing fails.
|
|
370
|
+
KeychainError: If the keychain is locked or inaccessible.
|
|
371
|
+
|
|
372
|
+
Examples:
|
|
373
|
+
```python
|
|
374
|
+
envelope = auths.sign_action_as("deploy", payload_json, identity=identity.did)
|
|
375
|
+
```
|
|
376
|
+
"""
|
|
377
|
+
from auths._native import sign_action_as_identity
|
|
378
|
+
|
|
379
|
+
pp = passphrase or self._passphrase
|
|
380
|
+
try:
|
|
381
|
+
return sign_action_as_identity(
|
|
382
|
+
action_type, payload, identity, self.repo_path, pp
|
|
383
|
+
)
|
|
384
|
+
except (ValueError, RuntimeError) as exc:
|
|
385
|
+
raise _map_error(exc, default_cls=CryptoError) from exc
|
|
386
|
+
|
|
387
|
+
def get_public_key(
|
|
388
|
+
self,
|
|
389
|
+
identity: str,
|
|
390
|
+
passphrase: str | None = None,
|
|
391
|
+
) -> str:
|
|
392
|
+
"""Retrieve the Ed25519 public key (hex) for an identity.
|
|
393
|
+
|
|
394
|
+
Args:
|
|
395
|
+
identity: The identity DID (`did:keri:...`).
|
|
396
|
+
passphrase: Override passphrase.
|
|
397
|
+
|
|
398
|
+
Returns:
|
|
399
|
+
Hex-encoded Ed25519 public key.
|
|
400
|
+
|
|
401
|
+
Raises:
|
|
402
|
+
CryptoError: If the identity key is not found.
|
|
403
|
+
KeychainError: If the keychain is locked or inaccessible.
|
|
404
|
+
|
|
405
|
+
Examples:
|
|
406
|
+
```python
|
|
407
|
+
pub_key = auths.get_public_key(identity.did)
|
|
408
|
+
```
|
|
409
|
+
"""
|
|
410
|
+
from auths._native import get_identity_public_key
|
|
411
|
+
|
|
412
|
+
pp = passphrase or self._passphrase
|
|
413
|
+
try:
|
|
414
|
+
return get_identity_public_key(identity, self.repo_path, pp)
|
|
415
|
+
except (ValueError, RuntimeError) as exc:
|
|
416
|
+
raise _map_error(exc, default_cls=CryptoError) from exc
|
|
417
|
+
|
|
418
|
+
def sign_as_agent(
|
|
419
|
+
self,
|
|
420
|
+
message: bytes,
|
|
421
|
+
key_alias: str,
|
|
422
|
+
passphrase: str | None = None,
|
|
423
|
+
) -> str:
|
|
424
|
+
"""Sign bytes using a delegated agent's own key.
|
|
425
|
+
|
|
426
|
+
Unlike `sign_as()` which resolves by identity DID, this uses the agent's
|
|
427
|
+
key alias directly — enabling delegated agents (`did:key:`) to sign.
|
|
428
|
+
|
|
429
|
+
Args:
|
|
430
|
+
message: Bytes to sign.
|
|
431
|
+
key_alias: The agent's key alias (e.g., "deploy-bot-agent").
|
|
432
|
+
passphrase: Override passphrase.
|
|
433
|
+
|
|
434
|
+
Returns:
|
|
435
|
+
Hex-encoded Ed25519 signature.
|
|
436
|
+
|
|
437
|
+
Raises:
|
|
438
|
+
CryptoError: If the agent key is not found or signing fails.
|
|
439
|
+
KeychainError: If the keychain is locked or inaccessible.
|
|
440
|
+
|
|
441
|
+
Examples:
|
|
442
|
+
```python
|
|
443
|
+
agent = auths.identities.delegate_agent(identity.did, "bot", ["sign"])
|
|
444
|
+
sig = auths.sign_as_agent(b"hello", key_alias=agent._key_alias)
|
|
445
|
+
```
|
|
446
|
+
"""
|
|
447
|
+
from auths._native import sign_as_agent as _sign_as_agent
|
|
448
|
+
|
|
449
|
+
pp = passphrase or self._passphrase
|
|
450
|
+
try:
|
|
451
|
+
return _sign_as_agent(message, key_alias, self.repo_path, pp)
|
|
452
|
+
except (ValueError, RuntimeError) as exc:
|
|
453
|
+
raise _map_error(exc, default_cls=CryptoError) from exc
|
|
454
|
+
|
|
455
|
+
def sign_action_as_agent(
|
|
456
|
+
self,
|
|
457
|
+
action_type: str,
|
|
458
|
+
payload: str,
|
|
459
|
+
key_alias: str,
|
|
460
|
+
agent_did: str,
|
|
461
|
+
passphrase: str | None = None,
|
|
462
|
+
) -> str:
|
|
463
|
+
"""Sign an action envelope using a delegated agent's own key.
|
|
464
|
+
|
|
465
|
+
Args:
|
|
466
|
+
action_type: Action type string.
|
|
467
|
+
payload: JSON payload string.
|
|
468
|
+
key_alias: The agent's key alias.
|
|
469
|
+
agent_did: The agent's DID (included in the envelope).
|
|
470
|
+
passphrase: Override passphrase.
|
|
471
|
+
|
|
472
|
+
Returns:
|
|
473
|
+
JSON-serialized signed action envelope.
|
|
474
|
+
|
|
475
|
+
Raises:
|
|
476
|
+
CryptoError: If signing fails.
|
|
477
|
+
KeychainError: If the keychain is locked or inaccessible.
|
|
478
|
+
|
|
479
|
+
Examples:
|
|
480
|
+
```python
|
|
481
|
+
agent = auths.identities.delegate_agent(identity.did, "bot", ["deploy"])
|
|
482
|
+
envelope = auths.sign_action_as_agent("deploy", payload, agent._key_alias, agent.did)
|
|
483
|
+
```
|
|
484
|
+
"""
|
|
485
|
+
from auths._native import sign_action_as_agent as _sign_action_as_agent
|
|
486
|
+
|
|
487
|
+
pp = passphrase or self._passphrase
|
|
488
|
+
try:
|
|
489
|
+
return _sign_action_as_agent(action_type, payload, key_alias, agent_did, self.repo_path, pp)
|
|
490
|
+
except (ValueError, RuntimeError) as exc:
|
|
491
|
+
raise _map_error(exc, default_cls=CryptoError) from exc
|
|
492
|
+
|
|
493
|
+
def sign_commit(
|
|
494
|
+
self,
|
|
495
|
+
data: bytes,
|
|
496
|
+
*,
|
|
497
|
+
identity_did: str,
|
|
498
|
+
passphrase: str | None = None,
|
|
499
|
+
) -> CommitSigningResult:
|
|
500
|
+
"""Sign git commit/tag data, producing an SSHSIG PEM signature.
|
|
501
|
+
|
|
502
|
+
Uses a 3-tier fallback:
|
|
503
|
+
|
|
504
|
+
1. ssh-agent (fastest, works on dev machines with agent running)
|
|
505
|
+
2. auto-start agent (starts a transient agent process)
|
|
506
|
+
3. direct signing (works everywhere, including headless CI)
|
|
507
|
+
|
|
508
|
+
Args:
|
|
509
|
+
data: The raw commit or tag bytes to sign.
|
|
510
|
+
identity_did: The KERI DID of the identity to sign with.
|
|
511
|
+
passphrase: Optional passphrase (for headless envs without ssh-agent).
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
CommitSigningResult with the SSHSIG PEM block, method, and namespace.
|
|
515
|
+
|
|
516
|
+
Raises:
|
|
517
|
+
CryptoError: If signing fails or the identity key is not found.
|
|
518
|
+
KeychainError: If the keychain is locked or inaccessible.
|
|
519
|
+
|
|
520
|
+
Examples:
|
|
521
|
+
```python
|
|
522
|
+
result = auths.sign_commit(commit_bytes, identity_did=identity.did)
|
|
523
|
+
```
|
|
524
|
+
"""
|
|
525
|
+
from auths._native import sign_commit as _sign_commit
|
|
526
|
+
from auths.commit import CommitSigningResult
|
|
527
|
+
|
|
528
|
+
pp = passphrase or self._passphrase
|
|
529
|
+
try:
|
|
530
|
+
raw = _sign_commit(data, identity_did, self.repo_path, pp)
|
|
531
|
+
return CommitSigningResult(
|
|
532
|
+
signature_pem=raw.signature_pem,
|
|
533
|
+
method=raw.method,
|
|
534
|
+
namespace=raw.namespace,
|
|
535
|
+
)
|
|
536
|
+
except (ValueError, RuntimeError) as exc:
|
|
537
|
+
raise _map_error(exc, default_cls=CryptoError) from exc
|
|
538
|
+
|
|
539
|
+
def sign_artifact(
|
|
540
|
+
self,
|
|
541
|
+
path: str,
|
|
542
|
+
*,
|
|
543
|
+
identity_did: str,
|
|
544
|
+
expires_in: int | None = None,
|
|
545
|
+
note: str | None = None,
|
|
546
|
+
) -> ArtifactSigningResult:
|
|
547
|
+
"""Sign a file artifact, producing a dual-signed attestation.
|
|
548
|
+
|
|
549
|
+
Computes SHA-256 digest of the file and creates an attestation binding
|
|
550
|
+
the digest to your identity.
|
|
551
|
+
|
|
552
|
+
Args:
|
|
553
|
+
path: Path to the file to sign.
|
|
554
|
+
identity_did: The identity DID to sign with (used as key alias).
|
|
555
|
+
expires_in: Duration in seconds until expiration (per RFC 6749).
|
|
556
|
+
note: Optional human-readable note.
|
|
557
|
+
|
|
558
|
+
Returns:
|
|
559
|
+
ArtifactSigningResult with the attestation JSON, RID, digest, and file size.
|
|
560
|
+
|
|
561
|
+
Raises:
|
|
562
|
+
FileNotFoundError: If the file does not exist.
|
|
563
|
+
CryptoError: If signing fails.
|
|
564
|
+
|
|
565
|
+
Examples:
|
|
566
|
+
```python
|
|
567
|
+
result = auths.sign_artifact("release.tar.gz", identity_did=identity.did)
|
|
568
|
+
```
|
|
569
|
+
"""
|
|
570
|
+
from auths._native import sign_artifact as _sign_artifact
|
|
571
|
+
from auths.artifact import ArtifactSigningResult
|
|
572
|
+
|
|
573
|
+
pp = self._passphrase
|
|
574
|
+
try:
|
|
575
|
+
raw = _sign_artifact(
|
|
576
|
+
path, identity_did, self.repo_path, pp, expires_in, note,
|
|
577
|
+
)
|
|
578
|
+
return ArtifactSigningResult(
|
|
579
|
+
attestation_json=raw.attestation_json,
|
|
580
|
+
rid=raw.rid,
|
|
581
|
+
digest=raw.digest,
|
|
582
|
+
file_size=raw.file_size,
|
|
583
|
+
)
|
|
584
|
+
except FileNotFoundError:
|
|
585
|
+
raise
|
|
586
|
+
except (ValueError, RuntimeError) as exc:
|
|
587
|
+
raise _map_error(exc, default_cls=CryptoError) from exc
|
|
588
|
+
|
|
589
|
+
def sign_artifact_bytes(
|
|
590
|
+
self,
|
|
591
|
+
data: bytes,
|
|
592
|
+
*,
|
|
593
|
+
identity_did: str,
|
|
594
|
+
expires_in: int | None = None,
|
|
595
|
+
note: str | None = None,
|
|
596
|
+
) -> ArtifactSigningResult:
|
|
597
|
+
"""Sign raw bytes, producing a dual-signed attestation.
|
|
598
|
+
|
|
599
|
+
Use this for non-file artifacts: container manifest digests,
|
|
600
|
+
git tree hashes, API response bodies.
|
|
601
|
+
|
|
602
|
+
Args:
|
|
603
|
+
data: The raw bytes to sign.
|
|
604
|
+
identity_did: The identity DID to sign with (used as key alias).
|
|
605
|
+
expires_in: Duration in seconds until expiration (per RFC 6749).
|
|
606
|
+
note: Optional human-readable note.
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
ArtifactSigningResult with the attestation JSON, RID, digest, and size.
|
|
610
|
+
|
|
611
|
+
Raises:
|
|
612
|
+
CryptoError: If signing fails.
|
|
613
|
+
|
|
614
|
+
Examples:
|
|
615
|
+
```python
|
|
616
|
+
result = auths.sign_artifact_bytes(manifest_bytes, identity_did=did)
|
|
617
|
+
```
|
|
618
|
+
"""
|
|
619
|
+
from auths._native import sign_artifact_bytes as _sign_artifact_bytes
|
|
620
|
+
from auths.artifact import ArtifactSigningResult
|
|
621
|
+
|
|
622
|
+
pp = self._passphrase
|
|
623
|
+
try:
|
|
624
|
+
raw = _sign_artifact_bytes(
|
|
625
|
+
data, identity_did, self.repo_path, pp, expires_in, note,
|
|
626
|
+
)
|
|
627
|
+
return ArtifactSigningResult(
|
|
628
|
+
attestation_json=raw.attestation_json,
|
|
629
|
+
rid=raw.rid,
|
|
630
|
+
digest=raw.digest,
|
|
631
|
+
file_size=raw.file_size,
|
|
632
|
+
)
|
|
633
|
+
except (ValueError, RuntimeError) as exc:
|
|
634
|
+
raise _map_error(exc, default_cls=CryptoError) from exc
|
|
635
|
+
|
|
636
|
+
def publish_artifact(
|
|
637
|
+
self,
|
|
638
|
+
attestation_json: str,
|
|
639
|
+
*,
|
|
640
|
+
registry_url: str,
|
|
641
|
+
package_name: str | None = None,
|
|
642
|
+
) -> "ArtifactPublishResult":
|
|
643
|
+
"""Publish a signed attestation to a registry.
|
|
644
|
+
|
|
645
|
+
Args:
|
|
646
|
+
attestation_json: The attestation JSON string from `sign_artifact()`.
|
|
647
|
+
registry_url: Base URL of the target registry.
|
|
648
|
+
package_name: Optional ecosystem-prefixed identifier (e.g. "npm:react@18.3.0").
|
|
649
|
+
|
|
650
|
+
Returns:
|
|
651
|
+
ArtifactPublishResult with the registry RID, package name, and signer DID.
|
|
652
|
+
|
|
653
|
+
Raises:
|
|
654
|
+
StorageError: If the attestation is a duplicate.
|
|
655
|
+
VerificationError: If the registry rejects the attestation.
|
|
656
|
+
NetworkError: If the registry is unreachable.
|
|
657
|
+
|
|
658
|
+
Examples:
|
|
659
|
+
```python
|
|
660
|
+
signed = auths.sign_artifact("release.tar.gz", identity_did=did)
|
|
661
|
+
result = auths.publish_artifact(
|
|
662
|
+
signed.attestation_json,
|
|
663
|
+
registry_url="https://registry.example.com",
|
|
664
|
+
)
|
|
665
|
+
```
|
|
666
|
+
"""
|
|
667
|
+
from auths._native import publish_artifact as _publish_artifact
|
|
668
|
+
from auths.artifact import ArtifactPublishResult
|
|
669
|
+
|
|
670
|
+
try:
|
|
671
|
+
raw = _publish_artifact(attestation_json, registry_url, package_name)
|
|
672
|
+
return ArtifactPublishResult(
|
|
673
|
+
attestation_rid=raw.attestation_rid,
|
|
674
|
+
package_name=raw.package_name,
|
|
675
|
+
signer_did=raw.signer_did,
|
|
676
|
+
)
|
|
677
|
+
except (ValueError, RuntimeError) as exc:
|
|
678
|
+
msg = str(exc)
|
|
679
|
+
if "duplicate_attestation" in msg:
|
|
680
|
+
raise StorageError(msg, code="duplicate_attestation") from exc
|
|
681
|
+
if "verification_failed" in msg:
|
|
682
|
+
raise VerificationError(msg, code="verification_failed") from exc
|
|
683
|
+
if "unreachable" in msg.lower() or "connection" in msg.lower() or "timeout" in msg.lower():
|
|
684
|
+
raise _map_network_error(exc) from exc
|
|
685
|
+
raise _map_error(exc) from exc
|
|
686
|
+
|
|
687
|
+
def get_token(
|
|
688
|
+
self,
|
|
689
|
+
bridge_url: str,
|
|
690
|
+
chain_json: str,
|
|
691
|
+
root_key: str,
|
|
692
|
+
capabilities: list[str] | None = None,
|
|
693
|
+
) -> str:
|
|
694
|
+
"""Exchange an attestation chain for a bearer token.
|
|
695
|
+
|
|
696
|
+
Args:
|
|
697
|
+
bridge_url: The OIDC bridge base URL.
|
|
698
|
+
chain_json: JSON-serialized attestation chain.
|
|
699
|
+
root_key: Root identity's public key hex.
|
|
700
|
+
capabilities: Optional list of capabilities to request.
|
|
701
|
+
|
|
702
|
+
Returns:
|
|
703
|
+
JWT bearer token string.
|
|
704
|
+
|
|
705
|
+
Raises:
|
|
706
|
+
NetworkError: If the bridge is unreachable or returns an error.
|
|
707
|
+
"""
|
|
708
|
+
try:
|
|
709
|
+
return _get_token(bridge_url, chain_json, root_key, capabilities or [])
|
|
710
|
+
except ConnectionError as exc:
|
|
711
|
+
raise _map_network_error(exc) from exc
|
|
712
|
+
except (ValueError, RuntimeError) as exc:
|
|
713
|
+
raise _map_network_error(exc) from exc
|