blob: c93c2164c996bdfc8a800ab468f9cd25dbdfaaf9 [file] [log] [blame]
/*
* 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
* if an IO error occurred
* @throws PGPException
* if some PGP error occurred
*/
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
* if an IO error occurred
* @throws PGPException
* if a PGP error occurred
*/
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(")"));
}
}