1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-09 10:19:39 +02:00

Rework ASCII armor API

This commit is contained in:
Paul Schaub 2025-05-07 14:06:34 +02:00
parent 9856aa43c4
commit 3ccc8601d7
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
7 changed files with 65 additions and 19 deletions

View file

@ -4,8 +4,12 @@
package org.pgpainless package org.pgpainless
import java.io.ByteArrayOutputStream
import java.io.OutputStream import java.io.OutputStream
import java.util.* import java.util.*
import org.bouncycastle.bcpg.ArmoredOutputStream
import org.bouncycastle.bcpg.BCPGOutputStream
import org.bouncycastle.bcpg.PacketFormat
import org.bouncycastle.openpgp.PGPKeyRing import org.bouncycastle.openpgp.PGPKeyRing
import org.bouncycastle.openpgp.PGPPublicKeyRing import org.bouncycastle.openpgp.PGPPublicKeyRing
import org.bouncycastle.openpgp.PGPSecretKeyRing import org.bouncycastle.openpgp.PGPSecretKeyRing
@ -16,6 +20,7 @@ import org.bouncycastle.openpgp.api.OpenPGPImplementation
import org.bouncycastle.openpgp.api.OpenPGPKey import org.bouncycastle.openpgp.api.OpenPGPKey
import org.bouncycastle.openpgp.api.OpenPGPKeyGenerator import org.bouncycastle.openpgp.api.OpenPGPKeyGenerator
import org.bouncycastle.openpgp.api.OpenPGPKeyReader import org.bouncycastle.openpgp.api.OpenPGPKeyReader
import org.bouncycastle.openpgp.api.OpenPGPSignature
import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi
import org.pgpainless.algorithm.OpenPGPKeyVersion import org.pgpainless.algorithm.OpenPGPKeyVersion
import org.pgpainless.bouncycastle.PolicyAdapter import org.pgpainless.bouncycastle.PolicyAdapter
@ -59,6 +64,46 @@ class PGPainless(
api = BcOpenPGPApi(implementation) api = BcOpenPGPApi(implementation)
} }
@JvmOverloads
fun toAsciiArmor(
certOrKey: OpenPGPCertificate,
packetFormat: PacketFormat = PacketFormat.ROUNDTRIP
): String {
val armorBuilder = ArmoredOutputStream.builder().clearHeaders()
ArmorUtils.keyToHeader(certOrKey.primaryKey.pgpPublicKey)
.getOrDefault(ArmorUtils.HEADER_COMMENT, setOf())
.forEach { armorBuilder.addComment(it) }
return certOrKey.toAsciiArmoredString(packetFormat, armorBuilder)
}
@JvmOverloads
fun toAsciiArmor(
signature: OpenPGPSignature,
packetFormat: PacketFormat = PacketFormat.ROUNDTRIP
): String {
val armorBuilder = ArmoredOutputStream.builder().clearHeaders()
armorBuilder.addComment(signature.keyIdentifier.toPrettyPrint())
return signature.toAsciiArmoredString(packetFormat, armorBuilder)
}
@JvmOverloads
fun toAsciiArmor(
signature: PGPSignature,
packetFormat: PacketFormat = PacketFormat.ROUNDTRIP
): String {
val armorBuilder = ArmoredOutputStream.builder().clearHeaders()
OpenPGPSignature.getMostExpressiveIdentifier(signature.keyIdentifiers)?.let {
armorBuilder.addComment(it.toPrettyPrint())
}
val bOut = ByteArrayOutputStream()
val aOut = armorBuilder.build(bOut)
val pOut = BCPGOutputStream(aOut, packetFormat)
signature.encode(pOut)
pOut.close()
aOut.close()
return bOut.toString()
}
/** /**
* Generate a new [OpenPGPKey] from predefined templates. * Generate a new [OpenPGPKey] from predefined templates.
* *
@ -290,10 +335,13 @@ class PGPainless(
*/ */
@JvmStatic @JvmStatic
fun asciiArmor(key: PGPKeyRing): String = fun asciiArmor(key: PGPKeyRing): String =
if (key is PGPSecretKeyRing) ArmorUtils.toAsciiArmoredString(key) getInstance().toAsciiArmor(getInstance().toKeyOrCertificate(key))
else ArmorUtils.toAsciiArmoredString(key as PGPPublicKeyRing)
@JvmStatic fun asciiArmor(cert: OpenPGPCertificate) = asciiArmor(cert.pgpKeyRing) @JvmStatic
@Deprecated(
"Call getInstance().toAsciiArmor(cert) instead.",
replaceWith = ReplaceWith("getInstance().toAsciiArmor(cert)"))
fun asciiArmor(cert: OpenPGPCertificate): String = getInstance().toAsciiArmor(cert)
/** /**
* Wrap a key of certificate in ASCII armor and write the result into the given * Wrap a key of certificate in ASCII armor and write the result into the given
@ -318,8 +366,10 @@ class PGPainless(
* @throws IOException in case of an error during the armoring process * @throws IOException in case of an error during the armoring process
*/ */
@JvmStatic @JvmStatic
@Deprecated("Covert to OpenPGPSignature and call .toAsciiArmoredString() instead.") @Deprecated(
fun asciiArmor(signature: PGPSignature): String = ArmorUtils.toAsciiArmoredString(signature) "Call toAsciiArmor(signature) on an instance of PGPainless instead.",
replaceWith = ReplaceWith("getInstance().toAsciiArmor(signature)"))
fun asciiArmor(signature: PGPSignature): String = getInstance().toAsciiArmor(signature)
/** /**
* Create an [EncryptionBuilder], which can be used to encrypt and/or sign data using * Create an [EncryptionBuilder], which can be used to encrypt and/or sign data using

View file

@ -229,7 +229,7 @@ class ArmorUtils {
* @return header map * @return header map
*/ */
@JvmStatic @JvmStatic
private fun keyToHeader(publicKey: PGPPublicKey): Map<String, Set<String>> { fun keyToHeader(publicKey: PGPPublicKey): Map<String, Set<String>> {
val headerMap = mutableMapOf<String, MutableSet<String>>() val headerMap = mutableMapOf<String, MutableSet<String>>()
val userIds = KeyRingUtils.getUserIdsIgnoringInvalidUTF8(publicKey) val userIds = KeyRingUtils.getUserIdsIgnoringInvalidUTF8(publicKey)
val first: String? = userIds.firstOrNull() val first: String? = userIds.firstOrNull()

View file

@ -113,10 +113,11 @@ public class CanonicalizedDataEncryptionTest {
@BeforeAll @BeforeAll
public static void readKeys() throws IOException { public static void readKeys() throws IOException {
secretKeys = PGPainless.getInstance().readKey().parseKey(KEY); PGPainless api = PGPainless.getInstance();
secretKeys = api.readKey().parseKey(KEY);
publicKeys = secretKeys.toCertificate(); publicKeys = secretKeys.toCertificate();
// CHECKSTYLE:OFF // CHECKSTYLE:OFF
System.out.println(PGPainless.asciiArmor(secretKeys)); System.out.println(api.toAsciiArmor(secretKeys));
// CHECKSTYLE:ON // CHECKSTYLE:ON
} }

View file

@ -599,7 +599,7 @@ class KeyRingReaderTest {
public void testReadKeyRingWithArmoredPublicKey() throws IOException { public void testReadKeyRingWithArmoredPublicKey() throws IOException {
OpenPGPKey secretKeys = api.generateKey().modernKeyRing("Alice <alice@pgpainless.org>"); OpenPGPKey secretKeys = api.generateKey().modernKeyRing("Alice <alice@pgpainless.org>");
OpenPGPCertificate publicKeys = secretKeys.toCertificate(); OpenPGPCertificate publicKeys = secretKeys.toCertificate();
String armored = PGPainless.asciiArmor(publicKeys); String armored = api.toAsciiArmor(publicKeys);
PGPKeyRing keyRing = PGPainless.readKeyRing() PGPKeyRing keyRing = PGPainless.readKeyRing()
.keyRing(armored); .keyRing(armored);

View file

@ -132,7 +132,7 @@ public class ArmorUtilsTest {
@Test @Test
public void signatureToAsciiArmoredString() { public void signatureToAsciiArmoredString() {
String SIG = "-----BEGIN PGP SIGNATURE-----\n" + String SIG = "-----BEGIN PGP SIGNATURE-----\n" +
"Version: PGPainless\n" + "Comment: 4F66 5C4D C2C4 660B C642 5E41 5736 E693 1ACF 370C\n" +
"\n" + "\n" +
"iHUEARMKAB0WIQRPZlxNwsRmC8ZCXkFXNuaTGs83DAUCYJ/x5gAKCRBXNuaTGs83\n" + "iHUEARMKAB0WIQRPZlxNwsRmC8ZCXkFXNuaTGs83DAUCYJ/x5gAKCRBXNuaTGs83\n" +
"DFRwAP9/4wMvV3WcX59Clo7mkRce6iwW3VBdiN+yMu3tjmHB2wD/RfE28Q1v4+eo\n" + "DFRwAP9/4wMvV3WcX59Clo7mkRce6iwW3VBdiN+yMu3tjmHB2wD/RfE28Q1v4+eo\n" +

View file

@ -7,7 +7,6 @@ package org.pgpainless.sop
import java.io.InputStream import java.io.InputStream
import java.io.OutputStream import java.io.OutputStream
import org.pgpainless.PGPainless import org.pgpainless.PGPainless
import org.pgpainless.util.ArmorUtils
import org.pgpainless.util.ArmoredOutputStreamFactory import org.pgpainless.util.ArmoredOutputStreamFactory
import sop.Ready import sop.Ready
import sop.operation.ExtractCert import sop.operation.ExtractCert
@ -26,10 +25,8 @@ class ExtractCertImpl(private val api: PGPainless) : ExtractCert {
if (certs.size == 1) { if (certs.size == 1) {
val cert = certs[0] val cert = certs[0]
// This way we have a nice armor header with fingerprint and user-ids // This way we have a nice armor header with fingerprint and user-ids
val armorOut = val armored = cert.toAsciiArmoredString()
ArmorUtils.toAsciiArmoredStream(cert.pgpKeyRing, outputStream) outputStream.write(armored.toByteArray())
armorOut.write(cert.encoded)
armorOut.close()
} else { } else {
// for multiple certs, add no info headers to the ASCII armor // for multiple certs, add no info headers to the ASCII armor
val armorOut = ArmoredOutputStreamFactory.get(outputStream) val armorOut = ArmoredOutputStreamFactory.get(outputStream)

View file

@ -18,7 +18,6 @@ import org.pgpainless.key.generation.type.KeyType
import org.pgpainless.key.generation.type.eddsa_legacy.EdDSALegacyCurve import org.pgpainless.key.generation.type.eddsa_legacy.EdDSALegacyCurve
import org.pgpainless.key.generation.type.rsa.RsaLength import org.pgpainless.key.generation.type.rsa.RsaLength
import org.pgpainless.key.generation.type.xdh_legacy.XDHLegacySpec import org.pgpainless.key.generation.type.xdh_legacy.XDHLegacySpec
import org.pgpainless.util.ArmorUtils
import org.pgpainless.util.Passphrase import org.pgpainless.util.Passphrase
import sop.Profile import sop.Profile
import sop.Ready import sop.Ready
@ -50,9 +49,8 @@ class GenerateKeyImpl(private val api: PGPainless) : GenerateKey {
return object : Ready() { return object : Ready() {
override fun writeTo(outputStream: OutputStream) { override fun writeTo(outputStream: OutputStream) {
if (armor) { if (armor) {
val armorOut = ArmorUtils.toAsciiArmoredStream(key.pgpKeyRing, outputStream) val armored = key.toAsciiArmoredString()
key.pgpKeyRing.encode(armorOut) outputStream.write(armored.toByteArray())
armorOut.close()
} else { } else {
key.pgpKeyRing.encode(outputStream) key.pgpKeyRing.encode(outputStream)
} }