-
Notifications
You must be signed in to change notification settings - Fork 0
/
known_hosts.clj
170 lines (140 loc) · 6.43 KB
/
known_hosts.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
(ns clj-libssh2.known-hosts
"Utilities for checking the host key of a remote machine against a list of
known hosts."
(:require [clojure.java.io :refer [file]]
[clojure.string :as str]
[clojure.tools.logging :as log]
[digest]
[clj-libssh2.error :as error :refer [handle-errors with-timeout]]
[clj-libssh2.libssh2 :as libssh2]
[clj-libssh2.libssh2.knownhost :as libssh2-knownhost]
[clj-libssh2.libssh2.session :as libssh2-session])
(:import [com.sun.jna.ptr IntByReference PointerByReference]))
(defn- checkp-result
"Re-interpret the result of libssh2_knownhost_checkp to either succeed or
cause handle-errors to throw, as appropriate.
Arguments:
fail-on-mismatch Boolean, true if a call to check-host-key should fail if
the remote host's key does not match the value in the known
hosts file.
fail-on-missing Boolean, true if a call to check-host-key should fail if
there is no entry for the remote host in the known hosts
file.
result The result of the call to libssh2_knownhost_checkp.
Return:
An integer, 0 if libssh2_knownhost_checkp was successful,
libssh2/ERROR_HOSTKEY_SIGN if libssh2_knownhost_checkp was unsuccessful and
that lack of success - interpreted in the context of fail-on-missing and
fail-on-mismatch - is considered a failure. If libssh2_knownhost_checkp
returns an unknown value then an exception will be raised."
[fail-on-mismatch fail-on-missing result]
(condp = (.longValue result)
libssh2/KNOWNHOST_CHECK_MATCH 0
libssh2/KNOWNHOST_CHECK_MISMATCH (if fail-on-mismatch
libssh2/ERROR_HOSTKEY_SIGN
0)
libssh2/KNOWNHOST_CHECK_NOTFOUND (if fail-on-missing
libssh2/ERROR_HOSTKEY_SIGN
0)
libssh2/KNOWNHOST_CHECK_FAILURE libssh2/ERROR_HOSTKEY_SIGN
(error/raise "Unknown return code from libssh2_knownhost_checkp."
{:function "libssh2_knownhost_checkp"
:return result})))
(defn- host-key
"Get the remote host's key.
Arguments:
session The clj-libssh2.session.Session object for the current session.
Return:
A byte array containing the key of the remote host."
[session]
(when (:session session)
(let [len (IntByReference.)
typ (IntByReference.)
hostkey_ptr (libssh2-session/hostkey (:session session) len typ)]
(.getByteArray hostkey_ptr 0 (.getValue len)))))
(defn- check-host-key
"Call libssh2_knownhost_checkp to check the current remote host.
Arguments:
session The clj-libssh2.session.Session object for the current
session.
known-hosts A native pointer from libssh2_knownhost_init.
host The hostname or IP of the remote host.
port The port in use for connecting to the remote host.
host-key The remote host's key.
fail-on-missing Fail if the remote host's key is not in the known hosts
file.
fail-on-mismatch Fail if the remote host's key does not match the one found
in the known hosts file.
Return:
0 on success or an exception if the key does not validate."
[session known-hosts host port host-key fail-on-missing fail-on-mismatch]
(handle-errors session
(with-timeout session :known-hosts
(checkp-result fail-on-mismatch fail-on-missing
(libssh2-knownhost/checkp known-hosts
host
port
host-key
(count host-key)
(bit-or libssh2/KNOWNHOST_TYPE_PLAIN
libssh2/KNOWNHOST_KEYENC_RAW)
(PointerByReference.))))))
(defn- load-known-hosts
"Load a known hosts file into the known hosts object.
Arguments:
session The clj-libssh2.session.Session object for the current
session.
known-hosts A native pointer from libssh2_knownhost_init.
known-hosts-file The path to the file containing the known hosts.
Return:
Nil if the known hosts file did not exist. If the file does exist, this
returns the number of hosts loaded. Throws an exception on error."
[session known-hosts known-hosts-file]
(when (.exists (file known-hosts-file))
(handle-errors session
(with-timeout session :known-hosts
(libssh2-knownhost/readfile known-hosts
known-hosts-file
libssh2/KNOWNHOST_FILE_OPENSSH)))))
(defn- fingerprint
"Generate a fingerprint for a host key.
Arguments:
host-key The full host key as a byte array.
Return:
A String with a fingerprint of the host key."
[host-key]
(->> host-key
digest/sha-256
(partition 2)
(map #(str/join "" %))
(str/join ":")))
(defn check
"Given a session that has already completed a handshake with a remote host,
check the host key of the remote host against the known hosts file.
Arguments:
session The clj-libssh2.session.Session object for the current session.
Return:
0 on success or an exception if the host key does not validate."
[session]
(let [known-hosts (libssh2-knownhost/init (:session session))
remote-host-key (host-key session)
session-options (:options session)
file (or (:known-hosts-file session-options)
(str (System/getProperty "user.home") "/.ssh/known_hosts"))
fail-on-mismatch (-> session-options :fail-unless-known-hosts-matches)
fail-on-missing (-> session-options :fail-if-not-in-known-hosts)]
(when (nil? known-hosts)
(error/maybe-throw-error session libssh2/ERROR_ALLOC))
(try
(log/infof "Loading known hosts file: %s" file)
(load-known-hosts session known-hosts file)
(log/infof "Checking host key (%s) against known hosts file."
(fingerprint remote-host-key))
(check-host-key session
known-hosts
(:host session)
(:port session)
remote-host-key
fail-on-missing
fail-on-mismatch)
(finally (libssh2-knownhost/free known-hosts)))))