diff --git a/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java b/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java index 77860b4b..0b0dab34 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java +++ b/pgpainless-core/src/main/java/org/pgpainless/PGPainless.java @@ -19,6 +19,7 @@ import java.io.IOException; import javax.annotation.Nonnull; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.SymmetricKeyAlgorithm; @@ -27,6 +28,7 @@ import org.pgpainless.decryption_verification.DecryptionStream; import org.pgpainless.encryption_signing.EncryptionBuilder; import org.pgpainless.encryption_signing.EncryptionStream; import org.pgpainless.key.generation.KeyRingBuilder; +import org.pgpainless.key.info.KeyRingInfo; import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor; import org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditorInterface; import org.pgpainless.key.parsing.KeyRingReader; @@ -71,6 +73,16 @@ public class PGPainless { return new SecretKeyRingEditor(secretKeys); } + /** + * Quickly access information about a {@link org.bouncycastle.openpgp.PGPPublicKeyRing} / {@link PGPSecretKeyRing}. + * + * @param keyRing key ring + * @return access object + */ + public static KeyRingInfo inspectKeyRing(PGPKeyRing keyRing) { + return new KeyRingInfo(keyRing); + } + /** * Encrypt some data symmetrically using OpenPGP and a password. * The resulting data will be uncompressed and integrity protected. 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 new file mode 100644 index 00000000..c5d62e07 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyRingInfo.java @@ -0,0 +1,255 @@ +/* + * Copyright 2020 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.info; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.Iterator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.pgpainless.algorithm.PublicKeyAlgorithm; +import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.key.OpenPgpV4Fingerprint; + +/** + * Utility class to quickly extract certain information from a {@link PGPPublicKeyRing}/{@link PGPSecretKeyRing}. + */ +public class KeyRingInfo { + + private static final Pattern PATTERN_EMAIL = Pattern.compile("[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}"); + + private final PGPKeyRing keys; + + public KeyRingInfo(PGPKeyRing keys) { + this.keys = keys; + } + + /** + * Return the first {@link PGPPublicKey} of this key ring. + * + * @return public key + */ + public PGPPublicKey getPublicKey() { + return keys.getPublicKey(); + } + + /** + * Return all {@link PGPPublicKey PGPPublicKeys} of this key ring. + * The first key in the list being the primary key. + * Note that the list is unmodifiable. + * + * @return list of public keys + */ + public List getPublicKeys() { + List list = new ArrayList<>(); + Iterator iterator = keys.getPublicKeys(); + while (iterator.hasNext()) { + list.add(iterator.next()); + } + return Collections.unmodifiableList(list); + } + + /** + * Return the primary {@link PGPSecretKey} of this key ring or null if the key ring is not a {@link PGPSecretKeyRing}. + * + * @return primary secret key or null if the key ring is public + */ + public PGPSecretKey getSecretKey() { + if (keys instanceof PGPSecretKeyRing) { + PGPSecretKeyRing secretKeys = (PGPSecretKeyRing) keys; + return secretKeys.getSecretKey(); + } + return null; + } + + /** + * Return all secret keys of the key ring. + * If the key ring is a {@link PGPPublicKeyRing}, then return an empty list. + * Note that the list is unmodifiable. + * + * @return list of secret keys + */ + public List getSecretKeys() { + List list = new ArrayList<>(); + if (keys instanceof PGPSecretKeyRing) { + PGPSecretKeyRing secretKeys = (PGPSecretKeyRing) keys; + Iterator iterator = secretKeys.getSecretKeys(); + while (iterator.hasNext()) { + list.add(iterator.next()); + } + } + return Collections.unmodifiableList(list); + } + + /** + * Return the key id of the primary key of this key ring. + * + * @return key id + */ + public long getKeyId() { + return getPublicKey().getKeyID(); + } + + /** + * Return the {@link OpenPgpV4Fingerprint} of this key ring. + * + * @return fingerprint + */ + public OpenPgpV4Fingerprint getFingerprint() { + return new OpenPgpV4Fingerprint(getPublicKey()); + } + + /** + * Return a list of all user-ids of the primary key. + * + * @return list of user-ids + */ + public List getUserIds() { + List userIds = new ArrayList<>(); + Iterator iterator = getPublicKey().getUserIDs(); + while (iterator.hasNext()) { + userIds.add(iterator.next()); + } + return userIds; + } + + /** + * Return a list of all user-ids of the primary key that appear to be email-addresses. + * + * @return email addresses + */ + public List getEmailAddresses() { + List userIds = getUserIds(); + List emails = new ArrayList<>(); + for (String userId : userIds) { + Matcher matcher = PATTERN_EMAIL.matcher(userId); + if (matcher.find()) { + emails.add(matcher.group()); + } + } + return emails; + } + + /** + * Return the algorithm of the primary key. + * + * @return public key algorithm + */ + public PublicKeyAlgorithm getAlgorithm() { + return PublicKeyAlgorithm.fromId(getPublicKey().getAlgorithm()); + } + + /** + * Return the creation date of the primary key. + * + * @return creation date + */ + public Date getCreationDate() { + return getPublicKey().getCreationTime(); + } + + /** + * Return the date on which the key ring was last modified. + * This date corresponds to the date of the last signature that was made on this key ring by the primary key. + * + * @return last modification date. + */ + public Date getLastModified() { + Iterator signatures = getPublicKey().getSignatures(); + long last = 0L; + while (signatures.hasNext()) { + PGPSignature signature = signatures.next(); + if (getKeyId() != signature.getKeyID()) { + // Throw away signatures made from others + continue; + } + last = Math.max(last, signature.getCreationTime().getTime()); + } + return new Date(last); + } + + /** + * Return the date on which the primary key was revoked, or null if it has not yet been revoked. + * + * @return revocation date or null + */ + public Date getRevocationDate() { + Iterator revocations = getPublicKey().getSignaturesOfType(SignatureType.KEY_REVOCATION.getCode()); + while (revocations.hasNext()) { + PGPSignature revocation = revocations.next(); + if (getKeyId() != revocation.getKeyID()) { + // Throw away signatures made from others + continue; + } + return revocation.getCreationTime(); + } + return null; + } + + /** + * Return the date of expiration of the primary key. + * + * @return expiration date + */ + public Date getExpirationDate() { + long validSeconds = getPublicKey().getValidSeconds(); + return new Date(getCreationDate().getTime() + (1000 * validSeconds)); + } + + /** + * Return true if the key ring is a {@link PGPSecretKeyRing}. + * If it is a {@link PGPPublicKeyRing} return false and if it is neither, throw an {@link AssertionError}. + * + * @return true if the key ring is a secret key ring. + */ + public boolean isSecretKey() { + if (keys instanceof PGPSecretKeyRing) { + return true; + } else if (keys instanceof PGPPublicKeyRing) { + return false; + } else { + throw new AssertionError("Expected PGPKeyRing to be either PGPPublicKeyRing or PGPSecretKeyRing, but got " + keys.getClass().getName() + " instead."); + } + } + + /** + * 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. + * + * @return true if all secret keys are unencrypted. + */ + public boolean isFullyDecrypted() { + if (isSecretKey()) { + for (PGPSecretKey secretKey : getSecretKeys()) { + if (secretKey.getKeyEncryptionAlgorithm() != SymmetricKeyAlgorithm.NULL.getAlgorithmId()) { + return false; + } + } + } + return true; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/package-info.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/package-info.java new file mode 100644 index 00000000..4f3e7d2a --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/info/package-info.java @@ -0,0 +1,19 @@ +/* + * Copyright 2018 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. + */ +/** + * Extract information from PGPKeyRings. + */ +package org.pgpainless.key.info;