From b05f6887bd3cbfe34d589daa0548c5b9aba449cb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sat, 15 May 2021 15:56:51 +0200 Subject: [PATCH] Better support for Armor headers --- .../java/org/pgpainless/util/ArmorUtils.java | 55 ++++++++++++- .../util/ArmoredOutputStreamFactory.java | 2 +- .../org/pgpainless/util/ArmorUtilsTest.java | 77 +++++++++++++++++++ 3 files changed, 131 insertions(+), 3 deletions(-) create mode 100644 pgpainless-core/src/test/java/org/pgpainless/util/ArmorUtilsTest.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java b/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java index 02d5892d..10258606 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/ArmorUtils.java @@ -19,17 +19,27 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.Iterator; +import java.util.List; +import org.bouncycastle.bcpg.ArmoredInputStream; import org.bouncycastle.bcpg.ArmoredOutputStream; import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.util.io.Streams; +import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.key.OpenPgpV4Fingerprint; public class ArmorUtils { + public static final String HEADER_COMMENT = "Comment"; + public static final String HEADER_VERSION = "Version"; + public static final String HEADER_MESSAGEID = "MessageID"; + public static final String HEADER_HASH = "Hash"; + public static final String HEADER_CHARSET = "Charset"; + public static String toAsciiArmoredString(PGPSecretKeyRing secretKeys) throws IOException { MultiMap header = keyToHeader(secretKeys); return toAsciiArmoredString(secretKeys.getEncoded(), header); @@ -45,9 +55,9 @@ public class ArmorUtils { OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint(keyRing); Iterator userIds = keyRing.getPublicKey().getUserIDs(); - header.put("Comment", fingerprint.prettyPrint()); + header.put(HEADER_COMMENT, fingerprint.prettyPrint()); if (userIds.hasNext()) { - header.put("Comment", userIds.next()); + header.put(HEADER_COMMENT, userIds.next()); } return header; } @@ -79,4 +89,45 @@ public class ArmorUtils { return out.toString(); } + + public static List getCommendHeaderValues(ArmoredInputStream armor) { + return getArmorHeaderValues(armor, HEADER_COMMENT); + } + + public static List getMessageIdHeaderValues(ArmoredInputStream armor) { + return getArmorHeaderValues(armor, HEADER_MESSAGEID); + } + + public static List getHashHeaderValues(ArmoredInputStream armor) { + return getArmorHeaderValues(armor, HEADER_HASH); + } + + public static List getHashAlgorithms(ArmoredInputStream armor) { + List algorithmNames = getHashHeaderValues(armor); + List algorithms = new ArrayList<>(algorithmNames.size()); + for (String name : algorithmNames) { + algorithms.add(HashAlgorithm.fromName(name)); + } + return algorithms; + } + + public static List getVersionHeaderValues(ArmoredInputStream armor) { + return getArmorHeaderValues(armor, HEADER_VERSION); + } + + public static List getCharsetHeaderValues(ArmoredInputStream armor) { + return getArmorHeaderValues(armor, HEADER_CHARSET); + } + + public static List getArmorHeaderValues(ArmoredInputStream armor, String headerKey) { + String[] header = armor.getArmorHeaders(); + String key = headerKey + ": "; + List values = new ArrayList<>(); + for (String line : header) { + if (line.startsWith(key)) { + values.add(line.substring(key.length())); + } + } + return values; + } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/util/ArmoredOutputStreamFactory.java b/pgpainless-core/src/main/java/org/pgpainless/util/ArmoredOutputStreamFactory.java index 57deb8bc..9dc33c52 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/util/ArmoredOutputStreamFactory.java +++ b/pgpainless-core/src/main/java/org/pgpainless/util/ArmoredOutputStreamFactory.java @@ -28,7 +28,7 @@ public class ArmoredOutputStreamFactory { public static ArmoredOutputStream get(OutputStream outputStream) { ArmoredOutputStream armoredOutputStream = new ArmoredOutputStream(outputStream); - armoredOutputStream.setHeader(ArmoredOutputStream.VERSION_HDR, VERSION); + armoredOutputStream.setHeader(ArmorUtils.HEADER_VERSION, VERSION); return armoredOutputStream; } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/util/ArmorUtilsTest.java b/pgpainless-core/src/test/java/org/pgpainless/util/ArmorUtilsTest.java new file mode 100644 index 00000000..cd030bf2 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/util/ArmorUtilsTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2021 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.junit.jupiter.api.Test; +import org.pgpainless.algorithm.HashAlgorithm; + +public class ArmorUtilsTest { + + @Test + public void testParseArmorHeader() throws IOException { + String armoredKey = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Version: BCPG v1.68\n" + + "Hash: SHA512\n" + + "Comment: This is a comment\n" + + "Comment: This is another comment\n" + + "\n" + + "mDMEYJ/OsRYJKwYBBAHaRw8BAQdAaOs6IF1fWhN/dqwfSrxD/MNnBXVEx8WlecCa\n" + + "cAiSCv60DnRlc3RAdGVzdC50ZXN0iHgEExYKACAFAmCfzrECGwMFFgIDAQAECwkI\n" + + "BwUVCgkICwIeAQIZAQAKCRD2lyhrcqSwzDWIAP9i6LfaUp3gEhGQR3FojyhfPVB1\n" + + "Y3bBU7osj/XOpEN6RAD/YzL9VO45yYp1IUvU1NQWJy42ZHHZy4ZrjULLQ/HbpQW4\n" + + "OARgn86xEgorBgEEAZdVAQUBAQdASAPiuOakmDdL0HaSemeNB5Hl7lniD8vCeFgz\n" + + "OcgWjSYDAQgHiHUEGBYKAB0FAmCfzrECGwwFFgIDAQAECwkIBwUVCgkICwIeAQAK\n" + + "CRD2lyhrcqSwzJ4HAQD7uDYyEsqEGHI4LULfphxPSC5nG9pbBA3mL4ze46uDmAD/\n" + + "aea172D0TfBwQXZxujLECTce5/1jyTaM+ee8gfw1BQ8=\n" + + "=RQHd\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + + ByteArrayInputStream in = new ByteArrayInputStream(armoredKey.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream armorIn = new ArmoredInputStream(in); + + // No charset + assertEquals(0, ArmorUtils.getCharsetHeaderValues(armorIn).size()); + + // Version + List versionHeader = ArmorUtils.getVersionHeaderValues(armorIn); + assertEquals(1, versionHeader.size()); + assertEquals("BCPG v1.68", versionHeader.get(0)); + + // Hash + List hashHeader = ArmorUtils.getHashHeaderValues(armorIn); + assertEquals(1, hashHeader.size()); + assertEquals("SHA512", hashHeader.get(0)); + List hashes = ArmorUtils.getHashAlgorithms(armorIn); + assertEquals(HashAlgorithm.SHA512, hashes.get(0)); + + // Comment + List commentHeader = ArmorUtils.getCommendHeaderValues(armorIn); + assertEquals(2, commentHeader.size()); + assertEquals("This is a comment", commentHeader.get(0)); + assertEquals("This is another comment", commentHeader.get(1)); + + // MessageID + assertEquals(0, ArmorUtils.getMessageIdHeaderValues(armorIn).size()); + } +}