package org.primftpd.pojo; import org.bouncycastle.jce.ECNamedCurveTable; import org.bouncycastle.jce.provider.JCEECPublicKey; import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec; import org.bouncycastle.jce.spec.ECPublicKeySpec; import org.bouncycastle.math.ec.ECPoint; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigInteger; import java.nio.ByteBuffer; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PublicKey; import java.security.spec.DSAPublicKeySpec; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAPublicKeySpec; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; public class KeyParser { // http://blog.oddbit.com/2011/05/08/converting-openssh-public-keys/ public static final String NAME_RSA = "ssh-rsa"; public static final String NAME_DSA = "ssh-dss"; public static final String NAME_ECDSA_256 = "ecdsa-sha2-nistp256"; public static final String NAME_ECDSA_384 = "ecdsa-sha2-nistp384"; public static final String NAME_ECDSA_521 = "ecdsa-sha2-nistp521"; public static final int LENGTH_LENGTH = 4; public static final Map<String, Integer> EC_NAME_TO_COORD_SIZE; public static final Map<String, String> EC_NAME_TO_CURVE_NAME; static { Map<String, Integer> tmpCoordSize = new HashMap<>(); tmpCoordSize.put(NAME_ECDSA_256, Integer.valueOf(32)); tmpCoordSize.put(NAME_ECDSA_384, Integer.valueOf(48)); tmpCoordSize.put(NAME_ECDSA_521, Integer.valueOf(66)); EC_NAME_TO_COORD_SIZE = Collections.unmodifiableMap(tmpCoordSize); // see org.bouncycastle.asn1.nist.NISTNamedCurves Map<String, String> tmpCurveName = new HashMap<>(); tmpCurveName.put(NAME_ECDSA_256, "P-256"); tmpCurveName.put(NAME_ECDSA_384, "P-384"); tmpCurveName.put(NAME_ECDSA_521, "P-521"); EC_NAME_TO_CURVE_NAME = Collections.unmodifiableMap(tmpCurveName); } public static List<PublicKey> parsePublicKeys(InputStream is, Base64Decoder base64Decoder) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException, NoSuchProviderException { if (is == null) { throw new IllegalArgumentException("input stream cannot be null"); } List<PublicKey> keys = new ArrayList<>(); BufferedReader reader = new BufferedReader(new InputStreamReader(is)); while (reader.ready()) { String keyLine = reader.readLine(); String[] parts = keyLine.split(" "); String name = null; String keyEncoded = null; if (parts.length > 2 && parts.length <= 3) { name = parts[0]; keyEncoded = parts[1]; } if (keyEncoded != null) { byte[] keyBytes = base64Decoder.decode(keyEncoded); PublicKey key = null; if (NAME_RSA.equals(name)) { key = parsePublicKeyRsa(keyBytes); } else if (NAME_DSA.equals(name)) { key = parsePublicKeyDsa(keyBytes); } else if (NAME_ECDSA_256.equals(name)) { key = parsePublicKeyEcdsa(name, keyBytes); } else if (NAME_ECDSA_384.equals(name)) { key = parsePublicKeyEcdsa(name, keyBytes); } else if (NAME_ECDSA_521.equals(name)) { key = parsePublicKeyEcdsa(name, keyBytes); } if (key != null) { keys.add(key); } } } return keys; } protected static PublicKey parsePublicKeyRsa(byte[] keyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException { // name is also included in bytes ByteBuffer byteBuffer = ByteBuffer.wrap(keyBytes); int nameLength = byteBuffer.getInt(); byteBuffer.position(nameLength + LENGTH_LENGTH); BigInteger exponent = readNext(byteBuffer); BigInteger modulus = readNext(byteBuffer); return createPubKeyRsa(exponent, modulus); } protected static PublicKey parsePublicKeyDsa(byte[] keyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException { // name is also included in bytes ByteBuffer byteBuffer = ByteBuffer.wrap(keyBytes); int nameLength = byteBuffer.getInt(); byteBuffer.position(nameLength + LENGTH_LENGTH); BigInteger p = readNext(byteBuffer); BigInteger q = readNext(byteBuffer); BigInteger g = readNext(byteBuffer); BigInteger y = readNext(byteBuffer); return createPubKeyDsa(y, p, q, g); } protected static BigInteger readNext(ByteBuffer byteBuffer) { int nextLength = byteBuffer.getInt(); byte[] nextBytes = new byte[nextLength]; byteBuffer.get(nextBytes); return new BigInteger(nextBytes); } protected static PublicKey createPubKeyRsa(BigInteger exponent, BigInteger modulus) throws NoSuchAlgorithmException, InvalidKeySpecException { RSAPublicKeySpec keySpec = new RSAPublicKeySpec(modulus, exponent); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePublic(keySpec); } protected static PublicKey createPubKeyDsa( BigInteger y, BigInteger p, BigInteger q, BigInteger g) throws NoSuchAlgorithmException, InvalidKeySpecException { DSAPublicKeySpec keySpec = new DSAPublicKeySpec(y, p, q, g); KeyFactory kf = KeyFactory.getInstance("DSA"); return kf.generatePublic(keySpec); } protected static PublicKey parsePublicKeyEcdsa(String name, byte[] keyBytes) throws InvalidKeySpecException, NoSuchAlgorithmException, IOException, NoSuchProviderException { ByteBuffer byteBuffer = ByteBuffer.wrap(keyBytes); // https://security.stackexchange.com/questions/129910/ecdsa-why-do-ssh-keygen-and-java-generated-public-keys-have-different-sizes final int coordLength = EC_NAME_TO_COORD_SIZE.get(name); byteBuffer.position(keyBytes.length - 2*coordLength); byte[] xBytes = new byte[coordLength]; byteBuffer.get(xBytes); BigInteger x = new BigInteger(1, xBytes); byteBuffer.position(keyBytes.length - coordLength); byte[] yBytes = new byte[coordLength]; byteBuffer.get(yBytes); BigInteger y = new BigInteger(1, yBytes); return createPubKeyEcdsa(name, x, y); } public static PublicKey createPubKeyEcdsa(String name, BigInteger x, BigInteger y) throws NoSuchAlgorithmException, InvalidKeySpecException { final String curveName = EC_NAME_TO_CURVE_NAME.get(name); ECNamedCurveParameterSpec curveParaSpecBc = ECNamedCurveTable.getParameterSpec(curveName); ECPoint point = curveParaSpecBc.getCurve().createPoint(x, y); ECPublicKeySpec pubKeySpec = new ECPublicKeySpec(point, curveParaSpecBc); return new JCEECPublicKey("EC", pubKeySpec); } }