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