From 4ed2cdaed9db9aadaff7cbdee0dc9f7f46d59caa Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 13 Nov 2020 15:59:28 +0100 Subject: [PATCH] Introduce UserId utility class --- .../key/generation/KeyRingBuilder.java | 23 +++- .../generation/KeyRingBuilderInterface.java | 9 ++ .../modification/KeyRingEditorInterface.java | 13 ++ .../java/org/pgpainless/key/util/UserId.java | 121 ++++++++++++++++++ .../java/org/pgpainless/key/UserIdTest.java | 77 +++++++++++ 5 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/util/UserId.java create mode 100644 pgpainless-core/src/test/java/org/pgpainless/key/UserIdTest.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java index d73c15f8..34b085fc 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java @@ -57,6 +57,7 @@ import org.pgpainless.key.collection.PGPKeyRing; import org.pgpainless.key.generation.type.KeyType; import org.pgpainless.key.generation.type.curve.EllipticCurve; import org.pgpainless.key.generation.type.length.RsaLength; +import org.pgpainless.key.util.UserId; import org.pgpainless.provider.ProviderFactory; import org.pgpainless.util.Passphrase; @@ -69,6 +70,11 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { private Set additionalUserIds = new LinkedHashSet<>(); private Passphrase passphrase; + public PGPKeyRing simpleRsaKeyRing(@Nonnull UserId userId, @Nonnull RsaLength length) + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { + return simpleRsaKeyRing(userId.toString(), length); + } + /** * Creates a simple, unencrypted RSA KeyPair of length {@code length} with user-id {@code userId}. * The KeyPair consists of a single RSA master key which is used for signing, encryption and certification. @@ -87,6 +93,11 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { return simpleRsaKeyRing(userId, length, null); } + public PGPKeyRing simpleRsaKeyRing(@Nonnull UserId userId, @Nonnull RsaLength rsaLength, String password) + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { + return simpleRsaKeyRing(userId.toString(), rsaLength, password); + } + /** * Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}. * The KeyPair consists of a single RSA master key which is used for signing, encryption and certification. @@ -117,6 +128,11 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { } } + public PGPKeyRing simpleEcKeyRing(@Nonnull UserId userId) + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { + return simpleEcKeyRing(userId.toString()); + } + /** * Creates an unencrypted key ring consisting of an ECDSA master key and an ECDH sub-key. * The ECDSA master key is used for signing messages and certifying the sub key. @@ -135,6 +151,11 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { return simpleEcKeyRing(userId, null); } + public PGPKeyRing simpleEcKeyRing(@Nonnull UserId userId, String password) + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { + return simpleEcKeyRing(userId.toString(), password); + } + /** * Creates a key ring consisting of an ECDSA master key and an ECDH sub-key. * The ECDSA master key is used for signing messages and certifying the sub key. @@ -347,7 +368,7 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { PBESecretKeyDecryptor decryptor = passphrase == null || passphrase.isEmpty() ? null : new JcePBESecretKeyDecryptorBuilder() - .build(passphrase.getChars()); + .build(passphrase.getChars()); return decryptor; } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilderInterface.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilderInterface.java index 8cda9eba..25c32ef9 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilderInterface.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilderInterface.java @@ -21,6 +21,7 @@ import java.security.NoSuchAlgorithmException; import org.bouncycastle.openpgp.PGPException; import org.pgpainless.key.collection.PGPKeyRing; +import org.pgpainless.key.util.UserId; import org.pgpainless.util.Passphrase; public interface KeyRingBuilderInterface { @@ -31,6 +32,10 @@ public interface KeyRingBuilderInterface { interface WithPrimaryUserId { + default WithAdditionalUserIdOrPassphrase withPrimaryUserId(@Nonnull UserId userId) { + return withPrimaryUserId(userId.toString()); + } + WithAdditionalUserIdOrPassphrase withPrimaryUserId(@Nonnull String userId); WithAdditionalUserIdOrPassphrase withPrimaryUserId(@Nonnull byte[] userId); @@ -39,6 +44,10 @@ public interface KeyRingBuilderInterface { interface WithAdditionalUserIdOrPassphrase { + default WithAdditionalUserIdOrPassphrase withAdditionalUserId(@Nonnull UserId userId) { + return withAdditionalUserId(userId.toString()); + } + WithAdditionalUserIdOrPassphrase withAdditionalUserId(@Nonnull String userId); WithAdditionalUserIdOrPassphrase withAdditionalUserId(@Nonnull byte[] userId); diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditorInterface.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditorInterface.java index 25a37391..a6290ded 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditorInterface.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditorInterface.java @@ -27,10 +27,15 @@ import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.generation.KeySpec; import org.pgpainless.key.protection.KeyRingProtectionSettings; import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.key.util.UserId; import org.pgpainless.util.Passphrase; public interface KeyRingEditorInterface { + default KeyRingEditorInterface addUserId(UserId userId, SecretKeyRingProtector secretKeyRingProtector) throws PGPException { + return addUserId(userId.toString(), secretKeyRingProtector); + } + /** * Add a user-id to the primary key of the key ring. * @@ -39,10 +44,18 @@ public interface KeyRingEditorInterface { */ KeyRingEditorInterface addUserId(String userId, SecretKeyRingProtector secretKeyRingProtector) throws PGPException; + default KeyRingEditorInterface addUserId(OpenPgpV4Fingerprint fingerprint, UserId userId, SecretKeyRingProtector secretKeyRingProtector) throws PGPException { + return addUserId(fingerprint, userId.toString(), secretKeyRingProtector); + } + default KeyRingEditorInterface addUserId(OpenPgpV4Fingerprint fingerprint, String userId, SecretKeyRingProtector secretKeyRingProtector) throws PGPException { return addUserId(fingerprint.getKeyId(), userId, secretKeyRingProtector); } + default KeyRingEditorInterface addUserId(long keyId, UserId userId, SecretKeyRingProtector secretKeyRingProtector) throws PGPException { + return addUserId(keyId, userId.toString(), secretKeyRingProtector); + } + KeyRingEditorInterface addUserId(long keyId, String userId, SecretKeyRingProtector secretKeyRingProtector) throws PGPException; /** diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/UserId.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/UserId.java new file mode 100644 index 00000000..bb9bb4a8 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/util/UserId.java @@ -0,0 +1,121 @@ +/* + * 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.util; + +public class UserId implements CharSequence { + + private final String name; + private final String comment; + private final String email; + + public UserId(String name, String comment, String email) { + this.name = name; + this.comment = comment; + this.email = email; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + if (name != null) { + sb.append(name); + } + if (comment != null) { + sb.append(" (").append(comment).append(')'); + } + if (email != null) { + sb.append(sb.length() != 0 ? " <" : '<').append(email).append('>'); + } + return sb.toString(); + } + + public static UserId onlyEmail(String email) { + if (email == null) { + throw new IllegalArgumentException("Email must not be null."); + } + return new UserId(null, null, email); + } + + public static WithComment withName(String name) { + if (name == null) { + throw new IllegalArgumentException("Name must not be null."); + } + return new WithComment(name); + } + + public static class WithComment { + + private final String name; + + public WithComment(String name) { + this.name = name; + } + + public WithEmail withComment(String comment) { + if (comment == null) { + throw new IllegalArgumentException("Comment must not be null."); + } + return new WithEmail(name, comment); + } + + public WithEmail noComment() { + return new WithEmail(name, null); + } + + public UserId build() { + return new UserId(name, null, null); + } + } + + public static class WithEmail { + + private final String name; + private final String comment; + + public WithEmail(String name, String comment) { + this.name = name; + this.comment = comment; + } + + public UserId withEmail(String email) { + if (email == null) { + throw new IllegalArgumentException("Email must not be null."); + } + return new UserId(name, comment, email); + } + + public UserId noEmail() { + return new UserId(name, comment, null); + } + } + + + @Override + public int length() { + return toString().length(); + } + + @Override + public char charAt(int i) { + return toString().charAt(i); + } + + @Override + public CharSequence subSequence(int i, int i1) { + return toString().subSequence(i, i1); + } + +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/UserIdTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/UserIdTest.java new file mode 100644 index 00000000..71ce4e46 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/UserIdTest.java @@ -0,0 +1,77 @@ +/* + * 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; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; +import org.pgpainless.key.util.UserId; + +public class UserIdTest { + + @Test(expected = IllegalArgumentException.class) + public void throwForNullName() { + UserId.withName(null); + } + + @Test(expected = IllegalArgumentException.class) + public void throwForNullComment() { + UserId.withName("foo") + .withComment(null); + } + + @Test(expected = IllegalArgumentException.class) + public void throwForNullEmail() { + UserId.withName("foo") + .withComment("bar") + .withEmail(null); + } + + @Test + public void testFormatOnlyName() { + assertEquals( + "Juliet Capulet", + UserId.withName("Juliet Capulet") + .build().toString()); + } + + @Test + public void testFormatNameAndComment() { + assertEquals( + "Juliet Capulet (from the play)", + UserId.withName("Juliet Capulet") + .withComment("from the play") + .noEmail().toString()); + } + + @Test + public void testFormatNameCommentAndMail() { + assertEquals("Juliet Capulet (from the play) ", + UserId.withName("Juliet Capulet") + .withComment("from the play") + .withEmail("juliet@capulet.lit") + .toString()); + } + + @Test + public void testFormatNameAndEmail() { + assertEquals("Juliet Capulet ", + UserId.withName("Juliet Capulet") + .noComment() + .withEmail("juliet@capulet.lit") + .toString()); + } +}