9b96c136ffa5de84a5b9e2e22ae6c867b28ce960
[netconf.git] / transport / transport-ssh / src / test / java / org / opendaylight / netconf / transport / ssh / TestUtils.java
1 /*
2  * Copyright (c) 2023 PANTHEON.tech s.r.o. All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netconf.transport.ssh;
9
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;
61
62 public final class TestUtils {
63
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();
70
71     static {
72         Security.addProvider(new BouncyCastleProvider());
73     }
74
75     private TestUtils() {
76         // utility class
77     }
78
79     public static ServerIdentity buildServerIdentityWithKeyPair(final KeyData keyData) {
80         return new ServerIdentityBuilder().setHostKey(List.of(buildServerHostKeyWithKeyPair(keyData))).build();
81     }
82
83     public static ServerIdentity buildServerIdentityWithCertificate(final KeyData keyData) {
84         return new ServerIdentityBuilder().setHostKey(List.of(buildServerHostKeyWithCertificate(keyData))).build();
85     }
86
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))
97                     .build())
98                 .build())
99             .build();
100     }
101
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))
112                     .build())
113                 .build())
114             .build();
115     }
116
117     public static ServerAuthentication buildServerAuthWithPublicKey(final KeyData keyData) {
118         return new ServerAuthenticationBuilder()
119             .setSshHostKeys(new SshHostKeysBuilder()
120                 .setInlineOrTruststore(buildTruststorePublicKeyLocal(keyData))
121                 .build())
122             .build();
123     }
124
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
134                     .PublicKeyBuilder()
135                         .setName(PUBLIC_KEY_NAME)
136                         .setPublicKeyFormat(SshPublicKeyFormat.VALUE)
137                         .setPublicKey(keyData.publicKeySshBytes())
138                         .build()))
139                 .build())
140             .build();
141     }
142
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()))
148                 .build())
149             .build();
150     }
151
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))
162                     .build()))
163                 .build())
164             .build();
165     }
166
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());
170     }
171
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())
183                 .build())
184             .build();
185     }
186
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()))
201                 .build())
202             .build();
203     }
204
205     public static ClientAuthentication buildClientAuthWithPassword(final String userName, final String cryptHash) {
206         return buildClientAuth(buildServerUserWithPassword(userName, cryptHash));
207     }
208
209     public static ClientAuthentication buildClientAuthHostBased(final String userName, final KeyData keyData) {
210         return buildClientAuth(buildServerUserHostBased(userName, keyData.publicKeySshBytes()));
211     }
212
213     public static ClientAuthentication buildClientAuthWithPublicKey(final String userName, final KeyData keyData) {
214         return buildClientAuth(buildServerUserWithPublicKey(userName, keyData.publicKeySshBytes()));
215     }
216
217     private static ClientAuthentication buildClientAuth(final User user) {
218         return new ClientAuthenticationBuilder()
219             .setUsers(new UsersBuilder().setUser(BindingMap.of(user)).build())
220             .build();
221     }
222
223     private static User buildServerUserHostBased(final String userName, final byte[] publicKeyBytes) {
224         return new UserBuilder()
225             .setName(userName)
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))
229                 .build())
230             .build();
231     }
232
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)
246                     .build()))
247                 .build())
248             .build();
249     }
250
251     public static User buildServerUserWithPublicKey(final String userName, final byte[] publicKeyBytes) {
252         return new UserBuilder()
253             .setName(userName)
254             .setPublicKeys(new PublicKeysBuilder().setInlineOrTruststore(buildPublicKeyLocal(publicKeyBytes)).build())
255             .build();
256     }
257
258     private static User buildServerUserWithPassword(final String userName, final String cryptHash) {
259         return new UserBuilder().setName(userName).setPassword(new CryptHash(cryptHash)).build();
260     }
261
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())
268             .build();
269     }
270
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))
277                 .build())
278             .build();
279     }
280
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))
287                 .build())
288             .build();
289     }
290
291     private static PrivateKeyFormat getPrivateKeyFormat(final String algorithm) {
292         return isRSA(algorithm) ? RsaPrivateKeyFormat.VALUE : EcPrivateKeyFormat.VALUE;
293     }
294
295     public static KeyData generateKeyPairWithCertificate(final String algorithm) throws IOException {
296         try {
297             final var keyPairGenerator = KeyPairGenerator.getInstance(algorithm, BC);
298             if (isRSA(algorithm)) {
299                 keyPairGenerator.initialize(
300                         new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4), SECURE_RANDOM);
301             } else {
302                 keyPairGenerator.initialize(new ECGenParameterSpec("prime256v1"), SECURE_RANDOM);
303             }
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();
309
310             return new KeyData(algorithm, privateKeyBytes, publicKeyBytes,
311                     OpenSSHPublicKeyUtil.encodePublicKey(PublicKeyFactory.createKey(publicKeyBytes)),
312                     certificate.getEncoded());
313
314         } catch (NoSuchAlgorithmException | NoSuchProviderException | OperatorCreationException | CertificateException
315                  | InvalidAlgorithmParameterException e) {
316             throw new IOException(e); // simplify method signature
317         }
318     }
319
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());
324
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))),
329                 x500Name,
330                 keyPair.getPublic());
331         return new JcaX509CertificateConverter()
332                 .setProvider(new BouncyCastleProvider()).getCertificate(certificateBuilder.build(contentSigner));
333     }
334
335     private static boolean isRSA(final String algorithm) {
336         return "RSA".equals(algorithm);
337     }
338
339     public record KeyData(String algorithm, byte[] privateKeyBytes, byte[] publicKeyBytes,
340                           byte[] publicKeySshBytes, byte[] certificateBytes) {
341     }
342 }