From 2e1ef9dbcd153203ff5c384f9246fc9c3db2edfc Mon Sep 17 00:00:00 2001 From: Bernd Schuller Date: Mon, 20 Sep 2021 12:20:30 +0200 Subject: [PATCH] Support v3 PuTTY keys (#716) * Support v3 PuTTY keys * add test for putty v3 key * Format PuTTYKeyFile to fix Codacy warnings Signed-off-by: Jeroen van Erp Co-authored-by: Jeroen van Erp --- .../userauth/keyprovider/PuTTYKeyFile.java | 48 +++++++++---------- .../sshj/keyprovider/PuTTYKeyFileTest.java | 19 ++++++++ 2 files changed, 43 insertions(+), 24 deletions(-) diff --git a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java index 818ae7b6..41b95d6a 100644 --- a/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java +++ b/src/main/java/net/schmizz/sshj/userauth/keyprovider/PuTTYKeyFile.java @@ -45,6 +45,7 @@ /** *

Sample PuTTY file format

+ * *
  * PuTTY-User-Key-File-2: ssh-rsa
  * Encryption: none
@@ -70,8 +71,7 @@
  */
 public class PuTTYKeyFile extends BaseFileKeyProvider {
 
-    public static class Factory
-            implements net.schmizz.sshj.common.Factory.Named {
+    public static class Factory implements net.schmizz.sshj.common.Factory.Named {
 
         @Override
         public FileKeyProvider create() {
@@ -88,11 +88,16 @@ public String getName() {
     private byte[] publicKey;
 
     /**
-     * Key type. Either "ssh-rsa" for RSA key, or "ssh-dss" for DSA key.
+     * Key type
      */
     @Override
     public KeyType getType() throws IOException {
-        return KeyType.fromString(headers.get("PuTTY-User-Key-File-2"));
+        for (String h : headers.keySet()) {
+            if (h.startsWith("PuTTY-User-Key-File-")) {
+                return KeyType.fromString(headers.get(h));
+            }
+        }
+        return KeyType.UNKNOWN;
     }
 
     public boolean isEncrypted() {
@@ -100,21 +105,18 @@ public boolean isEncrypted() {
         return "aes256-cbc".equals(headers.get("Encryption"));
     }
 
-    private Map payload
-            = new HashMap();
+    private Map payload = new HashMap();
 
     /**
      * For each line that looks like "Xyz: vvv", it will be stored in this map.
      */
-    private final Map headers
-            = new HashMap();
-
+    private final Map headers = new HashMap();
 
     protected KeyPair readKeyPair() throws IOException {
         this.parseKeyPair();
         final Buffer.PlainBuffer publicKeyReader = new Buffer.PlainBuffer(publicKey);
         final Buffer.PlainBuffer privateKeyReader = new Buffer.PlainBuffer(privateKey);
-        publicKeyReader.readBytes();  // The first part of the payload is a human-readable key format name.
+        publicKeyReader.readBytes(); // The first part of the payload is a human-readable key format name.
         if (KeyType.RSA.equals(this.getType())) {
             // public key exponent
             BigInteger e = publicKeyReader.readMPInt();
@@ -131,10 +133,8 @@ protected KeyPair readKeyPair() throws IOException {
                 throw new IOException(s.getMessage(), s);
             }
             try {
-                return new KeyPair(
-                        factory.generatePublic(new RSAPublicKeySpec(n, e)),
-                        factory.generatePrivate(new RSAPrivateKeySpec(n, d))
-                );
+                return new KeyPair(factory.generatePublic(new RSAPublicKeySpec(n, e)),
+                        factory.generatePrivate(new RSAPrivateKeySpec(n, d)));
             } catch (InvalidKeySpecException i) {
                 throw new IOException(i.getMessage(), i);
             }
@@ -155,10 +155,8 @@ protected KeyPair readKeyPair() throws IOException {
                 throw new IOException(s.getMessage(), s);
             }
             try {
-                return new KeyPair(
-                        factory.generatePublic(new DSAPublicKeySpec(y, p, q, g)),
-                        factory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g))
-                );
+                return new KeyPair(factory.generatePublic(new DSAPublicKeySpec(y, p, q, g)),
+                        factory.generatePrivate(new DSAPrivateKeySpec(x, p, q, g)));
             } catch (InvalidKeySpecException e) {
                 throw new IOException(e.getMessage(), e);
             }
@@ -187,8 +185,8 @@ protected KeyPair readKeyPair() throws IOException {
         if (ecdsaCurve != null) {
             BigInteger s = new BigInteger(1, privateKeyReader.readBytes());
             X9ECParameters ecParams = NISTNamedCurves.getByName(ecdsaCurve);
-            ECNamedCurveSpec ecCurveSpec =
-                    new ECNamedCurveSpec(ecdsaCurve, ecParams.getCurve(), ecParams.getG(), ecParams.getN());
+            ECNamedCurveSpec ecCurveSpec = new ECNamedCurveSpec(ecdsaCurve, ecParams.getCurve(), ecParams.getG(),
+                    ecParams.getN());
             ECPrivateKeySpec pks = new ECPrivateKeySpec(s, ecCurveSpec);
             try {
                 PrivateKey privateKey = SecurityUtils.getKeyFactory(KeyAlgorithm.ECDSA).generatePrivate(pks);
@@ -247,7 +245,8 @@ protected void parseKeyPair() throws IOException {
     }
 
     /**
-     * Converts a passphrase into a key, by following the convention that PuTTY uses.
+     * Converts a passphrase into a key, by following the convention that PuTTY
+     * uses.
      * 

*

* This is used to decrypt the private key when it's encrypted. @@ -256,15 +255,16 @@ private byte[] toKey(final String passphrase) throws IOException { try { MessageDigest digest = MessageDigest.getInstance("SHA-1"); - // The encryption key is derived from the passphrase by means of a succession of SHA-1 hashes. + // The encryption key is derived from the passphrase by means of a succession of + // SHA-1 hashes. // Sequence number 0 - digest.update(new byte[]{0, 0, 0, 0}); + digest.update(new byte[] { 0, 0, 0, 0 }); digest.update(passphrase.getBytes()); byte[] key1 = digest.digest(); // Sequence number 1 - digest.update(new byte[]{0, 0, 0, 1}); + digest.update(new byte[] { 0, 0, 0, 1 }); digest.update(passphrase.getBytes()); byte[] key2 = digest.digest(); diff --git a/src/test/java/net/schmizz/sshj/keyprovider/PuTTYKeyFileTest.java b/src/test/java/net/schmizz/sshj/keyprovider/PuTTYKeyFileTest.java index b22b9840..e00c820b 100644 --- a/src/test/java/net/schmizz/sshj/keyprovider/PuTTYKeyFileTest.java +++ b/src/test/java/net/schmizz/sshj/keyprovider/PuTTYKeyFileTest.java @@ -226,6 +226,17 @@ public class PuTTYKeyFileTest { "nLEIBzB8WEaMMgDz5qwlqq7eBxLPIIi9uHyjMf7NOsQ=\n" + "Private-MAC: b200c6d801fc8dd8f84a14afc3b94d9f9bb2df90\n"; + final static String v3_ecdsa = "PuTTY-User-Key-File-3: ecdsa-sha2-nistp256\n" + + "Encryption: none\n" + + "Comment: ecdsa-key-20210819\n" + + "Public-Lines: 3\n" + + "AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBP5mbdlgVmkw\n" + + "LzDkznoY8TXKnok/mlMkpk8FELFNSECnXNdtZ4B8+Bpqnvchhk/jY/0tUU98lFxt\n" + + "JR0o0l8B5y0=\n" + + "Private-Lines: 1\n" + + "AAAAIEblmwyKaGuvc6dLgNeHsc1BuZeQORTSxBF5SBLNyjYc\n" + + "Private-MAC: e1aed15a209f48fdaa5228640f1109a7740340764a96f97ec6023da7f92d07ea"; + @Test public void test2048() throws Exception { PuTTYKeyFile key = new PuTTYKeyFile(); @@ -341,6 +352,14 @@ public void testEcDsa521() throws Exception { assertEquals(key.getPublic(), referenceKey.getPublic()); } + @Test + public void testV3Key() throws Exception { + PuTTYKeyFile key = new PuTTYKeyFile(); + key.init(new StringReader(v3_ecdsa)); + assertNotNull(key.getPrivate()); + assertNotNull(key.getPublic()); + } + @Test public void testCorrectPassphraseRsa() throws Exception { PuTTYKeyFile key = new PuTTYKeyFile();