├── hcert ├── __init__.py ├── utils.py ├── optical.py ├── hcert.py ├── cli.py └── cwt.py ├── README.md ├── tools ├── tl.py ├── cwtdump.py ├── dgc_test.py └── scanner.py ├── pyproject.toml ├── .github └── workflows │ └── test.yml ├── LICENSE ├── test └── test_hcert.py └── poetry.lock /hcert/__init__.py: -------------------------------------------------------------------------------- 1 | import pkg_resources 2 | 3 | __version__ = pkg_resources.get_distribution("hcert").version 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python for Electronic Health Certificates 2 | 3 | This repository contains an implementation of [Electronic Health Certificates](https://github.com/ehn-digital-green-development/hcert-spec). 4 | -------------------------------------------------------------------------------- /tools/tl.py: -------------------------------------------------------------------------------- 1 | """Export trusted list from SE Digital Covid Certificate Trust Point as JWKS""" 2 | 3 | import json 4 | 5 | import jwt as pyjwt 6 | import requests 7 | 8 | response = requests.get("https://dgcg.covidbevis.se/tp/trust-list") 9 | response.raise_for_status() 10 | 11 | 12 | tl = pyjwt.decode(response.text, options={"verify_signature": False}) 13 | 14 | with open("tl.json", "wt") as output_file: 15 | json.dump(tl, output_file, indent=True) 16 | 17 | dsc_trust_list = tl["dsc_trust_list"] 18 | 19 | keys = [] 20 | for country, data in dsc_trust_list.items(): 21 | for jwk_dict in data.get("keys", []): 22 | keys.append({"issuer": country, **jwk_dict}) 23 | 24 | with open("tl-jwks.json", "wt") as output_file: 25 | json.dump({"keys": keys}, output_file, indent=True) 26 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "hcert" 3 | version = "0.5.0" 4 | description = "Electronic Health Certificate (HCERT)" 5 | authors = ["Jakob Schlyter "] 6 | 7 | [tool.poetry.dependencies] 8 | python = "^3.9" 9 | jsonschema = "^3.2.0" 10 | cose = "0.9.dev7" 11 | cbor2 = "^5.2.0" 12 | cryptojwt = "^1.4.0" 13 | base45 = "^0.3.1" 14 | PyYAML = "^5.4.1" 15 | Pillow = "^8.1.1" 16 | qrcode = "^6.1" 17 | 18 | [tool.poetry.dev-dependencies] 19 | pylama = "^7.7.1" 20 | black = "^20.8b1" 21 | isort = "^5.7.0" 22 | pytest = "^6.2.2" 23 | pytest-isort = "^1.3.0" 24 | pytest-black = "^0.3.12" 25 | deepdiff = "^5.5.0" 26 | 27 | [build-system] 28 | requires = ["poetry-core>=1.0.0"] 29 | build-backend = "poetry.core.masonry.api" 30 | 31 | [tool.isort] 32 | profile = "black" 33 | 34 | [tool.poetry.scripts] 35 | hcert = "hcert.cli:main" 36 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | - push 5 | - pull_request 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | python-version: 13 | - "3.9" 14 | - "3.10" 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Set up Python ${{ matrix.python-version }} 18 | uses: actions/setup-python@v2 19 | with: 20 | python-version: ${{ matrix.python-version }} 21 | - name: Get full python version 22 | id: full-python-version 23 | run: | 24 | echo ::set-output name=version::$(python -c "import sys; print('-'.join(str(v) for v in sys.version_info[:3]))") 25 | - name: Set up cache 26 | uses: actions/cache@v2 27 | with: 28 | path: .venv 29 | key: ${{ runner.os }}-venv-${{ steps.full-python-version.outputs.version }}-${{ hashFiles('**/poetry.lock') }} 30 | - name: Install and set up Poetry 31 | run: | 32 | pip install poetry 33 | poetry config virtualenvs.in-project true 34 | - name: Install dependencies 35 | run: poetry install 36 | - name: Test 37 | run: | 38 | poetry run pytest -v --black --isort 39 | -------------------------------------------------------------------------------- /hcert/utils.py: -------------------------------------------------------------------------------- 1 | from cryptography import x509 2 | from cryptography.hazmat.backends import default_backend 3 | from cryptography.hazmat.primitives import hashes 4 | from cryptography.hazmat.primitives.asymmetric import ec, rsa 5 | from cryptojwt.jwk.ec import ECKey 6 | from cryptojwt.jwk.rsa import RSAKey 7 | from cryptojwt.jwk.x509 import import_public_key_from_pem_data 8 | from cryptojwt.utils import b64e 9 | 10 | 11 | def pem_to_jwk_dict(pem_data: str): 12 | """Read PEM certificate and return JWK dictionary""" 13 | public_key = import_public_key_from_pem_data(pem_data) 14 | if isinstance(public_key, rsa.RSAPublicKey): 15 | jwk = RSAKey().load_key(public_key) 16 | elif isinstance(public_key, ec.EllipticCurvePublicKey): 17 | jwk = ECKey().load_key(public_key) 18 | else: 19 | raise ValueError("Unknown key type") 20 | jwk_dict = jwk.serialize() 21 | cert = x509.load_pem_x509_certificate(pem_data.encode(), default_backend()) 22 | fp = cert.fingerprint(hashes.SHA256()) 23 | jwk_dict["kid"] = b64e(fp[:8]).decode() 24 | jwk_dict["x5t#S256"] = b64e(fp).decode() 25 | jwk_dict["x5a"] = { 26 | "subject": cert.subject.rfc4514_string(), 27 | "issuer": cert.issuer.rfc4514_string(), 28 | "serial": cert.serial_number, 29 | } 30 | return jwk_dict 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2021, Kirei AB 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /tools/cwtdump.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import binascii 3 | 4 | import cbor2 5 | from cose.headers import CoseHeaderAttribute 6 | from cryptojwt.utils import b64e 7 | 8 | from hcert.cwt import CwtClaims 9 | 10 | 11 | def print_header(hdr): 12 | if isinstance(hdr, dict): 13 | for k, v in hdr.items(): 14 | attr = CoseHeaderAttribute.from_id(k) 15 | if isinstance(v, bytes): 16 | v = "{hex}" + binascii.hexlify(v).decode() 17 | print(f" {k} ({attr.fullname}) = {v}") 18 | 19 | 20 | def print_message(msg): 21 | if isinstance(msg, dict): 22 | for k, v in msg.items(): 23 | try: 24 | claim = CwtClaims(k).name.lower() 25 | except ValueError: 26 | claim = None 27 | if isinstance(v, bytes): 28 | v = "{hex}" + binascii.hexlify(v).decode() 29 | print(f" {k} ({claim}) = {v}") 30 | 31 | 32 | def main(): 33 | 34 | parser = argparse.ArgumentParser(description="CWT dump tool") 35 | 36 | parser.add_argument( 37 | "file", 38 | help="File with CWT contents", 39 | ) 40 | 41 | args = parser.parse_args() 42 | 43 | with open(args.file, "rb") as cwt_file: 44 | cwt_bytes = cwt_file.read() 45 | 46 | cwt_cbor = cbor2.loads(cwt_bytes) 47 | 48 | phdr = cbor2.loads(cwt_cbor.value[0]) if cwt_cbor.value[0] else None 49 | try: 50 | uhdr = cbor2.loads(cwt_cbor.value[1]) if cwt_cbor.value[1] else None 51 | except TypeError as exc: 52 | print("Exception:", exc) 53 | print("Unprotected header:", cwt_cbor.value[1]) 54 | uhdr = None 55 | message = cbor2.loads(cwt_cbor.value[2]) 56 | signature = cwt_cbor.value[3] 57 | 58 | print("Protected header:") 59 | print_header(phdr) 60 | 61 | print("Unprotected header:") 62 | print_header(uhdr) 63 | 64 | print("Message:") 65 | print_message(message) 66 | 67 | print("Signature:", b64e(signature).decode()) 68 | 69 | 70 | if __name__ == "__main__": 71 | main() 72 | -------------------------------------------------------------------------------- /test/test_hcert.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from cryptojwt.jwk.ec import new_ec_key 4 | 5 | from hcert.cwt import cosekey_from_jwk_dict 6 | from hcert.hcert import sign, verify 7 | from hcert.optical import compress_and_encode, decode_and_decompress, save_qrcode 8 | 9 | 10 | def gen_keypair(): 11 | 12 | jwk = new_ec_key(crv="P-256") 13 | 14 | private_key_dict = jwk.serialize(private=True) 15 | public_key_dict = jwk.serialize(private=False) 16 | 17 | private_key = cosekey_from_jwk_dict(private_key_dict, private=True) 18 | public_key = cosekey_from_jwk_dict(public_key_dict, private=False) 19 | 20 | return private_key, public_key 21 | 22 | 23 | def test_sign1_verify(): 24 | 25 | private_key, public_key = gen_keypair() 26 | 27 | issuer = "hello" 28 | ttl = 3600 29 | payload = {"test": True} 30 | 31 | signed_data = sign(private_key, issuer, ttl, payload) 32 | res = verify(signed_data, [public_key]) 33 | 34 | assert res.eu_dgc_v1.get("test") is True 35 | assert res.expired is False 36 | 37 | 38 | def test_sign_verify(): 39 | 40 | private_key, public_key = gen_keypair() 41 | 42 | issuer = "hello" 43 | ttl = 3600 44 | payload = {"test": True} 45 | 46 | signed_data = sign(private_key, issuer, ttl, payload, sign1=False) 47 | res = verify(signed_data, [public_key]) 48 | 49 | assert res.eu_dgc_v1.get("test") is True 50 | assert res.expired is False 51 | 52 | 53 | def test_sign1_verify_unprotected_kid(): 54 | 55 | private_key, public_key = gen_keypair() 56 | 57 | issuer = "hello" 58 | ttl = 3600 59 | payload = {"test": True} 60 | 61 | signed_data = sign(private_key, issuer, ttl, payload, kid_protected=False) 62 | res = verify(signed_data, [public_key]) 63 | 64 | assert res.eu_dgc_v1.get("test") is True 65 | assert res.expired is False 66 | 67 | 68 | def test_optical(): 69 | 70 | payload = os.urandom(1024) 71 | 72 | e = compress_and_encode(payload) 73 | d = decode_and_decompress(e) 74 | 75 | assert d == payload 76 | 77 | 78 | def test_qr(): 79 | 80 | private_key, public_key = gen_keypair() 81 | 82 | issuer = "hello" 83 | ttl = 3600 84 | payload = {"test": True} 85 | 86 | signed_data = sign(private_key, issuer, ttl, payload) 87 | img = save_qrcode(signed_data) 88 | assert len(img) 89 | -------------------------------------------------------------------------------- /hcert/optical.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import logging 3 | import zlib 4 | from typing import Optional 5 | 6 | import base45 7 | import qrcode 8 | import qrcode.image.pil 9 | import qrcode.image.svg 10 | import qrcode.util 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | def compress_and_encode(data: bytes) -> bytes: 16 | compressed_data = zlib.compress(data, level=zlib.Z_BEST_COMPRESSION) 17 | encoded_compressed_data = base45.b45encode(compressed_data) 18 | logger.debug( 19 | "Uncompressed data: %d bytes, %s", 20 | len(data), 21 | binascii.hexlify(data).decode(), 22 | ) 23 | logger.debug( 24 | "Compressed data: %d bytes, %s", 25 | len(compressed_data), 26 | binascii.hexlify(compressed_data).decode(), 27 | ) 28 | logger.debug( 29 | "Encoded compressed data: %d bytes, %s", 30 | len(encoded_compressed_data), 31 | binascii.hexlify(encoded_compressed_data).decode(), 32 | ) 33 | return encoded_compressed_data 34 | 35 | 36 | def decode_and_decompress(data: bytes) -> bytes: 37 | decoded_data = base45.b45decode(data) 38 | decompressed_data = zlib.decompress(decoded_data) 39 | logger.debug( 40 | "Uncompressed data: %d bytes, %s", 41 | len(decompressed_data), 42 | binascii.hexlify(decompressed_data).decode(), 43 | ) 44 | return decompressed_data 45 | 46 | 47 | def save_qrcode(payload: bytes, filename: Optional[str] = None) -> bytes: 48 | """Save CWT as QR Code""" 49 | logger.debug("Encoding %d bytes for QR", len(payload)) 50 | qr_data = b"HC1:" + compress_and_encode(payload) 51 | logger.info("QR data: %s", qr_data) 52 | qr = qrcode.QRCode( 53 | version=None, 54 | error_correction=qrcode.constants.ERROR_CORRECT_Q, 55 | box_size=4, 56 | border=4, 57 | ) 58 | if filename is None or filename.endswith(".png"): 59 | image_factory = qrcode.image.pil.PilImage 60 | elif filename.endswith(".svg"): 61 | image_factory = qrcode.image.svg.SvgImage 62 | else: 63 | raise ValueError("Unknown QRcode image format") 64 | qr.add_data(qr_data, optimize=0) 65 | assert qr.data_list[0].mode == qrcode.util.MODE_ALPHA_NUM 66 | qr.make(fit=True) 67 | img = qr.make_image(image_factory=image_factory) 68 | if filename: 69 | with open(filename, "wb") as qr_file: 70 | img.save(qr_file) 71 | logger.info("Wrote %d bytes as QR to %s", len(qr_data), filename) 72 | else: 73 | return img.tobytes() 74 | -------------------------------------------------------------------------------- /hcert/hcert.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import time 3 | from dataclasses import dataclass 4 | from datetime import datetime 5 | from enum import Enum 6 | from typing import List 7 | 8 | import cose.algorithms 9 | from cose.keys.cosekey import CoseKey 10 | from cryptojwt.utils import b64e 11 | 12 | from .cwt import CWT, CwtClaims 13 | 14 | SIGN_ALG = cose.algorithms.Es256 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | 19 | class HealthCertificateClaims(Enum): 20 | EU_DCC_V1 = 1 21 | 22 | 23 | @dataclass 24 | class HcertVerifyResult: 25 | kid: bytes 26 | key: CoseKey 27 | iss: str 28 | iat: datetime 29 | exp: datetime 30 | expired: bool 31 | eu_dgc_v1: dict 32 | 33 | 34 | def sign( 35 | private_key: CoseKey, 36 | issuer: str, 37 | ttl: int, 38 | payload: dict, 39 | content: HealthCertificateClaims = HealthCertificateClaims.EU_DCC_V1, 40 | kid_protected: bool = True, 41 | sign1: bool = True, 42 | ) -> bytes: 43 | """Create signed HCERT""" 44 | 45 | claims = { 46 | CwtClaims.HCERT.value: {content.value: payload}, 47 | } 48 | cwt = CWT.from_dict(claims=claims, issuer=issuer, ttl=ttl) 49 | cwt_bytes = cwt.sign( 50 | private_key=private_key, alg=SIGN_ALG, kid_protected=kid_protected, sign1=sign1 51 | ) 52 | 53 | logger.debug("Raw signed CWT: %d bytes", len(cwt_bytes)) 54 | 55 | return cwt_bytes 56 | 57 | 58 | def verify(signed_data: bytes, public_keys: List[CoseKey]) -> dict: 59 | 60 | now = int(time.time()) 61 | cwt = CWT.from_bytes(signed_data=signed_data, public_keys=public_keys) 62 | 63 | if (iss := cwt.claims.get(CwtClaims.ISS.value)) is not None: 64 | logger.debug("Signatured issued by: %s", iss) 65 | 66 | logger.debug( 67 | "Signature verified by: %s (%s)", 68 | b64e(cwt.key.kid).decode(), 69 | cwt.key.issuer if hasattr(cwt.key, "issuer") else None, 70 | ) 71 | 72 | if (iat := cwt.claims.get(CwtClaims.IAT.value)) is not None: 73 | logger.debug("Signatured issued at: %s", datetime.fromtimestamp(iat)) 74 | 75 | if (exp := cwt.claims.get(CwtClaims.EXP.value)) is not None: 76 | if exp > now: 77 | logger.debug("Signatured expires at: %s", datetime.fromtimestamp(exp)) 78 | expired = False 79 | else: 80 | logger.debug("Signatured expired at: %s", datetime.fromtimestamp(exp)) 81 | expired = True 82 | 83 | hcert = cwt.claims.get(CwtClaims.HCERT.value) 84 | eu_dgc_v1 = hcert.get(HealthCertificateClaims.EU_DCC_V1.value) 85 | 86 | return HcertVerifyResult( 87 | iss=iss, 88 | key=cwt.key, 89 | kid=cwt.key.kid, 90 | iat=datetime.fromtimestamp(iat) if iat else None, 91 | exp=datetime.fromtimestamp(exp) if exp else None, 92 | expired=expired, 93 | eu_dgc_v1=eu_dgc_v1, 94 | ) 95 | -------------------------------------------------------------------------------- /tools/dgc_test.py: -------------------------------------------------------------------------------- 1 | """Tool to process DCC test data""" 2 | 3 | import argparse 4 | import datetime 5 | import json 6 | import logging 7 | import sys 8 | from datetime import timezone 9 | 10 | from cryptojwt.utils import b64e 11 | from deepdiff import DeepDiff 12 | 13 | from hcert.cwt import cosekey_from_jwk_dict 14 | from hcert.hcert import verify 15 | from hcert.optical import decode_and_decompress 16 | from hcert.utils import pem_to_jwk_dict 17 | 18 | PEM_CERT_START_DELIMITER = "-----BEGIN CERTIFICATE-----" 19 | PEM_CERT_END_DELIMITER = "-----END CERTIFICATE-----" 20 | 21 | TIMESTAMP_ISO8601_EXTENDED = "%Y-%m-%dT%H:%M:%S.%fZ" 22 | 23 | logger = logging.getLogger(__name__) 24 | 25 | 26 | def json_datetime_encoder(o): 27 | """Encoding JSON with datetime""" 28 | if isinstance(o, (datetime.date, datetime.datetime)): 29 | return o.astimezone(timezone.utc).strftime(TIMESTAMP_ISO8601_EXTENDED) 30 | 31 | 32 | def canonicalize_dict(d: dict) -> dict: 33 | """Canonicalize dict using JSON""" 34 | return json.loads( 35 | json.dumps(d, indent=4, sort_keys=True, default=json_datetime_encoder) 36 | ) 37 | 38 | 39 | def main(): 40 | """Main function""" 41 | 42 | parser = argparse.ArgumentParser(description="DCC test eval") 43 | 44 | parser.add_argument( 45 | "testfile", 46 | metavar="filename", 47 | help="Test filename", 48 | ) 49 | parser.add_argument( 50 | "--verbose", 51 | action="store_true", 52 | help="Verbose output", 53 | required=False, 54 | ) 55 | parser.add_argument( 56 | "--debug", 57 | action="store_true", 58 | help="Debug output", 59 | required=False, 60 | ) 61 | 62 | args = parser.parse_args() 63 | 64 | if args.debug: 65 | logging.basicConfig(level=logging.DEBUG) 66 | elif args.verbose: 67 | logging.basicConfig(level=logging.INFO) 68 | else: 69 | logging.basicConfig(level=logging.WARNING) 70 | 71 | with open(args.testfile) as input_file: 72 | testdata = json.load(input_file) 73 | 74 | certificate_data = testdata["TESTCTX"]["CERTIFICATE"] 75 | 76 | cert_pem = ( 77 | PEM_CERT_START_DELIMITER 78 | + "\n" 79 | + certificate_data 80 | + "\n" 81 | + PEM_CERT_END_DELIMITER 82 | ) 83 | jwk_dict = pem_to_jwk_dict(cert_pem) 84 | public_key = cosekey_from_jwk_dict(jwk_dict, private=False) 85 | 86 | reference_payload = testdata.get("JSON") 87 | 88 | optical_payload = testdata["PREFIX"] 89 | assert optical_payload.startswith("HC1:") 90 | 91 | if (base45_payload := testdata.get("BASE45")) : 92 | assert optical_payload[4:] == base45_payload 93 | else: 94 | base45_payload = optical_payload[4:] 95 | 96 | signed_data = decode_and_decompress(base45_payload.encode()) 97 | 98 | res = verify(signed_data=signed_data, public_keys=[public_key]) 99 | logger.info("Signature verified") 100 | 101 | if res.eu_dcc_v1 is None: 102 | logger.warning("No EU DCC version 1 found in payload") 103 | sys.exit(-1) 104 | 105 | if reference_payload: 106 | reference_serialized = canonicalize_dict(reference_payload) 107 | verified_serialized = canonicalize_dict(res.eu_dgc_v1) 108 | ddiff = DeepDiff(reference_serialized, verified_serialized) 109 | 110 | if ddiff: 111 | logger.error("Reference data does not match payload") 112 | print(json.dumps(ddiff, indent=4)) 113 | sys.exit(-1) 114 | else: 115 | logger.info("Reference data match payload") 116 | logger.info("Reference payload: %s", reference_serialized) 117 | logger.info("Verified payload: %s", verified_serialized) 118 | sys.exit(0) 119 | else: 120 | logger.warning("Reference data not checked") 121 | 122 | 123 | if __name__ == "__main__": 124 | main() 125 | -------------------------------------------------------------------------------- /tools/scanner.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import json 3 | import logging 4 | import time 5 | from typing import Optional 6 | 7 | import serial 8 | from cryptojwt.utils import b64d, b64e 9 | 10 | from hcert.cwt import cosekey_from_jwk_dict 11 | from hcert.hcert import verify 12 | from hcert.optical import decode_and_decompress 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | DEFAULT_SCANNER_PORT = "/dev/tty.usbmodem1143101" 17 | 18 | 19 | class SerialScanner: 20 | """Serial scanner""" 21 | 22 | def __init__(self, port: str, baudrate: Optional[int] = None) -> None: 23 | self.scanner = serial.Serial(port=port, baudrate=baudrate or 115200) 24 | 25 | def read(self) -> Optional[bytes]: 26 | """Read data from scanner""" 27 | waiting = self.scanner.inWaiting() 28 | if waiting > 0: 29 | data = self.scanner.read(waiting) 30 | return data 31 | return None 32 | 33 | def write(self, data: bytes) -> None: 34 | """Write data to scanner""" 35 | self.scanner.write(data) 36 | 37 | 38 | class AccessIsAtr110(SerialScanner): 39 | def __init__(self, port: str, baudrate: Optional[int] = None) -> None: 40 | super().__init__(port, baudrate) 41 | self.send_modify_command("AISRDS", 1) 42 | self.send_modify_command("ALLENA", 1) 43 | 44 | def send_command(self, command: str) -> bytes: 45 | """Send command to scanner, return any resulting data""" 46 | prefix = [0x16, 0x4D, 0x0D] 47 | data = bytes(prefix) + command.encode() 48 | self.write(data) 49 | return self.read() 50 | 51 | def send_modify_command( 52 | self, command: str, parameter=None, permanent: bool = False 53 | ): 54 | """Send modify command to scanner""" 55 | if permanent: 56 | # modify a setting permanently 57 | terminator = "." 58 | else: 59 | # modify a setting temporarily 60 | terminator = "!" 61 | if parameter is not None: 62 | self.send_command(command + str(parameter) + terminator) 63 | else: 64 | self.send_command(command + terminator) 65 | 66 | 67 | def process_hc1_cwt(signed_data: bytes, public_keys): 68 | 69 | res = verify(signed_data=signed_data, public_keys=public_keys) 70 | 71 | logger.info("Signatured issued by: %s", res.iss) 72 | logger.info("Signature verified by: %s", b64e(res.kid).decode()) 73 | logger.info("Signatured issued at: %s", res.iat) 74 | 75 | if res.expired: 76 | logger.warning("Signatured expired at: %s", res.exp) 77 | else: 78 | logger.info("Signatured expires at: %s", res.exp) 79 | 80 | if res.eu_dgc_v1 is None: 81 | logger.warning("No EU HCERT version 1 found in payload") 82 | 83 | logger.info("Verified payload: %s", json.dumps(res.eu_dgc_v1, indent=4)) 84 | 85 | 86 | def main(): 87 | parser = argparse.ArgumentParser( 88 | description="Electronic Health Certificate Optical Verifier" 89 | ) 90 | parser.add_argument( 91 | "--port", 92 | metavar="port", 93 | help="Scanner serial port", 94 | default=DEFAULT_SCANNER_PORT, 95 | ) 96 | parser.add_argument("--jwks", metavar="filename", help="JWKS filename") 97 | parser.add_argument("--input", metavar="filename", help="Raw input filename") 98 | parser.add_argument("--output", metavar="filename", help="Raw output filename") 99 | parser.add_argument( 100 | "--debug", 101 | action="store_true", 102 | help="Debug output", 103 | required=False, 104 | ) 105 | args = parser.parse_args() 106 | 107 | if args.debug: 108 | logging.basicConfig(level=logging.DEBUG) 109 | else: 110 | logging.basicConfig(level=logging.INFO) 111 | 112 | public_keys = [] 113 | 114 | if args.jwks: 115 | with open(args.jwks) as jwks_file: 116 | jwks = json.load(jwks_file) 117 | for jwk_dict in jwks.get("keys", []): 118 | key = cosekey_from_jwk_dict(jwk_dict, private=False) 119 | public_keys.append(key) 120 | 121 | if args.input: 122 | with open(args.input, "rb") as input_file: 123 | data = input_file.read() 124 | process_hc1_cwt(data, public_keys) 125 | return 126 | 127 | scanner = AccessIsAtr110(port=args.port) 128 | print("Waiting for data from scanner...") 129 | count = 1 130 | while True: 131 | data = scanner.read() 132 | if data: 133 | s = data.decode() 134 | if s.startswith("HC1:"): 135 | print(data) 136 | signed_data = decode_and_decompress(data[4:]) 137 | if args.output: 138 | filename = f"{args.output}-{count}" 139 | with open(filename, "wb") as output_file: 140 | output_file.write(signed_data) 141 | count += 1 142 | print("Scanned data written to", filename) 143 | if args.jwks: 144 | process_hc1_cwt(signed_data, public_keys) 145 | time.sleep(1) 146 | 147 | 148 | if __name__ == "__main__": 149 | main() 150 | -------------------------------------------------------------------------------- /hcert/cli.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import binascii 3 | import json 4 | import logging 5 | 6 | from cryptojwt.utils import b64d, b64e 7 | 8 | from .cwt import cosekey_from_jwk_dict, read_cosekey 9 | from .hcert import sign, verify 10 | from .optical import save_qrcode 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | def command_sign(args: argparse.Namespace): 16 | """Create signed EHC""" 17 | 18 | private_key = read_cosekey(args.key, private=True) 19 | if args.kid: 20 | private_key.kid = b64d(args.kid.encode()) 21 | 22 | with open(args.input, "rt") as input_file: 23 | input_data = input_file.read() 24 | 25 | logger.info("Input JSON data: %d bytes", len(input_data)) 26 | 27 | eu_dgc_v1 = json.loads(input_data) 28 | cwt_bytes = sign( 29 | private_key=private_key, 30 | payload=eu_dgc_v1, 31 | issuer=args.issuer, 32 | ttl=args.ttl, 33 | sign1=args.sign1, 34 | ) 35 | logger.info("Raw signed CWT: %d bytes", len(cwt_bytes)) 36 | 37 | if args.output: 38 | with open(args.output, "wb") as output_file: 39 | output_file.write(cwt_bytes) 40 | else: 41 | logger.info("Output: %s", binascii.hexlify(cwt_bytes).decode()) 42 | 43 | if args.qrcode: 44 | save_qrcode(cwt_bytes, args.qrcode) 45 | 46 | 47 | def command_verify(args: argparse.Namespace): 48 | """Verify signed EHC""" 49 | 50 | public_keys = [] 51 | if args.jwks: 52 | with open(args.jwks) as jwks_file: 53 | jwks = json.load(jwks_file) 54 | for jwk_dict in jwks.get("keys", []): 55 | key = cosekey_from_jwk_dict(jwk_dict, private=False) 56 | key.issuer = jwk_dict.get("issuer") 57 | public_keys.append(key) 58 | elif args.key: 59 | public_keys = [read_cosekey(args.key, private=False)] 60 | 61 | if args.kid: 62 | public_key.kid = b64d(args.kid.encode()) 63 | 64 | with open(args.input, "rb") as input_file: 65 | signed_data = input_file.read() 66 | 67 | res = verify(signed_data=signed_data, public_keys=public_keys) 68 | 69 | logger.info("Signatured issued by: %s", res.iss) 70 | logger.info( 71 | "Signature verified by: %s (%s)", 72 | b64e(res.kid).decode(), 73 | res.key.issuer if hasattr(res.key, "issuer") else None, 74 | ) 75 | logger.info("Signatured issued at: %s", res.iat) 76 | 77 | if res.expired: 78 | logger.warning("Signatured expired at: %s", res.exp) 79 | else: 80 | logger.info("Signatured expires at: %s", res.exp) 81 | 82 | if res.eu_dgc_v1 is None: 83 | logger.warning("No EU DCC version 1 found in payload") 84 | 85 | if args.output: 86 | with open(args.output, "wt") as output_file: 87 | json.dump(res.eu_dgc_v1, output_file, indent=4) 88 | else: 89 | logger.info("Verified payload: %s", json.dumps(res.eu_dgc_v1, indent=4)) 90 | 91 | 92 | def main(): 93 | """Main function""" 94 | 95 | parser = argparse.ArgumentParser( 96 | description="Electronic Health Certificate signer/verifier" 97 | ) 98 | 99 | parser.add_argument( 100 | "--verbose", 101 | action="store_true", 102 | help="Verbose output", 103 | required=False, 104 | ) 105 | parser.add_argument( 106 | "--debug", 107 | action="store_true", 108 | help="Debug output", 109 | required=False, 110 | ) 111 | 112 | subparsers = parser.add_subparsers(dest="command", required=True) 113 | 114 | parser_sign = subparsers.add_parser("sign", help="Sign health cert") 115 | parser_sign.set_defaults(func=command_sign) 116 | parser_sign.add_argument( 117 | "--key", metavar="filename", help="Private JWK filename", required=True 118 | ) 119 | parser_sign.add_argument( 120 | "--issuer", 121 | metavar="issuer", 122 | help="Signature issuer (ISO 3166 country code)", 123 | required=False, 124 | ) 125 | parser_sign.add_argument( 126 | "--ttl", 127 | metavar="seconds", 128 | help="Signature TTL", 129 | type=int, 130 | required=False, 131 | ) 132 | parser_sign.add_argument( 133 | "--input", 134 | metavar="filename", 135 | help="JSON-encoded payload", 136 | required=True, 137 | ) 138 | parser_sign.add_argument( 139 | "--output", 140 | metavar="filename", 141 | help="Binary CWT output", 142 | required=False, 143 | ) 144 | parser_sign.add_argument( 145 | "--kid", 146 | metavar="id", 147 | help="Key identifier (base64url encoded)", 148 | required=False, 149 | ) 150 | parser_sign.add_argument( 151 | "--qrcode", 152 | metavar="filename", 153 | help="QR output", 154 | required=False, 155 | ) 156 | parser_sign.add_argument( 157 | "--sign1", 158 | action="store_true", 159 | help="Sign with COSE_Sign1", 160 | default=True, 161 | ) 162 | 163 | parser_verify = subparsers.add_parser("verify", help="Verify signed cert") 164 | parser_verify.set_defaults(func=command_verify) 165 | parser_verify.add_argument("--key", metavar="filename", help="Public JWK filename") 166 | parser_verify.add_argument( 167 | "--jwks", metavar="filename", help="Public JWKS filename" 168 | ) 169 | parser_verify.add_argument( 170 | "--input", 171 | metavar="filename", 172 | help="Compressed CBOR input", 173 | required=True, 174 | ) 175 | parser_verify.add_argument( 176 | "--output", 177 | metavar="filename", 178 | help="JSON-encoded payload", 179 | required=False, 180 | ) 181 | parser_verify.add_argument( 182 | "--kid", 183 | metavar="id", 184 | help="Key identifier (base64url encoded)", 185 | required=False, 186 | ) 187 | 188 | args = parser.parse_args() 189 | 190 | if args.debug: 191 | logging.basicConfig(level=logging.DEBUG) 192 | elif args.verbose: 193 | logging.basicConfig(level=logging.INFO) 194 | else: 195 | logging.basicConfig(level=logging.WARNING) 196 | 197 | args.func(args) 198 | 199 | 200 | if __name__ == "__main__": 201 | main() 202 | -------------------------------------------------------------------------------- /hcert/cwt.py: -------------------------------------------------------------------------------- 1 | import json 2 | import time 3 | from base64 import b64decode 4 | from enum import Enum 5 | from typing import Dict, List, Optional 6 | 7 | import cbor2 8 | import cose.algorithms 9 | import cose.headers 10 | import cose.keys.curves 11 | import cose.keys.keyops 12 | import cryptojwt.exception 13 | from cose.keys.cosekey import CoseKey 14 | from cose.keys.ec2 import EC2Key 15 | from cose.keys.rsa import RSAKey 16 | from cose.messages import CoseMessage 17 | from cose.messages.sign1message import Sign1Message 18 | from cose.messages.signer import CoseSignature 19 | from cose.messages.signmessage import SignMessage 20 | from cryptojwt.jwk.ec import ECKey 21 | from cryptojwt.jwk.x509 import ( 22 | import_private_key_from_pem_file, 23 | import_public_key_from_cert_file, 24 | ) 25 | from cryptojwt.utils import b64d 26 | 27 | 28 | class CoseContentTypes(Enum): 29 | CWT = 61 30 | 31 | 32 | class CwtClaims(Enum): 33 | ISS = 1 34 | SUB = 2 35 | AUD = 3 36 | EXP = 4 37 | NBF = 5 38 | IAT = 6 39 | CTI = 7 40 | HCERT = -260 41 | 42 | 43 | def read_cosekey(filename: str, private: bool = True) -> CoseKey: 44 | """Read key and return CoseKey""" 45 | if filename.endswith(".json"): 46 | with open(filename, "rt") as jwk_file: 47 | jwk_dict = json.load(jwk_file) 48 | elif filename.endswith(".key"): 49 | key = import_private_key_from_pem_file(filename) 50 | jwk = ECKey() 51 | jwk.load_key(key) 52 | jwk_dict = jwk.serialize(private=private) 53 | elif filename.endswith(".crt"): 54 | if private: 55 | raise ValueError("No private keys in certificates") 56 | key = import_public_key_from_cert_file(filename) 57 | jwk = ECKey() 58 | jwk.load_key(key) 59 | jwk_dict = jwk.serialize(private=private) 60 | else: 61 | raise ValueError("Unknown key format") 62 | return cosekey_from_jwk_dict(jwk_dict, private) 63 | 64 | 65 | def cosekey_from_jwk_dict(jwk_dict: Dict, private: bool = True) -> CoseKey: 66 | """Read key and return CoseKey""" 67 | 68 | if jwk_dict["kty"] == "EC": 69 | 70 | if jwk_dict["crv"] != "P-256": 71 | raise ValueError("Only P-256 supported") 72 | 73 | if private: 74 | key = EC2Key( 75 | crv=cose.keys.curves.P256, 76 | x=b64d(jwk_dict["x"].encode()), 77 | y=b64d(jwk_dict["y"].encode()), 78 | d=b64d(jwk_dict["d"].encode()), 79 | ) 80 | else: 81 | key = EC2Key( 82 | crv=cose.keys.curves.P256, 83 | x=b64d(jwk_dict["x"].encode()), 84 | y=b64d(jwk_dict["y"].encode()), 85 | ) 86 | 87 | elif jwk_dict["kty"] == "RSA": 88 | 89 | if private: 90 | key = RSAKey( 91 | e=b64d(jwk_dict["e"].encode()), 92 | n=b64d(jwk_dict["n"].encode()), 93 | p=b64d(jwk_dict["p"].encode()), 94 | q=b64d(jwk_dict["q"].encode()), 95 | d=b64d(jwk_dict["d"].encode()), 96 | ) 97 | else: 98 | key = RSAKey( 99 | e=b64d(jwk_dict["e"].encode()), 100 | n=b64d(jwk_dict["n"].encode()), 101 | ) 102 | 103 | else: 104 | raise ValueError("Unsupport key type: " + jwk_dict["kty"]) 105 | 106 | if private: 107 | key.key_ops = [cose.keys.keyops.SignOp, cose.keys.keyops.VerifyOp] 108 | else: 109 | key.key_ops = [cose.keys.keyops.VerifyOp] 110 | 111 | if "kid" in jwk_dict: 112 | kid = jwk_dict["kid"] 113 | try: 114 | key.kid = b64d(kid.encode()) 115 | except cryptojwt.exception.BadSyntax: 116 | key.kid = b64decode(kid.encode()) 117 | 118 | return key 119 | 120 | 121 | class CWT(object): 122 | 123 | claims_map = CwtClaims 124 | 125 | def __init__(self, *args, **kwargs) -> None: 126 | self.protected_header = kwargs.get("protected_header", {}) 127 | self.unprotected_header = kwargs.get("unprotected_header", {}) 128 | self.claims = kwargs.get("claims", {}) 129 | self.key = kwargs.get("key") 130 | 131 | def sign( 132 | self, 133 | private_key: CoseKey, 134 | alg: cose.algorithms.CoseAlgorithm, 135 | kid_protected: bool = True, 136 | sign1: bool = True, 137 | ) -> bytes: 138 | self.protected_header.update( 139 | { 140 | cose.headers.Algorithm: alg, 141 | cose.headers.ContentType: CoseContentTypes.CWT.value, 142 | } 143 | ) 144 | if kid_protected: 145 | self.protected_header[cose.headers.KID] = private_key.kid 146 | else: 147 | self.unprotected_header[cose.headers.KID] = private_key.kid 148 | if sign1: 149 | cose_msg = Sign1Message( 150 | phdr=self.protected_header if len(self.protected_header) else None, 151 | uhdr=self.unprotected_header if len(self.unprotected_header) else None, 152 | payload=cbor2.dumps(self.claims), 153 | ) 154 | cose_msg.key = private_key 155 | else: 156 | signers = [ 157 | CoseSignature( 158 | phdr=self.protected_header if len(self.protected_header) else None, 159 | uhdr=self.unprotected_header 160 | if len(self.unprotected_header) 161 | else None, 162 | key=private_key, 163 | ) 164 | ] 165 | cose_msg = SignMessage( 166 | phdr={cose.headers.ContentType: CoseContentTypes.CWT.value}, 167 | uhdr=self.unprotected_header if len(self.unprotected_header) else None, 168 | payload=cbor2.dumps(self.claims), 169 | signers=signers, 170 | ) 171 | return cose_msg.encode() 172 | 173 | @classmethod 174 | def from_dict( 175 | cls, claims: Dict = {}, issuer: Optional[str] = None, ttl: Optional[int] = None 176 | ): 177 | 178 | now = int(time.time()) 179 | cwt_claims = {CwtClaims.IAT.value: now} 180 | if ttl is not None: 181 | cwt_claims[CwtClaims.EXP.value] = now + ttl 182 | if issuer is not None: 183 | cwt_claims[CwtClaims.ISS.value] = issuer 184 | for k, v in claims.items(): 185 | if isinstance(k, str): 186 | k = cls.claims_map[k.upper()].value 187 | cwt_claims[k] = v 188 | return cls(claims=cwt_claims) 189 | 190 | @classmethod 191 | def from_bytes(cls, signed_data: bytes, public_keys: List[CoseKey]): 192 | cose_msg = CoseMessage.decode(signed_data) 193 | 194 | if isinstance(cose_msg, Sign1Message): 195 | messages = [cose_msg] 196 | elif isinstance(cose_msg, SignMessage): 197 | messages = cose_msg.signers 198 | else: 199 | raise RuntimeError("Unsupported COSE message format") 200 | 201 | signers = [] 202 | for msg in messages: 203 | kid = msg.phdr.get(cose.headers.KID) 204 | if kid is None: 205 | kid = msg.uhdr.get(cose.headers.KID) 206 | signers.append((kid, msg)) 207 | 208 | verified_key = None 209 | for key in public_keys: 210 | for kid, msg in signers: 211 | if key.kid == kid: 212 | msg.key = key 213 | if msg.verify_signature(): 214 | verified_key = key 215 | break 216 | if verified_key: 217 | break 218 | else: 219 | raise RuntimeError("Bad signature") 220 | 221 | return cls( 222 | protected_header=cose_msg.phdr, 223 | unprotected_header=cose_msg.uhdr, 224 | claims=cbor2.loads(cose_msg.payload), 225 | key=verified_key, 226 | ) 227 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | [[package]] 2 | name = "appdirs" 3 | version = "1.4.4" 4 | description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." 5 | category = "dev" 6 | optional = false 7 | python-versions = "*" 8 | 9 | [[package]] 10 | name = "asn1crypto" 11 | version = "1.4.0" 12 | description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" 13 | category = "main" 14 | optional = false 15 | python-versions = "*" 16 | 17 | [[package]] 18 | name = "atomicwrites" 19 | version = "1.4.0" 20 | description = "Atomic file writes." 21 | category = "dev" 22 | optional = false 23 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 24 | 25 | [[package]] 26 | name = "attrs" 27 | version = "21.2.0" 28 | description = "Classes Without Boilerplate" 29 | category = "main" 30 | optional = false 31 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 32 | 33 | [package.extras] 34 | dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit"] 35 | docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] 36 | tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface"] 37 | tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins"] 38 | 39 | [[package]] 40 | name = "base45" 41 | version = "0.3.1" 42 | description = "Base45 Encoder/Decoder" 43 | category = "main" 44 | optional = false 45 | python-versions = ">=3.7,<4.0" 46 | 47 | [[package]] 48 | name = "black" 49 | version = "20.8b1" 50 | description = "The uncompromising code formatter." 51 | category = "dev" 52 | optional = false 53 | python-versions = ">=3.6" 54 | 55 | [package.dependencies] 56 | appdirs = "*" 57 | click = ">=7.1.2" 58 | mypy-extensions = ">=0.4.3" 59 | pathspec = ">=0.6,<1" 60 | regex = ">=2020.1.8" 61 | toml = ">=0.10.1" 62 | typed-ast = ">=1.4.0" 63 | typing-extensions = ">=3.7.4" 64 | 65 | [package.extras] 66 | colorama = ["colorama (>=0.4.3)"] 67 | d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] 68 | 69 | [[package]] 70 | name = "cbor2" 71 | version = "5.4.2" 72 | description = "Pure Python CBOR (de)serializer with extensive tag support" 73 | category = "main" 74 | optional = false 75 | python-versions = ">=3.6" 76 | 77 | [package.extras] 78 | doc = ["sphinx-rtd-theme", "sphinx-autodoc-typehints (>=1.2.0)"] 79 | test = ["pytest", "pytest-cov"] 80 | 81 | [[package]] 82 | name = "certifi" 83 | version = "2021.10.8" 84 | description = "Python package for providing Mozilla's CA Bundle." 85 | category = "main" 86 | optional = false 87 | python-versions = "*" 88 | 89 | [[package]] 90 | name = "certvalidator" 91 | version = "0.11.1" 92 | description = "Validates X.509 certificates and paths" 93 | category = "main" 94 | optional = false 95 | python-versions = "*" 96 | 97 | [package.dependencies] 98 | asn1crypto = ">=0.18.1" 99 | oscrypto = ">=0.16.1" 100 | 101 | [[package]] 102 | name = "cffi" 103 | version = "1.15.0" 104 | description = "Foreign Function Interface for Python calling C code." 105 | category = "main" 106 | optional = false 107 | python-versions = "*" 108 | 109 | [package.dependencies] 110 | pycparser = "*" 111 | 112 | [[package]] 113 | name = "charset-normalizer" 114 | version = "2.0.7" 115 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 116 | category = "main" 117 | optional = false 118 | python-versions = ">=3.5.0" 119 | 120 | [package.extras] 121 | unicode_backport = ["unicodedata2"] 122 | 123 | [[package]] 124 | name = "click" 125 | version = "8.0.3" 126 | description = "Composable command line interface toolkit" 127 | category = "dev" 128 | optional = false 129 | python-versions = ">=3.6" 130 | 131 | [package.dependencies] 132 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 133 | 134 | [[package]] 135 | name = "colorama" 136 | version = "0.4.4" 137 | description = "Cross-platform colored terminal text." 138 | category = "main" 139 | optional = false 140 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 141 | 142 | [[package]] 143 | name = "cose" 144 | version = "0.9.dev7" 145 | description = "CBOR Object Signing and Encryption (COSE) implementation" 146 | category = "main" 147 | optional = false 148 | python-versions = ">=3.6" 149 | 150 | [package.dependencies] 151 | attrs = "*" 152 | cbor2 = "*" 153 | certvalidator = "*" 154 | cryptography = "*" 155 | ecdsa = "*" 156 | 157 | [[package]] 158 | name = "cryptography" 159 | version = "3.4.8" 160 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." 161 | category = "main" 162 | optional = false 163 | python-versions = ">=3.6" 164 | 165 | [package.dependencies] 166 | cffi = ">=1.12" 167 | 168 | [package.extras] 169 | docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] 170 | docstest = ["doc8", "pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] 171 | pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] 172 | sdist = ["setuptools-rust (>=0.11.4)"] 173 | ssh = ["bcrypt (>=3.1.5)"] 174 | test = ["pytest (>=6.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] 175 | 176 | [[package]] 177 | name = "cryptojwt" 178 | version = "1.5.2" 179 | description = "Python implementation of JWT, JWE, JWS and JWK" 180 | category = "main" 181 | optional = false 182 | python-versions = ">=3.6,<4.0" 183 | 184 | [package.dependencies] 185 | cryptography = ">=3.4.6,<4.0.0" 186 | readerwriterlock = ">=1.0.8,<2.0.0" 187 | requests = ">=2.25.1,<3.0.0" 188 | 189 | [[package]] 190 | name = "deepdiff" 191 | version = "5.6.0" 192 | description = "Deep Difference and Search of any Python object/data." 193 | category = "dev" 194 | optional = false 195 | python-versions = ">=3.6" 196 | 197 | [package.dependencies] 198 | ordered-set = "4.0.2" 199 | 200 | [package.extras] 201 | cli = ["click (==7.1.2)", "pyyaml (==5.4)", "toml (==0.10.2)", "clevercsv (==0.6.7)"] 202 | 203 | [[package]] 204 | name = "ecdsa" 205 | version = "0.17.0" 206 | description = "ECDSA cryptographic signature library (pure python)" 207 | category = "main" 208 | optional = false 209 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 210 | 211 | [package.dependencies] 212 | six = ">=1.9.0" 213 | 214 | [package.extras] 215 | gmpy = ["gmpy"] 216 | gmpy2 = ["gmpy2"] 217 | 218 | [[package]] 219 | name = "idna" 220 | version = "3.3" 221 | description = "Internationalized Domain Names in Applications (IDNA)" 222 | category = "main" 223 | optional = false 224 | python-versions = ">=3.5" 225 | 226 | [[package]] 227 | name = "iniconfig" 228 | version = "1.1.1" 229 | description = "iniconfig: brain-dead simple config-ini parsing" 230 | category = "dev" 231 | optional = false 232 | python-versions = "*" 233 | 234 | [[package]] 235 | name = "isort" 236 | version = "5.9.3" 237 | description = "A Python utility / library to sort Python imports." 238 | category = "dev" 239 | optional = false 240 | python-versions = ">=3.6.1,<4.0" 241 | 242 | [package.extras] 243 | pipfile_deprecated_finder = ["pipreqs", "requirementslib"] 244 | requirements_deprecated_finder = ["pipreqs", "pip-api"] 245 | colors = ["colorama (>=0.4.3,<0.5.0)"] 246 | plugins = ["setuptools"] 247 | 248 | [[package]] 249 | name = "jsonschema" 250 | version = "3.2.0" 251 | description = "An implementation of JSON Schema validation for Python" 252 | category = "main" 253 | optional = false 254 | python-versions = "*" 255 | 256 | [package.dependencies] 257 | attrs = ">=17.4.0" 258 | pyrsistent = ">=0.14.0" 259 | six = ">=1.11.0" 260 | 261 | [package.extras] 262 | format = ["idna", "jsonpointer (>1.13)", "rfc3987", "strict-rfc3339", "webcolors"] 263 | format_nongpl = ["idna", "jsonpointer (>1.13)", "webcolors", "rfc3986-validator (>0.1.0)", "rfc3339-validator"] 264 | 265 | [[package]] 266 | name = "mccabe" 267 | version = "0.6.1" 268 | description = "McCabe checker, plugin for flake8" 269 | category = "dev" 270 | optional = false 271 | python-versions = "*" 272 | 273 | [[package]] 274 | name = "mypy-extensions" 275 | version = "0.4.3" 276 | description = "Experimental type system extensions for programs checked with the mypy typechecker." 277 | category = "dev" 278 | optional = false 279 | python-versions = "*" 280 | 281 | [[package]] 282 | name = "ordered-set" 283 | version = "4.0.2" 284 | description = "A set that remembers its order, and allows looking up its items by their index in that order." 285 | category = "dev" 286 | optional = false 287 | python-versions = ">=3.5" 288 | 289 | [[package]] 290 | name = "oscrypto" 291 | version = "1.2.1" 292 | description = "TLS (SSL) sockets, key generation, encryption, decryption, signing, verification and KDFs using the OS crypto libraries. Does not require a compiler, and relies on the OS for patching. Works on Windows, OS X and Linux/BSD." 293 | category = "main" 294 | optional = false 295 | python-versions = "*" 296 | 297 | [package.dependencies] 298 | asn1crypto = ">=1.0.0" 299 | 300 | [[package]] 301 | name = "packaging" 302 | version = "21.0" 303 | description = "Core utilities for Python packages" 304 | category = "dev" 305 | optional = false 306 | python-versions = ">=3.6" 307 | 308 | [package.dependencies] 309 | pyparsing = ">=2.0.2" 310 | 311 | [[package]] 312 | name = "pathspec" 313 | version = "0.9.0" 314 | description = "Utility library for gitignore style pattern matching of file paths." 315 | category = "dev" 316 | optional = false 317 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 318 | 319 | [[package]] 320 | name = "pillow" 321 | version = "8.4.0" 322 | description = "Python Imaging Library (Fork)" 323 | category = "main" 324 | optional = false 325 | python-versions = ">=3.6" 326 | 327 | [[package]] 328 | name = "pluggy" 329 | version = "1.0.0" 330 | description = "plugin and hook calling mechanisms for python" 331 | category = "dev" 332 | optional = false 333 | python-versions = ">=3.6" 334 | 335 | [package.extras] 336 | dev = ["pre-commit", "tox"] 337 | testing = ["pytest", "pytest-benchmark"] 338 | 339 | [[package]] 340 | name = "py" 341 | version = "1.10.0" 342 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 343 | category = "dev" 344 | optional = false 345 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 346 | 347 | [[package]] 348 | name = "pycodestyle" 349 | version = "2.8.0" 350 | description = "Python style guide checker" 351 | category = "dev" 352 | optional = false 353 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 354 | 355 | [[package]] 356 | name = "pycparser" 357 | version = "2.20" 358 | description = "C parser in Python" 359 | category = "main" 360 | optional = false 361 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 362 | 363 | [[package]] 364 | name = "pydocstyle" 365 | version = "6.1.1" 366 | description = "Python docstring style checker" 367 | category = "dev" 368 | optional = false 369 | python-versions = ">=3.6" 370 | 371 | [package.dependencies] 372 | snowballstemmer = "*" 373 | 374 | [package.extras] 375 | toml = ["toml"] 376 | 377 | [[package]] 378 | name = "pyflakes" 379 | version = "2.4.0" 380 | description = "passive checker of Python programs" 381 | category = "dev" 382 | optional = false 383 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 384 | 385 | [[package]] 386 | name = "pylama" 387 | version = "7.7.1" 388 | description = "pylama -- Code audit tool for python" 389 | category = "dev" 390 | optional = false 391 | python-versions = "*" 392 | 393 | [package.dependencies] 394 | mccabe = ">=0.5.2" 395 | pycodestyle = ">=2.3.1" 396 | pydocstyle = ">=2.0.0" 397 | pyflakes = ">=1.5.0" 398 | 399 | [[package]] 400 | name = "pyparsing" 401 | version = "3.0.3" 402 | description = "Python parsing module" 403 | category = "dev" 404 | optional = false 405 | python-versions = ">=3.6" 406 | 407 | [package.extras] 408 | diagrams = ["jinja2", "railroad-diagrams"] 409 | 410 | [[package]] 411 | name = "pyrsistent" 412 | version = "0.18.0" 413 | description = "Persistent/Functional/Immutable data structures" 414 | category = "main" 415 | optional = false 416 | python-versions = ">=3.6" 417 | 418 | [[package]] 419 | name = "pytest" 420 | version = "6.2.5" 421 | description = "pytest: simple powerful testing with Python" 422 | category = "dev" 423 | optional = false 424 | python-versions = ">=3.6" 425 | 426 | [package.dependencies] 427 | atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} 428 | attrs = ">=19.2.0" 429 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 430 | iniconfig = "*" 431 | packaging = "*" 432 | pluggy = ">=0.12,<2.0" 433 | py = ">=1.8.2" 434 | toml = "*" 435 | 436 | [package.extras] 437 | testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] 438 | 439 | [[package]] 440 | name = "pytest-black" 441 | version = "0.3.12" 442 | description = "A pytest plugin to enable format checking with black" 443 | category = "dev" 444 | optional = false 445 | python-versions = ">=2.7" 446 | 447 | [package.dependencies] 448 | black = {version = "*", markers = "python_version >= \"3.6\""} 449 | pytest = ">=3.5.0" 450 | toml = "*" 451 | 452 | [[package]] 453 | name = "pytest-isort" 454 | version = "1.3.0" 455 | description = "py.test plugin to check import ordering using isort" 456 | category = "dev" 457 | optional = false 458 | python-versions = "*" 459 | 460 | [package.dependencies] 461 | isort = ">=4.0" 462 | 463 | [package.extras] 464 | tests = ["mock"] 465 | 466 | [[package]] 467 | name = "pyyaml" 468 | version = "5.4.1" 469 | description = "YAML parser and emitter for Python" 470 | category = "main" 471 | optional = false 472 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 473 | 474 | [[package]] 475 | name = "qrcode" 476 | version = "6.1" 477 | description = "QR Code image generator" 478 | category = "main" 479 | optional = false 480 | python-versions = "*" 481 | 482 | [package.dependencies] 483 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 484 | six = "*" 485 | 486 | [package.extras] 487 | dev = ["tox", "pytest", "mock"] 488 | maintainer = ["zest.releaser"] 489 | pil = ["pillow"] 490 | test = ["pytest", "pytest-cov", "mock"] 491 | 492 | [[package]] 493 | name = "readerwriterlock" 494 | version = "1.0.9" 495 | description = "A python implementation of the three Reader-Writer problems." 496 | category = "main" 497 | optional = false 498 | python-versions = ">=3.6" 499 | 500 | [package.dependencies] 501 | typing-extensions = "*" 502 | 503 | [[package]] 504 | name = "regex" 505 | version = "2021.10.23" 506 | description = "Alternative regular expression module, to replace re." 507 | category = "dev" 508 | optional = false 509 | python-versions = "*" 510 | 511 | [[package]] 512 | name = "requests" 513 | version = "2.26.0" 514 | description = "Python HTTP for Humans." 515 | category = "main" 516 | optional = false 517 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 518 | 519 | [package.dependencies] 520 | certifi = ">=2017.4.17" 521 | charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} 522 | idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} 523 | urllib3 = ">=1.21.1,<1.27" 524 | 525 | [package.extras] 526 | socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] 527 | use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] 528 | 529 | [[package]] 530 | name = "six" 531 | version = "1.16.0" 532 | description = "Python 2 and 3 compatibility utilities" 533 | category = "main" 534 | optional = false 535 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 536 | 537 | [[package]] 538 | name = "snowballstemmer" 539 | version = "2.1.0" 540 | description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." 541 | category = "dev" 542 | optional = false 543 | python-versions = "*" 544 | 545 | [[package]] 546 | name = "toml" 547 | version = "0.10.2" 548 | description = "Python Library for Tom's Obvious, Minimal Language" 549 | category = "dev" 550 | optional = false 551 | python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" 552 | 553 | [[package]] 554 | name = "typed-ast" 555 | version = "1.4.3" 556 | description = "a fork of Python 2 and 3 ast modules with type comment support" 557 | category = "dev" 558 | optional = false 559 | python-versions = "*" 560 | 561 | [[package]] 562 | name = "typing-extensions" 563 | version = "3.10.0.2" 564 | description = "Backported and Experimental Type Hints for Python 3.5+" 565 | category = "main" 566 | optional = false 567 | python-versions = "*" 568 | 569 | [[package]] 570 | name = "urllib3" 571 | version = "1.26.7" 572 | description = "HTTP library with thread-safe connection pooling, file post, and more." 573 | category = "main" 574 | optional = false 575 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" 576 | 577 | [package.extras] 578 | brotli = ["brotlipy (>=0.6.0)"] 579 | secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] 580 | socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] 581 | 582 | [metadata] 583 | lock-version = "1.1" 584 | python-versions = "^3.9" 585 | content-hash = "85fe81c7b12be9f9d5658e4d98841ff16d82f08f091f9abdd45d034bb0e45dbe" 586 | 587 | [metadata.files] 588 | appdirs = [ 589 | {file = "appdirs-1.4.4-py2.py3-none-any.whl", hash = "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128"}, 590 | {file = "appdirs-1.4.4.tar.gz", hash = "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41"}, 591 | ] 592 | asn1crypto = [ 593 | {file = "asn1crypto-1.4.0-py2.py3-none-any.whl", hash = "sha256:4bcdf33c861c7d40bdcd74d8e4dd7661aac320fcdf40b9a3f95b4ee12fde2fa8"}, 594 | {file = "asn1crypto-1.4.0.tar.gz", hash = "sha256:f4f6e119474e58e04a2b1af817eb585b4fd72bdd89b998624712b5c99be7641c"}, 595 | ] 596 | atomicwrites = [ 597 | {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, 598 | {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, 599 | ] 600 | attrs = [ 601 | {file = "attrs-21.2.0-py2.py3-none-any.whl", hash = "sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"}, 602 | {file = "attrs-21.2.0.tar.gz", hash = "sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"}, 603 | ] 604 | base45 = [ 605 | {file = "base45-0.3.1-py3-none-any.whl", hash = "sha256:de7613e8b0ae823fb488a70e4edb294ea32c279eb81cf629d9b9f9875bcde590"}, 606 | {file = "base45-0.3.1.tar.gz", hash = "sha256:2a086154572c5f644e82024004a09f6a391f3ce564067e1bf35919d3aee242ba"}, 607 | ] 608 | black = [ 609 | {file = "black-20.8b1.tar.gz", hash = "sha256:1c02557aa099101b9d21496f8a914e9ed2222ef70336404eeeac8edba836fbea"}, 610 | ] 611 | cbor2 = [ 612 | {file = "cbor2-5.4.2-cp36-cp36m-macosx_10_14_x86_64.whl", hash = "sha256:0153635a78e62d70f26f5b3469cb8de822420eda69c996304fb3d0dc1a53d7f3"}, 613 | {file = "cbor2-5.4.2-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70fb47a4bca70ae2d1b2b6c9d5ed6c898494739966653f7c84737f795b26d754"}, 614 | {file = "cbor2-5.4.2-cp36-cp36m-win_amd64.whl", hash = "sha256:10f178697e66eaae51534c3ef9acce1abf2f747e63c841e8702b9453c5bc4cfe"}, 615 | {file = "cbor2-5.4.2-cp37-cp37m-macosx_10_14_x86_64.whl", hash = "sha256:4c112b21fad53b2d936ef2d55c698641a6d49025c1a9cf73db3ef24684514db1"}, 616 | {file = "cbor2-5.4.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:93baf1ee33a305acf8f1391fed4e63ea2e837b561294fd6275ce8785acb795fa"}, 617 | {file = "cbor2-5.4.2-cp37-cp37m-win_amd64.whl", hash = "sha256:d9f64241bb30439f6ebde74101683e9dceecab95afe105560ac9d0e54d1b3c2e"}, 618 | {file = "cbor2-5.4.2-cp38-cp38-macosx_10_14_x86_64.whl", hash = "sha256:2757a7b624478d3b6707adf8ab7aef03d81fdaca6bea981b2323069660b9360f"}, 619 | {file = "cbor2-5.4.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32c1d00d8ad1a89f2358bf444fd43cacc3ca61f3c3feb1a2f2b2bea8ba4853d2"}, 620 | {file = "cbor2-5.4.2-cp38-cp38-win_amd64.whl", hash = "sha256:79c10306d9128258dea110c01abbe9c58c48ee2ffcf995f982fb063f1a82e2ee"}, 621 | {file = "cbor2-5.4.2-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:0e27c52abdccadadab858437f6d98d5e8711aa84f0af7417dd1404d16127db7f"}, 622 | {file = "cbor2-5.4.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:395aec8f415f039ab3be6cb58861c21bd2202f5c0ad6537b31956da532ea74ad"}, 623 | {file = "cbor2-5.4.2-cp39-cp39-win_amd64.whl", hash = "sha256:310c7d7925f7aa6cb90791606be17c164bbf3b28d4d17047b5d19d303f2fe817"}, 624 | {file = "cbor2-5.4.2.tar.gz", hash = "sha256:e283e70b55a049ff364cc5e648fde587e4d9b0e87e4b2664c69e639135e6b3b8"}, 625 | ] 626 | certifi = [ 627 | {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, 628 | {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, 629 | ] 630 | certvalidator = [ 631 | {file = "certvalidator-0.11.1-py2.py3-none-any.whl", hash = "sha256:77520b269f516d4fb0902998d5bd0eb3727fe153b659aa1cb828dcf12ea6b8de"}, 632 | {file = "certvalidator-0.11.1.tar.gz", hash = "sha256:922d141c94393ab285ca34338e18dd4093e3ae330b1f278e96c837cb62cffaad"}, 633 | ] 634 | cffi = [ 635 | {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, 636 | {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, 637 | {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, 638 | {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, 639 | {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, 640 | {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, 641 | {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, 642 | {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, 643 | {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, 644 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, 645 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, 646 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, 647 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, 648 | {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, 649 | {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, 650 | {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, 651 | {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, 652 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, 653 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, 654 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, 655 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, 656 | {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, 657 | {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, 658 | {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, 659 | {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, 660 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, 661 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, 662 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, 663 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, 664 | {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, 665 | {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, 666 | {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, 667 | {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, 668 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, 669 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, 670 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, 671 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, 672 | {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, 673 | {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, 674 | {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, 675 | {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, 676 | {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, 677 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, 678 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, 679 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, 680 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, 681 | {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, 682 | {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, 683 | {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, 684 | {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, 685 | ] 686 | charset-normalizer = [ 687 | {file = "charset-normalizer-2.0.7.tar.gz", hash = "sha256:e019de665e2bcf9c2b64e2e5aa025fa991da8720daa3c1138cadd2fd1856aed0"}, 688 | {file = "charset_normalizer-2.0.7-py3-none-any.whl", hash = "sha256:f7af805c321bfa1ce6714c51f254e0d5bb5e5834039bc17db7ebe3a4cec9492b"}, 689 | ] 690 | click = [ 691 | {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, 692 | {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, 693 | ] 694 | colorama = [ 695 | {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, 696 | {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, 697 | ] 698 | cose = [ 699 | {file = "cose-0.9.dev7-py3-none-any.whl", hash = "sha256:06c58658781eaa36d179a76ff374e52e2dd5ec0612806cae41b038ed0916360b"}, 700 | {file = "cose-0.9.dev7.tar.gz", hash = "sha256:d82cb1ebcdc5c759c1307f7302c5e6cb327d4195c03c31cb5fbdf6851a74d7ea"}, 701 | ] 702 | cryptography = [ 703 | {file = "cryptography-3.4.8-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:a00cf305f07b26c351d8d4e1af84ad7501eca8a342dedf24a7acb0e7b7406e14"}, 704 | {file = "cryptography-3.4.8-cp36-abi3-macosx_11_0_arm64.whl", hash = "sha256:f44d141b8c4ea5eb4dbc9b3ad992d45580c1d22bf5e24363f2fbf50c2d7ae8a7"}, 705 | {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0a7dcbcd3f1913f664aca35d47c1331fce738d44ec34b7be8b9d332151b0b01e"}, 706 | {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34dae04a0dce5730d8eb7894eab617d8a70d0c97da76b905de9efb7128ad7085"}, 707 | {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1eb7bb0df6f6f583dd8e054689def236255161ebbcf62b226454ab9ec663746b"}, 708 | {file = "cryptography-3.4.8-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:9965c46c674ba8cc572bc09a03f4c649292ee73e1b683adb1ce81e82e9a6a0fb"}, 709 | {file = "cryptography-3.4.8-cp36-abi3-win32.whl", hash = "sha256:21ca464b3a4b8d8e86ba0ee5045e103a1fcfac3b39319727bc0fc58c09c6aff7"}, 710 | {file = "cryptography-3.4.8-cp36-abi3-win_amd64.whl", hash = "sha256:3520667fda779eb788ea00080124875be18f2d8f0848ec00733c0ec3bb8219fc"}, 711 | {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:d2a6e5ef66503da51d2110edf6c403dc6b494cc0082f85db12f54e9c5d4c3ec5"}, 712 | {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a305600e7a6b7b855cd798e00278161b681ad6e9b7eca94c721d5f588ab212af"}, 713 | {file = "cryptography-3.4.8-pp36-pypy36_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:3fa3a7ccf96e826affdf1a0a9432be74dc73423125c8f96a909e3835a5ef194a"}, 714 | {file = "cryptography-3.4.8-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:d9ec0e67a14f9d1d48dd87a2531009a9b251c02ea42851c060b25c782516ff06"}, 715 | {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5b0fbfae7ff7febdb74b574055c7466da334a5371f253732d7e2e7525d570498"}, 716 | {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94fff993ee9bc1b2440d3b7243d488c6a3d9724cc2b09cdb297f6a886d040ef7"}, 717 | {file = "cryptography-3.4.8-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:8695456444f277af73a4877db9fc979849cd3ee74c198d04fc0776ebc3db52b9"}, 718 | {file = "cryptography-3.4.8-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:cd65b60cfe004790c795cc35f272e41a3df4631e2fb6b35aa7ac6ef2859d554e"}, 719 | {file = "cryptography-3.4.8.tar.gz", hash = "sha256:94cc5ed4ceaefcbe5bf38c8fba6a21fc1d365bb8fb826ea1688e3370b2e24a1c"}, 720 | ] 721 | cryptojwt = [ 722 | {file = "cryptojwt-1.5.2-py3-none-any.whl", hash = "sha256:c0af595e926c9fc724ab247d23ca52707c3a8d85e7e1c603d3b2d75ef4599ed1"}, 723 | {file = "cryptojwt-1.5.2.tar.gz", hash = "sha256:a4270473f62a599f66eeac4786965aa856061cde52a73ea70ad77240dd15c4e8"}, 724 | ] 725 | deepdiff = [ 726 | {file = "deepdiff-5.6.0-py3-none-any.whl", hash = "sha256:ef3410ca31e059a9d10edfdff552245829835b3ecd03212dc5b533d45a6c3f57"}, 727 | {file = "deepdiff-5.6.0.tar.gz", hash = "sha256:e3f1c3a375c7ea5ca69dba6f7920f9368658318ff1d8a496293c79481f48e649"}, 728 | ] 729 | ecdsa = [ 730 | {file = "ecdsa-0.17.0-py2.py3-none-any.whl", hash = "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676"}, 731 | {file = "ecdsa-0.17.0.tar.gz", hash = "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa"}, 732 | ] 733 | idna = [ 734 | {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, 735 | {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, 736 | ] 737 | iniconfig = [ 738 | {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, 739 | {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, 740 | ] 741 | isort = [ 742 | {file = "isort-5.9.3-py3-none-any.whl", hash = "sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"}, 743 | {file = "isort-5.9.3.tar.gz", hash = "sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899"}, 744 | ] 745 | jsonschema = [ 746 | {file = "jsonschema-3.2.0-py2.py3-none-any.whl", hash = "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163"}, 747 | {file = "jsonschema-3.2.0.tar.gz", hash = "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a"}, 748 | ] 749 | mccabe = [ 750 | {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, 751 | {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, 752 | ] 753 | mypy-extensions = [ 754 | {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, 755 | {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, 756 | ] 757 | ordered-set = [ 758 | {file = "ordered-set-4.0.2.tar.gz", hash = "sha256:ba93b2df055bca202116ec44b9bead3df33ea63a7d5827ff8e16738b97f33a95"}, 759 | ] 760 | oscrypto = [ 761 | {file = "oscrypto-1.2.1-py2.py3-none-any.whl", hash = "sha256:988087e05b17df8bfcc7c5fac51f54595e46d3e4dffa7b3d15955cf61a633529"}, 762 | {file = "oscrypto-1.2.1.tar.gz", hash = "sha256:7d2cca6235d89d1af6eb9cfcd4d2c0cb405849868157b2f7b278beb644d48694"}, 763 | ] 764 | packaging = [ 765 | {file = "packaging-21.0-py3-none-any.whl", hash = "sha256:c86254f9220d55e31cc94d69bade760f0847da8000def4dfe1c6b872fd14ff14"}, 766 | {file = "packaging-21.0.tar.gz", hash = "sha256:7dc96269f53a4ccec5c0670940a4281106dd0bb343f47b7471f779df49c2fbe7"}, 767 | ] 768 | pathspec = [ 769 | {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, 770 | {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, 771 | ] 772 | pillow = [ 773 | {file = "Pillow-8.4.0-cp310-cp310-macosx_10_10_universal2.whl", hash = "sha256:81f8d5c81e483a9442d72d182e1fb6dcb9723f289a57e8030811bac9ea3fef8d"}, 774 | {file = "Pillow-8.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:3f97cfb1e5a392d75dd8b9fd274d205404729923840ca94ca45a0af57e13dbe6"}, 775 | {file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb9fc393f3c61f9054e1ed26e6fe912c7321af2f41ff49d3f83d05bacf22cc78"}, 776 | {file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d82cdb63100ef5eedb8391732375e6d05993b765f72cb34311fab92103314649"}, 777 | {file = "Pillow-8.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:62cc1afda735a8d109007164714e73771b499768b9bb5afcbbee9d0ff374b43f"}, 778 | {file = "Pillow-8.4.0-cp310-cp310-win32.whl", hash = "sha256:e3dacecfbeec9a33e932f00c6cd7996e62f53ad46fbe677577394aaa90ee419a"}, 779 | {file = "Pillow-8.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:620582db2a85b2df5f8a82ddeb52116560d7e5e6b055095f04ad828d1b0baa39"}, 780 | {file = "Pillow-8.4.0-cp36-cp36m-macosx_10_10_x86_64.whl", hash = "sha256:1bc723b434fbc4ab50bb68e11e93ce5fb69866ad621e3c2c9bdb0cd70e345f55"}, 781 | {file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:72cbcfd54df6caf85cc35264c77ede902452d6df41166010262374155947460c"}, 782 | {file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:70ad9e5c6cb9b8487280a02c0ad8a51581dcbbe8484ce058477692a27c151c0a"}, 783 | {file = "Pillow-8.4.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:25a49dc2e2f74e65efaa32b153527fc5ac98508d502fa46e74fa4fd678ed6645"}, 784 | {file = "Pillow-8.4.0-cp36-cp36m-win32.whl", hash = "sha256:93ce9e955cc95959df98505e4608ad98281fff037350d8c2671c9aa86bcf10a9"}, 785 | {file = "Pillow-8.4.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2e4440b8f00f504ee4b53fe30f4e381aae30b0568193be305256b1462216feff"}, 786 | {file = "Pillow-8.4.0-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:8c803ac3c28bbc53763e6825746f05cc407b20e4a69d0122e526a582e3b5e153"}, 787 | {file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8a17b5d948f4ceeceb66384727dde11b240736fddeda54ca740b9b8b1556b29"}, 788 | {file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1394a6ad5abc838c5cd8a92c5a07535648cdf6d09e8e2d6df916dfa9ea86ead8"}, 789 | {file = "Pillow-8.4.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:792e5c12376594bfcb986ebf3855aa4b7c225754e9a9521298e460e92fb4a488"}, 790 | {file = "Pillow-8.4.0-cp37-cp37m-win32.whl", hash = "sha256:d99ec152570e4196772e7a8e4ba5320d2d27bf22fdf11743dd882936ed64305b"}, 791 | {file = "Pillow-8.4.0-cp37-cp37m-win_amd64.whl", hash = "sha256:7b7017b61bbcdd7f6363aeceb881e23c46583739cb69a3ab39cb384f6ec82e5b"}, 792 | {file = "Pillow-8.4.0-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:d89363f02658e253dbd171f7c3716a5d340a24ee82d38aab9183f7fdf0cdca49"}, 793 | {file = "Pillow-8.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:0a0956fdc5defc34462bb1c765ee88d933239f9a94bc37d132004775241a7585"}, 794 | {file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b7bb9de00197fb4261825c15551adf7605cf14a80badf1761d61e59da347779"}, 795 | {file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:72b9e656e340447f827885b8d7a15fc8c4e68d410dc2297ef6787eec0f0ea409"}, 796 | {file = "Pillow-8.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5a4532a12314149d8b4e4ad8ff09dde7427731fcfa5917ff16d0291f13609df"}, 797 | {file = "Pillow-8.4.0-cp38-cp38-win32.whl", hash = "sha256:82aafa8d5eb68c8463b6e9baeb4f19043bb31fefc03eb7b216b51e6a9981ae09"}, 798 | {file = "Pillow-8.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:066f3999cb3b070a95c3652712cffa1a748cd02d60ad7b4e485c3748a04d9d76"}, 799 | {file = "Pillow-8.4.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:5503c86916d27c2e101b7f71c2ae2cddba01a2cf55b8395b0255fd33fa4d1f1a"}, 800 | {file = "Pillow-8.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4acc0985ddf39d1bc969a9220b51d94ed51695d455c228d8ac29fcdb25810e6e"}, 801 | {file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0b052a619a8bfcf26bd8b3f48f45283f9e977890263e4571f2393ed8898d331b"}, 802 | {file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:493cb4e415f44cd601fcec11c99836f707bb714ab03f5ed46ac25713baf0ff20"}, 803 | {file = "Pillow-8.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b8831cb7332eda5dc89b21a7bce7ef6ad305548820595033a4b03cf3091235ed"}, 804 | {file = "Pillow-8.4.0-cp39-cp39-win32.whl", hash = "sha256:5e9ac5f66616b87d4da618a20ab0a38324dbe88d8a39b55be8964eb520021e02"}, 805 | {file = "Pillow-8.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:3eb1ce5f65908556c2d8685a8f0a6e989d887ec4057326f6c22b24e8a172c66b"}, 806 | {file = "Pillow-8.4.0-pp36-pypy36_pp73-macosx_10_10_x86_64.whl", hash = "sha256:ddc4d832a0f0b4c52fff973a0d44b6c99839a9d016fe4e6a1cb8f3eea96479c2"}, 807 | {file = "Pillow-8.4.0-pp36-pypy36_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9a3e5ddc44c14042f0844b8cf7d2cd455f6cc80fd7f5eefbe657292cf601d9ad"}, 808 | {file = "Pillow-8.4.0-pp36-pypy36_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c70e94281588ef053ae8998039610dbd71bc509e4acbc77ab59d7d2937b10698"}, 809 | {file = "Pillow-8.4.0-pp37-pypy37_pp73-macosx_10_10_x86_64.whl", hash = "sha256:3862b7256046fcd950618ed22d1d60b842e3a40a48236a5498746f21189afbbc"}, 810 | {file = "Pillow-8.4.0-pp37-pypy37_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4901622493f88b1a29bd30ec1a2f683782e57c3c16a2dbc7f2595ba01f639df"}, 811 | {file = "Pillow-8.4.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:84c471a734240653a0ec91dec0996696eea227eafe72a33bd06c92697728046b"}, 812 | {file = "Pillow-8.4.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:244cf3b97802c34c41905d22810846802a3329ddcb93ccc432870243211c79fc"}, 813 | {file = "Pillow-8.4.0.tar.gz", hash = "sha256:b8e2f83c56e141920c39464b852de3719dfbfb6e3c99a2d8da0edf4fb33176ed"}, 814 | ] 815 | pluggy = [ 816 | {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, 817 | {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, 818 | ] 819 | py = [ 820 | {file = "py-1.10.0-py2.py3-none-any.whl", hash = "sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"}, 821 | {file = "py-1.10.0.tar.gz", hash = "sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3"}, 822 | ] 823 | pycodestyle = [ 824 | {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, 825 | {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, 826 | ] 827 | pycparser = [ 828 | {file = "pycparser-2.20-py2.py3-none-any.whl", hash = "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"}, 829 | {file = "pycparser-2.20.tar.gz", hash = "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0"}, 830 | ] 831 | pydocstyle = [ 832 | {file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"}, 833 | {file = "pydocstyle-6.1.1.tar.gz", hash = "sha256:1d41b7c459ba0ee6c345f2eb9ae827cab14a7533a88c5c6f7e94923f72df92dc"}, 834 | ] 835 | pyflakes = [ 836 | {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, 837 | {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, 838 | ] 839 | pylama = [ 840 | {file = "pylama-7.7.1-py2.py3-none-any.whl", hash = "sha256:fd61c11872d6256b019ef1235be37b77c922ef37ac9797df6bd489996dddeb15"}, 841 | {file = "pylama-7.7.1.tar.gz", hash = "sha256:9bae53ef9c1a431371d6a8dca406816a60d547147b60a4934721898f553b7d8f"}, 842 | ] 843 | pyparsing = [ 844 | {file = "pyparsing-3.0.3-py3-none-any.whl", hash = "sha256:f8d3fe9fc404576c5164f0f0c4e382c96b85265e023c409c43d48f65da9d60d0"}, 845 | {file = "pyparsing-3.0.3.tar.gz", hash = "sha256:9e3511118010f112a4b4b435ae50e1eaa610cda191acb9e421d60cf5fde83455"}, 846 | ] 847 | pyrsistent = [ 848 | {file = "pyrsistent-0.18.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f4c8cabb46ff8e5d61f56a037974228e978f26bfefce4f61a4b1ac0ba7a2ab72"}, 849 | {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:da6e5e818d18459fa46fac0a4a4e543507fe1110e808101277c5a2b5bab0cd2d"}, 850 | {file = "pyrsistent-0.18.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5e4395bbf841693eaebaa5bb5c8f5cdbb1d139e07c975c682ec4e4f8126e03d2"}, 851 | {file = "pyrsistent-0.18.0-cp36-cp36m-win32.whl", hash = "sha256:527be2bfa8dc80f6f8ddd65242ba476a6c4fb4e3aedbf281dfbac1b1ed4165b1"}, 852 | {file = "pyrsistent-0.18.0-cp36-cp36m-win_amd64.whl", hash = "sha256:2aaf19dc8ce517a8653746d98e962ef480ff34b6bc563fc067be6401ffb457c7"}, 853 | {file = "pyrsistent-0.18.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:58a70d93fb79dc585b21f9d72487b929a6fe58da0754fa4cb9f279bb92369396"}, 854 | {file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:4916c10896721e472ee12c95cdc2891ce5890898d2f9907b1b4ae0f53588b710"}, 855 | {file = "pyrsistent-0.18.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:73ff61b1411e3fb0ba144b8f08d6749749775fe89688093e1efef9839d2dcc35"}, 856 | {file = "pyrsistent-0.18.0-cp37-cp37m-win32.whl", hash = "sha256:b29b869cf58412ca5738d23691e96d8aff535e17390128a1a52717c9a109da4f"}, 857 | {file = "pyrsistent-0.18.0-cp37-cp37m-win_amd64.whl", hash = "sha256:097b96f129dd36a8c9e33594e7ebb151b1515eb52cceb08474c10a5479e799f2"}, 858 | {file = "pyrsistent-0.18.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:772e94c2c6864f2cd2ffbe58bb3bdefbe2a32afa0acb1a77e472aac831f83427"}, 859 | {file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_i686.whl", hash = "sha256:c1a9ff320fa699337e05edcaae79ef8c2880b52720bc031b219e5b5008ebbdef"}, 860 | {file = "pyrsistent-0.18.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:cd3caef37a415fd0dae6148a1b6957a8c5f275a62cca02e18474608cb263640c"}, 861 | {file = "pyrsistent-0.18.0-cp38-cp38-win32.whl", hash = "sha256:e79d94ca58fcafef6395f6352383fa1a76922268fa02caa2272fff501c2fdc78"}, 862 | {file = "pyrsistent-0.18.0-cp38-cp38-win_amd64.whl", hash = "sha256:a0c772d791c38bbc77be659af29bb14c38ced151433592e326361610250c605b"}, 863 | {file = "pyrsistent-0.18.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:d5ec194c9c573aafaceebf05fc400656722793dac57f254cd4741f3c27ae57b4"}, 864 | {file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_i686.whl", hash = "sha256:6b5eed00e597b5b5773b4ca30bd48a5774ef1e96f2a45d105db5b4ebb4bca680"}, 865 | {file = "pyrsistent-0.18.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:48578680353f41dca1ca3dc48629fb77dfc745128b56fc01096b2530c13fd426"}, 866 | {file = "pyrsistent-0.18.0-cp39-cp39-win32.whl", hash = "sha256:f3ef98d7b76da5eb19c37fda834d50262ff9167c65658d1d8f974d2e4d90676b"}, 867 | {file = "pyrsistent-0.18.0-cp39-cp39-win_amd64.whl", hash = "sha256:404e1f1d254d314d55adb8d87f4f465c8693d6f902f67eb6ef5b4526dc58e6ea"}, 868 | {file = "pyrsistent-0.18.0.tar.gz", hash = "sha256:773c781216f8c2900b42a7b638d5b517bb134ae1acbebe4d1e8f1f41ea60eb4b"}, 869 | ] 870 | pytest = [ 871 | {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, 872 | {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, 873 | ] 874 | pytest-black = [ 875 | {file = "pytest-black-0.3.12.tar.gz", hash = "sha256:1d339b004f764d6cd0f06e690f6dd748df3d62e6fe1a692d6a5500ac2c5b75a5"}, 876 | ] 877 | pytest-isort = [ 878 | {file = "pytest-isort-1.3.0.tar.gz", hash = "sha256:46a12331a701e2f21d48548b2828c8b0a7956dbf1cd5347163f537deb24332dd"}, 879 | {file = "pytest_isort-1.3.0-py3-none-any.whl", hash = "sha256:074255ad393088a2daee6ca7f2305b7b86358ff632f62302896d8d4b2b339107"}, 880 | ] 881 | pyyaml = [ 882 | {file = "PyYAML-5.4.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:3b2b1824fe7112845700f815ff6a489360226a5609b96ec2190a45e62a9fc922"}, 883 | {file = "PyYAML-5.4.1-cp27-cp27m-win32.whl", hash = "sha256:129def1b7c1bf22faffd67b8f3724645203b79d8f4cc81f674654d9902cb4393"}, 884 | {file = "PyYAML-5.4.1-cp27-cp27m-win_amd64.whl", hash = "sha256:4465124ef1b18d9ace298060f4eccc64b0850899ac4ac53294547536533800c8"}, 885 | {file = "PyYAML-5.4.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:bb4191dfc9306777bc594117aee052446b3fa88737cd13b7188d0e7aa8162185"}, 886 | {file = "PyYAML-5.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:6c78645d400265a062508ae399b60b8c167bf003db364ecb26dcab2bda048253"}, 887 | {file = "PyYAML-5.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:4e0583d24c881e14342eaf4ec5fbc97f934b999a6828693a99157fde912540cc"}, 888 | {file = "PyYAML-5.4.1-cp36-cp36m-win32.whl", hash = "sha256:3bd0e463264cf257d1ffd2e40223b197271046d09dadf73a0fe82b9c1fc385a5"}, 889 | {file = "PyYAML-5.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:e4fac90784481d221a8e4b1162afa7c47ed953be40d31ab4629ae917510051df"}, 890 | {file = "PyYAML-5.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:5accb17103e43963b80e6f837831f38d314a0495500067cb25afab2e8d7a4018"}, 891 | {file = "PyYAML-5.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e1d4970ea66be07ae37a3c2e48b5ec63f7ba6804bdddfdbd3cfd954d25a82e63"}, 892 | {file = "PyYAML-5.4.1-cp37-cp37m-win32.whl", hash = "sha256:dd5de0646207f053eb0d6c74ae45ba98c3395a571a2891858e87df7c9b9bd51b"}, 893 | {file = "PyYAML-5.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf"}, 894 | {file = "PyYAML-5.4.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d2d9808ea7b4af864f35ea216be506ecec180628aced0704e34aca0b040ffe46"}, 895 | {file = "PyYAML-5.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:8c1be557ee92a20f184922c7b6424e8ab6691788e6d86137c5d93c1a6ec1b8fb"}, 896 | {file = "PyYAML-5.4.1-cp38-cp38-win32.whl", hash = "sha256:fa5ae20527d8e831e8230cbffd9f8fe952815b2b7dae6ffec25318803a7528fc"}, 897 | {file = "PyYAML-5.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:0f5f5786c0e09baddcd8b4b45f20a7b5d61a7e7e99846e3c799b05c7c53fa696"}, 898 | {file = "PyYAML-5.4.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:294db365efa064d00b8d1ef65d8ea2c3426ac366c0c4368d930bf1c5fb497f77"}, 899 | {file = "PyYAML-5.4.1-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:74c1485f7707cf707a7aef42ef6322b8f97921bd89be2ab6317fd782c2d53183"}, 900 | {file = "PyYAML-5.4.1-cp39-cp39-win32.whl", hash = "sha256:49d4cdd9065b9b6e206d0595fee27a96b5dd22618e7520c33204a4a3239d5b10"}, 901 | {file = "PyYAML-5.4.1-cp39-cp39-win_amd64.whl", hash = "sha256:c20cfa2d49991c8b4147af39859b167664f2ad4561704ee74c1de03318e898db"}, 902 | {file = "PyYAML-5.4.1.tar.gz", hash = "sha256:607774cbba28732bfa802b54baa7484215f530991055bb562efbed5b2f20a45e"}, 903 | ] 904 | qrcode = [ 905 | {file = "qrcode-6.1-py2.py3-none-any.whl", hash = "sha256:3996ee560fc39532910603704c82980ff6d4d5d629f9c3f25f34174ce8606cf5"}, 906 | {file = "qrcode-6.1.tar.gz", hash = "sha256:505253854f607f2abf4d16092c61d4e9d511a3b4392e60bff957a68592b04369"}, 907 | ] 908 | readerwriterlock = [ 909 | {file = "readerwriterlock-1.0.9-py3-none-any.whl", hash = "sha256:8c4b704e60d15991462081a27ef46762fea49b478aa4426644f2146754759ca7"}, 910 | {file = "readerwriterlock-1.0.9.tar.gz", hash = "sha256:b7c4cc003435d7a8ff15b312b0a62a88d9800ba6164af88991f87f8b748f9bea"}, 911 | ] 912 | regex = [ 913 | {file = "regex-2021.10.23-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:45b65d6a275a478ac2cbd7fdbf7cc93c1982d613de4574b56fd6972ceadb8395"}, 914 | {file = "regex-2021.10.23-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:74d071dbe4b53c602edd87a7476ab23015a991374ddb228d941929ad7c8c922e"}, 915 | {file = "regex-2021.10.23-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:34d870f9f27f2161709054d73646fc9aca49480617a65533fc2b4611c518e455"}, 916 | {file = "regex-2021.10.23-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2fb698037c35109d3c2e30f2beb499e5ebae6e4bb8ff2e60c50b9a805a716f79"}, 917 | {file = "regex-2021.10.23-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:cb46b542133999580ffb691baf67410306833ee1e4f58ed06b6a7aaf4e046952"}, 918 | {file = "regex-2021.10.23-cp310-cp310-win32.whl", hash = "sha256:5e9c9e0ce92f27cef79e28e877c6b6988c48b16942258f3bc55d39b5f911df4f"}, 919 | {file = "regex-2021.10.23-cp310-cp310-win_amd64.whl", hash = "sha256:ab7c5684ff3538b67df3f93d66bd3369b749087871ae3786e70ef39e601345b0"}, 920 | {file = "regex-2021.10.23-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:de557502c3bec8e634246588a94e82f1ee1b9dfcfdc453267c4fb652ff531570"}, 921 | {file = "regex-2021.10.23-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ee684f139c91e69fe09b8e83d18b4d63bf87d9440c1eb2eeb52ee851883b1b29"}, 922 | {file = "regex-2021.10.23-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:5095a411c8479e715784a0c9236568ae72509450ee2226b649083730f3fadfc6"}, 923 | {file = "regex-2021.10.23-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7b568809dca44cb75c8ebb260844ea98252c8c88396f9d203f5094e50a70355f"}, 924 | {file = "regex-2021.10.23-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:eb672217f7bd640411cfc69756ce721d00ae600814708d35c930930f18e8029f"}, 925 | {file = "regex-2021.10.23-cp36-cp36m-win32.whl", hash = "sha256:a7a986c45d1099a5de766a15de7bee3840b1e0e1a344430926af08e5297cf666"}, 926 | {file = "regex-2021.10.23-cp36-cp36m-win_amd64.whl", hash = "sha256:6d7722136c6ed75caf84e1788df36397efdc5dbadab95e59c2bba82d4d808a4c"}, 927 | {file = "regex-2021.10.23-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:9f665677e46c5a4d288ece12fdedf4f4204a422bb28ff05f0e6b08b7447796d1"}, 928 | {file = "regex-2021.10.23-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:450dc27483548214314640c89a0f275dbc557968ed088da40bde7ef8fb52829e"}, 929 | {file = "regex-2021.10.23-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:129472cd06062fb13e7b4670a102951a3e655e9b91634432cfbdb7810af9d710"}, 930 | {file = "regex-2021.10.23-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a940ca7e7189d23da2bfbb38973832813eab6bd83f3bf89a977668c2f813deae"}, 931 | {file = "regex-2021.10.23-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:530fc2bbb3dc1ebb17f70f7b234f90a1dd43b1b489ea38cea7be95fb21cdb5c7"}, 932 | {file = "regex-2021.10.23-cp37-cp37m-win32.whl", hash = "sha256:ded0c4a3eee56b57fcb2315e40812b173cafe79d2f992d50015f4387445737fa"}, 933 | {file = "regex-2021.10.23-cp37-cp37m-win_amd64.whl", hash = "sha256:391703a2abf8013d95bae39145d26b4e21531ab82e22f26cd3a181ee2644c234"}, 934 | {file = "regex-2021.10.23-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:be04739a27be55631069b348dda0c81d8ea9822b5da10b8019b789e42d1fe452"}, 935 | {file = "regex-2021.10.23-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13ec99df95003f56edcd307db44f06fbeb708c4ccdcf940478067dd62353181e"}, 936 | {file = "regex-2021.10.23-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:8d1cdcda6bd16268316d5db1038965acf948f2a6f43acc2e0b1641ceab443623"}, 937 | {file = "regex-2021.10.23-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0c186691a7995ef1db61205e00545bf161fb7b59cdb8c1201c89b333141c438a"}, 938 | {file = "regex-2021.10.23-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:2b20f544cbbeffe171911f6ce90388ad36fe3fad26b7c7a35d4762817e9ea69c"}, 939 | {file = "regex-2021.10.23-cp38-cp38-win32.whl", hash = "sha256:c0938ddd60cc04e8f1faf7a14a166ac939aac703745bfcd8e8f20322a7373019"}, 940 | {file = "regex-2021.10.23-cp38-cp38-win_amd64.whl", hash = "sha256:56f0c81c44638dfd0e2367df1a331b4ddf2e771366c4b9c5d9a473de75e3e1c7"}, 941 | {file = "regex-2021.10.23-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:80bb5d2e92b2258188e7dcae5b188c7bf868eafdf800ea6edd0fbfc029984a88"}, 942 | {file = "regex-2021.10.23-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1dae12321b31059a1a72aaa0e6ba30156fe7e633355e445451e4021b8e122b6"}, 943 | {file = "regex-2021.10.23-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:1f2b59c28afc53973d22e7bc18428721ee8ca6079becf1b36571c42627321c65"}, 944 | {file = "regex-2021.10.23-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d134757a37d8640f3c0abb41f5e68b7cf66c644f54ef1cb0573b7ea1c63e1509"}, 945 | {file = "regex-2021.10.23-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0dcc0e71118be8c69252c207630faf13ca5e1b8583d57012aae191e7d6d28b84"}, 946 | {file = "regex-2021.10.23-cp39-cp39-win32.whl", hash = "sha256:a30513828180264294953cecd942202dfda64e85195ae36c265daf4052af0464"}, 947 | {file = "regex-2021.10.23-cp39-cp39-win_amd64.whl", hash = "sha256:0f7552429dd39f70057ac5d0e897e5bfe211629652399a21671e53f2a9693a4e"}, 948 | {file = "regex-2021.10.23.tar.gz", hash = "sha256:f3f9a91d3cc5e5b0ddf1043c0ae5fa4852f18a1c0050318baf5fc7930ecc1f9c"}, 949 | ] 950 | requests = [ 951 | {file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"}, 952 | {file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"}, 953 | ] 954 | six = [ 955 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 956 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 957 | ] 958 | snowballstemmer = [ 959 | {file = "snowballstemmer-2.1.0-py2.py3-none-any.whl", hash = "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2"}, 960 | {file = "snowballstemmer-2.1.0.tar.gz", hash = "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914"}, 961 | ] 962 | toml = [ 963 | {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, 964 | {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, 965 | ] 966 | typed-ast = [ 967 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:2068531575a125b87a41802130fa7e29f26c09a2833fea68d9a40cf33902eba6"}, 968 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:c907f561b1e83e93fad565bac5ba9c22d96a54e7ea0267c708bffe863cbe4075"}, 969 | {file = "typed_ast-1.4.3-cp35-cp35m-manylinux2014_aarch64.whl", hash = "sha256:1b3ead4a96c9101bef08f9f7d1217c096f31667617b58de957f690c92378b528"}, 970 | {file = "typed_ast-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:dde816ca9dac1d9c01dd504ea5967821606f02e510438120091b84e852367428"}, 971 | {file = "typed_ast-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:777a26c84bea6cd934422ac2e3b78863a37017618b6e5c08f92ef69853e765d3"}, 972 | {file = "typed_ast-1.4.3-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:f8afcf15cc511ada719a88e013cec87c11aff7b91f019295eb4530f96fe5ef2f"}, 973 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:52b1eb8c83f178ab787f3a4283f68258525f8d70f778a2f6dd54d3b5e5fb4341"}, 974 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:01ae5f73431d21eead5015997ab41afa53aa1fbe252f9da060be5dad2c730ace"}, 975 | {file = "typed_ast-1.4.3-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:c190f0899e9f9f8b6b7863debfb739abcb21a5c054f911ca3596d12b8a4c4c7f"}, 976 | {file = "typed_ast-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:398e44cd480f4d2b7ee8d98385ca104e35c81525dd98c519acff1b79bdaac363"}, 977 | {file = "typed_ast-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:bff6ad71c81b3bba8fa35f0f1921fb24ff4476235a6e94a26ada2e54370e6da7"}, 978 | {file = "typed_ast-1.4.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:0fb71b8c643187d7492c1f8352f2c15b4c4af3f6338f21681d3681b3dc31a266"}, 979 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:760ad187b1041a154f0e4d0f6aae3e40fdb51d6de16e5c99aedadd9246450e9e"}, 980 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:5feca99c17af94057417d744607b82dd0a664fd5e4ca98061480fd8b14b18d04"}, 981 | {file = "typed_ast-1.4.3-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:95431a26309a21874005845c21118c83991c63ea800dd44843e42a916aec5899"}, 982 | {file = "typed_ast-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:aee0c1256be6c07bd3e1263ff920c325b59849dc95392a05f258bb9b259cf39c"}, 983 | {file = "typed_ast-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9ad2c92ec681e02baf81fdfa056fe0d818645efa9af1f1cd5fd6f1bd2bdfd805"}, 984 | {file = "typed_ast-1.4.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:b36b4f3920103a25e1d5d024d155c504080959582b928e91cb608a65c3a49e1a"}, 985 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:067a74454df670dcaa4e59349a2e5c81e567d8d65458d480a5b3dfecec08c5ff"}, 986 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:7538e495704e2ccda9b234b82423a4038f324f3a10c43bc088a1636180f11a41"}, 987 | {file = "typed_ast-1.4.3-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:af3d4a73793725138d6b334d9d247ce7e5f084d96284ed23f22ee626a7b88e39"}, 988 | {file = "typed_ast-1.4.3-cp38-cp38-win32.whl", hash = "sha256:f2362f3cb0f3172c42938946dbc5b7843c2a28aec307c49100c8b38764eb6927"}, 989 | {file = "typed_ast-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:dd4a21253f42b8d2b48410cb31fe501d32f8b9fbeb1f55063ad102fe9c425e40"}, 990 | {file = "typed_ast-1.4.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f328adcfebed9f11301eaedfa48e15bdece9b519fb27e6a8c01aa52a17ec31b3"}, 991 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_i686.whl", hash = "sha256:2c726c276d09fc5c414693a2de063f521052d9ea7c240ce553316f70656c84d4"}, 992 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:cae53c389825d3b46fb37538441f75d6aecc4174f615d048321b716df2757fb0"}, 993 | {file = "typed_ast-1.4.3-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:b9574c6f03f685070d859e75c7f9eeca02d6933273b5e69572e5ff9d5e3931c3"}, 994 | {file = "typed_ast-1.4.3-cp39-cp39-win32.whl", hash = "sha256:209596a4ec71d990d71d5e0d312ac935d86930e6eecff6ccc7007fe54d703808"}, 995 | {file = "typed_ast-1.4.3-cp39-cp39-win_amd64.whl", hash = "sha256:9c6d1a54552b5330bc657b7ef0eae25d00ba7ffe85d9ea8ae6540d2197a3788c"}, 996 | {file = "typed_ast-1.4.3.tar.gz", hash = "sha256:fb1bbeac803adea29cedd70781399c99138358c26d05fcbd23c13016b7f5ec65"}, 997 | ] 998 | typing-extensions = [ 999 | {file = "typing_extensions-3.10.0.2-py2-none-any.whl", hash = "sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7"}, 1000 | {file = "typing_extensions-3.10.0.2-py3-none-any.whl", hash = "sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34"}, 1001 | {file = "typing_extensions-3.10.0.2.tar.gz", hash = "sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e"}, 1002 | ] 1003 | urllib3 = [ 1004 | {file = "urllib3-1.26.7-py2.py3-none-any.whl", hash = "sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"}, 1005 | {file = "urllib3-1.26.7.tar.gz", hash = "sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece"}, 1006 | ] 1007 | --------------------------------------------------------------------------------