2 * Copyright (c) 2023 PANTHEON.tech s.r.o. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.netconf.transport.ssh;
10 import java.io.IOException;
11 import java.math.BigInteger;
12 import java.security.InvalidAlgorithmParameterException;
13 import java.security.KeyPair;
14 import java.security.KeyPairGenerator;
15 import java.security.NoSuchAlgorithmException;
16 import java.security.NoSuchProviderException;
17 import java.security.SecureRandom;
18 import java.security.Security;
19 import java.security.cert.CertificateException;
20 import java.security.cert.X509Certificate;
21 import java.security.spec.ECGenParameterSpec;
22 import java.security.spec.RSAKeyGenParameterSpec;
23 import java.time.Duration;
24 import java.time.Instant;
25 import java.util.Date;
26 import java.util.List;
27 import org.bouncycastle.asn1.x500.X500Name;
28 import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
29 import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
30 import org.bouncycastle.crypto.util.OpenSSHPublicKeyUtil;
31 import org.bouncycastle.crypto.util.PublicKeyFactory;
32 import org.bouncycastle.jce.provider.BouncyCastleProvider;
33 import org.bouncycastle.operator.OperatorCreationException;
34 import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
35 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.iana.crypt.hash.rev140806.CryptHash;
36 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev231228.EcPrivateKeyFormat;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev231228.EndEntityCertCms;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev231228.PrivateKeyFormat;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev231228.RsaPrivateKeyFormat;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev231228.SshPublicKeyFormat;
41 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev231228.SubjectPublicKeyInfoFormat;
42 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev231228.TrustAnchorCertCms;
43 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev231228._private.key.grouping._private.key.type.CleartextPrivateKeyBuilder;
44 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev231228.password.grouping.password.type.CleartextPasswordBuilder;
45 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228.ssh.client.grouping.ClientIdentity;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228.ssh.client.grouping.ClientIdentityBuilder;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228.ssh.client.grouping.ServerAuthentication;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228.ssh.client.grouping.ServerAuthenticationBuilder;
49 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228.ssh.client.grouping.server.authentication.CaCertsBuilder;
50 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228.ssh.client.grouping.server.authentication.SshHostKeysBuilder;
51 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228.ssh.server.grouping.ClientAuthentication;
52 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228.ssh.server.grouping.ClientAuthenticationBuilder;
53 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228.ssh.server.grouping.ServerIdentity;
54 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228.ssh.server.grouping.ServerIdentityBuilder;
55 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228.ssh.server.grouping.client.authentication.UsersBuilder;
56 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228.ssh.server.grouping.client.authentication.users.User;
57 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228.ssh.server.grouping.client.authentication.users.UserBuilder;
58 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228.ssh.server.grouping.client.authentication.users.user.PublicKeysBuilder;
59 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228.inline.or.truststore.certs.grouping.inline.or.truststore.inline.inline.definition.CertificateBuilder;
60 import org.opendaylight.yangtools.yang.binding.util.BindingMap;
62 public final class TestUtils {
64 private static final String PUBLIC_KEY_NAME = "public-key-name";
65 private static final String HOST_KEY_NAME = "host-key-name";
66 private static final String CERTIFICATE_NAME = "certificate-name";
67 private static final String CERTIFICATE_CN = "certificate-cn";
68 private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
69 private static final SecureRandom SECURE_RANDOM = new SecureRandom();
72 Security.addProvider(new BouncyCastleProvider());
79 public static ServerIdentity buildServerIdentityWithKeyPair(final KeyData keyData) {
80 return new ServerIdentityBuilder().setHostKey(List.of(buildServerHostKeyWithKeyPair(keyData))).build();
83 public static ServerIdentity buildServerIdentityWithCertificate(final KeyData keyData) {
84 return new ServerIdentityBuilder().setHostKey(List.of(buildServerHostKeyWithCertificate(keyData))).build();
87 private static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228
88 .ssh.server.grouping.server.identity.HostKey buildServerHostKeyWithKeyPair(final KeyData keyData) {
89 return new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228
90 .ssh.server.grouping.server.identity.HostKeyBuilder()
91 .setName(HOST_KEY_NAME)
92 .setHostKeyType(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228
93 .ssh.server.grouping.server.identity.host.key.host.key.type.PublicKeyBuilder()
94 .setPublicKey(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228
95 .ssh.server.grouping.server.identity.host.key.host.key.type._public.key.PublicKeyBuilder()
96 .setInlineOrKeystore(buildAsymmetricKeyLocal(keyData))
102 private static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228
103 .ssh.server.grouping.server.identity.HostKey buildServerHostKeyWithCertificate(final KeyData keyData) {
104 return new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228
105 .ssh.server.grouping.server.identity.HostKeyBuilder()
106 .setName(HOST_KEY_NAME)
107 .setHostKeyType(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228
108 .ssh.server.grouping.server.identity.host.key.host.key.type.CertificateBuilder()
109 .setCertificate(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228
110 .ssh.server.grouping.server.identity.host.key.host.key.type.certificate.CertificateBuilder()
111 .setInlineOrKeystore(buildEndEntityCertWithKeyLocal(keyData))
117 public static ServerAuthentication buildServerAuthWithPublicKey(final KeyData keyData) {
118 return new ServerAuthenticationBuilder()
119 .setSshHostKeys(new SshHostKeysBuilder()
120 .setInlineOrTruststore(buildTruststorePublicKeyLocal(keyData))
125 private static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228
126 .inline.or.truststore._public.keys.grouping.inline.or.truststore.Inline buildTruststorePublicKeyLocal(
127 final KeyData keyData) {
128 return new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228
129 .inline.or.truststore._public.keys.grouping.inline.or.truststore.InlineBuilder()
130 .setInlineDefinition(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228
131 .inline.or.truststore._public.keys.grouping.inline.or.truststore.inline.InlineDefinitionBuilder()
132 .setPublicKey(BindingMap.of(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore
133 .rev231228.inline.or.truststore._public.keys.grouping.inline.or.truststore.inline.inline.definition
135 .setName(PUBLIC_KEY_NAME)
136 .setPublicKeyFormat(SshPublicKeyFormat.VALUE)
137 .setPublicKey(keyData.publicKeySshBytes())
143 public static ServerAuthentication buildServerAuthWithCertificate(final KeyData keyData) {
144 // NB both CA anc EE certificates are processed same way, no reason for additional eeCerts builder
145 return new ServerAuthenticationBuilder()
146 .setCaCerts(new CaCertsBuilder()
147 .setInlineOrTruststore(buildTruststoreCertificatesLocal(keyData.certificateBytes()))
152 private static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228
153 .inline.or.truststore.certs.grouping.inline.or.truststore.Inline buildTruststoreCertificatesLocal(
154 final byte[] certificateBytes) {
155 return new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228
156 .inline.or.truststore.certs.grouping.inline.or.truststore.InlineBuilder()
157 .setInlineDefinition(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228
158 .inline.or.truststore.certs.grouping.inline.or.truststore.inline.InlineDefinitionBuilder()
159 .setCertificate(BindingMap.of(new CertificateBuilder()
160 .setName(CERTIFICATE_NAME)
161 .setCertData(new TrustAnchorCertCms(certificateBytes))
167 private static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev231228
168 .inline.or.keystore.asymmetric.key.grouping.InlineOrKeystore buildAsymmetricKeyLocal(final KeyData data) {
169 return buildAsymmetricKeyLocal(data.algorithm(), data.publicKeyBytes(), data.privateKeyBytes());
172 private static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev231228
173 .inline.or.keystore.asymmetric.key.grouping.InlineOrKeystore buildAsymmetricKeyLocal(final String algorithm,
174 final byte[] publicKeyBytes, final byte[] privateKeyBytes) {
175 return new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev231228
176 .inline.or.keystore.asymmetric.key.grouping.inline.or.keystore.InlineBuilder()
177 .setInlineDefinition(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev231228
178 .inline.or.keystore.asymmetric.key.grouping.inline.or.keystore.inline.InlineDefinitionBuilder()
179 .setPublicKeyFormat(SubjectPublicKeyInfoFormat.VALUE)
180 .setPublicKey(publicKeyBytes)
181 .setPrivateKeyFormat(getPrivateKeyFormat(algorithm))
182 .setPrivateKeyType(new CleartextPrivateKeyBuilder().setCleartextPrivateKey(privateKeyBytes).build())
187 public static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev231228
188 .inline.or.keystore.end.entity.cert.with.key.grouping.InlineOrKeystore buildEndEntityCertWithKeyLocal(
189 final KeyData keyData) {
190 return new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev231228
191 .inline.or.keystore.end.entity.cert.with.key.grouping.inline.or.keystore.InlineBuilder()
192 .setInlineDefinition(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev231228
193 .inline.or.keystore.end.entity.cert.with.key.grouping.inline.or.keystore.inline
194 .InlineDefinitionBuilder()
195 .setPublicKeyFormat(SubjectPublicKeyInfoFormat.VALUE)
196 .setPublicKey(keyData.publicKeyBytes())
197 .setPrivateKeyFormat(getPrivateKeyFormat(keyData.algorithm()))
198 .setPrivateKeyType(new CleartextPrivateKeyBuilder()
199 .setCleartextPrivateKey(keyData.privateKeyBytes()).build())
200 .setCertData(new EndEntityCertCms(keyData.certificateBytes()))
205 public static ClientAuthentication buildClientAuthWithPassword(final String userName, final String cryptHash) {
206 return buildClientAuth(buildServerUserWithPassword(userName, cryptHash));
209 public static ClientAuthentication buildClientAuthHostBased(final String userName, final KeyData keyData) {
210 return buildClientAuth(buildServerUserHostBased(userName, keyData.publicKeySshBytes()));
213 public static ClientAuthentication buildClientAuthWithPublicKey(final String userName, final KeyData keyData) {
214 return buildClientAuth(buildServerUserWithPublicKey(userName, keyData.publicKeySshBytes()));
217 private static ClientAuthentication buildClientAuth(final User user) {
218 return new ClientAuthenticationBuilder()
219 .setUsers(new UsersBuilder().setUser(BindingMap.of(user)).build())
223 private static User buildServerUserHostBased(final String userName, final byte[] publicKeyBytes) {
224 return new UserBuilder()
226 .setHostbased(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev231228
227 .ssh.server.grouping.client.authentication.users.user.HostbasedBuilder()
228 .setInlineOrTruststore(buildPublicKeyLocal(publicKeyBytes))
233 private static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228
234 .inline.or.truststore._public.keys.grouping.inline.or.truststore.Inline buildPublicKeyLocal(
235 final byte[] publicKeyBytes) {
236 return new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228
237 .inline.or.truststore._public.keys.grouping.inline.or.truststore.InlineBuilder()
238 .setInlineDefinition(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev231228
239 .inline.or.truststore._public.keys.grouping.inline.or.truststore.inline.InlineDefinitionBuilder()
240 .setPublicKey(BindingMap.of(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf
241 .truststore.rev231228.inline.or.truststore._public.keys.grouping.inline.or.truststore.inline.inline
242 .definition.PublicKeyBuilder()
243 .setPublicKeyFormat(SshPublicKeyFormat.VALUE)
244 .setName(PUBLIC_KEY_NAME)
245 .setPublicKey(publicKeyBytes)
251 public static User buildServerUserWithPublicKey(final String userName, final byte[] publicKeyBytes) {
252 return new UserBuilder()
254 .setPublicKeys(new PublicKeysBuilder().setInlineOrTruststore(buildPublicKeyLocal(publicKeyBytes)).build())
258 private static User buildServerUserWithPassword(final String userName, final String cryptHash) {
259 return new UserBuilder().setName(userName).setPassword(new CryptHash(cryptHash)).build();
262 public static ClientIdentity buildClientIdentityWithPassword(final String username, final String password) {
263 return new ClientIdentityBuilder()
264 .setUsername(username)
265 .setPassword(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228
266 .ssh.client.grouping.client.identity.PasswordBuilder()
267 .setPasswordType(new CleartextPasswordBuilder().setCleartextPassword(password).build()).build())
271 public static ClientIdentity buildClientIdentityHostBased(final String username, final KeyData data) {
272 return new ClientIdentityBuilder()
273 .setUsername(username)
274 .setHostbased(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228
275 .ssh.client.grouping.client.identity.HostbasedBuilder()
276 .setInlineOrKeystore(buildAsymmetricKeyLocal(data))
281 public static ClientIdentity buildClientIdentityWithPublicKey(final String username, final KeyData data) {
282 return new ClientIdentityBuilder()
283 .setUsername(username)
284 .setPublicKey(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev231228
285 .ssh.client.grouping.client.identity.PublicKeyBuilder()
286 .setInlineOrKeystore(buildAsymmetricKeyLocal(data))
291 private static PrivateKeyFormat getPrivateKeyFormat(final String algorithm) {
292 return isRSA(algorithm) ? RsaPrivateKeyFormat.VALUE : EcPrivateKeyFormat.VALUE;
295 public static KeyData generateKeyPairWithCertificate(final String algorithm) throws IOException {
297 final var keyPairGenerator = KeyPairGenerator.getInstance(algorithm, BC);
298 if (isRSA(algorithm)) {
299 keyPairGenerator.initialize(
300 new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4), SECURE_RANDOM);
302 keyPairGenerator.initialize(new ECGenParameterSpec("prime256v1"), SECURE_RANDOM);
304 final var keyPair = keyPairGenerator.generateKeyPair();
305 final var certificate = generateCertificate(keyPair,
306 isRSA(algorithm) ? "SHA256withRSA" : "SHA256withECDSA");
307 final var publicKeyBytes = keyPair.getPublic().getEncoded();
308 final var privateKeyBytes = keyPair.getPrivate().getEncoded();
310 return new KeyData(algorithm, privateKeyBytes, publicKeyBytes,
311 OpenSSHPublicKeyUtil.encodePublicKey(PublicKeyFactory.createKey(publicKeyBytes)),
312 certificate.getEncoded());
314 } catch (NoSuchAlgorithmException | NoSuchProviderException | OperatorCreationException | CertificateException
315 | InvalidAlgorithmParameterException e) {
316 throw new IOException(e); // simplify method signature
320 private static X509Certificate generateCertificate(final KeyPair keyPair, final String hashAlgorithm)
321 throws OperatorCreationException, CertificateException {
322 final var now = Instant.now();
323 final var contentSigner = new JcaContentSignerBuilder(hashAlgorithm).build(keyPair.getPrivate());
325 final var x500Name = new X500Name("CN=" + CERTIFICATE_CN);
326 final var certificateBuilder = new JcaX509v3CertificateBuilder(x500Name,
327 BigInteger.valueOf(now.toEpochMilli()),
328 Date.from(now), Date.from(now.plus(Duration.ofDays(365))),
330 keyPair.getPublic());
331 return new JcaX509CertificateConverter()
332 .setProvider(new BouncyCastleProvider()).getCertificate(certificateBuilder.build(contentSigner));
335 private static boolean isRSA(final String algorithm) {
336 return "RSA".equals(algorithm);
339 public record KeyData(String algorithm, byte[] privateKeyBytes, byte[] publicKeyBytes,
340 byte[] publicKeySshBytes, byte[] certificateBytes) {