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

Implement OpenPGP Signature generation using PGPainless #48

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
8 changes: 2 additions & 6 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,8 @@

<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk18on</artifactId>
<groupId>org.pgpainless</groupId>
<artifactId>pgpainless-core</artifactId>
</dependency>
</dependencies>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@
import org.bouncycastle.openpgp.bc.BcPGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.pgpainless.PGPainless;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.util.KeyIdUtil;
import org.pgpainless.util.Passphrase;

public final class PgpHelper {
private PgpHelper() {
Expand Down Expand Up @@ -133,4 +137,23 @@ public static PGPSecretKey loadSecretKey(final InputStream input, final String k

return null;
}

public static PGPSecretKeyRing loadSecretKeyRing(InputStream inputStream)
throws IOException {
return PGPainless.readKeyRing().secretKeyRing(inputStream);
}

public static long parseKeyId(String keyId) {
if (keyId == null) {
return 0L;
}
return KeyIdUtil.fromLongKeyId(keyId);
}

public static SecretKeyRingProtector protectorFromPassword(String password) {
if (password == null || password.isEmpty()) {
return SecretKeyRingProtector.unprotectedKeys();
}
return SecretKeyRingProtector.unlockAnyKeyWith(Passphrase.fromPassword(password));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;

/**
* Signing Stream.
*
* @deprecated use {@link SigningStream2} instead.
*/
@Deprecated
public class SigningStream extends OutputStream {
private final OutputStream stream;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* Copyright (c) 2015, 2019 Contributors to the Eclipse Foundation
*
* 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.IOException;
import java.io.OutputStream;
import java.util.List;
import java.util.Objects;

import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.DocumentSignatureType;
import org.pgpainless.encryption_signing.EncryptionResult;
import org.pgpainless.encryption_signing.EncryptionStream;
import org.pgpainless.encryption_signing.ProducerOptions;
import org.pgpainless.encryption_signing.SigningOptions;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.util.ArmoredOutputStreamFactory;

public class SigningStream2 extends OutputStream {
private final OutputStream stream;

private final PGPSecretKeyRing secretKeys;

private final SecretKeyRingProtector protector;

private final long keyId;

private final boolean inline;

private boolean initialized;

private EncryptionStream signingStream;

/**
* Create a new signing stream
*
* @param stream the actual output stream
* @param secretKeys the signing key ring
* @param protector protector to unlock the signing key
* @param inline whether to sign inline or just write the signature
* @param version the optional version which will be in the signature comment
*/
public SigningStream2(final OutputStream stream,
final PGPSecretKeyRing secretKeys,
final SecretKeyRingProtector protector,
final long keyId,
final boolean inline,
final String version) {
this.stream = stream;
this.secretKeys = secretKeys;
this.protector = protector;
this.keyId = keyId;
this.inline = inline;

ArmoredOutputStreamFactory.setVersionInfo(version);
}

/**
* Create a new signing stream
*
* @param stream the actual output stream
* @param secretKeys the signing key ring
* @param protector protector to unlock the signing key
* @param inline whether to sign inline or just write the signature
*/
public SigningStream2(final OutputStream stream,
final PGPSecretKeyRing secretKeys,
final SecretKeyRingProtector protector,
final long keyId,
final boolean inline) {
this(stream, secretKeys, protector, keyId, inline, null);
}

protected void testInit() throws IOException {
if (this.initialized) {
return;
}

this.initialized = true;

try {
long signingKeyId = keyId;
if (signingKeyId == 0) {
List<PGPPublicKey> signingKeys = PGPainless.inspectKeyRing(secretKeys).getSigningSubkeys();
if (signingKeys.isEmpty()) {
throw new PGPException("No available signing subkey found.");
}
// Sign with first signing subkey found
signingKeyId = signingKeys.get(0).getKeyID();
}

if (inline) {

SigningOptions signingOptions = SigningOptions.get();
if (keyId != 0) {
signingOptions.addInlineSignature(protector, secretKeys, signingKeyId);
} else {
signingOptions.addInlineSignature(protector, secretKeys, DocumentSignatureType.BINARY_DOCUMENT);
}
ProducerOptions producerOptions = ProducerOptions.sign(signingOptions)
.setCleartextSigned();

signingStream = PGPainless.encryptAndOrSign()
.onOutputStream(stream) // write data and sig to the output stream
.withOptions(producerOptions);

} else {

SigningOptions signingOptions = SigningOptions.get();
if (keyId != 0) {
signingOptions.addDetachedSignature(protector, secretKeys, keyId);
} else {
signingOptions.addDetachedSignature(protector, secretKeys, DocumentSignatureType.BINARY_DOCUMENT);
}
ProducerOptions producerOptions = ProducerOptions.sign(signingOptions);

signingStream = PGPainless.encryptAndOrSign()
.onOutputStream(
// do not output the plaintext data, just emit the signature in close()
new OutputStream() {
@Override
public void write(int i) throws IOException {
// Ignore data
}
})
.withOptions(producerOptions);
}
} catch (final PGPException e) {
throw new IOException(e);
}
}

@Override
public void write(final int b) throws IOException {
write(new byte[] { (byte) b });
}

@Override
public void write(final byte[] b, final int off, final int len) throws IOException {
Objects.requireNonNull(b);

testInit();

signingStream.write(b, off, len);
}

@Override
public void close() throws IOException {
testInit();

signingStream.close();

if (this.inline) {
return;
}

EncryptionResult result = signingStream.getResult();
final PGPSignature signature = result.getDetachedSignatures().flatten().iterator().next();
ArmoredOutputStream armoredOutput = ArmoredOutputStreamFactory.get(stream);
signature.encode(new BCPGOutputStream(armoredOutput));
armoredOutput.close();

super.close();
}
}
12 changes: 3 additions & 9 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,10 @@

<dependencyManagement>
<dependencies>

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk18on</artifactId>
<version>${bouncycastle.version}</version>
<groupId>org.pgpainless</groupId>
<artifactId>pgpainless-core</artifactId>
<version>1.6.7</version>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be provided as a variable.

</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/*
* Copyright (c) 2023 Contributors to the Eclipse Foundation
*
* 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;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.util.Objects;

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.PGPainless;
import org.pgpainless.algorithm.DocumentSignatureType;
import org.pgpainless.encryption_signing.EncryptionResult;
import org.pgpainless.encryption_signing.EncryptionStream;
import org.pgpainless.encryption_signing.ProducerOptions;
import org.pgpainless.encryption_signing.SigningOptions;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* An RSA signature processor for the header section only.
*/
public class PgpHeaderSignatureProcessor implements SignatureProcessor {
vanitasvitae marked this conversation as resolved.
Show resolved Hide resolved
private final static Logger logger = LoggerFactory.getLogger(PgpHeaderSignatureProcessor.class);

private final PGPSecretKeyRing secretKeys;

private final SecretKeyRingProtector protector;

private final long keyId;

private PGPSignature signature;

private byte[] value;

public PgpHeaderSignatureProcessor(final PGPSecretKeyRing secretKeys,
final SecretKeyRingProtector protector,
final long keyId) {
this.secretKeys = Objects.requireNonNull(secretKeys);
this.protector = Objects.requireNonNull(protector);
this.keyId = keyId;
}

public PgpHeaderSignatureProcessor(final PGPSecretKeyRing secretKeys, SecretKeyRingProtector protector) {
this(secretKeys, protector, 0);
}

public PgpHeaderSignatureProcessor(final PGPSecretKeyRing secretKeys) {
this(secretKeys, SecretKeyRingProtector.unprotectedKeys());
}

@Override
public void feedHeader(final ByteBuffer header) {
try {
OutputStream sink = new OutputStream() {
@Override
public void write(int i) throws IOException {
// ignore "ciphertext"
}
};
SigningOptions signingOptions = SigningOptions.get();
if (keyId == 0) {
signingOptions.addDetachedSignature(protector, secretKeys, DocumentSignatureType.BINARY_DOCUMENT);
} else {
signingOptions.addDetachedSignature(protector, secretKeys, keyId);
}
EncryptionStream signingStream = PGPainless.encryptAndOrSign()
.onOutputStream(sink)
.withOptions(ProducerOptions.sign(signingOptions));

if (header.hasArray()) {
signingStream.write(header.array(), header.position(), header.remaining());
} else {
final byte[] buffer = new byte[header.remaining()];
header.get(buffer);
signingStream.write(buffer);
}

signingStream.close();
EncryptionResult result = signingStream.getResult();

this.signature = result.getDetachedSignatures().flatten().iterator().next();
this.value = signature.getEncoded();
} catch (final Exception e) {
throw new RuntimeException(e);
}
}

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

@Override
public void finish(final Header<RpmSignatureTag> signature) {
switch (this.signature.getKeyAlgorithm()) {
// RSA
case PublicKeyAlgorithmTags.RSA_GENERAL: // 1
logger.info("RSA HEADER: {}", this.value);
signature.putBlob(RpmSignatureTag.RSAHEADER, this.value);
vanitasvitae marked this conversation as resolved.
Show resolved Hide resolved
break;

// DSA
case PublicKeyAlgorithmTags.DSA: // 17
case PublicKeyAlgorithmTags.EDDSA_LEGACY: // 22
logger.info("DSA HEADER: {}", this.value);
signature.putBlob(RpmSignatureTag.DSAHEADER, this.value);
break;

default:
throw new RuntimeException("Unsupported public key algorithm id: " + this.signature.getKeyAlgorithm());
}

}
}
Loading