ccf 7.0.0.dev1__py3-none-any.whl → 7.0.0.dev3__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.
ccf/cose.py CHANGED
@@ -4,18 +4,17 @@
4
4
  import argparse
5
5
  import sys
6
6
 
7
- from typing import Optional, Type
7
+ from typing import Optional, Any
8
8
 
9
9
  import base64
10
+ import cwt
11
+ import cwt.const
12
+ import cwt.utils
13
+ import cwt.enums
10
14
  import cbor2
11
15
  import json
12
- from hashlib import sha256
16
+ import hashlib
13
17
  from datetime import datetime
14
- import pycose.headers # type: ignore
15
- from pycose.keys.ec2 import EC2Key # type: ignore
16
- from pycose.keys.curves import P256, P384, P521, CoseCurve # type: ignore
17
- from pycose.keys.keyparam import EC2KpCurve, EC2KpX, EC2KpY, EC2KpD # type: ignore
18
- from pycose.messages import Sign1Message # type: ignore
19
18
  from cryptography.hazmat.primitives.asymmetric import ec
20
19
  from cryptography.hazmat.primitives.asymmetric.ec import (
21
20
  EllipticCurvePrivateKey,
@@ -24,7 +23,10 @@ from cryptography.hazmat.primitives.asymmetric.ec import (
24
23
  from cryptography.hazmat.backends import default_backend
25
24
  from cryptography.x509 import load_pem_x509_certificate
26
25
  from cryptography.hazmat.primitives import hashes
27
- from cryptography.hazmat.primitives.serialization import load_pem_private_key
26
+ from cryptography.hazmat.primitives.serialization import (
27
+ load_pem_private_key,
28
+ load_pem_public_key,
29
+ )
28
30
  from cryptography.x509.base import CertificatePublicKeyTypes
29
31
  from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
30
32
 
@@ -41,7 +43,6 @@ GOV_MSG_TYPES = [
41
43
  ] + GOV_MSG_TYPES_WITH_PROPOSAL_ID
42
44
 
43
45
  # See https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs/
44
- # should move to a pycose.header value after RFC publication
45
46
 
46
47
  COSE_PHDR_VDP_LABEL = 396
47
48
  COSE_PHDR_VDS_LABEL = 395
@@ -54,59 +55,18 @@ CCF_PROOF_LEAF_LABEL = 1
54
55
  CCF_PROOF_PATH_LABEL = 2
55
56
 
56
57
 
57
- def from_cryptography_eckey_obj(ext_key) -> EC2Key:
58
- """
59
- Returns an initialized COSE Key object of type EC2Key.
60
- :param ext_key: Python cryptography key.
61
- :return: an initialized EC key
62
- """
63
- if hasattr(ext_key, "private_numbers"):
64
- priv_nums = ext_key.private_numbers()
65
- pub_nums = priv_nums.public_numbers
66
- else:
67
- priv_nums = None
68
- pub_nums = ext_key.public_numbers()
69
-
70
- curve: Type[CoseCurve]
71
- if pub_nums.curve.name == "secp256r1":
72
- curve = P256
73
- elif pub_nums.curve.name == "secp384r1":
74
- curve = P384
75
- elif pub_nums.curve.name == "secp521r1":
76
- curve = P521
77
- else:
78
- raise NotImplementedError("unsupported curve")
79
-
80
- cose_key = {}
81
- if pub_nums:
82
- cose_key.update(
83
- {
84
- EC2KpCurve: curve,
85
- EC2KpX: pub_nums.x.to_bytes(curve.size, "big"),
86
- EC2KpY: pub_nums.y.to_bytes(curve.size, "big"),
87
- }
88
- )
89
- if priv_nums:
90
- cose_key.update(
91
- {
92
- EC2KpD: priv_nums.private_value.to_bytes(curve.size, "big"),
93
- }
94
- )
95
- return EC2Key.from_dict(cose_key)
96
-
97
-
98
- def default_algorithm_for_key(key) -> str:
58
+ def default_algorithm_for_key(key) -> int:
99
59
  """
100
60
  Get the default algorithm for a given key, based on its
101
61
  type and parameters.
102
62
  """
103
63
  if isinstance(key, EllipticCurvePublicKey):
104
64
  if isinstance(key.curve, ec.SECP256R1):
105
- return "ES256"
65
+ return cwt.enums.COSEAlgs.ES256
106
66
  elif isinstance(key.curve, ec.SECP384R1):
107
- return "ES384"
67
+ return cwt.enums.COSEAlgs.ES384
108
68
  elif isinstance(key.curve, ec.SECP521R1):
109
- return "ES512"
69
+ return cwt.enums.COSEAlgs.ES512
110
70
  else:
111
71
  raise NotImplementedError("unsupported curve")
112
72
  else:
@@ -125,30 +85,36 @@ def cert_fingerprint(cert_pem: Pem):
125
85
  return cert.fingerprint(hashes.SHA256()).hex().encode("utf-8")
126
86
 
127
87
 
88
+ def key_fingerprint_from_cert(cert_pem: Pem):
89
+ cert = load_pem_x509_certificate(cert_pem.encode("ascii"), default_backend())
90
+ pub_key = cert.public_key().public_bytes(
91
+ Encoding.DER, PublicFormat.SubjectPublicKeyInfo
92
+ )
93
+ return hashlib.sha256(pub_key).hexdigest()
94
+
95
+
96
+ def key_fingerprint_from_key(key_pem: Pem):
97
+ key = load_pem_public_key(key_pem.encode("ascii"), default_backend())
98
+ pub_key = key.public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo)
99
+ return hashlib.sha256(pub_key).hexdigest()
100
+
101
+
128
102
  def create_cose_sign1(
129
103
  payload: bytes,
130
104
  key_priv_pem: Pem,
131
105
  cert_pem: Pem,
132
106
  additional_protected_header: Optional[dict] = None,
133
107
  ) -> bytes:
134
- key_type = get_priv_key_type(key_priv_pem)
135
-
136
- cert = load_pem_x509_certificate(cert_pem.encode("ascii"), default_backend())
137
- alg = default_algorithm_for_key(cert.public_key())
138
- kid = cert_fingerprint(cert_pem)
139
-
140
- protected_header = {pycose.headers.Algorithm: alg, pycose.headers.KID: kid}
141
- protected_header.update(additional_protected_header or {})
142
- msg = Sign1Message(phdr=protected_header, payload=payload)
143
-
144
- key = load_pem_private_key(key_priv_pem.encode("ascii"), None, default_backend())
145
- if key_type == "ec":
146
- cose_key = from_cryptography_eckey_obj(key)
147
- else:
148
- raise NotImplementedError("unsupported key type")
149
- msg.key = cose_key
150
-
151
- return msg.encode()
108
+ cose_ctx = cwt.COSE.new(alg_auto_inclusion=True, deterministic_header=True)
109
+ cose_key = cwt.COSEKey.from_pem(key_priv_pem, kid=cert_fingerprint(cert_pem))
110
+ # kid is passed explicitly in the protected header, because kid_auto_inclusion
111
+ # sets the kid in the unprotected header
112
+ phdr: dict[Any, Any] = {int(cwt.COSEHeaders.KID): cert_fingerprint(cert_pem)}
113
+ additional_header = additional_protected_header or {}
114
+ phdr.update(additional_header)
115
+ return cose_ctx.encode_and_sign(
116
+ payload, cose_key, protected=cwt.utils.ResolvedHeader(phdr)
117
+ )
152
118
 
153
119
 
154
120
  def create_cose_sign1_prepare(
@@ -160,10 +126,14 @@ def create_cose_sign1_prepare(
160
126
  alg = default_algorithm_for_key(cert.public_key())
161
127
  kid = cert_fingerprint(cert_pem)
162
128
 
163
- protected_header = {pycose.headers.Algorithm: alg, pycose.headers.KID: kid}
129
+ protected_header: dict[str | int, Any] = {
130
+ int(cwt.COSEHeaders.ALG): alg,
131
+ int(cwt.COSEHeaders.KID): kid,
132
+ }
164
133
  protected_header.update(additional_protected_header or {})
165
- msg = Sign1Message(phdr=protected_header, payload=payload)
166
- tbs = cbor2.dumps(["Signature1", msg.phdr_encoded, b"", payload])
134
+ protected_header = cwt.utils.sort_keys_for_deterministic_encoding(protected_header)
135
+ phdr_encoded = cbor2.dumps(protected_header)
136
+ tbs = cbor2.dumps(["Signature1", phdr_encoded, b"", payload])
167
137
 
168
138
  assert cert.signature_hash_algorithm
169
139
  digester = hashes.Hash(cert.signature_hash_algorithm)
@@ -182,25 +152,42 @@ def create_cose_sign1_finish(
182
152
  alg = default_algorithm_for_key(cert.public_key())
183
153
  kid = cert_fingerprint(cert_pem)
184
154
 
185
- protected_header = {pycose.headers.Algorithm: alg, pycose.headers.KID: kid}
155
+ protected_header: dict[str | int, Any] = {
156
+ int(cwt.COSEHeaders.ALG): alg,
157
+ int(cwt.COSEHeaders.KID): kid,
158
+ }
186
159
  protected_header.update(additional_protected_header or {})
187
- msg = Sign1Message(phdr=protected_header, payload=payload)
188
-
189
- msg._signature = base64.urlsafe_b64decode(signature)
190
- return msg.encode(sign=False)
191
-
192
-
193
- def validate_cose_sign1(pubkey, cose_sign1, payload=None):
194
- cose_key = from_cryptography_eckey_obj(pubkey)
195
- msg = Sign1Message.decode(cose_sign1)
196
- msg.key = cose_key
160
+ protected_header = cwt.utils.sort_keys_for_deterministic_encoding(protected_header)
161
+ phdr_encoded = cbor2.dumps(protected_header)
162
+
163
+ sig = base64.urlsafe_b64decode(signature)
164
+ assert isinstance(sig, bytes)
165
+ msg = cbor2.CBORTag(
166
+ cwt.const.COSE_TYPE_TO_TAG[cwt.const.COSETypes.SIGN1],
167
+ [phdr_encoded, {}, payload, sig],
168
+ )
169
+ return cbor2.dumps(msg)
170
+
171
+
172
+ def verify_cose_sign1_with_cert(certificate, cose_sign1, use_key=True, payload=None):
173
+ cose_ctx = cwt.COSE.new()
174
+ cert_pem = certificate.decode()
175
+ cose_key = cwt.COSEKey.from_pem(
176
+ cert_pem,
177
+ kid=(
178
+ key_fingerprint_from_cert(cert_pem)
179
+ if use_key
180
+ else cert_fingerprint(cert_pem)
181
+ ),
182
+ )
183
+ return cose_ctx.decode_with_headers(cose_sign1, cose_key, detached_payload=payload)
197
184
 
198
- if payload:
199
- # Detached payload
200
- msg.payload = payload
201
185
 
202
- if not msg.verify_signature():
203
- raise ValueError("signature is invalid")
186
+ def verify_cose_sign1_with_key(key, cose_sign1, payload=None):
187
+ cose_ctx = cwt.COSE.new()
188
+ key_pem = key.decode()
189
+ cose_key = cwt.COSEKey.from_pem(key_pem, kid=key_fingerprint_from_key(key_pem))
190
+ return cose_ctx.decode_with_headers(cose_sign1, cose_key, detached_payload=payload)
204
191
 
205
192
 
206
193
  def verify_receipt(
@@ -210,52 +197,53 @@ def verify_receipt(
210
197
  Verify a COSE Sign1 receipt as defined in https://datatracker.ietf.org/doc/draft-ietf-cose-merkle-tree-proofs/,
211
198
  using the CCF tree algorithm defined in https://datatracker.ietf.org/doc/draft-birkholz-cose-receipts-ccf-profile/
212
199
  """
213
- # Extract the expected KID from the public key used for verification,
214
- # and check it against the value set in the COSE header before using
215
- # it to verify the proofs.
216
- expected_kid = (
217
- sha256(key.public_bytes(Encoding.DER, PublicFormat.SubjectPublicKeyInfo))
218
- .digest()
219
- .hex()
220
- .encode()
200
+ key_pem = key.public_bytes(Encoding.PEM, PublicFormat.SubjectPublicKeyInfo).decode(
201
+ "ascii"
221
202
  )
222
- receipt = Sign1Message.decode(receipt_bytes)
223
- cose_key = from_cryptography_eckey_obj(key)
224
- assert receipt.phdr[pycose.headers.KID] == expected_kid
225
- receipt.key = cose_key
203
+ expected_kid = key_fingerprint_from_key(key_pem)
204
+ cose_key = cwt.COSEKey.from_pem(key_pem, kid=expected_kid)
205
+ cose_ctx = cwt.COSE.new()
226
206
 
207
+ receipt = cbor2.loads(receipt_bytes)
208
+ assert receipt.tag == cwt.const.COSE_TYPE_TO_TAG[cwt.const.COSETypes.SIGN1]
209
+ phdr, uhdr, payload, sig = receipt.value
210
+ phdr = cbor2.loads(phdr)
211
+
212
+ assert phdr[4] == expected_kid.encode("utf-8")
213
+
214
+ assert COSE_PHDR_VDS_LABEL in phdr, "Verifiable data structure type is required"
227
215
  assert (
228
- COSE_PHDR_VDS_LABEL in receipt.phdr
229
- ), "Verifiable data structure type is required"
230
- assert (
231
- receipt.phdr[COSE_PHDR_VDS_LABEL] == COSE_PHDR_VDS_CCF_LEDGER_SHA256
216
+ phdr[COSE_PHDR_VDS_LABEL] == COSE_PHDR_VDS_CCF_LEDGER_SHA256
232
217
  ), "vds(395) protected header must be CCF_LEDGER_SHA256(2)"
233
218
 
234
- assert COSE_PHDR_VDP_LABEL in receipt.uhdr, "Verifiable data proof is required"
235
- proof = receipt.uhdr[COSE_PHDR_VDP_LABEL]
219
+ assert COSE_PHDR_VDP_LABEL in uhdr, "Verifiable data proof is required"
220
+ proof = uhdr[COSE_PHDR_VDP_LABEL]
236
221
  assert COSE_RECEIPT_INCLUSION_PROOF_LABEL in proof, "Inclusion proof is required"
237
222
  inclusion_proofs = proof[COSE_RECEIPT_INCLUSION_PROOF_LABEL]
238
223
  assert inclusion_proofs, "At least one inclusion proof is required"
224
+
225
+ ic_phdr = None
239
226
  for inclusion_proof in inclusion_proofs:
240
227
  assert isinstance(inclusion_proof, bytes), "Inclusion proof must be bstr"
241
228
  proof = cbor2.loads(inclusion_proof)
242
229
  assert CCF_PROOF_LEAF_LABEL in proof, "Leaf must be present"
243
230
  leaf = proof[CCF_PROOF_LEAF_LABEL]
244
- accumulator = sha256(
245
- leaf[0] + sha256(leaf[1].encode()).digest() + leaf[2]
231
+ if claim_digest != leaf[2]:
232
+ raise ValueError(f"Claim digest mismatch: {leaf[2]!r} != {claim_digest!r}")
233
+ accumulator = hashlib.sha256(
234
+ leaf[0] + hashlib.sha256(leaf[1].encode()).digest() + leaf[2]
246
235
  ).digest()
247
236
  assert CCF_PROOF_PATH_LABEL in proof, "Path must be present"
248
237
  path = proof[CCF_PROOF_PATH_LABEL]
249
238
  for left, digest in path:
250
239
  if left:
251
- accumulator = sha256(digest + accumulator).digest()
240
+ accumulator = hashlib.sha256(digest + accumulator).digest()
252
241
  else:
253
- accumulator = sha256(accumulator + digest).digest()
254
- if not receipt.verify_signature(accumulator):
255
- raise ValueError("Signature verification failed")
256
- if claim_digest != leaf[2]:
257
- raise ValueError(f"Claim digest mismatch: {leaf[2]!r} != {claim_digest!r}")
258
- return receipt.phdr
242
+ accumulator = hashlib.sha256(accumulator + digest).digest()
243
+ ic_phdr, _, _ = cose_ctx.decode_with_headers(
244
+ receipt_bytes, cose_key, detached_payload=accumulator
245
+ )
246
+ return ic_phdr
259
247
 
260
248
 
261
249
  _SIGN_DESCRIPTION = """Create and sign a COSE Sign1 message for CCF governance
ccf/ledger.py CHANGED
@@ -20,7 +20,7 @@ from cryptography.hazmat.primitives.asymmetric import utils, ec
20
20
 
21
21
  from ccf.merkletree import MerkleTree
22
22
  from ccf.tx_id import TxID
23
- from ccf.cose import validate_cose_sign1
23
+ import ccf.cose
24
24
  import ccf.receipt
25
25
  from hashlib import sha256
26
26
 
@@ -474,11 +474,11 @@ class BaseValidator:
474
474
  @staticmethod
475
475
  def _verify_root_cose_signature(service_cert, root, cose_sign1):
476
476
  try:
477
- cert = load_pem_x509_certificate(
478
- service_cert.encode("ascii"), default_backend()
479
- )
480
- validate_cose_sign1(
481
- cose_sign1=cose_sign1, pubkey=cert.public_key(), payload=root
477
+ ccf.cose.verify_cose_sign1_with_cert(
478
+ certificate=service_cert.encode("ascii"),
479
+ cose_sign1=cose_sign1,
480
+ use_key=True,
481
+ payload=root,
482
482
  )
483
483
  except Exception as exc:
484
484
  raise InvalidRootCoseSignatureException(
ccf/receipt.py CHANGED
@@ -5,6 +5,7 @@ import base64
5
5
  from hashlib import sha256
6
6
  from typing import List
7
7
  from cryptography.x509 import Certificate
8
+ from cryptography.x509.verification import PolicyBuilder, Store
8
9
  from cryptography.hazmat.primitives import hashes
9
10
  from cryptography.hazmat.primitives.asymmetric import ec, utils
10
11
 
@@ -54,7 +55,9 @@ def check_endorsement(endorsee: Certificate, endorser: Certificate):
54
55
 
55
56
 
56
57
  def check_endorsements(
57
- node_cert: Certificate, service_cert: Certificate, endorsements: List[Certificate]
58
+ node_cert: Certificate,
59
+ service_cert: Certificate,
60
+ endorsements: List[Certificate],
58
61
  ):
59
62
  """
60
63
  Check a node certificate is endorsed by a service certificate, transitively through a list of endorsements.
@@ -64,3 +67,21 @@ def check_endorsements(
64
67
  check_endorsement(cert_i, endorsement)
65
68
  cert_i = endorsement
66
69
  check_endorsement(cert_i, service_cert)
70
+
71
+
72
+ def check_cert_chain(
73
+ node_cert: Certificate,
74
+ service_cert: Certificate,
75
+ endorsements: List[Certificate],
76
+ ):
77
+ """
78
+ Use default cryptography policy to verify CCF cert chain
79
+ """
80
+ builder = PolicyBuilder()
81
+ builder = builder.store(Store([service_cert]))
82
+
83
+ # This would ideally be `build_server_verifier`, but that requires a
84
+ # Subject which is either a valid DNSName or IPAddress. Our node cert's
85
+ # Subject is "CCF Node", and we may not have a better value in SAN
86
+ verifier = builder.build_client_verifier()
87
+ verifier.verify(leaf=node_cert, intermediates=endorsements)
@@ -66,12 +66,10 @@ if [ -z "${member_id_cert}" ]; then
66
66
  exit 1
67
67
  fi
68
68
 
69
- if [ ! -f "env/bin/activate" ]
70
- then
71
- python3 -m venv env
69
+ if ! command -v ccf_cose_sign1 > /dev/null; then
70
+ echo "Error: This script requires the ccf_cose_sign1 CLI tool, distributed as part of the CCF Python package. Please install it via 'pip install ccf' in the current Python environment"
71
+ exit 1
72
72
  fi
73
- source env/bin/activate
74
- pip install -q ccf
75
73
 
76
74
  # Compute member ID, as the SHA-256 fingerprint of the signing certificate
77
75
  member_id=$(openssl x509 -in "$member_id_cert" -noout -fingerprint -sha256 | cut -d "=" -f 2 | sed 's/://g' | awk '{print tolower($0)}')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ccf
3
- Version: 7.0.0.dev1
3
+ Version: 7.0.0.dev3
4
4
  Summary: Set of tools and utilities for the Confidential Consortium Framework (CCF)
5
5
  Author-email: CCF Team <CCF-Sec@microsoft.com>
6
6
  Project-URL: Homepage, https://github.com/microsoft/ccf
@@ -11,9 +11,9 @@ Requires-Python: >=3.8
11
11
  Description-Content-Type: text/markdown
12
12
  License-File: LICENSE
13
13
  Requires-Dist: loguru==0.*,>=0.5
14
- Requires-Dist: cryptography<46,>=44
14
+ Requires-Dist: cryptography<47,>=44
15
15
  Requires-Dist: string-color==1.*,>=1.2.1
16
- Requires-Dist: pycose==1.*,>=1.0.1
16
+ Requires-Dist: cwt==3.*,>=3.1.0
17
17
  Requires-Dist: setuptools<81,>=74
18
18
  Requires-Dist: packaging<26,>=24
19
19
  Dynamic: license-file
@@ -1,21 +1,21 @@
1
1
  ccf/__init__.py,sha256=7h-KCpYSfH_Z8x5CFAoDlHfY0wqRNqOn8vTsaGW8dGA,101
2
2
  ccf/_versionifier.py,sha256=17vUCXFp_VixNCuhBmMS9SCM74JnHR1OIZnMQSspSs4,2951
3
- ccf/cose.py,sha256=NyeBz7Rs8hriT0xNJ2CUwjRAhfqqxAOhS5ZmMzQjL3I,15157
4
- ccf/ledger.py,sha256=3y52PazBj5aIsR7uc8qvwXGYCiV36Md_KUXhIIsRcLI,42293
3
+ ccf/cose.py,sha256=hueXb_gvswxRkDAcUacmSIdkfqAFegZIWkA66M4yKVk,14950
4
+ ccf/ledger.py,sha256=X6ljpGPB8HJ6eAw9F3KkEz_meJpMbPALSr0Fbn6R_cg,42240
5
5
  ccf/ledger_code.py,sha256=_malocCXly7TdBwBt8ldY6_yNfFwvyCb5Zkp8T4t9fg,3859
6
6
  ccf/ledger_viz.py,sha256=l1p2wKgLDayxquVa9-yzsMcLx9vvygCV8EZ_PrVpvMg,6430
7
7
  ccf/merkletree.py,sha256=JHUnGYYHJfi18YUNGiHmNCa1AwmCAwLAN3ias5j61ws,2931
8
8
  ccf/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
9
  ccf/read_ledger.py,sha256=UzmEKAMS2GJmwuaenKBJbkBDZIWeCa9xcXdpZB51SEg,9370
10
- ccf/receipt.py,sha256=jK8RGkGJwrW1yJC-GWx7DpxQXCCBdnZHIkrMXZgTOew,2079
10
+ ccf/receipt.py,sha256=dySVvy-IqvsB8pj5uooIokbQR853vpT7uC0TQ-IWIQk,2765
11
11
  ccf/split_ledger.py,sha256=wspNZUFPMSvMgGxmwP3QuSbx7QDzTjrC6ecFFJSp14o,5900
12
12
  ccf/tx_id.py,sha256=E7XJyUoJ-a8h7Fp36zNQdhSYdyuNdFV-r_aXhoWl1ks,736
13
13
  ccf/verify_ledger_secrets_chain.py,sha256=_UDc4wNro2mNUVbErDkhl24jLeJC0Vr-kECeZEdeHME,3590
14
- ccf-7.0.0.dev1.data/scripts/keygenerator.sh,sha256=r9i8rURcDUPU8c9NKkxrjweU1qU09Hv3SWc3IFQ648A,2391
15
- ccf-7.0.0.dev1.data/scripts/submit_recovery_share.sh,sha256=jz8ZKmRKfOuRaaMt46xb9gJsH8pQAXSbpYQdRvomJ_s,3151
16
- ccf-7.0.0.dev1.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
17
- ccf-7.0.0.dev1.dist-info/METADATA,sha256=dv775g2p1C_gZxEAVKWSfCMv250WRX8vtm-r3h79Iv8,994
18
- ccf-7.0.0.dev1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
- ccf-7.0.0.dev1.dist-info/entry_points.txt,sha256=3hbXI2LSOY06QitxM8GQqT9NwY7rCp1RtSU9gGG20A4,365
20
- ccf-7.0.0.dev1.dist-info/top_level.txt,sha256=I0tWtkKe6KRqXt0nIp8W-ln8j431-vDBb39bQGKkL9Q,4
21
- ccf-7.0.0.dev1.dist-info/RECORD,,
14
+ ccf-7.0.0.dev3.data/scripts/keygenerator.sh,sha256=r9i8rURcDUPU8c9NKkxrjweU1qU09Hv3SWc3IFQ648A,2391
15
+ ccf-7.0.0.dev3.data/scripts/submit_recovery_share.sh,sha256=v1L_PjduUASCVYjOJiG7lDwtcSwQPOd3G2CY9ipSUXU,3289
16
+ ccf-7.0.0.dev3.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
17
+ ccf-7.0.0.dev3.dist-info/METADATA,sha256=xjxa_WRC7OcqnzILwOHCd20okt7bhm__gTZZF5aYdgE,991
18
+ ccf-7.0.0.dev3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
19
+ ccf-7.0.0.dev3.dist-info/entry_points.txt,sha256=3hbXI2LSOY06QitxM8GQqT9NwY7rCp1RtSU9gGG20A4,365
20
+ ccf-7.0.0.dev3.dist-info/top_level.txt,sha256=I0tWtkKe6KRqXt0nIp8W-ln8j431-vDBb39bQGKkL9Q,4
21
+ ccf-7.0.0.dev3.dist-info/RECORD,,