/*
 * Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc. (https://www.bouncycastle.org)
 * <p>
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software
 * and associated documentation files (the "Software"), to deal in the Software without restriction,
 *including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 * </p>
 * <p>
 * The above copyright notice and this permission notice shall be included in all copies or substantial
 * portions of the Software.
 * </p>
 * <p>
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 * </p>
 */
package org.eclipse.jgit.gpg.bc.internal.keys;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.Date;

import org.bouncycastle.asn1.x9.ECNamedCurveTable;
import org.bouncycastle.bcpg.DSAPublicBCPGKey;
import org.bouncycastle.bcpg.DSASecretBCPGKey;
import org.bouncycastle.bcpg.ECDSAPublicBCPGKey;
import org.bouncycastle.bcpg.ECPublicBCPGKey;
import org.bouncycastle.bcpg.ECSecretBCPGKey;
import org.bouncycastle.bcpg.ElGamalPublicBCPGKey;
import org.bouncycastle.bcpg.ElGamalSecretBCPGKey;
import org.bouncycastle.bcpg.HashAlgorithmTags;
import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.bcpg.PublicKeyPacket;
import org.bouncycastle.bcpg.RSAPublicBCPGKey;
import org.bouncycastle.bcpg.RSASecretBCPGKey;
import org.bouncycastle.bcpg.S2K;
import org.bouncycastle.bcpg.SecretKeyPacket;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.Strings;

/**
 * A parser for secret keys stored in s-expressions. Original BouncyCastle code
 * modified by the JGit team to:
 * <ul>
 * <li>handle unencrypted DSA, EC, and ElGamal keys (upstream only handles
 * unencrypted RSA), and</li>
 * <li>handle secret keys using AES/OCB as encryption (those don't have a
 * hash).</li>
 * </ul>
 */
@SuppressWarnings("nls")
public class SExprParser {
	private final PGPDigestCalculatorProvider digestProvider;

	/**
	 * Base constructor.
	 *
	 * @param digestProvider
	 *            a provider for digest calculations. Used to confirm key
	 *            protection hashes.
	 */
	public SExprParser(PGPDigestCalculatorProvider digestProvider) {
		this.digestProvider = digestProvider;
	}

	/**
	 * Parse a secret key from one of the GPG S expression keys associating it
	 * with the passed in public key.
	 *
	 * @param inputStream
	 *            to read from
	 * @param keyProtectionRemoverFactory
	 *            for decrypting encrypted keys
	 * @param pubKey
	 *            the private key should belong to
	 *
	 * @return a secret key object.
	 * @throws IOException
	 * @throws PGPException
	 */
	public PGPSecretKey parseSecretKey(InputStream inputStream,
			PBEProtectionRemoverFactory keyProtectionRemoverFactory,
			PGPPublicKey pubKey) throws IOException, PGPException {
		SXprUtils.skipOpenParenthesis(inputStream);

		String type;

		type = SXprUtils.readString(inputStream, inputStream.read());
		if (type.equals("protected-private-key")
				|| type.equals("private-key")) {
			SXprUtils.skipOpenParenthesis(inputStream);

			String keyType = SXprUtils.readString(inputStream,
					inputStream.read());
			if (keyType.equals("ecc")) {
				SXprUtils.skipOpenParenthesis(inputStream);

				String curveID = SXprUtils.readString(inputStream,
						inputStream.read());
				String curveName = SXprUtils.readString(inputStream,
						inputStream.read());

				SXprUtils.skipCloseParenthesis(inputStream);

				byte[] qVal;

				SXprUtils.skipOpenParenthesis(inputStream);

				type = SXprUtils.readString(inputStream, inputStream.read());
				if (type.equals("q")) {
					qVal = SXprUtils.readBytes(inputStream, inputStream.read());
				} else {
					throw new PGPException("no q value found");
				}

				SXprUtils.skipCloseParenthesis(inputStream);

				BigInteger d = processECSecretKey(inputStream, curveID,
						curveName, qVal, keyProtectionRemoverFactory);

				if (curveName.startsWith("NIST ")) {
					curveName = curveName.substring("NIST ".length());
				}

				ECPublicBCPGKey basePubKey = new ECDSAPublicBCPGKey(
						ECNamedCurveTable.getOID(curveName),
						new BigInteger(1, qVal));
				ECPublicBCPGKey assocPubKey = (ECPublicBCPGKey) pubKey
						.getPublicKeyPacket().getKey();
				if (!basePubKey.getCurveOID().equals(assocPubKey.getCurveOID())
						|| !basePubKey.getEncodedPoint()
								.equals(assocPubKey.getEncodedPoint())) {
					throw new PGPException(
							"passed in public key does not match secret key");
				}

				return new PGPSecretKey(
						new SecretKeyPacket(pubKey.getPublicKeyPacket(),
								SymmetricKeyAlgorithmTags.NULL, null, null,
								new ECSecretBCPGKey(d).getEncoded()),
						pubKey);
			} else if (keyType.equals("dsa")) {
				BigInteger p = readBigInteger("p", inputStream);
				BigInteger q = readBigInteger("q", inputStream);
				BigInteger g = readBigInteger("g", inputStream);

				BigInteger y = readBigInteger("y", inputStream);

				BigInteger x = processDSASecretKey(inputStream, p, q, g, y,
						keyProtectionRemoverFactory);

				DSAPublicBCPGKey basePubKey = new DSAPublicBCPGKey(p, q, g, y);
				DSAPublicBCPGKey assocPubKey = (DSAPublicBCPGKey) pubKey
						.getPublicKeyPacket().getKey();
				if (!basePubKey.getP().equals(assocPubKey.getP())
						|| !basePubKey.getQ().equals(assocPubKey.getQ())
						|| !basePubKey.getG().equals(assocPubKey.getG())
						|| !basePubKey.getY().equals(assocPubKey.getY())) {
					throw new PGPException(
							"passed in public key does not match secret key");
				}
				return new PGPSecretKey(
						new SecretKeyPacket(pubKey.getPublicKeyPacket(),
								SymmetricKeyAlgorithmTags.NULL, null, null,
								new DSASecretBCPGKey(x).getEncoded()),
						pubKey);
			} else if (keyType.equals("elg")) {
				BigInteger p = readBigInteger("p", inputStream);
				BigInteger g = readBigInteger("g", inputStream);

				BigInteger y = readBigInteger("y", inputStream);

				BigInteger x = processElGamalSecretKey(inputStream, p, g, y,
						keyProtectionRemoverFactory);

				ElGamalPublicBCPGKey basePubKey = new ElGamalPublicBCPGKey(p, g,
						y);
				ElGamalPublicBCPGKey assocPubKey = (ElGamalPublicBCPGKey) pubKey
						.getPublicKeyPacket().getKey();
				if (!basePubKey.getP().equals(assocPubKey.getP())
						|| !basePubKey.getG().equals(assocPubKey.getG())
						|| !basePubKey.getY().equals(assocPubKey.getY())) {
					throw new PGPException(
							"passed in public key does not match secret key");
				}

				return new PGPSecretKey(
						new SecretKeyPacket(pubKey.getPublicKeyPacket(),
								SymmetricKeyAlgorithmTags.NULL, null, null,
								new ElGamalSecretBCPGKey(x).getEncoded()),
						pubKey);
			} else if (keyType.equals("rsa")) {
				BigInteger n = readBigInteger("n", inputStream);
				BigInteger e = readBigInteger("e", inputStream);

				BigInteger[] values = processRSASecretKey(inputStream, n, e,
						keyProtectionRemoverFactory);

				// TODO: type of RSA key?
				RSAPublicBCPGKey basePubKey = new RSAPublicBCPGKey(n, e);
				RSAPublicBCPGKey assocPubKey = (RSAPublicBCPGKey) pubKey
						.getPublicKeyPacket().getKey();
				if (!basePubKey.getModulus().equals(assocPubKey.getModulus())
						|| !basePubKey.getPublicExponent()
								.equals(assocPubKey.getPublicExponent())) {
					throw new PGPException(
							"passed in public key does not match secret key");
				}

				return new PGPSecretKey(new SecretKeyPacket(
						pubKey.getPublicKeyPacket(),
						SymmetricKeyAlgorithmTags.NULL, null, null,
						new RSASecretBCPGKey(values[0], values[1], values[2])
								.getEncoded()),
						pubKey);
			} else {
				throw new PGPException("unknown key type: " + keyType);
			}
		}

		throw new PGPException("unknown key type found");
	}

	/**
	 * Parse a secret key from one of the GPG S expression keys.
	 *
	 * @param inputStream
	 *            to read from
	 * @param keyProtectionRemoverFactory
	 *            for decrypting encrypted keys
	 * @param fingerPrintCalculator
	 *            for calculating key fingerprints
	 *
	 * @return a secret key object.
	 * @throws IOException
	 * @throws PGPException
	 */
	public PGPSecretKey parseSecretKey(InputStream inputStream,
			PBEProtectionRemoverFactory keyProtectionRemoverFactory,
			KeyFingerPrintCalculator fingerPrintCalculator)
			throws IOException, PGPException {
		SXprUtils.skipOpenParenthesis(inputStream);

		String type;

		type = SXprUtils.readString(inputStream, inputStream.read());
		if (type.equals("protected-private-key")
				|| type.equals("private-key")) {
			SXprUtils.skipOpenParenthesis(inputStream);

			String keyType = SXprUtils.readString(inputStream,
					inputStream.read());
			if (keyType.equals("ecc")) {
				SXprUtils.skipOpenParenthesis(inputStream);

				String curveID = SXprUtils.readString(inputStream,
						inputStream.read());
				String curveName = SXprUtils.readString(inputStream,
						inputStream.read());

				if (curveName.startsWith("NIST ")) {
					curveName = curveName.substring("NIST ".length());
				}

				SXprUtils.skipCloseParenthesis(inputStream);

				byte[] qVal;

				SXprUtils.skipOpenParenthesis(inputStream);

				type = SXprUtils.readString(inputStream, inputStream.read());
				if (type.equals("q")) {
					qVal = SXprUtils.readBytes(inputStream, inputStream.read());
				} else {
					throw new PGPException("no q value found");
				}

				PublicKeyPacket pubPacket = new PublicKeyPacket(
						PublicKeyAlgorithmTags.ECDSA, new Date(),
						new ECDSAPublicBCPGKey(
								ECNamedCurveTable.getOID(curveName),
								new BigInteger(1, qVal)));

				SXprUtils.skipCloseParenthesis(inputStream);

				BigInteger d = processECSecretKey(inputStream, curveID,
						curveName, qVal, keyProtectionRemoverFactory);

				return new PGPSecretKey(
						new SecretKeyPacket(pubPacket,
								SymmetricKeyAlgorithmTags.NULL, null, null,
								new ECSecretBCPGKey(d).getEncoded()),
						new PGPPublicKey(pubPacket, fingerPrintCalculator));
			} else if (keyType.equals("dsa")) {
				BigInteger p = readBigInteger("p", inputStream);
				BigInteger q = readBigInteger("q", inputStream);
				BigInteger g = readBigInteger("g", inputStream);

				BigInteger y = readBigInteger("y", inputStream);

				BigInteger x = processDSASecretKey(inputStream, p, q, g, y,
						keyProtectionRemoverFactory);

				PublicKeyPacket pubPacket = new PublicKeyPacket(
						PublicKeyAlgorithmTags.DSA, new Date(),
						new DSAPublicBCPGKey(p, q, g, y));

				return new PGPSecretKey(
						new SecretKeyPacket(pubPacket,
								SymmetricKeyAlgorithmTags.NULL, null, null,
								new DSASecretBCPGKey(x).getEncoded()),
						new PGPPublicKey(pubPacket, fingerPrintCalculator));
			} else if (keyType.equals("elg")) {
				BigInteger p = readBigInteger("p", inputStream);
				BigInteger g = readBigInteger("g", inputStream);

				BigInteger y = readBigInteger("y", inputStream);

				BigInteger x = processElGamalSecretKey(inputStream, p, g, y,
						keyProtectionRemoverFactory);

				PublicKeyPacket pubPacket = new PublicKeyPacket(
						PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT, new Date(),
						new ElGamalPublicBCPGKey(p, g, y));

				return new PGPSecretKey(
						new SecretKeyPacket(pubPacket,
								SymmetricKeyAlgorithmTags.NULL, null, null,
								new ElGamalSecretBCPGKey(x).getEncoded()),
						new PGPPublicKey(pubPacket, fingerPrintCalculator));
			} else if (keyType.equals("rsa")) {
				BigInteger n = readBigInteger("n", inputStream);
				BigInteger e = readBigInteger("e", inputStream);

				BigInteger[] values = processRSASecretKey(inputStream, n, e,
						keyProtectionRemoverFactory);

				// TODO: type of RSA key?
				PublicKeyPacket pubPacket = new PublicKeyPacket(
						PublicKeyAlgorithmTags.RSA_GENERAL, new Date(),
						new RSAPublicBCPGKey(n, e));

				return new PGPSecretKey(
						new SecretKeyPacket(pubPacket,
								SymmetricKeyAlgorithmTags.NULL, null, null,
								new RSASecretBCPGKey(values[0], values[1],
										values[2]).getEncoded()),
						new PGPPublicKey(pubPacket, fingerPrintCalculator));
			} else {
				throw new PGPException("unknown key type: " + keyType);
			}
		}

		throw new PGPException("unknown key type found");
	}

	private BigInteger readBigInteger(String expectedType,
			InputStream inputStream) throws IOException, PGPException {
		SXprUtils.skipOpenParenthesis(inputStream);

		String type = SXprUtils.readString(inputStream, inputStream.read());
		if (!type.equals(expectedType)) {
			throw new PGPException(expectedType + " value expected");
		}

		byte[] nBytes = SXprUtils.readBytes(inputStream, inputStream.read());
		BigInteger v = new BigInteger(1, nBytes);

		SXprUtils.skipCloseParenthesis(inputStream);

		return v;
	}

	private static byte[][] extractData(InputStream inputStream,
			PBEProtectionRemoverFactory keyProtectionRemoverFactory)
			throws PGPException, IOException {
		byte[] data;
		byte[] protectedAt = null;

		SXprUtils.skipOpenParenthesis(inputStream);

		String type = SXprUtils.readString(inputStream, inputStream.read());
		if (type.equals("protected")) {
			String protection = SXprUtils.readString(inputStream,
					inputStream.read());

			SXprUtils.skipOpenParenthesis(inputStream);

			S2K s2k = SXprUtils.parseS2K(inputStream);

			byte[] iv = SXprUtils.readBytes(inputStream, inputStream.read());

			SXprUtils.skipCloseParenthesis(inputStream);

			byte[] secKeyData = SXprUtils.readBytes(inputStream,
					inputStream.read());

			SXprUtils.skipCloseParenthesis(inputStream);

			PBESecretKeyDecryptor keyDecryptor = keyProtectionRemoverFactory
					.createDecryptor(protection);

			// TODO: recognise other algorithms
			byte[] key = keyDecryptor.makeKeyFromPassPhrase(
					SymmetricKeyAlgorithmTags.AES_128, s2k);

			data = keyDecryptor.recoverKeyData(
					SymmetricKeyAlgorithmTags.AES_128, key, iv, secKeyData, 0,
					secKeyData.length);

			// check if protected at is present
			if (inputStream.read() == '(') {
				ByteArrayOutputStream bOut = new ByteArrayOutputStream();

				bOut.write('(');
				int ch;
				while ((ch = inputStream.read()) >= 0 && ch != ')') {
					bOut.write(ch);
				}

				if (ch != ')') {
					throw new IOException("unexpected end to SExpr");
				}

				bOut.write(')');

				protectedAt = bOut.toByteArray();
			}

			SXprUtils.skipCloseParenthesis(inputStream);
			SXprUtils.skipCloseParenthesis(inputStream);
		} else if (type.equals("d") || type.equals("x")) {
			// JGit modification: unencrypted DSA or ECC keys can have an "x"
			// here
			return null;
		} else {
			throw new PGPException("protected block not found");
		}

		return new byte[][] { data, protectedAt };
	}

	private BigInteger processDSASecretKey(InputStream inputStream,
			BigInteger p, BigInteger q, BigInteger g, BigInteger y,
			PBEProtectionRemoverFactory keyProtectionRemoverFactory)
			throws IOException, PGPException {
		String type;
		byte[][] basicData = extractData(inputStream,
				keyProtectionRemoverFactory);

		// JGit modification: handle unencrypted DSA keys
		if (basicData == null) {
			byte[] nBytes = SXprUtils.readBytes(inputStream,
					inputStream.read());
			BigInteger x = new BigInteger(1, nBytes);
			SXprUtils.skipCloseParenthesis(inputStream);
			return x;
		}

		byte[] keyData = basicData[0];
		byte[] protectedAt = basicData[1];

		//
		// parse the secret key S-expr
		//
		InputStream keyIn = new ByteArrayInputStream(keyData);

		SXprUtils.skipOpenParenthesis(keyIn);
		SXprUtils.skipOpenParenthesis(keyIn);

		BigInteger x = readBigInteger("x", keyIn);

		SXprUtils.skipCloseParenthesis(keyIn);

		// JGit modification: OCB-encrypted keys don't have and don't need a
		// hash
		if (keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) {
			return x;
		}

		SXprUtils.skipOpenParenthesis(keyIn);
		type = SXprUtils.readString(keyIn, keyIn.read());

		if (!type.equals("hash")) {
			throw new PGPException("hash keyword expected");
		}
		type = SXprUtils.readString(keyIn, keyIn.read());

		if (!type.equals("sha1")) {
			throw new PGPException("hash keyword expected");
		}

		byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read());

		SXprUtils.skipCloseParenthesis(keyIn);

		if (digestProvider != null) {
			PGPDigestCalculator digestCalculator = digestProvider
					.get(HashAlgorithmTags.SHA1);

			OutputStream dOut = digestCalculator.getOutputStream();

			dOut.write(Strings.toByteArray("(3:dsa"));
			writeCanonical(dOut, "p", p);
			writeCanonical(dOut, "q", q);
			writeCanonical(dOut, "g", g);
			writeCanonical(dOut, "y", y);
			writeCanonical(dOut, "x", x);

			// check protected-at
			if (protectedAt != null) {
				dOut.write(protectedAt);
			}

			dOut.write(Strings.toByteArray(")"));

			byte[] check = digestCalculator.getDigest();
			if (!Arrays.constantTimeAreEqual(check, hashBytes)) {
				throw new PGPException(
						"checksum on protected data failed in SExpr");
			}
		}

		return x;
	}

	private BigInteger processElGamalSecretKey(InputStream inputStream,
			BigInteger p, BigInteger g, BigInteger y,
			PBEProtectionRemoverFactory keyProtectionRemoverFactory)
			throws IOException, PGPException {
		String type;
		byte[][] basicData = extractData(inputStream,
				keyProtectionRemoverFactory);

		// JGit modification: handle unencrypted EC keys
		if (basicData == null) {
			byte[] nBytes = SXprUtils.readBytes(inputStream,
					inputStream.read());
			BigInteger x = new BigInteger(1, nBytes);
			SXprUtils.skipCloseParenthesis(inputStream);
			return x;
		}

		byte[] keyData = basicData[0];
		byte[] protectedAt = basicData[1];

		//
		// parse the secret key S-expr
		//
		InputStream keyIn = new ByteArrayInputStream(keyData);

		SXprUtils.skipOpenParenthesis(keyIn);
		SXprUtils.skipOpenParenthesis(keyIn);

		BigInteger x = readBigInteger("x", keyIn);

		SXprUtils.skipCloseParenthesis(keyIn);

		// JGit modification: OCB-encrypted keys don't have and don't need a
		// hash
		if (keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) {
			return x;
		}

		SXprUtils.skipOpenParenthesis(keyIn);
		type = SXprUtils.readString(keyIn, keyIn.read());

		if (!type.equals("hash")) {
			throw new PGPException("hash keyword expected");
		}
		type = SXprUtils.readString(keyIn, keyIn.read());

		if (!type.equals("sha1")) {
			throw new PGPException("hash keyword expected");
		}

		byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read());

		SXprUtils.skipCloseParenthesis(keyIn);

		if (digestProvider != null) {
			PGPDigestCalculator digestCalculator = digestProvider
					.get(HashAlgorithmTags.SHA1);

			OutputStream dOut = digestCalculator.getOutputStream();

			dOut.write(Strings.toByteArray("(3:elg"));
			writeCanonical(dOut, "p", p);
			writeCanonical(dOut, "g", g);
			writeCanonical(dOut, "y", y);
			writeCanonical(dOut, "x", x);

			// check protected-at
			if (protectedAt != null) {
				dOut.write(protectedAt);
			}

			dOut.write(Strings.toByteArray(")"));

			byte[] check = digestCalculator.getDigest();
			if (!Arrays.constantTimeAreEqual(check, hashBytes)) {
				throw new PGPException(
						"checksum on protected data failed in SExpr");
			}
		}

		return x;
	}

	private BigInteger processECSecretKey(InputStream inputStream,
			String curveID, String curveName, byte[] qVal,
			PBEProtectionRemoverFactory keyProtectionRemoverFactory)
			throws IOException, PGPException {
		String type;

		byte[][] basicData = extractData(inputStream,
				keyProtectionRemoverFactory);

		// JGit modification: handle unencrypted EC keys
		if (basicData == null) {
			byte[] nBytes = SXprUtils.readBytes(inputStream,
					inputStream.read());
			BigInteger d = new BigInteger(1, nBytes);
			SXprUtils.skipCloseParenthesis(inputStream);
			return d;
		}

		byte[] keyData = basicData[0];
		byte[] protectedAt = basicData[1];

		//
		// parse the secret key S-expr
		//
		InputStream keyIn = new ByteArrayInputStream(keyData);

		SXprUtils.skipOpenParenthesis(keyIn);
		SXprUtils.skipOpenParenthesis(keyIn);
		BigInteger d = readBigInteger("d", keyIn);
		SXprUtils.skipCloseParenthesis(keyIn);

		// JGit modification: OCB-encrypted keys don't have and don't need a
		// hash
		if (keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) {
			return d;
		}

		SXprUtils.skipOpenParenthesis(keyIn);

		type = SXprUtils.readString(keyIn, keyIn.read());

		if (!type.equals("hash")) {
			throw new PGPException("hash keyword expected");
		}
		type = SXprUtils.readString(keyIn, keyIn.read());

		if (!type.equals("sha1")) {
			throw new PGPException("hash keyword expected");
		}

		byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read());

		SXprUtils.skipCloseParenthesis(keyIn);

		if (digestProvider != null) {
			PGPDigestCalculator digestCalculator = digestProvider
					.get(HashAlgorithmTags.SHA1);

			OutputStream dOut = digestCalculator.getOutputStream();

			dOut.write(Strings.toByteArray("(3:ecc"));

			dOut.write(Strings.toByteArray("(" + curveID.length() + ":"
					+ curveID + curveName.length() + ":" + curveName + ")"));

			writeCanonical(dOut, "q", qVal);
			writeCanonical(dOut, "d", d);

			// check protected-at
			if (protectedAt != null) {
				dOut.write(protectedAt);
			}

			dOut.write(Strings.toByteArray(")"));

			byte[] check = digestCalculator.getDigest();

			if (!Arrays.constantTimeAreEqual(check, hashBytes)) {
				throw new PGPException(
						"checksum on protected data failed in SExpr");
			}
		}

		return d;
	}

	private BigInteger[] processRSASecretKey(InputStream inputStream,
			BigInteger n, BigInteger e,
			PBEProtectionRemoverFactory keyProtectionRemoverFactory)
			throws IOException, PGPException {
		String type;
		byte[][] basicData = extractData(inputStream,
				keyProtectionRemoverFactory);

		byte[] keyData;
		byte[] protectedAt = null;

		InputStream keyIn;
		BigInteger d;

		if (basicData == null) {
			keyIn = inputStream;
			byte[] nBytes = SXprUtils.readBytes(inputStream,
					inputStream.read());
			d = new BigInteger(1, nBytes);

			SXprUtils.skipCloseParenthesis(inputStream);

		} else {
			keyData = basicData[0];
			protectedAt = basicData[1];

			keyIn = new ByteArrayInputStream(keyData);

			SXprUtils.skipOpenParenthesis(keyIn);
			SXprUtils.skipOpenParenthesis(keyIn);
			d = readBigInteger("d", keyIn);
		}

		//
		// parse the secret key S-expr
		//

		BigInteger p = readBigInteger("p", keyIn);
		BigInteger q = readBigInteger("q", keyIn);
		BigInteger u = readBigInteger("u", keyIn);

		// JGit modification: OCB-encrypted keys don't have and don't need a
		// hash
		if (basicData == null
				|| keyProtectionRemoverFactory instanceof OCBPBEProtectionRemoverFactory) {
			return new BigInteger[] { d, p, q, u };
		}

		SXprUtils.skipCloseParenthesis(keyIn);

		SXprUtils.skipOpenParenthesis(keyIn);
		type = SXprUtils.readString(keyIn, keyIn.read());

		if (!type.equals("hash")) {
			throw new PGPException("hash keyword expected");
		}
		type = SXprUtils.readString(keyIn, keyIn.read());

		if (!type.equals("sha1")) {
			throw new PGPException("hash keyword expected");
		}

		byte[] hashBytes = SXprUtils.readBytes(keyIn, keyIn.read());

		SXprUtils.skipCloseParenthesis(keyIn);

		if (digestProvider != null) {
			PGPDigestCalculator digestCalculator = digestProvider
					.get(HashAlgorithmTags.SHA1);

			OutputStream dOut = digestCalculator.getOutputStream();

			dOut.write(Strings.toByteArray("(3:rsa"));

			writeCanonical(dOut, "n", n);
			writeCanonical(dOut, "e", e);
			writeCanonical(dOut, "d", d);
			writeCanonical(dOut, "p", p);
			writeCanonical(dOut, "q", q);
			writeCanonical(dOut, "u", u);

			// check protected-at
			if (protectedAt != null) {
				dOut.write(protectedAt);
			}

			dOut.write(Strings.toByteArray(")"));

			byte[] check = digestCalculator.getDigest();

			if (!Arrays.constantTimeAreEqual(check, hashBytes)) {
				throw new PGPException(
						"checksum on protected data failed in SExpr");
			}
		}

		return new BigInteger[] { d, p, q, u };
	}

	private void writeCanonical(OutputStream dOut, String label, BigInteger i)
			throws IOException {
		writeCanonical(dOut, label, i.toByteArray());
	}

	private void writeCanonical(OutputStream dOut, String label, byte[] data)
			throws IOException {
		dOut.write(Strings.toByteArray(
				"(" + label.length() + ":" + label + data.length + ":"));
		dOut.write(data);
		dOut.write(Strings.toByteArray(")"));
	}
}
