Refresh IETF client/server models
[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.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;
62
63 public final class TestUtils {
64
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();
71
72     static {
73         Security.addProvider(new BouncyCastleProvider());
74     }
75
76     private TestUtils() {
77         // utility class
78     }
79
80     public static ServerIdentity buildServerIdentityWithKeyPair(final KeyData keyData) {
81         return new ServerIdentityBuilder().setHostKey(List.of(buildServerHostKeyWithKeyPair(keyData))).build();
82     }
83
84     public static ServerIdentity buildServerIdentityWithCertificate(final KeyData keyData) {
85         return new ServerIdentityBuilder().setHostKey(List.of(buildServerHostKeyWithCertificate(keyData))).build();
86     }
87
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))
98                     .build())
99                 .build())
100             .build();
101     }
102
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))
113                     .build())
114                 .build())
115             .build();
116     }
117
118     public static ServerAuthentication buildServerAuthWithPublicKey(final KeyData keyData) {
119         return new ServerAuthenticationBuilder()
120             .setSshHostKeys(new SshHostKeysBuilder()
121                 .setInlineOrTruststore(buildTruststorePublicKeyLocal(keyData))
122                 .build())
123             .build();
124     }
125
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
135                     .PublicKeyBuilder()
136                         .setName(PUBLIC_KEY_NAME)
137                         .setPublicKeyFormat(SshPublicKeyFormat.VALUE)
138                         .setPublicKey(keyData.publicKeySshBytes())
139                         .build()))
140                 .build())
141             .build();
142     }
143
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()))
149                 .build())
150             .build();
151     }
152
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))
163                     .build()))
164                 .build())
165             .build();
166     }
167
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());
171     }
172
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())
184                 .build())
185             .build();
186     }
187
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()))
202                 .build())
203             .build();
204     }
205
206     public static ClientAuthentication buildClientAuthWithPassword(final String userName, final String cryptHash) {
207         return buildClientAuth(buildServerUserWithPassword(userName, cryptHash));
208     }
209
210     public static ClientAuthentication buildClientAuthHostBased(final String userName, final KeyData keyData) {
211         return buildClientAuth(buildServerUserHostBased(userName, keyData.publicKeySshBytes()));
212     }
213
214     public static ClientAuthentication buildClientAuthWithPublicKey(final String userName, final KeyData keyData) {
215         return buildClientAuth(buildServerUserWithPublicKey(userName, keyData.publicKeySshBytes()));
216     }
217
218     private static ClientAuthentication buildClientAuth(final User user) {
219         return new ClientAuthenticationBuilder()
220             .setUsers(new UsersBuilder().setUser(BindingMap.of(user)).build())
221             .build();
222     }
223
224     private static User buildServerUserHostBased(final String userName, final byte[] publicKeyBytes) {
225         return new UserBuilder()
226             .setName(userName)
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))
230                 .build())
231             .build();
232     }
233
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)
247                     .build()))
248                 .build())
249             .build();
250     }
251
252     public static User buildServerUserWithPublicKey(final String userName, final byte[] publicKeyBytes) {
253         return new UserBuilder()
254             .setName(userName)
255             .setPublicKeys(new PublicKeysBuilder().setInlineOrTruststore(buildPublicKeyLocal(publicKeyBytes)).build())
256             .build();
257     }
258
259     private static User buildServerUserWithPassword(final String userName, final String cryptHash) {
260         return new UserBuilder()
261             .setName(userName)
262             .setPassword(new PasswordBuilder()
263                 .setHashedPassword(new CryptHash(cryptHash))
264                 .build())
265             .build();
266     }
267
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())
274             .build();
275     }
276
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))
283                 .build())
284             .build();
285     }
286
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))
293                 .build())
294             .build();
295     }
296
297     private static PrivateKeyFormat getPrivateKeyFormat(final String algorithm) {
298         return isRSA(algorithm) ? RsaPrivateKeyFormat.VALUE : EcPrivateKeyFormat.VALUE;
299     }
300
301     public static KeyData generateKeyPairWithCertificate(final String algorithm) throws IOException {
302         try {
303             final var keyPairGenerator = KeyPairGenerator.getInstance(algorithm, BC);
304             if (isRSA(algorithm)) {
305                 keyPairGenerator.initialize(
306                         new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4), SECURE_RANDOM);
307             } else {
308                 keyPairGenerator.initialize(new ECGenParameterSpec("prime256v1"), SECURE_RANDOM);
309             }
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();
315
316             return new KeyData(algorithm, privateKeyBytes, publicKeyBytes,
317                     OpenSSHPublicKeyUtil.encodePublicKey(PublicKeyFactory.createKey(publicKeyBytes)),
318                     certificate.getEncoded());
319
320         } catch (NoSuchAlgorithmException | NoSuchProviderException | OperatorCreationException | CertificateException
321                  | InvalidAlgorithmParameterException e) {
322             throw new IOException(e); // simplify method signature
323         }
324     }
325
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());
330
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))),
335                 x500Name,
336                 keyPair.getPublic());
337         return new JcaX509CertificateConverter()
338                 .setProvider(new BouncyCastleProvider()).getCertificate(certificateBuilder.build(contentSigner));
339     }
340
341     private static boolean isRSA(final String algorithm) {
342         return "RSA".equals(algorithm);
343     }
344
345     public record KeyData(String algorithm, byte[] privateKeyBytes, byte[] publicKeyBytes,
346                           byte[] publicKeySshBytes, byte[] certificateBytes) {
347     }
348 }