Fix findbugs violations in netconf
[netconf.git] / netconf / callhome-protocol / src / main / java / org / opendaylight / netconf / callhome / protocol / 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.protocol;
9
10 import java.io.ByteArrayOutputStream;
11 import java.io.DataOutputStream;
12 import java.io.IOException;
13 import java.math.BigInteger;
14 import java.nio.charset.StandardCharsets;
15 import java.security.GeneralSecurityException;
16 import java.security.KeyFactory;
17 import java.security.PublicKey;
18 import java.security.interfaces.DSAParams;
19 import java.security.interfaces.DSAPublicKey;
20 import java.security.interfaces.RSAPublicKey;
21 import java.security.spec.DSAPublicKeySpec;
22 import java.security.spec.ECPoint;
23 import java.security.spec.ECPublicKeySpec;
24 import java.security.spec.RSAPublicKeySpec;
25 import java.util.Arrays;
26 import java.util.Base64;
27 import java.util.HashMap;
28 import java.util.Map;
29 import org.apache.sshd.common.util.security.SecurityUtils;
30 import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
31 import org.bouncycastle.jce.ECNamedCurveTable;
32 import org.bouncycastle.jce.ECPointUtil;
33 import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
34 import org.bouncycastle.jce.spec.ECNamedCurveSpec;
35
36 /**
37  * FIXME: This should be probably located at AAA library.
38  */
39 public class AuthorizedKeysDecoder {
40
41     private static final String KEY_FACTORY_TYPE_RSA = "RSA";
42     private static final String KEY_FACTORY_TYPE_DSA = "DSA";
43     private static final String KEY_FACTORY_TYPE_ECDSA = "EC";
44
45     private static final Map<String, String> ECDSA_CURVES = new HashMap<>();
46
47     static {
48         ECDSA_CURVES.put("nistp256", "secp256r1");
49         ECDSA_CURVES.put("nistp384", "secp384r1");
50         ECDSA_CURVES.put("nistp512", "secp512r1");
51     }
52
53     private static final String ECDSA_SUPPORTED_CURVE_NAME = "nistp256";
54     private static final String ECDSA_SUPPORTED_CURVE_NAME_SPEC = ECDSA_CURVES.get(ECDSA_SUPPORTED_CURVE_NAME);
55
56     private static final String KEY_TYPE_RSA = "ssh-rsa";
57     private static final String KEY_TYPE_DSA = "ssh-dss";
58     private static final String KEY_TYPE_ECDSA = "ecdsa-sha2-" + ECDSA_SUPPORTED_CURVE_NAME;
59
60     private byte[] bytes = new byte[0];
61     private int pos = 0;
62
63     public PublicKey decodePublicKey(final String keyLine) throws GeneralSecurityException {
64
65         // look for the Base64 encoded part of the line to decode
66         // both ssh-rsa and ssh-dss begin with "AAAA" due to the length bytes
67         bytes = Base64.getDecoder().decode(keyLine.getBytes(StandardCharsets.UTF_8));
68         if (bytes.length == 0) {
69             throw new IllegalArgumentException("No Base64 part to decode in " + keyLine);
70         }
71
72         pos = 0;
73
74         String type = decodeType();
75         if (type.equals(KEY_TYPE_RSA)) {
76             return decodeAsRSA();
77         }
78
79         if (type.equals(KEY_TYPE_DSA)) {
80             return decodeAsDSA();
81         }
82
83         if (type.equals(KEY_TYPE_ECDSA)) {
84             return decodeAsEcDSA();
85         }
86
87         throw new IllegalArgumentException("Unknown decode key type " + type + " in " + keyLine);
88     }
89
90     private PublicKey decodeAsEcDSA() throws GeneralSecurityException {
91         KeyFactory ecdsaFactory = SecurityUtils.getKeyFactory(KEY_FACTORY_TYPE_ECDSA);
92
93         ECNamedCurveParameterSpec spec256r1 = ECNamedCurveTable.getParameterSpec(ECDSA_SUPPORTED_CURVE_NAME_SPEC);
94         ECNamedCurveSpec params256r1 = new ECNamedCurveSpec(
95             ECDSA_SUPPORTED_CURVE_NAME_SPEC, spec256r1.getCurve(), spec256r1.getG(), spec256r1.getN());
96         // copy last 65 bytes from ssh key.
97         ECPoint point = ECPointUtil.decodePoint(params256r1.getCurve(), Arrays.copyOfRange(bytes, 39, bytes.length));
98         ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params256r1);
99
100         return ecdsaFactory.generatePublic(pubKeySpec);
101     }
102
103     private PublicKey decodeAsDSA() throws GeneralSecurityException {
104         KeyFactory dsaFactory = SecurityUtils.getKeyFactory(KEY_FACTORY_TYPE_DSA);
105         BigInteger prime = decodeBigInt();
106         BigInteger subPrime = decodeBigInt();
107         BigInteger base = decodeBigInt();
108         BigInteger publicKey = decodeBigInt();
109         DSAPublicKeySpec spec = new DSAPublicKeySpec(publicKey, prime, subPrime, base);
110
111         return dsaFactory.generatePublic(spec);
112     }
113
114     private PublicKey decodeAsRSA() throws GeneralSecurityException {
115         KeyFactory rsaFactory = SecurityUtils.getKeyFactory(KEY_FACTORY_TYPE_RSA);
116         BigInteger exponent = decodeBigInt();
117         BigInteger modulus = decodeBigInt();
118         RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
119
120         return rsaFactory.generatePublic(spec);
121     }
122
123     private String decodeType() {
124         int len = decodeInt();
125         String type = new String(bytes, pos, len, StandardCharsets.UTF_8);
126         pos += len;
127         return type;
128     }
129
130     private int decodeInt() {
131         return (bytes[pos++] & 0xFF) << 24 | (bytes[pos++] & 0xFF) << 16
132                 | (bytes[pos++] & 0xFF) << 8 | bytes[pos++] & 0xFF;
133     }
134
135     private BigInteger decodeBigInt() {
136         int len = decodeInt();
137         byte[] bigIntBytes = new byte[len];
138         System.arraycopy(bytes, pos, bigIntBytes, 0, len);
139         pos += len;
140         return new BigInteger(bigIntBytes);
141     }
142
143     public static String encodePublicKey(final PublicKey publicKey) throws IOException {
144         ByteArrayOutputStream byteOs = new ByteArrayOutputStream();
145         if (publicKey.getAlgorithm().equals(KEY_FACTORY_TYPE_RSA) && publicKey instanceof RSAPublicKey) {
146             RSAPublicKey rsaPublicKey = (RSAPublicKey) publicKey;
147             DataOutputStream dout = new DataOutputStream(byteOs);
148             dout.writeInt(KEY_TYPE_RSA.getBytes(StandardCharsets.UTF_8).length);
149             dout.write(KEY_TYPE_RSA.getBytes(StandardCharsets.UTF_8));
150             dout.writeInt(rsaPublicKey.getPublicExponent().toByteArray().length);
151             dout.write(rsaPublicKey.getPublicExponent().toByteArray());
152             dout.writeInt(rsaPublicKey.getModulus().toByteArray().length);
153             dout.write(rsaPublicKey.getModulus().toByteArray());
154         } else if (publicKey.getAlgorithm().equals(KEY_FACTORY_TYPE_DSA) && publicKey instanceof DSAPublicKey) {
155             DSAPublicKey dsaPublicKey = (DSAPublicKey) publicKey;
156             DSAParams dsaParams = dsaPublicKey.getParams();
157             DataOutputStream dout = new DataOutputStream(byteOs);
158             dout.writeInt(KEY_TYPE_DSA.getBytes(StandardCharsets.UTF_8).length);
159             dout.write(KEY_TYPE_DSA.getBytes(StandardCharsets.UTF_8));
160             dout.writeInt(dsaParams.getP().toByteArray().length);
161             dout.write(dsaParams.getP().toByteArray());
162             dout.writeInt(dsaParams.getQ().toByteArray().length);
163             dout.write(dsaParams.getQ().toByteArray());
164             dout.writeInt(dsaParams.getG().toByteArray().length);
165             dout.write(dsaParams.getG().toByteArray());
166             dout.writeInt(dsaPublicKey.getY().toByteArray().length);
167             dout.write(dsaPublicKey.getY().toByteArray());
168         } else if (publicKey.getAlgorithm().equals(KEY_FACTORY_TYPE_ECDSA) && publicKey instanceof BCECPublicKey) {
169             BCECPublicKey ecPublicKey = (BCECPublicKey) publicKey;
170             DataOutputStream dout = new DataOutputStream(byteOs);
171             dout.writeInt(KEY_TYPE_ECDSA.getBytes(StandardCharsets.UTF_8).length);
172             dout.write(KEY_TYPE_ECDSA.getBytes(StandardCharsets.UTF_8));
173             dout.writeInt(ECDSA_SUPPORTED_CURVE_NAME.getBytes(StandardCharsets.UTF_8).length);
174             dout.write(ECDSA_SUPPORTED_CURVE_NAME.getBytes(StandardCharsets.UTF_8));
175
176             byte[] coordX = ecPublicKey.getQ().getAffineXCoord().getEncoded();
177             byte[] coordY = ecPublicKey.getQ().getAffineYCoord().getEncoded();
178             dout.writeInt(coordX.length + coordY.length + 1);
179             dout.writeByte(0x04);
180             dout.write(coordX);
181             dout.write(coordY);
182         } else {
183             throw new IllegalArgumentException("Unknown public key encoding: " + publicKey.getAlgorithm());
184         }
185         return Base64.getEncoder().encodeToString(byteOs.toByteArray());
186     }
187 }