Rework SslHandlerFactory
[netconf.git] / apps / callhome-provider / src / main / java / org / opendaylight / netconf / topology / callhome / AuthorizedKeysDecoder.java
1 /*
2  * Copyright (c) 2016 Brocade Communication Systems and others.  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.topology.callhome;
9
10 import com.google.common.collect.ImmutableMap;
11 import java.io.ByteArrayOutputStream;
12 import java.io.DataOutput;
13 import java.io.DataOutputStream;
14 import java.io.IOException;
15 import java.math.BigInteger;
16 import java.nio.charset.StandardCharsets;
17 import java.security.GeneralSecurityException;
18 import java.security.KeyFactory;
19 import java.security.NoSuchAlgorithmException;
20 import java.security.PublicKey;
21 import java.security.interfaces.DSAPublicKey;
22 import java.security.interfaces.RSAPublicKey;
23 import java.security.spec.DSAPublicKeySpec;
24 import java.security.spec.ECPoint;
25 import java.security.spec.ECPublicKeySpec;
26 import java.security.spec.RSAPublicKeySpec;
27 import java.util.Arrays;
28 import org.bouncycastle.jce.ECNamedCurveTable;
29 import org.bouncycastle.jce.ECPointUtil;
30 import org.bouncycastle.jce.interfaces.ECPublicKey;
31 import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
32 import org.bouncycastle.jce.spec.ECNamedCurveSpec;
33 import org.eclipse.jdt.annotation.NonNull;
34 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.netconf.callhome.server.rev240129.SshPublicKey;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
37
38 final class AuthorizedKeysDecoder {
39     private static final Logger LOG = LoggerFactory.getLogger(AuthorizedKeysDecoder.class);
40
41     private static final ImmutableMap<String, String> ECDSA_CURVES = ImmutableMap.<String, String>builder()
42         .put("nistp256", "secp256r1")
43         .put("nistp384", "secp384r1")
44         .put("nistp512", "secp512r1")
45         .build();
46
47     private static final String ECDSA_SUPPORTED_CURVE_NAME = "nistp256";
48     private static final byte[] ECDSA_SUPPORTED_CURVE_NAME_BYTES =
49         ECDSA_SUPPORTED_CURVE_NAME.getBytes(StandardCharsets.UTF_8);
50     private static final String ECDSA_SUPPORTED_CURVE_NAME_SPEC = ECDSA_CURVES.get(ECDSA_SUPPORTED_CURVE_NAME);
51
52     private static final String KEY_TYPE_RSA = "ssh-rsa";
53     private static final byte[] KEY_TYPE_RSA_BYTES = KEY_TYPE_RSA.getBytes(StandardCharsets.UTF_8);
54
55     private static final String KEY_TYPE_DSA = "ssh-dss";
56     private static final byte[] KEY_TYPE_DSA_BYTES = KEY_TYPE_DSA.getBytes(StandardCharsets.UTF_8);
57     private static final String KEY_TYPE_ECDSA = "ecdsa-sha2-" + ECDSA_SUPPORTED_CURVE_NAME;
58     private static final byte[] KEY_TYPE_ECDSA_BYTES = KEY_TYPE_ECDSA.getBytes(StandardCharsets.UTF_8);
59
60     private static final KeyFactory RSA_FACTORY = loadOrWarn("RSA");
61     private static final KeyFactory DSA_FACTORY = loadOrWarn("DSA");
62     private static final KeyFactory EC_FACTORY = loadOrWarn("EC");
63
64     private static KeyFactory loadOrWarn(final String algorithm) {
65         try {
66             return KeyFactory.getInstance(algorithm);
67         } catch (NoSuchAlgorithmException e) {
68             LOG.warn("KeyFactory for {} not found", algorithm, e);
69             return null;
70         }
71     }
72
73     private final byte[] bytes;
74     private int pos;
75
76     private AuthorizedKeysDecoder(final SshPublicKey keyLine) {
77         bytes = keyLine.getValue();
78     }
79
80     static @NonNull PublicKey decodePublicKey(final @NonNull SshPublicKey keyLine) throws GeneralSecurityException {
81         final var instance = new AuthorizedKeysDecoder(keyLine);
82         final var type = instance.decodeType();
83         return switch (type) {
84             case KEY_TYPE_RSA -> instance.decodeAsRSA();
85             case KEY_TYPE_DSA -> instance.decodeAsDSA();
86             case KEY_TYPE_ECDSA -> instance.decodeAsEcDSA();
87             default -> throw new NoSuchAlgorithmException("Unknown decode key type " + type + " in " + keyLine);
88         };
89     }
90
91     static @NonNull SshPublicKey encodePublicKey(final PublicKey publicKey) throws IOException {
92         final var baos = new ByteArrayOutputStream();
93
94         try (var dout = new DataOutputStream(baos)) {
95             if (publicKey instanceof RSAPublicKey rsa) {
96                 dout.writeInt(KEY_TYPE_RSA_BYTES.length);
97                 dout.write(KEY_TYPE_RSA_BYTES);
98                 encodeBigInt(dout, rsa.getPublicExponent());
99                 encodeBigInt(dout, rsa.getModulus());
100             } else if (publicKey instanceof DSAPublicKey dsa) {
101                 final var dsaParams = dsa.getParams();
102                 dout.writeInt(KEY_TYPE_DSA_BYTES.length);
103                 dout.write(KEY_TYPE_DSA_BYTES);
104                 encodeBigInt(dout, dsaParams.getP());
105                 encodeBigInt(dout, dsaParams.getQ());
106                 encodeBigInt(dout, dsaParams.getG());
107                 encodeBigInt(dout, dsa.getY());
108             } else if (publicKey instanceof ECPublicKey ec) {
109                 dout.writeInt(KEY_TYPE_ECDSA_BYTES.length);
110                 dout.write(KEY_TYPE_ECDSA_BYTES);
111                 dout.writeInt(ECDSA_SUPPORTED_CURVE_NAME_BYTES.length);
112                 dout.write(ECDSA_SUPPORTED_CURVE_NAME_BYTES);
113
114                 final var q = ec.getQ();
115                 final var coordX = q.getAffineXCoord().getEncoded();
116                 final var coordY = q.getAffineYCoord().getEncoded();
117                 dout.writeInt(coordX.length + coordY.length + 1);
118                 dout.writeByte(0x04);
119                 dout.write(coordX);
120                 dout.write(coordY);
121             } else {
122                 throw new IOException("Unknown public key encoding: " + publicKey);
123             }
124         }
125         return new SshPublicKey(baos.toByteArray());
126     }
127
128     private @NonNull PublicKey decodeAsEcDSA() throws GeneralSecurityException {
129         if (EC_FACTORY == null) {
130             throw new NoSuchAlgorithmException("ECDSA keys are not supported");
131         }
132
133         ECNamedCurveParameterSpec spec256r1 = ECNamedCurveTable.getParameterSpec(ECDSA_SUPPORTED_CURVE_NAME_SPEC);
134         ECNamedCurveSpec params256r1 = new ECNamedCurveSpec(
135             ECDSA_SUPPORTED_CURVE_NAME_SPEC, spec256r1.getCurve(), spec256r1.getG(), spec256r1.getN());
136         // copy last 65 bytes from ssh key.
137         ECPoint point = ECPointUtil.decodePoint(params256r1.getCurve(), Arrays.copyOfRange(bytes, 39, bytes.length));
138         return EC_FACTORY.generatePublic(new ECPublicKeySpec(point, params256r1));
139     }
140
141     private @NonNull PublicKey decodeAsDSA() throws GeneralSecurityException {
142         if (DSA_FACTORY == null) {
143             throw new NoSuchAlgorithmException("RSA keys are not supported");
144         }
145
146         final var p = decodeBigInt();
147         final var q = decodeBigInt();
148         final var g = decodeBigInt();
149         final var y = decodeBigInt();
150         return DSA_FACTORY.generatePublic(new DSAPublicKeySpec(y, p, q, g));
151     }
152
153     private @NonNull PublicKey decodeAsRSA() throws GeneralSecurityException {
154         if (RSA_FACTORY == null) {
155             throw new NoSuchAlgorithmException("RSA keys are not supported");
156         }
157
158         final var exponent = decodeBigInt();
159         final var modulus = decodeBigInt();
160         return RSA_FACTORY.generatePublic(new RSAPublicKeySpec(modulus, exponent));
161     }
162
163     private String decodeType() {
164         int len = decodeInt();
165         String type = new String(bytes, pos, len, StandardCharsets.UTF_8);
166         pos += len;
167         return type;
168     }
169
170     private int decodeInt() {
171         return (bytes[pos++] & 0xFF) << 24 | (bytes[pos++] & 0xFF) << 16
172                 | (bytes[pos++] & 0xFF) << 8 | bytes[pos++] & 0xFF;
173     }
174
175     private BigInteger decodeBigInt() {
176         int len = decodeInt();
177         byte[] bigIntBytes = new byte[len];
178         System.arraycopy(bytes, pos, bigIntBytes, 0, len);
179         pos += len;
180         return new BigInteger(bigIntBytes);
181     }
182
183     private static void encodeBigInt(final DataOutput out, final BigInteger value) throws IOException {
184         final var bytes = value.toByteArray();
185         out.writeInt(bytes.length);
186         out.write(bytes);
187     }
188 }