18
18
*/
19
19
package com .gitblit .transport .ssh ;
20
20
21
+ import java .io .File ;
21
22
import java .io .FileInputStream ;
22
23
import java .io .InputStreamReader ;
24
+ import java .security .KeyFactory ;
23
25
import java .security .KeyPair ;
26
+ import java .security .PrivateKey ;
27
+ import java .security .PublicKey ;
24
28
import java .util .Arrays ;
25
29
import java .util .Iterator ;
26
30
import java .util .NoSuchElementException ;
27
31
32
+ import org .apache .sshd .common .config .keys .PrivateKeyEntryDecoder ;
28
33
import org .apache .sshd .common .keyprovider .AbstractKeyPairProvider ;
29
34
import org .apache .sshd .common .util .security .SecurityUtils ;
30
- import org .bouncycastle .openssl .PEMDecryptorProvider ;
31
- import org .bouncycastle .openssl .PEMEncryptedKeyPair ;
32
35
import org .bouncycastle .openssl .PEMKeyPair ;
33
36
import org .bouncycastle .openssl .PEMParser ;
34
- import org .bouncycastle .openssl .PasswordFinder ;
35
37
import org .bouncycastle .openssl .jcajce .JcaPEMKeyConverter ;
36
- import org .bouncycastle .openssl .jcajce .JcePEMDecryptorProviderBuilder ;
38
+ import org .bouncycastle .crypto .params .AsymmetricKeyParameter ;
39
+ import org .bouncycastle .crypto .params .Ed25519PrivateKeyParameters ;
40
+ import org .bouncycastle .crypto .params .Ed25519PublicKeyParameters ;
41
+ import org .bouncycastle .crypto .util .OpenSSHPrivateKeyUtil ;
42
+ import org .bouncycastle .crypto .util .OpenSSHPublicKeyUtil ;
43
+ import org .bouncycastle .jcajce .spec .OpenSSHPrivateKeySpec ;
44
+ import org .bouncycastle .jcajce .spec .OpenSSHPublicKeySpec ;
45
+ import org .bouncycastle .util .io .pem .PemObject ;
46
+ import org .bouncycastle .util .io .pem .PemReader ;
37
47
38
48
/**
39
49
* This host key provider loads private keys from the specified files.
40
- *
50
+ * <p>
41
51
* Note that this class has a direct dependency on BouncyCastle and won't work
42
52
* unless it has been correctly registered as a security provider.
43
53
*
44
54
* @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD Project</a>
45
55
*/
46
- public class FileKeyPairProvider extends AbstractKeyPairProvider {
56
+ public class FileKeyPairProvider extends AbstractKeyPairProvider
57
+ {
47
58
48
59
private String [] files ;
49
- private PasswordFinder passwordFinder ;
50
-
51
- public FileKeyPairProvider () {
52
- }
53
60
54
- public FileKeyPairProvider (String [] files ) {
55
- this . files = files ;
61
+ public FileKeyPairProvider ()
62
+ {
56
63
}
57
64
58
- public FileKeyPairProvider (String [] files , PasswordFinder passwordFinder ) {
65
+ public FileKeyPairProvider (String [] files )
66
+ {
59
67
this .files = files ;
60
- this .passwordFinder = passwordFinder ;
61
68
}
62
69
63
- public String [] getFiles () {
70
+ public String [] getFiles ()
71
+ {
64
72
return files ;
65
73
}
66
74
67
- public void setFiles (String [] files ) {
75
+ public void setFiles (String [] files )
76
+ {
68
77
this .files = files ;
69
78
}
70
79
71
- public PasswordFinder getPasswordFinder () {
72
- return passwordFinder ;
73
- }
74
-
75
- public void setPasswordFinder (PasswordFinder passwordFinder ) {
76
- this .passwordFinder = passwordFinder ;
77
- }
78
80
79
- public Iterable <KeyPair > loadKeys () {
81
+ @ Override
82
+ public Iterable <KeyPair > loadKeys ()
83
+ {
80
84
if (!SecurityUtils .isBouncyCastleRegistered ()) {
81
85
throw new IllegalStateException ("BouncyCastle must be registered as a JCE provider" );
82
86
}
83
- return new Iterable <KeyPair >() {
87
+ return new Iterable <KeyPair >()
88
+ {
84
89
@ Override
85
- public Iterator <KeyPair > iterator () {
86
- return new Iterator <KeyPair >() {
90
+ public Iterator <KeyPair > iterator ()
91
+ {
92
+ return new Iterator <KeyPair >()
93
+ {
87
94
private final Iterator <String > iterator = Arrays .asList (files ).iterator ();
88
95
private KeyPair nextKeyPair ;
89
96
private boolean nextKeyPairSet = false ;
97
+
90
98
@ Override
91
- public boolean hasNext () {
99
+ public boolean hasNext ()
100
+ {
92
101
return nextKeyPairSet || setNextObject ();
93
102
}
103
+
94
104
@ Override
95
- public KeyPair next () {
105
+ public KeyPair next ()
106
+ {
96
107
if (!nextKeyPairSet ) {
97
108
if (!setNextObject ()) {
98
109
throw new NoSuchElementException ();
@@ -101,13 +112,22 @@ public KeyPair next() {
101
112
nextKeyPairSet = false ;
102
113
return nextKeyPair ;
103
114
}
115
+
104
116
@ Override
105
- public void remove () {
117
+ public void remove ()
118
+ {
106
119
throw new UnsupportedOperationException ();
107
120
}
108
- private boolean setNextObject () {
121
+
122
+ private boolean setNextObject ()
123
+ {
109
124
while (iterator .hasNext ()) {
110
125
String file = iterator .next ();
126
+ File f = new File (file );
127
+ if (!f .isFile ()) {
128
+ log .debug ("File does not exist, skipping {}" , file );
129
+ continue ;
130
+ }
111
131
nextKeyPair = doLoadKey (file );
112
132
if (nextKeyPair != null ) {
113
133
nextKeyPairSet = true ;
@@ -122,30 +142,71 @@ private boolean setNextObject() {
122
142
};
123
143
}
124
144
125
- protected KeyPair doLoadKey (String file ) {
145
+
146
+ private KeyPair doLoadKey (String file )
147
+ {
126
148
try {
127
- PEMParser r = new PEMParser (new InputStreamReader (new FileInputStream (file )));
128
- try {
149
+
150
+ try (PemReader r = new PemReader (new InputStreamReader (new FileInputStream (file )))) {
151
+ PemObject pemObject = r .readPemObject ();
152
+ if ("OPENSSH PRIVATE KEY" .equals (pemObject .getType ())) {
153
+ // This reads a properly OpenSSH formatted ed25519 private key file.
154
+ // It is currently unused because the SSHD library in play doesn't work with proper keys.
155
+ // This is kept in the hope that in the future the library offers proper support.
156
+ try {
157
+ byte [] privateKeyContent = pemObject .getContent ();
158
+ AsymmetricKeyParameter privateKeyParameters = OpenSSHPrivateKeyUtil .parsePrivateKeyBlob (privateKeyContent );
159
+ if (privateKeyParameters instanceof Ed25519PrivateKeyParameters ) {
160
+ OpenSSHPrivateKeySpec privkeySpec = new OpenSSHPrivateKeySpec (privateKeyContent );
161
+
162
+ Ed25519PublicKeyParameters publicKeyParameters = ((Ed25519PrivateKeyParameters )privateKeyParameters ).generatePublicKey ();
163
+ OpenSSHPublicKeySpec pubKeySpec = new OpenSSHPublicKeySpec (OpenSSHPublicKeyUtil .encodePublicKey (publicKeyParameters ));
164
+
165
+ KeyFactory kf = KeyFactory .getInstance ("Ed25519" , "BC" );
166
+ PrivateKey privateKey = kf .generatePrivate (privkeySpec );
167
+ PublicKey publicKey = kf .generatePublic (pubKeySpec );
168
+ return new KeyPair (publicKey , privateKey );
169
+ }
170
+ else {
171
+ log .warn ("OpenSSH format is only supported for Ed25519 key type. Unable to read key " + file );
172
+ }
173
+ }
174
+ catch (Exception e ) {
175
+ log .warn ("Unable to read key " + file , e );
176
+ }
177
+ return null ;
178
+ }
179
+
180
+ if ("EDDSA PRIVATE KEY" .equals (pemObject .getType ())) {
181
+ // This reads the ed25519 key from a file format that we created in SshDaemon.
182
+ // The type EDDSA PRIVATE KEY was given by us and nothing official.
183
+ byte [] privateKeyContent = pemObject .getContent ();
184
+ PrivateKeyEntryDecoder <? extends PublicKey ,? extends PrivateKey > decoder = SecurityUtils .getOpenSSHEDDSAPrivateKeyEntryDecoder ();
185
+ PrivateKey privateKey = decoder .decodePrivateKey (null , privateKeyContent , 0 , privateKeyContent .length );
186
+ PublicKey publicKey = SecurityUtils . recoverEDDSAPublicKey (privateKey );
187
+ return new KeyPair (publicKey , privateKey );
188
+ }
189
+ }
190
+
191
+ try (PEMParser r = new PEMParser (new InputStreamReader (new FileInputStream (file )))) {
129
192
Object o = r .readObject ();
130
193
131
194
JcaPEMKeyConverter pemConverter = new JcaPEMKeyConverter ();
132
195
pemConverter .setProvider ("BC" );
133
- if (passwordFinder != null && o instanceof PEMEncryptedKeyPair ) {
134
- JcePEMDecryptorProviderBuilder decryptorBuilder = new JcePEMDecryptorProviderBuilder ();
135
- PEMDecryptorProvider pemDecryptor = decryptorBuilder .build (passwordFinder .getPassword ());
136
- o = pemConverter .getKeyPair (((PEMEncryptedKeyPair ) o ).decryptKeyPair (pemDecryptor ));
137
- }
138
-
139
196
if (o instanceof PEMKeyPair ) {
140
197
o = pemConverter .getKeyPair ((PEMKeyPair )o );
141
- return (KeyPair ) o ;
142
- } else if (o instanceof KeyPair ) {
143
- return (KeyPair ) o ;
198
+ return (KeyPair )o ;
199
+ }
200
+ else if (o instanceof KeyPair ) {
201
+ return (KeyPair )o ;
202
+ }
203
+ else {
204
+ log .warn ("Cannot read unsupported PEM object of type: " + o .getClass ().getCanonicalName ());
144
205
}
145
- } finally {
146
- r .close ();
147
206
}
148
- } catch (Exception e ) {
207
+
208
+ }
209
+ catch (Exception e ) {
149
210
log .warn ("Unable to read key " + file , e );
150
211
}
151
212
return null ;
0 commit comments