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

Rework ASCII armor API

This commit is contained in:
Paul Schaub 2025-05-07 14:06:34 +02:00
parent 165ea8ae76
commit d477203a77
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
import java.io.ByteArrayOutputStream
import java.io.OutputStream
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.PGPPublicKeyRing
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.OpenPGPKeyGenerator
import org.bouncycastle.openpgp.api.OpenPGPKeyReader
import org.bouncycastle.openpgp.api.OpenPGPSignature
import org.bouncycastle.openpgp.api.bc.BcOpenPGPApi
import org.pgpainless.algorithm.OpenPGPKeyVersion
import org.pgpainless.bouncycastle.PolicyAdapter
@ -59,6 +64,46 @@ class PGPainless(
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.
*
@ -290,10 +335,13 @@ class PGPainless(
*/
@JvmStatic
fun asciiArmor(key: PGPKeyRing): String =
if (key is PGPSecretKeyRing) ArmorUtils.toAsciiArmoredString(key)
else ArmorUtils.toAsciiArmoredString(key as PGPPublicKeyRing)
getInstance().toAsciiArmor(getInstance().toKeyOrCertificate(key))
@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
@ -318,8 +366,10 @@ class PGPainless(
* @throws IOException in case of an error during the armoring process
*/
@JvmStatic
@Deprecated("Covert to OpenPGPSignature and call .toAsciiArmoredString() instead.")
fun asciiArmor(signature: PGPSignature): String = ArmorUtils.toAsciiArmoredString(signature)
@Deprecated(
"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

View file

@ -229,7 +229,7 @@ class ArmorUtils {
* @return header map
*/
@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 userIds = KeyRingUtils.getUserIdsIgnoringInvalidUTF8(publicKey)
val first: String? = userIds.firstOrNull()

View file

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

View file

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

View file

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

View file

@ -7,7 +7,6 @@ package org.pgpainless.sop
import java.io.InputStream
import java.io.OutputStream
import org.pgpainless.PGPainless
import org.pgpainless.util.ArmorUtils
import org.pgpainless.util.ArmoredOutputStreamFactory
import sop.Ready
import sop.operation.ExtractCert
@ -26,10 +25,8 @@ class ExtractCertImpl(private val api: PGPainless) : ExtractCert {
if (certs.size == 1) {
val cert = certs[0]
// This way we have a nice armor header with fingerprint and user-ids
val armorOut =
ArmorUtils.toAsciiArmoredStream(cert.pgpKeyRing, outputStream)
armorOut.write(cert.encoded)
armorOut.close()
val armored = cert.toAsciiArmoredString()
outputStream.write(armored.toByteArray())
} else {
// for multiple certs, add no info headers to the ASCII armor
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.rsa.RsaLength
import org.pgpainless.key.generation.type.xdh_legacy.XDHLegacySpec
import org.pgpainless.util.ArmorUtils
import org.pgpainless.util.Passphrase
import sop.Profile
import sop.Ready
@ -50,9 +49,8 @@ class GenerateKeyImpl(private val api: PGPainless) : GenerateKey {
return object : Ready() {
override fun writeTo(outputStream: OutputStream) {
if (armor) {
val armorOut = ArmorUtils.toAsciiArmoredStream(key.pgpKeyRing, outputStream)
key.pgpKeyRing.encode(armorOut)
armorOut.close()
val armored = key.toAsciiArmoredString()
outputStream.write(armored.toByteArray())
} else {
key.pgpKeyRing.encode(outputStream)
}