2 * Copyright (c) 2017 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.aaa.encrypt;
10 import java.io.ByteArrayOutputStream;
11 import java.io.DataOutputStream;
12 import java.io.FileInputStream;
13 import java.io.IOException;
14 import java.io.InputStreamReader;
15 import java.io.Reader;
16 import java.io.StringReader;
17 import java.math.BigInteger;
18 import java.nio.charset.StandardCharsets;
19 import java.security.GeneralSecurityException;
20 import java.security.KeyFactory;
21 import java.security.KeyPair;
22 import java.security.NoSuchAlgorithmException;
23 import java.security.Provider;
24 import java.security.PublicKey;
25 import java.security.Security;
26 import java.security.interfaces.DSAParams;
27 import java.security.interfaces.DSAPublicKey;
28 import java.security.interfaces.RSAPublicKey;
29 import java.security.spec.DSAPublicKeySpec;
30 import java.security.spec.ECPoint;
31 import java.security.spec.ECPublicKeySpec;
32 import java.security.spec.RSAPublicKeySpec;
33 import java.util.Arrays;
34 import java.util.Base64;
35 import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
36 import org.bouncycastle.jce.ECNamedCurveTable;
37 import org.bouncycastle.jce.ECPointUtil;
38 import org.bouncycastle.jce.provider.BouncyCastleProvider;
39 import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
40 import org.bouncycastle.jce.spec.ECNamedCurveSpec;
41 import org.bouncycastle.openssl.PEMDecryptorProvider;
42 import org.bouncycastle.openssl.PEMEncryptedKeyPair;
43 import org.bouncycastle.openssl.PEMKeyPair;
44 import org.bouncycastle.openssl.PEMParser;
45 import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
46 import org.bouncycastle.openssl.jcajce.JcePEMDecryptorProviderBuilder;
49 * PKI related utilities.
51 public class PKIUtil {
53 private interface KeyFactorySupplier {
54 KeyFactory get() throws NoSuchAlgorithmException;
57 private static final Provider BCPROV;
60 final Provider prov = Security.getProvider(BouncyCastleProvider.PROVIDER_NAME);
61 BCPROV = prov != null ? prov : new BouncyCastleProvider();
64 private static final String KEY_FACTORY_TYPE_RSA = "RSA";
65 private static final String KEY_FACTORY_TYPE_DSA = "DSA";
66 private static final String KEY_FACTORY_TYPE_ECDSA = "EC";
68 private static final KeyFactorySupplier RSA_KEY_FACTORY_SUPPLIER = resolveKeyFactory(KEY_FACTORY_TYPE_RSA);
69 private static final KeyFactorySupplier DSA_KEY_FACTORY_SUPPLIER = resolveKeyFactory(KEY_FACTORY_TYPE_DSA);
70 private static final KeyFactorySupplier ECDSA_KEY_FACTORY_SUPPLIER = resolveKeyFactory(KEY_FACTORY_TYPE_ECDSA);
72 private static KeyFactorySupplier resolveKeyFactory(final String algorithm) {
73 final KeyFactory factory;
75 factory = KeyFactory.getInstance(algorithm);
76 } catch (NoSuchAlgorithmException e) {
84 private static final String ECDSA_SUPPORTED_CURVE_NAME = "nistp256";
85 private static final String ECDSA_SUPPORTED_CURVE_NAME_SPEC = "secp256r1";
86 private static final int ECDSA_THIRD_STR_LEN = 65;
87 private static final int ECDSA_TOTAL_STR_LEN = 104;
89 private static final String KEY_TYPE_RSA = "ssh-rsa";
90 private static final String KEY_TYPE_DSA = "ssh-dss";
91 private static final String KEY_TYPE_ECDSA = "ecdsa-sha2-" + ECDSA_SUPPORTED_CURVE_NAME;
93 private byte[] bytes = new byte[0];
96 public PublicKey decodePublicKey(final String keyLine) throws GeneralSecurityException {
98 // look for the Base64 encoded part of the line to decode
99 // both ssh-rsa and ssh-dss begin with "AAAA" due to the length bytes
100 bytes = Base64.getDecoder().decode(keyLine.getBytes(StandardCharsets.UTF_8));
101 if (bytes.length == 0) {
102 throw new IllegalArgumentException("No Base64 part to decode in " + keyLine);
106 String type = decodeType();
107 return switch (type) {
108 case KEY_TYPE_RSA -> decodeAsRSA();
109 case KEY_TYPE_DSA -> decodeAsDSA();
110 case KEY_TYPE_ECDSA -> decodeAsECDSA();
111 default -> throw new IllegalArgumentException("Unknown decode key type " + type + " in " + keyLine);
115 @SuppressWarnings("AbbreviationAsWordInName")
116 private PublicKey decodeAsECDSA() throws GeneralSecurityException {
117 KeyFactory ecdsaFactory = ECDSA_KEY_FACTORY_SUPPLIER.get();
119 ECNamedCurveParameterSpec spec256r1 = ECNamedCurveTable.getParameterSpec(ECDSA_SUPPORTED_CURVE_NAME_SPEC);
120 ECNamedCurveSpec params256r1 = new ECNamedCurveSpec(ECDSA_SUPPORTED_CURVE_NAME_SPEC, spec256r1.getCurve(),
121 spec256r1.getG(), spec256r1.getN());
122 // The total length is 104 bytes, and the X and Y encoding uses the last 65 of these 104 bytes.
123 ECPoint point = ECPointUtil.decodePoint(params256r1.getCurve(),
124 Arrays.copyOfRange(bytes, ECDSA_TOTAL_STR_LEN - ECDSA_THIRD_STR_LEN, ECDSA_TOTAL_STR_LEN));
125 ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, params256r1);
127 return ecdsaFactory.generatePublic(pubKeySpec);
130 private PublicKey decodeAsDSA() throws GeneralSecurityException {
131 KeyFactory dsaFactory = DSA_KEY_FACTORY_SUPPLIER.get();
132 BigInteger var1 = decodeBigInt();
133 BigInteger var2 = decodeBigInt();
134 BigInteger var3 = decodeBigInt();
135 BigInteger var4 = decodeBigInt();
136 DSAPublicKeySpec spec = new DSAPublicKeySpec(var4, var1, var2, var3);
138 return dsaFactory.generatePublic(spec);
141 private PublicKey decodeAsRSA() throws GeneralSecurityException {
142 KeyFactory rsaFactory = RSA_KEY_FACTORY_SUPPLIER.get();
143 BigInteger exponent = decodeBigInt();
144 BigInteger modulus = decodeBigInt();
145 RSAPublicKeySpec spec = new RSAPublicKeySpec(modulus, exponent);
147 return rsaFactory.generatePublic(spec);
150 private String decodeType() {
151 int len = decodeInt();
152 String type = new String(bytes, pos, len, StandardCharsets.UTF_8);
157 private int decodeInt() {
158 return (bytes[pos++] & 0xFF) << 24 | (bytes[pos++] & 0xFF) << 16 | (bytes[pos++] & 0xFF) << 8
159 | bytes[pos++] & 0xFF;
162 private BigInteger decodeBigInt() {
163 int len = decodeInt();
164 byte[] bigIntBytes = new byte[len];
165 System.arraycopy(bytes, pos, bigIntBytes, 0, len);
167 return new BigInteger(bigIntBytes);
170 public String encodePublicKey(final PublicKey publicKey) throws IOException {
171 ByteArrayOutputStream byteOs = new ByteArrayOutputStream();
172 if (publicKey instanceof RSAPublicKey rsaPublicKey
173 && rsaPublicKey.getAlgorithm().equals(KEY_FACTORY_TYPE_RSA)) {
174 DataOutputStream dataOutputStream = new DataOutputStream(byteOs);
175 dataOutputStream.writeInt(KEY_TYPE_RSA.getBytes(StandardCharsets.UTF_8).length);
176 dataOutputStream.write(KEY_TYPE_RSA.getBytes(StandardCharsets.UTF_8));
177 dataOutputStream.writeInt(rsaPublicKey.getPublicExponent().toByteArray().length);
178 dataOutputStream.write(rsaPublicKey.getPublicExponent().toByteArray());
179 dataOutputStream.writeInt(rsaPublicKey.getModulus().toByteArray().length);
180 dataOutputStream.write(rsaPublicKey.getModulus().toByteArray());
181 } else if (publicKey instanceof DSAPublicKey dsaPublicKey
182 && dsaPublicKey.getAlgorithm().equals(KEY_FACTORY_TYPE_DSA)) {
183 DSAParams dsaParams = dsaPublicKey.getParams();
184 DataOutputStream dataOutputStream = new DataOutputStream(byteOs);
185 dataOutputStream.writeInt(KEY_TYPE_DSA.getBytes(StandardCharsets.UTF_8).length);
186 dataOutputStream.write(KEY_TYPE_DSA.getBytes(StandardCharsets.UTF_8));
187 dataOutputStream.writeInt(dsaParams.getP().toByteArray().length);
188 dataOutputStream.write(dsaParams.getP().toByteArray());
189 dataOutputStream.writeInt(dsaParams.getQ().toByteArray().length);
190 dataOutputStream.write(dsaParams.getQ().toByteArray());
191 dataOutputStream.writeInt(dsaParams.getG().toByteArray().length);
192 dataOutputStream.write(dsaParams.getG().toByteArray());
193 dataOutputStream.writeInt(dsaPublicKey.getY().toByteArray().length);
194 dataOutputStream.write(dsaPublicKey.getY().toByteArray());
195 } else if (publicKey instanceof BCECPublicKey ecPublicKey
196 && ecPublicKey.getAlgorithm().equals(KEY_FACTORY_TYPE_ECDSA)) {
197 DataOutputStream dataOutputStream = new DataOutputStream(byteOs);
198 dataOutputStream.writeInt(KEY_TYPE_ECDSA.getBytes(StandardCharsets.UTF_8).length);
199 dataOutputStream.write(KEY_TYPE_ECDSA.getBytes(StandardCharsets.UTF_8));
200 dataOutputStream.writeInt(ECDSA_SUPPORTED_CURVE_NAME.getBytes(StandardCharsets.UTF_8).length);
201 dataOutputStream.write(ECDSA_SUPPORTED_CURVE_NAME.getBytes(StandardCharsets.UTF_8));
202 byte[] affineXCoord = ecPublicKey.getQ().getAffineXCoord().getEncoded();
203 byte[] affineYCoord = ecPublicKey.getQ().getAffineYCoord().getEncoded();
204 dataOutputStream.writeInt(affineXCoord.length + affineYCoord.length + 1);
205 dataOutputStream.writeByte(0x04);
206 dataOutputStream.write(affineXCoord);
207 dataOutputStream.write(affineYCoord);
209 throw new IllegalArgumentException("Unknown public key encoding: " + publicKey.getAlgorithm());
212 return Base64.getEncoder().encodeToString(byteOs.toByteArray());
216 public KeyPair decodePrivateKey(final StringReader reader, final String passphrase) throws IOException {
217 return doDecodePrivateKey(reader, passphrase);
220 public KeyPair decodePrivateKey(final String keyPath, final String passphrase) throws IOException {
221 try (Reader reader = new InputStreamReader(new FileInputStream(keyPath), StandardCharsets.UTF_8)) {
222 return doDecodePrivateKey(reader, passphrase);
226 private static KeyPair doDecodePrivateKey(final Reader reader, final String passphrase) throws IOException {
227 try (PEMParser keyReader = new PEMParser(reader)) {
228 JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
229 PEMDecryptorProvider decryptionProv = new JcePEMDecryptorProviderBuilder().setProvider(BCPROV)
230 .build(passphrase.toCharArray());
232 Object privateKey = keyReader.readObject();
234 if (privateKey instanceof PEMEncryptedKeyPair pemPrivateKey) {
235 keyPair = converter.getKeyPair(pemPrivateKey.decryptKeyPair(decryptionProv));
237 keyPair = converter.getKeyPair((PEMKeyPair) privateKey);