[signing] Support reading keys from files. Resolves #143

This commit is contained in:
Andres Almiray
2021-05-11 22:04:31 +02:00
parent 2832aea4bc
commit 7e6dac1f46
14 changed files with 279 additions and 105 deletions

View File

@@ -38,7 +38,7 @@ import org.jreleaser.model.Distribution;
import org.jreleaser.model.JReleaserContext;
import org.jreleaser.model.Signing;
import org.jreleaser.model.util.Artifacts;
import org.jreleaser.util.signing.InMemoryKeyring;
import org.jreleaser.util.signing.Keyring;
import org.jreleaser.util.signing.SigningException;
import java.io.BufferedInputStream;
@@ -80,7 +80,7 @@ public class Signer {
context.getLogger().increaseIndent();
context.getLogger().setPrefix("sign");
InMemoryKeyring keyring = createInMemoryKeyring(context.getModel().getSigning());
Keyring keyring = context.createKeyring();
List<FilePair> files = collectArtifacts(context, keyring);
if (files.isEmpty()) {
@@ -108,18 +108,7 @@ public class Signer {
context.getLogger().decreaseIndent();
}
private static InMemoryKeyring createInMemoryKeyring(Signing sign) throws SigningException {
try {
InMemoryKeyring keyring = new InMemoryKeyring();
keyring.addPublicKey(sign.getResolvedPublicKey().getBytes());
keyring.addSecretKey(sign.getResolvedSecretKey().getBytes());
return keyring;
} catch (IOException | PGPException e) {
throw new SigningException("Could not initialize keyring", e);
}
}
private static void verify(JReleaserContext context, InMemoryKeyring keyring, List<FilePair> files) throws SigningException {
private static void verify(JReleaserContext context, Keyring keyring, List<FilePair> files) throws SigningException {
context.getLogger().debug("verifying {} signatures", files.size());
for (FilePair pair : files) {
@@ -133,7 +122,7 @@ public class Signer {
}
}
private static boolean verify(JReleaserContext context, InMemoryKeyring keyring, FilePair filePair) throws SigningException {
private static boolean verify(JReleaserContext context, Keyring keyring, FilePair filePair) throws SigningException {
context.getLogger().setPrefix("verify");
try {
@@ -179,7 +168,7 @@ public class Signer {
}
}
private static void sign(JReleaserContext context, InMemoryKeyring keyring, List<FilePair> files) throws SigningException {
private static void sign(JReleaserContext context, Keyring keyring, List<FilePair> files) throws SigningException {
Path signaturesDirectory = context.getSignaturesDirectory();
try {
@@ -198,7 +187,7 @@ public class Signer {
}
}
private static PGPSignatureGenerator initSignatureGenerator(Signing signing, InMemoryKeyring keyring) throws SigningException {
private static PGPSignatureGenerator initSignatureGenerator(Signing signing, Keyring keyring) throws SigningException {
try {
PGPSecretKey pgpSecretKey = keyring.getSecretKey();
@@ -250,7 +239,7 @@ public class Signer {
}
}
private static List<FilePair> collectArtifacts(JReleaserContext context, InMemoryKeyring keyring) {
private static List<FilePair> collectArtifacts(JReleaserContext context, Keyring keyring) {
List<FilePair> files = new ArrayList<>();
Path signaturesDirectory = context.getSignaturesDirectory();
@@ -285,7 +274,7 @@ public class Signer {
return files;
}
private static boolean isValid(JReleaserContext context, InMemoryKeyring keyring, FilePair pair) {
private static boolean isValid(JReleaserContext context, Keyring keyring, FilePair pair) {
if (Files.notExists(pair.getSignatureFile())) {
context.getLogger().debug("signature does not exist: {}",
context.getBasedir().relativize(pair.getSignatureFile()));

View File

@@ -48,6 +48,6 @@ public enum Active {
public static Active of(String str) {
if (isBlank(str)) return null;
return Active.valueOf(str.toUpperCase());
return Active.valueOf(str.toUpperCase().trim());
}
}

View File

@@ -321,7 +321,7 @@ public class Distribution extends Packagers implements ExtraProperties, Activata
if (isBlank(str)) return null;
return DistributionType.valueOf(str.replaceAll(" ", "_")
.replaceAll("-", "_")
.toUpperCase());
.toUpperCase().trim());
}
}
}

View File

@@ -17,10 +17,15 @@
*/
package org.jreleaser.model;
import org.bouncycastle.openpgp.PGPException;
import org.jreleaser.util.Constants;
import org.jreleaser.util.Errors;
import org.jreleaser.util.JReleaserLogger;
import org.jreleaser.util.Version;
import org.jreleaser.util.signing.FilesKeyring;
import org.jreleaser.util.signing.InMemoryKeyring;
import org.jreleaser.util.signing.Keyring;
import org.jreleaser.util.signing.SigningException;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -332,6 +337,24 @@ public class JReleaserContext {
}
}
public Keyring createKeyring() throws SigningException {
try {
if (model.getSigning().getMode() == Signing.Mode.FILE) {
return new FilesKeyring(
basedir.resolve(model.getSigning().getResolvedPublicKey()),
basedir.resolve(model.getSigning().getResolvedSecretKey())
).initialize();
}
return new InMemoryKeyring(
model.getSigning().getResolvedPublicKey().getBytes(),
model.getSigning().getResolvedSecretKey().getBytes()
).initialize();
} catch (IOException | PGPException e) {
throw new SigningException("Could not initialize keyring", e);
}
}
public enum Mode {
ASSEMBLE,
FULL

View File

@@ -23,6 +23,7 @@ import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.jreleaser.util.StringUtils.isBlank;
import static org.jreleaser.util.StringUtils.isNotBlank;
/**
@@ -40,6 +41,7 @@ public class Signing implements Domain, Activatable {
private String publicKey;
private String secretKey;
private String passphrase;
private Mode mode;
void setAll(Signing signing) {
this.active = signing.active;
@@ -48,6 +50,7 @@ public class Signing implements Domain, Activatable {
this.publicKey = signing.publicKey;
this.secretKey = signing.secretKey;
this.passphrase = signing.passphrase;
this.mode = signing.mode;
}
@Override
@@ -136,6 +139,18 @@ public class Signing implements Domain, Activatable {
this.passphrase = passphrase;
}
public Mode getMode() {
return mode;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public void setMode(String str) {
this.mode = Mode.of(str);
}
@Override
public Map<String, Object> asMap(boolean full) {
if (!full && !isEnabled()) return Collections.emptyMap();
@@ -144,10 +159,26 @@ public class Signing implements Domain, Activatable {
map.put("enabled", isEnabled());
map.put("active", active);
map.put("armored", isArmored());
map.put("mode", mode);
map.put("publicKey", isNotBlank(publicKey) ? "************" : "**unset**");
map.put("secretKey", isNotBlank(secretKey) ? "************" : "**unset**");
map.put("passphrase", isNotBlank(passphrase) ? "************" : "**unset**");
return map;
}
public enum Mode {
MEMORY,
FILE;
@Override
public String toString() {
return name().toLowerCase();
}
public static Mode of(String str) {
if (isBlank(str)) return null;
return Mode.valueOf(str.toUpperCase().trim());
}
}
}

View File

@@ -27,7 +27,7 @@ import org.jreleaser.model.RepositoryTool;
import org.jreleaser.model.releaser.spi.Releaser;
import org.jreleaser.model.releaser.spi.Repository;
import org.jreleaser.model.tool.spi.ToolProcessingException;
import org.jreleaser.sdk.git.InMemoryGpgSigner;
import org.jreleaser.sdk.git.JReleaserGpgSigner;
import org.jreleaser.util.Constants;
import org.jreleaser.util.FileUtils;
@@ -90,7 +90,7 @@ abstract class AbstractRepositoryToolProcessor<T extends RepositoryTool> extends
commitCommand = commitCommand
.setSign(true)
.setSigningKey("**********")
.setGpgSigner(new InMemoryGpgSigner(context));
.setGpgSigner(new JReleaserGpgSigner(context));
}
commitCommand.call();

View File

@@ -0,0 +1,50 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2020-2021 Andres Almiray.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jreleaser.util.signing;
import org.bouncycastle.openpgp.PGPException;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
/**
* @author Andres Almiray
* @since 0.4.0
*/
public final class FilesKeyring extends Keyring {
private final Path publicKeyring;
private final Path secretKeyring;
public FilesKeyring(Path publicKeyring, Path secretKeyring) throws IOException, PGPException {
this.publicKeyring = publicKeyring;
this.secretKeyring = secretKeyring;
}
@Override
protected InputStream getPublicKeyRingStream() throws IOException {
return Files.newInputStream(publicKeyring);
}
@Override
protected InputStream getSecretKeyRingStream() throws IOException {
return Files.newInputStream(secretKeyring);
}
}

View File

@@ -18,82 +18,31 @@
package org.jreleaser.util.signing;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Iterator;
/**
* Adapted from {@code name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.InMemoryKeyring}
* Original author: Jens Neuhalfen
* @author Andres Almiray
* @since 0.1.0
*/
public final class InMemoryKeyring {
private final KeyFingerPrintCalculator keyFingerPrintCalculator = new BcKeyFingerprintCalculator();
private PGPPublicKeyRingCollection publicKeyRings;
private PGPSecretKeyRingCollection secretKeyRings;
public final class InMemoryKeyring extends Keyring {
private final byte[] encodedPublicKey;
private final byte[] encodedPrivateKey;
public InMemoryKeyring() throws IOException, PGPException {
this.publicKeyRings = new PGPPublicKeyRingCollection(Collections.emptyList());
this.secretKeyRings = new PGPSecretKeyRingCollection(Collections.emptyList());
public InMemoryKeyring(byte[] encodedPublicKey, byte[] encodedPrivateKey) throws IOException, PGPException {
this.encodedPublicKey = encodedPublicKey;
this.encodedPrivateKey = encodedPrivateKey;
}
public KeyFingerPrintCalculator getKeyFingerPrintCalculator() {
return keyFingerPrintCalculator;
@Override
protected InputStream getPublicKeyRingStream() throws IOException {
return new ByteArrayInputStream(encodedPublicKey);
}
public void addPublicKey(byte[] encodedPublicKey) throws IOException, PGPException {
try (
InputStream raw = new ByteArrayInputStream(encodedPublicKey);
InputStream decoded = org.bouncycastle.openpgp.PGPUtil.getDecoderStream(raw)) {
addPublicKeyRing(new PGPPublicKeyRing(decoded, keyFingerPrintCalculator));
}
}
public void addSecretKey(byte[] encodedPrivateKey) throws IOException, PGPException {
try (
InputStream raw = new ByteArrayInputStream(encodedPrivateKey);
InputStream decoded = org.bouncycastle.openpgp.PGPUtil
.getDecoderStream(raw)) {
addSecretKeyRing(new PGPSecretKeyRing(decoded, keyFingerPrintCalculator));
}
}
public void addSecretKeyRing(PGPSecretKeyRing keyring) {
this.secretKeyRings = PGPSecretKeyRingCollection.addSecretKeyRing(this.secretKeyRings, keyring);
}
public void addPublicKeyRing(PGPPublicKeyRing keyring) {
this.publicKeyRings = PGPPublicKeyRingCollection.addPublicKeyRing(this.publicKeyRings, keyring);
}
public PGPSecretKey getSecretKey() throws SigningException, PGPException {
return secretKeyRings.getSecretKey(readPublicKey().getKeyID());
}
public PGPPublicKey readPublicKey() throws SigningException {
Iterator<PGPPublicKeyRing> keyRingIter = publicKeyRings.getKeyRings();
while (keyRingIter.hasNext()) {
PGPPublicKeyRing keyRing = keyRingIter.next();
Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
while (keyIter.hasNext()) {
PGPPublicKey key = keyIter.next();
if (key.isEncryptionKey()) {
return key;
}
}
}
throw new SigningException("Did not find public key for signing.");
@Override
protected InputStream getSecretKeyRingStream() throws IOException {
return new ByteArrayInputStream(encodedPrivateKey);
}
}

View File

@@ -0,0 +1,110 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* Copyright 2020-2021 Andres Almiray.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jreleaser.util.signing;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.operator.KeyFingerPrintCalculator;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.Iterator;
/**
* Adapted from {@code name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.InMemoryKeyring}
* Original author: Jens Neuhalfen
*
* @author Andres Almiray
* @since 0.4.0
*/
public abstract class Keyring {
private final KeyFingerPrintCalculator keyFingerPrintCalculator = new BcKeyFingerprintCalculator();
private PGPPublicKeyRingCollection publicKeyRings;
private PGPSecretKeyRingCollection secretKeyRings;
public Keyring() throws IOException, PGPException {
this.publicKeyRings = new PGPPublicKeyRingCollection(Collections.emptyList());
this.secretKeyRings = new PGPSecretKeyRingCollection(Collections.emptyList());
}
public Keyring initialize() throws IOException, PGPException {
try (InputStream pub = getPublicKeyRingStream();
InputStream sec = getSecretKeyRingStream()) {
addPublicKey(pub);
addSecretKey(sec);
}
return this;
}
public KeyFingerPrintCalculator getKeyFingerPrintCalculator() {
return keyFingerPrintCalculator;
}
protected abstract InputStream getPublicKeyRingStream() throws IOException;
protected abstract InputStream getSecretKeyRingStream() throws IOException;
public void addPublicKey(InputStream raw) throws IOException, PGPException {
try (InputStream decoded = org.bouncycastle.openpgp.PGPUtil.getDecoderStream(raw)) {
addPublicKeyRing(new PGPPublicKeyRing(decoded, keyFingerPrintCalculator));
}
}
public void addSecretKey(InputStream raw) throws IOException, PGPException {
try (InputStream decoded = org.bouncycastle.openpgp.PGPUtil.getDecoderStream(raw)) {
addSecretKeyRing(new PGPSecretKeyRing(decoded, keyFingerPrintCalculator));
}
}
public void addSecretKeyRing(PGPSecretKeyRing keyring) {
this.secretKeyRings = PGPSecretKeyRingCollection.addSecretKeyRing(this.secretKeyRings, keyring);
}
public void addPublicKeyRing(PGPPublicKeyRing keyring) {
this.publicKeyRings = PGPPublicKeyRingCollection.addPublicKeyRing(this.publicKeyRings, keyring);
}
public PGPSecretKey getSecretKey() throws SigningException, PGPException {
return secretKeyRings.getSecretKey(readPublicKey().getKeyID());
}
public PGPPublicKey readPublicKey() throws SigningException {
Iterator<PGPPublicKeyRing> keyRingIter = publicKeyRings.getKeyRings();
while (keyRingIter.hasNext()) {
PGPPublicKeyRing keyRing = keyRingIter.next();
Iterator<PGPPublicKey> keyIter = keyRing.getPublicKeys();
while (keyIter.hasNext()) {
PGPPublicKey key = keyIter.next();
if (key.isEncryptionKey()) {
return key;
}
}
}
throw new SigningException("Did not find public key for signing.");
}
}

View File

@@ -39,4 +39,8 @@ interface Signing {
Property<String> getPublicKey()
Property<String> getSecretKey()
Property<org.jreleaser.model.Signing.Mode> getMode()
void setMode(String mode)
}

View File

@@ -41,6 +41,7 @@ class SigningImpl implements Signing {
final Property<String> passphrase
final Property<String> publicKey
final Property<String> secretKey
final Property<org.jreleaser.model.Signing.Mode> mode
@Inject
SigningImpl(ObjectFactory objects) {
@@ -49,6 +50,7 @@ class SigningImpl implements Signing {
passphrase = objects.property(String).convention(Providers.notDefined())
publicKey = objects.property(String).convention(Providers.notDefined())
secretKey = objects.property(String).convention(Providers.notDefined())
mode = objects.property(org.jreleaser.model.Signing.Mode).convention(org.jreleaser.model.Signing.Mode.MEMORY)
}
@Internal
@@ -67,6 +69,13 @@ class SigningImpl implements Signing {
}
}
@Override
void setMode(String str) {
if (isNotBlank(str)) {
mode.set(org.jreleaser.model.Signing.Mode.of(str.trim()))
}
}
org.jreleaser.model.Signing toModel() {
org.jreleaser.model.Signing sign = new org.jreleaser.model.Signing()
if (active.present) sign.active = active.get()
@@ -74,6 +83,7 @@ class SigningImpl implements Signing {
if (passphrase.present) sign.passphrase = passphrase.get()
if (publicKey.present) sign.publicKey = publicKey.get()
if (secretKey.present) sign.secretKey = secretKey.get()
if (mode.present) sign.mode = mode.get()
sign
}
}

View File

@@ -27,6 +27,7 @@ public class Signing implements Activatable {
private String publicKey;
private String secretKey;
private String passphrase;
private Mode mode;
void setAll(Signing signing) {
this.active = signing.active;
@@ -82,4 +83,21 @@ public class Signing implements Activatable {
public void setPassphrase(String passphrase) {
this.passphrase = passphrase;
}
public Mode getMode() {
return mode;
}
public void setMode(Mode mode) {
this.mode = mode;
}
public String resolveMode() {
return mode != null ? mode.name() : null;
}
public enum Mode {
MEMORY,
FILE
}
}

View File

@@ -515,6 +515,7 @@ public final class JReleaserModelConverter {
s.setPublicKey(signing.getPublicKey());
s.setSecretKey(signing.getSecretKey());
s.setPassphrase(signing.getPassphrase());
s.setMode(signing.resolveMode());
return s;
}

View File

@@ -43,7 +43,7 @@ import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.transport.CredentialsProvider;
import org.jreleaser.model.JReleaserContext;
import org.jreleaser.model.Signing;
import org.jreleaser.util.signing.InMemoryKeyring;
import org.jreleaser.util.signing.Keyring;
import org.jreleaser.util.signing.SigningException;
import java.io.ByteArrayOutputStream;
@@ -58,7 +58,7 @@ import static org.jreleaser.util.StringUtils.isNotBlank;
* @author Andres Almiray
* @since 0.1.0
*/
public class InMemoryGpgSigner extends GpgSigner implements GpgObjectSigner {
public class JReleaserGpgSigner extends GpgSigner implements GpgObjectSigner {
static {
// replace BC provider with our version
Security.removeProvider(BouncyCastleProvider.PROVIDER_NAME);
@@ -68,7 +68,7 @@ public class InMemoryGpgSigner extends GpgSigner implements GpgObjectSigner {
private final JReleaserContext context;
public InMemoryGpgSigner(JReleaserContext context) {
public JReleaserGpgSigner(JReleaserContext context) {
this.context = context;
}
@@ -98,7 +98,7 @@ public class InMemoryGpgSigner extends GpgSigner implements GpgObjectSigner {
public void signObject(ObjectBuilder object, String gpgSigningKey, PersonIdent committer, CredentialsProvider credentialsProvider, GpgConfig config)
throws CanceledException, UnsupportedSigningFormatException {
try {
InMemoryKeyring keyring = createInMemoryKeyring(context.getModel().getSigning());
Keyring keyring = context.createKeyring();
PGPSignatureGenerator signatureGenerator = initSignatureGenerator(context.getModel().getSigning(), keyring);
adjustCommiterId(signatureGenerator, committer, keyring);
signObject(signatureGenerator, object);
@@ -107,18 +107,7 @@ public class InMemoryGpgSigner extends GpgSigner implements GpgObjectSigner {
}
}
private InMemoryKeyring createInMemoryKeyring(Signing sign) throws SigningException {
try {
InMemoryKeyring keyring = new InMemoryKeyring();
keyring.addPublicKey(sign.getResolvedPublicKey().getBytes());
keyring.addSecretKey(sign.getResolvedSecretKey().getBytes());
return keyring;
} catch (IOException | PGPException e) {
throw new SigningException("Could not initialize keyring", e);
}
}
private PGPSignatureGenerator initSignatureGenerator(Signing signing, InMemoryKeyring keyring) throws SigningException {
private PGPSignatureGenerator initSignatureGenerator(Signing signing, Keyring keyring) throws SigningException {
try {
PGPSecretKey secretKey = keyring.getSecretKey();
@@ -139,7 +128,7 @@ public class InMemoryGpgSigner extends GpgSigner implements GpgObjectSigner {
}
}
private void adjustCommiterId(PGPSignatureGenerator signatureGenerator, PersonIdent committer, InMemoryKeyring keyring)
private void adjustCommiterId(PGPSignatureGenerator signatureGenerator, PersonIdent committer, Keyring keyring)
throws SigningException {
PGPPublicKey publicKey = keyring.readPublicKey();
PGPSignatureSubpacketGenerator subpackets = new PGPSignatureSubpacketGenerator();