package org.xbib.net.security;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.EdECPrivateKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.DSAPublicKeySpec;
import java.security.spec.ECField;
import java.security.spec.ECFieldFp;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.ECPrivateKeySpec;
import java.security.spec.ECPublicKeySpec;
import java.security.spec.EdECPoint;
import java.security.spec.EdECPrivateKeySpec;
import java.security.spec.EdECPublicKeySpec;
import java.security.spec.EllipticCurve;
import java.security.spec.EncodedKeySpec;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.NamedParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAPrivateCrtKeySpec;
import java.security.spec.RSAPrivateKeySpec;
import java.security.spec.RSAPublicKeySpec;
import java.util.Arrays;
import java.util.Base64;
import java.util.Objects;
import javax.crypto.Cipher;
import javax.crypto.EncryptedPrivateKeyInfo;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import org.xbib.net.security.util.Asn1Object;
import org.xbib.net.security.util.DerParser;
import org.xbib.net.security.util.DerUtils;

/**
 * Best effort key reader for reading private keys from inputstreams,
 * PEM formatted text etc. with PKCS#8 or PKCS#1 encodings.
 * It doesn't support encrypted PEM files.
 */
public class PrivateKeyReader {

    public PrivateKeyReader() {
    }

    public KeySpec readPrivateKeySpec(InputStream inputStream,
                                      String password)
            throws IOException, NoSuchAlgorithmException, InvalidKeySpecException,
            InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException {
        RawKey rawKey = readKeyBytes(inputStream);
        return switch (rawKey.keyType) {
            case PKCS8 -> getPkcs8KeySpec(rawKey.raw, password != null ? password.toCharArray() : null);
            case RSA -> getRSAKeySpec(rawKey.raw);
            case DSA -> getDSAKeySpec(rawKey.raw);
            case EC -> getECKeySpec(rawKey.raw);
            case ED25519 -> getEdECKeySpec(rawKey.raw);
            case UNKNOWN -> throw new InvalidKeySpecException();
        };
    }

    public PrivateKey readPrivateKey(InputStream inputStream,
                                     String password)
            throws IOException, NoSuchAlgorithmException, InvalidKeySpecException,
            InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException {
        KeySpec keySpec = readPrivateKeySpec(inputStream, password);
        if (keySpec instanceof EncodedKeySpec) {
            return KeyFactory.getInstance("RSA").generatePrivate(keySpec);
        } else if (keySpec instanceof RSAPrivateCrtKeySpec) {
            return KeyFactory.getInstance("RSA").generatePrivate(keySpec);
        } else if (keySpec instanceof ECPrivateKeySpec) {
            return KeyFactory.getInstance("EC").generatePrivate(keySpec);
        } else if (keySpec instanceof EdECPrivateKeySpec) {
            return KeyFactory.getInstance("EdDSA").generatePrivate(keySpec);
        } else if (keySpec instanceof DSAPrivateKeySpec) {
            return KeyFactory.getInstance("DSA").generatePrivate(keySpec);
        }
        throw new IOException("invalid PEM");
    }

    public KeyPair readKeyPair(InputStream inputStream,
                               String password)
            throws IOException, NoSuchAlgorithmException, InvalidKeySpecException,
            InvalidAlgorithmParameterException, NoSuchPaddingException, InvalidKeyException {
        RawKey rawKey = readKeyBytes(inputStream);
        KeySpec keySpec = switch (rawKey.keyType) {
            case PKCS8 -> getPkcs8KeySpec(rawKey.raw, password != null ? password.toCharArray() : null);
            case RSA -> getRSAKeySpec(rawKey.raw);
            case DSA -> getDSAKeySpec(rawKey.raw);
            case EC -> getECKeySpec(rawKey.raw);
            case ED25519 -> getEdECKeySpec(rawKey.raw);
            case UNKNOWN -> throw new InvalidKeySpecException();
        };
        PrivateKey privateKey = null;
        PublicKey publicKey = null;
        switch (keySpec) {
            case DSAPrivateKeySpec dsaPrivateKeySpec -> {
                KeyFactory keyFactory = KeyFactory.getInstance("DSA");
                privateKey = keyFactory.generatePrivate(keySpec);
                publicKey = keyFactory.generatePublic(getDSAPublicKeySpec(privateKey));
            }
            case EncodedKeySpec encodedKeySpec -> {
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                privateKey = keyFactory.generatePrivate(keySpec);
                publicKey = keyFactory.generatePublic(getRSAPublicKeySpec(keyFactory, privateKey));
            }
            case RSAPrivateCrtKeySpec rsaPrivateCrtKeySpec -> {
                KeyFactory keyFactory = KeyFactory.getInstance("RSA");
                privateKey = keyFactory.generatePrivate(keySpec);
                publicKey = keyFactory.generatePublic(getRSAPublicKeySpec(keyFactory, privateKey));
            }
            case ECPrivateKeySpec ecPrivateKeySpec -> {
                KeyFactory keyFactory = KeyFactory.getInstance("EC");
                privateKey = keyFactory.generatePrivate(keySpec);
                publicKey = keyFactory.generatePublic(getECPublicKeySpec(privateKey));
            }
            case null, default -> {
                KeyFactory keyFactory = KeyFactory.getInstance("EdDSA");
                privateKey = keyFactory.generatePrivate(keySpec);
                publicKey = keyFactory.generatePublic(getEdECPublicKeySpec(privateKey, rawKey.raw));
            }
        }
        if (publicKey != null) {
            return new KeyPair(publicKey, privateKey);
        }
        throw new IOException("invalid PEM");
    }

    private static PKCS8EncodedKeySpec getPkcs8KeySpec(byte[] key, char[] password)
            throws IOException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeySpecException,
            InvalidKeyException, InvalidAlgorithmParameterException {
        if (password == null) {
            return new PKCS8EncodedKeySpec(key);
        }
        EncryptedPrivateKeyInfo encryptedPrivateKeyInfo = new EncryptedPrivateKeyInfo(key);
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance(encryptedPrivateKeyInfo.getAlgName());
        PBEKeySpec pbeKeySpec = new PBEKeySpec(password);
        SecretKey pbeKey = keyFactory.generateSecret(pbeKeySpec);
        Cipher cipher = Cipher.getInstance(encryptedPrivateKeyInfo.getAlgName());
        cipher.init(Cipher.DECRYPT_MODE, pbeKey, encryptedPrivateKeyInfo.getAlgParameters());
        return encryptedPrivateKeyInfo.getKeySpec(cipher);
    }

    /**
     * Convert PKCS#1 encoded private key into RSAPrivateCrtKeySpec.
     * The ASN.1 syntax for the private key with CRT is
     * <pre>
     * --
     * -- Representation of RSA private key with information for the CRT algorithm.
     * --
     * RSAPrivateKey ::= SEQUENCE {
     *   version           Version,
     *   modulus           INTEGER,  -- n
     *   publicExponent    INTEGER,  -- e
     *   privateExponent   INTEGER,  -- d
     *   prime1            INTEGER,  -- p
     *   prime2            INTEGER,  -- q
     *   exponent1         INTEGER,  -- d mod (p-1)
     *   exponent2         INTEGER,  -- d mod (q-1)
     *   coefficient       INTEGER,  -- (inverse of q) mod p
     *   otherPrimeInfos   OtherPrimeInfos OPTIONAL
     * }
     * </pre>
     *
     * @param keyBytes PKCS#1 encoded key
     * @return KeySpec
     * @throws IOException if failure
     */
    private static RSAPrivateCrtKeySpec getRSAKeySpec(byte[] keyBytes) throws IOException {
        DerParser parser = new DerParser(keyBytes);
        Asn1Object sequence = parser.read();
        if (sequence.getType() != DerParser.SEQUENCE) {
            throw new IOException("invalid DER: not a sequence");
        }
        parser = sequence.getParser();
        parser.read(); // skip version
        BigInteger modulus = parser.read().getInteger();
        BigInteger publicExp = parser.read().getInteger();
        BigInteger privateExp = parser.read().getInteger();
        BigInteger prime1 = parser.read().getInteger();
        BigInteger prime2 = parser.read().getInteger();
        BigInteger exp1 = parser.read().getInteger();
        BigInteger exp2 = parser.read().getInteger();
        BigInteger crtCoef = parser.read().getInteger();
        return new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, prime1, prime2, exp1, exp2, crtCoef);
    }

    private static RSAPublicKeySpec getRSAPublicKeySpec(KeyFactory keyFactory, PrivateKey key)
            throws InvalidKeyException, InvalidKeySpecException {
        if (!(key instanceof RSAPrivateKey)) {
            throw new InvalidKeyException("Private key is not RSA private key");
        }
        RSAPrivateKeySpec rsaPrivateKeySpec = keyFactory.getKeySpec(key, RSAPrivateKeySpec.class);
        return new RSAPublicKeySpec(rsaPrivateKeySpec.getModulus(), BigInteger.valueOf(65537));
    }

    /**
     * Read DSA key in PKCS#1 spec.
     *
     * @param keyBytes PKCS#1 encoded key
     * @return DSA private key spec
     * @throws IOException if ASN.1 parsing fails
     */
    private DSAPrivateKeySpec getDSAKeySpec(byte[] keyBytes) throws IOException {
        DerParser parser = new DerParser(keyBytes);
        Asn1Object sequence = parser.read();
        if (sequence.getType() != DerParser.SEQUENCE) {
            throw new IOException("invalid DER: not a sequence");
        }
        parser = sequence.getParser();
        parser.read(); // skip version
        BigInteger p = parser.read().getInteger();
        BigInteger q = parser.read().getInteger();
        BigInteger g = parser.read().getInteger();
        BigInteger pub = parser.read().getInteger();
        BigInteger prv = parser.read().getInteger();
        return new DSAPrivateKeySpec(prv, p, q, g);
    }

    private static DSAPublicKeySpec getDSAPublicKeySpec(PrivateKey key) throws InvalidKeyException {
        if (!(key instanceof DSAPrivateKey dsaPrivateKey)) {
            throw new InvalidKeyException("Private key is not DSA private key");
        }
        DSAParams dsaParams = dsaPrivateKey.getParams();
        BigInteger g = dsaParams.getG();
        BigInteger p = dsaParams.getP();
        BigInteger q = dsaParams.getQ();
        BigInteger y = dsaParams.getG().modPow(dsaPrivateKey.getX(), dsaParams.getP());
        return new DSAPublicKeySpec(y, p, q, g);
    }

    /**
     * Read EC private key in PKCS#1 format.
     *
     * ECPrivateKey ::= SEQUENCE {
     *          version       INTEGER { ecPrivkeyVer1(1) },
     *          privateKey    OCTET STRING,
     *          parameters    [0] EXPLICIT ECDomainParameters OPTIONAL,
     *          publicKey     [1] EXPLICIT BIT STRING OPTIONAL
     *   }
     *
     *  openssl asn1parse -i -in net-security/src/test/resources/ec.key
     *     0:d=0  hl=2 l= 119 cons: SEQUENCE
     *     2:d=1  hl=2 l=   1 prim:  INTEGER           :01
     *     5:d=1  hl=2 l=  32 prim:  OCTET STRING      [HEX DUMP]:7D9A378C22E17F85643D6D8B4EC14931220329FF5D03D20F4E15095BE40890F7
     *    39:d=1  hl=2 l=  10 cons:  cont [ 0 ]
     *    41:d=2  hl=2 l=   8 prim:   OBJECT            :prime256v1
     *    51:d=1  hl=2 l=  68 cons:  cont [ 1 ]
     *    53:d=2  hl=2 l=  66 prim:   BIT STRING
     *
     *  OID 1.2.840.10045.3.1.7 = "prime256v1"
     *
     * @param keyBytes PKCS#1 encoded key
     * @return the EC private key spec
     * @throws IOException if ASN.1 parsing fails
     */
    private ECPrivateKeySpec getECKeySpec(byte[] keyBytes) throws IOException {
        DerParser parser = new DerParser(keyBytes);
        Asn1Object sequence = parser.read();
        if (sequence.getType() != DerParser.SEQUENCE) {
            throw new IOException("invalid DER: not a sequence");
        }
        parser = sequence.getParser();
        parser.read(); // skip version
        byte[] privateKey = parser.read().getValue();
        Asn1Object asn1Object = parser.read();
        if (asn1Object.getType() != DerParser.ANY) {
            throw new IOException("invalid DER: not any: " + asn1Object.getType());
        }
        int[] oid = DerUtils.decodeOID(asn1Object.getValue());
        BigInteger bigInteger = new BigInteger(1, privateKey);
        String oidString = DerUtils.oidToString(oid);
        if (SECP256R1.getObjectId().equals(oidString)) {
            return new ECPrivateKeySpec(bigInteger, SECP256R1);
        } else if (SECP384R1.getObjectId().equals(oidString)) {
            return new ECPrivateKeySpec(bigInteger, SECP384R1);
        } else if (SECP521R1.getObjectId().equals(oidString)) {
            return new ECPrivateKeySpec(bigInteger, SECP521R1);
        } else {
            throw new IOException("invalid DER: unknown algo: " + oidString);
        }
    }

    private static ECPublicKeySpec getECPublicKeySpec(PrivateKey key)
            throws InvalidKeyException {
        if (!(key instanceof ECPrivateKey ecPrivateKey)) {
            throw new InvalidKeyException("Private key is not EC private key");
        }
        ECParameterSpec ecParameterSpec = ecPrivateKey.getParams();
        ECPoint ecPoint = scalarPointMultiplication(ecParameterSpec.getCurve(),
                ecParameterSpec.getOrder(), ecParameterSpec.getGenerator(), ecPrivateKey.getS());
        return new ECPublicKeySpec(ecPoint, ecParameterSpec);
    }

    private static EdECPrivateKeySpec getEdECKeySpec(byte[] keyBytes) throws InvalidKeySpecException {
        OpenSsh openSsh = decodeFromOpenSsh(keyBytes);
        NamedParameterSpec params = NamedParameterSpec.ED25519;
        return new EdECPrivateKeySpec(params, openSsh.privatekey);
    }

    private static EdECPublicKeySpec getEdECPublicKeySpec(PrivateKey key, byte[] keyBytes)
            throws InvalidKeyException, InvalidKeySpecException {
        if (!(key instanceof EdECPrivateKey edECPrivateKey)) {
            throw new InvalidKeyException("Private key is not EdEC private key");
        }
        OpenSsh openSsh = decodeFromOpenSsh(keyBytes);
        byte[] arr = openSsh.publickey;
        byte msb = arr[arr.length - 1];
        boolean xOdd = (msb & 0x80) != 0;
        arr[arr.length - 1] &= (byte) 0x7F;
        reverse(arr);
        BigInteger y = new BigInteger(1, arr);
        EdECPoint edECPoint = new EdECPoint(xOdd, y);
        return new EdECPublicKeySpec(edECPrivateKey.getParams(), edECPoint);
    }

    private static final Curve SECP256R1 = initializeCurve(
            "secp256r1 [NIST P-256, X9.62 prime256v1]",
            "1.2.840.10045.3.1.7",
            "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFF",
            "FFFFFFFF00000001000000000000000000000000FFFFFFFFFFFFFFFFFFFFFFFC",
            "5AC635D8AA3A93E7B3EBBD55769886BC651D06B0CC53B0F63BCE3C3E27D2604B",
            "6B17D1F2E12C4247F8BCE6E563A440F277037D812DEB33A0F4A13945D898C296",
            "4FE342E2FE1A7F9B8EE7EB4A7C0F9E162BCE33576B315ECECBB6406837BF51F5",
            "FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551",
            1
    );

    private static final Curve SECP384R1 = initializeCurve(
            "secp384r1 [NIST P-384]",
            "1.3.132.0.34",
            "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFF",
            "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFFFF0000000000000000FFFFFFFC",
            "B3312FA7E23EE7E4988E056BE3F82D19181D9C6EFE8141120314088F5013875AC656398D8A2ED19D2A85C8EDD3EC2AEF",
            "AA87CA22BE8B05378EB1C71EF320AD746E1D3B628BA79B9859F741E082542A385502F25DBF55296C3A545E3872760AB7",
            "3617DE4A96262C6F5D9E98BF9292DC29F8F41DBD289A147CE9DA3113B5F0B8C00A60B1CE1D7E819D7A431D7C90EA0E5F",
            "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC7634D81F4372DDF581A0DB248B0A77AECEC196ACCC52973",
            1
    );

    private static final Curve SECP521R1 = initializeCurve(
            "secp521r1 [NIST P-521]",
            "1.3.132.0.35",
            "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
            "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC",
            "0051953EB9618E1C9A1F929A21A0B68540EEA2DA725B99B315F3B8B489918EF109E156193951EC7E937B1652C0BD3BB1BF073573DF883D2C34F1EF451FD46B503F00",
            "00C6858E06B70404E9CD9E3ECB662395B4429C648139053FB521F828AF606B4D3DBAA14B5E77EFE75928FE1DC127A2FFA8DE3348B3C1856A429BF97E7E31C2E5BD66",
            "011839296A789A3BC0045C8A5FB42C7D1BD998F54449579B446817AFBD17273E662C97EE72995EF42640C550B9013FAD0761353C7086A272C24088BE94769FD16650",
            "01FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFA51868783BF2F966B7FCC0148F709A5D03BB5C9B8899C47AEBB6FB71E91386409",
            1
    );

    private static Curve initializeCurve(String name, String oid,
                                         String sfield, String a, String b,
                                         String x, String y, String n, int h) {
        BigInteger p = bigInt(sfield);
        ECField field = new ECFieldFp(p);
        EllipticCurve curve = new EllipticCurve(field, bigInt(a),bigInt(b));
        ECPoint g = new ECPoint(bigInt(x), bigInt(y));
        return new Curve(name, oid, curve, g, bigInt(n), h);
    }

    private static final class Curve extends ECParameterSpec {
        private final String name;
        private final String oid;

        private Curve(String name, String oid, EllipticCurve curve, ECPoint g, BigInteger n, int h) {
            super(curve, g, n, h);
            this.name = name;
            this.oid = oid;
        }
        private String getName() {
            return name;
        }
        private String getObjectId() {
            return oid;
        }
    }

    private static BigInteger bigInt(String s) {
        return new BigInteger(s, 16);
    }

    private static void reverse(byte [] arr) {
        int i = 0;
        int j = arr.length - 1;
        while (i < j) {
            swap(arr, i, j);
            i++;
            j--;
        }
    }

    private static void swap(byte[] arr, int i, int j) {
        byte tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }

    private static class RawKey {
        enum KeyType { UNKNOWN, PKCS8, DSA, RSA, EC, ED25519 };
        KeyType keyType;
        byte[] raw;
        RawKey(KeyType keyType, byte[] raw) {
            this.keyType = keyType;
            this.raw = raw;
        }
    }

    private  RawKey readKeyBytes(InputStream inputStream) throws IOException {
        Objects.requireNonNull(inputStream);
        byte[] rawByteArray = inputStream.readAllBytes();
        if (indexOf(rawByteArray, BEGIN_PRIVATE_KEY,0, rawByteArray.length) >= 0) {
            return new RawKey(RawKey.KeyType.PKCS8, decodeFromBase64(rawByteArray, BEGIN_PRIVATE_KEY, END_PRIVATE_KEY));
        } else if (indexOf(rawByteArray, BEGIN_RSA_PRIVATE_KEY,0, rawByteArray.length) >= 0) {
            return new RawKey(RawKey.KeyType.RSA, decodeFromBase64(rawByteArray, BEGIN_RSA_PRIVATE_KEY, END_RSA_PRIVATE_KEY));
        } else if (indexOf(rawByteArray, BEGIN_DSA_PRIVATE_KEY,0, rawByteArray.length) >= 0) {
            return new RawKey(RawKey.KeyType.DSA, decodeFromBase64(rawByteArray, BEGIN_DSA_PRIVATE_KEY, END_DSA_PRIVATE_KEY));
        } else if (indexOf(rawByteArray, BEGIN_EC_PRIVATE_KEY,0, rawByteArray.length) >= 0) {
            return new RawKey(RawKey.KeyType.EC, decodeFromBase64(rawByteArray, BEGIN_EC_PRIVATE_KEY, END_EC_PRIVATE_KEY));
        } else if (indexOf(rawByteArray, BEGIN_OPENSSH_PRIVATE_KEY,0, rawByteArray.length) >= 0) {
            return new RawKey(RawKey.KeyType.ED25519, decodeFromBase64(rawByteArray, BEGIN_OPENSSH_PRIVATE_KEY, END_OPENSSH_PRIVATE_KEY));
        }
        return new RawKey(RawKey.KeyType.UNKNOWN, rawByteArray);
    }

    private static int indexOf(byte[] array, byte[] target, int start, int end) {
        if (target.length == 0) {
            return 0;
        }
        outer:
        for (int i = start; i < end - target.length + 1; i++) {
            for (int j = 0; j < target.length; j++) {
                if (array[i + j] != target[j]) {
                    continue outer;
                }
            }
            return i;
        }
        return -1;
    }

    private static byte[] decodeFromBase64(byte[] array, byte[] b1, byte[] b2) {
        int i1 = indexOf(array, b1, 0, array.length);
        if (i1 < 0) {
            throw new IllegalArgumentException("unable to extract: not found");
        }
        int i2 = indexOf(array, b2, 0, array.length);
        if (i2 < 0) {
            throw new IllegalArgumentException("unable to extract: not found");
        }
        int start = i1 + b1.length;
        byte[] b = new byte[i2 - start];
        System.arraycopy(array, start, b, 0, b.length);
        return Base64.getMimeDecoder().decode(b);
    }

    private static OpenSsh decodeFromOpenSsh(byte[] rawKey) throws InvalidKeySpecException {
        OpenSsh openSsh = new OpenSsh();
        openSsh.verify = Arrays.copyOfRange(rawKey, 0, OPENSSH_KEY_V1.length());
        if (!new String(openSsh.verify).equals(OPENSSH_KEY_V1)) {
            throw new InvalidKeySpecException("invalid OpenSSH key file");
        }
        boolean occurred = false;
        int index = 0;
        for (int i = 0; i < rawKey.length; i++) {
            if (rawKey[i] == 's'
                    && rawKey[i + 1] == 's'
                    && rawKey[i + 2] == 'h'
                    && rawKey[i + 3] == '-'
                    && rawKey[i + 4] == 'e'
                    && rawKey[i + 5] == 'd'
                    && rawKey[i + 6] == '2'
                    && rawKey[i + 7] == '5'
                    && rawKey[i + 8] == '5'
                    && rawKey[i + 9] == '1'
                    && rawKey[i + 10] == '9'
                    && rawKey[i + 11] == 0x00
                    && rawKey[i + 12] == 0x00
                    && rawKey[i + 13] == 0x00
                    && rawKey[i + 14] == ' ') {
                index = i + 15;
                if (occurred) {
                    break;
                }
                occurred = true;
            }
        }
        openSsh.publickey = Arrays.copyOfRange(rawKey, index, index + 32);
        index += 32;
        for (int i = index; i < rawKey.length; i++) {
            if (rawKey[i] == 0x00
                    && rawKey[i + 1] == 0x00
                    && rawKey[i + 2] == 0x00
                    && rawKey[i + 3] == '@') {
                index = i + 4;
                break;
            }
        }
        openSsh.privatekey = Arrays.copyOfRange(rawKey, index, index + 32);
        return openSsh;
    }

    private static final String OPENSSH_KEY_V1 = "openssh-key-v1";

    private static class OpenSsh {
        byte[] verify;
        byte[] publickey;
        byte[] privatekey;
    }

    private static ECPoint scalarPointMultiplication(EllipticCurve curve,
                                                     BigInteger order,
                                                     ECPoint ecPointP,
                                                     BigInteger scalar) {
        return scalarPointMultiplication(curve, ecPointP, scalar.mod(order));
    }

    private static ECPoint scalarPointMultiplication(EllipticCurve curve,
                                                     ECPoint ecPointP,
                                                     BigInteger scalar) {
        if (ecPointP.equals(ECPoint.POINT_INFINITY)) {
            return ecPointP;
        }
        ECPoint ecPointR = ECPoint.POINT_INFINITY;
        for (int i = scalar.bitLength() - 1; i >= 0; i--) {
            ecPointR = doublePoint(curve, ecPointR);
            if (scalar.testBit(i)) {
                ecPointR = addPoint(curve, ecPointR, ecPointP);
            }
        }
        return ecPointR;
    }

    private static ECPoint doublePoint(EllipticCurve curve, ECPoint ecPointP) {
        if (ecPointP.equals(ECPoint.POINT_INFINITY)) {
            return ecPointP;
        }
        BigInteger p = ((ECFieldFp) curve.getField()).getP();
        BigInteger a = curve.getA();
        BigInteger xp = ecPointP.getAffineX();
        BigInteger yp = ecPointP.getAffineY();
        BigInteger lambda = ((((xp.pow(2)).multiply(THREE)).add(a)).multiply((yp.multiply(TWO)).modInverse(p))).mod(p);
        BigInteger xr = computeXr(p, lambda, xp);
        BigInteger yr = computeYr(p, lambda, xp, yp, xr);
        return new ECPoint(xr, yr);
    }

    private static ECPoint addPoint(EllipticCurve curve, ECPoint ecPointQ, ECPoint ecPointP) {
        if (ecPointQ.equals(ecPointP)) {
            return doublePoint(curve, ecPointQ);
        }
        if (ecPointQ.equals(ECPoint.POINT_INFINITY)) {
            return ecPointP;
        }
        if (ecPointP.equals(ECPoint.POINT_INFINITY)) {
            return ecPointQ;
        }
        BigInteger p = ((ECFieldFp) curve.getField()).getP();
        BigInteger xq = ecPointQ.getAffineX();
        BigInteger yq = ecPointQ.getAffineY();
        BigInteger xp = ecPointP.getAffineX();
        BigInteger yp = ecPointP.getAffineY();
        BigInteger lambda = ((yq.subtract(yp)).multiply(xq.subtract(xp).modInverse(p))).mod(p);
        BigInteger xr = computeXr(p, lambda, xp, xq);
        BigInteger yr = computeYr(p, lambda, xp, yp, xr);
        return new ECPoint(xr, yr);
    }

    private static BigInteger computeXr(BigInteger p, BigInteger lambda, BigInteger xp) {
        return computeXr(p, lambda, xp, xp);
    }

    private static BigInteger computeXr(BigInteger p, BigInteger lambda, BigInteger xp, BigInteger xq) {
        return (((lambda.modPow(TWO, p).subtract(xq))).subtract(xp)).mod(p);
    }

    private static BigInteger computeYr(BigInteger p, BigInteger lambda, BigInteger xp, BigInteger yp, BigInteger xr) {
        return ((lambda.multiply(xp.subtract(xr)).mod(p)).subtract(yp)).mod(p);
    }

    private static final BigInteger ONE = BigInteger.ONE;

    private static final BigInteger TWO = ONE.add(ONE);

    private static final BigInteger THREE = TWO.add(ONE);

    private static final byte[] BEGIN_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----".getBytes(StandardCharsets.US_ASCII);

    private static final byte[] END_PRIVATE_KEY = "-----END PRIVATE KEY-----".getBytes(StandardCharsets.US_ASCII);

    private static final byte[] BEGIN_RSA_PRIVATE_KEY = "-----BEGIN RSA PRIVATE KEY-----".getBytes(StandardCharsets.US_ASCII);

    private static final byte[] END_RSA_PRIVATE_KEY = "-----END RSA PRIVATE KEY-----".getBytes(StandardCharsets.US_ASCII);

    private static final byte[] BEGIN_DSA_PRIVATE_KEY = "-----BEGIN DSA PRIVATE KEY-----".getBytes(StandardCharsets.US_ASCII);

    private static final byte[] END_DSA_PRIVATE_KEY = "-----END DSA PRIVATE KEY-----".getBytes(StandardCharsets.US_ASCII);

    private static final byte[] BEGIN_EC_PRIVATE_KEY = "-----BEGIN EC PRIVATE KEY-----".getBytes(StandardCharsets.US_ASCII);

    private static final byte[] END_EC_PRIVATE_KEY = "-----END EC PRIVATE KEY-----".getBytes(StandardCharsets.US_ASCII);

    private static final byte[] BEGIN_OPENSSH_PRIVATE_KEY = "-----BEGIN OPENSSH PRIVATE KEY-----".getBytes(StandardCharsets.US_ASCII);

    private static final byte[] END_OPENSSH_PRIVATE_KEY = "-----END OPENSSH PRIVATE KEY-----".getBytes(StandardCharsets.US_ASCII);

}
