diff --git a/rpm/src/main/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessor.java b/rpm/src/main/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessor.java new file mode 100644 index 0000000..130f05b --- /dev/null +++ b/rpm/src/main/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessor.java @@ -0,0 +1,221 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * mat1e, Groupe EDF - initial API and implementation + ********************************************************************************/ +package org.eclipse.packager.rpm.signature; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.ArrayList; +import java.util.List; + +import org.apache.commons.compress.utils.IOUtils; +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.bc.BcPGPSecretKeyRing; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.eclipse.packager.rpm.RpmSignatureTag; +import org.eclipse.packager.rpm.Rpms; +import org.eclipse.packager.rpm.header.Header; +import org.eclipse.packager.rpm.header.Headers; +import org.eclipse.packager.rpm.info.RpmInformation; +import org.eclipse.packager.rpm.info.RpmInformations; +import org.eclipse.packager.rpm.parse.RpmInputStream; + +/** + * + * Sign existing RPM file by calling + * {@link #perform(File, InputStream, String, OutputStream)} + * + * + */ +public class RpmFileSignatureProcessor { + + private RpmFileSignatureProcessor() { + // Hide default constructor because of the static context + } + + /** + *

+ * Perform the signature of the given RPM file with the given private key. This + * support only PGP. Write the result into the given {@link OutputStream} + *

+ * + * @param rpm : RPM file + * @param privateKeyIn : encrypted private key as {@link InputStream} + * @param passphrase : passphrase to decrypt the private key + * @param out : {@link OutputStream} to write to + * @throws IOException + * @throws PGPException + */ + public static void perform(File rpm, InputStream privateKeyIn, String passphrase, OutputStream out) + throws IOException, PGPException { + + final long leadLength = 96; + long signatureHeaderStart = 0L; + long signatureHeaderLength = 0L; + long payloadHeaderStart = 0L; + long payloadHeaderLength = 0L; + long payloadStart = 0L; + long archiveSize = 0L; + long payloadSize = 0L; + byte[] signatureHeader; + + if (!rpm.exists()) { + throw new IOException("The file " + rpm.getName() + " does not exist"); + } + + // Extract private key + PGPPrivateKey privateKey = getPrivateKey(privateKeyIn, passphrase); + + // Get the informations of the RPM + try (RpmInputStream rpmIn = new RpmInputStream(new FileInputStream(rpm))) { + signatureHeaderStart = rpmIn.getSignatureHeader().getStart(); + signatureHeaderLength = rpmIn.getSignatureHeader().getLength(); + payloadHeaderStart = rpmIn.getPayloadHeader().getStart(); + payloadHeaderLength = rpmIn.getPayloadHeader().getLength(); + RpmInformation info = RpmInformations.makeInformation(rpmIn); + payloadStart = info.getHeaderEnd(); + archiveSize = info.getArchiveSize(); + } + + if (signatureHeaderStart == 0L || signatureHeaderLength == 0L || payloadHeaderStart == 0L + || payloadHeaderLength == 0L || payloadStart == 0L || archiveSize == 0L) { + throw new IOException("Unable to read " + rpm.getName() + " informations."); + } + + // Build the signature header by digest payload header + payload + try (FileInputStream in = new FileInputStream(rpm)) { + FileChannel channelIn = in.getChannel(); + payloadSize = channelIn.size() - payloadStart; + channelIn.position(leadLength + signatureHeaderLength); + ByteBuffer payloadHeaderBuff = ByteBuffer.allocate((int) payloadHeaderLength); + IOUtils.readFully(channelIn, payloadHeaderBuff); + ByteBuffer payloadBuff = ByteBuffer.allocate((int) payloadSize); + IOUtils.readFully(channelIn, payloadBuff); + signatureHeader = getSignature(privateKey, payloadHeaderBuff, payloadBuff, archiveSize); + } + + // Write to the OutputStream + try (FileInputStream in = new FileInputStream(rpm)) { + IOUtils.copyRange(in, leadLength, out); + IOUtils.skip(in, signatureHeaderLength); + out.write(signatureHeader); + IOUtils.copy(in, out); + } + } + + /** + *

+ * Sign the payload with its header with the given private key, see https://rpm-software-management.github.io/rpm/manual/format.html + *

+ * + * @param privateKey : private key already extracted + * @param payloadHeader : Payload's header as {@link ByteBuffer} + * @param payload : Payload as {@link ByteBuffer} + * @param archiveSize : archiveSize retrieved in {@link RpmInformation} + * @return the signature header as a bytes array + * @throws IOException + */ + private static byte[] getSignature(PGPPrivateKey privateKey, ByteBuffer payloadHeader, ByteBuffer payload, + long archiveSize) throws IOException { + Header signatureHeader = new Header<>(); + List signatureProcessors = getSignatureProcessors(privateKey); + payloadHeader.flip(); + payload.flip(); + for (SignatureProcessor processor : signatureProcessors) { + processor.init(archiveSize); + processor.feedHeader(payloadHeader.slice()); + processor.feedPayloadData(payload.slice()); + processor.finish(signatureHeader); + } + ByteBuffer signatureBuf = Headers.render(signatureHeader.makeEntries(), true, Rpms.IMMUTABLE_TAG_SIGNATURE); + final int payloadSize = signatureBuf.remaining(); + final int padding = Rpms.padding(payloadSize); + byte[] signature = safeReadBuffer(signatureBuf); + ByteArrayOutputStream result = new ByteArrayOutputStream(); + result.write(signature); + if (padding > 0) { + result.write(safeReadBuffer(ByteBuffer.wrap(Rpms.EMPTY_128, 0, padding))); + } + return result.toByteArray(); + } + + /** + *

+ * Safe read (without buffer bytes) the given buffer and return it as a byte + * array + *

+ * + * @param buf : the {@link ByteBuffer} to read + * @return a bytes array + * @throws IOException + */ + private static byte[] safeReadBuffer(ByteBuffer buf) throws IOException { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + while (buf.hasRemaining()) { + result.write(buf.get()); + } + return result.toByteArray(); + } + + /** + *

+ * Return all {@link SignatureProcessor} required to perform signature + * {@link SignatureProcessors} + *

+ * + * @param privateKey : the private key, already extracted + * + * @return {@link List} of {@link SignatureProcessor} + */ + private static List getSignatureProcessors(PGPPrivateKey privateKey) { + List signatureProcessors = new ArrayList<>(); + signatureProcessors.add(SignatureProcessors.size()); + signatureProcessors.add(SignatureProcessors.sha256Header()); + signatureProcessors.add(SignatureProcessors.sha1Header()); + signatureProcessors.add(SignatureProcessors.md5()); + signatureProcessors.add(SignatureProcessors.payloadSize()); + signatureProcessors.add(new RsaSignatureProcessor(privateKey)); + return signatureProcessors; + } + + /** + *

+ * Decrypt and retrieve the private key + *

+ * + * @param privateKeyIn : InputStream containing the encrypted private key + * @param passphrase : passphrase to decrypt private key + * @return private key as {@link PGPPrivateKey} + * @throws PGPException : if the private key cannot be extrated + * @throws IOException : if error happened with InputStream + */ + private static PGPPrivateKey getPrivateKey(InputStream privateKeyIn, String passphrase) + throws PGPException, IOException { + ArmoredInputStream armor = new ArmoredInputStream(privateKeyIn); + PGPSecretKeyRing secretKeyRing = new BcPGPSecretKeyRing(armor); + PGPSecretKey secretKey = secretKeyRing.getSecretKey(); + return secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()) + .build(passphrase.toCharArray())); + } +} diff --git a/rpm/src/test/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessorTest.java b/rpm/src/test/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessorTest.java new file mode 100644 index 0000000..f894dc7 --- /dev/null +++ b/rpm/src/test/java/org/eclipse/packager/rpm/signature/RpmFileSignatureProcessorTest.java @@ -0,0 +1,134 @@ +/******************************************************************************** + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * mat1e, Groupe EDF - initial API and implementation + ********************************************************************************/ +package org.eclipse.packager.rpm.signature; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.fail; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.bc.BcPGPPublicKeyRing; +import org.eclipse.packager.rpm.RpmSignatureTag; +import org.eclipse.packager.rpm.Rpms; +import org.eclipse.packager.rpm.parse.InputHeader; +import org.eclipse.packager.rpm.parse.RpmInputStream; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; +import org.junit.jupiter.api.Order; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestMethodOrder; + +@TestMethodOrder(OrderAnnotation.class) +public class RpmFileSignatureProcessorTest { + + private static final String SOURCE_FILE_PATH = "src/test/resources/data/org.eclipse.scada-0.2.1-1.noarch.rpm"; + private static final String PRIVATE_KEY_PATH = "src/test/resources/key/private_key.txt"; + private static final String PUBLIC_KEY_PATH = "src/test/resources/key/public_key.txt"; + private static final String RESULT_FILE_PATH = "src/test/resources/result/org.eclipse.scada-0.2.1-1.noarch.rpm"; + private static final String RESULT_DIR = "src/test/resources/result"; + + @Test + @Order(1) + public void testSigningExistingRpm() throws IOException, PGPException { + // Read files + final String passPhrase = "testkey"; // Do not change + File rpm = new File(SOURCE_FILE_PATH); + File private_key = new File(PRIVATE_KEY_PATH); + if (!rpm.exists() || !private_key.exists()) { + fail("Input files rpm or private_key does not exist"); + } + // Init the signed RPM + File resultDirectory = new File(RESULT_DIR); + resultDirectory.mkdir(); + File signedRpm = new File(RESULT_FILE_PATH); + signedRpm.createNewFile(); + + try (FileOutputStream resultOut = new FileOutputStream(signedRpm); + InputStream privateKeyStream = new FileInputStream(private_key)) { + // Sign the RPM + RpmFileSignatureProcessor.perform(rpm, privateKeyStream, passPhrase, resultOut); + + // Read the initial (non signed) rpm file + RpmInputStream initialRpm = new RpmInputStream(new FileInputStream(rpm)); + initialRpm.available(); + initialRpm.close(); + InputHeader initialHeader = initialRpm.getSignatureHeader(); + + // Read the signed rpm file + RpmInputStream rpmSigned = new RpmInputStream(new FileInputStream(signedRpm)); + rpmSigned.available(); + rpmSigned.close(); + InputHeader signedHeader = rpmSigned.getSignatureHeader(); + + // Get informations of the initial rpm file + int initialSize = (int) initialHeader.getEntry(RpmSignatureTag.SIZE).get().getValue(); + int initialPayloadSize = (int) initialHeader.getEntry(RpmSignatureTag.PAYLOAD_SIZE).get().getValue(); + String initialSha1 = initialHeader.getEntry(RpmSignatureTag.SHA1HEADER).get().getValue().toString(); + String initialMd5 = Rpms.dumpValue(initialHeader.getEntry(RpmSignatureTag.MD5).get().getValue()); + + // Get informations of the signed rpm file + int signedSize = (int) signedHeader.getEntry(RpmSignatureTag.SIZE).get().getValue(); + int signedPayloadSize = (int) signedHeader.getEntry(RpmSignatureTag.PAYLOAD_SIZE).get().getValue(); + String signedSha1 = signedHeader.getEntry(RpmSignatureTag.SHA1HEADER).get().getValue().toString(); + String signedMd5 = Rpms.dumpValue(signedHeader.getEntry(RpmSignatureTag.MD5).get().getValue()); + String pgpSignature = Rpms.dumpValue(signedHeader.getEntry(RpmSignatureTag.PGP).get().getValue()); + + // Compare informations values of initial rpm and signed rpm + assertEquals(initialSize, signedSize); + assertEquals(initialPayloadSize, signedPayloadSize); + assertEquals(initialSha1, signedSha1); + assertEquals(initialMd5, signedMd5); + + // Verify if signature is present + assertNotNull(pgpSignature); + } + } + + @Test + @Order(2) + @Disabled + public void verifyRpmSignature() throws IOException, PGPException { + File public_key = new File(PUBLIC_KEY_PATH); + File signedRpm = new File(RESULT_FILE_PATH); + if (!public_key.exists() || !signedRpm.exists()) { + fail("Input files signedRpm or public_key does not exist"); + } + InputStream publicKeyStream = new FileInputStream(public_key); + ArmoredInputStream armoredInputStream = new ArmoredInputStream(publicKeyStream); + PGPPublicKeyRing publicKeyRing = new BcPGPPublicKeyRing(armoredInputStream); + PGPPublicKey publicKey = publicKeyRing.getPublicKey(); + // TODO Signature Check + } + + @AfterAll + public static void clean() { + File resultDir = new File(RESULT_DIR); + File signedRpm = new File(RESULT_FILE_PATH); + if (resultDir.exists()) { + if (signedRpm.exists()) { + signedRpm.delete(); + } + resultDir.delete(); + } + } +} diff --git a/rpm/src/test/resources/key/private_key.txt b/rpm/src/test/resources/key/private_key.txt new file mode 100644 index 0000000..a0e8804 --- /dev/null +++ b/rpm/src/test/resources/key/private_key.txt @@ -0,0 +1,116 @@ +-----BEGIN PGP PRIVATE KEY BLOCK----- +Version: OpenPGP.js v4.10.10 +Comment: https://openpgpjs.org + +xcaGBGQAqhABEAC5XnVvJlEeArPCduQlVs+ekqIYOTJP40ShB7EOnM3N6ep8 +nzYvcYfuxh+cO8DO/C71AEvs9YnHnoZwUX/ki31d1tOzW6LLsuspQqHZUXH2 +HQsm8ph5hCQi/Cx50Ym22LXYI7Dkg18xEVJK+np6p9cR/grQfprXbGeJxc1v +tdNJZNp4p9txlwNLMGi7cVxebn3WFMieXdwx9qPrZHob+r7r/Jm0ORUvPHRz +Q8SLR1SxpaUm1EVZKu6A8sQ8GLgqLtm4PZtUznucMZN5Vntn9OthSPXtfw7L +Z6jERwrSw9B9k8cRvOws1CFIDMzZwz8GXwa9QRQUF0doOCKW/MEX0h8q+B8i +kkWvpKKzYe8lGsuiXDjfiXgmnn8BlbUNej1vu2/kOUwZ6GtFq+7XWDv5mpHr +sr4+pcQULW8JqRlOxcCj4mU80ih6JE2BObUxbgGmuGk/B4ePkNv5GBYeGarg +LJz6ynlQDj6lo9yY8QmNPN5ddEAXOgVGyHKyjNNKWl1jj7PqS2vpOkWRPLto +3XWXVuWjifuEnV/tHVtiATOM6tOxjL51+357FtX7YnEDznMEiu6SLAeRkhwi +ffH6Cyj12rbEURQThk5uQIKAeiMMfjKlTdxoR1+xra5HgQCNZZ9B+L0fNEa9 +vwVDgK5GE+94sugK1kT3fL1GCQArysaqLU2J0wARAQAB/gkDCJ6ntlLBFtVx +4JS5hTbHB3pkshObsrUueu+Cx0JVjcS7axLRdmd1Jv+QV1C3WJ/sDpPAK/qF +tuj8eYd2AuvVZ/UjqMsYXw/pGWIt2UPNr7oPaJZO36bUyQdKR9RPK0OQ/s3Y +qIMgzNs3NxApb0QndCdhmeswRdpOVCzH3JwtuT+KLrNkuiJ6+odcjATTNWft +KhSjkUr+lIEXvER2EAFDecLp0rsQWmuDWYj+Y5P+BFQSDNZdLAGlTPUro3xd +31GuJTl8//bUbnvNSbqUYykwwUcBxwbsRIN/aUBPmkXM9gE/U7QCKT84NOpa +iZL1HXppkSnvgPpvFBELpzffhi0hOwyXLOsTO4qQlmrxQmAKWpL9WXdKYEB4 +71qGsRHI1CVhWc6+dJBxFrPvsCpC9q/NC64ReYgW/5WVplRAmUeTMUoT29yf +bwlhNwYuvMdUdtQt0VcjUESAinmFNlIAFsPb/x1/wCryBQ44Dwhi7gYk396M +YFvrPb8A+q2rD7FxSSvJTXYdGE3fsr+YUsTCgPW+Ajs6RcWrx86SEQEosXAm +lmwFgo1YGXmuuTup+kTFfhbUJkG1cd+d2d14I1n+AmpcX0y5Sj+CPoXO9M3T +UyewnI+0j+CU7O9J1PjIIpVgCa6zwb2Zhfm/okHC8NVPlb/MezGUSsW/dhBa +jZViU0p15SOUoeAv7BI3dkhOR0GqL6xgLm/ZgnbraGOxipaU6NdJ3D6CKbfu +S8rtI4GhJCUoquv0+k7VCG9Fc3iLGedISMiHO7/NMazKWPlBhO26JkXTBCiS +HgbMEiD2OOFWzk1Y7ruvWc0ReDnspw2yk66oBDkxAZpKzZOWznBEDhCH0RTO +R1IyhHIvdyHEHdpyCk3WTPzsEvjSdlAOdzaaPGv8cJVbWhnYh0vhi7e9DOdI +8a4v+3jO04i8OKCqHsMkJIlIpxlAW3sqoKkJH8E84213D+F4wH9Ftu0PLSZZ +TuGEO3rcWFZcUudj+a/0Np7kCxlKfGFvaBhStkGlSxWyiJ1KOR0gK/0cmUQW +G3ui36CxBQF3V1x/6SeDWaZLcwSEe4rHq0MfOSXe4vBjC0Mx395sPKsAjbDY +7Gy9spkI+FjnApd7waiMmmCiNUf/FN1w+/hB1gIe9PdmUbpXh9McFMRykXan +83zhvHQTxRHBiSPTOgK8VHIpcvIt7xH437+ZAIoHNe/Fwj0sHHJdNqi+XmTY +leyDF/NwBeKV4I6EdFotLw2Jc5CknqEJlupvNFwRyfd38ZQRt9lg+5Crnnvr +lc8C8Q3mbCDGgGfiADRSGj1D0gMVojl0LYCsC7rhUFHxDa3WaQGVK3HStmGy +88rhF1IuE4K7csjl8UNgKIEDZhr/oDuHQEdQwtrChUnm0CsilfMdm9Uhdt30 +Stj3EOfan/bbDAo5StwhQoMzcQdaFONnEa2kE81AsbbjovC6F6xcI76L01OM +SlRDCGn7P6IygMQiG3rgrWAAzGQYZf9eu6hwasc4w4aMe+w18Tc4sSheQGO6 +aN4HPmVOx6GNPetOzmQJaYr8wjy4AkJ0//JqCa+0q13WRrkH30peThZKgDbm +BmwN0nmLdFzQG1BwCseRGs4c6bOP77e0qgBko+A89/I3HfZi4fCCTduWJj8h +oN1mL/cAdElHtoyFf8Wr6VpQDJpGMKi3FIN0CWwo3lERYx187tQzfpZswgOw +ZwFM0kAW/qfcIIiHg1f9xvX3L6dqXl1rH5NYUNUpHNSDDPEBhPcKaQEQxa4q +1Fn4iUvNibn0ppjFyZekdgw02WvNEm1hdCA8dGVzdEB0ZXN0LmZyPsLBjQQQ +AQgAIAUCZACqEAYLCQcIAwIEFQgKAgQWAgEAAhkBAhsDAh4BACEJEBhkesNn +n1cjFiEEB7+Zgkw1JOzWGdkzGGR6w2efVyOSfQ//Y3JL6bnc7dlNYZvjthYu +s5t97mogxQ2Ro49cYBlPG921Wft2nviJ1a3ZCcX1OOKjzJacAN/+z/n/6UKi +K/qfwEWNruw6b9Q6/ZH4FMsQUS8AeX+z1lZBKvE5d8S7zwmTUhpawWtu//Fm +0r9NLf1jONOst2sLS4gEtuabcxiDKjZ8AEWGyAKZxqxV0j5WkaGCXnvtr/Gk +hryoX0ziaR7sjmh0YRpv04TlPwmHltjF+7fUvnuebq5tY4CMMJFoiuWSpIN3 +SlSv2JPkLJjvZY6+aYIpOpurVXL0W+xIlqHCoqFJ6neYCrSdcSSlXD2FgBsF +kMSIaBhgBaCpDLxH7z1VpHs/mOmrxzDJqtkiMHCLwUEYDHWEUh8BgRpbs5aA +yWD/EPXvfDC2oB3rzVPArH6u0phNvuVKfcs3EFlTo3YxhxsICzLsyUsoXxw5 +oTSbSBBxAKMc+1Oo3Gx1IiEJHvRXPPKrSr2DwFoXM4iMMKeX+bul2BNmtbk0 +YWEL7/DkwLXToMq58PWpVvQRNcLFUz3/QcvHpPX+sgfvHElevK/QSca4B4sI +roSNeuEpU8fV2BTym7KjqIvCSSJG6hgROEHgKNhseXT5l0Dcuq01H1A9Jcsa +I/Dns7PL4RxTNS4EIEa/4/eeBRhXzeonRdwvv1+xBzvvaeCMaNrgFg/tUwkY +3pfHxoYEZACqEAEQAPev+fA4IoixO7I5jTM9ckAwW3Aw7hJaLDoy8fi/Ud2X +zaW5gLCMSjMHpI6yrmjQhzuVt4s0Fj8cHnS92qcOWNiX/25tBoHXTqj95hap +ezRKsfpBtqzg3OuD63BZLHNvj3LWzX7etLV5YZwny4VESQcKQQIq0LMGkyH+ +0UEDsyrXNeS7Hpo6wrdcalwiaoaSiQcqyrzaPgp8PPBI62+X8/oSWCCy8FA2 +05/H1HohTApQGyRJ8ymbeS0ZyyAGfLEb/mbYPl/ged6Eqi1rUWcUpQS655vR +AIl0RZSwH+DsSwcdIeLAB9VM1I6F/T5Q/CwsLkmLU0yYfhTpxMKXZuzfClP+ +HJnFycuKzmT9z5Wehjg9KjCGBjGPMWQzF6ODyoygVq3xHDZsS0tjA7JZEE0o +gceHw94WDs3+xZ4rLCVU6YEUXvguqvOPxFUxl/Nt/i26TCH1hPv4mumbw4B9 +50tCymy5E0P7j9aqbkQLWJggsgqiZwKTowLMOaOLW7LE7r7BWp/Sb6zWkR4f +/JX3RdOjXBW+B6kAByBcdU/ekOORnBSrB/aTnnOX+MQpL+a/wjKmKPpVh8wT +xfJxdSAlwstUFqbU6f2hr87b+kyPiks2/scRdaLpuhqJsld+YwJHU1S6HIwc +EjCWnV/GAxPKt7zD2n0v6+v4BcR3GsoPOwu4N81NABEBAAH+CQMIOt1CNpf1 +VPngIktHElglGcA0dzdJgO4rwfzo/YeS4NlNWG0QJe372vq4k/RqpXjSFnK4 +ojg/imNmg/BdUxfjoaz/fnXeZ4PBXdESLvqtDgdnN7aJK9Lj0n2/rslF5sTq +QBzA+77L0WiTKakzRfZKKHd/4BAzCJWWNwM9P5FJ0VWaaAYufI95mW3IgZq2 +xYAMaux6bOI0EM+I/766FdrDqU8eEoLHgdTQK0S/LYvdEbGJT3crBEjEttB4 +qZg+p84lrVV/kN2ORtELxHvV1jfFSKkl5fKfgrRZdWYPlH2wAHSN/q/P2iza +cZ2UJMHzyBm3W45o/Wf6cU0+7F56uUMIJyzjsqZFhNesnFWWvvpKziuRaC5V +rqcn72ZpsX1HVKJWkj8eWW/bHQix/7IO8RFhsK2gtvxsSGclPoGU+GJyl0/p +QTI/uUv44nSXTq6PyRVJK3VwrKM5bq5gRcWQi+OP+I/7eam92n74tw+Eg6fK +tqJ5OXNCvIW6B4e422qe5D1+kGg6Y6DyJxFmsf1U806f4bzuGNjg2cRgi129 +r6/tbRdMBG2UztqsdzOIlhHPCI3zUQfqH1IM9ppr0xO01w0FP2+eY390Xpho +Hx4auv1UzKSMm6xQWX7zbY12tU0SAwHi5DPCsZQXFVMM+uxUvGVpGhk0gQGy +3hvV2F36L6/nygbszl1CUHH1rR12y/WtgK0mAhMoT79xf8jL9Lef1FCVFy6p +uerFDHkn/RBS/gmAzmdVMj+F/OXDw0SBMnmhuQI1CGDsbqdhwi36kE+K9KdR +YV51t+CqsqOyORphdVU6Sqb5rrt9oqgQuRHRolcKmEnJ5hmsJ6EFP49F5yfg +GdscKexeOblmq+RXOTjV/YHr3TxhdWOOrJqFgIjidAw5OfTlPF/+nmFlMM8t +TQMOG8VlGmbYB4NHWgRK3/p4GAGy12C4l2Nv2WQutGcF18/5oSlF8ErOKGl4 +UfRr7RHDfb42Ts9gqNZizmIFXQ/DW7Byrl2CJtbmwk/fSXf1c+CXMDQn4uXR +mI2znukohD7cwCDiU5c2U/amKAw5sPGkAB1PWyl509XXrC9wq9MsKToRWF6I +H46rzkbiPTCDAggwnd6zXQYRBv30C2EySn4R89PXX4LH611mukC2VCQWg7jO +czIQLXINj4Zi7olWeGaAauf4+gNVL5Lf8cOhiB4PV4i1kwYhpJ8C2V847JWQ +UPrHp8Fh35ipqKu3xD78YoFE3AZt8Puoyh+zlxlnqQc0Zhs63uM4j9yXOEcT +2xGCi7HDNY1aTgpOfbrNxTFWjSDpIfhFtL9FRsPCgA57E+Byl3OWvS4Zg5Zd +/F/nN2SqNLrNC64ukVml0f5OxEdlyzRff2kGjsh+cKKj6GKCIeY+txjMMIW+ +xHoeOeQ1rjC5slQ7+7F1yBi8RoasvlJSd7sfglHpr6lIrsKQL0c53UNcCdDm +zbUYelJ4l2My/ghFU5t2QrgU+n5APlzM0lQKdbuWKybEpI7aKyWb4sw5s8d6 +imKLSU0uPCK1o/eojKbol3dYrWMlDpSZZzocHHvTHJ0aBK1b6dW/o6Fs3DRD +Fzf+vaqt0vEZ2kQKPLAjdrmzXYEMoAZJKzhYj68tw7VOBD1bZVJtXD/wjv6b +/IXulQbPwsegmACojpkrNQeNzV9jvXXIKmFe9U2+xv5N7k7/orsrDzfAu4Yy +oWYOwBATLgWcJHBQ0E+BjC5HKSl2c+2tlTx8YJwUMTPSgqLI3szfY4mVHBhT +ljy2RiixnlfwG0ElVbgNy2y7n3L5PcLBdgQYAQgACQUCZACqEAIbDAAhCRAY +ZHrDZ59XIxYhBAe/mYJMNSTs1hnZMxhkesNnn1cjREAP/A+3NibKiGu90Ol4 +MQEHxbXVr20MGC+XXwEjFJfG2OnrHTEpmAIyQANC2BmKACo2TNRhOhw/vfM3 +WIbjRv4BMiZi7GduJe2SiveMZEJX8o0RGQawGQS462L19xKL0X50N/ewS8g8 +tO6tfrLNDiHecCZieyi9bwjjS9aLCPAcMI5VtxfN1xSZFPe1encNZekmazF9 +VYxnjdo8XEqGVZmOUS2r+oie5v+//vNsfT/JTddMDnb+5sU/ay+AgiYuCC5S +TnPDLnKaygBmeisEmk2fir85RF3IZXR0CoV8Tkxx/iAxI0MVO9DeGKrQq6Gv +QTRqqt3+i9Gb+8muX1GaDTuEWE/GJxSihblg6VfQAU0SsZyipHrn8IE2AKbs +ic5pJImMftJDfc3+0ETsjl3YYfAr3c/GgdJ1igzV4Oh2usp6WrzjxhebaiJq +goYgB4k+Ka4HZgErvtDCUYsZFzdx+pYOmPfZbcmQRigot3gFbpH/+vJmdTgT +s4+7rSvSKXdY89W+idL0VXWBr3+JOi3b27p1nWZGvYnNQ3FuRqDv+LF9gkQu +HzhM+ElV1QtvSef6ewobxniLmPCifprFkkQz633hItACudBqR5zj+nktn4Pi +M9NpcU44xdtvK347CZ8khYof+vkAWISVpie34C3rUNETMAikRkXhnU74tO2B +25uiY4mR +=iZMt +-----END PGP PRIVATE KEY BLOCK----- diff --git a/rpm/src/test/resources/key/public_key.txt b/rpm/src/test/resources/key/public_key.txt new file mode 100644 index 0000000..71cf1d4 --- /dev/null +++ b/rpm/src/test/resources/key/public_key.txt @@ -0,0 +1,56 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: OpenPGP.js v4.10.10 +Comment: https://openpgpjs.org + +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 +=wNfm +-----END PGP PUBLIC KEY BLOCK-----