I think your forgot to commit the changes to a few files: KeyUtils, KeyPairProvider
Le dim. 19 avr. 2020 à 18:00, <lgoldst...@apache.org> a écrit : > 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 47f779f06cb345c7cb706cb81f1214c37dab1fda > Author: FliegenKLATSCH <ch...@koras.de> > AuthorDate: Wed Apr 8 21:18:40 2020 +0200 > > [SSHD-660] Add support for serer side openssh host certkeys > --- > .../org/apache/sshd/cli/server/SshServerMain.java | 15 ++ > .../apache/sshd/common/config/keys/KeyUtils.java | 8 + > .../common/config/keys/OpenSshCertificate.java | 63 +++++++ > .../common/config/keys/OpenSshCertificateImpl.java | 210 > +++++++++++++++++++++ > .../keys/impl/OpenSSHCertificateDecoder.java | 118 ++++++++++++ > .../FileHostKeyCertificateProvider.java | 102 ++++++++++ > .../keyprovider/HostKeyCertificateProvider.java | 32 ++++ > .../sshd/common/keyprovider/KeyPairProvider.java | 14 +- > .../sshd/common/signature/BuiltinSignatures.java | 88 +++++++++ > .../sshd/common/signature/SignatureFactory.java | 2 +- > .../apache/sshd/common/signature/SignatureRSA.java | 4 +- > .../org/apache/sshd/common/util/buffer/Buffer.java | 34 +++- > .../sshd/common/util/buffer/ByteArrayBuffer.java | 7 + > .../util/buffer/keys/BufferPublicKeyParser.java | 1 + > .../buffer/keys/OpenSSHCertPublicKeyParser.java | 89 +++++++++ > .../security/eddsa/EdDSASecurityProviderUtils.java | 2 - > .../util/security/eddsa/EDDSAProviderTest.java | 4 +- > .../java/org/apache/sshd/client/ClientBuilder.java | 6 + > .../apache/sshd/client/ClientFactoryManager.java | 6 + > .../java/org/apache/sshd/client/kex/DHGClient.java | 105 ++++++++++- > .../sshd/client/session/AbstractClientSession.java | 27 ++- > .../java/org/apache/sshd/server/ServerBuilder.java | 8 + > .../apache/sshd/server/ServerFactoryManager.java | 6 + > .../java/org/apache/sshd/server/SshServer.java | 11 ++ > .../sshd/server/config/keys/ServerIdentity.java | 23 ++- > .../java/org/apache/sshd/server/kex/DHGServer.java | 1 + > .../sshd/server/session/AbstractServerSession.java | 43 ++++- > .../common/signature/OpenSSHCertificateTest.java | 161 ++++++++++++++++ > .../org/apache/sshd/common/signature/example-ca | 49 +++++ > .../apache/sshd/common/signature/example-ca.pub | 1 + > .../apache/sshd/common/signature/ssh_host_rsa_key | 38 ++++ > .../common/signature/ssh_host_rsa_key-cert.pub | 1 + > .../sshd/common/signature/ssh_host_rsa_key.pub | 1 + > .../signature/ssh_host_rsa_key_sha1-cert.pub | 1 + > 34 files changed, 1259 insertions(+), 22 deletions(-) > > 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 321ae1e..c0f42ac 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 > @@ -19,6 +19,7 @@ > > package org.apache.sshd.cli.server; > > +import java.nio.file.Paths; > import java.util.Collection; > import java.util.LinkedList; > import java.util.List; > @@ -26,12 +27,15 @@ import java.util.Map; > import java.util.Objects; > import java.util.TreeMap; > import java.util.logging.Level; > +import java.util.stream.Collectors; > > import org.apache.sshd.common.NamedResource; > import org.apache.sshd.common.PropertyResolver; > import org.apache.sshd.common.PropertyResolverUtils; > import org.apache.sshd.common.config.ConfigFileReaderSupport; > import org.apache.sshd.common.config.SshConfigFileReader; > +import org.apache.sshd.common.keyprovider.FileHostKeyCertificateProvider; > +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.SshServer; > @@ -62,6 +66,7 @@ public class SshServerMain extends SshServerCliSupport { > String hostKeyType = > AbstractGeneratorHostKeyProvider.DEFAULT_ALGORITHM; > int hostKeySize = 0; > Collection<String> keyFiles = null; > + Collection<String> certFiles = null; > Map<String, Object> options = new > TreeMap<>(String.CASE_INSENSITIVE_ORDER); > > int numArgs = GenericUtils.length(args); > @@ -140,6 +145,11 @@ public class SshServerMain extends > SshServerCliSupport { > keyFiles = new LinkedList<>(); > } > keyFiles.add(optValue); > + } else if > (ServerIdentity.HOST_CERT_CONFIG_PROP.equals(optName)) { > + if (certFiles == null) { > + certFiles = new LinkedList<>(); > + } > + certFiles.add(optValue); > } else if > (ConfigFileReaderSupport.PORT_CONFIG_PROP.equals(optName)) { > port = Integer.parseInt(optValue); > } else { > @@ -171,6 +181,11 @@ public class SshServerMain extends > SshServerCliSupport { > KeyPairProvider hostKeyProvider = > resolveServerKeys(System.err, hostKeyType, hostKeySize, > keyFiles); > sshd.setKeyPairProvider(hostKeyProvider); > + if (certFiles != null) { > + HostKeyCertificateProvider certProvider = new > FileHostKeyCertificateProvider( > + > 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 > setupServerBanner(sshd, resolver); > sshd.setPort(port); > diff --git > a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java > b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java > index 37f4051..fd89f9e 100644 > --- > a/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java > +++ > b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/KeyUtils.java > @@ -68,6 +68,7 @@ import org.apache.sshd.common.Factory; > import org.apache.sshd.common.cipher.ECCurves; > import org.apache.sshd.common.config.keys.impl.DSSPublicKeyEntryDecoder; > import org.apache.sshd.common.config.keys.impl.ECDSAPublicKeyEntryDecoder; > +import org.apache.sshd.common.config.keys.impl.OpenSSHCertificateDecoder; > import org.apache.sshd.common.config.keys.impl.RSAPublicKeyDecoder; > import > org.apache.sshd.common.config.keys.impl.SkECDSAPublicKeyEntryDecoder; > import > org.apache.sshd.common.config.keys.impl.SkED25519PublicKeyEntryDecoder; > @@ -140,6 +141,8 @@ public final class KeyUtils { > /** @see <A HREF="">https://tools.ietf.org/html/rfc8332#section-3</A> > */ > public static final String RSA_SHA256_KEY_TYPE_ALIAS = "rsa-sha2-256"; > public static final String RSA_SHA512_KEY_TYPE_ALIAS = "rsa-sha2-512"; > + public static final String RSA_SHA256_CERT_TYPE_ALIAS = " > rsa-sha2-256-cert-...@openssh.com"; > + public static final String RSA_SHA512_CERT_TYPE_ALIAS = " > rsa-sha2-512-cert-...@openssh.com"; > > private static final AtomicReference<DigestFactory> > DEFAULT_DIGEST_HOLDER = new AtomicReference<>(); > > @@ -153,9 +156,12 @@ public final class KeyUtils { > NavigableMapBuilder.<String, > String>builder(String.CASE_INSENSITIVE_ORDER) > .put(RSA_SHA256_KEY_TYPE_ALIAS, KeyPairProvider.SSH_RSA) > .put(RSA_SHA512_KEY_TYPE_ALIAS, KeyPairProvider.SSH_RSA) > + .put(RSA_SHA256_CERT_TYPE_ALIAS, KeyPairProvider.SSH_RSA_CERT) > + .put(RSA_SHA512_CERT_TYPE_ALIAS, KeyPairProvider.SSH_RSA_CERT) > .build(); > > static { > + registerPublicKeyEntryDecoder(OpenSSHCertificateDecoder.INSTANCE); > registerPublicKeyEntryDecoder(RSAPublicKeyDecoder.INSTANCE); > registerPublicKeyEntryDecoder(DSSPublicKeyEntryDecoder.INSTANCE); > > @@ -800,6 +806,8 @@ public final class KeyUtils { > } > } else if > (SecurityUtils.EDDSA.equalsIgnoreCase(key.getAlgorithm())) { > return KeyPairProvider.SSH_ED25519; > + } else if (key instanceof OpenSshCertificate) { > + return ((OpenSshCertificate) key).getKeyType(); > } > > return null; > 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 > new file mode 100644 > index 0000000..9fa7d5a > --- /dev/null > +++ > b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/OpenSshCertificate.java > @@ -0,0 +1,63 @@ > +/* > + * 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; > +import java.util.List; > + > +public interface OpenSshCertificate extends PublicKey, PrivateKey { > + int SSH_CERT_TYPE_USER = 1; > + int SSH_CERT_TYPE_HOST = 2; > + > + String getRawKeyType(); > + > + byte[] getNonce(); > + > + String getKeyType(); > + > + PublicKey getServerHostKey(); > + > + long getSerial(); > + > + int getType(); > + > + String getId(); > + > + Collection<String> getPrincipals(); > + > + long getValidAfter(); > + > + long getValidBefore(); > + > + List<String> getCriticalOptions(); > + > + List<String> getExtensions(); > + > + String getReserved(); > + > + PublicKey getCaPubKey(); > + > + byte[] getMessage(); > + > + byte[] getSignature(); > + > + String getSignatureAlg(); > +} > 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 > new file mode 100644 > index 0000000..3af92ea > --- /dev/null > +++ > b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/OpenSshCertificateImpl.java > @@ -0,0 +1,210 @@ > +/* > + * 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.PublicKey; > +import java.util.Collection; > +import java.util.List; > + > +import org.apache.sshd.common.util.buffer.ByteArrayBuffer; > + > +public class OpenSshCertificateImpl implements OpenSshCertificate { > + > + private static final long serialVersionUID = -3592634724148744943L; > + > + private String keyType; > + private byte[] nonce; > + private PublicKey serverHostKey; > + private long serial; > + private int type; > + private String id; > + private Collection<String> principals; > + private long validAfter; > + private long validBefore; > + private List<String> criticalOptions; > + private List<String> extensions; > + private String reserved; > + private PublicKey caPubKey; > + private byte[] message; > + private byte[] signature; > + > + public OpenSshCertificateImpl() { > + super(); > + } > + > + @Override > + public String getRawKeyType() { > + return keyType.split("@")[0].substring(0, > keyType.indexOf("-cert")); > + } > + > + @Override > + public byte[] getNonce() { > + return nonce; > + } > + > + @Override > + public String getKeyType() { > + return keyType; > + } > + > + @Override > + public PublicKey getServerHostKey() { > + return serverHostKey; > + } > + > + @Override > + public long getSerial() { > + return serial; > + } > + > + @Override > + public int getType() { > + return type; > + } > + > + @Override > + public String getId() { > + return id; > + } > + > + @Override > + public Collection<String> getPrincipals() { > + return principals; > + } > + > + @Override > + public long getValidAfter() { > + return validAfter; > + } > + > + @Override > + public long getValidBefore() { > + return validBefore; > + } > + > + @Override > + public List<String> getCriticalOptions() { > + return criticalOptions; > + } > + > + @Override > + public List<String> getExtensions() { > + return extensions; > + } > + > + @Override > + public String getReserved() { > + return reserved; > + } > + > + @Override > + public PublicKey getCaPubKey() { > + return caPubKey; > + } > + > + @Override > + public byte[] getMessage() { > + return message; > + } > + > + @Override > + public byte[] getSignature() { > + return signature; > + } > + > + @Override > + public String getSignatureAlg() { > + return new ByteArrayBuffer(signature).getString(); > + } > + > + @Override > + public String getAlgorithm() { > + return null; > + } > + > + @Override > + public String getFormat() { > + return null; > + } > + > + @Override > + public byte[] getEncoded() { > + return new byte[0]; > + } > + > + public void setKeyType(String keyType) { > + this.keyType = keyType; > + } > + > + public void setNonce(byte[] nonce) { > + this.nonce = nonce; > + } > + > + public void setServerHostKey(PublicKey serverHostKey) { > + this.serverHostKey = serverHostKey; > + } > + > + public void setSerial(long serial) { > + this.serial = serial; > + } > + > + public void setType(int type) { > + this.type = type; > + } > + > + public void setId(String id) { > + this.id = id; > + } > + > + public void setPrincipals(Collection<String> principals) { > + this.principals = principals; > + } > + > + public void setValidAfter(long validAfter) { > + this.validAfter = validAfter; > + } > + > + public void setValidBefore(long validBefore) { > + this.validBefore = validBefore; > + } > + > + public void setCriticalOptions(List<String> criticalOptions) { > + this.criticalOptions = criticalOptions; > + } > + > + public void setExtensions(List<String> extensions) { > + this.extensions = extensions; > + } > + > + public void setReserved(String reserved) { > + this.reserved = reserved; > + } > + > + public void setCaPubKey(PublicKey caPubKey) { > + this.caPubKey = caPubKey; > + } > + > + public void setMessage(byte[] message) { > + this.message = message; > + } > + > + public void setSignature(byte[] signature) { > + this.signature = signature; > + } > +} > 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 > new file mode 100644 > index 0000000..1e26aa8 > --- /dev/null > +++ > b/sshd-common/src/main/java/org/apache/sshd/common/config/keys/impl/OpenSSHCertificateDecoder.java > @@ -0,0 +1,118 @@ > +/* > + * 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.io.ByteArrayInputStream; > +import java.io.ByteArrayOutputStream; > +import java.io.IOException; > +import java.io.InputStream; > +import java.io.OutputStream; > +import java.security.GeneralSecurityException; > +import java.security.KeyFactory; > +import java.security.KeyPairGenerator; > +import java.util.Arrays; > +import java.util.Collections; > +import java.util.Map; > +import java.util.Objects; > + > +import org.apache.sshd.common.config.keys.KeyUtils; > +import org.apache.sshd.common.config.keys.OpenSshCertificate; > +import org.apache.sshd.common.keyprovider.KeyPairProvider; > +import org.apache.sshd.common.session.SessionContext; > +import org.apache.sshd.common.util.buffer.ByteArrayBuffer; > +import org.apache.sshd.common.util.buffer.keys.OpenSSHCertPublicKeyParser; > +import org.apache.sshd.common.util.io.IoUtils; > + > +/** > + * @author <a href="mailto:dev@mina.apache.org">Apache MINA SSHD > Project</a> > + */ > +public class OpenSSHCertificateDecoder extends > AbstractPublicKeyEntryDecoder<OpenSshCertificate, OpenSshCertificate> { > + public static final OpenSSHCertificateDecoder INSTANCE = new > OpenSSHCertificateDecoder(); > + > + public OpenSSHCertificateDecoder() { > + super(OpenSshCertificate.class, OpenSshCertificate.class, > + Collections.unmodifiableList(Arrays.asList( > + KeyUtils.RSA_SHA256_CERT_TYPE_ALIAS, > + KeyUtils.RSA_SHA512_CERT_TYPE_ALIAS, > + KeyPairProvider.SSH_RSA_CERT, > + KeyPairProvider.SSH_DSS_CERT, > + KeyPairProvider.SSH_ED25519_CERT, > + KeyPairProvider.SSH_ECDSA_SHA2_NISTP256_CERT, > + KeyPairProvider.SSH_ECDSA_SHA2_NISTP384_CERT, > + KeyPairProvider.SSH_ECDSA_SHA2_NISTP521_CERT > + ))); > + } > + > + @Override > + public OpenSshCertificate decodePublicKey( > + SessionContext session, String keyType, InputStream keyData, > Map<String, String> headers) > + throws IOException, GeneralSecurityException { > + > + byte[] bytes = IoUtils.toByteArray(keyData); > + > + ByteArrayBuffer buffer = new ByteArrayBuffer(bytes); > + > + OpenSshCertificate cert = (OpenSshCertificate) > OpenSSHCertPublicKeyParser.INSTANCE.getRawPublicKey(keyType, buffer); > + > + if (cert.getType() != OpenSshCertificate.SSH_CERT_TYPE_HOST) { > + throw new GeneralSecurityException("The provided certificate > is not a Host certificate."); > + } > + > + return cert; > + } > + > + @Override > + public String encodePublicKey(OutputStream s, OpenSshCertificate key) > throws IOException { > + Objects.requireNonNull(key, "No public key provided"); > + > + ByteArrayBuffer buffer = new ByteArrayBuffer(); > + buffer.putRawPublicKeyBytes(key); > + s.write(buffer.getCompactData()); > + > + return key.getKeyType(); > + } > + > + @Override > + public OpenSshCertificate clonePublicKey(OpenSshCertificate key) > throws GeneralSecurityException { > + try (ByteArrayOutputStream outStream = new > ByteArrayOutputStream()) { > + String keyType = encodePublicKey(outStream, key); > + try (InputStream inStream = new > ByteArrayInputStream(outStream.toByteArray())) { > + return decodePublicKey(null, keyType, inStream, null); > + } > + } catch (IOException e) { > + throw new GeneralSecurityException("Unable to clone key.", e); > + } > + } > + > + @Override > + public OpenSshCertificate clonePrivateKey(OpenSshCertificate key) > throws GeneralSecurityException { > + return clonePublicKey(key); > + } > + > + @Override > + public KeyPairGenerator getKeyPairGenerator() throws > GeneralSecurityException { > + return null; > + } > + > + @Override > + public KeyFactory getKeyFactoryInstance() throws > GeneralSecurityException { > + return null; > + } > +} > 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 > new file mode 100644 > index 0000000..0c9b8b1 > --- /dev/null > +++ > b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/FileHostKeyCertificateProvider.java > @@ -0,0 +1,102 @@ > +/* > + * 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.keyprovider; > + > +import java.io.IOException; > +import java.nio.charset.StandardCharsets; > +import java.nio.file.Files; > +import java.nio.file.Path; > +import java.security.GeneralSecurityException; > +import java.security.InvalidKeyException; > +import java.security.PublicKey; > +import java.util.ArrayList; > +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; > +import org.apache.sshd.common.session.SessionContext; > +import org.apache.sshd.common.util.GenericUtils; > +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(); > + } > + > + public FileHostKeyCertificateProvider(Path path) { > + this(Collections.singletonList(Objects.requireNonNull(path, "No > path provided"))); > + } > + > + public FileHostKeyCertificateProvider(Path... files) { > + this(Arrays.asList(ValidateUtils.checkNotNullAndNotEmpty(files, > "No path provided"))); > + } > + > + public FileHostKeyCertificateProvider(Collection<? extends Path> > files) { > + this.files = files; > + } > + > + public Collection<? extends Path> getPaths() { > + return files; > + } > + > + @Override > + public Iterable<OpenSshCertificate> loadCertificates(SessionContext > session) throws IOException, GeneralSecurityException { > + > + List<OpenSshCertificate> certificates = new ArrayList<>(); > + for (Path file : files) { > + List<String> lines = Files.readAllLines(file, > StandardCharsets.UTF_8); > + for (String line : lines) { > + line = GenericUtils.replaceWhitespaceAndTrim(line); > + if (line.isEmpty() || line.startsWith("#")) { > + continue; > + } > + > + PublicKeyEntry publicKeyEntry = > PublicKeyEntry.parsePublicKeyEntry(line); > + 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 { > + return > StreamSupport.stream(loadCertificates(session).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 > new file mode 100644 > index 0000000..ab8eba0 > --- /dev/null > +++ > b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/HostKeyCertificateProvider.java > @@ -0,0 +1,32 @@ > +/* > + * 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.keyprovider; > + > +import java.io.IOException; > +import java.security.GeneralSecurityException; > + > +import org.apache.sshd.common.config.keys.OpenSshCertificate; > +import org.apache.sshd.common.session.SessionContext; > + > +public interface HostKeyCertificateProvider { > + > + Iterable<OpenSshCertificate> loadCertificates(SessionContext session) > throws IOException, GeneralSecurityException; > + > + OpenSshCertificate loadCertificate(SessionContext session, String > keyType) throws IOException, GeneralSecurityException; > +} > diff --git > a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java > b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java > index 237b61f..2bc6114 100644 > --- > a/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java > +++ > b/sshd-common/src/main/java/org/apache/sshd/common/keyprovider/KeyPairProvider.java > @@ -73,6 +73,16 @@ public interface KeyPairProvider extends > KeyIdentityProvider { > String ECDSA_SHA2_NISTP521 = ECCurves.nistp521.getKeyType(); > > /** > + * SSH identifier for openssh cert keys > + */ > + String SSH_RSA_CERT = "ssh-rsa-cert-...@openssh.com"; > + String SSH_DSS_CERT = "ssh-dss-cert-...@openssh.com"; > + String SSH_ED25519_CERT = "ssh-ed25519-cert-...@openssh.com"; > + String SSH_ECDSA_SHA2_NISTP256_CERT = " > ecdsa-sha2-nistp256-cert-...@openssh.com"; > + String SSH_ECDSA_SHA2_NISTP384_CERT = " > ecdsa-sha2-nistp384-cert-...@openssh.com"; > + String SSH_ECDSA_SHA2_NISTP521_CERT = " > ecdsa-sha2-nistp521-cert-...@openssh.com"; > + > + /** > * A {@link KeyPairProvider} that has no keys > */ > KeyPairProvider EMPTY_KEYPAIR_PROVIDER = > @@ -84,7 +94,7 @@ public interface KeyPairProvider extends > KeyIdentityProvider { > > @Override > public Iterable<String> getKeyTypes(SessionContext session) { > - return Collections.emptyList(); > + return Collections.emptySet(); > } > > @Override > @@ -122,7 +132,7 @@ public interface KeyPairProvider extends > KeyIdentityProvider { > /** > * @param session The {@link SessionContext} for invoking this load > command - may > * be {@code null} if not invoked within a session context (e.g., > offline tool). > - * @return The available {@link Iterable} key types in preferred > order - never {@code null} > + * @return The available {@link Iterable} key types - never {@code > null} > * @throws IOException If failed to read/parse the keys data > * @throws GeneralSecurityException If failed to generate the keys > */ > diff --git > a/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java > b/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java > index bedf5de..695e26a 100644 > --- > a/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java > +++ > b/sshd-common/src/main/java/org/apache/sshd/common/signature/BuiltinSignatures.java > @@ -57,18 +57,36 @@ public enum BuiltinSignatures implements > SignatureFactory { > return new SignatureDSA(); > } > }, > + dsa_cert(KeyPairProvider.SSH_DSS_CERT) { > + @Override > + public Signature create() { > + return new SignatureDSA(); > + } > + }, > rsa(KeyPairProvider.SSH_RSA) { > @Override > public Signature create() { > return new SignatureRSASHA1(); > } > }, > + rsa_cert(KeyPairProvider.SSH_RSA_CERT) { > + @Override > + public Signature create() { > + return new SignatureRSASHA1(); > + } > + }, > rsaSHA256(KeyUtils.RSA_SHA256_KEY_TYPE_ALIAS) { > @Override > public Signature create() { > return new SignatureRSASHA256(); > } > }, > + rsaSHA256_cert(KeyUtils.RSA_SHA256_CERT_TYPE_ALIAS) { > + @Override > + public Signature create() { > + return new SignatureRSASHA256(); > + } > + }, > rsaSHA512(KeyUtils.RSA_SHA512_KEY_TYPE_ALIAS) { > private final AtomicReference<Boolean> supportHolder = new > AtomicReference<>(); > > @@ -95,6 +113,32 @@ public enum BuiltinSignatures implements > SignatureFactory { > return supported; > } > }, > + rsaSHA512_cert(KeyUtils.RSA_SHA512_CERT_TYPE_ALIAS) { > + private final AtomicReference<Boolean> supportHolder = new > AtomicReference<>(); > + > + @Override > + public Signature create() { > + return new SignatureRSASHA512(); > + } > + > + @Override > + public boolean isSupported() { > + Boolean supported = supportHolder.get(); > + if (supported == null) { > + try { > + java.security.Signature sig = > + > SecurityUtils.getSignature(SignatureRSASHA512.ALGORITHM); > + supported = sig != null; > + } catch (Exception e) { > + supported = Boolean.FALSE; > + } > + > + supportHolder.set(supported); > + } > + > + return supported; > + } > + }, > nistp256(KeyPairProvider.ECDSA_SHA2_NISTP256) { > @Override > public Signature create() { > @@ -106,6 +150,17 @@ public enum BuiltinSignatures implements > SignatureFactory { > return SecurityUtils.isECCSupported(); > } > }, > + nistp256_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP256_CERT) { > + @Override > + public Signature create() { > + return new SignatureECDSA.SignatureECDSA256(); > + } > + > + @Override > + public boolean isSupported() { > + return SecurityUtils.isECCSupported(); > + } > + }, > nistp384(KeyPairProvider.ECDSA_SHA2_NISTP384) { > @Override > public Signature create() { > @@ -117,6 +172,17 @@ public enum BuiltinSignatures implements > SignatureFactory { > return SecurityUtils.isECCSupported(); > } > }, > + nistp384_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP384_CERT) { > + @Override > + public Signature create() { > + return new SignatureECDSA.SignatureECDSA384(); > + } > + > + @Override > + public boolean isSupported() { > + return SecurityUtils.isECCSupported(); > + } > + }, > nistp521(KeyPairProvider.ECDSA_SHA2_NISTP521) { > @Override > public Signature create() { > @@ -128,6 +194,17 @@ public enum BuiltinSignatures implements > SignatureFactory { > return SecurityUtils.isECCSupported(); > } > }, > + nistp521_cert(KeyPairProvider.SSH_ECDSA_SHA2_NISTP521_CERT) { > + @Override > + public Signature create() { > + return new SignatureECDSA.SignatureECDSA521(); > + } > + > + @Override > + public boolean isSupported() { > + return SecurityUtils.isECCSupported(); > + } > + }, > sk_ecdsa_sha2_nistp256(SkECDSAPublicKeyEntryDecoder.KEY_TYPE) { > @Override > public Signature create() { > @@ -150,6 +227,17 @@ public enum BuiltinSignatures implements > SignatureFactory { > return SecurityUtils.isEDDSACurveSupported(); > } > }, > + ed25519_cert(KeyPairProvider.SSH_ED25519_CERT) { > + @Override > + public Signature create() { > + return SecurityUtils.getEDDSASigner(); > + } > + > + @Override > + public boolean isSupported() { > + return SecurityUtils.isEDDSACurveSupported(); > + } > + }, > sk_ssh_ed25519(SkED25519PublicKeyEntryDecoder.KEY_TYPE) { > @Override > public Signature create() { > diff --git > a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java > b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java > index 25296d0..363859c 100644 > --- > a/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java > +++ > b/sshd-common/src/main/java/org/apache/sshd/common/signature/SignatureFactory.java > @@ -90,7 +90,6 @@ public interface SignatureFactory extends > BuiltinFactory<Signature> { > return Collections.emptyList(); > } > > - // We want to preserve the original available order as it > indicates the preference > Set<String> providedKeys = new HashSet<>(); > for (String providedType : provided) { > Collection<String> equivTypes = > @@ -102,6 +101,7 @@ public interface SignatureFactory extends > BuiltinFactory<Signature> { > return Collections.emptyList(); > } > > + // We want to preserve the original available order as it > indicates the preference > List<String> supported = new ArrayList<>(available); > for (int index = 0; index < supported.size(); index++) { > String kt = supported.get(index); > 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 b0fb2fe..ff84404 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,9 @@ public abstract class SignatureRSA extends > AbstractSignature { > * corresponds to a good-faith implementation and is > considered safe to accept. > */ > String canonicalName = KeyUtils.getCanonicalKeyType(keyType); > - > ValidateUtils.checkTrue(KeyPairProvider.SSH_RSA.equals(canonicalName), > "Mismatched key type: %s", keyType); > + 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/Buffer.java > b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/Buffer.java > index 2482fb0..db65ac9 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 > @@ -60,6 +60,7 @@ import org.apache.sshd.common.SshConstants; > import org.apache.sshd.common.SshException; > import org.apache.sshd.common.cipher.ECCurves; > import org.apache.sshd.common.config.keys.KeyUtils; > +import org.apache.sshd.common.config.keys.OpenSshCertificate; > import org.apache.sshd.common.keyprovider.KeyPairProvider; > import org.apache.sshd.common.util.GenericUtils; > import org.apache.sshd.common.util.NumberUtils; > @@ -114,6 +115,11 @@ public abstract class Buffer implements Readable { > public abstract byte[] array(); > > /** > + * @return The bytes consumed so far > + */ > + public abstract byte[] getBytesConsumed(); > + > + /** > * @param pos A position in the <U>raw</U> underlying data bytes > * @return The byte at the specified position without changing the > * current {@link #rpos() read position}. <B>Note:</B> no validation > @@ -893,18 +899,21 @@ public abstract class Buffer implements Readable { > } > > public void putRawPublicKey(PublicKey key) { > + putString(KeyUtils.getKeyType(key)); > + putRawPublicKeyBytes(key); > + } > + > + public void putRawPublicKeyBytes(PublicKey key) { > Objects.requireNonNull(key, "No key"); > if (key instanceof RSAPublicKey) { > RSAPublicKey rsaPub = (RSAPublicKey) key; > > - putString(KeyPairProvider.SSH_RSA); > putMPInt(rsaPub.getPublicExponent()); > putMPInt(rsaPub.getModulus()); > } else if (key instanceof DSAPublicKey) { > DSAPublicKey dsaPub = (DSAPublicKey) key; > DSAParams dsaParams = dsaPub.getParams(); > > - putString(KeyPairProvider.SSH_DSS); > putMPInt(dsaParams.getP()); > putMPInt(dsaParams.getQ()); > putMPInt(dsaParams.getG()); > @@ -918,11 +927,30 @@ public abstract class Buffer implements Readable { > } > > byte[] ecPoint = ECCurves.encodeECPoint(ecKey.getW(), > ecParams); > - putString(curve.getKeyType()); > putString(curve.getName()); > putBytes(ecPoint); > } else if (SecurityUtils.EDDSA.equals(key.getAlgorithm())) { > SecurityUtils.putRawEDDSAPublicKey(this, key); > + } else if (key instanceof OpenSshCertificate) { > + OpenSshCertificate cert = (OpenSshCertificate) key; > + > + putBytes(cert.getNonce()); > + putRawPublicKeyBytes(cert.getServerHostKey()); > + 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.putRawPublicKey(cert.getCaPubKey()); > + putBytes(tmpBuffer.getCompactData()); > + putBytes(cert.getSignature()); > } else { > throw new BufferException("Unsupported raw public key > algorithm: " + key.getAlgorithm()); > } > diff --git > a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java > b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java > index 65fcb0b..2a98434 100644 > --- > a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java > +++ > b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/ByteArrayBuffer.java > @@ -158,6 +158,13 @@ public class ByteArrayBuffer extends Buffer { > } > > @Override > + public byte[] getBytesConsumed() { > + byte[] consumed = new byte[rpos]; > + System.arraycopy(data, 0, consumed, 0, rpos); > + return consumed; > + } > + > + @Override > public byte rawByte(int pos) { > return data[pos]; > } > diff --git > a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/BufferPublicKeyParser.java > b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/BufferPublicKeyParser.java > index 6b9bae2..2b788e5 100644 > --- > a/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/BufferPublicKeyParser.java > +++ > b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/BufferPublicKeyParser.java > @@ -60,6 +60,7 @@ public interface BufferPublicKeyParser<PUB extends > PublicKey> { > ECBufferPublicKeyParser.INSTANCE, > SkECBufferPublicKeyParser.INSTANCE, > ED25519BufferPublicKeyParser.INSTANCE, > + OpenSSHCertPublicKeyParser.INSTANCE, > SkED25519BufferPublicKeyParser.INSTANCE)); > > /** > 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 > new file mode 100644 > index 0000000..1c9823a > --- /dev/null > +++ > b/sshd-common/src/main/java/org/apache/sshd/common/util/buffer/keys/OpenSSHCertPublicKeyParser.java > @@ -0,0 +1,89 @@ > +/* > + * 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.util.buffer.keys; > + > +import java.security.GeneralSecurityException; > +import java.security.PublicKey; > +import java.util.Arrays; > +import java.util.Collection; > + > +import org.apache.sshd.common.SshException; > +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); > + } > + > + @Override > + public PublicKey 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)); > + > + certificate.setSerial(buffer.getLong()); > + certificate.setType(buffer.getInt()); > + > + certificate.setId(buffer.getString()); > + > + certificate.setPrincipals(new > ByteArrayBuffer(buffer.getBytes()).getStringList(false)); > + certificate.setValidAfter(buffer.getLong()); > + certificate.setValidBefore(buffer.getLong()); > + > + certificate.setCriticalOptions(buffer.getNameList()); > + certificate.setExtensions(buffer.getNameList()); > + > + certificate.setReserved(buffer.getString()); > + > + try { > + certificate.setCaPubKey(buffer.getPublicKey()); > + } catch (SshException ex) { > + throw new GeneralSecurityException("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: " > + + buffer.rpos() + ", actual: " + buffer.wpos() + ". ID of > the ca certificate: " + certificate.getId()); > + } > + > + return certificate; > + } > +} > diff --git > a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java > b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java > index 242f550..e002d4b 100644 > --- > a/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java > +++ > b/sshd-common/src/main/java/org/apache/sshd/common/util/security/eddsa/EdDSASecurityProviderUtils.java > @@ -30,7 +30,6 @@ import java.util.Objects; > > import org.apache.sshd.common.config.keys.PrivateKeyEntryDecoder; > import org.apache.sshd.common.config.keys.PublicKeyEntryDecoder; > -import org.apache.sshd.common.keyprovider.KeyPairProvider; > import org.apache.sshd.common.util.ValidateUtils; > import org.apache.sshd.common.util.buffer.Buffer; > import org.apache.sshd.common.util.security.SecurityUtils; > @@ -187,7 +186,6 @@ public final class EdDSASecurityProviderUtils { > EdDSAPublicKey edKey = ValidateUtils.checkInstanceOf(key, > EdDSAPublicKey.class, "Not an EDDSA public key: %s", key); > byte[] seed = Ed25519PublicKeyDecoder.getSeedValue(edKey); > ValidateUtils.checkNotNull(seed, "No seed extracted from key: > %s", edKey.getA()); > - buffer.putString(KeyPairProvider.SSH_ED25519); > buffer.putBytes(seed); > return buffer; > } > diff --git > a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EDDSAProviderTest.java > b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EDDSAProviderTest.java > index 3ad00be..51ffd0b 100644 > --- > a/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EDDSAProviderTest.java > +++ > b/sshd-common/src/test/java/org/apache/sshd/common/util/security/eddsa/EDDSAProviderTest.java > @@ -30,7 +30,6 @@ import java.security.Signature; > import org.apache.sshd.common.config.keys.AuthorizedKeyEntry; > import org.apache.sshd.common.config.keys.KeyUtils; > import org.apache.sshd.common.keyprovider.KeyPairProvider; > -import org.apache.sshd.common.util.buffer.Buffer; > import org.apache.sshd.common.util.buffer.ByteArrayBuffer; > import org.apache.sshd.common.util.security.SecurityUtils; > import org.apache.sshd.util.test.JUnitTestSupport; > @@ -118,7 +117,8 @@ public class EDDSAProviderTest extends > JUnitTestSupport { > assertNotNull("No public key generated", pubKey); > assertEquals("Mismatched public key algorithm", > SecurityUtils.EDDSA, pubKey.getAlgorithm()); > > - Buffer buf = SecurityUtils.putRawEDDSAPublicKey(new > ByteArrayBuffer(), pubKey); > + ByteArrayBuffer buf = new ByteArrayBuffer(); > + buf.putRawPublicKey(pubKey); > PublicKey actual = buf.getRawPublicKey(); > assertEquals("Mismatched key algorithm", pubKey.getAlgorithm(), > actual.getAlgorithm()); > assertEquals("Mismatched recovered key", pubKey, actual); > diff --git > a/sshd-core/src/main/java/org/apache/sshd/client/ClientBuilder.java > b/sshd-core/src/main/java/org/apache/sshd/client/ClientBuilder.java > index 46d7ba9..36d1e4c 100644 > --- a/sshd-core/src/main/java/org/apache/sshd/client/ClientBuilder.java > +++ b/sshd-core/src/main/java/org/apache/sshd/client/ClientBuilder.java > @@ -76,6 +76,12 @@ public class ClientBuilder extends > BaseBuilder<SshClient, ClientBuilder> { > */ > Collections.unmodifiableList( > Arrays.asList( > + BuiltinSignatures.nistp256_cert, > + BuiltinSignatures.nistp384_cert, > + BuiltinSignatures.nistp521_cert, > + BuiltinSignatures.ed25519_cert, > + BuiltinSignatures.rsa_cert, > + BuiltinSignatures.dsa_cert, > BuiltinSignatures.nistp256, > BuiltinSignatures.nistp384, > BuiltinSignatures.nistp521, > 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 5c8694b..017cf7a 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 > @@ -114,6 +114,12 @@ public interface ClientFactoryManager > boolean DEFAULT_IGNORE_INVALID_IDENTITIES = true; > > /** > + * 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"; > + > + /** > * @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 e2694ee..4f1a4f0 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 > @@ -18,22 +18,31 @@ > */ > package org.apache.sshd.client.kex; > > +import java.net.InetSocketAddress; > +import java.net.SocketAddress; > +import java.security.PublicKey; > +import java.util.Collection; > import java.util.Objects; > > +import org.apache.sshd.client.ClientFactoryManager; > import org.apache.sshd.common.NamedFactory; > import org.apache.sshd.common.SshConstants; > import org.apache.sshd.common.SshException; > import org.apache.sshd.common.config.keys.KeyUtils; > +import org.apache.sshd.common.config.keys.OpenSshCertificate; > import org.apache.sshd.common.kex.AbstractDH; > import org.apache.sshd.common.kex.DHFactory; > +import org.apache.sshd.common.kex.KexProposalOption; > import org.apache.sshd.common.kex.KeyExchange; > import org.apache.sshd.common.kex.KeyExchangeFactory; > +import org.apache.sshd.common.keyprovider.KeyPairProvider; > import org.apache.sshd.common.session.Session; > import org.apache.sshd.common.signature.Signature; > import org.apache.sshd.common.util.GenericUtils; > import org.apache.sshd.common.util.ValidateUtils; > import org.apache.sshd.common.util.buffer.Buffer; > import org.apache.sshd.common.util.buffer.ByteArrayBuffer; > +import org.apache.sshd.common.util.net.SshdSocketAddress; > > /** > * Base class for DHG key exchange algorithms. > @@ -124,10 +133,29 @@ public class DHGClient extends > AbstractDHClientKeyExchange { > > buffer = new ByteArrayBuffer(k_s); > serverKey = buffer.getRawPublicKey(); > - String keyAlg = KeyUtils.getKeyType(serverKey); > + PublicKey serverPublicHostKey = serverKey; > + > + if (serverKey instanceof OpenSshCertificate) { > + OpenSshCertificate openSshKey = (OpenSshCertificate) > serverKey; > + serverPublicHostKey = openSshKey.getServerHostKey(); > + > + try { > + verifyCertificate(session, openSshKey); > + } catch (SshException e) { > + if > (session.getBooleanProperty(ClientFactoryManager.ABORT_ON_INVALID_CERTIFICATE, > false)) { > + throw e; > + } else { > + // ignore certificate > + serverKey = openSshKey.getServerHostKey(); > + log.info("Ignoring invalid certificate {}", > openSshKey.getId(), e); > + } > + } > + } > + > + String keyAlg = > session.getNegotiatedKexParameter(KexProposalOption.SERVERKEYS); > if (GenericUtils.isEmpty(keyAlg)) { > - throw new SshException("Unsupported server key type: " + > serverKey.getAlgorithm() > - + "[" + serverKey.getFormat() + "]"); > + throw new SshException("Unsupported server key type: " + > serverPublicHostKey.getAlgorithm() > + + "[" + serverPublicHostKey.getFormat() + "]"); > } > > buffer = new ByteArrayBuffer(); > @@ -145,7 +173,7 @@ public class DHGClient extends > AbstractDHClientKeyExchange { > Signature verif = ValidateUtils.checkNotNull( > NamedFactory.create(session.getSignatureFactories(), keyAlg), > "No verifier located for algorithm=%s", keyAlg); > - verif.initVerifier(session, serverKey); > + verif.initVerifier(session, serverPublicHostKey); > verif.update(session, h); > if (!verif.verify(session, sig)) { > throw new > SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED, > @@ -154,4 +182,73 @@ public class DHGClient extends > AbstractDHClientKeyExchange { > > return true; > } > + > + protected void verifyCertificate(Session session, OpenSshCertificate > openSshKey) throws Exception { > + PublicKey signatureKey = openSshKey.getCaPubKey(); > + String keyAlg = KeyUtils.getKeyType(signatureKey); > + > + 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); > + keyAlg = variant; > + } else { > + throw new > SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED, > + "Found invalid signature alg " + variant); > + } > + } > + > + Signature verif = ValidateUtils.checkNotNull( > + NamedFactory.create(session.getSignatureFactories(), > keyAlg), > + "No verifier located for algorithm=%s", keyAlg); > + 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); > + } > + > + 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()); > + } > + > + long now = System.currentTimeMillis() / 1000; > + // valid after <= current time < valid before > + 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()); > + } > + > + /* > + * We compare only the connect address against the principals and > do not do any reverse DNS lookups. > + * If one wants to connect with the IP it has to be included in > the principals list of the certificate. > + */ > + SocketAddress connectSocketAddress = > getClientSession().getConnectAddress(); > + 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)) { > + throw new > SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED, > + "KeyExchange signature verification failed, > invalid principal: " > + + principals); > + } > + } else { > + throw new > SshException(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED, > + "KeyExchange signature verification failed, could not > determine connect host."); > + } > + > + 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()); > + } > + } > } > diff --git > a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java > b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java > index 558266c..b36dfb6 100644 > --- > a/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java > +++ > b/sshd-core/src/main/java/org/apache/sshd/client/session/AbstractClientSession.java > @@ -51,6 +51,7 @@ import > org.apache.sshd.common.channel.PtyChannelConfigurationHolder; > import org.apache.sshd.common.cipher.BuiltinCiphers; > import org.apache.sshd.common.cipher.CipherNone; > import org.apache.sshd.common.config.keys.KeyUtils; > +import org.apache.sshd.common.config.keys.OpenSshCertificate; > import org.apache.sshd.common.forward.ForwardingFilter; > import org.apache.sshd.common.future.DefaultKeyExchangeFuture; > import org.apache.sshd.common.future.KeyExchangeFuture; > @@ -546,10 +547,28 @@ public abstract class AbstractClientSession extends > AbstractSession implements C > IoSession networkSession = getIoSession(); > SocketAddress remoteAddress = networkSession.getRemoteAddress(); > PublicKey serverKey = kex.getServerKey(); > - boolean verified = serverKeyVerifier.verifyServerKey(this, > remoteAddress, serverKey); > - if (log.isDebugEnabled()) { > - log.debug("checkKeys({}) key={}-{}, verified={}", > - this, KeyUtils.getKeyType(serverKey), > KeyUtils.getFingerPrint(serverKey), verified); > + > + boolean verified = false; > + if (serverKey instanceof OpenSshCertificate) { > + // check if we trust the CA > + verified = serverKeyVerifier.verifyServerKey(this, > remoteAddress, ((OpenSshCertificate) serverKey).getCaPubKey()); > + if (log.isDebugEnabled()) { > + log.debug("checkCA({}) key={}-{}, verified={}", > + this, KeyUtils.getKeyType(serverKey), > KeyUtils.getFingerPrint(serverKey), verified); > + } > + > + if (!verified) { > + // fallback to actual public host key > + serverKey = ((OpenSshCertificate) > serverKey).getServerHostKey(); > + } > + } > + > + if (!verified) { > + verified = serverKeyVerifier.verifyServerKey(this, > remoteAddress, serverKey); > + if (log.isDebugEnabled()) { > + log.debug("checkKeys({}) key={}-{}, verified={}", > + this, KeyUtils.getKeyType(serverKey), > KeyUtils.getFingerPrint(serverKey), verified); > + } > } > > if (!verified) { > diff --git > a/sshd-core/src/main/java/org/apache/sshd/server/ServerBuilder.java > b/sshd-core/src/main/java/org/apache/sshd/server/ServerBuilder.java > index 0f34f80..cbcebc3 100644 > --- a/sshd-core/src/main/java/org/apache/sshd/server/ServerBuilder.java > +++ b/sshd-core/src/main/java/org/apache/sshd/server/ServerBuilder.java > @@ -98,6 +98,14 @@ public class ServerBuilder extends > BaseBuilder<SshServer, ServerBuilder> { > public static final List<BuiltinSignatures> > DEFAULT_SIGNATURE_PREFERENCE = > Collections.unmodifiableList( > Arrays.asList( > + BuiltinSignatures.nistp256_cert, > + BuiltinSignatures.nistp384_cert, > + BuiltinSignatures.nistp521_cert, > + BuiltinSignatures.ed25519_cert, > + BuiltinSignatures.rsaSHA512_cert, > + BuiltinSignatures.rsaSHA256_cert, > + BuiltinSignatures.rsa_cert, > + BuiltinSignatures.dsa_cert, > BuiltinSignatures.nistp256, > BuiltinSignatures.nistp384, > BuiltinSignatures.nistp521, > 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 10e23b0..83f7770 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,6 +22,7 @@ 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; > @@ -110,4 +111,9 @@ 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 fcc676f..be5d946 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 > @@ -39,6 +39,7 @@ import > org.apache.sshd.common.helpers.AbstractFactoryManager; > import org.apache.sshd.common.io.IoAcceptor; > import org.apache.sshd.common.io.IoServiceFactory; > import org.apache.sshd.common.io.IoSession; > +import org.apache.sshd.common.keyprovider.HostKeyCertificateProvider; > import org.apache.sshd.common.keyprovider.KeyPairProvider; > import org.apache.sshd.common.session.helpers.AbstractSession; > import org.apache.sshd.common.util.GenericUtils; > @@ -105,6 +106,7 @@ public class SshServer extends AbstractFactoryManager > implements ServerFactoryMa > private List<SubsystemFactory> subsystemFactories; > private List<UserAuthFactory> userAuthFactories; > private KeyPairProvider keyPairProvider; > + private HostKeyCertificateProvider hostKeyCertificateProvider; > private PasswordAuthenticator passwordAuthenticator; > private PublickeyAuthenticator publickeyAuthenticator; > private KeyboardInteractiveAuthenticator interactiveAuthenticator; > @@ -261,6 +263,15 @@ public class SshServer extends AbstractFactoryManager > implements ServerFactoryMa > } > > @Override > + public HostKeyCertificateProvider getHostKeyCertificateProvider() { > + return hostKeyCertificateProvider; > + } > + > + public void setHostKeyCertificateProvider(HostKeyCertificateProvider > hostKeyCertificateProvider) { > + this.hostKeyCertificateProvider = hostKeyCertificateProvider; > + } > + > + @Override > protected void checkConfig() { > super.checkConfig(); > > diff --git > a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java > b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java > index 7cc5010..5e3ffc5 100644 > --- > a/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java > +++ > b/sshd-core/src/main/java/org/apache/sshd/server/config/keys/ServerIdentity.java > @@ -55,6 +55,7 @@ public final class ServerIdentity { > * The server's keys configuration multi-value > */ > public static final String HOST_KEY_CONFIG_PROP = "HostKey"; > + public static final String HOST_CERT_CONFIG_PROP = "HostCertificate"; > > public static final Function<String, String> ID_GENERATOR = > ServerIdentity::getIdentityFileName; > @@ -137,11 +138,31 @@ public final class ServerIdentity { > * @see > org.apache.sshd.common.config.ConfigFileReaderSupport#readConfigFile(Path, > java.nio.file.OpenOption...) > */ > public static Map<String, Path> findIdentities(Properties props, > LinkOption... options) throws IOException { > + return getLocations(HOST_KEY_CONFIG_PROP, props, options); > + } > + > + /** > + * @param props The {@link Properties} holding the server's > configuration - ignored > + * if {@code null}/empty > + * @param options The {@link LinkOption}s to use when checking files > existence > + * @return A {@link Map} of the found certificates where key=the > identity type > + * (case <U>insensitive</U>) and value=the {@link Path} of the file > holding > + * the specific type key > + * @throws IOException If failed to access the file system > + * @see #getIdentityType(String) > + * @see #HOST_CERT_CONFIG_PROP > + * @see > org.apache.sshd.common.config.ConfigFileReaderSupport#readConfigFile(Path, > java.nio.file.OpenOption...) > + */ > + public static Map<String, Path> findCertificates(Properties props, > LinkOption... options) throws IOException { > + return getLocations(HOST_CERT_CONFIG_PROP, props, options); > + } > + > + private static Map<String, Path> getLocations(String configPropKey, > Properties props, LinkOption... options) throws IOException { > if (GenericUtils.isEmpty(props)) { > return Collections.emptyMap(); > } > > - String keyList = props.getProperty(HOST_KEY_CONFIG_PROP); > + String keyList = props.getProperty(configPropKey); > String[] paths = GenericUtils.split(keyList, ','); > if (GenericUtils.isEmpty(paths)) { > return Collections.emptyMap(); > diff --git > a/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGServer.java > b/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGServer.java > index c8ee45e..7e96045 100644 > --- a/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGServer.java > +++ b/sshd-core/src/main/java/org/apache/sshd/server/kex/DHGServer.java > @@ -104,6 +104,7 @@ public class DHGServer extends > AbstractDHServerKeyExchange { > > KeyPair kp = Objects.requireNonNull(session.getHostKey(), "No > server key pair available"); > String algo = > session.getNegotiatedKexParameter(KexProposalOption.SERVERKEYS); > + > Signature sig = ValidateUtils.checkNotNull( > NamedFactory.create(session.getSignatureFactories(), algo), > "Unknown negotiated server keys: %s", algo); > 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 7232935..5f4ddcf 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,6 +28,8 @@ 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; > import org.apache.sshd.common.NamedResource; > @@ -37,6 +39,7 @@ import org.apache.sshd.common.SshConstants; > import org.apache.sshd.common.SshException; > import org.apache.sshd.common.auth.AbstractUserAuthServiceFactory; > import org.apache.sshd.common.config.keys.KeyUtils; > +import org.apache.sshd.common.config.keys.OpenSshCertificate; > import org.apache.sshd.common.io.IoService; > import org.apache.sshd.common.io.IoSession; > import org.apache.sshd.common.io.IoWriteFuture; > @@ -46,6 +49,7 @@ import org.apache.sshd.common.kex.KexState; > import org.apache.sshd.common.kex.extension.KexExtensionHandler; > import > org.apache.sshd.common.kex.extension.KexExtensionHandler.AvailabilityPhase; > import org.apache.sshd.common.kex.extension.KexExtensionHandler.KexPhase; > +import org.apache.sshd.common.keyprovider.HostKeyCertificateProvider; > import org.apache.sshd.common.keyprovider.KeyPairProvider; > import org.apache.sshd.common.session.ConnectionService; > import org.apache.sshd.common.session.SessionContext; > @@ -81,6 +85,7 @@ public abstract class AbstractServerSession extends > AbstractSession implements S > private HostBasedAuthenticator hostBasedAuthenticator; > private List<UserAuthFactory> userAuthFactories; > private KeyPairProvider keyPairProvider; > + private HostKeyCertificateProvider hostKeyCertificateProvider; > > protected AbstractServerSession(ServerFactoryManager factoryManager, > IoSession ioSession) { > super(true, factoryManager, ioSession); > @@ -189,6 +194,15 @@ public abstract class AbstractServerSession extends > AbstractSession implements S > (parent == null) ? null : ((ServerAuthenticationManager) > parent).getKeyPairProvider()); > } > > + public HostKeyCertificateProvider getHostKeyCertificateProvider() { > + ServerFactoryManager manager = getFactoryManager(); > + return resolveEffectiveProvider(HostKeyCertificateProvider.class, > hostKeyCertificateProvider, manager.getHostKeyCertificateProvider()); > + } > + > + public void setHostKeyCertificateProvider(HostKeyCertificateProvider > hostKeyCertificateProvider) { > + this.hostKeyCertificateProvider = hostKeyCertificateProvider; > + } > + > @Override > public void setKeyPairProvider(KeyPairProvider keyPairProvider) { > this.keyPairProvider = keyPairProvider; > @@ -367,9 +381,25 @@ public abstract class AbstractServerSession extends > AbstractSession implements S > > KeyPairProvider kpp = getKeyPairProvider(); > boolean debugEnabled = log.isDebugEnabled(); > - Iterable<String> provided; > + Set<String> provided = null; > try { > - provided = (kpp == null) ? null : kpp.getKeyTypes(this); > + if (kpp != null) { > + provided = > GenericUtils.stream(kpp.getKeyTypes(this)).collect(Collectors.toSet()); > + > + HostKeyCertificateProvider hostKeyCertificateProvider = > getHostKeyCertificateProvider(); > + if (hostKeyCertificateProvider != null) { > + 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())) { > + provided.add(certificate.getKeyType()); > + } else { > + log.info("No private key for provided > certificate available. Missing private key type: {}", > + certificate.getRawKeyType()); > + } > + } > + } > + } > } catch (Error e) { > log.warn("resolveAvailableSignaturesProposal({}) failed ({}) > to get key types: {}", > this, e.getClass().getSimpleName(), e.getMessage()); > @@ -498,7 +528,16 @@ public abstract class AbstractServerSession extends > AbstractSession implements S > KeyPairProvider provider = > Objects.requireNonNull(getKeyPairProvider(), "No host keys > provider"); > try { > + HostKeyCertificateProvider hostKeyCertificateProvider = > getHostKeyCertificateProvider(); > + if (hostKeyCertificateProvider != null) { > + OpenSshCertificate publicKey = > hostKeyCertificateProvider.loadCertificate(this, keyType); > + if (publicKey != null) { > + KeyPair keyPair = provider.loadKey(this, > publicKey.getRawKeyType()); > + return new KeyPair(publicKey, keyPair.getPrivate()); > + } > + } > 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 > new file mode 100644 > index 0000000..50bd39d > --- /dev/null > +++ > b/sshd-core/src/test/java/org/apache/sshd/common/signature/OpenSSHCertificateTest.java > @@ -0,0 +1,161 @@ > +/* > + * 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.signature; > + > +import java.util.ArrayList; > +import java.util.Arrays; > +import java.util.Collections; > +import java.util.List; > + > +import org.apache.sshd.client.ClientFactoryManager; > +import org.apache.sshd.client.SshClient; > +import org.apache.sshd.client.session.ClientSession; > +import org.apache.sshd.common.NamedFactory; > +import org.apache.sshd.common.PropertyResolverUtils; > +import org.apache.sshd.common.SshConstants; > +import org.apache.sshd.common.SshException; > +import org.apache.sshd.common.keyprovider.FileHostKeyCertificateProvider; > +import org.apache.sshd.common.keyprovider.FileKeyPairProvider; > +import org.apache.sshd.server.SshServer; > +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.BeforeClass; > +import org.junit.FixMethodOrder; > +import org.junit.Test; > +import org.junit.runner.RunWith; > +import org.junit.runners.MethodSorters; > +import org.junit.runners.Parameterized; > + > +@FixMethodOrder(MethodSorters.NAME_ASCENDING) > +@RunWith(Parameterized.class) // see > https://github.com/junit-team/junit/wiki/Parameterized-tests > > +@Parameterized.UseParametersRunnerFactory(JUnit4ClassRunnerWithParametersFactory.class) > +public class OpenSSHCertificateTest extends BaseTestSupport { > + private static SshServer sshd; > + private static SshClient client; > + private static int port; > + > + 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)); > + this.signatureFactory = signatureFactory; > + } > + > + @BeforeClass > + public static void setupClientAndServer() throws Exception { > + sshd = > CoreTestSupportUtils.setupTestServer(OpenSSHCertificateTest.class); > + sshd.start(); > + port = sshd.getPort(); > + > + client = > CoreTestSupportUtils.setupTestClient(OpenSSHCertificateTest.class); > + 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; > + } > + } > + } > + > + @Parameterized.Parameters(name = "type={2}") > + public static List<Object[]> parameters() { > + List<Object[]> list = new ArrayList<>(); > + > + String key = "ssh_host_rsa_key"; > + String certificate = "ssh_host_rsa_key_sha1-cert.pub"; > + String certificateSha512 = "ssh_host_rsa_key-cert.pub"; > + > + // default client > + list.add(new Object[]{key, certificate, null}); > + list.add(new Object[]{key, certificate, > Arrays.asList(BuiltinSignatures.rsa_cert, BuiltinSignatures.rsa)}); > + // client does not support cert > + list.add(new Object[]{key, certificate, > Collections.singletonList(BuiltinSignatures.rsa)}); > + // rsa variant > + list.add(new Object[]{key, certificateSha512, > Arrays.asList(BuiltinSignatures.rsaSHA512_cert, > BuiltinSignatures.rsaSHA512)}); > + list.add(new Object[]{key, certificateSha512, > Arrays.asList(BuiltinSignatures.rsa_cert, BuiltinSignatures.rsaSHA512)}); > + > + return Collections.unmodifiableList(list); > + } > + > + @Test > + public void testOpenSshCertificates() throws Exception { > + sshd.setKeyPairProvider(keyPairProvider); > + sshd.setHostKeyCertificateProvider(certificateProvider); > + if (signatureFactory != null) { > + client.setSignatureFactories(signatureFactory); > + } > + > + // default client > + try (ClientSession s = client.connect(getCurrentTestName(), > TEST_LOCALHOST, port) > + .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 > + PropertyResolverUtils.updateProperty(client, > ClientFactoryManager.ABORT_ON_INVALID_CERTIFICATE, false); > + try (ClientSession s = client.connect(getCurrentTestName(), > "localhost", port) > + .verify(CONNECT_TIMEOUT).getSession()) { > + s.addPasswordIdentity(getCurrentTestName()); > + s.auth().verify(AUTH_TIMEOUT); > + } > + > + // invalid principal, abort > + PropertyResolverUtils.updateProperty(client, > ClientFactoryManager.ABORT_ON_INVALID_CERTIFICATE, true); > + try (ClientSession s = client.connect(getCurrentTestName(), > "localhost", port) > + .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)); > + } catch (SshException e) { > + > Assert.assertEquals(SshConstants.SSH2_DISCONNECT_KEY_EXCHANGE_FAILED, > e.getDisconnectCode()); > + } > + } > +} > diff --git > a/sshd-core/src/test/resources/org/apache/sshd/common/signature/example-ca > b/sshd-core/src/test/resources/org/apache/sshd/common/signature/example-ca > new file mode 100644 > index 0000000..5c1f3cd > --- /dev/null > +++ > b/sshd-core/src/test/resources/org/apache/sshd/common/signature/example-ca > @@ -0,0 +1,49 @@ > +-----BEGIN OPENSSH PRIVATE KEY----- > +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 > +-----END OPENSSH PRIVATE KEY----- > diff --git > a/sshd-core/src/test/resources/org/apache/sshd/common/signature/example-ca.pub > b/sshd-core/src/test/resources/org/apache/sshd/common/signature/example-ca.pub > new file mode 100644 > index 0000000..c5776fc > --- /dev/null > +++ > b/sshd-core/src/test/resources/org/apache/sshd/common/signature/example-ca.pub > @@ -0,0 +1 @@ > +ssh-rsa > AAAAB3NzaC1yc2EAAAADAQABAAACAQDC9ueamUQ2HJDZvS9L6fpb4umROUrPrRjb6TaIuH3K8h9uT8+8oJIM4Pqn7mlSMvX0Vi8VUCpMQ2fZnqukFrB5W8jNdGG7a/IEDF/Yg3dON1D7I43GRo/I7Qk9EGSeDayvIeh0g6dqalx7dyIg83GvsuqC+dZlQpLdFML1PciaHur1NHF9zDyLKjb+Qfxi738Y3tmfSFOd37r0r2dWLqRQ0uDk1uY2fF0YYnQonvpW3Sc6pS8lxJXAOPRYs0a3IG0AQKvzXxjg3U4NYKU0rJ8LLpoeEYbgSYwCPg6RWYg618Yu6NU7OjdQ0i3ZfoCh4O/xuW+oz/RoEEkM8bzDr7GuLeluX7u5jUXUYoeBCbgdp21xTIlCezmzYhZYb6X5Vuk481xXCaEvHAJz++Qz8M3K7YWGilgynFFm6UUHCefgGirei10X4x24FpnTxKmLpxgGWlW1 > [...] > diff --git > a/sshd-core/src/test/resources/org/apache/sshd/common/signature/ssh_host_rsa_key > b/sshd-core/src/test/resources/org/apache/sshd/common/signature/ssh_host_rsa_key > new file mode 100644 > index 0000000..1db1976 > --- /dev/null > +++ > b/sshd-core/src/test/resources/org/apache/sshd/common/signature/ssh_host_rsa_key > @@ -0,0 +1,38 @@ > +-----BEGIN OPENSSH PRIVATE KEY----- > +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 > +-----END OPENSSH PRIVATE KEY----- > diff --git > a/sshd-core/src/test/resources/org/apache/sshd/common/signature/ssh_host_rsa_key-cert.pub > b/sshd-core/src/test/resources/org/apache/sshd/common/signature/ssh_host_rsa_key-cert.pub > new file mode 100644 > index 0000000..c570d82 > --- /dev/null > +++ > b/sshd-core/src/test/resources/org/apache/sshd/common/signature/ssh_host_rsa_key-cert.pub > @@ -0,0 +1 @@ > +ssh-rsa-cert-...@openssh.com > AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgaLndJbPMJA6TJ5Gd1R+vScUdZaKELX/nEdWsfMmmOZ8AAAADAQABAAABgQDq0+az6bCKulks7E/RQBGUNlo8Pxe8Ylwc7KF2PxTrlGtD2qEjNyLMkBGAJMOME5PKiAoUNpmgxA/2oDbNQUEQpHOISnqz58SrkYwtlbRPntiu1bfZKQBxgs8oP0nVZU30ck9CmUVWdOY3R+sy85lL/g1vEwgGQPJHmnARg76TlX1xaDaR2RsHR+dz5MfPgZUn3C1SSIWSEoiuTNoh9uthHulybAhnHccTCt+90HN7nUXGSuoiu6P+Wg0p3oqClMk548MOBtSNyPFsSKFEOoivPHOfIuP1j5Of+j9iYhTp6bIrqKkUeX/y6s9iuw/d/ZlKYp2/h913iCHQ+VlJVJa/C96FXzudZ67pmuJ > [...] > diff --git > a/sshd-core/src/test/resources/org/apache/sshd/common/signature/ssh_host_rsa_key.pub > b/sshd-core/src/test/resources/org/apache/sshd/common/signature/ssh_host_rsa_key.pub > new file mode 100644 > index 0000000..65bea39 > --- /dev/null > +++ > b/sshd-core/src/test/resources/org/apache/sshd/common/signature/ssh_host_rsa_key.pub > @@ -0,0 +1 @@ > +ssh-rsa > AAAAB3NzaC1yc2EAAAADAQABAAABgQDq0+az6bCKulks7E/RQBGUNlo8Pxe8Ylwc7KF2PxTrlGtD2qEjNyLMkBGAJMOME5PKiAoUNpmgxA/2oDbNQUEQpHOISnqz58SrkYwtlbRPntiu1bfZKQBxgs8oP0nVZU30ck9CmUVWdOY3R+sy85lL/g1vEwgGQPJHmnARg76TlX1xaDaR2RsHR+dz5MfPgZUn3C1SSIWSEoiuTNoh9uthHulybAhnHccTCt+90HN7nUXGSuoiu6P+Wg0p3oqClMk548MOBtSNyPFsSKFEOoivPHOfIuP1j5Of+j9iYhTp6bIrqKkUeX/y6s9iuw/d/ZlKYp2/h913iCHQ+VlJVJa/C96FXzudZ67pmuJKIesGlYfQLz96Q/Tq5F+QuJm0wG0qUuq3Aa+LZcobOSw+Th/WEmQ9j9O6stZ4Pfh/BPUwPa12u19pLgKH8m25DYLjBLYZO+Zu > [...] > diff --git > a/sshd-core/src/test/resources/org/apache/sshd/common/signature/ssh_host_rsa_key_sha1-cert.pub > b/sshd-core/src/test/resources/org/apache/sshd/common/signature/ssh_host_rsa_key_sha1-cert.pub > new file mode 100644 > index 0000000..67116fa > --- /dev/null > +++ > b/sshd-core/src/test/resources/org/apache/sshd/common/signature/ssh_host_rsa_key_sha1-cert.pub > @@ -0,0 +1 @@ > +ssh-rsa-cert-...@openssh.com > AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgH+fKXukuzsdEZJ/q+yMiHyxAjVOMR3ZNBfiwxjfKVc0AAAADAQABAAABgQDq0+az6bCKulks7E/RQBGUNlo8Pxe8Ylwc7KF2PxTrlGtD2qEjNyLMkBGAJMOME5PKiAoUNpmgxA/2oDbNQUEQpHOISnqz58SrkYwtlbRPntiu1bfZKQBxgs8oP0nVZU30ck9CmUVWdOY3R+sy85lL/g1vEwgGQPJHmnARg76TlX1xaDaR2RsHR+dz5MfPgZUn3C1SSIWSEoiuTNoh9uthHulybAhnHccTCt+90HN7nUXGSuoiu6P+Wg0p3oqClMk548MOBtSNyPFsSKFEOoivPHOfIuP1j5Of+j9iYhTp6bIrqKkUeX/y6s9iuw/d/ZlKYp2/h913iCHQ+VlJVJa/C96FXzudZ67pmuJ > [...] > > -- ------------------------ Guillaume Nodet