fix: support multiple SSHPubKey records per user

There was one blazing issue with the previous implementation regardless
of the multiple records: we were generating fingerprints by storing
the key into a file and reading it with ssh-keygen. This is absolutely
terrible and was not meant to be left around (it was forgotten, my bad).

Took this opportunity to clean up a few things:
- simplify pubkey validation
- centralize things a bit better

Signed-off-by: Kevin Morris <kevr@0cost.org>
This commit is contained in:
Kevin Morris 2022-02-08 07:50:15 -08:00
parent 660d57340a
commit 4c14a10b91
No known key found for this signature in database
GPG key ID: F7E46DED420788F3
11 changed files with 162 additions and 108 deletions

View file

@ -577,10 +577,13 @@ def test_post_register_error_ssh_pubkey_taken(client: TestClient, user: User):
# Read in the public key, then delete the temp dir we made.
pk = open(f"{tmpdir}/test.ssh.pub").read().rstrip()
prefix, key, loc = pk.split()
norm_pk = prefix + " " + key
# Take the sha256 fingerprint of the ssh public key, create it.
fp = get_fingerprint(pk)
fp = get_fingerprint(norm_pk)
with db.begin():
create(SSHPubKey, UserID=user.ID, PubKey=pk, Fingerprint=fp)
create(SSHPubKey, UserID=user.ID, PubKey=norm_pk, Fingerprint=fp)
with client as request:
response = post_register(request, PK=pk)
@ -1080,22 +1083,16 @@ def test_post_account_edit_missing_ssh_pubkey(client: TestClient, user: User):
def test_post_account_edit_invalid_ssh_pubkey(client: TestClient, user: User):
pubkey = "ssh-rsa fake key"
request = Request()
sid = user.login(request, "testPassword")
post_data = {
data = {
"U": "test",
"E": "test@example.org",
"P": "newPassword",
"C": "newPassword",
"PK": pubkey,
"passwd": "testPassword"
}
cookies = {"AURSID": user.login(Request(), "testPassword")}
with client as request:
response = request.post("/account/test/edit", cookies={
"AURSID": sid
}, data=post_data, allow_redirects=False)
response = request.post("/account/test/edit", data=data,
cookies=cookies, allow_redirects=False)
assert response.status_code == int(HTTPStatus.BAD_REQUEST)

View file

@ -53,4 +53,4 @@ def test_adduser_ssh_pk():
"--ssh-pubkey", TEST_SSH_PUBKEY])
test = db.query(User).filter(User.Username == "test").first()
assert test is not None
assert TEST_SSH_PUBKEY.startswith(test.ssh_pub_key.PubKey)
assert TEST_SSH_PUBKEY.startswith(test.ssh_pub_keys.first().PubKey)

View file

@ -1,3 +1,5 @@
from subprocess import PIPE, Popen
import pytest
from aurweb import db
@ -61,8 +63,12 @@ def test_pubkey_cs(user: User):
def test_pubkey_fingerprint():
assert get_fingerprint(TEST_SSH_PUBKEY) is not None
proc = Popen(["ssh-keygen", "-l", "-f", "-"], stdin=PIPE, stdout=PIPE)
out, _ = proc.communicate(TEST_SSH_PUBKEY.encode())
expected = out.decode().split()[1].split(":", 1)[1]
assert get_fingerprint(TEST_SSH_PUBKEY) == expected
def test_pubkey_invalid_fingerprint():
assert get_fingerprint("ssh-rsa fake and invalid") is None
with pytest.raises(ValueError):
get_fingerprint("invalid-prefix some-fake-content")

View file

@ -183,14 +183,14 @@ def test_user_has_credential(user: User):
def test_user_ssh_pub_key(user: User):
assert user.ssh_pub_key is None
assert user.ssh_pub_keys.first() is None
with db.begin():
ssh_pub_key = db.create(SSHPubKey, UserID=user.ID,
Fingerprint="testFingerprint",
PubKey="testPubKey")
assert user.ssh_pub_key == ssh_pub_key
assert user.ssh_pub_keys.first() == ssh_pub_key
def test_user_credential_types(user: User):

View file

@ -60,3 +60,53 @@ def test_valid_homepage():
assert not util.valid_homepage("https://[google.com/broken-ipv6")
assert not util.valid_homepage("gopher://gopher.hprc.utoronto.ca/")
def test_parse_ssh_key():
# Test a valid key.
pk = """ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyN\
TYAAABBBEURnkiY6JoLyqDE8Li1XuAW+LHmkmLDMW/GL5wY7k4/A+Ta7bjA3MOKrF9j4EuUTvCuNX\
ULxvpfSqheTFWZc+g="""
prefix, key = util.parse_ssh_key(pk)
e_prefix, e_key = pk.split()
assert prefix == e_prefix
assert key == e_key
# Test an invalid key with just one word in it.
with pytest.raises(ValueError):
util.parse_ssh_key("ssh-rsa")
# Test a valid key with extra words in it (after the PK).
pk = pk + " blah blah"
prefix, key = util.parse_ssh_key(pk)
assert prefix == e_prefix
assert key == e_key
# Test an invalid prefix.
with pytest.raises(ValueError):
util.parse_ssh_key("invalid-prefix fake-content")
def test_parse_ssh_keys():
pks = """ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyN\
TYAAABBBEURnkiY6JoLyqDE8Li1XuAW+LHmkmLDMW/GL5wY7k4/A+Ta7bjA3MOKrF9j4EuUTvCuNX\
ULxvpfSqheTFWZc+g=
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDmqEapFMh/ajPHnm1dBweYPeLOUjC0Ydp6uw7rB\
S5KCggUVQR8WfIm+sRYTj2+smGsK6zHMBjFnbzvV11vnMqcnY+Sa4LhIAdwkbt/b8HlGaLj1hCWSh\
a5b5/noeK7L+CECGHdvfJhpxBbhq38YEdFnCGbslk/4NriNcUp/DO81CXb1RzJ9GBFH8ivPW1mbe9\
YbxDwGimZZslg0OZu9UzoAT6xEGyiZsqJkTMbRp1ZYIOv9jHCJxRuxxuN3fzxyT3xE69+vhq2/NJX\
8aRsxGPL9G/XKcaYGS6y6LW4quIBCz/XsTZfx1GmkQeZPYHH8FeE+XC/+toXL/kamxdOQKFYEEpWK\
vTNJCD6JtMClxbIXW9q74nNqG+2SD/VQNMUz/505TK1PbY/4uyFfq5HquHJXQVCBll03FRerNHH2N\
schFne6BFHpa48PCoZNH45wLjFXwUyrGU1HrNqh6ZPdRfBTrTOkgs+BKBxGNeV45aYUPu/cFBSPcB\
fRSo6OFcejKc="""
keys = util.parse_ssh_keys(pks)
assert len(keys) == 2
pfx1, key1, pfx2, key2 = pks.split()
k1, k2 = keys
assert pfx1 == k1[0]
assert key1 == k1[1]
assert pfx2 == k2[0]
assert key2 == k2[1]