Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alternative approach at signature creation using PGPainless #56

Draft
wants to merge 16 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* Copyright (c) 2024 Paul Schaub
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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
*/

package org.eclipse.packager.security.pgp;

import org.bouncycastle.openpgp.PGPPrivateKey;

import java.io.OutputStream;
import java.util.function.Function;

/**
* Implementation of {@link PgpSignerCreator} that depends on Bouncy Castle directly.
* Here, the user needs to pass in the {@link PGPPrivateKey} and digest algorithm they want to use
* for signing explicitly.
*/
public class BcPgpSignerCreator extends PgpSignerCreator {

private final PGPPrivateKey privateKey;
private final int hashAlgorithm;

/**
* Construct a {@link PgpSignerCreator} that uses Bouncy Castle classes directly and signs
* using a {@link SigningStream}.
*
* @param privateKey private signing key
* @param hashAlgorithmId OpenPGP hash algorithm ID of the digest algorithm to use for signing
* @param inlineSigned if true, use the cleartext signature framework to sign data inline.
* Otherwise, sign using detached signatures.
*/
public BcPgpSignerCreator(PGPPrivateKey privateKey, int hashAlgorithmId, boolean inlineSigned) {
super(inlineSigned);
this.hashAlgorithm = hashAlgorithmId;
this.privateKey = privateKey;
}

@Override
public Function<OutputStream, OutputStream> createSigningStream() {
if (privateKey == null) {
return null;
}

return outputStream -> new SigningStream(outputStream, privateKey, hashAlgorithm, inlineSigned);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) 2024 Paul Schaub
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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
*/

package org.eclipse.packager.security.pgp;

import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;

import java.util.NoSuchElementException;

/**
* Implementation of the {@link PgpSignerCreatorFactory} that uses BC.
*/
public class BcPgpSignerCreatorFactory implements PgpSignerCreatorFactory {

@Override
public PgpSignerCreator getSignerCreator(
PGPSecretKeyRing signingKey,
long signingKeyId,
char[] passphrase,
int hashAlgorithm,
boolean inlineSigned) {
PGPSecretKey key = signingKey.getSecretKey(signingKeyId);
if (key == null) {
throw new NoSuchElementException("No such signing key");
}
try {
PGPPrivateKey privateKey = key.extractPrivateKey(
new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider())
.build(passphrase));
return new BcPgpSignerCreator(privateKey, hashAlgorithm, inlineSigned);
} catch (PGPException e) {
throw new RuntimeException("Could not unlock private key.");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -133,4 +133,34 @@ public static PGPSecretKey loadSecretKey(final InputStream input, final String k

return null;
}

public static PGPSecretKeyRing loadSecretKeyRing(final InputStream input, final String keyId) throws IOException, PGPException {
final long keyIdNum = Long.parseUnsignedLong(keyId, 16);

final BcPGPSecretKeyRingCollection keyrings = new BcPGPSecretKeyRingCollection(PGPUtil.getDecoderStream(input));

final Iterator<?> keyRingIter = keyrings.getKeyRings();
while (keyRingIter.hasNext()) {
final PGPSecretKeyRing secretKeyRing = (PGPSecretKeyRing) keyRingIter.next();

final Iterator<?> secretKeyIterator = secretKeyRing.getSecretKeys();
while (secretKeyIterator.hasNext()) {
final PGPSecretKey key = (PGPSecretKey) secretKeyIterator.next();

if (!key.isSigningKey()) {
continue;
}

final long shortId = key.getKeyID() & 0xFFFFFFFFL;

if (key.getKeyID() != keyIdNum && shortId != keyIdNum) {
continue;
}

return secretKeyRing;
}
}

return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2024 Paul Schaub
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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
*/

package org.eclipse.packager.security.pgp;

import java.io.OutputStream;
import java.util.function.Function;

/**
* Factory for creating signing streams.
*/
public abstract class PgpSignerCreator {

protected final boolean inlineSigned;

public PgpSignerCreator(boolean inlineSigned) {
this.inlineSigned = inlineSigned;
}

/**
* Return a {@link Function} that wraps an {@link OutputStream} into a signing stream.
* This method has no arguments (key, algorithms etc.) to be implementation agnostic.
* Subclasses shall pass those details as constructor arguments.
*
* @return transforming function
*/
public abstract Function<OutputStream, OutputStream> createSigningStream();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2024 Paul Schaub
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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
*/

package org.eclipse.packager.security.pgp;

import org.bouncycastle.openpgp.PGPSecretKeyRing;

/**
* Factory interface for instantiating {@link PgpSignerCreator} classes.
* This class acts as the public interface for choosing the OpenPGP signing backend.
* By default, Bouncy Castle is used via {@link BcPgpSignerCreatorFactory}.
* TODO: Use dependency injection to allow optional dependencies to replace the default instance.
*/
public interface PgpSignerCreatorFactory {

PgpSignerCreator getSignerCreator(
PGPSecretKeyRing signingKey,
long signingKeyId,
char[] passphrase,
int hashAlgorithm,
boolean inlineSigned);
}
63 changes: 63 additions & 0 deletions pgpainless_signer/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.packager</groupId>
<artifactId>packager</artifactId>
<version>0.20.1-SNAPSHOT</version>
</parent>

<artifactId>packager-pgpainless_signer</artifactId>
<name>Eclipse Packager :: PGPainless Signer</name>

<dependencies>
<dependency>
<groupId>org.eclipse.packager</groupId>
<artifactId>packager-core</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.packager</groupId>
<artifactId>packager-rpm</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.pgpainless</groupId>
<artifactId>pgpainless-core</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>

<!-- testing -->

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<!-- show log output during tests using logback as the slf4j backend. -->
<!-- https://mvnrepository.com/artifact/ch.qos.logback/logback-classic -->
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright (c) 2024 Paul Schaub
*
* See the NOTICE file(s) distributed with this work for additional
* information regarding copyright ownership.
*
* 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
*/

package org.eclipse.packager.rpm.signature.pgpainless;

import org.bouncycastle.bcpg.PublicKeyAlgorithmTags;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.eclipse.packager.rpm.RpmSignatureTag;
import org.eclipse.packager.rpm.header.Header;
import org.pgpainless.encryption_signing.EncryptionResult;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.nio.ByteBuffer;

public class PGPainlessHeaderSignatureProcessor extends PGPainlessSignatureProcessor {

private final Logger logger = LoggerFactory.getLogger(PGPainlessHeaderSignatureProcessor.class);

public PGPainlessHeaderSignatureProcessor(PGPSecretKeyRing key, SecretKeyRingProtector keyProtector, int hashAlgorithm) {
super(key, keyProtector, hashAlgorithm);
}

@Override
public Logger getLogger() {
return logger;
}

@Override
public void feedPayloadData(ByteBuffer data) {
// We only work on header data
}

@Override
public void finish(Header<RpmSignatureTag> signature) {
try {
signingStream.close();
EncryptionResult result = signingStream.getResult();
PGPSignature pgpSignature = result.getDetachedSignatures().flatten().iterator().next();
byte[] value = pgpSignature.getEncoded();
switch (pgpSignature.getKeyAlgorithm()) {
// RSA
case PublicKeyAlgorithmTags.RSA_GENERAL: // 1
getLogger().info("RSA HEADER: {}", value);
signature.putBlob(RpmSignatureTag.RSAHEADER, value);
break;

// DSA
case PublicKeyAlgorithmTags.DSA: // 17
case PublicKeyAlgorithmTags.EDDSA_LEGACY: // 22
vanitasvitae marked this conversation as resolved.
Show resolved Hide resolved
getLogger().info("DSA HEADER: {}", value);
signature.putBlob(RpmSignatureTag.DSAHEADER, value);
break;

default:
throw new RuntimeException("Unsupported public key algorithm id: " + pgpSignature.getKeyAlgorithm());
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Loading