diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java b/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java index 760977a3..e6e55fca 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/collection/PGPKeyRingCollection.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Flowcrypt a.s. Copyright 2021 Paul Schaub + * Copyright 2021 Paul Schaub. Copyright 2021 Flowcrypt a.s. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -77,7 +77,8 @@ public class PGPKeyRingCollection { pgpPublicKeyRingCollection = new PGPPublicKeyRingCollection(publicKeyRings); } - public PGPKeyRingCollection(Collection collection, boolean isSilent) throws IOException, PGPException { + public PGPKeyRingCollection(@Nonnull Collection collection, boolean isSilent) + throws IOException, PGPException { List secretKeyRings = new ArrayList<>(); List publicKeyRings = new ArrayList<>(); diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java index adb7e1ae..0f24ff85 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2021 Paul Schaub. + * Copyright 2021 Paul Schaub. Copyright 2021 Flowcrypt a.s. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import org.bouncycastle.bcpg.ECDHPublicBCPGKey; import org.bouncycastle.bcpg.ECDSAPublicBCPGKey; import org.bouncycastle.bcpg.ECPublicBCPGKey; import org.bouncycastle.bcpg.EdDSAPublicBCPGKey; +import org.bouncycastle.bcpg.S2K; import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKey; @@ -39,6 +40,38 @@ public class KeyInfo { this.secretKey = null; } + public String getCurveName() { + return getCurveName(publicKey); + } + + /** + * Returns indication that a contained secret key is encrypted. + * + * @return true if secret key is encrypted, false if secret key is not encrypted or there is public key only. + */ + public boolean isEncrypted() { + return secretKey != null && isEncrypted(secretKey); + } + + /** + * Returns indication that a contained secret key is not encrypted. + * + * @return true if secret key is not encrypted or there is public key only, false if secret key is encrypted. + */ + public boolean isDecrypted() { + return secretKey == null || isDecrypted(secretKey); + } + + /** + * Returns indication that a contained secret key has S2K of a type GNU_DUMMY_S2K. + * + * @return true if secret key has S2K of a type GNU_DUMMY_S2K, false if there is public key only, + * or S2K on the secret key is absent or not of a type GNU_DUMMY_S2K. + */ + public boolean hasDummyS2K() { + return secretKey != null && hasDummyS2K(secretKey); + } + public static String getCurveName(PGPPublicKey publicKey) { PublicKeyAlgorithm algorithm = PublicKeyAlgorithm.fromId(publicKey.getAlgorithm()); ECPublicBCPGKey key; @@ -64,4 +97,35 @@ public class KeyInfo { public static String getCurveName(ECPublicBCPGKey key) { return ECUtil.getCurveName(key.getCurveOID()); } + + /** + * Returns indication that a secret key is encrypted. + * + * @param secretKey A secret key to examine. + * @return true if secret key is encrypted, false otherwise. + */ + public static boolean isEncrypted(PGPSecretKey secretKey) { + return secretKey.getS2KUsage() != 0; + } + + /** + * Returns indication that a secret key is not encrypted. + * + * @param secretKey A secret key to examine. + * @return true if secret key is encrypted, false otherwise. + */ + public static boolean isDecrypted(PGPSecretKey secretKey) { + return secretKey.getS2KUsage() == 0; + } + + /** + * Returns indication that a secret key has S2K of a type GNU_DUMMY_S2K. + * + * @param secretKey A secret key to examine. + * @return true if secret key has S2K of a type GNU_DUMMY_S2K, false otherwise. + */ + public static boolean hasDummyS2K(PGPSecretKey secretKey) { + final S2K s2k = secretKey.getS2K(); + return s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K; + } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java index 6edd03b9..96aae0b5 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Paul Schaub. + * Copyright 2020 Paul Schaub. Copyright 2021 Flowcrypt a.s. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -297,16 +297,17 @@ public class KeyRingInfo { } /** - * Return true when every secret key on the key ring is not encrypted. - * If there is at least one encrypted secret key on the ring, return false. - * If the ring is a {@link PGPPublicKeyRing}, return true. + * Returns true when every secret key on the key ring is not encrypted. + * If there is at least one encrypted secret key on the key ring, returns false. + * If the key ring is a {@link PGPPublicKeyRing}, returns true. + * Sub-keys with S2K of a type GNU_DUMMY_S2K do not affect the result. * * @return true if all secret keys are unencrypted. */ public boolean isFullyDecrypted() { if (isSecretKey()) { for (PGPSecretKey secretKey : getSecretKeys()) { - if (secretKey.getS2KUsage() != 0) { + if (!KeyInfo.hasDummyS2K(secretKey) && KeyInfo.isEncrypted(secretKey)) { return false; } } @@ -314,6 +315,26 @@ public class KeyRingInfo { return true; } + /** + * Returns true when every secret key on the key ring is encrypted. + * If there is at least one not encrypted secret key on the key ring, returns false. + * If the key ring is a {@link PGPPublicKeyRing}, returns false. + * Sub-keys with S2K of a type GNU_DUMMY_S2K do not affect a result. + * + * @return true if all secret keys are encrypted. + */ + public boolean isFullyEncrypted() { + if (isSecretKey()) { + for (PGPSecretKey secretKey : getSecretKeys()) { + if (!KeyInfo.hasDummyS2K(secretKey) && KeyInfo.isDecrypted(secretKey)) { + return false; + } + } + return true; + } + return false; + } + public List getSelfSignaturesOnKey(long subkeyId) { PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(keys, subkeyId); Iterator it = publicKey.getSignaturesForKeyID(keys.getPublicKey().getKeyID()); diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java index 0c43ff22..e54f0df3 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/info/KeyRingInfoTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2020 Paul Schaub. + * Copyright 2020 Paul Schaub. Copyright 2021 Flowcrypt a.s. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -89,16 +89,34 @@ public class KeyRingInfoTest { assertTrue(info.isFullyDecrypted()); - secretKeys = PGPainless.modifyKeyRing(secretKeys) - .changePassphraseFromOldPassphrase(null) - .withSecureDefaultSettings() - .toNewPassphrase(Passphrase.fromPassword("sw0rdf1sh")) - .done(); + secretKeys = encryptSecretKeys(secretKeys); info = PGPainless.inspectKeyRing(secretKeys); assertFalse(info.isFullyDecrypted()); } + @Test + public void testIsFullyEncrypted() throws IOException, PGPException { + PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing(); + KeyRingInfo info = PGPainless.inspectKeyRing(secretKeys); + + assertFalse(info.isFullyEncrypted()); + + secretKeys = encryptSecretKeys(secretKeys); + info = PGPainless.inspectKeyRing(secretKeys); + + assertTrue(info.isFullyEncrypted()); + } + + private static PGPSecretKeyRing encryptSecretKeys(PGPSecretKeyRing secretKeys) throws PGPException { + return PGPainless.modifyKeyRing(secretKeys) + .changePassphraseFromOldPassphrase(null) + .withSecureDefaultSettings() + .toNewPassphrase(Passphrase.fromPassword("sw0rdf1sh")) + .done(); + } + + @Test public void testGetSecretKey() throws IOException, PGPException { PGPSecretKeyRing secretKeys = TestKeys.getCryptieSecretKeyRing();