This is an automated email from the ASF dual-hosted git repository. lgoldstein pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit c85261df4e0db3f8015461ec5705a5008e251a9b Author: Lyor Goldstein <lgoldst...@apache.org> AuthorDate: Fri Nov 20 20:00:54 2020 +0200 [SSHD-1104] Added RSA signature variants tests --- .../common/config/keys/OpenSshCertificate.java | 6 + .../common/config/keys/OpenSshCertificateImpl.java | 3 + .../FileHostKeyCertificateProvider.java | 27 ++-- .../keyprovider/HostKeyCertificateProvider.java | 15 +- .../org/apache/sshd/common/util/buffer/Buffer.java | 8 +- .../auth/pubkey/RSAVariantsAuthPublicKeyTest.java | 154 +++++++++++++++++++++ .../org/apache/sshd/util/test/BaseTestSupport.java | 26 +++- .../sshd/scp/client/AbstractScpTestSupport.java | 4 +- 8 files changed, 215 insertions(+), 28 deletions(-) diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/OpenSshCertificate.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/OpenSshCertificate.java index d51db78..90406e4 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/OpenSshCertificate.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/OpenSshCertificate.java @@ -25,6 +25,12 @@ import java.util.Date; import java.util.List; import java.util.concurrent.TimeUnit; +/** + * Represents and OpenSSH certificate key as specified in + * <A HREF="https://cvsweb.openbsd.org/src/usr.bin/ssh/PROTOCOL.certkeys?annotate=HEAD">PROTOCOL.certkeys</A> + * + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ public interface OpenSshCertificate extends PublicKey, PrivateKey { int SSH_CERT_TYPE_USER = 1; int SSH_CERT_TYPE_HOST = 2; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/OpenSshCertificateImpl.java b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/OpenSshCertificateImpl.java index fb16082..94742f8 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/OpenSshCertificateImpl.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/OpenSshCertificateImpl.java @@ -26,6 +26,9 @@ import org.apache.sshd.common.util.GenericUtils; import org.apache.sshd.common.util.NumberUtils; import org.apache.sshd.common.util.buffer.ByteArrayBuffer; +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ public class OpenSshCertificateImpl implements OpenSshCertificate { private static final long serialVersionUID = -3592634724148744943L; diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/FileHostKeyCertificateProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/FileHostKeyCertificateProvider.java index c5467f5..05c3ae8 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/FileHostKeyCertificateProvider.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/FileHostKeyCertificateProvider.java @@ -30,8 +30,6 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Objects; -import java.util.stream.StreamSupport; import org.apache.sshd.common.config.keys.OpenSshCertificate; import org.apache.sshd.common.config.keys.PublicKeyEntry; @@ -62,12 +60,18 @@ public class FileHostKeyCertificateProvider extends AbstractLoggingBean implemen @Override public Iterable<OpenSshCertificate> loadCertificates(SessionContext session) throws IOException, GeneralSecurityException { + Collection<? extends Path> keyPaths = getPaths(); List<OpenSshCertificate> certificates = new ArrayList<>(); - for (Path file : getPaths()) { - List<String> lines = Files.readAllLines(file, StandardCharsets.UTF_8); + boolean debugEnabled = log.isDebugEnabled(); + for (Path file : keyPaths) { + if (debugEnabled) { + log.debug("loadCertificates({}) loading file {}", session, file); + } + + Collection<String> lines = Files.readAllLines(file, StandardCharsets.UTF_8); for (String line : lines) { line = GenericUtils.replaceWhitespaceAndTrim(line); - if (GenericUtils.isEmpty(line) || line.startsWith("#")) { + if (GenericUtils.isEmpty(line) || (line.charAt(0) == '#')) { continue; } @@ -75,27 +79,20 @@ public class FileHostKeyCertificateProvider extends AbstractLoggingBean implemen if (publicKeyEntry == null) { continue; } + PublicKey publicKey = publicKeyEntry.resolvePublicKey(session, null, null); if (publicKey == null) { continue; } + if (!(publicKey instanceof OpenSshCertificate)) { throw new InvalidKeyException("Got unexpected key type in " + file + ". Expected OpenSSHCertificate."); } + certificates.add((OpenSshCertificate) publicKey); } } return certificates; } - - @Override - public OpenSshCertificate loadCertificate(SessionContext session, String keyType) - throws IOException, GeneralSecurityException { - Iterable<OpenSshCertificate> certificates = loadCertificates(session); - return StreamSupport.stream(certificates.spliterator(), false) - .filter(pubKey -> Objects.equals(pubKey.getKeyType(), keyType)) - .findFirst() - .orElse(null); - } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/HostKeyCertificateProvider.java b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/HostKeyCertificateProvider.java index ab8eba0..0f2ee56 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/HostKeyCertificateProvider.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/HostKeyCertificateProvider.java @@ -20,13 +20,26 @@ package org.apache.sshd.common.keyprovider; import java.io.IOException; import java.security.GeneralSecurityException; +import java.util.Objects; +import java.util.stream.StreamSupport; import org.apache.sshd.common.config.keys.OpenSshCertificate; import org.apache.sshd.common.session.SessionContext; +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +@FunctionalInterface public interface HostKeyCertificateProvider { Iterable<OpenSshCertificate> loadCertificates(SessionContext session) throws IOException, GeneralSecurityException; - OpenSshCertificate loadCertificate(SessionContext session, String keyType) throws IOException, GeneralSecurityException; + default OpenSshCertificate loadCertificate(SessionContext session, String keyType) + throws IOException, GeneralSecurityException { + Iterable<OpenSshCertificate> certificates = loadCertificates(session); + return StreamSupport.stream(certificates.spliterator(), false) + .filter(pubKey -> Objects.equals(pubKey.getKeyType(), keyType)) + .findFirst() + .orElse(null); + } } diff --git a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java index 4a1bfeb..a691a8f 100644 --- a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java +++ b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java @@ -764,7 +764,7 @@ public abstract class Buffer implements Readable { * A name-list is represented as a uint32 containing its length (number of bytes * that follow) followed by a comma-separated list of zero or more names. * </CODE> - * + * * @param names The name list to put */ public void putNameList(Collection<String> names) { @@ -925,17 +925,21 @@ public abstract class Buffer implements Readable { putLong(cert.getSerial()); putInt(cert.getType()); putString(cert.getId()); + ByteArrayBuffer tmpBuffer = new ByteArrayBuffer(); tmpBuffer.putStringList(cert.getPrincipals(), false); putBytes(tmpBuffer.getCompactData()); + putLong(cert.getValidAfter()); putLong(cert.getValidBefore()); putNameList(cert.getCriticalOptions()); putNameList(cert.getExtensions()); putString(cert.getReserved()); - tmpBuffer = new ByteArrayBuffer(); + + tmpBuffer = new ByteArrayBuffer(); // TODO tmpBuffer.clear() instead of allocate new buffer tmpBuffer.putRawPublicKey(cert.getCaPubKey()); putBytes(tmpBuffer.getCompactData()); + putBytes(cert.getSignature()); } else { throw new BufferException("Unsupported raw public key algorithm: " + key.getAlgorithm()); diff --git a/sshd-core/src/test/java/org/apache/sshd/client/auth/pubkey/RSAVariantsAuthPublicKeyTest.java b/sshd-core/src/test/java/org/apache/sshd/client/auth/pubkey/RSAVariantsAuthPublicKeyTest.java new file mode 100644 index 0000000..1380e24 --- /dev/null +++ b/sshd-core/src/test/java/org/apache/sshd/client/auth/pubkey/RSAVariantsAuthPublicKeyTest.java @@ -0,0 +1,154 @@ +/* + * 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.client.auth.pubkey; + +import java.io.IOException; +import java.security.KeyPair; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +import org.apache.sshd.client.SshClient; +import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.common.BaseBuilder; +import org.apache.sshd.common.NamedFactory; +import org.apache.sshd.common.config.keys.KeyUtils; +import org.apache.sshd.common.kex.KexProposalOption; +import org.apache.sshd.common.keyprovider.KeyPairProvider; +import org.apache.sshd.common.signature.Signature; +import org.apache.sshd.common.signature.SignatureFactory; +import org.apache.sshd.server.SshServer; +import org.apache.sshd.server.auth.hostbased.RejectAllHostBasedAuthenticator; +import org.apache.sshd.server.auth.password.RejectAllPasswordAuthenticator; +import org.apache.sshd.server.keyprovider.AbstractGeneratorHostKeyProvider; +import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; +import org.apache.sshd.util.test.BaseTestSupport; +import org.apache.sshd.util.test.CoreTestSupportUtils; +import org.apache.sshd.util.test.JUnit4ClassRunnerWithParametersFactory; +import org.junit.AfterClass; +import org.junit.Assume; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.MethodSorters; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.junit.runners.Parameterized.UseParametersRunnerFactory; + +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +@RunWith(Parameterized.class) // see https://github.com/junit-team/junit/wiki/Parameterized-tests +@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class) +public class RSAVariantsAuthPublicKeyTest extends BaseTestSupport { + private static final List<NamedFactory<Signature>> RSA_FACTORIES = Collections.unmodifiableList( + BaseBuilder.DEFAULT_SIGNATURE_PREFERENCE.stream() + .filter(f -> f.getName().contains("rsa")) + .filter(f -> !f.getName().contains("cert")) + .collect(Collectors.toList())); + private static final AbstractGeneratorHostKeyProvider KEYS_PROVIDER = new SimpleGeneratorHostKeyProvider() { + { + setAlgorithm(KeyUtils.RSA_ALGORITHM); + setKeySize(2048); + } + }; + + private static SshServer sshd; + private static int port; + private static SshClient client; + + private final SignatureFactory factory; + + public RSAVariantsAuthPublicKeyTest(SignatureFactory factory) { + Assume.assumeTrue("Skip unsupported factory", factory.isSupported()); + this.factory = factory; + } + + @BeforeClass + public static void setupClientAndServer() throws Exception { + sshd = CoreTestSupportUtils.setupTestServer(RSAVariantsAuthPublicKeyTest.class); + sshd.setSignatureFactories(RSA_FACTORIES); + sshd.setKeyPairProvider(KEYS_PROVIDER); + sshd.setPasswordAuthenticator(RejectAllPasswordAuthenticator.INSTANCE); + sshd.setHostBasedAuthenticator(RejectAllHostBasedAuthenticator.INSTANCE); + sshd.setPublickeyAuthenticator((username, key, session) -> { + String keyType = KeyUtils.getKeyType(key); + outputDebugMessage("authenticate(%s) keyType=%s session=%s", username, keyType, session); + return KeyPairProvider.SSH_RSA.equals(keyType); + }); + + sshd.start(); + port = sshd.getPort(); + + client = CoreTestSupportUtils.setupTestClient(RSAVariantsAuthPublicKeyTest.class); + client.setServerKeyVerifier((session, peerAddress, key) -> { + String keyType = KeyUtils.getKeyType(key); + outputDebugMessage("verifyServerKey - keyType=%s session=%s", keyType, session); + return KeyPairProvider.SSH_RSA.equals(keyType); + }); + client.start(); + } + + @AfterClass + public static void tearDownClientAndServer() throws Exception { + if (sshd != null) { + try { + sshd.stop(true); + } finally { + sshd = null; + } + } + + if (client != null) { + try { + client.stop(); + } finally { + client = null; + } + } + } + + @Parameters(name = "{0}") + public static List<Object[]> parameters() { + return parameterize(RSA_FACTORIES); + } + + @Test + public void testRSAVariantAuth() throws IOException { + client.setSignatureFactories(Collections.singletonList(factory)); + try (ClientSession session = createClientSession(client, port)) { + List<KeyPair> keys = KEYS_PROVIDER.loadKeys(session); + KeyPair kp = keys.get(0); + assertEquals("Mismatched key type", KeyPairProvider.SSH_RSA, KeyUtils.getKeyType(kp)); + session.addPublicKeyIdentity(kp); + session.auth().verify(AUTH_TIMEOUT); + + String serverKeyType = session.getNegotiatedKexParameter(KexProposalOption.SERVERKEYS); + assertEquals("Mismatched host key used", factory.getName(), serverKeyType); + } + } + + @Override + public String toString() { + return getClass().getSimpleName() + "[" + factory + "]"; + } +} diff --git a/sshd-core/src/test/java/org/apache/sshd/util/test/BaseTestSupport.java b/sshd-core/src/test/java/org/apache/sshd/util/test/BaseTestSupport.java index 5bc19cc..068a2d8 100644 --- a/sshd-core/src/test/java/org/apache/sshd/util/test/BaseTestSupport.java +++ b/sshd-core/src/test/java/org/apache/sshd/util/test/BaseTestSupport.java @@ -122,18 +122,24 @@ public abstract class BaseTestSupport extends JUnitTestSupport { } protected ClientSession createClientSession(SshClient client, int port) throws IOException { - return client.connect(getCurrentTestName(), TEST_LOCALHOST, port) + return createClientSession(getCurrentTestName(), client, port); + } + + protected ClientSession createAuthenticatedClientSession(SshClient client, int port) throws IOException { + return createAuthenticatedClientSession(getCurrentTestName(), client, port); + } + + public static ClientSession createClientSession(String username, SshClient client, int port) throws IOException { + return client.connect(username, TEST_LOCALHOST, port) .verify(CONNECT_TIMEOUT) .getSession(); } - protected ClientSession createAuthenticatedClientSession(SshClient client, int port) throws IOException { - ClientSession session = createClientSession(client, port); + public static ClientSession createAuthenticatedClientSession(String username, SshClient client, int port) + throws IOException { + ClientSession session = createClientSession(username, client, port); try { - session.addPasswordIdentity(getCurrentTestName()); - session.auth().verify(AUTH_TIMEOUT); - - ClientSession authSession = session; + ClientSession authSession = createAuthenticatedClientSession(session, username); session = null; // avoid auto-close at finally clause return authSession; } finally { @@ -143,6 +149,12 @@ public abstract class BaseTestSupport extends JUnitTestSupport { } } + public static ClientSession createAuthenticatedClientSession(ClientSession session, String username) throws IOException { + session.addPasswordIdentity(username); + session.auth().verify(AUTH_TIMEOUT); + return session; + } + public static IoServiceFactoryFactory getIoServiceProvider() { DefaultIoServiceFactoryFactory factory = DefaultIoServiceFactoryFactory.getDefaultIoServiceFactoryFactoryInstance(); return factory.getIoServiceProvider(); diff --git a/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java b/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java index b5f6c87..317e1f5 100644 --- a/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java +++ b/sshd-scp/src/test/java/org/apache/sshd/scp/client/AbstractScpTestSupport.java @@ -143,9 +143,7 @@ public abstract class AbstractScpTestSupport extends BaseTestSupport { } protected ClientSession createClientSession() throws IOException { - return client.connect(getCurrentTestName(), TEST_LOCALHOST, port) - .verify(CONNECT_TIMEOUT) - .getSession(); + return createClientSession(client, port); } protected ClientSession createAuthenticatedClientSession() throws IOException {