]> source.dussan.org Git - jgit.git/commitdiff
GPG: use BC PGP secret key parsing out of the box 69/1203669/2
authorThomas Wolf <twolf@apache.org>
Wed, 6 Nov 2024 18:14:47 +0000 (19:14 +0100)
committerThomas Wolf <twolf@apache.org>
Wed, 6 Nov 2024 19:20:39 +0000 (20:20 +0100)
Remove the custom S-expression parsing; BC has gotten many
improvements in 1.79 regarding PGP ed25519 keys, AES/OCB
encryption, and generally parsing key files. It now can do
all we need.

Change-Id: I392443e040cce150a9575d18795a7cb8195a3515
Signed-off-by: Thomas Wolf <twolf@apache.org>
org.eclipse.jgit.gpg.bc.test/META-INF/MANIFEST.MF
org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java
org.eclipse.jgit.gpg.bc/META-INF/MANIFEST.MF
org.eclipse.jgit.gpg.bc/about.html
org.eclipse.jgit.gpg.bc/resources/org/eclipse/jgit/gpg/bc/internal/BCText.properties
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BCText.java
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/BouncyCastleGpgSigner.java
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/OCBPBEProtectionRemoverFactory.java [deleted file]
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SExprParser.java [deleted file]
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SXprUtils.java [deleted file]
org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeys.java

index c71f9b65413d9ccd528004904ad9c67d6c08a7d7..a86f5ab2c66a4798f64d1f6945fa24e08c3e6774 100644 (file)
@@ -8,11 +8,12 @@ Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: plugin
 Bundle-RequiredExecutionEnvironment: JavaSE-17
 Require-Bundle: org.hamcrest.core;bundle-version="[1.3.0,2.0.0)"
-Import-Package: org.bouncycastle.jce.provider;version="[1.65.0,2.0.0)",
- org.bouncycastle.openpgp;version="[1.65.0,2.0.0)",
- org.bouncycastle.openpgp.operator;version="[1.65.0,2.0.0)",
- org.bouncycastle.openpgp.operator.jcajce;version="[1.65.0,2.0.0)",
- org.bouncycastle.util.encoders;version="[1.65.0,2.0.0)",
+Import-Package: org.bouncycastle.asn1.cryptlib;version="[1.79.0,2.0.0)",
+ org.bouncycastle.jce.provider;version="[1.79.0,2.0.0)",
+ org.bouncycastle.openpgp;version="[1.79.0,2.0.0)",
+ org.bouncycastle.openpgp.operator;version="[1.79.0,2.0.0)",
+ org.bouncycastle.openpgp.operator.jcajce;version="[1.79.0,2.0.0)",
+ org.bouncycastle.util.encoders;version="[1.79.0,2.0.0)",
  org.eclipse.jgit.gpg.bc.internal;version="[7.1.0,7.2.0)",
  org.eclipse.jgit.gpg.bc.internal.keys;version="[7.1.0,7.2.0)",
  org.eclipse.jgit.util.sha1;version="[7.1.0,7.2.0)",
index fed06103b623a874e1ffd5fe28b4435d88a09886..d486c977f09a6bd262ccc3d5835f40538f1f2fec 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2021, 2024 Thomas Wolf <twolf@apache.org> and others
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -9,10 +9,7 @@
  */
 package org.eclipse.jgit.gpg.bc.internal.keys;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
 
 import java.io.BufferedInputStream;
 import java.io.IOException;
@@ -20,8 +17,6 @@ import java.io.InputStream;
 import java.security.Security;
 import java.util.Iterator;
 
-import javax.crypto.Cipher;
-
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPPublicKey;
@@ -49,39 +44,15 @@ public class SecretKeysTest {
                }
        }
 
-       private static volatile Boolean haveOCB;
-
-       private static boolean ocbAvailable() {
-               Boolean haveIt = haveOCB;
-               if (haveIt != null) {
-                       return haveIt.booleanValue();
-               }
-               try {
-                       Cipher c = Cipher.getInstance("AES/OCB/NoPadding"); //$NON-NLS-1$
-                       if (c == null) {
-                               haveOCB = Boolean.FALSE;
-                               return false;
-                       }
-               } catch (NoClassDefFoundError | Exception e) {
-                       haveOCB = Boolean.FALSE;
-                       return false;
-               }
-               haveOCB = Boolean.TRUE;
-               return true;
-       }
-
        private static class TestData {
 
                final String name;
 
                final boolean encrypted;
 
-               final boolean keyValue;
-
-               TestData(String name, boolean encrypted, boolean keyValue) {
+               TestData(String name, boolean encrypted) {
                        this.name = name;
                        this.encrypted = encrypted;
-                       this.keyValue = keyValue;
                }
 
                @Override
@@ -93,19 +64,12 @@ public class SecretKeysTest {
        @Parameters(name = "{0}")
        public static TestData[] initTestData() {
                return new TestData[] {
-                               new TestData("AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11", false, false),
-                               new TestData("2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A", false, true),
-                               new TestData("66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9", true, true),
-                               new TestData("F727FAB884DA3BD402B6E0F5472E108D21033124", true, true),
-                               new TestData("62D43D7F117F7A5E4998ECB6617EE9942D069C14", true, true),
-                               new TestData("faked", false, true) };
-       }
-
-       private static byte[] readTestKey(String filename) throws Exception {
-               try (InputStream in = new BufferedInputStream(
-                               SecretKeysTest.class.getResourceAsStream(filename))) {
-                       return SecretKeys.keyFromNameValueFormat(in);
-               }
+                               new TestData("AFDA8EA10E185ACF8C0D0F8885A0EF61A72ECB11", false),
+                               new TestData("2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A", false),
+                               new TestData("66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9", true),
+                               new TestData("F727FAB884DA3BD402B6E0F5472E108D21033124", true),
+                               new TestData("62D43D7F117F7A5E4998ECB6617EE9942D069C14", true),
+                               new TestData("faked", false) };
        }
 
        private static PGPPublicKey readAsc(InputStream in)
@@ -131,11 +95,6 @@ public class SecretKeysTest {
 
        @Test
        public void testKeyRead() throws Exception {
-               if (data.keyValue) {
-                       byte[] bytes = readTestKey(data.name + ".key");
-                       assertEquals('(', bytes[0]);
-                       assertEquals(')', bytes[bytes.length - 1]);
-               }
                try (InputStream pubIn = this.getClass()
                                .getResourceAsStream(data.name + ".asc")) {
                        if (pubIn != null) {
@@ -151,11 +110,6 @@ public class SecretKeysTest {
                                                                        : null,
                                                        publicKey);
                                        assertNotNull(secretKey);
-                               } catch (PGPException e) {
-                                       // Currently we may not be able to load OCB-encrypted keys.
-                                       assertTrue(e.toString(), e.getMessage().contains("OCB"));
-                                       assertTrue(data.encrypted);
-                                       assertFalse(ocbAvailable());
                                }
                        }
                }
index 6e3321ebf6d8e207494303317cd640c7dd5ef1d6..1413c44dcb0c0603e0c38d01d4efe829e088d498 100644 (file)
@@ -8,26 +8,23 @@ Bundle-Vendor: %Bundle-Vendor
 Bundle-Localization: OSGI-INF/l10n/gpg_bc
 Bundle-Version: 7.1.0.qualifier
 Bundle-RequiredExecutionEnvironment: JavaSE-17
-Import-Package: org.bouncycastle.asn1;version="[1.69.0,2.0.0)",
- org.bouncycastle.asn1.x9;version="[1.69.0,2.0.0)",
- org.bouncycastle.bcpg;version="[1.69.0,2.0.0)",
- org.bouncycastle.bcpg.sig;version="[1.69.0,2.0.0)",
- org.bouncycastle.crypto.ec;version="[1.69.0,2.0.0)",
- org.bouncycastle.gpg;version="[1.69.0,2.0.0)",
- org.bouncycastle.gpg.keybox;version="[1.69.0,2.0.0)",
- org.bouncycastle.gpg.keybox.jcajce;version="[1.69.0,2.0.0)",
- org.bouncycastle.jcajce.interfaces;version="[1.69.0,2.0.0)",
- org.bouncycastle.jcajce.util;version="[1.69.0,2.0.0)",
- org.bouncycastle.jce.provider;version="[1.69.0,2.0.0)",
- org.bouncycastle.math.ec;version="[1.69.0,2.0.0)",
- org.bouncycastle.math.field;version="[1.69.0,2.0.0)",
- org.bouncycastle.openpgp;version="[1.69.0,2.0.0)",
- org.bouncycastle.openpgp.jcajce;version="[1.69.0,2.0.0)",
- org.bouncycastle.openpgp.operator;version="[1.69.0,2.0.0)",
- org.bouncycastle.openpgp.operator.jcajce;version="[1.69.0,2.0.0)",
- org.bouncycastle.util;version="[1.69.0,2.0.0)",
- org.bouncycastle.util.encoders;version="[1.69.0,2.0.0)",
- org.bouncycastle.util.io;version="[1.69.0,2.0.0)",
+Import-Package: org.bouncycastle.asn1;version="[1.79.0,2.0.0)",
+ org.bouncycastle.asn1.x9;version="[1.79.0,2.0.0)",
+ org.bouncycastle.bcpg;version="[1.79.0,2.0.0)",
+ org.bouncycastle.bcpg.sig;version="[1.79.0,2.0.0)",
+ org.bouncycastle.crypto.ec;version="[1.79.0,2.0.0)",
+ org.bouncycastle.gpg;version="[1.79.0,2.0.0)",
+ org.bouncycastle.gpg.keybox;version="[1.79.0,2.0.0)",
+ org.bouncycastle.gpg.keybox.jcajce;version="[1.79.0,2.0.0)",
+ org.bouncycastle.jcajce.interfaces;version="[1.79.0,2.0.0)",
+ org.bouncycastle.jcajce.util;version="[1.79.0,2.0.0)",
+ org.bouncycastle.math.ec;version="[1.79.0,2.0.0)",
+ org.bouncycastle.math.field;version="[1.79.0,2.0.0)",
+ org.bouncycastle.openpgp;version="[1.79.0,2.0.0)",
+ org.bouncycastle.openpgp.jcajce;version="[1.79.0,2.0.0)",
+ org.bouncycastle.openpgp.operator;version="[1.79.0,2.0.0)",
+ org.bouncycastle.openpgp.operator.jcajce;version="[1.79.0,2.0.0)",
+ org.bouncycastle.util.encoders;version="[1.79.0,2.0.0)",
  org.slf4j;version="[1.7.0,3.0.0)"
 Export-Package: org.eclipse.jgit.gpg.bc.internal;version="7.1.0";x-friends:="org.eclipse.jgit.gpg.bc.test",
  org.eclipse.jgit.gpg.bc.internal.keys;version="7.1.0";x-friends:="org.eclipse.jgit.gpg.bc.test"
index fc527d5a3a1d22c26cfaff63057cac2eab74475c..92b9409831e661307431bafe839126a7e53aed22 100644 (file)
@@ -58,32 +58,6 @@ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWIS
 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 POSSIBILITY OF SUCH DAMAGE.</p>
 
-<hr>
-<p><b>org.eclipse.jgit.gpg.bc.internal.keys.SExprParser - MIT</b></p>
-
-<p>Copyright (c) 2000-2021 The Legion of the Bouncy Castle Inc.
-(<a href="https://www.bouncycastle.org">https://www.bouncycastle.org</a>)</p>
-
-<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>
-
 </body>
 
 </html>
index 77ca2cd0a4dbc0bcdc494805b567fb78b455637f..9e7f98cab157575e839df49df69e10543a675360 100644 (file)
@@ -1,7 +1,5 @@
 corrupt25519Key=Ed25519/Curve25519 public key has wrong length: {0}
 credentialPassphrase=Passphrase
-cryptCipherError=Cannot create cipher to decrypt: {0}
-cryptWrongDecryptedLength=Decrypted key has wrong length; expected {0} bytes, got only {1} bytes
 gpgFailedToParseSecretKey=Failed to parse secret key file {0}. Is the entered passphrase correct?
 gpgNoCredentialsProvider=missing credentials provider
 gpgNoKeygrip=Cannot find key {0}: cannot determine key grip
@@ -9,22 +7,14 @@ gpgNoKeyring=neither pubring.kbx nor secring.gpg files found
 gpgNoKeyInLegacySecring=no matching secret key found in legacy secring.gpg for key or user id: {0}
 gpgNoPublicKeyFound=Unable to find a public-key with key or user id: {0}
 gpgNoSecretKeyForPublicKey=unable to find associated secret key for public key: {0}
-gpgNoSuchAlgorithm=Cannot decrypt encrypted secret key: encryption algorithm {0} is not available
 gpgNotASigningKey=Secret key ({0}) is not suitable for signing
 gpgKeyInfo=GPG Key (fingerprint {0})
 gpgSigningCancelled=Signing was cancelled
+keyAlgorithmMismatch=Secret key has a different algorithm than the public key
+keyMismatch=Secret key does not match public key; public key is {0} {1} while secret key is for {2} {3}
 logWarnGnuPGHome=Cannot access GPG home directory given by environment variable GNUPGHOME={}
 logWarnGpgHomeProperty=Cannot access GPG home directory given by Java system property jgit.gpg.home={}
 nonSignatureError=Signature does not decode into a signature object
-secretKeyTooShort=Secret key file corrupt; only {0} bytes read
-sexprHexNotClosed=Hex number in s-expression not closed
-sexprHexOdd=Hex number in s-expression has an odd number of digits
-sexprStringInvalidEscape=Invalid escape {0} in s-expression
-sexprStringInvalidEscapeAtEnd=Invalid s-expression: quoted string ends with escape character
-sexprStringInvalidHexEscape=Invalid hex escape in s-expression
-sexprStringInvalidOctalEscape=Invalid octal escape in s-expression
-sexprStringNotClosed=String in s-expression not closed
-sexprUnhandled=Unhandled token {0} in s-expression
 signatureInconsistent=Inconsistent signature; key ID {0} does not match issuer fingerprint {1}
 signatureKeyLookupError=Error occurred while looking for public key
 signatureNoKeyInfo=No way to determine a public key from the signature
index 705e195e44c60ca180ffb75d3b48c8d95805acf0..fcae7c2b98975b368e8eb802dfd62198a7fe4413 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2018, 2021 Salesforce and others
+ * Copyright (C) 2018, 2024 Salesforce and others
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -30,8 +30,6 @@ public final class BCText extends TranslationBundle {
        // @formatter:off
        /***/ public String corrupt25519Key;
        /***/ public String credentialPassphrase;
-       /***/ public String cryptCipherError;
-       /***/ public String cryptWrongDecryptedLength;
        /***/ public String gpgFailedToParseSecretKey;
        /***/ public String gpgNoCredentialsProvider;
        /***/ public String gpgNoKeygrip;
@@ -39,22 +37,14 @@ public final class BCText extends TranslationBundle {
        /***/ public String gpgNoKeyInLegacySecring;
        /***/ public String gpgNoPublicKeyFound;
        /***/ public String gpgNoSecretKeyForPublicKey;
-       /***/ public String gpgNoSuchAlgorithm;
        /***/ public String gpgNotASigningKey;
        /***/ public String gpgKeyInfo;
        /***/ public String gpgSigningCancelled;
+       /***/ public String keyAlgorithmMismatch;
+       /***/ public String keyMismatch;
        /***/ public String logWarnGnuPGHome;
        /***/ public String logWarnGpgHomeProperty;
        /***/ public String nonSignatureError;
-       /***/ public String secretKeyTooShort;
-       /***/ public String sexprHexNotClosed;
-       /***/ public String sexprHexOdd;
-       /***/ public String sexprStringInvalidEscape;
-       /***/ public String sexprStringInvalidEscapeAtEnd;
-       /***/ public String sexprStringInvalidHexEscape;
-       /***/ public String sexprStringInvalidOctalEscape;
-       /***/ public String sexprStringNotClosed;
-       /***/ public String sexprUnhandled;
        /***/ public String signatureInconsistent;
        /***/ public String signatureKeyLookupError;
        /***/ public String signatureNoKeyInfo;
index 1d187a5db2aee1e71edb673c6566f00f7b0953c4..adac9b199d5a4af6b8c2327a4a2431755aeef44d 100644 (file)
@@ -105,7 +105,8 @@ public class BouncyCastleGpgSigner implements Signer {
                        PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(
                                        new JcaPGPContentSignerBuilder(
                                                        publicKey.getAlgorithm(),
-                                                       HashAlgorithmTags.SHA256));
+                                                       HashAlgorithmTags.SHA256),
+                                       publicKey);
                        signatureGenerator.init(PGPSignature.BINARY_DOCUMENT, privateKey);
                        PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator();
                        subpackets.setIssuerFingerprint(false, publicKey);
diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/OCBPBEProtectionRemoverFactory.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/OCBPBEProtectionRemoverFactory.java
deleted file mode 100644 (file)
index e6f1dec..0000000
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Distribution License v. 1.0 which is available at
- * https://www.eclipse.org/org/documents/edl-v10.php.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- */
-package org.eclipse.jgit.gpg.bc.internal.keys;
-
-import java.security.NoSuchAlgorithmException;
-import java.text.MessageFormat;
-
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import org.bouncycastle.openpgp.PGPException;
-import org.bouncycastle.openpgp.PGPUtil;
-import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory;
-import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
-import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
-import org.bouncycastle.util.Arrays;
-import org.eclipse.jgit.gpg.bc.internal.BCText;
-
-/**
- * A {@link PBEProtectionRemoverFactory} using AES/OCB/NoPadding for decryption.
- * It accepts an AAD in the factory's constructor, so the factory can be used to
- * create a {@link PBESecretKeyDecryptor} only for a particular input.
- * <p>
- * For JGit's needs, this is sufficient, but for a general upstream
- * implementation that limitation might not be acceptable.
- * </p>
- */
-class OCBPBEProtectionRemoverFactory
-               implements PBEProtectionRemoverFactory {
-
-       private final PGPDigestCalculatorProvider calculatorProvider;
-
-       private final char[] passphrase;
-
-       private final byte[] aad;
-
-       /**
-        * Creates a new factory instance with the given parameters.
-        * <p>
-        * Because the AAD is given at factory level, the {@link PBESecretKeyDecryptor}s
-        * created by the factory can be used to decrypt only a particular input
-        * matching this AAD.
-        * </p>
-        *
-        * @param passphrase         to use for secret key derivation
-        * @param calculatorProvider for computing digests
-        * @param aad                for the OCB decryption
-        */
-       OCBPBEProtectionRemoverFactory(char[] passphrase,
-                       PGPDigestCalculatorProvider calculatorProvider, byte[] aad) {
-               this.calculatorProvider = calculatorProvider;
-               this.passphrase = passphrase;
-               this.aad = aad;
-       }
-
-       @Override
-       public PBESecretKeyDecryptor createDecryptor(String protection)
-                       throws PGPException {
-               return new PBESecretKeyDecryptor(passphrase, calculatorProvider) {
-
-                       @Override
-                       public byte[] recoverKeyData(int encAlgorithm, byte[] key,
-                                       byte[] iv, byte[] encrypted, int encryptedOffset,
-                                       int encryptedLength) throws PGPException {
-                               String algorithmName = PGPUtil
-                                               .getSymmetricCipherName(encAlgorithm);
-                               byte[] decrypted = null;
-                               try {
-                                       // errorprone: "Dynamically constructed transformation
-                                       // strings are also flagged, as they may conceal an instance
-                                       // of ECB mode."
-                                       @SuppressWarnings("InsecureCryptoUsage")
-                                       Cipher c = Cipher
-                                                       .getInstance(algorithmName + "/OCB/NoPadding"); //$NON-NLS-1$
-                                       SecretKey secretKey = new SecretKeySpec(key, algorithmName);
-                                       c.init(Cipher.DECRYPT_MODE, secretKey,
-                                                       new IvParameterSpec(iv));
-                                       c.updateAAD(aad);
-                                       decrypted = new byte[c.getOutputSize(encryptedLength)];
-                                       int decryptedLength = c.update(encrypted, encryptedOffset,
-                                                       encryptedLength, decrypted);
-                                       // doFinal() for OCB will check the MAC and throw an
-                                       // exception if it doesn't match
-                                       decryptedLength += c.doFinal(decrypted, decryptedLength);
-                                       if (decryptedLength != decrypted.length) {
-                                               throw new PGPException(MessageFormat.format(
-                                                               BCText.get().cryptWrongDecryptedLength,
-                                                               Integer.valueOf(decryptedLength),
-                                                               Integer.valueOf(decrypted.length)));
-                                       }
-                                       byte[] result = decrypted;
-                                       decrypted = null; // Don't clear in finally
-                                       return result;
-                               } catch (NoClassDefFoundError e) {
-                                       String msg = MessageFormat.format(
-                                                       BCText.get().gpgNoSuchAlgorithm,
-                                                       algorithmName + "/OCB"); //$NON-NLS-1$
-                                       throw new PGPException(msg,
-                                                       new NoSuchAlgorithmException(msg, e));
-                               } catch (PGPException e) {
-                                       throw e;
-                               } catch (Exception e) {
-                                       throw new PGPException(
-                                                       MessageFormat.format(BCText.get().cryptCipherError,
-                                                                       e.getLocalizedMessage()),
-                                                       e);
-                               } finally {
-                                       if (decrypted != null) {
-                                               // Prevent halfway decrypted data leaking.
-                                               Arrays.fill(decrypted, (byte) 0);
-                                       }
-                               }
-                       }
-
-                       @Override
-                       public byte[] recoverKeyData(int encAlgorithm, int aeadAlgorithm,
-                                       byte[] s2kKey, byte[] iv, int packetTag, int keyVersion,
-                                       byte[] keyData, byte[] pubkeyData) throws PGPException {
-                               throw new UnsupportedOperationException();
-                       }
-               };
-       }
-}
\ No newline at end of file
diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SExprParser.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SExprParser.java
deleted file mode 100644 (file)
index b062ead..0000000
+++ /dev/null
@@ -1,711 +0,0 @@
-/*
- * 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 org.bouncycastle.asn1.ASN1ObjectIdentifier;
-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.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.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)</li>
- * <li>handle secret keys using AES/OCB as encryption (those don't have a
- * hash)</li>
- * <li>fix EC parsing to account for "flags" sub-list present for ed25519 and
- * curve25519</li>
- * <li>add support for ed25519 OIDs unknown to BouncyCastle</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());
-                               // JGit: c.f. https://github.com/bcgit/bc-java/issues/1590.
-                               // There may be a flags sub-list here for ed25519 or curve25519.
-                               if (type.equals("flags")) {
-                                       SXprUtils.readString(inputStream, inputStream.read());
-                                       SXprUtils.skipCloseParenthesis(inputStream);
-                                       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());
-                               }
-
-                               // JGit: BC doesn't know Ed25519 curve name.
-                               ASN1ObjectIdentifier curveOid = ECNamedCurveTable
-                                               .getOID(curveName);
-                               if (curveOid == null) {
-                                       curveOid = ObjectIds.getByName(curveName);
-                               }
-                               ECPublicBCPGKey basePubKey = new ECDSAPublicBCPGKey(
-                                               curveOid,
-                                               new BigInteger(1, qVal));
-                               ECPublicBCPGKey assocPubKey = (ECPublicBCPGKey) pubKey
-                                               .getPublicKeyPacket().getKey();
-                               if (!ObjectIds.match(basePubKey.getCurveOID(),
-                                               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");
-       }
-
-       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(")"));
-       }
-}
diff --git a/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SXprUtils.java b/org.eclipse.jgit.gpg.bc/src/org/eclipse/jgit/gpg/bc/internal/keys/SXprUtils.java
deleted file mode 100644 (file)
index 220aa28..0000000
+++ /dev/null
@@ -1,110 +0,0 @@
-/*
- * 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;
-
-// This class is an unmodified copy from Bouncy Castle; needed because it's package-visible only and used by SExprParser.
-
-import java.io.IOException;
-import java.io.InputStream;
-
-import org.bouncycastle.bcpg.HashAlgorithmTags;
-import org.bouncycastle.bcpg.S2K;
-import org.bouncycastle.util.io.Streams;
-
-/**
- * Utility functions for looking a S-expression keys. This class will move when
- * it finds a better home!
- * <p>
- * Format documented here:
- * http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master
- * </p>
- */
-class SXprUtils {
-       private static int readLength(InputStream in, int ch) throws IOException {
-               int len = ch - '0';
-
-               while ((ch = in.read()) >= 0 && ch != ':') {
-                       len = len * 10 + ch - '0';
-               }
-
-               return len;
-       }
-
-       static String readString(InputStream in, int ch) throws IOException {
-               int len = readLength(in, ch);
-
-               char[] chars = new char[len];
-
-               for (int i = 0; i != chars.length; i++) {
-                       chars[i] = (char) in.read();
-               }
-
-               return new String(chars);
-       }
-
-       static byte[] readBytes(InputStream in, int ch) throws IOException {
-               int len = readLength(in, ch);
-
-               byte[] data = new byte[len];
-
-               Streams.readFully(in, data);
-
-               return data;
-       }
-
-       static S2K parseS2K(InputStream in) throws IOException {
-               skipOpenParenthesis(in);
-
-               // Algorithm is hard-coded to SHA1 below anyway.
-               readString(in, in.read());
-               byte[] iv = readBytes(in, in.read());
-               final long iterationCount = Long.parseLong(readString(in, in.read()));
-
-               skipCloseParenthesis(in);
-
-               // we have to return the actual iteration count provided.
-               S2K s2k = new S2K(HashAlgorithmTags.SHA1, iv, (int) iterationCount) {
-                       @Override
-                       public long getIterationCount() {
-                               return iterationCount;
-                       }
-               };
-
-               return s2k;
-       }
-
-       static void skipOpenParenthesis(InputStream in) throws IOException {
-               int ch = in.read();
-               if (ch != '(') {
-                       throw new IOException(
-                                       "unknown character encountered: " + (char) ch); //$NON-NLS-1$
-               }
-       }
-
-       static void skipCloseParenthesis(InputStream in) throws IOException {
-               int ch = in.read();
-               if (ch != ')') {
-                       throw new IOException("unknown character encountered"); //$NON-NLS-1$
-               }
-       }
-}
index a659d38fd3d39096bf29e0c1fa5936fec7fb7e19..a56e418bf4589fdd0c0d4297c1303412da787c52 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 Thomas Wolf <thomas.wolf@paranor.ch> and others
+ * Copyright (C) 2021, 2024 Thomas Wolf <twolf@apache.org> and others
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Distribution License v. 1.0 which is available at
@@ -9,34 +9,36 @@
  */
 package org.eclipse.jgit.gpg.bc.internal.keys;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.EOFException;
+import java.io.BufferedInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.io.StreamCorruptedException;
 import java.net.URISyntaxException;
-import java.nio.charset.StandardCharsets;
 import java.text.MessageFormat;
-import java.util.Arrays;
 
+import org.bouncycastle.bcpg.ECPublicBCPGKey;
+import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
+import org.bouncycastle.gpg.PGPSecretKeyParser;
+import org.bouncycastle.gpg.SExprParser;
+import org.bouncycastle.openpgp.OpenedPGPKeyData;
 import org.bouncycastle.openpgp.PGPException;
 import org.bouncycastle.openpgp.PGPPublicKey;
 import org.bouncycastle.openpgp.PGPSecretKey;
 import org.bouncycastle.openpgp.operator.PBEProtectionRemoverFactory;
 import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
 import org.bouncycastle.openpgp.operator.jcajce.JcePBEProtectionRemoverFactory;
-import org.bouncycastle.util.io.Streams;
 import org.eclipse.jgit.api.errors.CanceledException;
 import org.eclipse.jgit.errors.UnsupportedCredentialItem;
 import org.eclipse.jgit.gpg.bc.internal.BCText;
-import org.eclipse.jgit.util.RawParseUtils;
 
 /**
  * Utilities for reading GPG secret keys from a gpg-agent key file.
  */
 public final class SecretKeys {
 
+       // Maximum nesting depth of sub-lists in an S-Expression for a secret key.
+       private static final int MAX_SEXPR_NESTING = 20;
+
        private SecretKeys() {
                // No instantiation.
        }
@@ -64,12 +66,6 @@ public final class SecretKeys {
                                UnsupportedCredentialItem, URISyntaxException;
        }
 
-       private static final byte[] PROTECTED_KEY = "protected-private-key" //$NON-NLS-1$
-                       .getBytes(StandardCharsets.US_ASCII);
-
-       private static final byte[] OCB_PROTECTED = "openpgp-s2k3-ocb-aes" //$NON-NLS-1$
-                       .getBytes(StandardCharsets.US_ASCII);
-
        /**
         * Reads a GPG secret key from the given stream.
         *
@@ -99,500 +95,59 @@ public final class SecretKeys {
                        PassphraseSupplier passphraseSupplier, PGPPublicKey publicKey)
                        throws IOException, PGPException, CanceledException,
                        UnsupportedCredentialItem, URISyntaxException {
-               byte[] data = Streams.readAll(in);
-               if (data.length == 0) {
-                       throw new EOFException();
-               } else if (data.length < 4 + PROTECTED_KEY.length) {
-                       // +4 for "(21:" for a binary protected key
-                       throw new IOException(
-                                       MessageFormat.format(BCText.get().secretKeyTooShort,
-                                                       Integer.toUnsignedString(data.length)));
-               }
-               SExprParser parser = new SExprParser(calculatorProvider);
-               byte firstChar = data[0];
-               try {
-                       if (firstChar == '(') {
-                               // Binary format.
-                               PBEProtectionRemoverFactory decryptor = null;
-                               if (matches(data, 4, PROTECTED_KEY)) {
-                                       // AES/CBC encrypted.
-                                       decryptor = new JcePBEProtectionRemoverFactory(
-                                                       passphraseSupplier.getPassphrase(),
-                                                       calculatorProvider);
-                               }
-                               try (InputStream sIn = new ByteArrayInputStream(data)) {
-                                       return parser.parseSecretKey(sIn, decryptor, publicKey);
-                               }
-                       }
-                       // Assume it's the new key-value format.
-                       try (ByteArrayInputStream keyIn = new ByteArrayInputStream(data)) {
-                               byte[] rawData = keyFromNameValueFormat(keyIn);
-                               if (!matches(rawData, 1, PROTECTED_KEY)) {
-                                       // Not encrypted human-readable format.
-                                       try (InputStream sIn = new ByteArrayInputStream(
-                                                       convertSexpression(rawData))) {
-                                               return parser.parseSecretKey(sIn, null, publicKey);
-                                       }
-                               }
-                               // An encrypted key from a key-value file. Most likely AES/OCB
-                               // encrypted.
-                               boolean isOCB[] = { false };
-                               byte[] sExp = convertSexpression(rawData, isOCB);
-                               PBEProtectionRemoverFactory decryptor;
-                               if (isOCB[0]) {
-                                       decryptor = new OCBPBEProtectionRemoverFactory(
-                                                       passphraseSupplier.getPassphrase(),
-                                                       calculatorProvider, getAad(sExp));
-                               } else {
-                                       decryptor = new JcePBEProtectionRemoverFactory(
-                                                       passphraseSupplier.getPassphrase(),
-                                                       calculatorProvider);
-                               }
-                               try (InputStream sIn = new ByteArrayInputStream(sExp)) {
-                                       return parser.parseSecretKey(sIn, decryptor, publicKey);
-                               }
-                       }
-               } catch (IOException e) {
-                       throw new PGPException(e.getLocalizedMessage(), e);
-               }
-       }
-
-       /**
-        * Extract the AAD for the OCB decryption from an s-expression.
-        *
-        * @param sExp
-        *            buffer containing a valid binary s-expression
-        * @return the AAD
-        */
-       private static byte[] getAad(byte[] sExp) {
-               // Given a key
-               // @formatter:off
-               // (protected-private-key (rsa ... (protected openpgp-s2k3-ocb-aes ... )(protected-at ...)))
-               //                        A        B                                    C                  D
-               // The AAD is [A..B)[C..D). (From the binary serialized form.)
-               // @formatter:on
-               int i = 1; // Skip initial '('
-               while (sExp[i] != '(') {
-                       i++;
-               }
-               int aadStart = i++;
-               int aadEnd = skip(sExp, aadStart);
-               byte[] protectedPrefix = "(9:protected" //$NON-NLS-1$
-                               .getBytes(StandardCharsets.US_ASCII);
-               while (!matches(sExp, i, protectedPrefix)) {
-                       i++;
-               }
-               int protectedStart = i;
-               int protectedEnd = skip(sExp, protectedStart);
-               byte[] aadData = new byte[aadEnd - aadStart
-                               - (protectedEnd - protectedStart)];
-               System.arraycopy(sExp, aadStart, aadData, 0, protectedStart - aadStart);
-               System.arraycopy(sExp, protectedEnd, aadData, protectedStart - aadStart,
-                               aadEnd - protectedEnd);
-               return aadData;
-       }
-
-       /**
-        * Skips a list including nested lists.
-        *
-        * @param sExp
-        *            buffer containing valid binary s-expression data
-        * @param start
-        *            index of the opening '(' of the list to skip
-        * @return the index after the closing ')' of the skipped list
-        */
-       private static int skip(byte[] sExp, int start) {
-               int i = start + 1;
-               int depth = 1;
-               while (depth > 0) {
-                       switch (sExp[i]) {
-                       case '(':
-                               depth++;
-                               break;
-                       case ')':
-                               depth--;
-                               break;
-                       default:
-                               // We must be on a length
-                               int j = i;
-                               while (sExp[j] >= '0' && sExp[j] <= '9') {
-                                       j++;
-                               }
-                               // j is on the colon
-                               int length = Integer.parseInt(
-                                               new String(sExp, i, j - i, StandardCharsets.US_ASCII));
-                               i = j + length;
-                       }
-                       i++;
-               }
-               return i;
-       }
-
-       /**
-        * Checks whether the {@code needle} matches {@code src} at offset
-        * {@code from}.
-        *
-        * @param src
-        *            to match against {@code needle}
-        * @param from
-        *            position in {@code src} to start matching
-        * @param needle
-        *            to match against
-        * @return {@code true} if {@code src} contains {@code needle} at position
-        *         {@code from}, {@code false} otherwise
-        */
-       private static boolean matches(byte[] src, int from, byte[] needle) {
-               if (from < 0 || from + needle.length > src.length) {
-                       return false;
-               }
-               return org.bouncycastle.util.Arrays.constantTimeAreEqual(needle.length,
-                               src, from, needle, 0);
-       }
-
-       /**
-        * Converts a human-readable serialized s-expression into a binary
-        * serialized s-expression.
-        *
-        * @param humanForm
-        *            to convert
-        * @return the converted s-expression
-        * @throws IOException
-        *             if the conversion fails
-        */
-       private static byte[] convertSexpression(byte[] humanForm)
-                       throws IOException {
-               boolean[] isOCB = { false };
-               return convertSexpression(humanForm, isOCB);
-       }
-
-       /**
-        * Converts a human-readable serialized s-expression into a binary
-        * serialized s-expression.
-        *
-        * @param humanForm
-        *            to convert
-        * @param isOCB
-        *            returns whether the s-expression specified AES/OCB encryption
-        * @return the converted s-expression
-        * @throws IOException
-        *             if the conversion fails
-        */
-       private static byte[] convertSexpression(byte[] humanForm, boolean[] isOCB)
-                       throws IOException {
-               int pos = 0;
-               try (ByteArrayOutputStream out = new ByteArrayOutputStream(
-                               humanForm.length)) {
-                       while (pos < humanForm.length) {
-                               byte b = humanForm[pos];
-                               if (b == '(' || b == ')') {
-                                       out.write(b);
-                                       pos++;
-                               } else if (isGpgSpace(b)) {
-                                       pos++;
-                               } else if (b == '#') {
-                                       // Hex value follows up to the next #
-                                       int i = ++pos;
-                                       while (i < humanForm.length && isHex(humanForm[i])) {
-                                               i++;
-                                       }
-                                       if (i == pos || humanForm[i] != '#') {
-                                               throw new StreamCorruptedException(
-                                                               BCText.get().sexprHexNotClosed);
-                                       }
-                                       if ((i - pos) % 2 != 0) {
-                                               throw new StreamCorruptedException(
-                                                               BCText.get().sexprHexOdd);
-                                       }
-                                       int l = (i - pos) / 2;
-                                       out.write(Integer.toString(l)
-                                                       .getBytes(StandardCharsets.US_ASCII));
-                                       out.write(':');
-                                       while (pos < i) {
-                                               int x = (nibble(humanForm[pos]) << 4)
-                                                               | nibble(humanForm[pos + 1]);
-                                               pos += 2;
-                                               out.write(x);
-                                       }
-                                       pos = i + 1;
-                               } else if (isTokenChar(b)) {
-                                       // Scan the token
-                                       int start = pos++;
-                                       while (pos < humanForm.length
-                                                       && isTokenChar(humanForm[pos])) {
-                                               pos++;
-                                       }
-                                       int l = pos - start;
-                                       if (pos - start == OCB_PROTECTED.length
-                                                       && matches(humanForm, start, OCB_PROTECTED)) {
-                                               isOCB[0] = true;
-                                       }
-                                       out.write(Integer.toString(l)
-                                                       .getBytes(StandardCharsets.US_ASCII));
-                                       out.write(':');
-                                       out.write(humanForm, start, pos - start);
-                               } else if (b == '"') {
-                                       // Potentially quoted string.
-                                       int start = ++pos;
-                                       boolean escaped = false;
-                                       while (pos < humanForm.length
-                                                       && (escaped || humanForm[pos] != '"')) {
-                                               int ch = humanForm[pos++];
-                                               escaped = !escaped && ch == '\\';
-                                       }
-                                       if (pos >= humanForm.length) {
-                                               throw new StreamCorruptedException(
-                                                               BCText.get().sexprStringNotClosed);
-                                       }
-                                       // start is on the first character of the string, pos on the
-                                       // closing quote.
-                                       byte[] dq = dequote(humanForm, start, pos);
-                                       out.write(Integer.toString(dq.length)
-                                                       .getBytes(StandardCharsets.US_ASCII));
-                                       out.write(':');
-                                       out.write(dq);
-                                       pos++;
-                               } else {
-                                       throw new StreamCorruptedException(
-                                                       MessageFormat.format(BCText.get().sexprUnhandled,
-                                                                       Integer.toHexString(b & 0xFF)));
-                               }
-                       }
-                       return out.toByteArray();
+               OpenedPGPKeyData data;
+               try (InputStream keyIn = new BufferedInputStream(in)) {
+                       data = PGPSecretKeyParser.parse(keyIn, MAX_SEXPR_NESTING);
                }
-       }
-
-       /**
-        * GPG-style string de-quoting, which is basically C-style, with some
-        * literal CR/LF escaping.
-        *
-        * @param in
-        *            buffer containing the quoted string
-        * @param from
-        *            index after the opening quote in {@code in}
-        * @param to
-        *            index of the closing quote in {@code in}
-        * @return the dequoted raw string value
-        * @throws StreamCorruptedException
-        *             if object stream is corrupt
-        */
-       private static byte[] dequote(byte[] in, int from, int to)
-                       throws StreamCorruptedException {
-               // Result must be shorter or have the same length
-               byte[] out = new byte[to - from];
-               int j = 0;
-               int i = from;
-               while (i < to) {
-                       byte b = in[i++];
-                       if (b != '\\') {
-                               out[j++] = b;
-                               continue;
-                       }
-                       if (i == to) {
-                               throw new StreamCorruptedException(
-                                               BCText.get().sexprStringInvalidEscapeAtEnd);
-                       }
-                       b = in[i++];
-                       switch (b) {
-                       case 'b':
-                               out[j++] = '\b';
-                               break;
-                       case 'f':
-                               out[j++] = '\f';
-                               break;
-                       case 'n':
-                               out[j++] = '\n';
-                               break;
-                       case 'r':
-                               out[j++] = '\r';
-                               break;
-                       case 't':
-                               out[j++] = '\t';
-                               break;
-                       case 'v':
-                               out[j++] = 0x0B;
-                               break;
-                       case '"':
-                       case '\'':
-                       case '\\':
-                               out[j++] = b;
-                               break;
-                       case '\r':
-                               // Escaped literal line end. If an LF is following, skip that,
-                               // too.
-                               if (i < to && in[i] == '\n') {
-                                       i++;
-                               }
-                               break;
-                       case '\n':
-                               // Same for LF possibly followed by CR.
-                               if (i < to && in[i] == '\r') {
-                                       i++;
-                               }
-                               break;
-                       case 'x':
-                               if (i + 1 >= to || !isHex(in[i]) || !isHex(in[i + 1])) {
-                                       throw new StreamCorruptedException(
-                                                       BCText.get().sexprStringInvalidHexEscape);
-                               }
-                               out[j++] = (byte) ((nibble(in[i]) << 4) | nibble(in[i + 1]));
-                               i += 2;
-                               break;
-                       case '0':
-                       case '1':
-                       case '2':
-                       case '3':
-                               if (i + 2 >= to || !isOctal(in[i]) || !isOctal(in[i + 1])
-                                               || !isOctal(in[i + 2])) {
-                                       throw new StreamCorruptedException(
-                                                       BCText.get().sexprStringInvalidOctalEscape);
-                               }
-                               out[j++] = (byte) (((((in[i] - '0') << 3)
-                                               | (in[i + 1] - '0')) << 3) | (in[i + 2] - '0'));
-                               i += 3;
-                               break;
-                       default:
-                               throw new StreamCorruptedException(MessageFormat.format(
-                                               BCText.get().sexprStringInvalidEscape,
-                                               Integer.toHexString(b & 0xFF)));
-                       }
+               PBEProtectionRemoverFactory decryptor = null;
+               if (isProtected(data)) {
+                       decryptor = new JcePBEProtectionRemoverFactory(
+                                       passphraseSupplier.getPassphrase(), calculatorProvider);
                }
-               return Arrays.copyOf(out, j);
-       }
-
-       /**
-        * Extracts the key from a GPG name-value-pair key file.
-        * <p>
-        * Package-visible for tests only.
-        * </p>
-        *
-        * @param in
-        *            {@link InputStream} to read from; should be buffered
-        * @return the raw key data as extracted from the file
-        * @throws IOException
-        *             if the {@code in} stream cannot be read or does not contain a
-        *             key
-        */
-       static byte[] keyFromNameValueFormat(InputStream in) throws IOException {
-               // It would be nice if we could use RawParseUtils here, but GPG compares
-               // names case-insensitively. We're only interested in the "Key:"
-               // name-value pair.
-               int[] nameLow = { 'k', 'e', 'y', ':' };
-               int[] nameCap = { 'K', 'E', 'Y', ':' };
-               int nameIdx = 0;
-               for (;;) {
-                       int next = in.read();
-                       if (next < 0) {
-                               throw new EOFException();
+               switch (publicKey.getAlgorithm()) {
+               case PublicKeyAlgorithmTags.EDDSA_LEGACY:
+               case PublicKeyAlgorithmTags.Ed25519:
+                       // If we let Bouncy Castle check whether the secret key matches the
+                       // given public key it may get into trouble in some cases with
+                       // ed25519 keys. It appears that we may end up with secret keys
+                       // using the official RFC 8410 OID for ed25519, "1.3.101.112", while
+                       // the public key passed in may have a non-standard OpenPGP-specific
+                       // OID "1.3.6.1.4.1.11591.15.1", or vice versa. Bouncy Castle then
+                       // throws an exception because of the different OIDs.
+                       //
+                       // The work-around is to just read the secret key, and double-check
+                       // later that the OIDs are compatible and the curve points match.
+                       PGPSecretKey secret = data.getKeyData(null, calculatorProvider,
+                                       decryptor, new JcaKeyFingerprintCalculator(),
+                                       MAX_SEXPR_NESTING);
+                       PGPPublicKey pubKeyRead = secret.getPublicKey();
+                       int algoRead = pubKeyRead.getAlgorithm();
+                       if (algoRead != PublicKeyAlgorithmTags.EDDSA_LEGACY
+                                       && algoRead != PublicKeyAlgorithmTags.Ed25519) {
+                               throw new PGPException(BCText.get().keyAlgorithmMismatch);
                        }
-                       if (next == '\n') {
-                               nameIdx = 0;
-                       } else if (nameIdx >= 0) {
-                               if (nameLow[nameIdx] == next || nameCap[nameIdx] == next) {
-                                       nameIdx++;
-                                       if (nameIdx == nameLow.length) {
-                                               break;
-                                       }
-                               } else {
-                                       nameIdx = -1;
-                               }
-                       }
-               }
-               // We're after "Key:". Read the value as continuation lines.
-               int last = ':';
-               byte[] rawData;
-               try (ByteArrayOutputStream out = new ByteArrayOutputStream(8192)) {
-                       for (;;) {
-                               int next = in.read();
-                               if (next < 0) {
-                                       break;
-                               }
-                               if (last == '\n') {
-                                       if (next == ' ' || next == '\t') {
-                                               // Continuation line; skip this whitespace
-                                               last = next;
-                                               continue;
-                                       }
-                                       break; // Not a continuation line
-                               }
-                               out.write(next);
-                               last = next;
+                       ECPublicBCPGKey ec1 = (ECPublicBCPGKey) publicKey
+                                       .getPublicKeyPacket().getKey();
+                       ECPublicBCPGKey ec2 = (ECPublicBCPGKey) pubKeyRead
+                                       .getPublicKeyPacket().getKey();
+                       if (!ObjectIds.match(ec1.getCurveOID(), ec2.getCurveOID())
+                                       || !ec1.getEncodedPoint().equals(ec2.getEncodedPoint())) {
+                               throw new PGPException(
+                                               MessageFormat.format(BCText.get().keyMismatch,
+                                                               ec1.getCurveOID(), ec1.getEncodedPoint(),
+                                                               ec2.getCurveOID(), ec2.getEncodedPoint()));
                        }
-                       rawData = out.toByteArray();
-               }
-               // GPG trims off trailing whitespace, and a line having only whitespace
-               // is a single LF.
-               try (ByteArrayOutputStream out = new ByteArrayOutputStream(
-                               rawData.length)) {
-                       int lineStart = 0;
-                       boolean trimLeading = true;
-                       while (lineStart < rawData.length) {
-                               int nextLineStart = RawParseUtils.nextLF(rawData, lineStart);
-                               if (trimLeading) {
-                                       while (lineStart < nextLineStart
-                                                       && isGpgSpace(rawData[lineStart])) {
-                                               lineStart++;
-                                       }
-                               }
-                               // Trim trailing
-                               int i = nextLineStart - 1;
-                               while (lineStart < i && isGpgSpace(rawData[i])) {
-                                       i--;
-                               }
-                               if (i <= lineStart) {
-                                       // Empty line signifies LF
-                                       out.write('\n');
-                                       trimLeading = true;
-                               } else {
-                                       out.write(rawData, lineStart, i - lineStart + 1);
-                                       trimLeading = false;
-                               }
-                               lineStart = nextLineStart;
-                       }
-                       return out.toByteArray();
-               }
-       }
-
-       private static boolean isGpgSpace(int ch) {
-               return ch == ' ' || ch == '\t' || ch == '\r' || ch == '\n';
-       }
-
-       private static boolean isTokenChar(int ch) {
-               switch (ch) {
-               case '-':
-               case '.':
-               case '/':
-               case '_':
-               case ':':
-               case '*':
-               case '+':
-               case '=':
-                       return true;
+                       return secret;
                default:
-                       if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')
-                                       || (ch >= '0' && ch <= '9')) {
-                               return true;
-                       }
-                       return false;
+                       // For other key types let Bouncy Castle do the check.
+                       return data.getKeyData(publicKey, calculatorProvider, decryptor,
+                                       null, MAX_SEXPR_NESTING);
                }
        }
 
-       private static boolean isHex(int ch) {
-               return (ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F')
-                               || (ch >= 'a' && ch <= 'f');
+       private static boolean isProtected(OpenedPGPKeyData data) {
+               return SExprParser.ProtectionFormatTypeTags.PROTECTED_PRIVATE_KEY == SExprParser
+                               .getProtectionType(data.getKeyExpression().getString(0));
        }
 
-       private static boolean isOctal(int ch) {
-               return (ch >= '0' && ch <= '7');
-       }
-
-       private static int nibble(int ch) {
-               if (ch >= '0' && ch <= '9') {
-                       return ch - '0';
-               } else if (ch >= 'A' && ch <= 'F') {
-                       return ch - 'A' + 10;
-               } else if (ch >= 'a' && ch <= 'f') {
-                       return ch - 'a' + 10;
-               }
-               return -1;
-       }
 }