Skip to content

Commit

Permalink
Signature decoding now works
Browse files Browse the repository at this point in the history
  • Loading branch information
scheibling committed Nov 11, 2023
1 parent 505c338 commit 339dae2
Show file tree
Hide file tree
Showing 4 changed files with 421 additions and 0 deletions.
11 changes: 11 additions & 0 deletions make_signatures.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash
mkdir -p ./testkeys

ssh-keygen -t rsa -b 4096 -f testkeys/id_rsa -N ''
ssh-keygen -t ecdsa -f testkeys/id_ecdsa -N ''
ssh-keygen -t ed25519 -f testkeys/id_ed25519 -N ''
echo "Hello World" | tee testkeys/rsa.txt | tee testkeys/ecdsa.txt | tee testkeys/ed25519.txt

ssh-keygen -Y sign -n hello@world -f testkeys/id_rsa testkeys/rsa.txt
ssh-keygen -Y sign -n hello@world -f testkeys/id_ecdsa testkeys/ecdsa.txt
ssh-keygen -Y sign -n hello@world -f testkeys/id_ed25519 testkeys/ed25519.txt
70 changes: 70 additions & 0 deletions signatures.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
from src.sshkey_tools import (
fields as _F,
keys as _K,
exceptions as _E,
signatures as _S
)
rsa_pub = _K.PublicKey.from_file('testkeys/id_rsa.pub')
rsa_priv = _K.PrivateKey.from_file('testkeys/id_rsa')
ecdsa_pub = _K.PublicKey.from_file('testkeys/id_ecdsa.pub')
ecdsa_priv = _K.PrivateKey.from_file('testkeys/id_ecdsa')
ed25519_pub = _K.PublicKey.from_file('testkeys/id_ed25519.pub')
ed25519_priv = _K.PrivateKey.from_file('testkeys/id_ed25519')

rsa_data = open('testkeys/rsa.txt', 'rb').read()
ecdsa_data = open('testkeys/ecdsa.txt', 'rb').read()
ed25519_data = open('testkeys/ed25519.txt', 'rb').read()

signature_fields = _S.SignatureFieldset(
hash_algorithm="sha512",
namespace="hello@world"
)

# rsa_new_sig = _S.SSHSignature(rsa_priv, signature_fields)
# rsa_new_sig.sign(rsa_data)
# rsa_new_sig.to_file('testkeys/rsa.txt.sig2')

ecdsa_new_sig = _S.SSHSignature(ecdsa_priv, signature_fields)
ecdsa_new_sig.sign(ecdsa_data)
ecdsa_new_sig.to_file('testkeys/ecdsa.txt.sig2')

# ed25519_new_sig = _S.SSHSignature(ed25519_priv, signature_fields)
# ed25519_new_sig.sign(ed25519_data)
# ed25519_new_sig.to_file('testkeys/ed25519.txt.sig')

# Validate files created with ssh-keygen (WORKS!)
# rsa_sign = _S.SSHSignature.from_file('testkeys/rsa.txt.sig')
# rsa_sign2 = _S.SSHSignature.from_file('testkeys/rsa.txt.sig2')
print(1)
ecdsa_sign = _S.SSHSignature.from_file('testkeys/ecdsa.txt.sig')
print(2)
ecdsa_sign2 = _S.SSHSignature.from_file('testkeys/ecdsa.txt.sig2')
print(3)
ed25519_sign = _S.SSHSignature.from_file('testkeys/ed25519.txt.sig')

rsa_sign.verify(rsa_data)
rsa_sign2.verify(rsa_data)
ecdsa_sign.verify(ecdsa_data)
ed25519_sign.verify(ed25519_data)


rsa_signable = rsa_sign.get_signable(rsa_data)
ecdsa_signable = ecdsa_sign.get_signable(ecdsa_data)
ed25519_signable = ed25519_sign.get_signable(ed25519_data)

try:
rsa_pub.verify(rsa_signable, rsa_sign.fields.signature.value)
except:
print("RSA validation failed")

try:
ecdsa_pub.verify(ecdsa_signable, ecdsa_sign.fields.signature.value)
except:
print("ECDSA validation failed")

try:
ed25519_pub.verify(ed25519_signable, ed25519_sign.fields.signature.value)
except:
print("Ed25519 validation failed")

print()
114 changes: 114 additions & 0 deletions src/sshkey_tools/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -1685,3 +1685,117 @@ def sign(self, data: bytes, **kwargs) -> None:
"""
self.value = self.private_key.sign(data)
self.is_signed = True

PUBKEY_FIELD_TYPE_MAP = {
b"ssh-rsa": "RsaTypedPubkeyField",
b"rsa-sha2-256": "RsaTypedPubkeyField",
b"rsa-sha2-512": "RsaTypedPubkeyField",
b"ecdsa-sha2-nistp256": "EcdsaTypedPubkeyField",
b"ecdsa-sha2-nistp384": "EcdsaTypedPubkeyField",
b"ecdsa-sha2-nistp521": "EcdsaTypedPubkeyField",
b"ssh-ed25519": "Ed25519TypedPubkeyField",
}

class TypedPublicKeyField(PublicKeyField):
@classmethod
def decode(cls, data: bytes) -> "PublicKeyField":
"""
Decode from a packed pair of key type and key data
Args:
data (bytes): Packed key type and data
Raises:
_EX.InvalidKeyException: Invalid
Returns:
PublicKeyField: A public key field
"""
pk_data, data = BytestringField.decode(data)
pk_type, pk_data = BytestringField.decode(pk_data)

target_class = globals()[PUBKEY_FIELD_TYPE_MAP[pk_type]]

return target_class.from_decode(pk_data)[0], data


class SshsigField(CertificateField):
"""
SSH Signature magic preamble field (static b'SSHSIG')
"""
DATA_TYPE = bytes
DEFAULT = b'SSHSIG'
@classmethod
def encode(cls, value: bytes) -> bytes:
"""
Encodes the SSH Signature magic preamble field
Args:
value (bytes): The SSH Signature magic preamble field
Returns:
bytes: The SSH Signature magic preamble field
"""
return b'SSHSIG'

@classmethod
def decode(cls, data: bytes) -> Tuple[bytes, bytes]:
"""
Decodes the SSH Signature magic preamble field
Args:
data (bytes): The SSH Signature magic preamble field
Returns:
bytes: The SSH Signature magic preamble field
"""
return data[:6], data[6:]

def __validate_value__(self) -> Union[bool, Exception]:
"""
Validates the contents of the field
"""
return True

class SignatureVersionField(Integer32Field):
DATA_TYPE = int
DEFAULT = 1

def __validate_value__(self) -> Union[bool, Exception]:
"""
Validates the contents of the field
"""
if self.value != 1:
return _EX.InvalidCertificateFieldException(
"The certificate version is invalid"
)

return True

class SignatureNamespaceField(StringField):
DATA_TYPE = (str, bytes)
DEFAULT = ""

class SignatureNamespaceField(StringField):
DATA_TYPE = (str, bytes)
DEFAULT = ""

def __validate_value__(self) -> Union[bool, Exception]:
if len(self.value) == 0:
return _EX.InvalidFieldDataException(
f"{self.get_name()} must be a non-empty string"
)

return True

class SignatureHashAlgorithmField(StringField):
DATA_TYPE = (str, bytes)
DEFAULT = "sha512"

def __validate_value__(self) -> Union[bool, Exception]:
if self.value not in ("sha256", "sha512"):
return _EX.InvalidFieldDataException(
f"{self.get_name()} must be one of 'sha256' or 'sha512'"
)

return True
Loading

0 comments on commit 339dae2

Please sign in to comment.