Skip to content

Commit

Permalink
Merge pull request #6 from s4u/sub-keys
Browse files Browse the repository at this point in the history
support for sub keys
  • Loading branch information
slawekjaranowski authored Dec 23, 2020
2 parents 16e85f1 + 460e127 commit 40afc42
Show file tree
Hide file tree
Showing 16 changed files with 334 additions and 36 deletions.
7 changes: 5 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,9 @@
</goals>
<configuration>
<artifactItems>
<!--
https://repository.apache.org/content/groups/snapshots/org/apache/maven/maven/
-->
<artifactItem>
<groupId>org.apache.maven</groupId>
<artifactId>apache-maven</artifactId>
Expand All @@ -332,10 +335,10 @@
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${project.build.directory}/keys-cache/BC/06</outputDirectory>
<outputDirectory>${project.build.directory}/keys-cache</outputDirectory>
<resources>
<resource>
<directory>src/test/resources/BC/06</directory>
<directory>src/test/resources/public-keys</directory>
</resource>
</resources>
</configuration>
Expand Down
2 changes: 1 addition & 1 deletion src/it/big-artifact/pom-test.xml
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@
<configuration>
<keyId>AC71B3E31C0C0D38</keyId>
<keyPass>testPass</keyPass>
<keyFile>@project.basedir@/src/test/resources/pgp-priv-key.asc</keyFile>
<keyFile>@project.basedir@/src/test/resources/priv-key.asc</keyFile>
</configuration>
</execution>

Expand Down
2 changes: 1 addition & 1 deletion src/it/pom-packaging/pom-test.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<configuration>
<keyId>AC71B3E31C0C0D38</keyId>
<keyPass>testPass</keyPass>
<keyFile>@project.basedir@/src/test/resources/pgp-priv-key.asc</keyFile>
<keyFile>@project.basedir@/src/test/resources/priv-key.asc</keyFile>
</configuration>
</execution>

Expand Down
5 changes: 2 additions & 3 deletions src/it/standard-packaging/pom-test.xml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>test-group</groupId>
<groupId>test-group-sub</groupId>
<artifactId>standard-packaging</artifactId>
<version>${revision}</version>

Expand Down Expand Up @@ -66,8 +66,7 @@
<goal>sign</goal>
</goals>
<configuration>
<keyPass>testPass</keyPass>
<keyFile>@project.basedir@/src/test/resources/pgp-priv-key.asc</keyFile>
<keyFile>@project.basedir@/src/test/resources/priv-sub-key-no-pass.asc</keyFile>
</configuration>
</execution>

Expand Down
3 changes: 2 additions & 1 deletion src/it/verify-sign/keys-map.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@
# limitations under the License.
#

test-group = 0xBC065DCAA903A7785FF79E6EAC71B3E31C0C0D38
test-group = 0xBC065DCAA903A7785FF79E6EAC71B3E31C0C0D38
test-group-sub = 0xF6A2666F8A3B4AF014BCA4B973593FDED8C63A19 # master key fingerprint
6 changes: 3 additions & 3 deletions src/it/verify-sign/pom-test.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,18 +41,18 @@
</dependency>

<dependency>
<groupId>test-group</groupId>
<groupId>test-group-sub</groupId>
<artifactId>standard-packaging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>test-group</groupId>
<groupId>test-group-sub</groupId>
<artifactId>standard-packaging</artifactId>
<version>1.1.1</version>
<type>dat</type>
</dependency>
<dependency>
<groupId>test-group</groupId>
<groupId>test-group-sub</groupId>
<artifactId>standard-packaging</artifactId>
<version>1.1.1</version>
<type>dat</type>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright 2020 Slawomir Jaranowski
*
* 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
*
* http://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.simplify4u.plugins.sign.openpgp;

import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature;

/**
* Utility for operation on Secret Keys.
*
* @author Slawomir Jaranowski
*/
public final class PGPSecretKeyUtils {

private PGPSecretKeyUtils() {
}

/**
* Generate keyId as hex string.
*
* @param secretKey a key to print id
*
* @return keyId in hex format
*/
public static String getKeyId(PGPSecretKey secretKey) {
return String.format("%16X", secretKey.getKeyID());
}

/**
* List of user ids from secret key. If secret key is sub key list is taken from master key.
*
* @param secretKey a secret key for user ids
* @param secretKeyRing a keyRing of connected keys - need for sub key
*
* @return List user ids from key
*/
public static Collection<String> getUserIDs(PGPSecretKey secretKey, PGPSecretKeyRing secretKeyRing) {
// use getRawUserIDs and standard java String to transform byte array to utf8
// because BC generate exception if there is some problem in decoding utf8
// https://github.com/s4u/pgpverify-maven-plugin/issues/61
Set<byte[]> ret = new LinkedHashSet<>();
secretKey.getPublicKey().getRawUserIDs().forEachRemaining(ret::add);

getMasterKey(secretKey, secretKeyRing).ifPresent(masterKey ->
masterKey.getPublicKey().getRawUserIDs().forEachRemaining(ret::add)
);

return ret.stream()
.map(b -> new String(b, StandardCharsets.UTF_8))
.collect(Collectors.toSet());
}

/**
* Generate string with key id description.
*
* @param secretKey given key
* @param secretKeyRing keys ring with master and sub keys
*
* @return string with key id description
*/
public static String keyIdDescription(PGPSecretKey secretKey, PGPSecretKeyRing secretKeyRing) {

Optional<PGPSecretKey> masterKey = getMasterKey(secretKey, secretKeyRing);

if (masterKey.isPresent()) {
return String.format("SubKeyId: 0x%016X of %s", secretKey.getKeyID(), fingerprint(masterKey.get()));
} else {
return "KeyId: " + fingerprint(secretKey);
}
}

/**
* Return master key for given sub public key.
*
* @param secretKey given key
* @param secretKeyRing keys ring with master and sub keys
*
* @return master key of empty if not found or given key is master key
*/
@SuppressWarnings("unchecked")
public static Optional<PGPSecretKey> getMasterKey(PGPSecretKey secretKey, PGPSecretKeyRing secretKeyRing) {

if (secretKey.isMasterKey()) {
return Optional.empty();
}

Iterable<PGPSignature> signatures = () ->
secretKey.getPublicKey().getSignaturesOfType(PGPSignature.SUBKEY_BINDING);

return StreamSupport.stream(signatures.spliterator(), false)
.map(s -> secretKeyRing.getSecretKey(s.getKeyID()))
.findFirst();
}

/**
* Generate string version of key fingerprint
*
* @param secretKey given key
*
* @return fingerprint as string
*/
public static String fingerprint(PGPSecretKey secretKey) {
return fingerprintToString(secretKey.getPublicKey().getFingerprint());
}

private static String fingerprintToString(byte[] bytes) {
StringBuilder ret = new StringBuilder();
ret.append("0x");
for (byte b : bytes) {
ret.append(String.format("%02X", b));
}
return ret.toString();
}
}
43 changes: 26 additions & 17 deletions src/main/java/org/simplify4u/plugins/sign/openpgp/PGPSigner.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,14 @@
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.stream.StreamSupport;
import javax.inject.Named;

import static org.simplify4u.plugins.sign.openpgp.PGPSecretKeyUtils.getKeyId;
import static org.simplify4u.plugins.sign.openpgp.PGPSecretKeyUtils.getUserIDs;
import static org.simplify4u.plugins.sign.openpgp.PGPSecretKeyUtils.keyIdDescription;

import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.BCPGOutputStream;
Expand Down Expand Up @@ -58,13 +61,15 @@ public class PGPSigner {
private PGPSecretKey secretKey;
private PGPPrivateKey pgpPrivateKey;
private PGPSignatureSubpacketVector hashSubPackets;
private PGPSecretKeyRing secretKeyRing;

PGPSigner() {
// empty one
}

/**
* Setup key info which will be used for signing
*
* @param keyInfo private key info
*/
public void setKeyInfo(PGPKeyInfo keyInfo) {
Expand All @@ -78,9 +83,9 @@ public void setKeyInfo(PGPKeyInfo keyInfo) {
}

if (LOGGER.isInfoEnabled()) {
List<String> uIds = new ArrayList<>();
secretKey.getUserIDs().forEachRemaining(uIds::add);
LOGGER.info("Loaded keyId: {}, uIds: {}", String.format("%16X", secretKey.getKeyID()), uIds);
LOGGER.info("Loaded {}, uids: {}",
keyIdDescription(secretKey, secretKeyRing),
getUserIDs(secretKey, secretKeyRing));
}
}

Expand All @@ -101,20 +106,20 @@ private void loadKey() throws IOException, PGPException {
new JcaKeyFingerprintCalculator());

Long keyId = pgpKeyInfo.getId();
Optional<PGPSecretKey> secretKeyOptional;
if (keyId != null) {
secretKey = pgpSecretKeyRingCollection.getSecretKey(keyId);
secretKeyOptional = Optional.ofNullable(pgpSecretKeyRingCollection.getSecretKey(keyId));
} else {
// retrieve first master key
Iterator<PGPSecretKeyRing> keyRings = pgpSecretKeyRingCollection.getKeyRings();
if (keyRings.hasNext()) {
PGPSecretKeyRing secretKeys = keyRings.next();
secretKey = secretKeys.getSecretKey();
}
// retrieve first key with private key
secretKeyOptional = StreamSupport.stream(pgpSecretKeyRingCollection.spliterator(), false)
.flatMap(k -> StreamSupport.stream(k.spliterator(), false))
.filter(key -> !key.isPrivateKeyEmpty())
.findFirst();
}

if (secretKey == null) {
throw new PGPSignerException("Secret key not found");
}
secretKey = secretKeyOptional.orElseThrow(() -> new PGPSignerException("Secret key not found"));

secretKeyRing = pgpSecretKeyRingCollection.getSecretKeyRing(secretKey.getKeyID());

if (secretKey.getKeyEncryptionAlgorithm() == SymmetricKeyAlgorithmTags.NULL && pgpKeyInfo.getPass() != null) {
LOGGER.warn("Plain secret key - password is not needed");
Expand All @@ -124,6 +129,10 @@ private void loadKey() throws IOException, PGPException {
throw new PGPSignerException("Secret key is encrypted - keyPass is required");
}

if (secretKey.isPrivateKeyEmpty()) {
throw new PGPSignerException("Private key not found for keyId: " + getKeyId(secretKey));
}

pgpPrivateKey = secretKey
.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().build(pgpKeyInfo.getPass()));
}
Expand All @@ -139,7 +148,7 @@ private void loadKey() throws IOException, PGPException {
public void sign(InputStream inputStream, Path outputPath) {

PGPSignatureGenerator sGen = new PGPSignatureGenerator(
new JcaPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA256));
new JcaPGPContentSignerBuilder(secretKey.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA512));

try {
sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivateKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ void setup() {
when(artifactSignerFactory.getSigner(any())).thenReturn(artifactSigner);

//setup default values of mojo
mojo.setKeyFile(new File(getClass().getResource("/pgp-priv-key-no-pass.asc").getFile()));
mojo.setKeyFile(new File(getClass().getResource("/priv-key-no-pass.asc").getFile()));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class PGPKeyInfoTest {
private static final String KEY_PASS_STR = "pass";
private static final char[] KEY_PASS = KEY_PASS_STR.toCharArray();

private static final File KEY_FILE = new File(PGPKeyInfo.class.getResource("/pgp-priv-key-no-pass.asc").getFile());
private static final File KEY_FILE = new File(PGPKeyInfo.class.getResource("/priv-key-no-pass.asc").getFile());

@Test
void keyFromFileAllPropertiesSet() throws FileNotFoundException {
Expand Down
Loading

0 comments on commit 40afc42

Please sign in to comment.