Skip to content

Commit

Permalink
Support multiple keys per environment (#354)
Browse files Browse the repository at this point in the history
  • Loading branch information
erickduran authored Dec 21, 2022
1 parent 05406ec commit b7d0dd5
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 97 deletions.
2 changes: 1 addition & 1 deletion confidant/routes/jwks.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ def get_public_jwks(environment):
"""
jwks = jwk_manager.get_jwks(environment)
if jwks:
return jwks_list_response_schema.dumps(JWKSListResponse(keys=[jwks]))
return jwks_list_response_schema.dumps(JWKSListResponse(keys=jwks))

response = jsonify({
'error': 'Public key not found for this environment'
Expand Down
92 changes: 66 additions & 26 deletions confidant/services/jwkmanager.py
Original file line number Diff line number Diff line change
@@ -1,56 +1,84 @@
import logging
import jwt

from jwcrypto import jwk
from typing import Dict, Optional
from typing import Dict, Optional, List, Tuple

from confidant.settings import CERTIFICATE_AUTHORITIES, \
DEFAULT_JWT_EXPIRATION_SECONDS, JWT_CACHING_ENABLED
DEFAULT_JWT_EXPIRATION_SECONDS, JWT_CACHING_ENABLED, ACTIVE_SIGNING_KEYS
from confidant.utils import stats
from datetime import datetime, timezone, timedelta
from cerberus import Validator


logger = logging.getLogger(__name__)

CA_SCHEMA = {
'crt': {'type': 'string', 'required': True},
'key': {'type': 'string', 'required': True},
'passphrase': {'type': 'string', 'required': True},
'kid': {'type': 'string', 'required': True},
}


class JWKManager:
def __init__(self) -> None:
self._keys = jwk.JWKSet()
self._keys = {}
self._token_cache = {}
self._pem_cache = {}

self._load_certificate_authorities()

def _load_certificate_authorities(self) -> None:
validator = Validator(CA_SCHEMA)
if CERTIFICATE_AUTHORITIES:
for ca in CERTIFICATE_AUTHORITIES:
self.set_key(ca['name'], ca['key'],
passphrase=ca['passphrase'])

def set_key(self, kid: str, private_key: str,
for environment in CERTIFICATE_AUTHORITIES:
for ca in CERTIFICATE_AUTHORITIES[environment]:
if validator.validate(ca):
self.set_key(environment, ca['kid'], ca['key'],
passphrase=ca['passphrase'])
else:
logger.error(f'Invalid entry in {environment} '
f'in CERTIFICATE_AUTHORITIES')

def set_key(self, environment: str, kid: str,
private_key: str,
passphrase: Optional[str] = None,
encoding: str = 'utf-8') -> str:
if environment not in self._keys:
self._keys[environment] = jwk.JWKSet()

if passphrase:
passphrase = passphrase.encode(encoding)
key = jwk.JWK()
key.import_from_pem(private_key.encode(encoding),
password=passphrase, kid=kid)
if not self._keys.get_key(kid):
self._keys.add(key)
if not self._keys[environment].get_key(kid):
self._keys[environment].add(key)
return kid

def _get_key(self, kid: str):
if kid not in self._pem_cache:
def _get_key(self, kid: str, environment: str):
if environment not in self._pem_cache:
self._pem_cache[environment] = {}

if kid not in self._pem_cache[environment]:
# setting either way to avoid further lookups when response is None
self._pem_cache[kid] = self._keys.get_key(kid)
if self._pem_cache[kid]:
self._pem_cache[kid] = self._pem_cache[kid].export_to_pem(
self._pem_cache[environment][kid] = \
self._keys[environment].get_key(kid)
if self._pem_cache[environment][kid]:
self._pem_cache[environment][kid] = \
self._pem_cache[environment][kid].export_to_pem(
private_key=True,
password=None
)
return self._pem_cache[kid]
return self._pem_cache[environment][kid]

def get_jwt(self, kid: str, payload: dict,
def get_jwt(self, environment: str, payload: dict,
expiration_seconds: int = DEFAULT_JWT_EXPIRATION_SECONDS,
algorithm: str = 'RS256') -> str:
key = self._get_key(kid)
kid, key = self.get_active_key(environment)
if not key:
raise ValueError('This private key is not stored!')
raise ValueError('No active key for this environment')

if 'user' not in payload:
raise ValueError('Please include the user in the payload')
Expand Down Expand Up @@ -91,14 +119,26 @@ def get_jwt(self, kid: str, payload: dict,
stats.incr('get_jwt.create')
return token

def get_jwks(self, key_id: str, algorithm: str = 'RS256') -> Dict[str, str]:
key = self._keys.get_key(key_id)
if key:
stats.incr(f'get_jwks.{key_id}.hit')
return {**key.export_public(as_dict=True), 'alg': algorithm}
def get_active_key(self, environment: str) -> Tuple[str, Optional[jwk.JWK]]:
if environment in ACTIVE_SIGNING_KEYS and environment in self._keys:
return ACTIVE_SIGNING_KEYS[environment], self._get_key(
ACTIVE_SIGNING_KEYS[environment],
environment
)
return '', None

def get_jwks(self, environment: str, algorithm: str = 'RS256') \
-> List[Dict[str, str]]:
keys = self._keys.get(environment)
if keys:
stats.incr(f'get_jwks.{environment}.hit')
return [{
**key.export_public(as_dict=True),
'alg': algorithm
} for key in keys]
else:
stats.incr(f'get_jwks.{key_id}.miss')
return {}
stats.incr(f'get_jwks.{environment}.miss')
return []


jwk_manager = JWKManager()
16 changes: 16 additions & 0 deletions confidant/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -606,6 +606,17 @@ def str_env(var_name, default=''):
ENABLE_SAVE_LAST_DECRYPTION_TIME = bool_env('ENABLE_SAVE_LAST_DECRYPTION_TIME')

# Add any certificate authorities
# Should be in encrypted settings following this format (where name is
# the name of the environment) and key ids must be unique:
# [
# {
# "key": "--- RSA...",
# "crt": "--- CERT...",
# "name": "staging",
# "passphrase": "some-key",
# "kid": "some-kid"
# }, ...
# ]
decrypted_cas = encrypted_settings.decrypted_secrets.get(
'CERTIFICATE_AUTHORITIES'
)
Expand Down Expand Up @@ -636,3 +647,8 @@ def get(name, default=None):
# Module that will perform an external ACL check on API endpoints
ACL_MODULE = str_env('ACL_MODULE', 'confidant.authnz.rbac:default_acl')
DEFAULT_JWT_EXPIRATION_SECONDS = int_env('DEFAULT_JWT_EXPIRATION_SECONDS', 3600)

# Key IDs from CERTIFICATE_AUTHORITIES that should be used to sign new JWTs,
# provide a JSON with the following format:
# {"staging": "some_kid", "production": "some_kid"}
ACTIVE_SIGNING_KEYS = json.loads(str_env('ACTIVE_SIGNING_KEYS', '{}'))
1 change: 1 addition & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -194,3 +194,4 @@ pytz
# for jwt
pyjwt>=2.6.0
jwcrypto
cerberus
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ botocore==1.12.227
# boto3
# pynamodb
# s3transfer
cerberus==1.3.4
# via -r requirements.in
certifi==2019.6.16
# via requests
cffi==1.14.5
Expand Down
142 changes: 87 additions & 55 deletions tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,64 @@
import json
import pytest

from jwcrypto import jwk


TEST_PRIVATE_KEY = \
b'-----BEGIN PRIVATE KEY-----\n' \
b'MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDUu1TaFSVNJELf\n' \
b'YiV++1ZDAbrQ/flor64XOwK9RItzYqvnEUU1+aHw4QvDNyKJsyH7/uqv42vDZiCi\n' \
b'AH6T1RVLD30AdIswOQpVSsQcPEXcJfkIJ3ZQSWpuTVMbvJukrWK9ScPFkUyBC7Fp\n' \
b'kZ/RJ6pwiE2nVHjcrysAmK2/KB1Hk/NmO+fwevIcYXwrxNLm9k6XM5xyJcl7ZK3G\n' \
b'InZbuEcH4NdRYnAo7dCLtuwRFUW4fgSRUjjxFXGJLd830iDgHzM0VyTq77gzKpn5\n' \
b'VEZbIvDWgc8oxKHsrJKbyv3UGeC0q04K2EQR6tq71gbS/hv9QCxmR6ygNqq8bz0d\n' \
b'smZXeeYxAgMBAAECggEActo9Io0eGXsFW8OKiPc7iGvLqAAnAt0uub4TaYozW/We\n' \
b'598MJesEAqAOELSYwg1jwMDNhm7bhKCD59Mqg7gciezvySoi58M0D/6QyMnF0ejy\n' \
b'ffOITiqE+s5mm2gGBC/USmwj9WvQCS/99gg4Z9zpiV4dxsS1iDhOmEDWNYl73WNH\n' \
b'4aXL6fxG59r943qpm3JJqqqW/wsFQELLrKDrSWCv6u4niJ/zBE/XS6GaeZRm5ZNI\n' \
b'BxSRwuYQgNzFdi/cGLV3BPiwusT37pANvzCI+1ZUazKFVjTHPPdvFvpoFap24Foo\n' \
b'IkVdpe4Fm6qbi66uCUXlXPyGbmA1lkmmskACE4/7kQKBgQD75RxndWb90YvJAjhL\n' \
b'nDiXu8xe4p0wsDoFNGDuLag1ZKhkHfN3YG7PwZY+1aXUP0fgpGM9ivjWgctosmcR\n' \
b'nHPnyUi846PWNV5KQPFzR1Fmg8xAr0LdeyZHZetQYB+21QvMdfFsU1qU0rhU07Og\n' \
b'4CGVIJBZCpbMxSN4MCy57jFdvQKBgQDYMtWP7A2HwA0zxwn0rciF0VoTM6XERkz5\n' \
b'nwrnYneFiiShMjUOrLxq5XSU3sawIw/MD0gRsbDkv3kMdGf32sjsopLKFSk8aXid\n' \
b'BpLoaE7TRzSwJhmFSGNa4s3HBXqEjXJIgDDz3XlvZ/8h9WCQg/tJQ6IaV0oSx1q5\n' \
b'bpO258CvhQKBgE6gqKoeuoRWKYUYHUx0ujGa3GNt51UwXRwMyojuVYg9IFcIBlxo\n' \
b'DI7rRaPdesLy8dPMXHH0dFI49655qbSUmpVqfjr/779Ir2MMPJIYW+9dCp/SVVPf\n' \
b'QgadaMORDbU7cVBkLHT829SCpilMX9DCxZjQLl6s8H+Atd6pYvyyvlQdAoGAVGo8\n' \
b'0s4zVj7ZqM7dh0jXk9BzYC35WpKseYbs5f2fd2fB96K37rvpcb+X7oyxfZKjF2Uc\n' \
b'GbSMwjQ02nUVJ0So0SSFNhxfFnSEIKOxdsdLh9k0rFaj/lOOX61Q9ZWhCeKEreRH\n' \
b'uOBQCvzLNIIvqx2tXyTmRWyxwnVOajrPuEnzBVUCgYAKBdA3UC+Cu4f5nhn8FmG3\n' \
b'YuTBwFrUB82BHCQxC7L3prsxWVuguZptWln0ng+yTQme+shre03BaONl7A95hfz5\n' \
b'rhXTb8j86oFR6JIYf2Vfe6hrUBCxAxb4A1uDkt/GZMupHp+XiKCY8L+nKPjbX8aC\n' \
b'PmsiJweOFGfkN7QBzsdhsg==\n' \
b'-----END PRIVATE KEY-----\n'

TEST_CERTIFICATE = \
b'-----BEGIN CERTIFICATE-----\n' \
b'MIIDoDCCAogCCQDqKOyH38qgKDANBgkqhkiG9w0BAQsFADCBkTELMAkGA1UEBhMC\n' \
b'VVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1UZXN0IExvY2FsaXR5MRIwEAYDVQQK\n' \
b'DAlMeWZ0IFRlc3QxEjAQBgNVBAsMCVRlc3QgVW5pdDENMAsGA1UEAwwEdGVzdDEm\n' \
b'MCQGCSqGSIb3DQEJARYXdGVzdC1zb21ldGhpbmdAbHlmdC5jb20wHhcNMjIxMDA3\n' \
b'MjMxNTM5WhcNMjMxMDA3MjMxNTM5WjCBkTELMAkGA1UEBhMCVVMxCzAJBgNVBAgM\n' \
b'AkNBMRYwFAYDVQQHDA1UZXN0IExvY2FsaXR5MRIwEAYDVQQKDAlMeWZ0IFRlc3Qx\n' \
b'EjAQBgNVBAsMCVRlc3QgVW5pdDENMAsGA1UEAwwEdGVzdDEmMCQGCSqGSIb3DQEJ\n' \
b'ARYXdGVzdC1zb21ldGhpbmdAbHlmdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB\n' \
b'DwAwggEKAoIBAQDUu1TaFSVNJELfYiV++1ZDAbrQ/flor64XOwK9RItzYqvnEUU1\n' \
b'+aHw4QvDNyKJsyH7/uqv42vDZiCiAH6T1RVLD30AdIswOQpVSsQcPEXcJfkIJ3ZQ\n' \
b'SWpuTVMbvJukrWK9ScPFkUyBC7FpkZ/RJ6pwiE2nVHjcrysAmK2/KB1Hk/NmO+fw\n' \
b'evIcYXwrxNLm9k6XM5xyJcl7ZK3GInZbuEcH4NdRYnAo7dCLtuwRFUW4fgSRUjjx\n' \
b'FXGJLd830iDgHzM0VyTq77gzKpn5VEZbIvDWgc8oxKHsrJKbyv3UGeC0q04K2EQR\n' \
b'6tq71gbS/hv9QCxmR6ygNqq8bz0dsmZXeeYxAgMBAAEwDQYJKoZIhvcNAQELBQAD\n' \
b'ggEBAIPQnGGAlwbK+f4V7SUUjXnsO7oVlMtTO7JWAk+g8W9colUeMDHW/Ygcwu3e\n' \
b'OlX5NSEV1wcQxuqyNWbEgrsZourePdVWujc/9qSVfaU/BjOj2CLylAf6ZNj/XpL/\n' \
b'PNCSCLM40cbhw/SeiNZ9WxkuuHiC32QxmR4kyvvXcHEGqVA2cOVAvncstW4gGowi\n' \
b'ObNYddXOmoOf8d5oHcO5vlhYyfbmShuq1PLygzUhG2jS+5aX9gmDtv+LtVGdXXWV\n' \
b'zSCh3+H4NSUWs3P1pKDFIUT3jGLQ3UavIS5KCizfBUbltx6LBSgmBbHf89RsKBbT\n' \
b'rxaukysD4sNgVHKptTq0fJ+2CjM=\n' \
b'-----END CERTIFICATE-----\n'


@pytest.fixture(autouse=True)
def encrypted_settings_mock(mocker):
mocker.patch('confidant.settings.encrypted_settings.secret_string', {})
Expand Down Expand Up @@ -48,37 +104,8 @@ def test_encrypted_key():

@pytest.fixture
def test_key_pair():
test_private_key = \
b'-----BEGIN PRIVATE KEY-----\n' \
b'MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDUu1TaFSVNJELf\n' \
b'YiV++1ZDAbrQ/flor64XOwK9RItzYqvnEUU1+aHw4QvDNyKJsyH7/uqv42vDZiCi\n' \
b'AH6T1RVLD30AdIswOQpVSsQcPEXcJfkIJ3ZQSWpuTVMbvJukrWK9ScPFkUyBC7Fp\n' \
b'kZ/RJ6pwiE2nVHjcrysAmK2/KB1Hk/NmO+fwevIcYXwrxNLm9k6XM5xyJcl7ZK3G\n' \
b'InZbuEcH4NdRYnAo7dCLtuwRFUW4fgSRUjjxFXGJLd830iDgHzM0VyTq77gzKpn5\n' \
b'VEZbIvDWgc8oxKHsrJKbyv3UGeC0q04K2EQR6tq71gbS/hv9QCxmR6ygNqq8bz0d\n' \
b'smZXeeYxAgMBAAECggEActo9Io0eGXsFW8OKiPc7iGvLqAAnAt0uub4TaYozW/We\n' \
b'598MJesEAqAOELSYwg1jwMDNhm7bhKCD59Mqg7gciezvySoi58M0D/6QyMnF0ejy\n' \
b'ffOITiqE+s5mm2gGBC/USmwj9WvQCS/99gg4Z9zpiV4dxsS1iDhOmEDWNYl73WNH\n' \
b'4aXL6fxG59r943qpm3JJqqqW/wsFQELLrKDrSWCv6u4niJ/zBE/XS6GaeZRm5ZNI\n' \
b'BxSRwuYQgNzFdi/cGLV3BPiwusT37pANvzCI+1ZUazKFVjTHPPdvFvpoFap24Foo\n' \
b'IkVdpe4Fm6qbi66uCUXlXPyGbmA1lkmmskACE4/7kQKBgQD75RxndWb90YvJAjhL\n' \
b'nDiXu8xe4p0wsDoFNGDuLag1ZKhkHfN3YG7PwZY+1aXUP0fgpGM9ivjWgctosmcR\n' \
b'nHPnyUi846PWNV5KQPFzR1Fmg8xAr0LdeyZHZetQYB+21QvMdfFsU1qU0rhU07Og\n' \
b'4CGVIJBZCpbMxSN4MCy57jFdvQKBgQDYMtWP7A2HwA0zxwn0rciF0VoTM6XERkz5\n' \
b'nwrnYneFiiShMjUOrLxq5XSU3sawIw/MD0gRsbDkv3kMdGf32sjsopLKFSk8aXid\n' \
b'BpLoaE7TRzSwJhmFSGNa4s3HBXqEjXJIgDDz3XlvZ/8h9WCQg/tJQ6IaV0oSx1q5\n' \
b'bpO258CvhQKBgE6gqKoeuoRWKYUYHUx0ujGa3GNt51UwXRwMyojuVYg9IFcIBlxo\n' \
b'DI7rRaPdesLy8dPMXHH0dFI49655qbSUmpVqfjr/779Ir2MMPJIYW+9dCp/SVVPf\n' \
b'QgadaMORDbU7cVBkLHT829SCpilMX9DCxZjQLl6s8H+Atd6pYvyyvlQdAoGAVGo8\n' \
b'0s4zVj7ZqM7dh0jXk9BzYC35WpKseYbs5f2fd2fB96K37rvpcb+X7oyxfZKjF2Uc\n' \
b'GbSMwjQ02nUVJ0So0SSFNhxfFnSEIKOxdsdLh9k0rFaj/lOOX61Q9ZWhCeKEreRH\n' \
b'uOBQCvzLNIIvqx2tXyTmRWyxwnVOajrPuEnzBVUCgYAKBdA3UC+Cu4f5nhn8FmG3\n' \
b'YuTBwFrUB82BHCQxC7L3prsxWVuguZptWln0ng+yTQme+shre03BaONl7A95hfz5\n' \
b'rhXTb8j86oFR6JIYf2Vfe6hrUBCxAxb4A1uDkt/GZMupHp+XiKCY8L+nKPjbX8aC\n' \
b'PmsiJweOFGfkN7QBzsdhsg==\n' \
b'-----END PRIVATE KEY-----\n'
test_pair = jwk.JWK()
test_pair.import_from_pem(test_private_key)
test_pair.import_from_pem(TEST_PRIVATE_KEY)
return test_pair


Expand All @@ -102,38 +129,15 @@ def test_jwt():

@pytest.fixture
def test_certificate():
cert = \
b'-----BEGIN CERTIFICATE-----\n' \
b'MIIDoDCCAogCCQDqKOyH38qgKDANBgkqhkiG9w0BAQsFADCBkTELMAkGA1UEBhMC\n' \
b'VVMxCzAJBgNVBAgMAkNBMRYwFAYDVQQHDA1UZXN0IExvY2FsaXR5MRIwEAYDVQQK\n' \
b'DAlMeWZ0IFRlc3QxEjAQBgNVBAsMCVRlc3QgVW5pdDENMAsGA1UEAwwEdGVzdDEm\n' \
b'MCQGCSqGSIb3DQEJARYXdGVzdC1zb21ldGhpbmdAbHlmdC5jb20wHhcNMjIxMDA3\n' \
b'MjMxNTM5WhcNMjMxMDA3MjMxNTM5WjCBkTELMAkGA1UEBhMCVVMxCzAJBgNVBAgM\n' \
b'AkNBMRYwFAYDVQQHDA1UZXN0IExvY2FsaXR5MRIwEAYDVQQKDAlMeWZ0IFRlc3Qx\n' \
b'EjAQBgNVBAsMCVRlc3QgVW5pdDENMAsGA1UEAwwEdGVzdDEmMCQGCSqGSIb3DQEJ\n' \
b'ARYXdGVzdC1zb21ldGhpbmdAbHlmdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB\n' \
b'DwAwggEKAoIBAQDUu1TaFSVNJELfYiV++1ZDAbrQ/flor64XOwK9RItzYqvnEUU1\n' \
b'+aHw4QvDNyKJsyH7/uqv42vDZiCiAH6T1RVLD30AdIswOQpVSsQcPEXcJfkIJ3ZQ\n' \
b'SWpuTVMbvJukrWK9ScPFkUyBC7FpkZ/RJ6pwiE2nVHjcrysAmK2/KB1Hk/NmO+fw\n' \
b'evIcYXwrxNLm9k6XM5xyJcl7ZK3GInZbuEcH4NdRYnAo7dCLtuwRFUW4fgSRUjjx\n' \
b'FXGJLd830iDgHzM0VyTq77gzKpn5VEZbIvDWgc8oxKHsrJKbyv3UGeC0q04K2EQR\n' \
b'6tq71gbS/hv9QCxmR6ygNqq8bz0dsmZXeeYxAgMBAAEwDQYJKoZIhvcNAQELBQAD\n' \
b'ggEBAIPQnGGAlwbK+f4V7SUUjXnsO7oVlMtTO7JWAk+g8W9colUeMDHW/Ygcwu3e\n' \
b'OlX5NSEV1wcQxuqyNWbEgrsZourePdVWujc/9qSVfaU/BjOj2CLylAf6ZNj/XpL/\n' \
b'PNCSCLM40cbhw/SeiNZ9WxkuuHiC32QxmR4kyvvXcHEGqVA2cOVAvncstW4gGowi\n' \
b'ObNYddXOmoOf8d5oHcO5vlhYyfbmShuq1PLygzUhG2jS+5aX9gmDtv+LtVGdXXWV\n' \
b'zSCh3+H4NSUWs3P1pKDFIUT3jGLQ3UavIS5KCizfBUbltx6LBSgmBbHf89RsKBbT\n' \
b'rxaukysD4sNgVHKptTq0fJ+2CjM=\n' \
b'-----END CERTIFICATE-----\n'
return cert
return TEST_CERTIFICATE


@pytest.fixture
def test_jwks():
return {
'alg': 'RS256',
'e': 'AQAB',
'kid': 'test-key',
'kid': '0h7R8dL0rU-b3p3onft_BPfuRW1Ld7YjsFnOWJuFXUE',
'kty': 'RSA',
'n': '1LtU2hUlTSRC32IlfvtWQwG60P35aK-uFzsCvUSLc2Kr5xFFNfmh8OELwzci'
'ibMh-_7qr-Nrw2YgogB-k9UVSw99AHSLMDkKVUrEHDxF3CX5CCd2UElqbk1T'
Expand All @@ -142,3 +146,31 @@ def test_jwks():
'RVxiS3fN9Ig4B8zNFck6u-4MyqZ-VRGWyLw1oHPKMSh7KySm8r91BngtKtOCt'
'hEEerau9YG0v4b_UAsZkesoDaqvG89HbJmV3nmMQ'
}


@pytest.fixture
def test_certificate_authorities():
return json.dumps({
'test': [
{
'crt': TEST_CERTIFICATE.decode('utf-8'),
'key': TEST_PRIVATE_KEY.decode('utf-8'),
'passphrase': None,
'kid': '0h7R8dL0rU-b3p3onft_BPfuRW1Ld7YjsFnOWJuFXUE',
},
{
'crt': TEST_CERTIFICATE.decode('utf-8'),
'key': TEST_PRIVATE_KEY.decode('utf-8'),
'passphrase': None,
'kid': 'test-key',
},
],
'dummy': [
{
'crt': TEST_CERTIFICATE.decode('utf-8'),
'key': TEST_PRIVATE_KEY.decode('utf-8'),
'passphrase': None,
'kid': '0h7R8dL0rU-b3p3onft_BPfuRW1Ld7YjsFnOWJuFXUE',
},
],
})
Loading

0 comments on commit b7d0dd5

Please sign in to comment.