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.rev240208.EcPrivateKeyFormat;
37 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev240208.EndEntityCertCms;
38 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev240208.PrivateKeyFormat;
39 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev240208.RsaPrivateKeyFormat;
40 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev240208.SshPublicKeyFormat;
41 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev240208.SubjectPublicKeyInfoFormat;
42 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev240208.TrustAnchorCertCms;
43 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev240208._private.key.grouping._private.key.type.CleartextPrivateKeyBuilder;
44 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.crypto.types.rev240208.password.grouping.password.type.CleartextPasswordBuilder;
45 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev240208.ssh.client.grouping.ClientIdentity;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev240208.ssh.client.grouping.ClientIdentityBuilder;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev240208.ssh.client.grouping.ServerAuthentication;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev240208.ssh.client.grouping.ServerAuthenticationBuilder;
49 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev240208.ssh.client.grouping.server.authentication.CaCertsBuilder;
50 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev240208.ssh.client.grouping.server.authentication.SshHostKeysBuilder;
51 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208.ssh.server.grouping.ClientAuthentication;
52 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208.ssh.server.grouping.ClientAuthenticationBuilder;
53 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208.ssh.server.grouping.ServerIdentity;
54 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208.ssh.server.grouping.ServerIdentityBuilder;
55 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208.ssh.server.grouping.client.authentication.UsersBuilder;
56 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208.ssh.server.grouping.client.authentication.users.User;
57 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208.ssh.server.grouping.client.authentication.users.UserBuilder;
58 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208.ssh.server.grouping.client.authentication.users.user.PasswordBuilder;
59 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208.ssh.server.grouping.client.authentication.users.user.PublicKeysBuilder;
60 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev240208.inline.or.truststore.certs.grouping.inline.or.truststore.inline.inline.definition.CertificateBuilder;
61 import org.opendaylight.yangtools.yang.binding.util.BindingMap;
63 public final class TestUtils {
65 private static final String PUBLIC_KEY_NAME = "public-key-name";
66 private static final String HOST_KEY_NAME = "host-key-name";
67 private static final String CERTIFICATE_NAME = "certificate-name";
68 private static final String CERTIFICATE_CN = "certificate-cn";
69 private static final String BC = BouncyCastleProvider.PROVIDER_NAME;
70 private static final SecureRandom SECURE_RANDOM = new SecureRandom();
73 Security.addProvider(new BouncyCastleProvider());
80 public static ServerIdentity buildServerIdentityWithKeyPair(final KeyData keyData) {
81 return new ServerIdentityBuilder().setHostKey(List.of(buildServerHostKeyWithKeyPair(keyData))).build();
84 public static ServerIdentity buildServerIdentityWithCertificate(final KeyData keyData) {
85 return new ServerIdentityBuilder().setHostKey(List.of(buildServerHostKeyWithCertificate(keyData))).build();
88 private static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208
89 .ssh.server.grouping.server.identity.HostKey buildServerHostKeyWithKeyPair(final KeyData keyData) {
90 return new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208
91 .ssh.server.grouping.server.identity.HostKeyBuilder()
92 .setName(HOST_KEY_NAME)
93 .setHostKeyType(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208
94 .ssh.server.grouping.server.identity.host.key.host.key.type.PublicKeyBuilder()
95 .setPublicKey(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208
96 .ssh.server.grouping.server.identity.host.key.host.key.type._public.key.PublicKeyBuilder()
97 .setInlineOrKeystore(buildAsymmetricKeyLocal(keyData))
103 private static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208
104 .ssh.server.grouping.server.identity.HostKey buildServerHostKeyWithCertificate(final KeyData keyData) {
105 return new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208
106 .ssh.server.grouping.server.identity.HostKeyBuilder()
107 .setName(HOST_KEY_NAME)
108 .setHostKeyType(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208
109 .ssh.server.grouping.server.identity.host.key.host.key.type.CertificateBuilder()
110 .setCertificate(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208
111 .ssh.server.grouping.server.identity.host.key.host.key.type.certificate.CertificateBuilder()
112 .setInlineOrKeystore(buildEndEntityCertWithKeyLocal(keyData))
118 public static ServerAuthentication buildServerAuthWithPublicKey(final KeyData keyData) {
119 return new ServerAuthenticationBuilder()
120 .setSshHostKeys(new SshHostKeysBuilder()
121 .setInlineOrTruststore(buildTruststorePublicKeyLocal(keyData))
126 private static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev240208
127 .inline.or.truststore._public.keys.grouping.inline.or.truststore.Inline buildTruststorePublicKeyLocal(
128 final KeyData keyData) {
129 return new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev240208
130 .inline.or.truststore._public.keys.grouping.inline.or.truststore.InlineBuilder()
131 .setInlineDefinition(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev240208
132 .inline.or.truststore._public.keys.grouping.inline.or.truststore.inline.InlineDefinitionBuilder()
133 .setPublicKey(BindingMap.of(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore
134 .rev240208.inline.or.truststore._public.keys.grouping.inline.or.truststore.inline.inline.definition
136 .setName(PUBLIC_KEY_NAME)
137 .setPublicKeyFormat(SshPublicKeyFormat.VALUE)
138 .setPublicKey(keyData.publicKeySshBytes())
144 public static ServerAuthentication buildServerAuthWithCertificate(final KeyData keyData) {
145 // NB both CA anc EE certificates are processed same way, no reason for additional eeCerts builder
146 return new ServerAuthenticationBuilder()
147 .setCaCerts(new CaCertsBuilder()
148 .setInlineOrTruststore(buildTruststoreCertificatesLocal(keyData.certificateBytes()))
153 private static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev240208
154 .inline.or.truststore.certs.grouping.inline.or.truststore.Inline buildTruststoreCertificatesLocal(
155 final byte[] certificateBytes) {
156 return new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev240208
157 .inline.or.truststore.certs.grouping.inline.or.truststore.InlineBuilder()
158 .setInlineDefinition(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev240208
159 .inline.or.truststore.certs.grouping.inline.or.truststore.inline.InlineDefinitionBuilder()
160 .setCertificate(BindingMap.of(new CertificateBuilder()
161 .setName(CERTIFICATE_NAME)
162 .setCertData(new TrustAnchorCertCms(certificateBytes))
168 private static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev240208
169 .inline.or.keystore.asymmetric.key.grouping.InlineOrKeystore buildAsymmetricKeyLocal(final KeyData data) {
170 return buildAsymmetricKeyLocal(data.algorithm(), data.publicKeyBytes(), data.privateKeyBytes());
173 private static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev240208
174 .inline.or.keystore.asymmetric.key.grouping.InlineOrKeystore buildAsymmetricKeyLocal(final String algorithm,
175 final byte[] publicKeyBytes, final byte[] privateKeyBytes) {
176 return new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev240208
177 .inline.or.keystore.asymmetric.key.grouping.inline.or.keystore.InlineBuilder()
178 .setInlineDefinition(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev240208
179 .inline.or.keystore.asymmetric.key.grouping.inline.or.keystore.inline.InlineDefinitionBuilder()
180 .setPublicKeyFormat(SubjectPublicKeyInfoFormat.VALUE)
181 .setPublicKey(publicKeyBytes)
182 .setPrivateKeyFormat(getPrivateKeyFormat(algorithm))
183 .setPrivateKeyType(new CleartextPrivateKeyBuilder().setCleartextPrivateKey(privateKeyBytes).build())
188 public static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev240208
189 .inline.or.keystore.end.entity.cert.with.key.grouping.InlineOrKeystore buildEndEntityCertWithKeyLocal(
190 final KeyData keyData) {
191 return new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev240208
192 .inline.or.keystore.end.entity.cert.with.key.grouping.inline.or.keystore.InlineBuilder()
193 .setInlineDefinition(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.keystore.rev240208
194 .inline.or.keystore.end.entity.cert.with.key.grouping.inline.or.keystore.inline
195 .InlineDefinitionBuilder()
196 .setPublicKeyFormat(SubjectPublicKeyInfoFormat.VALUE)
197 .setPublicKey(keyData.publicKeyBytes())
198 .setPrivateKeyFormat(getPrivateKeyFormat(keyData.algorithm()))
199 .setPrivateKeyType(new CleartextPrivateKeyBuilder()
200 .setCleartextPrivateKey(keyData.privateKeyBytes()).build())
201 .setCertData(new EndEntityCertCms(keyData.certificateBytes()))
206 public static ClientAuthentication buildClientAuthWithPassword(final String userName, final String cryptHash) {
207 return buildClientAuth(buildServerUserWithPassword(userName, cryptHash));
210 public static ClientAuthentication buildClientAuthHostBased(final String userName, final KeyData keyData) {
211 return buildClientAuth(buildServerUserHostBased(userName, keyData.publicKeySshBytes()));
214 public static ClientAuthentication buildClientAuthWithPublicKey(final String userName, final KeyData keyData) {
215 return buildClientAuth(buildServerUserWithPublicKey(userName, keyData.publicKeySshBytes()));
218 private static ClientAuthentication buildClientAuth(final User user) {
219 return new ClientAuthenticationBuilder()
220 .setUsers(new UsersBuilder().setUser(BindingMap.of(user)).build())
224 private static User buildServerUserHostBased(final String userName, final byte[] publicKeyBytes) {
225 return new UserBuilder()
227 .setHostbased(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.server.rev240208
228 .ssh.server.grouping.client.authentication.users.user.HostbasedBuilder()
229 .setInlineOrTruststore(buildPublicKeyLocal(publicKeyBytes))
234 private static org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev240208
235 .inline.or.truststore._public.keys.grouping.inline.or.truststore.Inline buildPublicKeyLocal(
236 final byte[] publicKeyBytes) {
237 return new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev240208
238 .inline.or.truststore._public.keys.grouping.inline.or.truststore.InlineBuilder()
239 .setInlineDefinition(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.truststore.rev240208
240 .inline.or.truststore._public.keys.grouping.inline.or.truststore.inline.InlineDefinitionBuilder()
241 .setPublicKey(BindingMap.of(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf
242 .truststore.rev240208.inline.or.truststore._public.keys.grouping.inline.or.truststore.inline.inline
243 .definition.PublicKeyBuilder()
244 .setPublicKeyFormat(SshPublicKeyFormat.VALUE)
245 .setName(PUBLIC_KEY_NAME)
246 .setPublicKey(publicKeyBytes)
252 public static User buildServerUserWithPublicKey(final String userName, final byte[] publicKeyBytes) {
253 return new UserBuilder()
255 .setPublicKeys(new PublicKeysBuilder().setInlineOrTruststore(buildPublicKeyLocal(publicKeyBytes)).build())
259 private static User buildServerUserWithPassword(final String userName, final String cryptHash) {
260 return new UserBuilder()
262 .setPassword(new PasswordBuilder()
263 .setHashedPassword(new CryptHash(cryptHash))
268 public static ClientIdentity buildClientIdentityWithPassword(final String username, final String password) {
269 return new ClientIdentityBuilder()
270 .setUsername(username)
271 .setPassword(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev240208
272 .ssh.client.grouping.client.identity.PasswordBuilder()
273 .setPasswordType(new CleartextPasswordBuilder().setCleartextPassword(password).build()).build())
277 public static ClientIdentity buildClientIdentityHostBased(final String username, final KeyData data) {
278 return new ClientIdentityBuilder()
279 .setUsername(username)
280 .setHostbased(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev240208
281 .ssh.client.grouping.client.identity.HostbasedBuilder()
282 .setInlineOrKeystore(buildAsymmetricKeyLocal(data))
287 public static ClientIdentity buildClientIdentityWithPublicKey(final String username, final KeyData data) {
288 return new ClientIdentityBuilder()
289 .setUsername(username)
290 .setPublicKey(new org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.ssh.client.rev240208
291 .ssh.client.grouping.client.identity.PublicKeyBuilder()
292 .setInlineOrKeystore(buildAsymmetricKeyLocal(data))
297 private static PrivateKeyFormat getPrivateKeyFormat(final String algorithm) {
298 return isRSA(algorithm) ? RsaPrivateKeyFormat.VALUE : EcPrivateKeyFormat.VALUE;
301 public static KeyData generateKeyPairWithCertificate(final String algorithm) throws IOException {
303 final var keyPairGenerator = KeyPairGenerator.getInstance(algorithm, BC);
304 if (isRSA(algorithm)) {
305 keyPairGenerator.initialize(
306 new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4), SECURE_RANDOM);
308 keyPairGenerator.initialize(new ECGenParameterSpec("prime256v1"), SECURE_RANDOM);
310 final var keyPair = keyPairGenerator.generateKeyPair();
311 final var certificate = generateCertificate(keyPair,
312 isRSA(algorithm) ? "SHA256withRSA" : "SHA256withECDSA");
313 final var publicKeyBytes = keyPair.getPublic().getEncoded();
314 final var privateKeyBytes = keyPair.getPrivate().getEncoded();
316 return new KeyData(algorithm, privateKeyBytes, publicKeyBytes,
317 OpenSSHPublicKeyUtil.encodePublicKey(PublicKeyFactory.createKey(publicKeyBytes)),
318 certificate.getEncoded());
320 } catch (NoSuchAlgorithmException | NoSuchProviderException | OperatorCreationException | CertificateException
321 | InvalidAlgorithmParameterException e) {
322 throw new IOException(e); // simplify method signature
326 private static X509Certificate generateCertificate(final KeyPair keyPair, final String hashAlgorithm)
327 throws OperatorCreationException, CertificateException {
328 final var now = Instant.now();
329 final var contentSigner = new JcaContentSignerBuilder(hashAlgorithm).build(keyPair.getPrivate());
331 final var x500Name = new X500Name("CN=" + CERTIFICATE_CN);
332 final var certificateBuilder = new JcaX509v3CertificateBuilder(x500Name,
333 BigInteger.valueOf(now.toEpochMilli()),
334 Date.from(now), Date.from(now.plus(Duration.ofDays(365))),
336 keyPair.getPublic());
337 return new JcaX509CertificateConverter()
338 .setProvider(new BouncyCastleProvider()).getCertificate(certificateBuilder.build(contentSigner));
341 private static boolean isRSA(final String algorithm) {
342 return "RSA".equals(algorithm);
345 public record KeyData(String algorithm, byte[] privateKeyBytes, byte[] publicKeyBytes,
346 byte[] publicKeySshBytes, byte[] certificateBytes) {