New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Host-Keys signed by a CA aren't recognized as known hosts #771
Comments
Thanks for the report! I don't have personal experience using a CA system with SSH keys so not sure offhand how well Paramiko supports it; could be a bug, could be outright lack of support. I'm +1 on getting it working but would need someone both with CA experience & comfort w/ the codebase to at least get us pointed in the right direction. |
+1 on this. This is very useful and client certificates seem to already work, but host certificates have a slightly different syntax: https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.certkeys |
Tying this to #531 even though the two probably don't technically overlap (but they kinda do conceptually). |
I am very interested in supporting host keys with cert-authority. Has there been any progress on this? If not I will attempt an implementation. |
Still not implemented as near as I can tell from the documentation. Using load_system_host_keys on a file containing entries that start with @kazzmir did you ever attempt to implement? |
No Implementation until 2021? |
Hi! I just countered this issue upon trying to use Paramiko. Is there any update on it? |
Flagging this to include/consider in the key/auth rewrite of #387. That rewrite is waiting for bitprophet's drop of Python 2 and older 3.x from all of his packages. Not sure how long that will take; the key work will only happen after that modernization is done. |
Just ran into this, also, and poked through # hostkeys.py
# 339 - 347
fields = re.split(" |\t", line)
if len(fields) < 3:
# Bad number of fields
msg = "Not enough fields found in known_hosts in line {} ({!r})"
log.info(msg.format(lineno, line))
return None
fields = fields[:3] # <--- right there!
names, key_type, key = fields A signed host key's 0th field is
A naive solution might be to throw some more A related ergonomics issue: in my case, this signed key happens to be the first one in |
Thanks very much for the investigation and detail on this, @MajorDallas! If I read your comment correctly, and to summarize, you have a twofold recommendation?
Seems to me that these ought to be two different PRs. Separately:
|
A twofold recommendation, yes. However, for the first recommendation, rather than
I would say:
I know that's a quibble, but this issue will affect more than signed host keys: any host key that has been revoked, somehow specifies a host pattern* with a space in it (not sure that's even allowed, tbh), or comments will all break under the current method of splitting on space and indexing the resultant list without checking what's in the 0th field. I haven't checked, but if the same or a similar algorithm is also used for parsing the
The only spec I could find is right out of
Somewhat interesting (to me): as far as I can tell, libssh doesn't check for this field, either (I am not C-literate, though, and could have just missed it). I think the best possible reference is OpenSSH's own code. It looks like their line parsing logic starts in hostfile.c:760, with the function check_markers called on line 803 after ignoring leading whitespace and skipping comments, but before doing anything else at all. * On the topic of host patterns: I didn't read real far into how the host field is handled. I assume it's parsed at least enough to recognize a non-standard port (the syntax requires the pattern be enclosed in square brackets), but what about eg. wildcards? OpenSSH specifies support for (edit: use code block instead of quote block, since apparently "revoked" is a github user and I accidentally pinged them by quoting the man page 😅 ) |
I threw this together in about 20 minutes and tested with a cert-authority with comments example and a marker-free, comment-free example: from enum import Enum
from typing import Optional
class Markers(str, Enum):
CERT_AUTHORITY = "@cert-authority"
REVOKED = "@revoked"
class HostKeyParser:
marker: Optional[Markers]
hosts: str
keytype: str
key: str
comment: str
_original_line: str
__slots__ = (
"marker",
"hosts",
"keytype",
"key",
"comment",
"_original_line",
)
def __init__(self, hk_line: str):
(
self.marker,
self.hosts,
self.keytype,
self.key,
self.comment,
) = self.parse_line(hk_line)
self._original_line = hk_line
@staticmethod
def parse_line(hk_line: str) -> tuple[Optional[Markers], str, str, str, str]:
fields = hk_line.split(" ")
try:
marker = Markers(fields[0])
hosts, keytype, key, *comment = fields[1:]
except ValueError:
marker = None
hosts, keytype, key, *comment = fields
return marker, hosts, keytype, key, " ".join(comment) Fundamentally it's still just splitting on space, but I think my concerns about spurious spaces in fields besides It works, though! Edit: Yeah, I think I like it better this way: @dataclass(frozen=True, slots=True)
class HostKey:
marker: Optional[Markers]
hosts: str
keytype: str
key: str
comment: str
_original_line: str
def parse_line(hk_line: str) -> HostKey:
fields = hk_line.split(" ")
try:
marker = Markers(fields[0])
hosts, keytype, key, *comment = fields[1:]
except ValueError:
marker = None
hosts, keytype, key, *comment = fields
return HostKey(marker, hosts, keytype, key, " ".join(comment), hk_line) |
Agreed, @MajorDallas, the simplicity of the bare function is appealing. This implementation of the parsing logic makes sense to me and looks good -- it has a number of nice features. Looking at what's in Unless there's a reason to re-implement the container class, probably best to fix this with as minimal modification as possible to the existing code? This logic seems straightforward enough that adapting it shouldn't be too difficult? |
You're right. I admit I didn't refer to the existing code, I just wanted to prototype the logic. I'd need to spend some time to figure out how to integrate it, but someone already familiar with that module could certainly do it faster. One thing my prototype doesn't consider is host patterns. I kind of just assumed that there's logic elsewhere that implements the glob and negation operators and that the parser doesn't need to care about that. |
Nod, no worries - a POC like this should make it easier to implement: makes it a port, rather than a from-scratch task. Feel free to take a stab yourself, or leave it here for someone else to pick up. Your call! |
I just noticed something from the sample hostkeys above:
The second field, the key type, here is Field 3 is This very much looks like an invalid hostkey entry to me. I would not expect the man-page authors to put in an invalid example without somehow indicating that it's invalid, though 🤔 . I've never seen a hostkey entry like this in the wild, but it's not like I spend a lot of time reading random |
Not complete! Adds basic parser support for a marker in the first field. Additional handling of cert-authority should be discussed before being implemented. Adds sample keys to test_hostkeys.py.
Well, I took a stab at it 'cause why not 🤷 I wasn't sure whether to add an attribute to I added sample cert-authority and revoked keys to the sample data in Just need to discuss what, if anything, to do with the markers once we have them. |
Cool - thanks! I'd say rebase this onto a branch in your fork and create a PR--further discussion of the details of the handling for each marker case and any changes to the data structures would happen there. I'd replicate what you put in this most recent comment, as the starting point for conversation. (The rebase is important because it's bad practice to develop on |
An oversight on my part 😅 I'll get a branch and PR set-up. |
+1 |
Abstract
It seems like paramiko isn't correctly processing keys from
known_hosts
that are annotated with@cert-authority
. As I'm unfamiliar with python and paramiko, I cannot try to reproduce it. The setup works fine using the OpenSSH tools, but fails when using paramiko.Environment
I've encountered this bug in duplicity, a backup program that uses paramiko as an SSH agent.
Client has a
/etc/ssh/ssh_known_hosts
file that contains a@cert-authority
line with the public key of the signing CA:Server has
*-cert.pub
files for all of its host keys, e.g./etc/ssh/ssh_host_rsa_key-cert.pub
, which are configured in itssshd_config
like so:HostCertificate /etc/ssh/ssh_host_rsa_key-cert.pub
. These certificates were created using the private key of the aforementioned CA.Situation
Connecting via OpenSSH's program suite works fine:
Connecting via paramiko (through duplicity) does not:
The fingerprint displayed is of the signed public key.
Paramiko only manages to connect after adding the domain/ip and public host key of the server to the
known_hosts
file in the client, when it should not need this due to the given CA key and certificate.The text was updated successfully, but these errors were encountered: