-
Notifications
You must be signed in to change notification settings - Fork 16
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
vanitasvitae
wants to merge
19
commits into
eclipse:master
Choose a base branch
from
vanitasvitae:pgpainless
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
1fbc14b
Replace BC dependency with PGPainless (pulls in BC)
vanitasvitae bb2289f
Change default signature hash algorithm from SHA1 to SHA256
vanitasvitae 4b1827d
Base the SigningStream on PGPainless
vanitasvitae dce7bfa
Add PgpHeaderSignatureProcessor and PgpSignatureProcessor
vanitasvitae 977a59e
Base RpmFileSignatureProcessor on new PgpSignatureProcessor
vanitasvitae e1cd281
Remove extraneous semicolon
vanitasvitae b6f03da
Fix copyright statement in PgpHeaderSignatureProcessor
vanitasvitae 49967ee
Fix copyright statement in new PgpSignatureProcessor classes
vanitasvitae 489bd04
Add back and deprecate some old signing logic
vanitasvitae 060d2e8
Bump PGPainless to 1.5.3 and support provided key-IDs
vanitasvitae bcf9966
Flatten set of signatures
vanitasvitae bc1ca89
Merge branch 'master' into pgpainless
dwalluck a2ac387
Fix codestyle isses
vanitasvitae b90e86c
Bump PGPainless to 1.6.7
vanitasvitae 68f8658
Fix IOUtils import
dwalluck d68ab1a
For DSA signatures: Put DSAHEADER blob
vanitasvitae b2a8d25
Remove unused algorithm tags
vanitasvitae de14b42
Add deprecation notices to RsaSignatureProcessor and RsaHeaderSignatu…
vanitasvitae c2c9b84
Move logging statements to finish(), differentiating RSA and DSA
vanitasvitae File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
182 changes: 182 additions & 0 deletions
182
core/src/main/java/org/eclipse/packager/security/pgp/SigningStream2.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
131 changes: 131 additions & 0 deletions
131
rpm/src/main/java/org/eclipse/packager/rpm/signature/PgpHeaderSignatureProcessor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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()); | ||
} | ||
|
||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.