signature_utils: move to explicit image metadata
In Nova, we are moving away from using an unnamed dictionary to represent Glance image metadata. This change alters the signature_utils module to take in the specific image properties as individual parameters instead of as a dictionary. Change-Id: Ia79c847de6ef55ddb1305f24fca47dfb822ad7c8
This commit is contained in:
parent
18014a7608
commit
d8532315e0
@ -73,12 +73,6 @@ MASK_GEN_ALGORITHMS = {
|
|||||||
'MGF1': padding.MGF1,
|
'MGF1': padding.MGF1,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Required image property names
|
|
||||||
SIGNATURE = 'img_signature'
|
|
||||||
HASH_METHOD = 'img_signature_hash_method'
|
|
||||||
KEY_TYPE = 'img_signature_key_type'
|
|
||||||
CERT_UUID = 'img_signature_certificate_uuid'
|
|
||||||
|
|
||||||
|
|
||||||
class SignatureKeyType(object):
|
class SignatureKeyType(object):
|
||||||
|
|
||||||
@ -116,17 +110,15 @@ class SignatureKeyType(object):
|
|||||||
|
|
||||||
|
|
||||||
# each key type will require its own verifier
|
# each key type will require its own verifier
|
||||||
def create_verifier_for_pss(signature, hash_method, public_key,
|
def create_verifier_for_pss(signature, hash_method, public_key):
|
||||||
image_properties):
|
|
||||||
"""Create the verifier to use when the key type is RSA-PSS.
|
"""Create the verifier to use when the key type is RSA-PSS.
|
||||||
|
|
||||||
:param signature: the decoded signature to use
|
:param signature: the decoded signature to use
|
||||||
:param hash_method: the hash method to use, as a cryptography object
|
:param hash_method: the hash method to use, as a cryptography object
|
||||||
:param public_key: the public key to use, as a cryptography object
|
:param public_key: the public key to use, as a cryptography object
|
||||||
:param image_properties: the key-value properties about the image
|
|
||||||
:returns: the verifier to use to verify the signature for RSA-PSS
|
|
||||||
:raises: SignatureVerificationError if the RSA-PSS specific properties
|
:raises: SignatureVerificationError if the RSA-PSS specific properties
|
||||||
are invalid
|
are invalid
|
||||||
|
:returns: the verifier to use to verify the signature for RSA-PSS
|
||||||
"""
|
"""
|
||||||
# default to MGF1
|
# default to MGF1
|
||||||
mgf = padding.MGF1(hash_method)
|
mgf = padding.MGF1(hash_method)
|
||||||
@ -142,14 +134,12 @@ def create_verifier_for_pss(signature, hash_method, public_key,
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_verifier_for_ecc(signature, hash_method, public_key,
|
def create_verifier_for_ecc(signature, hash_method, public_key):
|
||||||
image_properties):
|
|
||||||
"""Create the verifier to use when the key type is ECC_*.
|
"""Create the verifier to use when the key type is ECC_*.
|
||||||
|
|
||||||
:param signature: the decoded signature to use
|
:param signature: the decoded signature to use
|
||||||
:param hash_method: the hash method to use, as a cryptography object
|
:param hash_method: the hash method to use, as a cryptography object
|
||||||
:param public_key: the public key to use, as a cryptography object
|
:param public_key: the public key to use, as a cryptography object
|
||||||
:param image_properties: the key-value properties about the image
|
|
||||||
:returns: the verifier to use to verify the signature for ECC_*.
|
:returns: the verifier to use to verify the signature for ECC_*.
|
||||||
"""
|
"""
|
||||||
# return the verifier
|
# return the verifier
|
||||||
@ -159,14 +149,12 @@ def create_verifier_for_ecc(signature, hash_method, public_key,
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def create_verifier_for_dsa(signature, hash_method, public_key,
|
def create_verifier_for_dsa(signature, hash_method, public_key):
|
||||||
image_properties):
|
|
||||||
"""Create the verifier to use when the key type is DSA
|
"""Create the verifier to use when the key type is DSA
|
||||||
|
|
||||||
:param signature: the decoded signature to use
|
:param signature: the decoded signature to use
|
||||||
:param hash_method: the hash method to use, as a cryptography object
|
:param hash_method: the hash method to use, as a cryptography object
|
||||||
:param public_key: the public key to use, as a cryptography object
|
:param public_key: the public key to use, as a cryptography object
|
||||||
:param image_properties: the key-value properties about the image
|
|
||||||
:returns: the verifier to use to verify the signature for DSA
|
:returns: the verifier to use to verify the signature for DSA
|
||||||
"""
|
"""
|
||||||
# return the verifier
|
# return the verifier
|
||||||
@ -187,48 +175,45 @@ for curve in ECC_CURVES:
|
|||||||
create_verifier_for_ecc)
|
create_verifier_for_ecc)
|
||||||
|
|
||||||
|
|
||||||
def should_verify_signature(image_properties):
|
def get_verifier(context, img_signature_certificate_uuid,
|
||||||
"""Determine whether a signature should be verified.
|
img_signature_hash_method, img_signature,
|
||||||
|
img_signature_key_type):
|
||||||
Using the image properties, determine whether existing properties indicate
|
"""Instantiate signature properties and use them to create a verifier.
|
||||||
that signature verification should be done.
|
|
||||||
|
|
||||||
:param image_properties: the key-value properties about the image
|
|
||||||
:returns: True, if signature metadata properties exist, False otherwise
|
|
||||||
"""
|
|
||||||
return (image_properties is not None and
|
|
||||||
CERT_UUID in image_properties and
|
|
||||||
HASH_METHOD in image_properties and
|
|
||||||
SIGNATURE in image_properties and
|
|
||||||
KEY_TYPE in image_properties)
|
|
||||||
|
|
||||||
|
|
||||||
def get_verifier(context, image_properties):
|
|
||||||
"""Retrieve the image properties and use them to create a verifier.
|
|
||||||
|
|
||||||
:param context: the user context for authentication
|
:param context: the user context for authentication
|
||||||
:param image_properties: the key-value properties about the image
|
:param img_signature_certificate_uuid:
|
||||||
|
uuid of signing certificate stored in key manager
|
||||||
|
:param img_signature_hash_method:
|
||||||
|
string denoting hash method used to compute signature
|
||||||
|
:param img_signature: string of base64 encoding of signature
|
||||||
|
:param img_signature_key_type:
|
||||||
|
string denoting type of keypair used to compute signature
|
||||||
:returns: instance of
|
:returns: instance of
|
||||||
cryptography.hazmat.primitives.asymmetric.AsymmetricVerificationContext
|
cryptography.hazmat.primitives.asymmetric.AsymmetricVerificationContext
|
||||||
:raises: SignatureVerificationError if we fail to build the verifier
|
:raises: SignatureVerificationError if we fail to build the verifier
|
||||||
"""
|
"""
|
||||||
if not should_verify_signature(image_properties):
|
image_meta_props = {'img_signature_uuid': img_signature_certificate_uuid,
|
||||||
raise exception.SignatureVerificationError(
|
'img_signature_hash_method': img_signature_hash_method,
|
||||||
reason=_('Required image properties for signature verification'
|
'img_signature': img_signature,
|
||||||
' do not exist. Cannot verify signature.'))
|
'img_signature_key_type': img_signature_key_type}
|
||||||
|
for key in image_meta_props.keys():
|
||||||
|
if image_meta_props[key] is None:
|
||||||
|
raise exception.SignatureVerificationError(
|
||||||
|
reason=_('Required image properties for signature verification'
|
||||||
|
' do not exist. Cannot verify signature. Missing'
|
||||||
|
' property: %s') % key)
|
||||||
|
|
||||||
signature = get_signature(image_properties[SIGNATURE])
|
signature = get_signature(img_signature)
|
||||||
hash_method = get_hash_method(image_properties[HASH_METHOD])
|
hash_method = get_hash_method(img_signature_hash_method)
|
||||||
signature_key_type = SignatureKeyType.lookup(image_properties[KEY_TYPE])
|
signature_key_type = SignatureKeyType.lookup(img_signature_key_type)
|
||||||
public_key = get_public_key(context,
|
public_key = get_public_key(context,
|
||||||
image_properties[CERT_UUID],
|
img_signature_certificate_uuid,
|
||||||
signature_key_type)
|
signature_key_type)
|
||||||
|
|
||||||
# create the verifier based on the signature key type
|
# create the verifier based on the signature key type
|
||||||
verifier = signature_key_type.create_verifier(signature,
|
verifier = signature_key_type.create_verifier(signature,
|
||||||
hash_method,
|
hash_method,
|
||||||
public_key,
|
public_key)
|
||||||
image_properties)
|
|
||||||
if verifier:
|
if verifier:
|
||||||
return verifier
|
return verifier
|
||||||
else:
|
else:
|
||||||
|
@ -41,12 +41,6 @@ TEST_ECC_PRIVATE_KEY = ec.generate_private_key(ec.SECP521R1(),
|
|||||||
TEST_DSA_PRIVATE_KEY = dsa.generate_private_key(key_size=3072,
|
TEST_DSA_PRIVATE_KEY = dsa.generate_private_key(key_size=3072,
|
||||||
backend=default_backend())
|
backend=default_backend())
|
||||||
|
|
||||||
# Required image property names
|
|
||||||
SIGNATURE = signature_utils.SIGNATURE
|
|
||||||
HASH_METHOD = signature_utils.HASH_METHOD
|
|
||||||
KEY_TYPE = signature_utils.KEY_TYPE
|
|
||||||
CERT_UUID = signature_utils.CERT_UUID
|
|
||||||
|
|
||||||
|
|
||||||
class FakeKeyManager(object):
|
class FakeKeyManager(object):
|
||||||
|
|
||||||
@ -111,31 +105,6 @@ class BadPublicKey(object):
|
|||||||
class TestSignatureUtils(test.NoDBTestCase):
|
class TestSignatureUtils(test.NoDBTestCase):
|
||||||
"""Test methods of signature_utils"""
|
"""Test methods of signature_utils"""
|
||||||
|
|
||||||
def test_should_verify_signature(self):
|
|
||||||
image_props = {CERT_UUID: 'CERT_UUID',
|
|
||||||
HASH_METHOD: 'HASH_METHOD',
|
|
||||||
SIGNATURE: 'SIGNATURE',
|
|
||||||
KEY_TYPE: 'SIG_KEY_TYPE'}
|
|
||||||
self.assertTrue(signature_utils.should_verify_signature(image_props))
|
|
||||||
|
|
||||||
def test_should_verify_signature_fail(self):
|
|
||||||
bad_image_properties = [{CERT_UUID: 'CERT_UUID',
|
|
||||||
HASH_METHOD: 'HASH_METHOD',
|
|
||||||
SIGNATURE: 'SIGNATURE'},
|
|
||||||
{CERT_UUID: 'CERT_UUID',
|
|
||||||
HASH_METHOD: 'HASH_METHOD',
|
|
||||||
KEY_TYPE: 'SIG_KEY_TYPE'},
|
|
||||||
{CERT_UUID: 'CERT_UUID',
|
|
||||||
SIGNATURE: 'SIGNATURE',
|
|
||||||
KEY_TYPE: 'SIG_KEY_TYPE'},
|
|
||||||
{HASH_METHOD: 'HASH_METHOD',
|
|
||||||
SIGNATURE: 'SIGNATURE',
|
|
||||||
KEY_TYPE: 'SIG_KEY_TYPE'}]
|
|
||||||
|
|
||||||
for bad_props in bad_image_properties:
|
|
||||||
result = signature_utils.should_verify_signature(bad_props)
|
|
||||||
self.assertFalse(result)
|
|
||||||
|
|
||||||
@mock.patch('nova.signature_utils.get_public_key')
|
@mock.patch('nova.signature_utils.get_public_key')
|
||||||
def test_verify_signature_PSS(self, mock_get_pub_key):
|
def test_verify_signature_PSS(self, mock_get_pub_key):
|
||||||
data = b'224626ae19824466f2a7f39ab7b80f7f'
|
data = b'224626ae19824466f2a7f39ab7b80f7f'
|
||||||
@ -150,12 +119,10 @@ class TestSignatureUtils(test.NoDBTestCase):
|
|||||||
)
|
)
|
||||||
signer.update(data)
|
signer.update(data)
|
||||||
signature = base64.b64encode(signer.finalize())
|
signature = base64.b64encode(signer.finalize())
|
||||||
image_props = {CERT_UUID:
|
img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693'
|
||||||
'fea14bc2-d75f-4ba5-bccc-b5c924ad0693',
|
verifier = signature_utils.get_verifier(None, img_sig_cert_uuid,
|
||||||
HASH_METHOD: hash_name,
|
hash_name, signature,
|
||||||
KEY_TYPE: 'RSA-PSS',
|
signature_utils.RSA_PSS)
|
||||||
SIGNATURE: signature}
|
|
||||||
verifier = signature_utils.get_verifier(None, image_props)
|
|
||||||
verifier.update(data)
|
verifier.update(data)
|
||||||
verifier.verify()
|
verifier.verify()
|
||||||
|
|
||||||
@ -182,12 +149,11 @@ class TestSignatureUtils(test.NoDBTestCase):
|
|||||||
)
|
)
|
||||||
signer.update(data)
|
signer.update(data)
|
||||||
signature = base64.b64encode(signer.finalize())
|
signature = base64.b64encode(signer.finalize())
|
||||||
image_props = {CERT_UUID:
|
img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693'
|
||||||
'fea14bc2-d75f-4ba5-bccc-b5c924ad0693',
|
verifier = signature_utils.get_verifier(None,
|
||||||
HASH_METHOD: hash_name,
|
img_sig_cert_uuid,
|
||||||
KEY_TYPE: key_type_name,
|
hash_name, signature,
|
||||||
SIGNATURE: signature}
|
key_type_name)
|
||||||
verifier = signature_utils.get_verifier(None, image_props)
|
|
||||||
verifier.update(data)
|
verifier.update(data)
|
||||||
verifier.verify()
|
verifier.verify()
|
||||||
|
|
||||||
@ -201,12 +167,10 @@ class TestSignatureUtils(test.NoDBTestCase):
|
|||||||
)
|
)
|
||||||
signer.update(data)
|
signer.update(data)
|
||||||
signature = base64.b64encode(signer.finalize())
|
signature = base64.b64encode(signer.finalize())
|
||||||
image_props = {CERT_UUID:
|
img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693'
|
||||||
'fea14bc2-d75f-4ba5-bccc-b5c924ad0693',
|
verifier = signature_utils.get_verifier(None, img_sig_cert_uuid,
|
||||||
HASH_METHOD: hash_name,
|
hash_name, signature,
|
||||||
KEY_TYPE: 'DSA',
|
signature_utils.DSA)
|
||||||
SIGNATURE: signature}
|
|
||||||
verifier = signature_utils.get_verifier(None, image_props)
|
|
||||||
verifier.update(data)
|
verifier.update(data)
|
||||||
verifier.verify()
|
verifier.verify()
|
||||||
|
|
||||||
@ -214,52 +178,43 @@ class TestSignatureUtils(test.NoDBTestCase):
|
|||||||
def test_verify_signature_bad_signature(self, mock_get_pub_key):
|
def test_verify_signature_bad_signature(self, mock_get_pub_key):
|
||||||
data = b'224626ae19824466f2a7f39ab7b80f7f'
|
data = b'224626ae19824466f2a7f39ab7b80f7f'
|
||||||
mock_get_pub_key.return_value = TEST_RSA_PRIVATE_KEY.public_key()
|
mock_get_pub_key.return_value = TEST_RSA_PRIVATE_KEY.public_key()
|
||||||
image_properties = {CERT_UUID:
|
img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693'
|
||||||
'fea14bc2-d75f-4ba5-bccc-b5c924ad0693',
|
verifier = signature_utils.get_verifier(None, img_sig_cert_uuid,
|
||||||
HASH_METHOD: 'SHA-256',
|
'SHA-256', 'BLAH',
|
||||||
KEY_TYPE: 'RSA-PSS',
|
signature_utils.RSA_PSS)
|
||||||
SIGNATURE: 'BLAH'}
|
|
||||||
verifier = signature_utils.get_verifier(None, image_properties)
|
|
||||||
verifier.update(data)
|
verifier.update(data)
|
||||||
self.assertRaises(crypto_exceptions.InvalidSignature,
|
self.assertRaises(crypto_exceptions.InvalidSignature,
|
||||||
verifier.verify)
|
verifier.verify)
|
||||||
|
|
||||||
@mock.patch('nova.signature_utils.should_verify_signature')
|
def test_get_verifier_invalid_image_props(self):
|
||||||
def test_get_verifier_invalid_image_props(self, mock_should):
|
|
||||||
mock_should.return_value = False
|
|
||||||
self.assertRaisesRegex(exception.SignatureVerificationError,
|
self.assertRaisesRegex(exception.SignatureVerificationError,
|
||||||
'Required image properties for signature'
|
'Required image properties for signature'
|
||||||
' verification do not exist. Cannot verify'
|
' verification do not exist. Cannot verify'
|
||||||
' signature.',
|
' signature. Missing property: .*',
|
||||||
signature_utils.get_verifier,
|
signature_utils.get_verifier,
|
||||||
None, None)
|
None, None, 'SHA-256', 'BLAH',
|
||||||
|
signature_utils.RSA_PSS)
|
||||||
|
|
||||||
@mock.patch('nova.signature_utils.get_public_key')
|
@mock.patch('nova.signature_utils.get_public_key')
|
||||||
def test_verify_signature_bad_sig_key_type(self, mock_get_pub_key):
|
def test_verify_signature_bad_sig_key_type(self, mock_get_pub_key):
|
||||||
mock_get_pub_key.return_value = TEST_RSA_PRIVATE_KEY.public_key()
|
mock_get_pub_key.return_value = TEST_RSA_PRIVATE_KEY.public_key()
|
||||||
image_properties = {CERT_UUID:
|
img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693'
|
||||||
'fea14bc2-d75f-4ba5-bccc-b5c924ad0693',
|
|
||||||
HASH_METHOD: 'SHA-256',
|
|
||||||
KEY_TYPE: 'BLAH',
|
|
||||||
SIGNATURE: 'BLAH'}
|
|
||||||
self.assertRaisesRegex(exception.SignatureVerificationError,
|
self.assertRaisesRegex(exception.SignatureVerificationError,
|
||||||
'Invalid signature key type: .*',
|
'Invalid signature key type: .*',
|
||||||
signature_utils.get_verifier,
|
signature_utils.get_verifier,
|
||||||
None, image_properties)
|
None, img_sig_cert_uuid, 'SHA-256',
|
||||||
|
'BLAH', 'BLAH')
|
||||||
|
|
||||||
@mock.patch('nova.signature_utils.get_public_key')
|
@mock.patch('nova.signature_utils.get_public_key')
|
||||||
def test_get_verifier_none(self, mock_get_pub_key):
|
def test_get_verifier_none(self, mock_get_pub_key):
|
||||||
mock_get_pub_key.return_value = BadPublicKey()
|
mock_get_pub_key.return_value = BadPublicKey()
|
||||||
image_properties = {CERT_UUID:
|
img_sig_cert_uuid = 'fea14bc2-d75f-4ba5-bccc-b5c924ad0693'
|
||||||
'fea14bc2-d75f-4ba5-bccc-b5c924ad0693',
|
|
||||||
HASH_METHOD: 'SHA-256',
|
|
||||||
KEY_TYPE: 'RSA-PSS',
|
|
||||||
SIGNATURE: 'BLAH'}
|
|
||||||
self.assertRaisesRegex(exception.SignatureVerificationError,
|
self.assertRaisesRegex(exception.SignatureVerificationError,
|
||||||
'Error occurred while creating'
|
'Error occurred while creating'
|
||||||
' the verifier',
|
' the verifier',
|
||||||
signature_utils.get_verifier,
|
signature_utils.get_verifier,
|
||||||
None, image_properties)
|
None, img_sig_cert_uuid, 'SHA-256',
|
||||||
|
'BLAH', signature_utils.RSA_PSS)
|
||||||
|
|
||||||
def test_get_signature(self):
|
def test_get_signature(self):
|
||||||
signature = b'A' * 256
|
signature = b'A' * 256
|
||||||
@ -285,7 +240,7 @@ class TestSignatureUtils(test.NoDBTestCase):
|
|||||||
signature_utils.get_hash_method, 'SHA-2')
|
signature_utils.get_hash_method, 'SHA-2')
|
||||||
|
|
||||||
def test_signature_key_type_lookup(self):
|
def test_signature_key_type_lookup(self):
|
||||||
for sig_format in ['RSA-PSS', 'DSA']:
|
for sig_format in [signature_utils.RSA_PSS, signature_utils.DSA]:
|
||||||
sig_key_type = signature_utils.SignatureKeyType.lookup(sig_format)
|
sig_key_type = signature_utils.SignatureKeyType.lookup(sig_format)
|
||||||
self.assertIsInstance(sig_key_type,
|
self.assertIsInstance(sig_key_type,
|
||||||
signature_utils.SignatureKeyType)
|
signature_utils.SignatureKeyType)
|
||||||
@ -301,7 +256,9 @@ class TestSignatureUtils(test.NoDBTestCase):
|
|||||||
def test_get_public_key_rsa(self, mock_get_cert):
|
def test_get_public_key_rsa(self, mock_get_cert):
|
||||||
fake_cert = FakeCryptoCertificate()
|
fake_cert = FakeCryptoCertificate()
|
||||||
mock_get_cert.return_value = fake_cert
|
mock_get_cert.return_value = fake_cert
|
||||||
sig_key_type = signature_utils.SignatureKeyType.lookup('RSA-PSS')
|
sig_key_type = signature_utils.SignatureKeyType.lookup(
|
||||||
|
signature_utils.RSA_PSS
|
||||||
|
)
|
||||||
result_pub_key = signature_utils.get_public_key(None, None,
|
result_pub_key = signature_utils.get_public_key(None, None,
|
||||||
sig_key_type)
|
sig_key_type)
|
||||||
self.assertEqual(fake_cert.public_key(), result_pub_key)
|
self.assertEqual(fake_cert.public_key(), result_pub_key)
|
||||||
@ -319,7 +276,9 @@ class TestSignatureUtils(test.NoDBTestCase):
|
|||||||
def test_get_public_key_dsa(self, mock_get_cert):
|
def test_get_public_key_dsa(self, mock_get_cert):
|
||||||
fake_cert = FakeCryptoCertificate(TEST_DSA_PRIVATE_KEY.public_key())
|
fake_cert = FakeCryptoCertificate(TEST_DSA_PRIVATE_KEY.public_key())
|
||||||
mock_get_cert.return_value = fake_cert
|
mock_get_cert.return_value = fake_cert
|
||||||
sig_key_type = signature_utils.SignatureKeyType.lookup('DSA')
|
sig_key_type = signature_utils.SignatureKeyType.lookup(
|
||||||
|
signature_utils.DSA
|
||||||
|
)
|
||||||
result_pub_key = signature_utils.get_public_key(None, None,
|
result_pub_key = signature_utils.get_public_key(None, None,
|
||||||
sig_key_type)
|
sig_key_type)
|
||||||
self.assertEqual(fake_cert.public_key(), result_pub_key)
|
self.assertEqual(fake_cert.public_key(), result_pub_key)
|
||||||
@ -328,7 +287,9 @@ class TestSignatureUtils(test.NoDBTestCase):
|
|||||||
def test_get_public_key_invalid_key(self, mock_get_certificate):
|
def test_get_public_key_invalid_key(self, mock_get_certificate):
|
||||||
bad_pub_key = 'A' * 256
|
bad_pub_key = 'A' * 256
|
||||||
mock_get_certificate.return_value = FakeCryptoCertificate(bad_pub_key)
|
mock_get_certificate.return_value = FakeCryptoCertificate(bad_pub_key)
|
||||||
sig_key_type = signature_utils.SignatureKeyType.lookup('RSA-PSS')
|
sig_key_type = signature_utils.SignatureKeyType.lookup(
|
||||||
|
signature_utils.RSA_PSS
|
||||||
|
)
|
||||||
self.assertRaisesRegex(exception.SignatureVerificationError,
|
self.assertRaisesRegex(exception.SignatureVerificationError,
|
||||||
'Invalid public key type for '
|
'Invalid public key type for '
|
||||||
'signature key type: .*',
|
'signature key type: .*',
|
||||||
|
Loading…
x
Reference in New Issue
Block a user