2 * Copyright (c) 2016 Brocade Communication Systems and others. All rights reserved.
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
8 package org.opendaylight.netconf.topology.callhome;
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;
38 final class AuthorizedKeysDecoder {
39 private static final Logger LOG = LoggerFactory.getLogger(AuthorizedKeysDecoder.class);
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")
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);
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);
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);
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");
64 private static KeyFactory loadOrWarn(final String algorithm) {
66 return KeyFactory.getInstance(algorithm);
67 } catch (NoSuchAlgorithmException e) {
68 LOG.warn("KeyFactory for {} not found", algorithm, e);
73 private final byte[] bytes;
76 private AuthorizedKeysDecoder(final SshPublicKey keyLine) {
77 bytes = keyLine.getValue();
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);
91 static @NonNull SshPublicKey encodePublicKey(final PublicKey publicKey) throws IOException {
92 final var baos = new ByteArrayOutputStream();
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);
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);
122 throw new IOException("Unknown public key encoding: " + publicKey);
125 return new SshPublicKey(baos.toByteArray());
128 private @NonNull PublicKey decodeAsEcDSA() throws GeneralSecurityException {
129 if (EC_FACTORY == null) {
130 throw new NoSuchAlgorithmException("ECDSA keys are not supported");
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));
141 private @NonNull PublicKey decodeAsDSA() throws GeneralSecurityException {
142 if (DSA_FACTORY == null) {
143 throw new NoSuchAlgorithmException("RSA keys are not supported");
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));
153 private @NonNull PublicKey decodeAsRSA() throws GeneralSecurityException {
154 if (RSA_FACTORY == null) {
155 throw new NoSuchAlgorithmException("RSA keys are not supported");
158 final var exponent = decodeBigInt();
159 final var modulus = decodeBigInt();
160 return RSA_FACTORY.generatePublic(new RSAPublicKeySpec(modulus, exponent));
163 private String decodeType() {
164 int len = decodeInt();
165 String type = new String(bytes, pos, len, StandardCharsets.UTF_8);
170 private int decodeInt() {
171 return (bytes[pos++] & 0xFF) << 24 | (bytes[pos++] & 0xFF) << 16
172 | (bytes[pos++] & 0xFF) << 8 | bytes[pos++] & 0xFF;
175 private BigInteger decodeBigInt() {
176 int len = decodeInt();
177 byte[] bigIntBytes = new byte[len];
178 System.arraycopy(bytes, pos, bigIntBytes, 0, len);
180 return new BigInteger(bigIntBytes);
183 private static void encodeBigInt(final DataOutput out, final BigInteger value) throws IOException {
184 final var bytes = value.toByteArray();
185 out.writeInt(bytes.length);