From 0958915b4cf2aab751915107d0f2f2a21d44d529 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 23 Jun 2021 21:18:46 +0200 Subject: [PATCH] Add examples for key generation and parsing --- .../org/pgpainless/example/GenerateKeys.java | 220 ++++++++++++++++++ .../java/org/pgpainless/example/ReadKeys.java | 175 ++++++++++++++ 2 files changed, 395 insertions(+) create mode 100644 pgpainless-core/src/test/java/org/pgpainless/example/GenerateKeys.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/example/ReadKeys.java diff --git a/pgpainless-core/src/test/java/org/pgpainless/example/GenerateKeys.java b/pgpainless-core/src/test/java/org/pgpainless/example/GenerateKeys.java new file mode 100644 index 00000000..03a70071 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/example/GenerateKeys.java @@ -0,0 +1,220 @@ +/* + * 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.example; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.CompressionAlgorithm; +import org.pgpainless.algorithm.EncryptionPurpose; +import org.pgpainless.algorithm.Feature; +import org.pgpainless.algorithm.HashAlgorithm; +import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.algorithm.PublicKeyAlgorithm; +import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.key.generation.KeySpec; +import org.pgpainless.key.generation.type.KeyType; +import org.pgpainless.key.generation.type.ecc.EllipticCurve; +import org.pgpainless.key.generation.type.eddsa.EdDSACurve; +import org.pgpainless.key.generation.type.rsa.RsaLength; +import org.pgpainless.key.info.KeyRingInfo; +import org.pgpainless.key.util.KeyRingUtils; +import org.pgpainless.key.util.UserId; +import org.pgpainless.util.ArmorUtils; +import org.pgpainless.util.Passphrase; + +/** + * This class demonstrates how to use PGPainless to generate secret keys. + * In general the starting point for generating secret keys using PGPainless is {@link PGPainless#generateKeyRing()}. + * The result ({@link org.pgpainless.key.generation.KeyRingBuilder}) provides some factory methods for key archetypes + * such as {@link org.pgpainless.key.generation.KeyRingBuilder#modernKeyRing(String, String)} or + * {@link org.pgpainless.key.generation.KeyRingBuilder#simpleRsaKeyRing(String, RsaLength)}. + * + * Those methods always take a user-id which is used as primary user-id, as well as a passphrase which is used to encrypt + * the secret key. + * To generate unencrypted secret keys, just pass {@code null} as passphrase. + * + * Besides the archetype methods, it is possible to generate fully customized keys (see {@link #generateCustomOpenPGPKey()}). + */ +public class GenerateKeys { + + /** + * This example demonstrates how to generate a modern OpenPGP key which consists of an ed25519 EdDSA primary key + * used solely for certification of subkeys, as well as an ed25519 EdDSA signing subkey, and an X25519 ECDH + * encryption subkey. + * + * This is the recommended way to generate OpenPGP keys with PGPainless. + * + * @throws PGPException + * @throws InvalidAlgorithmParameterException + * @throws NoSuchAlgorithmException + */ + @Test + public void generateModernEcKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { + // Define a primary user-id + String userId = "gbaker@pgpainless.org"; + // Set a password to protect the secret key + String password = "ra1nb0w"; + // Generate the OpenPGP key + PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() + .modernKeyRing(userId, password); + // Extract public key + PGPPublicKeyRing publicKey = KeyRingUtils.publicKeyRingFrom(secretKey); + // Encode the public key to an ASCII armored string ready for sharing + String asciiArmoredPublicKey = ArmorUtils.toAsciiArmoredString(publicKey); + + + KeyRingInfo keyInfo = new KeyRingInfo(secretKey); + assertEquals(3, keyInfo.getSecretKeys().size()); + assertEquals(userId, keyInfo.getPrimaryUserId()); + assertEquals(PublicKeyAlgorithm.EDDSA.getAlgorithmId(), + keyInfo.getPublicKey().getAlgorithm()); + assertEquals(PublicKeyAlgorithm.EDDSA.getAlgorithmId(), + keyInfo.getSigningSubkeys().get(0).getAlgorithm()); + assertEquals(PublicKeyAlgorithm.ECDH.getAlgorithmId(), + keyInfo.getEncryptionSubkeys(EncryptionPurpose.STORAGE_AND_COMMUNICATIONS).get(0).getAlgorithm()); + } + + /** + * This example demonstrates how to generate a simple OpenPGP key consisting of a 4096 bit RSA key. + * The RSA key is used for both signing and certifying, as well as encryption. + * + * This method is recommended if the application has to deal with legacy clients with poor algorithm support. + * + * @throws PGPException + * @throws InvalidAlgorithmParameterException + * @throws NoSuchAlgorithmException + */ + @Test + public void generateSimpleRSAKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + // Define a primary user-id + String userId = "mpage@pgpainless.org"; + // Set a password to protect the secret key + String password = "b1angl3s"; + // Generate the OpenPGP key + PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() + .simpleRsaKeyRing(userId, RsaLength._4096, password); + + + KeyRingInfo keyInfo = new KeyRingInfo(secretKey); + assertEquals(1, keyInfo.getSecretKeys().size()); + assertEquals(userId, keyInfo.getPrimaryUserId()); + assertEquals(PublicKeyAlgorithm.RSA_GENERAL.getAlgorithmId(), keyInfo.getPublicKey().getAlgorithm()); + } + + /** + * This example demonstrates how to generate a simple OpenPGP key based on elliptic curves. + * The key consists of an ECDSA primary key that is used both for certification of subkeys, as well as signing of data, + * and a single ECDH encryption subkey. + * + * This method is recommended if small keys and high performance are desired. + * + * @throws PGPException + * @throws InvalidAlgorithmParameterException + * @throws NoSuchAlgorithmException + */ + @Test + public void generateSimpleECKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + String userId = "mhelms@pgpainless.org"; + String password = "tr4ns"; + + PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() + .simpleEcKeyRing(userId, password); + + + KeyRingInfo keyInfo = new KeyRingInfo(secretKey); + assertEquals(2, keyInfo.getSecretKeys().size()); + assertEquals(userId, keyInfo.getPrimaryUserId()); + } + + /** + * This example demonstrates how to generate a custom OpenPGP secret key. + * Among user-id and password, the user can add an arbitrary number of subkeys and specify their algorithms and + * algorithm preferences. + * + * @throws PGPException + * @throws InvalidAlgorithmParameterException + * @throws NoSuchAlgorithmException + */ + @Test + public void generateCustomOpenPGPKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + // Instead of providing a string, we can assemble a user-id by using the user-id builder. + // The example below corresponds to "Morgan Carpenter (Pride!) " + UserId userId = UserId.newBuilder() + .withName("Morgan Carpenter") + .withEmail("mcarpenter@pgpainless.org") + .withComment("Pride!") + .build(); + + // It is recommended to use the Passphrase class, as it can be used to safely invalidate passwords from memory + Passphrase passphrase = Passphrase.fromPassword("1nters3x"); + + PGPSecretKeyRing secretKey = PGPainless.generateKeyRing() + // Add the first subkey (in this case encryption) + .withSubKey( + KeySpec.getBuilder( + // We choose an ECDH key over the brainpoolp256r1 curve + KeyType.ECDH(EllipticCurve._BRAINPOOLP256R1) + ) + // Our key can encrypt both communication data, as well as data at rest + .withKeyFlags(KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS) + // Optionally: Configure the subkey with custom algorithm preferences + // Is is recommended though to go with PGPainless' defaults which can be found in the + // AlgorithmSuite class. + .withDetailedConfiguration() + .withPreferredSymmetricAlgorithms(SymmetricKeyAlgorithm.AES_256, SymmetricKeyAlgorithm.AES_192, SymmetricKeyAlgorithm.AES_128) + .withPreferredHashAlgorithms(HashAlgorithm.SHA512, HashAlgorithm.SHA384, HashAlgorithm.SHA256) + .withPreferredCompressionAlgorithms(CompressionAlgorithm.ZIP, CompressionAlgorithm.BZIP2, CompressionAlgorithm.ZLIB) + // Modification Detection is highly recommended + .withFeature(Feature.MODIFICATION_DETECTION) + .done() + ) + // Add the second subkey (signing) + .withSubKey( + KeySpec.getBuilder( + KeyType.ECDSA(EllipticCurve._BRAINPOOLP384R1) + ) + // This key is used for creating signatures only + .withKeyFlags(KeyFlag.SIGN_DATA) + // Instead of manually specifying algorithm preferences, we can simply use PGPainless' sane defaults + .withDefaultAlgorithms() + ) + // Lastly we add the primary key (certification only in our case) + .withPrimaryKey( + KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519)) + // The primary key MUST carry the CERTIFY_OTHER flag, but CAN carry additional flags + .withKeyFlags(KeyFlag.CERTIFY_OTHER) + .withDefaultAlgorithms() + ) + .withPrimaryUserId(userId) + .withPassphrase(passphrase) + .build(); + + + KeyRingInfo keyInfo = new KeyRingInfo(secretKey); + assertEquals(3, keyInfo.getSecretKeys().size()); + assertEquals("Morgan Carpenter (Pride!) ", keyInfo.getPrimaryUserId()); + } + +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/example/ReadKeys.java b/pgpainless-core/src/test/java/org/pgpainless/example/ReadKeys.java new file mode 100644 index 00000000..89f91731 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/example/ReadKeys.java @@ -0,0 +1,175 @@ +/* + * 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.example; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +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; + +public class ReadKeys { + + /** + * This example demonstrates how to parse a public key (certificate) from an ASCII armored string. + * + * @throws IOException + */ + @Test + public void readCertificate() throws IOException { + String certificate = "" + + "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: Alice's OpenPGP certificate\n" + + "\n" + + "mDMEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U\n" + + "b7O1u120JkFsaWNlIExvdmVsYWNlIDxhbGljZUBvcGVucGdwLmV4YW1wbGU+iJAE\n" + + "ExYIADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTrhbtfozp14V6UTmPy\n" + + "MVUMT0fjjgUCXaWfOgAKCRDyMVUMT0fjjukrAPoDnHBSogOmsHOsd9qGsiZpgRnO\n" + + "dypvbm+QtXZqth9rvwD9HcDC0tC+PHAsO7OTh1S1TC9RiJsvawAfCPaQZoed8gK4\n" + + "OARcRwTpEgorBgEEAZdVAQUBAQdAQv8GIa2rSTzgqbXCpDDYMiKRVitCsy203x3s\n" + + "E9+eviIDAQgHiHgEGBYIACAWIQTrhbtfozp14V6UTmPyMVUMT0fjjgUCXEcE6QIb\n" + + "DAAKCRDyMVUMT0fjjlnQAQDFHUs6TIcxrNTtEZFjUFm1M0PJ1Dng/cDW4xN80fsn\n" + + "0QEA22Kr7VkCjeAEC08VSTeV+QFsmz55/lntWkwYWhmvOgE=\n" + + "=iIGO\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + + PGPPublicKeyRing publicKey = PGPainless.readKeyRing() + .publicKeyRing(certificate); + + + KeyRingInfo keyInfo = new KeyRingInfo(publicKey); + OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint("EB85 BB5F A33A 75E1 5E94 4E63 F231 550C 4F47 E38E"); + assertEquals(fingerprint, keyInfo.getFingerprint()); + assertEquals("Alice Lovelace ", keyInfo.getPrimaryUserId()); + } + + /** + * This example demonstrates how to parse an ASCII armored secret key. + * + * @throws PGPException + * @throws IOException + */ + @Test + public void readSecretKey() throws PGPException, IOException { + String key = "\n" + + "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: Alice's OpenPGP Transferable Secret Key\n" + + "\n" + + "lFgEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U\n" + + "b7O1u10AAP9XBeW6lzGOLx7zHH9AsUDUTb2pggYGMzd0P3ulJ2AfvQ4RtCZBbGlj\n" + + "ZSBMb3ZlbGFjZSA8YWxpY2VAb3BlbnBncC5leGFtcGxlPoiQBBMWCAA4AhsDBQsJ\n" + + "CAcCBhUKCQgLAgQWAgMBAh4BAheAFiEE64W7X6M6deFelE5j8jFVDE9H444FAl2l\n" + + "nzoACgkQ8jFVDE9H447pKwD6A5xwUqIDprBzrHfahrImaYEZzncqb25vkLV2arYf\n" + + "a78A/R3AwtLQvjxwLDuzk4dUtUwvUYibL2sAHwj2kGaHnfICnF0EXEcE6RIKKwYB\n" + + "BAGXVQEFAQEHQEL/BiGtq0k84Km1wqQw2DIikVYrQrMttN8d7BPfnr4iAwEIBwAA\n" + + "/3/xFPG6U17rhTuq+07gmEvaFYKfxRB6sgAYiW6TMTpQEK6IeAQYFggAIBYhBOuF\n" + + "u1+jOnXhXpROY/IxVQxPR+OOBQJcRwTpAhsMAAoJEPIxVQxPR+OOWdABAMUdSzpM\n" + + "hzGs1O0RkWNQWbUzQ8nUOeD9wNbjE3zR+yfRAQDbYqvtWQKN4AQLTxVJN5X5AWyb\n" + + "Pnn+We1aTBhaGa86AQ==\n" + + "=n8OM\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + + PGPSecretKeyRing secretKey = PGPainless.readKeyRing() + .secretKeyRing(key); + + + KeyRingInfo keyInfo = new KeyRingInfo(secretKey); + OpenPgpV4Fingerprint fingerprint = new OpenPgpV4Fingerprint("EB85 BB5F A33A 75E1 5E94 4E63 F231 550C 4F47 E38E"); + assertEquals(fingerprint, keyInfo.getFingerprint()); + assertEquals("Alice Lovelace ", keyInfo.getPrimaryUserId()); + } + + /** + * This example demonstrates how to read a collection of multiple OpenPGP public keys (certificates) at once. + * + * Note, that a public key collection can both be a concatenation of public key blocks (like below), + * as well as a single public key block containing multiple public key packets. + * + * @throws PGPException + * @throws IOException + */ + @Test + public void readKeyRingCollection() throws PGPException, IOException { + String certificateCollection = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: Alice's OpenPGP certificate\n" + + "\n" + + "mDMEXEcE6RYJKwYBBAHaRw8BAQdArjWwk3FAqyiFbFBKT4TzXcVBqPTB3gmzlC/U\n" + + "b7O1u120JkFsaWNlIExvdmVsYWNlIDxhbGljZUBvcGVucGdwLmV4YW1wbGU+iJAE\n" + + "ExYIADgCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AWIQTrhbtfozp14V6UTmPy\n" + + "MVUMT0fjjgUCXaWfOgAKCRDyMVUMT0fjjukrAPoDnHBSogOmsHOsd9qGsiZpgRnO\n" + + "dypvbm+QtXZqth9rvwD9HcDC0tC+PHAsO7OTh1S1TC9RiJsvawAfCPaQZoed8gK4\n" + + "OARcRwTpEgorBgEEAZdVAQUBAQdAQv8GIa2rSTzgqbXCpDDYMiKRVitCsy203x3s\n" + + "E9+eviIDAQgHiHgEGBYIACAWIQTrhbtfozp14V6UTmPyMVUMT0fjjgUCXEcE6QIb\n" + + "DAAKCRDyMVUMT0fjjlnQAQDFHUs6TIcxrNTtEZFjUFm1M0PJ1Dng/cDW4xN80fsn\n" + + "0QEA22Kr7VkCjeAEC08VSTeV+QFsmz55/lntWkwYWhmvOgE=\n" + + "=iIGO\n" + + "-----END PGP PUBLIC KEY BLOCK-----" + + "\n" + + "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" + + "Comment: Bob's OpenPGP certificate\n" + + "\n" + + "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\n" + + "NEJd3XZRzaXZE2aAMQ==\n" + + "=NXei\n" + + "-----END PGP PUBLIC KEY BLOCK-----"; + + PGPPublicKeyRingCollection collection = PGPainless.readKeyRing() + .publicKeyRingCollection(certificateCollection); + assertEquals(2, collection.size()); + } +}