Skip to content

Commit bb90267

Browse files
committed
Factor out keypair generation
Simplifies add-identity by factoring out the keypair creation.
1 parent 23afd07 commit bb90267

File tree

2 files changed

+74
-56
lines changed

2 files changed

+74
-56
lines changed

src/clj_ssh/ssh.clj

Lines changed: 68 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -127,8 +127,69 @@
127127
com.jcraft.jsch.IdentityFile 'newInstance [String String JSch]
128128
nil private-key-path public-key-path agent))
129129

130+
(defn ^KeyPair keypair
131+
"Return a KeyPair object for the given options.
132+
133+
:private-key A string specifying the private key
134+
:public-key A string specifying the public key
135+
:private-key-path A string specifying a path to the private key
136+
:public-key-path A string specifying a path to the public key
137+
:passphrase A byte array containing the passphrase
138+
:comment A comment for the key"
139+
[^JSch agent {:keys [^String public-key
140+
^String private-key
141+
^String public-key-path
142+
^String private-key-path
143+
^String comment
144+
^bytes passphrase]
145+
:as options}]
146+
{:pre [(map? options)]}
147+
(cond
148+
private-key
149+
(let [^KeyPair keypair
150+
(KeyPair/load agent (as-bytes private-key) (as-bytes public-key))]
151+
(when passphrase
152+
(.decrypt keypair passphrase))
153+
(.setPublicKeyComment keypair comment)
154+
keypair)
155+
156+
(and public-key-path private-key-path)
157+
(let [keypair (KeyPair/load agent private-key-path public-key-path)]
158+
(when passphrase
159+
(.decrypt keypair passphrase))
160+
(.setPublicKeyComment keypair comment)
161+
keypair)
162+
163+
private-key-path
164+
(let [keypair (KeyPair/load agent private-key-path)]
165+
(when passphrase
166+
(.decrypt keypair passphrase))
167+
(.setPublicKeyComment keypair comment)
168+
keypair)
169+
170+
:else
171+
(throw
172+
(ex-info
173+
"Don't know how to create keypair"
174+
{:reason :do-not-know-how-to-create-keypair
175+
:args options}))))
176+
177+
;; JSch's IdentityFile has a private constructor that would let us avoid this
178+
;; were it public.
179+
(deftype KeyPairIdentity [^JSch jsch ^String identity ^KeyPair kpair]
180+
Identity
181+
(^boolean setPassphrase [_ ^bytes passphrase] (.. kpair (decrypt passphrase)))
182+
(getPublicKeyBlob [_] (.. kpair getPublicKeyBlob))
183+
(^bytes getSignature [_ ^bytes data] (.. kpair (getSignature data)))
184+
(getAlgName [_]
185+
(String. (reflect/call-method KeyPair 'getKeyTypeName [] kpair)))
186+
(getName [_] identity)
187+
(isEncrypted [_] (.. kpair isEncrypted))
188+
(clear [_] (.. kpair dispose)))
189+
130190
(defn add-identity
131-
"Add an identity to the agent.
191+
"Add an identity to the agent. The identity is passed with the :identity
192+
keyword argument, or constructed from the other keyword arguments.
132193
133194
:private-key A string specifying the private key
134195
:public-key A string specifying the public key
@@ -145,61 +206,12 @@
145206
^bytes passphrase]
146207
:as options}]
147208
{:pre [(map? options)]}
148-
(let [name (or name private-key-path public-key)
149-
id-repository (fn []
150-
(reflect/call-method
151-
com.jcraft.jsch.JSch 'getIdentityRepository []
152-
agent))
153-
local-repo? (fn [id-repo]
154-
;; LocalIdentityRepository is not public, so we can't use
155-
;; instance?
156-
(= "com.jcraft.jsch.LocalIdentityRepository"
157-
(.getName ^Class (type id-repo))))]
158-
(cond
159-
identity
160-
(.addIdentity agent identity passphrase)
161-
162-
private-key
163-
(let [^com.jcraft.jsch.IdentityRepository id-repo (id-repository)]
164-
(if (local-repo? id-repo)
165-
(.addIdentity
166-
agent name (as-bytes private-key) (as-bytes public-key) passphrase)
167-
(let [^KeyPair keypair
168-
(KeyPair/load
169-
agent (as-bytes private-key) (as-bytes public-key))]
170-
(when passphrase
171-
(.decrypt keypair passphrase))
172-
(.setPublicKeyComment keypair "Added by clj-ssh")
173-
(.add id-repo (.forSSHAgent keypair)))))
174-
175-
(and public-key-path private-key-path)
176-
(let [^com.jcraft.jsch.IdentityRepository id-repo (id-repository)]
177-
(if (local-repo? id-repo)
178-
(.addIdentity
179-
agent
180-
(file-path private-key-path) (file-path public-key-path) passphrase)
181-
(let [keypair (KeyPair/load agent private-key-path public-key-path)]
182-
(when passphrase
183-
(.decrypt keypair passphrase))
184-
(.setPublicKeyComment keypair name)
185-
(.add id-repo (.forSSHAgent keypair)))))
186-
187-
private-key-path
188-
(let [^com.jcraft.jsch.IdentityRepository id-repo (id-repository)]
189-
(if (local-repo? id-repo)
190-
(.addIdentity agent (file-path private-key-path) passphrase)
191-
(let [keypair (KeyPair/load agent private-key-path)]
192-
(when passphrase
193-
(.decrypt keypair passphrase))
194-
(.setPublicKeyComment keypair name)
195-
(.add id-repo (.forSSHAgent keypair)))))
196-
197-
:else
198-
(throw
199-
(ex-info
200-
"Don't know how to add identity"
201-
{:reason :do-not-know-how-to-add-identity
202-
:args options})))))
209+
(let [^String comment (or name private-key-path public-key)
210+
^Identity identity
211+
(or identity
212+
(KeyPairIdentity.
213+
agent comment (keypair agent (assoc options :comment comment))))]
214+
(.addIdentity agent identity passphrase)))
203215

204216
(defn add-identity-with-keychain
205217
"Add a private key, only if not already known, using the keychain to obtain

test/clj_ssh/ssh_test.clj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,12 @@
497497
(test-scp-to-with session)
498498
(test-scp-from-with session)))))
499499

500+
(deftest keypair-test
501+
(let [agent (ssh-agent {:use-system-ssh-agent false})]
502+
(is (keypair agent {:private-key-path (private-key-path)}))
503+
(is (keypair agent {:private-key-path (private-key-path)
504+
:public-key-path (public-key-path)}))))
505+
500506
(deftest generate-keypair-test
501507
(let [agent (ssh-agent {:use-system-ssh-agent false})]
502508
(let [[priv pub] (generate-keypair agent :rsa 1024 "hello")]

0 commit comments

Comments
 (0)