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 7ebb469ea86460878abd009f357992378e03bde8
Author: Lyor Goldstein <lgoldst...@apache.org>
AuthorDate: Sun Apr 19 18:44:18 2020 +0300

    [SSHD-660] Added some code improvements and logging for certificates 
handling code
---
 CHANGES.md                                         |  4 +-
 README.md                                          |  3 ++
 docs/server-setup.md                               |  3 ++
 .../org/apache/sshd/cli/server/SshServerMain.java  |  4 +-
 .../common/config/keys/OpenSshCertificate.java     | 16 ++++++
 .../common/config/keys/OpenSshCertificateImpl.java | 19 +++++--
 .../keys/impl/OpenSSHCertificateDecoder.java       |  2 +-
 .../FileHostKeyCertificateProvider.java            | 29 ++++++-----
 .../apache/sshd/common/signature/SignatureRSA.java |  3 +-
 .../buffer/keys/OpenSSHCertPublicKeyParser.java    | 46 +++++++++--------
 .../apache/sshd/client/ClientFactoryManager.java   |  6 ++-
 .../java/org/apache/sshd/client/kex/DHGClient.java | 50 ++++++++++++-------
 .../sshd/server/ServerAuthenticationManager.java   |  8 +++
 .../apache/sshd/server/ServerFactoryManager.java   |  6 ---
 .../java/org/apache/sshd/server/SshServer.java     |  1 +
 .../sshd/server/session/AbstractServerSession.java | 33 ++++++++----
 .../common/signature/OpenSSHCertificateTest.java   | 58 ++++++++++++++--------
 .../server/ServerAuthenticationManagerTest.java    | 11 ++++
 18 files changed, 201 insertions(+), 101 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 7c0747d..7022e45 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -31,4 +31,6 @@ where the former validate the messages and deal with the idle 
timeout, and the l
 
 * [SSHD-972](https://issues.apache.org/jira/browse/SSHD-972) - Add support for 
peers using OpenSSH "security key" key types
 
-* [SSHD-977](https://issues.apache.org/jira/browse/SSHD-977) - Apply 
consistent logging policy to caught exceptions
\ No newline at end of file
+* [SSHD-977](https://issues.apache.org/jira/browse/SSHD-977) - Apply 
consistent logging policy to caught exceptions
+
+* [SSHD-660](https://issues.apache.org/jira/browse/SSHD-660) - Added support 
for server-side signed certificate keys
\ No newline at end of file
diff --git a/README.md b/README.md
index 11d6811..2a67a34 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ based applications requiring SSH support.
 * [Key Exchange (KEX) Method Updates and Recommendations for Secure 
Shell](https://tools.ietf.org/html/draft-ietf-curdle-ssh-kex-sha2-03)
 * [OpenSSH support for U2F/FIDO security 
keys](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.u2f)
     * **Note:** the server side supports these keys by default. The client 
side requires specific initialization
+* [OpenSSH public-key certificate authentication system for use by 
SSH](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.certkeys)
 * SFTP version 3-6 + extensions
     * `supported` - [DRAFT 05 - section 
4.4](http://tools.ietf.org/wg/secsh/draft-ietf-secsh-filexfer/draft-ietf-secsh-filexfer-05.tx)
     * `supported2` - [DRAFT 13 section 
5.4](https://tools.ietf.org/html/draft-ietf-secsh-filexfer-13#page-10)
@@ -60,6 +61,8 @@ based applications requiring SSH support.
 * **Compressions**: none, zlib, z...@openssh.com
 * **Signatures/Keys**: ssh-dss, ssh-rsa, rsa-sha2-256, rsa-sha2-512, nistp256, 
nistp384, nistp521
 , ed25519 (requires `eddsa` optional module), 
sk-ecdsa-sha2-nistp...@openssh.com, sk-ssh-ed25...@openssh.com
+, ssh-rsa-cert-...@openssh.com, ssh-dss-cert-...@openssh.com, 
ssh-ed25519-cert-...@openssh.com
+, ecdsa-sha2-nistp256-cert-...@openssh.com, 
ecdsa-sha2-nistp384-cert-...@openssh.com, 
ecdsa-sha2-nistp521-cert-...@openssh.com
 
 # [Release notes](./CHANGES.md)
 
diff --git a/docs/server-setup.md b/docs/server-setup.md
index 8c89f56..3d46fbe 100644
--- a/docs/server-setup.md
+++ b/docs/server-setup.md
@@ -37,6 +37,9 @@ is restarted, the same keys will be used to authenticate the 
server and avoid th
 the host keys are modified. **Note**: saving key files in PEM format requires  
that the [Bouncy Castle](https://www.bouncycastle.org/)
 supporting artifacts be available in the code's classpath.
 
+* `HostKeyCertificateProvider` - used for OpenSSH public-key certificate 
authentication system
+as defined in [this 
document](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL.certkeys)
+
 * `ShellFactory` - That's the part one usually has to write to customize the 
SSHD server. The shell factory will
 be used to create a new shell each time a user logs in and wants to run an 
interactive shell. SSHD provides a simple
 implementation that you can use if you want. This implementation will create a 
process and delegate everything to it,
diff --git 
a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerMain.java 
b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerMain.java
index c0f42ac..f79ea84 100644
--- a/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerMain.java
+++ b/sshd-cli/src/main/java/org/apache/sshd/cli/server/SshServerMain.java
@@ -181,9 +181,9 @@ public class SshServerMain extends SshServerCliSupport {
         KeyPairProvider hostKeyProvider =
             resolveServerKeys(System.err, hostKeyType, hostKeySize, keyFiles);
         sshd.setKeyPairProvider(hostKeyProvider);
-        if (certFiles != null) {
+        if (GenericUtils.isNotEmpty(certFiles)) {
             HostKeyCertificateProvider certProvider = new 
FileHostKeyCertificateProvider(
-                    
certFiles.stream().map(Paths::get).collect(Collectors.toList()));
+                
certFiles.stream().map(Paths::get).collect(Collectors.toList()));
             sshd.setHostKeyCertificateProvider(certProvider);
         }
         // Should come AFTER key pair provider setup so auto-welcome can be 
generated if needed
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 9fa7d5a..d51db78 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
@@ -21,7 +21,9 @@ package org.apache.sshd.common.config.keys;
 import java.security.PrivateKey;
 import java.security.PublicKey;
 import java.util.Collection;
+import java.util.Date;
 import java.util.List;
+import java.util.concurrent.TimeUnit;
 
 public interface OpenSshCertificate extends PublicKey, PrivateKey {
     int SSH_CERT_TYPE_USER = 1;
@@ -43,10 +45,20 @@ public interface OpenSshCertificate extends PublicKey, 
PrivateKey {
 
     Collection<String> getPrincipals();
 
+    // Seconds after epoch
     long getValidAfter();
 
+    default Date getValidAfterDate() {
+        return getValidDate(getValidAfter());
+    }
+
+    // Seconds after epoch
     long getValidBefore();
 
+    default Date getValidBeforeDate() {
+        return getValidDate(getValidBefore());
+    }
+
     List<String> getCriticalOptions();
 
     List<String> getExtensions();
@@ -60,4 +72,8 @@ public interface OpenSshCertificate extends PublicKey, 
PrivateKey {
     byte[] getSignature();
 
     String getSignatureAlg();
+
+    static Date getValidDate(long timestamp) {
+        return (timestamp == 0L) ? null : new 
Date(TimeUnit.SECONDS.toMillis(timestamp));
+    }
 }
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 3af92ea..9648d9d 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
@@ -22,6 +22,8 @@ import java.security.PublicKey;
 import java.util.Collection;
 import java.util.List;
 
+import org.apache.sshd.common.util.GenericUtils;
+import org.apache.sshd.common.util.NumberUtils;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 
 public class OpenSshCertificateImpl implements OpenSshCertificate {
@@ -50,7 +52,7 @@ public class OpenSshCertificateImpl implements 
OpenSshCertificate {
 
     @Override
     public String getRawKeyType() {
-        return keyType.split("@")[0].substring(0, keyType.indexOf("-cert"));
+        return GenericUtils.isEmpty(keyType) ? null : 
keyType.split("@")[0].substring(0, keyType.indexOf("-cert"));
     }
 
     @Override
@@ -130,7 +132,7 @@ public class OpenSshCertificateImpl implements 
OpenSshCertificate {
 
     @Override
     public String getSignatureAlg() {
-        return new ByteArrayBuffer(signature).getString();
+        return NumberUtils.isEmpty(signature) ? null : new 
ByteArrayBuffer(signature).getString();
     }
 
     @Override
@@ -145,7 +147,7 @@ public class OpenSshCertificateImpl implements 
OpenSshCertificate {
 
     @Override
     public byte[] getEncoded() {
-        return new byte[0];
+        return GenericUtils.EMPTY_BYTE_ARRAY;
     }
 
     public void setKeyType(String keyType) {
@@ -207,4 +209,15 @@ public class OpenSshCertificateImpl implements 
OpenSshCertificate {
     public void setSignature(byte[] signature) {
         this.signature = signature;
     }
+
+    @Override
+    public String toString() {
+        return getKeyType()
+            + "[id=" + getId()
+            + ", serial=" + getSerial()
+            + ", type=" + getType()
+            + ", validAfter=" + getValidAfterDate()
+            + ", validBefore=" + getValidBeforeDate()
+            + "]";
+    }
 }
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java
index 1e26aa8..26514b3 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java
@@ -97,7 +97,7 @@ public class OpenSSHCertificateDecoder extends 
AbstractPublicKeyEntryDecoder<Ope
                 return decodePublicKey(null, keyType, inStream, null);
             }
         } catch (IOException e) {
-            throw new GeneralSecurityException("Unable to clone key.", e);
+            throw new GeneralSecurityException("Unable to clone key ID=" + 
key.getId(), e);
         }
     }
 
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 0c9b8b1..885cea7 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
@@ -41,22 +41,18 @@ import org.apache.sshd.common.util.ValidateUtils;
 import org.apache.sshd.common.util.logging.AbstractLoggingBean;
 
 public class FileHostKeyCertificateProvider extends AbstractLoggingBean 
implements HostKeyCertificateProvider {
-    private Collection<? extends Path> files;
-
-    public FileHostKeyCertificateProvider() {
-        super();
-    }
+    private final Collection<? extends Path> files;
 
     public FileHostKeyCertificateProvider(Path path) {
-        this(Collections.singletonList(Objects.requireNonNull(path, "No path 
provided")));
+        this((path == null) ? Collections.emptyList() : 
Collections.singletonList(path));
     }
 
     public FileHostKeyCertificateProvider(Path... files) {
-        this(Arrays.asList(ValidateUtils.checkNotNullAndNotEmpty(files, "No 
path provided")));
+        this(GenericUtils.isEmpty(files) ? Collections.emptyList() : 
Arrays.asList(files));
     }
 
     public FileHostKeyCertificateProvider(Collection<? extends Path> files) {
-        this.files = files;
+        this.files = ValidateUtils.checkNotNullAndNotEmpty(files, "No paths 
provided");
     }
 
     public Collection<? extends Path> getPaths() {
@@ -64,14 +60,14 @@ public class FileHostKeyCertificateProvider extends 
AbstractLoggingBean implemen
     }
 
     @Override
-    public Iterable<OpenSshCertificate> loadCertificates(SessionContext 
session) throws IOException, GeneralSecurityException {
-
+    public Iterable<OpenSshCertificate> loadCertificates(SessionContext 
session)
+            throws IOException, GeneralSecurityException {
         List<OpenSshCertificate> certificates = new ArrayList<>();
-        for (Path file : files) {
+        for (Path file : getPaths()) {
             List<String> lines = Files.readAllLines(file, 
StandardCharsets.UTF_8);
             for (String line : lines) {
                 line = GenericUtils.replaceWhitespaceAndTrim(line);
-                if (line.isEmpty() || line.startsWith("#")) {
+                if (GenericUtils.isEmpty(line) || line.startsWith("#")) {
                     continue;
                 }
 
@@ -94,9 +90,12 @@ public class FileHostKeyCertificateProvider extends 
AbstractLoggingBean implemen
     }
 
     @Override
-    public OpenSshCertificate loadCertificate(SessionContext session, String 
keyType) throws IOException, GeneralSecurityException {
-        return StreamSupport.stream(loadCertificates(session).spliterator(), 
false)
+    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);
+            .findFirst()
+            .orElse(null);
     }
 }
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java 
b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java
index ff84404..ae46e88 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureRSA.java
@@ -78,7 +78,8 @@ public abstract class SignatureRSA extends AbstractSignature {
              *      corresponds to a good-faith implementation and is 
considered safe to accept.
              */
             String canonicalName = KeyUtils.getCanonicalKeyType(keyType);
-            if (!KeyPairProvider.SSH_RSA.equals(canonicalName) && 
!KeyPairProvider.SSH_RSA_CERT.equals(canonicalName)) {
+            if ((!KeyPairProvider.SSH_RSA.equals(canonicalName))
+                    && (!KeyPairProvider.SSH_RSA_CERT.equals(canonicalName))) {
                 throw new IllegalArgumentException("Mismatched key type: " + 
keyType);
             }
             data = encoding.getValue();
diff --git 
a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/OpenSSHCertPublicKeyParser.java
 
b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/OpenSSHCertPublicKeyParser.java
index 1c9823a..e34e6e6 100644
--- 
a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/OpenSSHCertPublicKeyParser.java
+++ 
b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/OpenSSHCertPublicKeyParser.java
@@ -19,49 +19,55 @@
 package org.apache.sshd.common.util.buffer.keys;
 
 import java.security.GeneralSecurityException;
+import java.security.InvalidKeyException;
 import java.security.PublicKey;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 
 import org.apache.sshd.common.SshException;
+import org.apache.sshd.common.config.keys.OpenSshCertificate;
 import org.apache.sshd.common.config.keys.OpenSshCertificateImpl;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.buffer.Buffer;
 import org.apache.sshd.common.util.buffer.ByteArrayBuffer;
 
-public class OpenSSHCertPublicKeyParser extends 
AbstractBufferPublicKeyParser<PublicKey> {
-
-    public static final OpenSSHCertPublicKeyParser INSTANCE = new 
OpenSSHCertPublicKeyParser(PublicKey.class,
-        Arrays.asList(
-            KeyPairProvider.SSH_RSA_CERT,
-            KeyPairProvider.SSH_DSS_CERT,
-            KeyPairProvider.SSH_ECDSA_SHA2_NISTP256_CERT,
-            KeyPairProvider.SSH_ECDSA_SHA2_NISTP384_CERT,
-            KeyPairProvider.SSH_ECDSA_SHA2_NISTP521_CERT,
-            KeyPairProvider.SSH_ED25519_CERT
-        ));
-
-    public OpenSSHCertPublicKeyParser(Class<PublicKey> keyClass, 
Collection<String> supported) {
-        super(keyClass, supported);
+public class OpenSSHCertPublicKeyParser extends 
AbstractBufferPublicKeyParser<OpenSshCertificate> {
+    public static final List<String> KEY_TYPES =
+        Collections.unmodifiableList(
+            Arrays.asList(
+                KeyPairProvider.SSH_RSA_CERT,
+                KeyPairProvider.SSH_DSS_CERT,
+                KeyPairProvider.SSH_ECDSA_SHA2_NISTP256_CERT,
+                KeyPairProvider.SSH_ECDSA_SHA2_NISTP384_CERT,
+                KeyPairProvider.SSH_ECDSA_SHA2_NISTP521_CERT,
+                KeyPairProvider.SSH_ED25519_CERT));
+
+    public static final OpenSSHCertPublicKeyParser INSTANCE = new 
OpenSSHCertPublicKeyParser();
+
+    public OpenSSHCertPublicKeyParser() {
+        super(OpenSshCertificate.class, KEY_TYPES);
     }
 
     @Override
-    public PublicKey getRawPublicKey(String keyType, Buffer buffer) throws 
GeneralSecurityException {
-
+    public OpenSshCertificate getRawPublicKey(String keyType, Buffer buffer) 
throws GeneralSecurityException {
         OpenSshCertificateImpl certificate = new OpenSshCertificateImpl();
         certificate.setKeyType(keyType);
 
         certificate.setNonce(buffer.getBytes());
 
         String rawKeyType = certificate.getRawKeyType();
-        certificate.setServerHostKey(DEFAULT.getRawPublicKey(rawKeyType, 
buffer));
+        PublicKey serverHostKey = DEFAULT.getRawPublicKey(rawKeyType, buffer);
+        certificate.setServerHostKey(serverHostKey);
 
         certificate.setSerial(buffer.getLong());
         certificate.setType(buffer.getInt());
 
         certificate.setId(buffer.getString());
 
-        certificate.setPrincipals(new 
ByteArrayBuffer(buffer.getBytes()).getStringList(false));
+        Collection<String> principals = new 
ByteArrayBuffer(buffer.getBytes()).getStringList(false);
+        certificate.setPrincipals(principals);
         certificate.setValidAfter(buffer.getLong());
         certificate.setValidBefore(buffer.getLong());
 
@@ -73,14 +79,14 @@ public class OpenSSHCertPublicKeyParser extends 
AbstractBufferPublicKeyParser<Pu
         try {
             certificate.setCaPubKey(buffer.getPublicKey());
         } catch (SshException ex) {
-            throw new GeneralSecurityException("Could not parse public CA key 
with ID: " + certificate.getId(), ex);
+            throw new InvalidKeyException("Could not parse public CA key with 
ID: " + certificate.getId(), ex);
         }
 
         certificate.setMessage(buffer.getBytesConsumed());
         certificate.setSignature(buffer.getBytes());
 
         if (buffer.rpos() != buffer.wpos()) {
-            throw new GeneralSecurityException("KeyExchange signature 
verification failed, got more data than expected: "
+            throw new InvalidKeyException("KeyExchange signature verification 
failed, got more data than expected: "
                 + buffer.rpos() + ", actual: " + buffer.wpos() + ". ID of the 
ca certificate: " + certificate.getId());
         }
 
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java 
b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
index 017cf7a..4eb1a62 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientFactoryManager.java
@@ -115,11 +115,15 @@ public interface ClientFactoryManager
 
     /**
      * Defines if we should abort in case we encounter an invalid (e.g. 
expired) openssh certificate.
-     * The default is to ignore the certificate and proceed with the plain 
host key.
      */
     String ABORT_ON_INVALID_CERTIFICATE = "abort-on-invalid-certificate";
 
     /**
+     * The default is to ignore the certificate and proceed with the plain 
host key.
+     */
+    boolean DEFAULT_ABORT_ON_INVALID_CERTIFICATE = false;
+
+    /**
      * @return The {@link HostConfigEntryResolver} to use in order to resolve 
the
      * effective session parameters - never {@code null}
      */
diff --git a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java 
b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
index 4f1a4f0..c27ff1e 100644
--- a/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
+++ b/sshd-core/src/main/java/org/apache/sshd/client/kex/DHGClient.java
@@ -23,6 +23,7 @@ import java.net.SocketAddress;
 import java.security.PublicKey;
 import java.util.Collection;
 import java.util.Objects;
+import java.util.concurrent.TimeUnit;
 
 import org.apache.sshd.client.ClientFactoryManager;
 import org.apache.sshd.common.NamedFactory;
@@ -142,7 +143,9 @@ public class DHGClient extends AbstractDHClientKeyExchange {
             try {
                 verifyCertificate(session, openSshKey);
             } catch (SshException e) {
-                if 
(session.getBooleanProperty(ClientFactoryManager.ABORT_ON_INVALID_CERTIFICATE, 
false)) {
+                if (session.getBooleanProperty(
+                        ClientFactoryManager.ABORT_ON_INVALID_CERTIFICATE,
+                        
ClientFactoryManager.DEFAULT_ABORT_ON_INVALID_CERTIFICATE)) {
                     throw e;
                 } else {
                     // ignore certificate
@@ -186,41 +189,48 @@ public class DHGClient extends 
AbstractDHClientKeyExchange {
     protected void verifyCertificate(Session session, OpenSshCertificate 
openSshKey) throws Exception {
         PublicKey signatureKey = openSshKey.getCaPubKey();
         String keyAlg = KeyUtils.getKeyType(signatureKey);
+        String keyId = openSshKey.getId();
 
         if (KeyPairProvider.SSH_RSA_CERT.equals(openSshKey.getKeyType())) {
             // allow sha2 signatures for legacy reasons
             String variant = openSshKey.getSignatureAlg();
-            if (!GenericUtils.isEmpty(variant) && 
KeyPairProvider.SSH_RSA.equals(KeyUtils.getCanonicalKeyType(variant))) {
-                log.debug("Allowing to use variant {} instead of {}", variant, 
keyAlg);
+            if ((!GenericUtils.isEmpty(variant))
+                    && 
KeyPairProvider.SSH_RSA.equals(KeyUtils.getCanonicalKeyType(variant))) {
+                if (log.isDebugEnabled()) {
+                    log.debug("verifyCertificate({})[id={}] Allowing to use 
variant {} instead of {}",
+                        session, keyId, variant, keyAlg);
+                }
                 keyAlg = variant;
             } else {
                 throw new 
SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
-                    "Found invalid signature alg " + variant);
+                    "Found invalid signature alg " + variant + " for key ID=" 
+ keyId);
             }
         }
 
         Signature verif = ValidateUtils.checkNotNull(
-                NamedFactory.create(session.getSignatureFactories(), keyAlg),
-                "No verifier located for algorithm=%s", keyAlg);
+            NamedFactory.create(session.getSignatureFactories(), keyAlg),
+            "No KeyExchange CA verifier located for algorithm=%s of key 
ID=%s", keyAlg, keyId);
         verif.initVerifier(session, signatureKey);
         verif.update(session, openSshKey.getMessage());
+
         if (!verif.verify(session, openSshKey.getSignature())) {
             throw new 
SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
-                    "KeyExchange CA signature verification failed for key 
type=" + keyAlg);
+                "KeyExchange CA signature verification failed for key type=" + 
keyAlg + " of key ID=" + keyId);
         }
 
         if (openSshKey.getType() != OpenSshCertificate.SSH_CERT_TYPE_HOST) {
             throw new 
SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
-                    "KeyExchange signature verification failed, not a host key 
(2): "
-                            + openSshKey.getType());
+                "KeyExchange signature verification failed, not a host key (2) 
"
+                + openSshKey.getType() + " for key ID=" + keyId);
         }
 
-        long now = System.currentTimeMillis() / 1000;
+        long now = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis());
         // valid after <= current time < valid before
-        if (!(openSshKey.getValidAfter() <= now && now < 
openSshKey.getValidBefore())) {
+        if (!((openSshKey.getValidAfter() <= now) && (now < 
openSshKey.getValidBefore()))) {
             throw new 
SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
-                    "KeyExchange signature verification failed, CA expired: "
-                            + openSshKey.getValidAfter() + "-" + 
openSshKey.getValidBefore());
+                "KeyExchange signature verification failed, CA expired "
+                + openSshKey.getValidAfterDate() + " - " + 
openSshKey.getValidBeforeDate()
+                + " for key ID=" + keyId);
         }
 
         /*
@@ -231,24 +241,26 @@ public class DHGClient extends 
AbstractDHClientKeyExchange {
         if (connectSocketAddress instanceof SshdSocketAddress) {
             connectSocketAddress = ((SshdSocketAddress) 
connectSocketAddress).toInetSocketAddress();
         }
+
         if (connectSocketAddress instanceof InetSocketAddress) {
             String hostName = ((InetSocketAddress) 
connectSocketAddress).getHostString();
             Collection<String> principals = openSshKey.getPrincipals();
-            if (GenericUtils.isEmpty(principals) || 
!principals.contains(hostName)) {
+            if (GenericUtils.isEmpty(principals) || 
(!principals.contains(hostName))) {
                 throw new 
SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
-                        "KeyExchange signature verification failed, invalid 
principal: "
-                                + principals);
+                    "KeyExchange signature verification failed, invalid 
principal "
+                        + hostName + " for key ID=" + keyId
+                        + " - allowed=" + principals);
             }
         } else {
             throw new 
SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
-                    "KeyExchange signature verification failed, could not 
determine connect host.");
+                "KeyExchange signature verification failed, could not 
determine connect host for key ID=" + keyId);
         }
 
         if (!GenericUtils.isEmpty(openSshKey.getCriticalOptions())) {
             // no critical option defined for host keys yet
             throw new 
SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED,
-                    "KeyExchange signature verification failed, unrecognized 
critical option: "
-                            + openSshKey.getCriticalOptions());
+                "KeyExchange signature verification failed, unrecognized 
critical options "
+                    + openSshKey.getCriticalOptions() + " for key ID=" + 
keyId);
         }
     }
 }
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/ServerAuthenticationManager.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/ServerAuthenticationManager.java
index 72877e6..84103fa 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/ServerAuthenticationManager.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/ServerAuthenticationManager.java
@@ -26,6 +26,7 @@ import java.util.List;
 
 import org.apache.sshd.common.NamedFactory;
 import org.apache.sshd.common.auth.UserAuthFactoriesManager;
+import org.apache.sshd.common.keyprovider.HostKeyCertificateProvider;
 import org.apache.sshd.common.keyprovider.KeyPairProviderHolder;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.common.util.ValidateUtils;
@@ -228,6 +229,13 @@ public interface ServerAuthenticationManager
     void setHostBasedAuthenticator(HostBasedAuthenticator 
hostBasedAuthenticator);
 
     /**
+     * @return a {@link HostKeyCertificateProvider} if available, null as 
default
+     */
+    HostKeyCertificateProvider getHostKeyCertificateProvider();
+
+    void setHostKeyCertificateProvider(HostKeyCertificateProvider provider);
+
+    /**
      * If user authentication factories already set, then simply returns them. 
Otherwise,
      * builds the factories list from the individual authenticators available 
for
      * the manager - password public key, keyboard-interactive, GSS, etc...
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java 
b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
index 83f7770..10e23b0 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java
@@ -22,7 +22,6 @@ import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.sshd.common.FactoryManager;
-import org.apache.sshd.common.keyprovider.HostKeyCertificateProvider;
 import org.apache.sshd.server.command.CommandFactory;
 import org.apache.sshd.server.session.ServerProxyAcceptorHolder;
 import org.apache.sshd.server.shell.ShellFactory;
@@ -111,9 +110,4 @@ public interface ServerFactoryManager
      * or {@code null}/empty if subsystems are not supported on this server
      */
     List<SubsystemFactory> getSubsystemFactories();
-
-    /**
-     * @return a {@link HostKeyCertificateProvider} if available, null as 
default
-     */
-    HostKeyCertificateProvider getHostKeyCertificateProvider();
 }
diff --git a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java 
b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
index be5d946..38c3f72 100644
--- a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
+++ b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java
@@ -267,6 +267,7 @@ public class SshServer extends AbstractFactoryManager 
implements ServerFactoryMa
         return hostKeyCertificateProvider;
     }
 
+    @Override
     public void setHostKeyCertificateProvider(HostKeyCertificateProvider 
hostKeyCertificateProvider) {
         this.hostKeyCertificateProvider = hostKeyCertificateProvider;
     }
diff --git 
a/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
 
b/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
index 5f4ddcf..f8c4721 100644
--- 
a/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
+++ 
b/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java
@@ -28,7 +28,6 @@ import java.util.Collection;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import java.util.Set;
 import java.util.stream.Collectors;
 
 import org.apache.sshd.common.FactoryManager;
@@ -194,11 +193,14 @@ public abstract class AbstractServerSession extends 
AbstractSession implements S
             (parent == null) ? null : ((ServerAuthenticationManager) 
parent).getKeyPairProvider());
     }
 
+    @Override
     public HostKeyCertificateProvider getHostKeyCertificateProvider() {
         ServerFactoryManager manager = getFactoryManager();
-        return resolveEffectiveProvider(HostKeyCertificateProvider.class, 
hostKeyCertificateProvider, manager.getHostKeyCertificateProvider());
+        return resolveEffectiveProvider(HostKeyCertificateProvider.class,
+            hostKeyCertificateProvider, 
manager.getHostKeyCertificateProvider());
     }
 
+    @Override
     public void setHostKeyCertificateProvider(HostKeyCertificateProvider 
hostKeyCertificateProvider) {
         this.hostKeyCertificateProvider = hostKeyCertificateProvider;
     }
@@ -381,21 +383,23 @@ public abstract class AbstractServerSession extends 
AbstractSession implements S
 
         KeyPairProvider kpp = getKeyPairProvider();
         boolean debugEnabled = log.isDebugEnabled();
-        Set<String> provided = null;
+        Collection<String> provided = null;
         try {
             if (kpp != null) {
                 provided = 
GenericUtils.stream(kpp.getKeyTypes(this)).collect(Collectors.toSet());
 
                 HostKeyCertificateProvider hostKeyCertificateProvider = 
getHostKeyCertificateProvider();
                 if (hostKeyCertificateProvider != null) {
-                    Iterable<OpenSshCertificate> certificates = 
hostKeyCertificateProvider.loadCertificates(this);
+                    Iterable<OpenSshCertificate> certificates =
+                        hostKeyCertificateProvider.loadCertificates(this);
                     for (OpenSshCertificate certificate : certificates) {
                         // Add the certificate alg only if the corresponding 
keyPair type is available
-                        if (provided.contains(certificate.getRawKeyType())) {
+                        String rawKeyType = certificate.getRawKeyType();
+                        if (provided.contains(rawKeyType)) {
                             provided.add(certificate.getKeyType());
                         } else {
-                            log.info("No private key for provided certificate 
available. Missing private key type: {}",
-                                certificate.getRawKeyType());
+                            log.info("resolveAvailableSignaturesProposal({}) 
No private key of type={} available in provided certificate",
+                                this, rawKeyType);
                         }
                     }
                 }
@@ -530,14 +534,23 @@ public abstract class AbstractServerSession extends 
AbstractSession implements S
         try {
             HostKeyCertificateProvider hostKeyCertificateProvider = 
getHostKeyCertificateProvider();
             if (hostKeyCertificateProvider != null) {
-                OpenSshCertificate publicKey = 
hostKeyCertificateProvider.loadCertificate(this, keyType);
+                OpenSshCertificate publicKey =
+                    hostKeyCertificateProvider.loadCertificate(this, keyType);
                 if (publicKey != null) {
-                    KeyPair keyPair = provider.loadKey(this, 
publicKey.getRawKeyType());
+                    String rawKeyType = publicKey.getRawKeyType();
+
+                    if (log.isDebugEnabled()) {
+                        log.debug("getHostKey({}) using certified key {}/{} 
with ID={}",
+                            this, keyType, rawKeyType, publicKey.getId());
+                    }
+
+                    KeyPair keyPair = provider.loadKey(this, rawKeyType);
+                    ValidateUtils.checkNotNull(keyPair, "No certified private 
key of type=%s available", rawKeyType);
                     return new KeyPair(publicKey, keyPair.getPrivate());
                 }
             }
-            return provider.loadKey(this, keyType);
 
+            return provider.loadKey(this, keyType);
         } catch (IOException | GeneralSecurityException | Error e) {
             log.warn("getHostKey({}) failed ({}) to load key of type={}[{}]: 
{}",
                  this, e.getClass().getSimpleName(), proposedKey, keyType, 
e.getMessage());
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/common/signature/OpenSSHCertificateTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/common/signature/OpenSSHCertificateTest.java
index 50bd39d..7259632 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/common/signature/OpenSSHCertificateTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/common/signature/OpenSSHCertificateTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.sshd.common.signature;
 
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -38,29 +39,33 @@ 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.Assert;
+import org.junit.Before;
 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;
 
 @FixMethodOrder(MethodSorters.NAME_ASCENDING)
 @RunWith(Parameterized.class)   // see 
https://github.com/junit-team/junit/wiki/Parameterized-tests
-@Parameterized.UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
+@UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class)
 public class OpenSSHCertificateTest extends BaseTestSupport {
     private static SshServer sshd;
     private static SshClient client;
     private static int port;
+    private static List<NamedFactory<Signature>> defaultSignatureFactories;
 
     private final FileHostKeyCertificateProvider certificateProvider;
     private final FileKeyPairProvider keyPairProvider;
     private final List<NamedFactory<Signature>> signatureFactory;
 
     public OpenSSHCertificateTest(String keyPath, String certPath, 
List<NamedFactory<Signature>> signatureFactory) {
-        this.keyPairProvider = new 
FileKeyPairProvider(getTestResourcesFolder().resolve(keyPath));
-        this.certificateProvider = new 
FileHostKeyCertificateProvider(getTestResourcesFolder().resolve(certPath));
+        Path testResourcesFolder = getTestResourcesFolder();
+        this.keyPairProvider = new 
FileKeyPairProvider(testResourcesFolder.resolve(keyPath));
+        this.certificateProvider = new 
FileHostKeyCertificateProvider(testResourcesFolder.resolve(certPath));
         this.signatureFactory = signatureFactory;
     }
 
@@ -72,6 +77,7 @@ public class OpenSSHCertificateTest extends BaseTestSupport {
 
         client = 
CoreTestSupportUtils.setupTestClient(OpenSSHCertificateTest.class);
         client.start();
+        defaultSignatureFactories = client.getSignatureFactories();
     }
 
     @AfterClass
@@ -93,7 +99,7 @@ public class OpenSSHCertificateTest extends BaseTestSupport {
         }
     }
 
-    @Parameterized.Parameters(name = "type={2}")
+    @Parameters(name = "type={2}")
     public static List<Object[]> parameters() {
         List<Object[]> list = new ArrayList<>();
 
@@ -113,49 +119,57 @@ public class OpenSSHCertificateTest extends 
BaseTestSupport {
         return Collections.unmodifiableList(list);
     }
 
-    @Test
-    public void testOpenSshCertificates() throws Exception {
+    @Before
+    public void setUp() {
         sshd.setKeyPairProvider(keyPairProvider);
         sshd.setHostKeyCertificateProvider(certificateProvider);
+
+        PropertyResolverUtils.updateProperty(client,
+            ClientFactoryManager.ABORT_ON_INVALID_CERTIFICATE,
+            ClientFactoryManager.DEFAULT_ABORT_ON_INVALID_CERTIFICATE);
+
         if (signatureFactory != null) {
             client.setSignatureFactories(signatureFactory);
+        } else {
+            client.setSignatureFactories(defaultSignatureFactories);
         }
+    }
 
+    @Test
+    public void testOpenSshCertificates() throws Exception {
         // default client
         try (ClientSession s = client.connect(getCurrentTestName(), 
TEST_LOCALHOST, port)
-            .verify(CONNECT_TIMEOUT).getSession()) {
+                .verify(CONNECT_TIMEOUT)
+                .getSession()) {
             s.addPasswordIdentity(getCurrentTestName());
             s.auth().verify(AUTH_TIMEOUT);
         }
     }
 
-    @Test
-    public void testPrincipal() throws Exception {
-        sshd.setKeyPairProvider(keyPairProvider);
-        sshd.setHostKeyCertificateProvider(certificateProvider);
-        if (signatureFactory != null) {
-            client.setSignatureFactories(signatureFactory);
-        }
-
-        // invalid principal, but continue
+    @Test   // invalid principal, but continue
+    public void testContinueOnInvalidPrincipal() throws Exception {
         PropertyResolverUtils.updateProperty(client, 
ClientFactoryManager.ABORT_ON_INVALID_CERTIFICATE, false);
         try (ClientSession s = client.connect(getCurrentTestName(), 
"localhost", port)
-            .verify(CONNECT_TIMEOUT).getSession()) {
+                .verify(CONNECT_TIMEOUT)
+                .getSession()) {
             s.addPasswordIdentity(getCurrentTestName());
             s.auth().verify(AUTH_TIMEOUT);
         }
+    }
 
-        // invalid principal, abort
+    @Test // invalid principal, abort
+    public void testAbortOnInvalidPrincipal() throws Exception {
         PropertyResolverUtils.updateProperty(client, 
ClientFactoryManager.ABORT_ON_INVALID_CERTIFICATE, true);
         try (ClientSession s = client.connect(getCurrentTestName(), 
"localhost", port)
-            .verify(CONNECT_TIMEOUT).getSession()) {
+                .verify(CONNECT_TIMEOUT)
+                .getSession()) {
             s.addPasswordIdentity(getCurrentTestName());
             s.auth().verify(AUTH_TIMEOUT);
 
             // in case client does not support cert, no exception should be 
thrown
-            
Assert.assertFalse(client.getSignatureFactories().contains(BuiltinSignatures.rsa_cert));
+            
assertFalse(client.getSignatureFactories().contains(BuiltinSignatures.rsa_cert));
         } catch (SshException e) {
-            
Assert.assertEquals(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED, 
e.getDisconnectCode());
+            assertEquals(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED, 
e.getDisconnectCode());
         }
     }
 }
diff --git 
a/sshd-core/src/test/java/org/apache/sshd/server/ServerAuthenticationManagerTest.java
 
b/sshd-core/src/test/java/org/apache/sshd/server/ServerAuthenticationManagerTest.java
index 40eebac..9215b20 100644
--- 
a/sshd-core/src/test/java/org/apache/sshd/server/ServerAuthenticationManagerTest.java
+++ 
b/sshd-core/src/test/java/org/apache/sshd/server/ServerAuthenticationManagerTest.java
@@ -23,6 +23,7 @@ import java.util.List;
 import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.sshd.common.NamedResource;
+import org.apache.sshd.common.keyprovider.HostKeyCertificateProvider;
 import org.apache.sshd.common.keyprovider.KeyPairProvider;
 import org.apache.sshd.common.util.GenericUtils;
 import org.apache.sshd.server.auth.BuiltinUserAuthFactories;
@@ -123,6 +124,16 @@ public class ServerAuthenticationManagerTest extends 
BaseTestSupport {
             public void setKeyPairProvider(KeyPairProvider keyPairProvider) {
                 throw new UnsupportedOperationException("setKeyPairProvider(" 
+ keyPairProvider + ")");
             }
+
+            @Override
+            public HostKeyCertificateProvider getHostKeyCertificateProvider() {
+                return null;
+            }
+
+            @Override
+            public void 
setHostKeyCertificateProvider(HostKeyCertificateProvider provider) {
+                throw new 
UnsupportedOperationException("setHostKeyCertificateProvider(" + provider + 
")");
+            }
         };
         assertEquals("Mismatched initial factories list", "", 
manager.getUserAuthFactoriesNameList());
 

Reply via email to