Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ venv/
aws_lambda_libs/
lambda_configs/
.pytest_cache/
.vscode/settings.json
*env/
17 changes: 10 additions & 7 deletions bless/ssh/certificate_authorities/rsa_certificate_authority.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,27 +4,30 @@
:license: Apache, see LICENSE for more details.
"""
from bless.ssh.certificate_authorities.ssh_certificate_authority import \
SSHCertificateAuthority
SSHCertificateAuthority, SSHCertificateSignetureKeyType
from bless.ssh.protocol.ssh_protocol import pack_ssh_mpint, pack_ssh_string
from bless.ssh.public_keys.ssh_public_key import SSHPublicKeyType
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.serialization import load_pem_private_key


class RSACertificateAuthority(SSHCertificateAuthority):
def __init__(self, pem_private_key, private_key_password=None):
def __init__(self, pem_private_key, private_key_password=None, cert_type="sha2"):
"""
RSA Certificate Authority used to sign certificates.
:param pem_private_key: PEM formatted RSA Private Key. It should be encrypted with a
password, but that is not required.
:param private_key_password: Password to decrypt the PEM RSA Private Key, if it is
encrypted. Which it should be.
"""
super(SSHCertificateAuthority, self).__init__()
self.public_key_type = SSHPublicKeyType.RSA

super().__init__()
if cert_type == "sha1":
self.public_key_type = SSHCertificateSignetureKeyType.RSA
self.algo = hashes.SHA1()
else:
self.public_key_type = SSHCertificateSignetureKeyType.RSA_SHA2
self.algo = hashes.SHA512()
self.private_key = load_pem_private_key(pem_private_key,
private_key_password,
default_backend())
Expand Down Expand Up @@ -53,6 +56,6 @@ def sign(self, body):
signature key.
:return: SSH RSA Signature.
"""
signature = self.private_key.sign(body, padding.PKCS1v15(), hashes.SHA1())
signature = self.private_key.sign(body, padding.PKCS1v15(), self.algo)

return self._serialize_signature(signature)
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@
from bless.ssh.protocol.ssh_protocol import pack_ssh_string


class SSHCertificateSignetureKeyType(object):
'''
SHA2 enhancement: implemention of rfc8332 section 3 rsa-sha2-512 to support SHA2 public key signing algorithm
'''
RSA = 'ssh-rsa'
RSA_SHA2 = 'rsa-sha2-512'
ED25519 = 'ssh-ed25519'


class SSHCertificateAuthorityPrivateKeyType(object):
RSA = '-----BEGIN RSA PRIVATE KEY-----\n'
# todo support other CA Private Key Types
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
SSHCertificateAuthorityPrivateKeyType


def get_ssh_certificate_authority(private_key, password=None):
def get_ssh_certificate_authority(private_key, password=None, cert_type="sha2"):
"""
Returns the proper SSHCertificateAuthority instance based off the private_key type.
:param private_key: ASCII bytes of an SSH compatible Private Key (e.g., PEM or SSH Protocol 2 Private Key).
Expand All @@ -18,6 +18,7 @@ def get_ssh_certificate_authority(private_key, password=None):
:return: An SSHCertificateAuthority instance.
"""
if private_key.decode('ascii').startswith(SSHCertificateAuthorityPrivateKeyType.RSA):
return RSACertificateAuthority(private_key, password)
return RSACertificateAuthority(pem_private_key=private_key, private_key_password=password, cert_type=cert_type)

else:
raise TypeError("Unsupported CA Private Key Type")
4 changes: 2 additions & 2 deletions bless/ssh/public_keys/ed25519_public_key.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,15 @@ def __init__(self, ssh_public_key):
except TypeError:
raise ValueError('Key is not in the proper format.')

inner_key_type, rest = ssh._ssh_read_next_string(decoded_data)
inner_key_type, rest = ssh._get_sshstr(decoded_data)

if inner_key_type != key_type.encode("utf-8"):
raise ValueError(
'Key header and key body contain different key type values.'
)

# ed25519 public key is a single string https://tools.ietf.org/html/rfc8032#section-5.1.5
self.a, rest = ssh._ssh_read_next_string(rest)
self.a, rest = ssh._get_sshstr(rest)

key_bytes = base64.b64decode(split_ssh_public_key[1])
fingerprint = hashlib.md5(key_bytes).hexdigest()
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ boto3==1.9.151
botocore==1.12.151
cffi==1.12.3
# Prevents fatal crashes when building on Mac
cryptography==2.9.0
cryptography==3.2.1
docutils==0.14
ipaddress==1.0.22
jmespath==0.9.4
Expand Down
26 changes: 20 additions & 6 deletions tests/ssh/test_ssh_certificate_authority_factory.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,38 @@
import pytest

from bless.ssh.certificate_authorities.rsa_certificate_authority import RSACertificateAuthority
from bless.ssh.certificate_authorities.rsa_certificate_authority import RSACertificateAuthority, SSHCertificateSignetureKeyType
from bless.ssh.certificate_authorities.ssh_certificate_authority_factory import \
get_ssh_certificate_authority
from bless.ssh.public_keys.ssh_public_key import SSHPublicKeyType
from tests.ssh.vectors import RSA_CA_PRIVATE_KEY, RSA_CA_PRIVATE_KEY_PASSWORD, \
RSA_CA_SSH_PUBLIC_KEY, RSA_CA_PRIVATE_KEY_NOT_ENCRYPTED
RSA_CA_SSH_PUBLIC_KEY, RSA_CA_PRIVATE_KEY_NOT_ENCRYPTED, RSA_SHA1_CA_SSH_PUBLIC_KEY, RSA_SHA1_CA_PRIVATE_KEY


def test_valid_key_valid_password():
ca = get_ssh_certificate_authority(RSA_CA_PRIVATE_KEY, RSA_CA_PRIVATE_KEY_PASSWORD)
assert isinstance(ca, RSACertificateAuthority)
assert SSHPublicKeyType.RSA == ca.public_key_type
assert SSHCertificateSignetureKeyType.RSA_SHA2 == ca.public_key_type
assert 65537 == ca.e
assert ca.get_signature_key() == RSA_CA_SSH_PUBLIC_KEY


def test_valid_key_valid_password_sha1():
ca = get_ssh_certificate_authority(private_key=RSA_SHA1_CA_PRIVATE_KEY, password=RSA_CA_PRIVATE_KEY_PASSWORD, cert_type="sha1")
assert isinstance(ca, RSACertificateAuthority)
assert SSHCertificateSignetureKeyType.RSA == ca.public_key_type
assert 65537 == ca.e
assert ca.get_signature_key() == RSA_SHA1_CA_SSH_PUBLIC_KEY


def test_valid_key_not_encrypted():
ca = get_ssh_certificate_authority(RSA_CA_PRIVATE_KEY_NOT_ENCRYPTED)
assert SSHPublicKeyType.RSA == ca.public_key_type
ca = get_ssh_certificate_authority(private_key=RSA_CA_PRIVATE_KEY_NOT_ENCRYPTED)
assert SSHCertificateSignetureKeyType.RSA_SHA2 == ca.public_key_type
assert 65537 == ca.e


def test_valid_key_not_encrypted_sha1():
ca = get_ssh_certificate_authority(private_key=RSA_CA_PRIVATE_KEY_NOT_ENCRYPTED, cert_type="sha1")
assert SSHCertificateSignetureKeyType.RSA == ca.public_key_type
assert 65537 == ca.e


Expand All @@ -39,4 +53,4 @@ def test_valid_key_not_encrypted_invalid_pass():

def test_invalid_key():
with pytest.raises(TypeError):
get_ssh_certificate_authority(b'bogus')
get_ssh_certificate_authority(b'bogus')
6 changes: 3 additions & 3 deletions tests/ssh/test_ssh_certificate_rsa.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import base64

import pytest
from cryptography.hazmat.primitives.serialization.ssh import _ssh_read_next_string
from cryptography.hazmat.primitives.serialization.ssh import _get_sshstr

from bless.ssh.certificate_authorities.rsa_certificate_authority import RSACertificateAuthority
from bless.ssh.certificates.rsa_certificate_builder import RSACertificateBuilder
Expand Down Expand Up @@ -39,8 +39,8 @@ def get_basic_cert_builder_rsa(cert_type=SSHCertificateType.USER,

def extract_nonce_from_cert(cert_file):
cert = cert_file.split(' ')[1]
cert_type, cert_remainder = _ssh_read_next_string(base64.b64decode(cert))
nonce, cert_remainder = _ssh_read_next_string(cert_remainder)
cert_type, cert_remainder = _get_sshstr(base64.b64decode(cert))
nonce, cert_remainder = _get_sshstr(cert_remainder)
return nonce


Expand Down
6 changes: 3 additions & 3 deletions tests/ssh/test_ssh_public_key_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,12 @@
EXAMPLE_ECDSA_PUBLIC_KEY, EXAMPLE_RSA_PUBLIC_KEY_N, EXAMPLE_RSA_PUBLIC_KEY_E, \
EXAMPLE_ED25519_PUBLIC_KEY_A


def test_valid_rsa():
pub_key = get_ssh_public_key(EXAMPLE_RSA_PUBLIC_KEY)
assert 'Test RSA User Key' == pub_key.key_comment
assert './user_ca' == pub_key.key_comment
assert EXAMPLE_RSA_PUBLIC_KEY_N == pub_key.n
assert EXAMPLE_RSA_PUBLIC_KEY_E == pub_key.e
assert 'RSA 57:3d:48:4c:65:90:30:8e:39:ba:d8:fa:d0:20:2e:6c' == pub_key.fingerprint
assert 'RSA 09:26:ae:1a:a1:27:7c:42:c3:00:cc:1e:82:0b:1f:2f'== pub_key.fingerprint


def test_valid_ed25519():
Expand All @@ -24,3 +23,4 @@ def test_valid_ed25519():
def test_invalid_key():
with pytest.raises(TypeError):
get_ssh_public_key(EXAMPLE_ECDSA_PUBLIC_KEY)

8 changes: 4 additions & 4 deletions tests/ssh/test_ssh_public_key_rsa.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@
from tests.ssh.vectors import EXAMPLE_RSA_PUBLIC_KEY, \
EXAMPLE_RSA_PUBLIC_KEY_NO_DESCRIPTION, EXAMPLE_ECDSA_PUBLIC_KEY, EXAMPLE_RSA_PUBLIC_KEY_N, \
EXAMPLE_RSA_PUBLIC_KEY_E, EXAMPLE_RSA_PUBLIC_KEY_2048, EXAMPLE_RSA_PUBLIC_KEY_1024, \
EXAMPLE_RSA_PUBLIC_KEY_SMALLPRIME, EXAMPLE_RSA_PUBLIC_KEY_E3
EXAMPLE_RSA_PUBLIC_KEY_SMALLPRIME, EXAMPLE_RSA_PUBLIC_KEY_E3, EXAMPLE_RSA_PUBLIC_KEY_N_NO_DESCRIPTION


def test_valid_key():
pub_key = RSAPublicKey(EXAMPLE_RSA_PUBLIC_KEY)
assert 'Test RSA User Key' == pub_key.key_comment
assert './user_ca' == pub_key.key_comment
assert EXAMPLE_RSA_PUBLIC_KEY_N == pub_key.n
assert EXAMPLE_RSA_PUBLIC_KEY_E == pub_key.e
assert 'RSA 57:3d:48:4c:65:90:30:8e:39:ba:d8:fa:d0:20:2e:6c' == pub_key.fingerprint
assert 'RSA 09:26:ae:1a:a1:27:7c:42:c3:00:cc:1e:82:0b:1f:2f' == pub_key.fingerprint


def test_valid_key_no_description():
pub_key = RSAPublicKey(EXAMPLE_RSA_PUBLIC_KEY_NO_DESCRIPTION)
assert '' == pub_key.key_comment
assert EXAMPLE_RSA_PUBLIC_KEY_N == pub_key.n
assert EXAMPLE_RSA_PUBLIC_KEY_N_NO_DESCRIPTION == pub_key.n
assert EXAMPLE_RSA_PUBLIC_KEY_E == pub_key.e
assert 'RSA 57:3d:48:4c:65:90:30:8e:39:ba:d8:fa:d0:20:2e:6c' == pub_key.fingerprint

Expand Down
Loading