Repository: mina-sshd Updated Branches: refs/heads/master 0f6976447 -> 4aef497ee
[SSHD-763] Add support for reading ECDSA PUTTY key files Project: http://git-wip-us.apache.org/repos/asf/mina-sshd/repo Commit: http://git-wip-us.apache.org/repos/asf/mina-sshd/commit/4aef497e Tree: http://git-wip-us.apache.org/repos/asf/mina-sshd/tree/4aef497e Diff: http://git-wip-us.apache.org/repos/asf/mina-sshd/diff/4aef497e Branch: refs/heads/master Commit: 4aef497ee8a5d161735da419591338cef9adec62 Parents: 0f69764 Author: Goldstein Lyor <l...@c-b4.com> Authored: Sun Sep 17 11:01:55 2017 +0300 Committer: Goldstein Lyor <l...@c-b4.com> Committed: Sun Sep 17 12:26:42 2017 +0300 ---------------------------------------------------------------------- .../loader/putty/AbstractPuttyKeyDecoder.java | 24 ++--- .../keys/loader/putty/DSSPuttyKeyDecoder.java | 6 +- .../keys/loader/putty/ECDSAPuttyKeyDecoder.java | 98 ++++++++++++++++++++ .../putty/PuttyKeyPairResourceParser.java | 11 +-- .../keys/loader/putty/PuttyKeyReader.java | 22 ++++- .../config/keys/loader/putty/PuttyKeyUtils.java | 22 +++-- .../keys/loader/putty/RSAPuttyKeyDecoder.java | 6 +- .../keys/loader/putty/PuttyKeyUtilsTest.java | 16 +++- ...KeyUtilsTest-ecdsa-sha2-nistp256-KeyPair.ppk | 10 ++ ...KeyUtilsTest-ecdsa-sha2-nistp384-KeyPair.ppk | 11 +++ ...KeyUtilsTest-ecdsa-sha2-nistp521-KeyPair.ppk | 12 +++ ...-AES-256-CBC-ecdsa-sha2-nistp256-KeyPair.ppk | 10 ++ ...-AES-256-CBC-ecdsa-sha2-nistp384-KeyPair.ppk | 11 +++ ...-AES-256-CBC-ecdsa-sha2-nistp521-KeyPair.ppk | 12 +++ .../org/apache/sshd/common/cipher/ECCurves.java | 9 +- .../config/keys/IdentityResourceLoader.java | 49 ++++++++++ .../common/config/keys/KeyEntryResolver.java | 22 +---- .../impl/AbstractIdentityResourceLoader.java | 62 +++++++++++++ .../keys/impl/AbstractKeyEntryResolver.java | 28 +----- .../keys/impl/ECDSAPublicKeyEntryDecoder.java | 2 +- .../apache/sshd/common/util/GenericUtils.java | 5 +- 21 files changed, 353 insertions(+), 95 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java index 5b29618..101119b 100644 --- a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java +++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/AbstractPuttyKeyDecoder.java @@ -25,35 +25,30 @@ import java.io.InputStream; import java.io.StreamCorruptedException; import java.security.GeneralSecurityException; import java.security.KeyPair; +import java.security.PrivateKey; +import java.security.PublicKey; import java.util.Base64; import java.util.Base64.Decoder; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Objects; import org.apache.sshd.common.config.keys.FilePasswordProvider; +import org.apache.sshd.common.config.keys.impl.AbstractIdentityResourceLoader; import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.ValidateUtils; -import org.apache.sshd.common.util.logging.AbstractLoggingBean; /** * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ -public abstract class AbstractPuttyKeyDecoder - extends AbstractLoggingBean - implements PuttyKeyPairResourceParser { +public abstract class AbstractPuttyKeyDecoder<PUB extends PublicKey, PRV extends PrivateKey> + extends AbstractIdentityResourceLoader<PUB, PRV> + implements PuttyKeyPairResourceParser<PUB, PRV> { public static final String ENCRYPTION_HEADER = "Encryption"; - private final String keyType; - protected AbstractPuttyKeyDecoder(String keyType) { - this.keyType = keyType; - } - - @Override - public String getKeyType() { - return keyType; + protected AbstractPuttyKeyDecoder(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) { + super(pubType, prvType, names); } @Override @@ -74,8 +69,9 @@ public abstract class AbstractPuttyKeyDecoder return false; } + Collection<String> supported = getSupportedTypeNames(); String typeValue = l.substring(pos + 1).trim(); - return Objects.equals(getKeyType(), typeValue); + return supported.contains(typeValue); } return false; http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java index c03a9d5..366aead 100644 --- a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java +++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/DSSPuttyKeyDecoder.java @@ -26,6 +26,8 @@ import java.security.KeyFactory; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.interfaces.DSAPrivateKey; +import java.security.interfaces.DSAPublicKey; import java.security.spec.DSAPrivateKeySpec; import java.security.spec.DSAPublicKeySpec; import java.util.Collection; @@ -38,11 +40,11 @@ import org.apache.sshd.common.util.security.SecurityUtils; /** * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ -public class DSSPuttyKeyDecoder extends AbstractPuttyKeyDecoder { +public class DSSPuttyKeyDecoder extends AbstractPuttyKeyDecoder<DSAPublicKey, DSAPrivateKey> { public static final DSSPuttyKeyDecoder INSTANCE = new DSSPuttyKeyDecoder(); public DSSPuttyKeyDecoder() { - super(KeyPairProvider.SSH_DSS); + super(DSAPublicKey.class, DSAPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_DSS)); } @Override http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/ECDSAPuttyKeyDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/ECDSAPuttyKeyDecoder.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/ECDSAPuttyKeyDecoder.java new file mode 100644 index 0000000..03bfa6c --- /dev/null +++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/ECDSAPuttyKeyDecoder.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sshd.common.config.keys.loader.putty; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.GeneralSecurityException; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.interfaces.ECPrivateKey; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECParameterSpec; +import java.security.spec.ECPoint; +import java.security.spec.ECPrivateKeySpec; +import java.security.spec.ECPublicKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.util.Collection; +import java.util.Collections; + +import org.apache.sshd.common.cipher.ECCurves; +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.util.buffer.BufferUtils; +import org.apache.sshd.common.util.security.SecurityUtils; + +/** + * TODO Add javadoc + * + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public class ECDSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder<ECPublicKey, ECPrivateKey> { + public static final ECDSAPuttyKeyDecoder INSTANCE = new ECDSAPuttyKeyDecoder(); + + public ECDSAPuttyKeyDecoder() { + super(ECPublicKey.class, ECPrivateKey.class, ECCurves.KEY_TYPES); + } + + @Override + public Collection<KeyPair> loadKeyPairs(String resourceKey, PuttyKeyReader pubReader, PuttyKeyReader prvReader) + throws IOException, GeneralSecurityException { + String keyType = pubReader.readString(); + ECCurves curve = ECCurves.fromKeyType(keyType); + if (curve == null) { + throw new InvalidKeySpecException("Not an EC curve name: " + keyType); + } + + if (!SecurityUtils.isECCSupported()) { + throw new NoSuchProviderException("ECC not supported"); + } + + String encCurveName = pubReader.readString(); + String keyCurveName = curve.getName(); + if (!keyCurveName.equals(encCurveName)) { + throw new InvalidKeySpecException("Mismatched key curve name (" + keyCurveName + ") vs. encoded one (" + encCurveName + ")"); + } + + byte[] octets = pubReader.read(); + ECPoint w; + try { + w = ECCurves.octetStringToEcPoint(octets); + if (w == null) { + throw new InvalidKeySpecException("No public ECPoint generated for curve=" + keyCurveName + + " from octets=" + BufferUtils.toHex(':', octets)); + } + } catch (RuntimeException e) { + throw new InvalidKeySpecException("Failed (" + e.getClass().getSimpleName() + ")" + + " to generate public ECPoint for curve=" + keyCurveName + + " from octets=" + BufferUtils.toHex(':', octets) + + ": " + e.getMessage()); + } + + KeyFactory kf = SecurityUtils.getKeyFactory(KeyUtils.EC_ALGORITHM); + ECParameterSpec paramSpec = curve.getParameters(); + PublicKey pubKey = kf.generatePublic(new ECPublicKeySpec(w, paramSpec)); + + BigInteger s = prvReader.readInt(); + PrivateKey prvKey = kf.generatePrivate(new ECPrivateKeySpec(s, paramSpec)); + return Collections.singletonList(new KeyPair(pubKey, prvKey)); + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyPairResourceParser.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyPairResourceParser.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyPairResourceParser.java index 774624b..a1e795b 100644 --- a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyPairResourceParser.java +++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyPairResourceParser.java @@ -25,6 +25,8 @@ import java.security.GeneralSecurityException; import java.security.KeyPair; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.spec.InvalidKeySpecException; import java.util.Arrays; import java.util.Collections; @@ -35,6 +37,7 @@ import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import org.apache.sshd.common.config.keys.IdentityResourceLoader; import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; import org.apache.sshd.common.digest.BuiltinDigests; import org.apache.sshd.common.util.GenericUtils; @@ -87,7 +90,8 @@ import org.apache.sshd.common.util.security.SecurityUtils; * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ //CHECKSTYLE:ON -public interface PuttyKeyPairResourceParser extends KeyPairResourceParser { +public interface PuttyKeyPairResourceParser<PUB extends PublicKey, PRV extends PrivateKey> + extends IdentityResourceLoader<PUB, PRV>, KeyPairResourceParser { String KEY_FILE_HEADER_PREFIX = "PuTTY-User-Key-File"; String PUBLIC_LINES_HEADER = "Public-Lines"; String PRIVATE_LINES_HEADER = "Private-Lines"; @@ -105,11 +109,6 @@ public interface PuttyKeyPairResourceParser extends KeyPairResourceParser { */ String NO_PRIVATE_KEY_ENCRYPTION_VALUE = "none"; - /** - * @return Type of key being parsed by the resource parser - */ - String getKeyType(); - @Override default boolean canExtractKeyPairs(String resourceKey, List<String> lines) throws IOException, GeneralSecurityException { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyReader.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyReader.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyReader.java index 9f1bd73..4fb63d1 100644 --- a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyReader.java +++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyReader.java @@ -25,6 +25,8 @@ import java.io.IOException; import java.io.InputStream; import java.io.StreamCorruptedException; import java.math.BigInteger; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; /** * Helper class for {@code Putty} key files decoders @@ -45,17 +47,27 @@ public class PuttyKeyReader implements Closeable { } } - private byte[] read() throws IOException { + public String readString() throws IOException { + return readString(StandardCharsets.UTF_8); + } + + public String readString(Charset cs) throws IOException { + byte[] data = read(); + return new String(data, cs); + } + + public BigInteger readInt() throws IOException { + byte[] bytes = read(); + return new BigInteger(bytes); + } + + public byte[] read() throws IOException { int len = di.readInt(); byte[] r = new byte[len]; di.readFully(r); return r; } - public BigInteger readInt() throws IOException { - return new BigInteger(read()); - } - @Override public void close() throws IOException { di.close(); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtils.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtils.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtils.java index 61ad233..a3b4745 100644 --- a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtils.java +++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtils.java @@ -20,9 +20,10 @@ package org.apache.sshd.common.config.keys.loader.putty; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.SortedMap; +import java.util.NavigableMap; import java.util.TreeMap; import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; @@ -31,20 +32,25 @@ import org.apache.sshd.common.config.keys.loader.KeyPairResourceParser; * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ public final class PuttyKeyUtils { - public static final List<PuttyKeyPairResourceParser> DEFAULT_PARSERS = + public static final List<PuttyKeyPairResourceParser<?, ?>> DEFAULT_PARSERS = Collections.unmodifiableList( Arrays.asList( RSAPuttyKeyDecoder.INSTANCE, - DSSPuttyKeyDecoder.INSTANCE)); - public static final SortedMap<String, PuttyKeyPairResourceParser> BY_KEY_TYPE = - Collections.unmodifiableSortedMap( - new TreeMap<String, PuttyKeyPairResourceParser>(String.CASE_INSENSITIVE_ORDER) { + DSSPuttyKeyDecoder.INSTANCE, + ECDSAPuttyKeyDecoder.INSTANCE)); + + public static final NavigableMap<String, PuttyKeyPairResourceParser<?, ?>> BY_KEY_TYPE = + Collections.unmodifiableNavigableMap( + new TreeMap<String, PuttyKeyPairResourceParser<?, ?>>(String.CASE_INSENSITIVE_ORDER) { // Not serializing it private static final long serialVersionUID = 1L; { - for (PuttyKeyPairResourceParser p : DEFAULT_PARSERS) { - put(p.getKeyType(), p); + for (PuttyKeyPairResourceParser<?, ?> p : DEFAULT_PARSERS) { + Collection<String> supported = p.getSupportedTypeNames(); + for (String k : supported) { + put(k, p); + } } } }); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java index d0de05d..0a55d55 100644 --- a/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java +++ b/sshd-contrib/src/main/java/org/apache/sshd/common/config/keys/loader/putty/RSAPuttyKeyDecoder.java @@ -26,6 +26,8 @@ import java.security.KeyFactory; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; import java.security.spec.RSAPrivateCrtKeySpec; import java.security.spec.RSAPrivateKeySpec; import java.security.spec.RSAPublicKeySpec; @@ -39,11 +41,11 @@ import org.apache.sshd.common.util.security.SecurityUtils; /** * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ -public class RSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder { +public class RSAPuttyKeyDecoder extends AbstractPuttyKeyDecoder<RSAPublicKey, RSAPrivateKey> { public static final RSAPuttyKeyDecoder INSTANCE = new RSAPuttyKeyDecoder(); public RSAPuttyKeyDecoder() { - super(KeyPairProvider.SSH_RSA); + super(RSAPublicKey.class, RSAPrivateKey.class, Collections.singletonList(KeyPairProvider.SSH_RSA)); } @Override http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest.java ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest.java b/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest.java index e8208d1..5ab953a 100644 --- a/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest.java +++ b/sshd-contrib/src/test/java/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest.java @@ -55,7 +55,7 @@ public class PuttyKeyUtilsTest extends BaseTestSupport { private final String keyType; private final String regularFile; private final String encryptedFile; - private final PuttyKeyPairResourceParser parser; + private final PuttyKeyPairResourceParser<?, ?> parser; public PuttyKeyUtilsTest(String keyType) { this.keyType = keyType; @@ -77,17 +77,24 @@ public class PuttyKeyUtilsTest extends BaseTestSupport { public void testCanDecodePuttyKeyFile() throws IOException, GeneralSecurityException { for (String resource : new String[]{regularFile, encryptedFile}) { URL url = getClass().getResource(resource); - assertNotNull("Missing test resource: " + resource, url); + if (GenericUtils.isSameReference(regularFile, resource)) { + assertNotNull("Missing test resource: " + resource, url); + } else { + if (url == null) { + outputDebugMessage("Skip non-existing encrypted file: %s", resource); + continue; + } + } List<String> lines = IoUtils.readAllLines(url); assertTrue(resource + " - can extract key pair", parser.canExtractKeyPairs(resource, lines)); - for (PuttyKeyPairResourceParser other : PuttyKeyUtils.BY_KEY_TYPE.values()) { + for (PuttyKeyPairResourceParser<?, ?> other : PuttyKeyUtils.BY_KEY_TYPE.values()) { if (parser == other) { continue; } - assertFalse(other.getKeyType() + "/" + resource + " - unexpected extraction capability", + assertFalse(other.getClass().getSimpleName() + "/" + resource + " - unexpected extraction capability", other.canExtractKeyPairs(resource, lines)); } } @@ -107,6 +114,7 @@ public class PuttyKeyUtilsTest extends BaseTestSupport { public void testDecodeEncryptedPuttyKeyFile() throws IOException, GeneralSecurityException { Assume.assumeTrue(BuiltinCiphers.aes256cbc.getTransformation() + " N/A", BuiltinCiphers.aes256cbc.isSupported()); URL url = getClass().getResource(encryptedFile); + Assume.assumeTrue("Skip non-existent encrypted file: " + encryptedFile, url != null); assertNotNull("Missing test resource: " + encryptedFile, url); Collection<KeyPair> keys = parser.loadKeyPairs(url, r -> PASSWORD); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ecdsa-sha2-nistp256-KeyPair.ppk ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ecdsa-sha2-nistp256-KeyPair.ppk b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ecdsa-sha2-nistp256-KeyPair.ppk new file mode 100644 index 0000000..509538a --- /dev/null +++ b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ecdsa-sha2-nistp256-KeyPair.ppk @@ -0,0 +1,10 @@ +PuTTY-User-Key-File-2: ecdsa-sha2-nistp256 +Encryption: none +Comment: ecdsa-key-20170917 +Public-Lines: 3 +AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBM99zj2+E6AN +xMZ/2SKFP/fAvPfUJUdsgJyn4g7nf36vNpoaRyq1FyHLxyT34AgTl1n3DwcaBXXC +O5pCv6xFwYk= +Private-Lines: 1 +AAAAIQDdmu/Dr68r6a0PNbQUN1bX+zS6J5jFsOlEAx8sR73Tuw== +Private-MAC: 012e0d61593a431ae84beb6216dd29e4b203c1c0 http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ecdsa-sha2-nistp384-KeyPair.ppk ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ecdsa-sha2-nistp384-KeyPair.ppk b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ecdsa-sha2-nistp384-KeyPair.ppk new file mode 100644 index 0000000..238261c --- /dev/null +++ b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ecdsa-sha2-nistp384-KeyPair.ppk @@ -0,0 +1,11 @@ +PuTTY-User-Key-File-2: ecdsa-sha2-nistp384 +Encryption: none +Comment: ecdsa-key-20170917 +Public-Lines: 3 +AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBNHjIMrdMfXw +CUqBAhkZw0vXB+qypkiTcL1CmcopmPrKvGHFieFmedeCQotjwJkoAAeb5isZNOXy +h+7TnHGNrE/pZkHuNwACilpOt659hbhR2OGHX0jdpb8y4RVkuPQssg== +Private-Lines: 2 +AAAAMHNGt3UPG3evJVl1GRoXXnqTafWLDQdWA2A1tXi8oRW0hhHMRe9/v0SCGL7S +nL3asg== +Private-MAC: 6fb6e93559ecacfa468aa5ff9776e4d4283db5ba http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ecdsa-sha2-nistp521-KeyPair.ppk ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ecdsa-sha2-nistp521-KeyPair.ppk b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ecdsa-sha2-nistp521-KeyPair.ppk new file mode 100644 index 0000000..f60a78e --- /dev/null +++ b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/PuttyKeyUtilsTest-ecdsa-sha2-nistp521-KeyPair.ppk @@ -0,0 +1,12 @@ +PuTTY-User-Key-File-2: ecdsa-sha2-nistp521 +Encryption: none +Comment: ecdsa-key-20170917 +Public-Lines: 4 +AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADGM237T9rT +zE++sOFDN0VWfYfojlQ8dYP82OlgA24Yh0ZpOsezBBiHtHfMHl9tWHmch1YKmH7B +lOfqbOgcIhz9cwA2V7Nu3IUGqxZT18LOXEpcdyDSphJ6jsy1urqBLrOz4DF6Udyr +rFV4OQELovf8YdUsM91YPfe1DfnSRi1I5v20uA== +Private-Lines: 2 +AAAAQgE28iZoHARx+2VzL7Y45FaY44TngX2b4StlC8wOlYF7NY/ba+Pt2RT/WcNL +ytmLdj5QeV/fFJ9x8i00mTU6KCF2AA== +Private-MAC: 7379e9986066087dff9339d2b0b968c2b31f45c7 http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ecdsa-sha2-nistp256-KeyPair.ppk ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ecdsa-sha2-nistp256-KeyPair.ppk b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ecdsa-sha2-nistp256-KeyPair.ppk new file mode 100644 index 0000000..4c601c7 --- /dev/null +++ b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ecdsa-sha2-nistp256-KeyPair.ppk @@ -0,0 +1,10 @@ +PuTTY-User-Key-File-2: ecdsa-sha2-nistp256 +Encryption: aes256-cbc +Comment: ecdsa-key-20170917 +Public-Lines: 3 +AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBM99zj2+E6AN +xMZ/2SKFP/fAvPfUJUdsgJyn4g7nf36vNpoaRyq1FyHLxyT34AgTl1n3DwcaBXXC +O5pCv6xFwYk= +Private-Lines: 1 +/8MdniIqAaST5t3/bRx4mFdFxqN8jIwI0Hh7VTy8IV143j+PktgGIoHsUwTiEujQ +Private-MAC: be4b37d4b65ad09e6890534a2ba355599da796c6 http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ecdsa-sha2-nistp384-KeyPair.ppk ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ecdsa-sha2-nistp384-KeyPair.ppk b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ecdsa-sha2-nistp384-KeyPair.ppk new file mode 100644 index 0000000..f57f5fd --- /dev/null +++ b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ecdsa-sha2-nistp384-KeyPair.ppk @@ -0,0 +1,11 @@ +PuTTY-User-Key-File-2: ecdsa-sha2-nistp384 +Encryption: aes256-cbc +Comment: ecdsa-key-20170917 +Public-Lines: 3 +AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBNHjIMrdMfXw +CUqBAhkZw0vXB+qypkiTcL1CmcopmPrKvGHFieFmedeCQotjwJkoAAeb5isZNOXy +h+7TnHGNrE/pZkHuNwACilpOt659hbhR2OGHX0jdpb8y4RVkuPQssg== +Private-Lines: 2 +MOgJnSZ8ZqGHFVtQvYKpOyTGWjMVIjIOMIUwhbxBuTiQ7WEyNPn9jjTsSwXtJxrG +UI6NGeIqZ41P2e8JINhMIg== +Private-MAC: 5ec40946270150ddfca35cce61f4265d7bfe7b7f http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ecdsa-sha2-nistp521-KeyPair.ppk ---------------------------------------------------------------------- diff --git a/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ecdsa-sha2-nistp521-KeyPair.ppk b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ecdsa-sha2-nistp521-KeyPair.ppk new file mode 100644 index 0000000..97077fd --- /dev/null +++ b/sshd-contrib/src/test/resources/org/apache/sshd/common/config/keys/loader/putty/super-secret-passphrase-AES-256-CBC-ecdsa-sha2-nistp521-KeyPair.ppk @@ -0,0 +1,12 @@ +PuTTY-User-Key-File-2: ecdsa-sha2-nistp521 +Encryption: aes256-cbc +Comment: ecdsa-key-20170917 +Public-Lines: 4 +AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADGM237T9rT +zE++sOFDN0VWfYfojlQ8dYP82OlgA24Yh0ZpOsezBBiHtHfMHl9tWHmch1YKmH7B +lOfqbOgcIhz9cwA2V7Nu3IUGqxZT18LOXEpcdyDSphJ6jsy1urqBLrOz4DF6Udyr +rFV4OQELovf8YdUsM91YPfe1DfnSRi1I5v20uA== +Private-Lines: 2 ++44AQO4aflPquBZbdB3UtdLuXuHV2u1YoghxYXPFGdhskMt+XjJhUlrOHNX8rmgR +E9nni474zGw9ni/3LIZwMILQI/xTfiQm3t6nKFV8nyI= +Private-MAC: 99c25e348a84de4876163758ad13b2ad1dc43629 http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java b/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java index 083778b..93e420c 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/cipher/ECCurves.java @@ -34,6 +34,7 @@ import java.util.Collections; import java.util.Comparator; import java.util.EnumSet; import java.util.List; +import java.util.NavigableSet; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -110,8 +111,8 @@ public enum ECCurves implements NamedResource, OptionalFeature { /** * A {@link Set} of all the known curves names */ - public static final Set<String> NAMES = - Collections.unmodifiableSet(GenericUtils.mapSort( + public static final NavigableSet<String> NAMES = + Collections.unmodifiableNavigableSet(GenericUtils.mapSort( VALUES, ECCurves::getName, String.CASE_INSENSITIVE_ORDER)); @@ -119,8 +120,8 @@ public enum ECCurves implements NamedResource, OptionalFeature { /** * A {@link Set} of all the known curves key types */ - public static final Set<String> KEY_TYPES = - Collections.unmodifiableSet(GenericUtils.mapSort( + public static final NavigableSet<String> KEY_TYPES = + Collections.unmodifiableNavigableSet(GenericUtils.mapSort( VALUES, ECCurves::getKeyType, String.CASE_INSENSITIVE_ORDER)); http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-core/src/main/java/org/apache/sshd/common/config/keys/IdentityResourceLoader.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/IdentityResourceLoader.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/IdentityResourceLoader.java new file mode 100644 index 0000000..d826821 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/IdentityResourceLoader.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sshd.common.config.keys; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Collection; + +/** + * @param <PUB> Type of {@link PublicKey} + * @param <PRV> Type of {@link PrivateKey} + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public interface IdentityResourceLoader<PUB extends PublicKey, PRV extends PrivateKey> { + /** + * @return The {@link Class} of the {@link PublicKey} that is the result + * of decoding + */ + Class<PUB> getPublicKeyType(); + + /** + * @return The {@link Class} of the {@link PrivateKey} that matches the + * public one + */ + Class<PRV> getPrivateKeyType(); + + /** + * @return The {@link Collection} of {@code OpenSSH} key type names that + * are supported by this decoder - e.g., ECDSA keys have several curve names. + * <B>Caveat:</B> this collection may be un-modifiable... + */ + Collection<String> getSupportedTypeNames(); +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java index 8cf8fa6..4bfbea0 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/KeyEntryResolver.java @@ -32,7 +32,6 @@ import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.PrivateKey; import java.security.PublicKey; -import java.util.Collection; import org.apache.sshd.common.util.io.IoUtils; @@ -41,26 +40,7 @@ import org.apache.sshd.common.util.io.IoUtils; * @param <PRV> Type of {@link PrivateKey} * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ -public interface KeyEntryResolver<PUB extends PublicKey, PRV extends PrivateKey> { - /** - * @return The {@link Class} of the {@link PublicKey} that is the result - * of decoding - */ - Class<PUB> getPublicKeyType(); - - /** - * @return The {@link Class} of the {@link PrivateKey} that matches the - * public one - */ - Class<PRV> getPrivateKeyType(); - - /** - * @return The {@link Collection} of {@code OpenSSH} key type names that - * are supported by this decoder - e.g., ECDSA keys have several curve names. - * <B>Caveat:</B> this collection may be un-modifiable... - */ - Collection<String> getSupportedTypeNames(); - +public interface KeyEntryResolver<PUB extends PublicKey, PRV extends PrivateKey> extends IdentityResourceLoader<PUB, PRV> { /** * @param keySize Key size in bits * @return A {@link KeyPair} with the specified key size http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractIdentityResourceLoader.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractIdentityResourceLoader.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractIdentityResourceLoader.java new file mode 100644 index 0000000..fbe8d71 --- /dev/null +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractIdentityResourceLoader.java @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.sshd.common.config.keys.impl; + +import java.security.PrivateKey; +import java.security.PublicKey; +import java.util.Collection; +import java.util.Objects; + +import org.apache.sshd.common.config.keys.IdentityResourceLoader; +import org.apache.sshd.common.util.ValidateUtils; +import org.apache.sshd.common.util.logging.AbstractLoggingBean; + +/** + * TODO Add javadoc + * + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +public abstract class AbstractIdentityResourceLoader<PUB extends PublicKey, PRV extends PrivateKey> + extends AbstractLoggingBean + implements IdentityResourceLoader<PUB, PRV> { + private final Class<PUB> pubType; + private final Class<PRV> prvType; + private final Collection<String> names; + + protected AbstractIdentityResourceLoader(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) { + this.pubType = Objects.requireNonNull(pubType, "No public key type specified"); + this.prvType = Objects.requireNonNull(prvType, "No private key type specified"); + this.names = ValidateUtils.checkNotNullAndNotEmpty(names, "No type names provided"); + } + + @Override + public final Class<PUB> getPublicKeyType() { + return pubType; + } + + @Override + public final Class<PRV> getPrivateKeyType() { + return prvType; + } + + @Override + public Collection<String> getSupportedTypeNames() { + return names; + } +} http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java index e8a78f3..7afa3a6 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/AbstractKeyEntryResolver.java @@ -25,11 +25,8 @@ import java.security.PrivateKey; import java.security.PublicKey; import java.security.spec.KeySpec; import java.util.Collection; -import java.util.Objects; import org.apache.sshd.common.config.keys.KeyEntryResolver; -import org.apache.sshd.common.util.ValidateUtils; -import org.apache.sshd.common.util.logging.AbstractLoggingBean; /** * @param <PUB> Type of {@link PublicKey} @@ -37,31 +34,10 @@ import org.apache.sshd.common.util.logging.AbstractLoggingBean; * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ public abstract class AbstractKeyEntryResolver<PUB extends PublicKey, PRV extends PrivateKey> - extends AbstractLoggingBean + extends AbstractIdentityResourceLoader<PUB, PRV> implements KeyEntryResolver<PUB, PRV> { - private final Class<PUB> pubType; - private final Class<PRV> prvType; - private final Collection<String> names; - protected AbstractKeyEntryResolver(Class<PUB> pubType, Class<PRV> prvType, Collection<String> names) { - this.pubType = Objects.requireNonNull(pubType, "No public key type specified"); - this.prvType = Objects.requireNonNull(prvType, "No private key type specified"); - this.names = ValidateUtils.checkNotNullAndNotEmpty(names, "No type names provided"); - } - - @Override - public final Class<PUB> getPublicKeyType() { - return pubType; - } - - @Override - public final Class<PRV> getPrivateKeyType() { - return prvType; - } - - @Override - public Collection<String> getSupportedTypeNames() { - return names; + super(pubType, prvType, names); } public PUB generatePublicKey(KeySpec keySpec) throws GeneralSecurityException { http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java index e6d4652..397a007 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/config/keys/impl/ECDSAPublicKeyEntryDecoder.java @@ -70,7 +70,6 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC } String keyCurveName = curve.getName(); - ECParameterSpec paramSpec = curve.getParameters(); // see rfc5656 section 3.1 String encCurveName = KeyEntryResolver.decodeString(keyData); if (!keyCurveName.equals(encCurveName)) { @@ -92,6 +91,7 @@ public class ECDSAPublicKeyEntryDecoder extends AbstractPublicKeyEntryDecoder<EC + ": " + e.getMessage()); } + ECParameterSpec paramSpec = curve.getParameters(); return generatePublicKey(new ECPublicKeySpec(w, paramSpec)); } http://git-wip-us.apache.org/repos/asf/mina-sshd/blob/4aef497e/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java ---------------------------------------------------------------------- diff --git a/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java b/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java index 7628ec9..b5ded29 100644 --- a/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java +++ b/sshd-core/src/main/java/org/apache/sshd/common/util/GenericUtils.java @@ -34,6 +34,7 @@ import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.NavigableSet; import java.util.Objects; import java.util.Set; import java.util.SortedMap; @@ -425,7 +426,7 @@ public final class GenericUtils { return stream(values).map(mapper).collect(Collectors.toList()); } - public static <T, U> SortedSet<U> mapSort( + public static <T, U> NavigableSet<U> mapSort( Collection<T> values, Function<? super T, ? extends U> mapper, Comparator<U> comparator) { return stream(values).map(mapper).collect(toSortedSet(comparator)); } @@ -446,7 +447,7 @@ public final class GenericUtils { }; } - public static <T> Collector<T, ?, SortedSet<T>> toSortedSet(Comparator<T> comparator) { + public static <T> Collector<T, ?, NavigableSet<T>> toSortedSet(Comparator<T> comparator) { return Collectors.toCollection(() -> new TreeSet<>(comparator)); }