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