From 8551440316df33ecdfd4b0624a080650e20e1af8 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 11 Feb 2017 01:23:11 +0100 Subject: [PATCH 1/3] add EdDSA support --- .../operations/results/OperationResult.java | 1 + .../keychain/pgp/CanonicalizedSecretKey.java | 1 + .../keychain/pgp/PgpKeyOperation.java | 18 +++++++++++++++++- .../keychain/pgp/PgpSecurityConstants.java | 3 +++ .../keychain/pgp/UncachedKeyRing.java | 1 + .../keychain/pgp/UncachedPublicKey.java | 3 ++- .../keychain/service/SaveKeyringParcel.java | 2 +- .../ui/dialog/AddSubkeyDialogFragment.java | 10 +++++++++- .../keychain/ui/util/KeyFormattingUtils.java | 4 ++++ OpenKeychain/src/main/res/values/strings.xml | 4 ++++ extern/bouncycastle | 2 +- 11 files changed, 44 insertions(+), 5 deletions(-) diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java index ed14512f5..f844acb0d 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/results/OperationResult.java @@ -533,6 +533,7 @@ public abstract class OperationResult implements Parcelable { MSG_CR_ERROR_FLAGS_DSA (LogLevel.ERROR, R.string.msg_cr_error_flags_dsa), MSG_CR_ERROR_FLAGS_ELGAMAL (LogLevel.ERROR, R.string.msg_cr_error_flags_elgamal), MSG_CR_ERROR_FLAGS_ECDSA (LogLevel.ERROR, R.string.msg_cr_error_flags_ecdsa), + MSG_CR_ERROR_FLAGS_EDDSA (LogLevel.ERROR, R.string.msg_cr_error_flags_eddsa), MSG_CR_ERROR_FLAGS_ECDH (LogLevel.ERROR, R.string.msg_cr_error_flags_ecdh), // secret key modify diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java index e32c97e2f..e8c776ef8 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -209,6 +209,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey { mPrivateKey = mSecretKey.extractPrivateKey(keyDecryptor); mPrivateKeyState = PRIVATE_KEY_STATE_UNLOCKED; } catch (PGPException e) { + Log.e(Constants.TAG, "Error extracting private key!", e); return false; } if (mPrivateKey == null) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java index 27ba4638c..e45e07c8e 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -45,6 +45,7 @@ import org.bouncycastle.bcpg.S2K; import org.bouncycastle.bcpg.sig.Features; import org.bouncycastle.bcpg.sig.KeyFlags; import org.bouncycastle.bcpg.sig.RevocationReasonTags; +import org.bouncycastle.jcajce.provider.asymmetric.eddsa.spec.EdDSAGenParameterSpec; import org.bouncycastle.jce.spec.ElGamalParameterSpec; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyFlags; @@ -174,7 +175,7 @@ public class PgpKeyOperation { log.add(LogType.MSG_CR_ERROR_NO_CURVE, indent); return null; } - } else { + } else if (add.getAlgorithm() != Algorithm.EDDSA) { if (add.getKeySize() == null) { log.add(LogType.MSG_CR_ERROR_NO_KEYSIZE, indent); return null; @@ -241,6 +242,21 @@ public class PgpKeyOperation { break; } + case EDDSA: { + if ((add.getFlags() & (PGPKeyFlags.CAN_ENCRYPT_COMMS | PGPKeyFlags.CAN_ENCRYPT_STORAGE)) > 0) { + log.add(LogType.MSG_CR_ERROR_FLAGS_ECDSA, indent); + return null; + } + progress(R.string.progress_generating_eddsa, 30); + EdDSAGenParameterSpec edParamSpec = + new EdDSAGenParameterSpec("ed25519"); + keyGen = KeyPairGenerator.getInstance("EdDSA", Constants.BOUNCY_CASTLE_PROVIDER_NAME); + keyGen.initialize(edParamSpec, new SecureRandom()); + + algorithm = PGPPublicKey.EDDSA; + break; + } + case ECDH: { // make sure there are no sign or certify flags set if ((add.getFlags() & (PGPKeyFlags.CAN_SIGN | PGPKeyFlags.CAN_CERTIFY)) > 0) { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java index 892207938..dc887bbef 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java @@ -174,6 +174,9 @@ public class PgpSecurityConstants { } return null; } + case PublicKeyAlgorithmTags.EDDSA: { + return null; + } // ELGAMAL_GENERAL: deprecated in RFC 4880, use ELGAMAL_ENCRYPT // DIFFIE_HELLMAN: unsure // TODO specialize all cases! diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java index 907fa6996..acdbb93fc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java @@ -250,6 +250,7 @@ public class UncachedKeyRing { PublicKeyAlgorithmTags.ECDSA, // 19 PublicKeyAlgorithmTags.ELGAMAL_GENERAL, // 20 // PublicKeyAlgorithmTags.DIFFIE_HELLMAN, // 21 + PublicKeyAlgorithmTags.EDDSA, // 22 }; /** "Canonicalizes" a public key, removing inconsistencies in the process. diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java index 4093ce6f7..77ce0dc90 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java @@ -223,7 +223,8 @@ public class UncachedPublicKey { public boolean isEC() { return getAlgorithm() == PGPPublicKey.ECDH - || getAlgorithm() == PGPPublicKey.ECDSA; + || getAlgorithm() == PGPPublicKey.ECDSA + || getAlgorithm() == PGPPublicKey.EDDSA; } public byte[] getFingerprint() { diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java index 06909127a..6d67fd5c1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/SaveKeyringParcel.java @@ -290,7 +290,7 @@ public abstract class SaveKeyringParcel implements Parcelable { // All supported algorithms public enum Algorithm { - RSA, DSA, ELGAMAL, ECDSA, ECDH + RSA, DSA, ELGAMAL, ECDSA, ECDH, EDDSA } // All curves defined in the standard diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java index bf1b2f378..d307482b0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/AddSubkeyDialogFragment.java @@ -62,7 +62,7 @@ public class AddSubkeyDialogFragment extends DialogFragment { } public enum SupportedKeyType { - RSA_2048, RSA_3072, RSA_4096, ECC_P256, ECC_P521 + RSA_2048, RSA_3072, RSA_4096, ECC_P256, ECC_P521, EDDSA } private static final String ARG_WILL_BE_MASTER_KEY = "will_be_master_key"; @@ -158,6 +158,8 @@ public class AddSubkeyDialogFragment extends DialogFragment { R.string.ecc_p256), getResources().getString(R.string.ecc_p256_description_html))); choices.add(new Choice<>(SupportedKeyType.ECC_P521, getResources().getString( R.string.ecc_p521), getResources().getString(R.string.ecc_p521_description_html))); + choices.add(new Choice<>(SupportedKeyType.EDDSA, getResources().getString( + R.string.ecc_eddsa), getResources().getString(R.string.ecc_eddsa_description_html))); TwoLineArrayAdapter adapter = new TwoLineArrayAdapter(context, android.R.layout.simple_spinner_item, choices); mKeyTypeSpinner.setAdapter(adapter); @@ -198,6 +200,9 @@ public class AddSubkeyDialogFragment extends DialogFragment { if (mWillBeMasterKey) { mUsageEncrypt.setEnabled(false); } + } else if (keyType == SupportedKeyType.EDDSA) { + mUsageSignAndEncrypt.setEnabled(false); + mUsageEncrypt.setEnabled(false); } else { // need to enable if previously disabled for ECC masterkey mUsageEncrypt.setEnabled(true); @@ -275,6 +280,9 @@ public class AddSubkeyDialogFragment extends DialogFragment { } break; } + case EDDSA: { + algorithm = Algorithm.EDDSA; + } } // set flags diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java index 85f721a90..b830c6cd9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/util/KeyFormattingUtils.java @@ -110,6 +110,10 @@ public class KeyFormattingUtils { return "ECDH (" + oidName + ")"; } + case PublicKeyAlgorithmTags.EDDSA: { + return "EdDSA"; + } + default: { if (context != null) { algorithmStr = context.getResources().getString(R.string.unknown); diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index 4f1fb4ea9..32c660cbf 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -315,6 +315,8 @@ "very tiny file size, considered secure until 2040 <br/> <u>experimental and not supported by all implementations</u>" "ECC P-521" "tiny file size, considered secure until 2040+ <br/> <u>experimental and not supported by all implementations</u>" + "ECC EdDSA" + "tiny file size, considered secure until 2040+ <br/> <u>experimental and not supported by all implementations</u>" "None (subkey binding only)" "Sign" "Encrypt" @@ -452,6 +454,7 @@ "generating new DSA key…" "generating new ElGamal key…" "generating new ECDSA key…" + "generating new EdDSA key…" "generating new ECDH key…" "modifying keyring…" @@ -1084,6 +1087,7 @@ "Bad key flags selected, DSA cannot be used for encryption!" "Bad key flags selected, ElGamal cannot be used for signing!" "Bad key flags selected, ECDSA cannot be used for encryption!" + "Bad key flags selected, EdDSA cannot be used for encryption!" "Bad key flags selected, ECDH cannot be used for signing!" diff --git a/extern/bouncycastle b/extern/bouncycastle index 2f4b5a448..1c44d1e9f 160000 --- a/extern/bouncycastle +++ b/extern/bouncycastle @@ -1 +1 @@ -Subproject commit 2f4b5a448c051ad980a437d7efbf4402b593f929 +Subproject commit 1c44d1e9f5fd52dd515552ae000e44be5ee9f4a4 From 27582c13104d35b5fe782525eb0a9eb225f53b17 Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sun, 12 Feb 2017 13:53:44 +0100 Subject: [PATCH 2/3] add some tests for eddsa --- .../keychain/provider/EddsaTest.java | 130 ++++++++++++++++++ .../resources/test-keys/eddsa-sample-msg.asc | 11 ++ 2 files changed, 141 insertions(+) create mode 100644 OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/EddsaTest.java create mode 100644 OpenKeychain/src/test/resources/test-keys/eddsa-sample-msg.asc diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/EddsaTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/EddsaTest.java new file mode 100644 index 000000000..e724cb3ff --- /dev/null +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/EddsaTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2014 Dominik Schürmann + * Copyright (C) 2014 Vincent Breitmoser + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.sufficientlysecure.keychain.provider; + + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; + +import android.app.Application; + +import org.bouncycastle.bcpg.sig.KeyFlags; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.openintents.openpgp.OpenPgpSignatureResult; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.shadows.ShadowLog; +import org.robolectric.util.Util; +import org.sufficientlysecure.keychain.KeychainTestRunner; +import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult; +import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog; +import org.sufficientlysecure.keychain.operations.results.PgpEditKeyResult; +import org.sufficientlysecure.keychain.operations.results.SaveKeyringResult; +import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel; +import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation; +import org.sufficientlysecure.keychain.pgp.PgpKeyOperation; +import org.sufficientlysecure.keychain.pgp.UncachedKeyRing; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.Algorithm; +import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd; + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertFalse; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; + + +@SuppressWarnings("WeakerAccess") +@RunWith(KeychainTestRunner.class) +public class EddsaTest { + private KeyWritableRepository keyRepository; + private Application context; + + + @BeforeClass + public static void setUpOnce() throws Exception { + ShadowLog.stream = System.out; + } + + @Before + public void setUp() throws Exception { + context = RuntimeEnvironment.application; + keyRepository = KeyWritableRepository.createDatabaseReadWriteInteractor(context); + + } + + @Test + public void testGpgSampleSignature() throws Exception { + // key from GnuPG's test suite, sample msg generated using GnuPG v2.1.18 + UncachedKeyRing ring = loadKeyringFromResource("/test-keys/eddsa-sample-1-pub.asc"); + + byte[] signedText = readBytesFromResource("/test-keys/eddsa-sample-msg.asc"); + PgpDecryptVerifyInputParcel pgpDecryptVerifyInputParcel = PgpDecryptVerifyInputParcel.builder() + .setInputBytes(signedText).build(); + + PgpDecryptVerifyOperation decryptVerifyOperation = new PgpDecryptVerifyOperation(context, keyRepository, null); + DecryptVerifyResult result = decryptVerifyOperation.execute(pgpDecryptVerifyInputParcel, null); + + assertTrue(result.success()); + assertEquals(OpenPgpSignatureResult.RESULT_VALID_KEY_UNCONFIRMED, result.getSignatureResult().getResult()); + assertEquals(ring.getMasterKeyId(), result.getSignatureResult().getKeyId()); + } + + @Test + public void testCreateEddsa() throws Exception { + SaveKeyringParcel.Builder builder = SaveKeyringParcel.buildNewKeyringParcel(); + builder.addSubkeyAdd(SubkeyAdd.createSubkeyAdd( + Algorithm.EDDSA, 0, null, KeyFlags.CERTIFY_OTHER, 0L)); + builder.addUserId("ed"); + + PgpKeyOperation op = new PgpKeyOperation(null); + PgpEditKeyResult result = op.createSecretKeyRing(builder.build()); + + assertTrue("initial test key creation must succeed", result.success()); + assertNotNull("initial test key creation must succeed", result.getRing()); + + CanonicalizedKeyRing canonicalizedKeyRing = result.getRing().canonicalize(new OperationLog(), 0); + assertNotNull(canonicalizedKeyRing); + } + + private UncachedKeyRing loadKeyringFromResource(String name) throws Exception { + UncachedKeyRing ring = readRingFromResource(name); + SaveKeyringResult saveKeyringResult = keyRepository.savePublicKeyRing(ring); + assertTrue(saveKeyringResult.success()); + assertFalse(saveKeyringResult.getLog().containsWarnings()); + return ring; + } + + private byte[] readBytesFromResource(String name) throws Exception { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + InputStream input = EddsaTest.class.getResourceAsStream(name); + + Util.copy(input, baos); + + return baos.toByteArray(); + } + + UncachedKeyRing readRingFromResource(String name) throws Exception { + return UncachedKeyRing.fromStream(EddsaTest.class.getResourceAsStream(name)).next(); + } + +} \ No newline at end of file diff --git a/OpenKeychain/src/test/resources/test-keys/eddsa-sample-msg.asc b/OpenKeychain/src/test/resources/test-keys/eddsa-sample-msg.asc new file mode 100644 index 000000000..1415f6f69 --- /dev/null +++ b/OpenKeychain/src/test/resources/test-keys/eddsa-sample-msg.asc @@ -0,0 +1,11 @@ +-----BEGIN PGP SIGNED MESSAGE----- +Hash: SHA256 + +eddsa sample signed msg +-----BEGIN PGP SIGNATURE----- + +iHUEARYIAB0WIQTJWb26+jKi+JoVO2eM/eEhl5ZamgUCWJ+tFQAKCRCM/eEhl5Za +miuCAQCGGkrsyYxv1PkQM7GH8mMwqdHd5YAOQw6qNjTjVAQ+FgD7B7AhHQ0nFgWx +oXDm7HDBLRidPJ9u+Tb0yUid7NfyxQg= +=K4E/ +-----END PGP SIGNATURE----- From 50d8cd05597ac85f3b87d2dd629d14e999b0327c Mon Sep 17 00:00:00 2001 From: Vincent Breitmoser Date: Sat, 29 Apr 2017 23:30:33 +0200 Subject: [PATCH 3/3] change opaque key tests to use unknown algorithms instead of eddsa --- .../keychain/pgp/OpaqueKeyTest.java | 10 ++++------ .../resources/test-keys/unknown-sample-1.pub | Bin 0 -> 230 bytes .../resources/test-keys/unknown-subkey.pub.asc | Bin 0 -> 1114 bytes 3 files changed, 4 insertions(+), 6 deletions(-) create mode 100644 OpenKeychain/src/test/resources/test-keys/unknown-sample-1.pub create mode 100644 OpenKeychain/src/test/resources/test-keys/unknown-subkey.pub.asc diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/OpaqueKeyTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/OpaqueKeyTest.java index b3dce051c..9e25841fb 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/OpaqueKeyTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/OpaqueKeyTest.java @@ -42,9 +42,7 @@ public class OpaqueKeyTest { @Test public void testOpaqueSubKey__canonicalize__shouldFail() throws Exception { - // key from GnuPG's test suite, sample msg generated using GnuPG v2.1.18 - // TODO use for actual tests once eddsa is supported! - UncachedKeyRing ring = readRingFromResource("/test-keys/eddsa-sample-1-pub.asc"); + UncachedKeyRing ring = readRingFromResource("/test-keys/unknown-sample-1.pub"); OperationLog log = new OperationLog(); ring.canonicalize(log, 0); @@ -61,12 +59,12 @@ public class OpaqueKeyTest { OperationLog log = new OperationLog(); ring.canonicalize(log, 0); - assertTrue(log.containsType(LogType.MSG_KC_ERROR_MASTER_ALGO)); + assertTrue(log.containsType(LogType.MSG_KC_SUB_BAD)); } @Test public void testOpaqueSubKey__canonicalize__shouldStrip() throws Exception { - UncachedKeyRing ring = readRingFromResource("/test-keys/eddsa-subkey.pub.asc"); + UncachedKeyRing ring = readRingFromResource("/test-keys/unknown-subkey.pub.asc"); OperationLog log = new OperationLog(); CanonicalizedKeyRing canonicalizedKeyRing = ring.canonicalize(log, 0); @@ -78,7 +76,7 @@ public class OpaqueKeyTest { @Test public void testOpaqueSubKey__reencode__shouldBeIdentical() throws Exception { byte[] rawKeyData = TestDataUtil.readFully( - OpaqueKeyTest.class.getResourceAsStream("/test-keys/eddsa-subkey.pub.asc")); + OpaqueKeyTest.class.getResourceAsStream("/test-keys/unknown-subkey.pub.asc")); UncachedKeyRing ring = UncachedKeyRing.decodeFromData(rawKeyData); diff --git a/OpenKeychain/src/test/resources/test-keys/unknown-sample-1.pub b/OpenKeychain/src/test/resources/test-keys/unknown-sample-1.pub new file mode 100644 index 0000000000000000000000000000000000000000..ff4354c9a2c9079f5348990dc297998de37960a1 GIT binary patch literal 230 zcmVU!c+av7y{E2o$31?;kt_YjGZhSfwTUF-+hlkE{y*5s g6NH5iX;q>8XVvYtAGQm<#c~`ofc6SlSuH`N6za72@w(!lY zu{ft&@A7(@t6kh?-2&a+50=`#`BoXBA|M=9`YC^s$qBbRw*$Tg8tuOSe!@!mr3WpK zulLqcP|be&_tl9r9Q_+Fw*6XRUZ;KTLuG1IQFr}{8?R=(xl}m&ea9lj#fv^P@Ns)( z`D#Whr51=OCbGJR6_uV^BI&QPqQ7Wf!VTfFi!--INd;M0g{-$MR+;5BIrrq;&^YCz z3-eCL7%l3%%d}_P^50uZpWPo?H7~g6sl+N`dS8~62^4lr(#))!%!kq$Sh+bl*qPWwIXJnvnOMY_ zm>HSm7@5Qy7`QkEU|OusaWE{}QZXsj#d7!5oi_9Br#q!h-}~N8X$k9IjjUIOk7i$= zdM`r$nue-$*(I$TbyxHL-ihwMezNu9Z3p|GE7w)(DT+M&)?iw*A(`#2%LJ>H;ad!} zK0H};>TA!v=L>dJ-j2RJQ(I%(21Y$gZd2VN`+m`fx9&bYIr08^nd__v9URxsSd_fS ziR=2ioe{@wW&B*?6241cIPcS3QT?k6HubXXmOOQp^F~D7+QZU&k9*I(#JGm_&^({A z{5|J)?@jQn@}1|miM!!S|8Mo`@`aa#I?eOjv}_kePk9vkpnFmHr{8Z@O*kxG*0v`^ z@2KyxT7#AX_VpGlXO{D2?sAhj(3^DL;@n|Qsnt7-L20f^i&LA8k%jS=J3k{MyTdDq z&ly(Ud##II*I%rAIi+#t_do3`^rs$_FV2aWKdN_4w?~Ii9b!E#mf}_C0Tf`F1mhsqX;CUlk~NU?P3*85ve9i+);qNbRhe z_N?2L=Nn~aDmkT|KXdGAUT|5ZlgIo{Muzsgm2))or(b+~Wi4;dtalUTGHg(0n;&cx{@PX3Wyr+7BiT_(={N=jhqiY;iypM@`acZ}0=9I_N*G3Dk-=fa=y{b3k zcfM1);34Z1zxPkwBAHvdu-uLRH@oBoC#(H(hk9q|2JlMHxqBo*BkS)nCaGMR$Q>y= zul6glO}q3@r}BKTVA)A)U5yPpr-;O?GoP@;M}fg9V5W9ec7d{z=d9?xw-(1UP4!LQ a*A#4!5EK!v7x<~DcecLt8XK)co45h)tMNbp literal 0 HcmV?d00001