1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-30 08:12:06 +01:00

Fix signature creation using keys without preferred algorithms

This commit is contained in:
Paul Schaub 2021-01-21 13:47:43 +01:00
parent 831dbb3c5d
commit 74c0c8a32e
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
4 changed files with 245 additions and 2 deletions

View file

@ -16,6 +16,8 @@
package org.pgpainless.key.util; package org.pgpainless.key.util;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -42,6 +44,9 @@ public class OpenPgpKeyAttributeUtil {
if (signatureType == SignatureType.POSITIVE_CERTIFICATION if (signatureType == SignatureType.POSITIVE_CERTIFICATION
|| signatureType == SignatureType.GENERIC_CERTIFICATION) { || signatureType == SignatureType.GENERIC_CERTIFICATION) {
int[] hashAlgos = signature.getHashedSubPackets().getPreferredHashAlgorithms(); int[] hashAlgos = signature.getHashedSubPackets().getPreferredHashAlgorithms();
if (hashAlgos == null) {
continue;
}
for (int h : hashAlgos) { for (int h : hashAlgos) {
hashAlgorithms.add(HashAlgorithm.fromId(h)); hashAlgorithms.add(HashAlgorithm.fromId(h));
} }
@ -53,4 +58,40 @@ public class OpenPgpKeyAttributeUtil {
} }
return hashAlgorithms; return hashAlgorithms;
} }
/**
* Return the hash algorithm that was used in the latest self signature.
*
* @param publicKey public key
* @return list of hash algorithm
*/
public static List<HashAlgorithm> guessPreferredHashAlgorithms(PGPPublicKey publicKey) {
HashAlgorithm hashAlgorithm = null;
Date lastCreationDate = null;
Iterator<?> keySignatures = publicKey.getSignatures();
while (keySignatures.hasNext()) {
PGPSignature signature = (PGPSignature) keySignatures.next();
if (signature.getKeyID() != publicKey.getKeyID()) {
continue;
}
SignatureType signatureType = SignatureType.valueOf(signature.getSignatureType());
if (signatureType != SignatureType.POSITIVE_CERTIFICATION
&& signatureType != SignatureType.GENERIC_CERTIFICATION) {
continue;
}
Date creationDate = signature.getCreationTime();
if (lastCreationDate == null || lastCreationDate.before(creationDate)) {
lastCreationDate = creationDate;
hashAlgorithm = HashAlgorithm.fromId(signature.getHashAlgorithm());
}
}
if (hashAlgorithm == null) {
return Collections.emptyList();
}
return Collections.singletonList(hashAlgorithm);
}
} }

View file

@ -37,14 +37,18 @@ public class SignatureUtils {
private static BcPGPContentSignerBuilder getPgpContentSignerBuilderForKey(PGPPublicKey publicKey) { private static BcPGPContentSignerBuilder getPgpContentSignerBuilderForKey(PGPPublicKey publicKey) {
List<HashAlgorithm> preferredHashAlgorithms = OpenPgpKeyAttributeUtil.getPreferredHashAlgorithms(publicKey); List<HashAlgorithm> preferredHashAlgorithms = OpenPgpKeyAttributeUtil.getPreferredHashAlgorithms(publicKey);
if (preferredHashAlgorithms.isEmpty()) {
preferredHashAlgorithms = OpenPgpKeyAttributeUtil.guessPreferredHashAlgorithms(publicKey);
}
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(preferredHashAlgorithms); HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(preferredHashAlgorithms);
return new BcPGPContentSignerBuilder(publicKey.getAlgorithm(), hashAlgorithm.getAlgorithmId()); return new BcPGPContentSignerBuilder(publicKey.getAlgorithm(), hashAlgorithm.getAlgorithmId());
} }
private static HashAlgorithm negotiateHashAlgorithm(List<HashAlgorithm> preferredHashAlgorithms) { private static HashAlgorithm negotiateHashAlgorithm(List<HashAlgorithm> preferredHashAlgorithms) {
// TODO: Match our list of supported hash algorithms against the list, to determine the best suitable algo. if (preferredHashAlgorithms.isEmpty()) {
// For now we just take the first algorithm in the list and hope that BC has support for it. return HashAlgorithm.SHA512;
}
return preferredHashAlgorithms.get(0); return preferredHashAlgorithms.get(0);
} }
} }

View file

@ -0,0 +1,135 @@
/*
* Copyright 2021 Paul Schaub.
*
* 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.pgpainless.key.modification;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditorInterface;
import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.protection.UnprotectedKeysProtector;
public class RevokeKeyWithoutPreferredAlgorithmsOnPrimaryKey {
/**
* This key has a primary key which does not carry a signature with preferred algorithms.
* We therefore have to guess a suitable algorithm to create a new signature with the new expiration date.
*/
private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
"Version: FlowCrypt Email Encryption 8.0.1\n" +
"Comment: Seamlessly send and receive encrypted email\n" +
"\n" +
"xcLYBGAIUhoBCADEnk3EACyTc/GVkwMI5MHls6oIlJT42qB2Wh6PF7FXpnS7\n" +
"0hBqZLSC0N+UxCROf/EUxYq7wUpwVp63Pd4hVyn1ppf1XOJztluGiXDPkVR6\n" +
"fQMMYXeIXnnJMcIBZLYYKUe+wMGMIz8oYzbU8dJHE8TCbsRukHtk5aFMTu1I\n" +
"+MCgq2jU/WVUYMJHILMF/e0hKix1lZBDyhg2g47xhdRvJgmHX81CKjOC4Jt/\n" +
"vz26jW276+koIqUK318EPBgYObsjEr7UcAKbOuUDbExZKdcgBCPswwcLaM7+\n" +
"kLBpvREYugyXlj2WT7laQB2iHc3aBpeYbPJ3Fn2UyXSICQaOAaM+b0+3ABEB\n" +
"AAEAB/44AlRqyhXopzWhgzBxHyEw+v4r1Y+eWEEvlbpwzrIBIvTL8Com9KsL\n" +
"PM8EBN/G0OFvJlq/427+E17BGkmlu7vDM4LELYKArejiqVJOfrrO7b+pjjZL\n" +
"zYXpz1fRp8vLlC7Q4v3/mqbKLYEYJg7dmn8JWB5y1IuiEVvibfVgPSQ6YDnu\n" +
"ZAbqxjxjrDa5d8pwFAuh+zkDrU5CY9Eoyuzi+Y2+K4XTchC/BZbEm+oClUiu\n" +
"KHxgyrby8BobLvhxr/duH6eUpDX8Czcw71ceWlBn+2fkh4JgnozKL/qED+I6\n" +
"URRE+GAEbaMT3f14HxyJvDcfZjwKEHhvSOPuzGCBI9dPtcepBADallzOOiQ+\n" +
"2+dy02Do766rr42KlRVa+WG2MTOqsCeA0wh/Hx8e7ufRVOfKczBZzDO9P2C6\n" +
"mEXIr87GxNsSD4MhzP9k9JNAIQXMb/bYGzB2o+sGPM1GeDpQsjaM2Cv1VrHt\n" +
"9XgP3uAUkllxpPgRP5Wjto7UXDu788V6Jf5yYgJ2dQQA5kVY14NeWo5/cKKH\n" +
"PfrYTMnw/7SECHTYaIhtZpAM040snjf/YYSfdIeVhoY+0wdbjKlLX8p4K0kZ\n" +
"cWdweLJ9EHic1I150D3Ql9RyNuS99Ti52bl2uSAukgOY6jdSgG2giy9Hl0BC\n" +
"Yls4J1/z+q0SjbrVDEUqKi2pgtBqTZ9ZH/sEAOFllvNtcEi47+hVd1ue8GfD\n" +
"uMHK7wj0dP6yQcS7DjooNLojlAQkrDWiIhR4ccvnr89e3k90HIfBP4dIJmfe\n" +
"Ey2GG9fCDts1QHdKErceQjorx68DvoYIKCMZwCeBJdXLdi8FVwBlNpyEgKsl\n" +
"FaynAXcbc+YJGHz0peLvIUiN7UyUP9vNFnRlc3Qta2V5QGZsb3djcnlwdC5j\n" +
"b23CwGIEEwEKAAwFAmAIUhoFiQAAC7cACgkQ9FHRAKrO1DL30Af+P9R+651n\n" +
"TTh40QUij44jnbZy/sBPWDGCCepLB+dAk+v7itd/vDT+CApnpewL7gFR7JoA\n" +
"cW+kV/8I8U2bAJwwakae36eTd+CGFPP4QHAHl/i6UBuC25Xr1UPUV6OQej+P\n" +
"1HGXPQN34oP5LyNQE3jAwkYKcBQan5LZ+7UzRCy0VHoHzY1bL24CYJpEmUrC\n" +
"un31P4McG921WfeFLfzqMyoDty/j1bt8BmGp/BFBSnvZJzHofRdRC3AfWncx\n" +
"lUQc9mth/DqQhipbu4FuRLQLNM0l1XcPjCcOaSdOylHQUMWhXhSdGM3/r74L\n" +
"kfYX60rotxuHuenXnt1QQxsbg+eIQ/DSpc0ZdGVzdC1zZWNvbmRAZmxvd2Ny\n" +
"eXB0LmNvbcLAYgQTAQoADAUCYAhSGgWJAAALtwAKCRD0UdEAqs7UMjxkB/4p\n" +
"cqeul3RrjpxkDwCsFdT9fwwE65PfzlImPDKI8T4DUeKOBthc/v6F+zAqpmgi\n" +
"nqSFZRD48dzFQHXA5NU+P9fVf0JQP/P5tiGZv9xsGVpteAH+t/7OYNboafbB\n" +
"h1HDx+A+M44J9O6xDdI+qiRKjgo4SmWBMxKS64Bde35lkesulujefWKpOw5j\n" +
"2Xg3vrt8mE+TDJpWJQ1fgt16mqgIkeqAQW5A6lMxr7KEesXmPou98ZLBEJjX\n" +
"uzS0Yodjk5BnanooOk6FytXh1XTHuXW4vzc2gveKODF2YjMi+KxGi6yk1tIy\n" +
"UjgkaoCVDnjvU2eChr3Ij3TinBhyDHHSRW1kMULjx8LYBGAIUhoBCADN0iMq\n" +
"+ZYNVW4vRJSvYEs9bFBS1tcyZfBtkbQ08kMexpoxLwW1l1cyil+cSewU0WpL\n" +
"XSFOcJYaNfKiA0u4lMh5e2+H7eNvX3qpmoifmWO2lhmBopo3I5F02Kb+MS17\n" +
"95LtN7GHZQ9F1BGzG13FIUTCZiQwmJBKay4qTd7/GIr7U6x7JUkAqbcPvbmP\n" +
"hM5ePLP1sTPyGtY7wgixCKKeifFHvQ79zMcErgiWuuS1LyMwv7awLPjVQtkO\n" +
"rBOKb3zoMtFJrXfnXYajXpNgYsQ+ZcDvjdi8h83RahbQmSUinCR7VVi57kvK\n" +
"mIaZSSnlOuqCaPjvXQRdUYAdgyIMNz4eA53lABEBAAEAB/4orHyZL0oVN/sG\n" +
"mcu1TbcIvCkyebT8x3LoQElHxlF32UEa86Mx8+a+POSwntYp9gmGu7CLjwnG\n" +
"w77/f928rCBjC37qspsFxS1ZK4oQ2i//ovGG8hJ+T4fc+ryjkqXdr/sH6H/r\n" +
"lQ/b2ZEm30NcY9rx/Nvtg5TONBijMRDewiOjD1cERLLBke6ohKv4teKxUEdE\n" +
"WYQnY2fEdaYxC4NzVgjKY2/F0MJrunJwZanI1lIuJF9avXTJTSt2qBWtmoSx\n" +
"my0cyWGzSq+qw9Aa4uG4CDFLb3ZibmeB2lZVUzam+GRbBGAezeKpqt2w7OlM\n" +
"aIfNqIRpP9vjGhDXlbe+CBk3hfY5BADcjj7GxHlEc+1QqOi/TSVPcqVzlyUK\n" +
"QZ5o4yC2WslQoQgBs0lG1m01QbJ7dQIY3iZ32PF/vK8I1/zqPykCFdRiL+dN\n" +
"Fq2a89BmqVbenZbuyOmQX8b2pYa+yOH+dT3FSV88fFa8gOJhnvB+qZJlc0yA\n" +
"OOe0868HeKTosuOaO09KKQQA7uWyPaj5myzfmHTMHOtJ5O6PT86gj5QYut2c\n" +
"fAJV81c1BEC5RUbmTojS5lo4l+zXCRBe9UxYt2KtrBW1l0SkJVfNIBLaRWMF\n" +
"KAWBMFye0lEBiMxMp5arXdkgAew/7hfrYmIXM70Y9Gat4KaDmMmB3LDmBoyl\n" +
"mbSQ/XdvI+fH5V0EAO1EtvBWUx56Ax0oyVMuNZxUanwdMedjfPyKkwLq9I2+\n" +
"/O0bx1V30EfmRvhy+EJTx+uCEgY9Pv81f28z676XOUdLoEyKAzAb9PWWgiCr\n" +
"VbmAsFdu5qE87d88c0p6/dmx/DKPTpFkkEZl2We7OVSQOaECrXHYqudsE5ZI\n" +
"LdetYlBhTQ3CwHkEGAEKACMCGwwFFgIDAQAECwkIBwUVCgkICwIeAQUCYAhS\n" +
"GgWJAAALtwAKCRD0UdEAqs7UMnmFB/9WD8+esxTjjVh/P+Rzhc0VQGt1o5TH\n" +
"bBVw8O2gA+mC78nqz26LfhGudVVZHWq7u3lIFuO/O6Ctp5BEqXFWRT+ikNHg\n" +
"zXAjQc0DxACt63xqaVXduvA4FDlmJnbrOnkH5MJUo4pcAfSTELDQWLuMIQsD\n" +
"ogYChjA3PG4bLEP0kz2NJuF5bHz/MNpexTaLoxNfA08MjkJCEhOArKQZj3kW\n" +
"OgbD1QvNflR5YA7I2pEkCCOLqjkWAOgowfb+ipUb55eZ2/4hM1S1kbrRXN80\n" +
"6GN3Hs+/BxvHo+JufAuSelNoln3zuX+GzAQaeZgkcN+RXcFiFOBEMNUmlGRl\n" +
"zqyz7qFrSCvY\n" +
"=3Zyp\n" +
"-----END PGP PRIVATE KEY BLOCK-----";
@Test
public void testChangingExpirationTimeWithKeyWithoutPrefAlgos() throws IOException, PGPException {
Date expirationDate = new Date();
PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY);
List<OpenPgpV4Fingerprint> fingerprintList = new ArrayList<>();
for (PGPSecretKey secretKey : secretKeys) {
fingerprintList.add(new OpenPgpV4Fingerprint(secretKey));
}
SecretKeyRingProtector protector = new UnprotectedKeysProtector();
SecretKeyRingEditorInterface modify = PGPainless.modifyKeyRing(secretKeys)
.setExpirationDate(expirationDate, protector);
for (int i = 1; i < fingerprintList.size(); i++) {
modify.setExpirationDate(fingerprintList.get(i), expirationDate, protector);
}
secretKeys = modify.done();
KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys);
assertEquals(expirationDate.getTime(), info.getExpirationDate().getTime(), 1000);
for (OpenPgpV4Fingerprint fingerprint : fingerprintList) {
assertEquals(expirationDate.getTime(), info.getExpirationDate(fingerprint).getTime(), 1000);
}
}
}

View file

@ -0,0 +1,63 @@
/*
* Copyright 2021 Paul Schaub.
*
* 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.pgpainless.util;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.key.generation.KeySpec;
import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.key.generation.type.eddsa.EdDSACurve;
import org.pgpainless.key.util.OpenPgpKeyAttributeUtil;
public class GuessPreferredHashAlgorithmTest {
@Test
public void test() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
PGPSecretKeyRing secretKeys = PGPainless.generateKeyRing()
.withMasterKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519))
.withKeyFlags(KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)
.withDetailedConfiguration()
// Do not specify preferred algorithms
.withPreferredSymmetricAlgorithms(new SymmetricKeyAlgorithm[] {})
.withPreferredHashAlgorithms(new HashAlgorithm[] {})
.withPreferredCompressionAlgorithms(new CompressionAlgorithm[] {})
.done())
.withPrimaryUserId("test@test.test")
.withoutPassphrase()
.build();
PGPPublicKey publicKey = secretKeys.getPublicKey();
assertEquals(Collections.emptyList(),
OpenPgpKeyAttributeUtil.getPreferredHashAlgorithms(publicKey));
assertEquals(Collections.singletonList(HashAlgorithm.SHA512),
OpenPgpKeyAttributeUtil.guessPreferredHashAlgorithms(publicKey));
}
}