Skip to content

Commit 5748326

Browse files
committed
Tests are functional
1 parent 34a35df commit 5748326

5 files changed

Lines changed: 126 additions & 46 deletions

File tree

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,5 @@ report.html
145145
test_certificate
146146
testing.py
147147
.idea
148-
core
148+
core
149+
tests/TestSignatures

src/sshkey_tools/signatures.py

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class SignatureFieldset(Fieldset):
3636
sig_version: _FIELD.SignatureVersionField = _FIELD.SignatureVersionField.factory
3737
public_key: _FIELD.PublicKeyField = _FIELD.PublicKeyField.factory
3838

39-
namespace: _FIELD.SignatureNamespaceField = _FIELD.StringField.factory
39+
namespace: _FIELD.SignatureNamespaceField = _FIELD.SignatureNamespaceField.factory
4040
reserved: _FIELD.ReservedField = _FIELD.ReservedField.factory
4141
hash_algorithm: _FIELD.SignatureHashAlgorithmField = _FIELD.SignatureHashAlgorithmField.factory
4242
signature: _FIELD.SignatureField = _FIELD.SignatureField.factory
@@ -75,6 +75,57 @@ def bytes_out(self):
7575
bytes(self.signature)
7676
)
7777

78+
@classmethod
79+
def decode(cls, data: bytes):
80+
"""
81+
Decode a SignatureFieldset from SSHSIG binary format.
82+
Overrides Fieldset.decode to handle the public key field
83+
which is encoded as a length-prefixed SSH wire-format key.
84+
"""
85+
cl_instance = cls()
86+
87+
decoded, data = cl_instance.magic_preamble.from_decode(data)
88+
cl_instance.magic_preamble = decoded
89+
90+
decoded, data = cl_instance.sig_version.from_decode(data)
91+
cl_instance.sig_version = decoded
92+
93+
# public_key is stored as string(ssh-wire-key) in SSHSIG format
94+
pubkey_bytes, data = _FIELD.BytestringField.decode(data)
95+
key_type, key_data = _FIELD.StringField.decode(pubkey_bytes)
96+
97+
sshsig_key_map = {
98+
"ssh-rsa": "RsaPubkeyField",
99+
"ecdsa-sha2-nistp256": "EcdsaPubkeyField",
100+
"ecdsa-sha2-nistp384": "EcdsaPubkeyField",
101+
"ecdsa-sha2-nistp521": "EcdsaPubkeyField",
102+
"ssh-ed25519": "Ed25519PubkeyField",
103+
}
104+
105+
field_cls_name = sshsig_key_map.get(key_type)
106+
if field_cls_name is None:
107+
raise _EX.InvalidDataException(
108+
f"Unknown public key type in signature: {key_type}"
109+
)
110+
111+
field_cls = getattr(_FIELD, field_cls_name)
112+
key_value, _ = field_cls.decode(key_data)
113+
cl_instance.public_key = field_cls(value=key_value)
114+
115+
decoded, data = cl_instance.namespace.from_decode(data)
116+
cl_instance.namespace = decoded
117+
118+
decoded, data = cl_instance.reserved.from_decode(data)
119+
cl_instance.reserved = decoded
120+
121+
decoded, data = cl_instance.hash_algorithm.from_decode(data)
122+
cl_instance.hash_algorithm = decoded
123+
124+
decoded, data = cl_instance.signature.from_decode(data)
125+
cl_instance.signature = decoded
126+
127+
return cl_instance, data
128+
78129
class SSHSignature:
79130
"""
80131
General class for SSH Signatures, used for loading and parsing.
@@ -189,9 +240,8 @@ def get_signable_file(self, path: str) -> bytes:
189240
def __str__(self) -> str:
190241
table = PrettyTable(["Field", "Value"])
191242

192-
for item in (self.header, self.fields, self.footer):
193-
for row in item.__table__():
194-
table.add_row(row)
243+
for row in self.fields.__table__():
244+
table.add_row(row)
195245

196246
return str(table)
197247

@@ -204,7 +254,8 @@ def get(self, field: str):
204254
def set(self, field: str, data):
205255
if field in self.fields.getattrs():
206256
self.fields.set(field, data)
207-
257+
return
258+
208259
raise _EX.InvalidCertificateFieldException(f"Unknown field {field}")
209260

210261
def verify(

tests/test_certificates.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import random
99
import shutil
1010
import unittest
11+
import subprocess
1112

1213
import faker
1314

@@ -569,14 +570,21 @@ def assertCertificateCreated(self, sub_type, ca_type):
569570
)
570571

571572
self.assertTrue(certificate.can_sign())
573+
574+
572575
certificate.sign()
573576
certificate.to_file(f"tests/certificates/{sub_type}_{ca_type}-cert.pub")
577+
578+
createdCmd = subprocess.run(
579+
["ssh-keygen", "-Lf", f"tests/certificates/{sub_type}_{ca_type}-cert.pub"],
580+
capture_output=True,
581+
text=True
582+
)
574583

575584
self.assertEqual(
576585
0,
577-
os.system(
578-
f"ssh-keygen -Lf tests/certificates/{sub_type}_{ca_type}-cert.pub"
579-
),
586+
createdCmd.returncode,
587+
f"ssh-keygen failed to read the created certificate: {createdCmd.stderr}"
580588
)
581589

582590
reloaded_cert = _CERT.SSHCertificate.from_file(

tests/test_keypairs.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import os
66
import shutil
77
import unittest
8+
import subprocess
89

910
from cryptography.hazmat.primitives.asymmetric import ec as _EC
1011
from cryptography.hazmat.primitives.asymmetric import ed25519 as _ED25519
@@ -38,16 +39,22 @@ def generateFiles(self, folder):
3839
shutil.rmtree(f"tests/{folder}")
3940
os.mkdir(f"tests/{folder}")
4041

41-
os.system(
42-
f'ssh-keygen -t rsa -b 2048 -f tests/{folder}/rsa_key_sshkeygen -N "password" > /dev/null 2>&1'
42+
subprocess.run(
43+
["ssh-keygen", "-t", "rsa", "-b", "2048", "-f", f"tests/{folder}/rsa_key_sshkeygen", "-N", "password"],
44+
stdout=subprocess.DEVNULL,
45+
stderr=subprocess.DEVNULL
4346
)
44-
os.system(
45-
f'ssh-keygen -t ecdsa -b 256 -f tests/{folder}/ecdsa_key_sshkeygen -N "" > /dev/null 2>&1'
47+
subprocess.run(
48+
["ssh-keygen", "-t", "ecdsa", "-b", "256", "-f", f"tests/{folder}/ecdsa_key_sshkeygen", "-N", ""],
49+
stdout=subprocess.DEVNULL,
50+
stderr=subprocess.DEVNULL
4651
)
47-
os.system(
48-
f'ssh-keygen -t ed25519 -f tests/{folder}/ed25519_key_sshkeygen -N "" > /dev/null 2>&1'
52+
subprocess.run(
53+
["ssh-keygen", "-t", "ed25519", "-f", f"tests/{folder}/ed25519_key_sshkeygen", "-N", ""],
54+
stdout=subprocess.DEVNULL,
55+
stderr=subprocess.DEVNULL
4956
)
50-
57+
5158
def setUp(self):
5259
self.generateClasses()
5360
self.generateFiles("KeypairMethods")
@@ -76,16 +83,11 @@ def assertEqualPublicKeys(self, keyclass, a, b):
7683
self.assertEqual(a.raw_bytes(), b.raw_bytes())
7784

7885
def assertEqualKeyFingerprint(self, file_a, file_b):
86+
fp_a = subprocess.run(["ssh-keygen", "-lf", file_a], capture_output=True, text=True)
87+
fp_b = subprocess.run(["ssh-keygen", "-lf", file_b], capture_output=True, text=True)
88+
7989
self.assertEqual(
80-
0,
81-
os.system(
82-
f"""bash -c "
83-
diff \
84-
<( ssh-keygen -lf {file_a}) \
85-
<( ssh-keygen -lf {file_b}) \
86-
"
87-
"""
88-
),
90+
fp_a.stdout, fp_b.stdout
8991
)
9092

9193

tests/test_signatures.py

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
# Test SSH signatures (SSHSIG format)
22
# Tests for signature-specific fields, SSHSignature creation/parsing
33
# Tests creating signatures with sshkey-tools and verifying with ssh-keygen, and vice versa
4-
4+
import io
55
import os
66
import shutil
77
import unittest
8+
import subprocess
89

910
import src.sshkey_tools.exceptions as _EX
1011
import src.sshkey_tools.fields as _FIELD
@@ -183,17 +184,20 @@ def generateFiles(self, folder):
183184
with open(f"tests/{folder}/testdata.txt", "w") as f:
184185
f.write("This is test data for SSH signature testing.")
185186

186-
os.system(
187-
f'ssh-keygen -t rsa -b 2048 -f tests/{folder}/rsa_key -N "" > /dev/null 2>&1'
187+
subprocess.run(
188+
["ssh-keygen", "-t", "rsa", "-b", "2048", "-f", f"tests/{folder}/rsa_key", "-N", '']
188189
)
189-
os.system(
190-
f'ssh-keygen -t ecdsa -b 256 -f tests/{folder}/ecdsa_key -N "" > /dev/null 2>&1'
190+
191+
subprocess.run(
192+
["ssh-keygen", "-t", "ecdsa", "-b", "256", "-f", f"tests/{folder}/ecdsa_key", "-N", '']
191193
)
192-
os.system(
193-
f'ssh-keygen -t ed25519 -f tests/{folder}/ed25519_key -N "" > /dev/null 2>&1'
194+
195+
subprocess.run(
196+
["ssh-keygen", "-t", "ed25519", "-f", f"tests/{folder}/ed25519_key", "-N", '']
194197
)
195-
198+
196199
for key_type in KEY_TYPES:
200+
os.chmod(f"tests/{folder}/{key_type}_key", 0o600)
197201
with open(f"tests/{folder}/{key_type}_key.pub") as f:
198202
pubkey = f.read().strip()
199203
with open(f"tests/{folder}/{key_type}_allowed_signers", "w") as f:
@@ -238,12 +242,23 @@ def assertSignAndVerifyWithSshkeygen(self, key_type, namespace="file", hash_alg=
238242
data_path = f"tests/{self.folder}/{key_type}_{namespace}_{hash_alg}_data.txt"
239243
with open(data_path, "wb") as f:
240244
f.write(data)
241-
242-
result = os.system(
243-
f"ssh-keygen -Y verify -f {allowed_signers_path} -I {PRINCIPAL} "
244-
f"-n {namespace} -s {sig_path} < {data_path} > /dev/null 2>&1"
245+
246+
247+
p = subprocess.Popen([
248+
"ssh-keygen", "-Y", "verify", "-f", allowed_signers_path, "-I", PRINCIPAL,
249+
"-n", namespace, "-s", sig_path
250+
],
251+
stdout=subprocess.PIPE,
252+
stdin=subprocess.PIPE,
253+
stderr=subprocess.PIPE,
254+
text=True
255+
)
256+
stdout_data = p.communicate(input=data.decode("utf-8"))
257+
258+
self.assertNotStartsWith(
259+
stdout_data[0],
260+
'Could not verify signature'
245261
)
246-
self.assertEqual(result, 0, f"ssh-keygen failed to verify {key_type} signature")
247262

248263
def test_rsa_sign_sha512(self):
249264
self.assertSignAndVerifyWithSshkeygen("rsa", "file", "sha512")
@@ -279,11 +294,14 @@ def test_sign_file_method(self):
279294

280295
allowed_signers_path = self.createSshkeyToolsAllowedSigners("ed25519_file_method", pubkey)
281296

282-
result = os.system(
283-
f"ssh-keygen -Y verify -f {allowed_signers_path} -I {PRINCIPAL} "
284-
f"-n file -s {sig_path} < tests/{self.folder}/testdata.txt > /dev/null 2>&1"
297+
result = subprocess.run(
298+
["bash", "-c",
299+
f"ssh-keygen -Y verify -f {allowed_signers_path} -I {PRINCIPAL} "
300+
f"-n file -s {sig_path} < tests/{self.folder}/testdata.txt",
301+
],
302+
capture_output=True
285303
)
286-
self.assertEqual(result, 0, "ssh-keygen failed to verify file-signed ed25519 signature")
304+
self.assertEqual(result.returncode, 0, "ssh-keygen failed to verify file-signed ed25519 signature")
287305

288306

289307
class TestSshkeygenSignaturesParsedBySshkeyTools(SignatureMethods):
@@ -298,11 +316,11 @@ def assertParseSshkeygenSignature(self, key_type):
298316
with open(data_path, "wb") as f:
299317
f.write(data)
300318

301-
ret = os.system(
302-
f"ssh-keygen -Y sign -f tests/{self.folder}/{key_type}_key "
303-
f"-n file {data_path} > /dev/null 2>&1"
319+
ret = subprocess.run(
320+
["ssh-keygen", "-Y", "sign", "-f", f"tests/{self.folder}/{key_type}_key", "-n", "file", data_path],
321+
capture_output=True
304322
)
305-
self.assertEqual(ret, 0, f"ssh-keygen failed to sign {data_path}")
323+
self.assertEqual(ret.returncode, 0, f"ssh-keygen failed to sign {data_path}")
306324

307325
sig = _SIG.SSHSignature.from_file(sig_path)
308326

0 commit comments

Comments
 (0)