1111// See the License for the specific language governing permissions and
1212// limitations under the License.
1313
14+ using java . io ;
1415using java . nio . file ;
16+ using java . security ;
17+ using org . apache . sshd . common . config . keys ;
18+ using org . apache . sshd . common . config . keys . writer . openssh ;
19+ using org . apache . sshd . common . util . io . resource ;
20+ using org . apache . sshd . common . util . security ;
1521using org . apache . sshd . server . keyprovider ;
1622
1723namespace ApacheMinaSSHD . NET . Wrapper . Factories
@@ -21,6 +27,10 @@ namespace ApacheMinaSSHD.NET.Wrapper.Factories
2127 /// </summary>
2228 public class AMNetSimpleGeneratorHostKeyProvider
2329 {
30+ private static readonly string TempFilePrefix = "amnet-sshd-hostkey-" ;
31+ private static readonly System . Collections . Concurrent . ConcurrentBag < string > _tempFiles = new ( ) ;
32+ private static bool _cleanupRegistered ;
33+
2434 /// <summary>
2535 /// Creates a generated host key provider.
2636 /// </summary>
@@ -182,8 +192,20 @@ public void ResolveKeyPath()
182192
183193 internal SimpleGeneratorHostKeyProvider ToJavaKeyPairProvider ( )
184194 {
185- var provider = new SimpleGeneratorHostKeyProvider ( ) ;
186195 ResolveKeyPath ( ) ;
196+
197+ if ( ! string . IsNullOrWhiteSpace ( ResolvedKeyPath )
198+ && ! string . IsNullOrEmpty ( Password )
199+ && System . IO . File . Exists ( ResolvedKeyPath ) )
200+ {
201+ string ? decryptedPath = TryDecryptHostKey ( ResolvedKeyPath , Password ) ;
202+ if ( decryptedPath != null )
203+ {
204+ ResolvedKeyPath = decryptedPath ;
205+ }
206+ }
207+
208+ var provider = new SimpleGeneratorHostKeyProvider ( ) ;
187209 if ( ! string . IsNullOrWhiteSpace ( ResolvedKeyPath ) )
188210 {
189211 provider . setPath ( Paths . get ( ResolvedKeyPath ) ) ;
@@ -193,12 +215,83 @@ internal SimpleGeneratorHostKeyProvider ToJavaKeyPairProvider()
193215 provider . setKeySize ( KeySize ) ;
194216 provider . setStrictFilePermissions ( StrictFilePermissions ) ;
195217
196- // Note: Password/passphrase protection for host keys is not supported
197- // by the upstream SimpleGeneratorHostKeyProvider in this SSHD version.
198- // If provided, the password is stored for future compatibility when/whether
199- // the upstream adds this capability.
200-
201218 return provider ;
202219 }
220+
221+ private static string ? TryDecryptHostKey ( string keyPath , string password )
222+ {
223+ try
224+ {
225+ var path = Paths . get ( keyPath ) ;
226+ var resourceKey = new PathResource ( path ) ;
227+ var passwordProvider = FilePasswordProvider . of ( password ) ;
228+
229+ java . io . InputStream ? inputStream = null ;
230+ java . io . OutputStream ? os = null ;
231+ try
232+ {
233+ inputStream = java . nio . file . Files . newInputStream ( path ) ;
234+ var keyPairs = SecurityUtils . loadKeyPairIdentities (
235+ null , resourceKey , inputStream , passwordProvider ) ;
236+
237+ if ( keyPairs == null || ! keyPairs . iterator ( ) . hasNext ( ) )
238+ {
239+ return null ;
240+ }
241+
242+ var kp = ( KeyPair ) keyPairs . iterator ( ) . next ( ) ;
243+ string tempFile = System . IO . Path . Combine (
244+ System . IO . Path . GetTempPath ( ) ,
245+ TempFilePrefix + Guid . NewGuid ( ) + ".openssh" ) ;
246+
247+ var tempPath = Paths . get ( tempFile ) ;
248+ os = java . nio . file . Files . newOutputStream ( tempPath ) ;
249+ var writer = new OpenSSHKeyPairResourceWriter ( ) ;
250+ writer . writePrivateKey ( kp , "host key" , null , os ) ;
251+
252+ RegisterTempFileCleanup ( tempFile ) ;
253+ return tempFile ;
254+ }
255+ finally
256+ {
257+ inputStream ? . close ( ) ;
258+ os ? . close ( ) ;
259+ }
260+ }
261+ catch ( System . Exception ex )
262+ {
263+ System . Diagnostics . Debug . WriteLine (
264+ $ "[AMNetSimpleGeneratorHostKeyProvider] Failed to decrypt host key '{ keyPath } ': { ex . Message } ") ;
265+ return null ;
266+ }
267+ }
268+
269+ private static void RegisterTempFileCleanup ( string path )
270+ {
271+ _tempFiles . Add ( path ) ;
272+
273+ if ( ! _cleanupRegistered )
274+ {
275+ _cleanupRegistered = true ;
276+ AppDomain . CurrentDomain . ProcessExit += ( _ , _ ) => CleanupTempFiles ( ) ;
277+ }
278+ }
279+
280+ private static void CleanupTempFiles ( )
281+ {
282+ foreach ( string file in _tempFiles )
283+ {
284+ try
285+ {
286+ if ( System . IO . File . Exists ( file ) )
287+ {
288+ System . IO . File . Delete ( file ) ;
289+ }
290+ }
291+ catch
292+ {
293+ }
294+ }
295+ }
203296 }
204297}
0 commit comments