aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit.gpg.bc.test/tst/org
diff options
context:
space:
mode:
authorThomas Wolf <thomas.wolf@paranor.ch>2021-01-24 02:13:43 +0100
committerMatthias Sohn <matthias.sohn@sap.com>2021-02-19 18:24:47 -0500
commitbdc48aeac756cc0471618b06d793083e63109ee0 (patch)
tree9f6cb3fb0bb543cfd0486e7c3cb36715942b8030 /org.eclipse.jgit.gpg.bc.test/tst/org
parenta14455dfd7ac61e13f2ea8c7d789463efd8eeb72 (diff)
downloadjgit-bdc48aeac756cc0471618b06d793083e63109ee0.tar.gz
jgit-bdc48aeac756cc0471618b06d793083e63109ee0.zip
GPG: handle extended private key format
Add detection for the key-value pair format that was available in gpg-agent for some time already and that has become the default since gpg-agent 2.2.20. If a secret key in the .gnupg/private-keys-v1.d directory is found to have this format, extract the human-readable key from it, convert it to the binary serialized form and hand that to BouncyCastle. Encrypted keys in the new format may use AES/OCB. OCB is a patent- encumbered algorithm; although there is a license for open-source software, that may not be good enough and OCB may not be available in Java. It is not available in the default security provider in Java, and it is also not available in the BouncyCastle version included in Eclipse. Implement AES/OCB decryption, throwing a PGPException with a nice message if the algorithm is not available. Include a copy of the normal s-expression parser of BouncyCastle and fix it to properly handle data from such keys: such keys do not contain an internal hash since the AES/OCB cipher includes and checks a MAC already. Bug: 570501 Change-Id: Ifa6391a809a84cfc6ae7c6610af6a79204b4143b Signed-off-by: Thomas Wolf <thomas.wolf@paranor.ch>
Diffstat (limited to 'org.eclipse.jgit.gpg.bc.test/tst/org')
-rw-r--r--org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java155
1 files changed, 155 insertions, 0 deletions
diff --git a/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java b/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java
new file mode 100644
index 0000000000..4eecaf3ab5
--- /dev/null
+++ b/org.eclipse.jgit.gpg.bc.test/tst/org/eclipse/jgit/gpg/bc/internal/keys/SecretKeysTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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 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;
+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;
+import org.bouncycastle.openpgp.PGPPublicKeyRing;
+import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
+import org.bouncycastle.openpgp.PGPSecretKey;
+import org.bouncycastle.openpgp.PGPUtil;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
+import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
+import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SecretKeysTest {
+
+ @BeforeClass
+ public static void ensureBC() {
+ if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
+ Security.addProvider(new BouncyCastleProvider());
+ }
+ }
+
+ 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;
+
+ TestData(String name, boolean encrypted) {
+ this.name = name;
+ this.encrypted = encrypted;
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+ }
+
+ @Parameters(name = "{0}")
+ public static TestData[] initTestData() {
+ return new TestData[] {
+ new TestData("2FB05DBB70FC07CB84C13431F640CA6CEA1DBF8A", false),
+ new TestData("66CCECEC2AB46A9735B10FEC54EDF9FD0F77BAF9", true),
+ new TestData("F727FAB884DA3BD402B6E0F5472E108D21033124", true),
+ new TestData("faked", false) };
+ }
+
+ private static byte[] readTestKey(String filename) throws Exception {
+ try (InputStream in = new BufferedInputStream(
+ SecretKeysTest.class.getResourceAsStream(filename))) {
+ return SecretKeys.keyFromNameValueFormat(in);
+ }
+ }
+
+ private static PGPPublicKey readAsc(InputStream in)
+ throws IOException, PGPException {
+ PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(
+ PGPUtil.getDecoderStream(in), new JcaKeyFingerprintCalculator());
+
+ Iterator<PGPPublicKeyRing> keyRings = pgpPub.getKeyRings();
+ while (keyRings.hasNext()) {
+ PGPPublicKeyRing keyRing = keyRings.next();
+
+ Iterator<PGPPublicKey> keys = keyRing.getPublicKeys();
+ if (keys.hasNext()) {
+ return keys.next();
+ }
+ }
+ return null;
+ }
+
+ // Injected by JUnit
+ @Parameter
+ public TestData data;
+
+ @Test
+ public void testKeyRead() throws Exception {
+ 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) {
+ PGPPublicKey publicKey = readAsc(pubIn);
+ // Do a full test trying to load the secret key.
+ PGPDigestCalculatorProvider calculatorProvider = new JcaPGPDigestCalculatorProviderBuilder()
+ .build();
+ try (InputStream in = new BufferedInputStream(this.getClass()
+ .getResourceAsStream(data.name + ".key"))) {
+ PGPSecretKey secretKey = SecretKeys.readSecretKey(in,
+ calculatorProvider, () -> "nonsense".toCharArray(),
+ publicKey);
+ assertNotNull(secretKey);
+ } catch (PGPException e) {
+ // Currently we may not be able to load OCB-encrypted keys.
+ assertTrue(e.getMessage().contains("OCB"));
+ assertTrue(data.encrypted);
+ assertFalse(ocbAvailable());
+ }
+ }
+ }
+ }
+
+}