+ * Format documented here: + * http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master + * http://people.csail.mit.edu/rivest/Sexp.txt + *
+ */ + class SXprReader + { + Stream stream; + int peekedByte; + + public SXprReader(Stream stream) + { + this.stream = stream; + this.peekedByte = -1; + } + + private int ReadByte() + { + if (this.peekedByte > 0) + { + int pb = this.peekedByte; + this.peekedByte = 0; + return pb; + } + return stream.ReadByte(); + } + + private void UnreadByte(int pb) + { + this.peekedByte = pb; + } + + private int ReadLength() + { + int ch; + int len = 0; + + while ((ch = ReadByte()) >= 0 && ch >= '0' && ch <= '9') + { + len = len * 10 + (ch - '0'); + } + UnreadByte(ch); + + return len; + } + + public string ReadString() + { + SkipWhitespace(); + + int ch = ReadByte(); + if (ch >= '0' && ch <= '9') + { + UnreadByte(ch); + + int len = ReadLength(); + ch = ReadByte(); + if (ch == ':') + { + char[] chars = new char[len]; + + for (int i = 0; i != chars.Length; i++) + { + chars[i] = (char)ReadByte(); + } + + return new string(chars); + } + else if (ch == '"') + { + return ReadQuotedString(len); + } + throw new IOException("unsupported encoding"); + } + else if (ch == '"') + { + return ReadQuotedString(0); + } + else if (ch == '{' || ch == '|' || ch == '#') + { + // TODO: Unsupported encoding + throw new IOException("unsupported encoding"); + } + else + { + StringBuilder sb = new StringBuilder(); + while (IsTokenChar(ch)) + { + sb.Append((char)ch); + ch = (char)ReadByte(); + } + UnreadByte(ch); + return sb.ToString(); + } + } + + private string ReadQuotedString(int length) + { + StringBuilder sb = new StringBuilder(length); + int ch; + bool skipNewLine = false; + do + { + ch = ReadByte(); + if ((ch == '\n' || ch == '\r') && skipNewLine) + { + skipNewLine = false; + } + else if (ch == '\\') + { + ch = (char)ReadByte(); + switch (ch) + { + case 'b': sb.Append('\b'); break; + case 't': sb.Append('\t'); break; + case 'v': sb.Append('\v'); break; + case 'n': sb.Append('\n'); break; + case 'r': sb.Append('\r'); break; + case 'f': sb.Append('\f'); break; + case '"': sb.Append('"'); break; + case '\'': sb.Append('\''); break; + case '\r': + case '\n': + skipNewLine = true; + break; + default: + // TODO: Octal value, hexadecimal value + throw new IOException("unsupported encoding"); + } + } + else if (ch != '"' && ch >= 0) + { + skipNewLine = false; + sb.Append((char)ch); + } + } + while (ch != '"' && ch > 0); + return sb.ToString(); + } + + private static bool IsTokenChar(int ch) + { + return + (ch >= 'a' && ch <= 'z') || + (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || + ch == '-' || ch == '.' || + ch == '/' || ch == '_' || + ch == ':' || ch == '*' || + ch == '+' || ch == '='; + } + + public byte[] ReadBytes() + { + SkipWhitespace(); + + int ch = ReadByte(); + if (ch >= '0' && ch <= '9') + { + UnreadByte(ch); + + int len = ReadLength(); + + if (ReadByte() != ':') + throw new IOException("unsupported encoding"); + + byte[] data = new byte[len]; + + Streams.ReadFully(stream, data); + + return data; + } + else if (ch == '#') + { + StringBuilder sb = new StringBuilder(); + do + { + ch = ReadByte(); + if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F')) + sb.Append((char)ch); + } + while (ch != '#' && ch >= 0); + return Hex.Decode(sb.ToString()); + } + + throw new IOException("unsupported encoding"); + } + + public S2k ParseS2k() + { + SkipOpenParenthesis(); + + string alg = ReadString(); + byte[] iv = ReadBytes(); + long iterationCount = Int64.Parse(ReadString()); + + SkipCloseParenthesis(); + + // we have to return the actual iteration count provided. + return new MyS2k(HashAlgorithmTag.Sha1, iv, iterationCount); + } + + public void SkipWhitespace() + { + int ch = ReadByte(); + while (ch == ' ' || ch == '\r' || ch == '\n') + { + ch = ReadByte(); + } + UnreadByte(ch); + } + + public void SkipOpenParenthesis() + { + SkipWhitespace(); + + int ch = ReadByte(); + if (ch != '(') + throw new IOException("unknown character encountered"); + } + + public void SkipCloseParenthesis() + { + SkipWhitespace(); + + int ch = ReadByte(); + if (ch != ')') + throw new IOException("unknown character encountered"); + } + + private class MyS2k : S2k + { + private readonly long mIterationCount64; + + internal MyS2k(HashAlgorithmTag algorithm, byte[] iv, long iterationCount64) + : base(algorithm, iv, (int)iterationCount64) + { + this.mIterationCount64 = iterationCount64; + } + + public override long IterationCount + { + get { return mIterationCount64; } + } + } + } +} diff --git a/crypto/src/openpgp/SXprUtilities.cs b/crypto/src/openpgp/SXprUtilities.cs deleted file mode 100644 index 68ff373a83..0000000000 --- a/crypto/src/openpgp/SXprUtilities.cs +++ /dev/null @@ -1,102 +0,0 @@ -using System; -using System.IO; - -using Org.BouncyCastle.Utilities.IO; - -namespace Org.BouncyCastle.Bcpg.OpenPgp -{ - /** - * Utility functions for looking a S-expression keys. This class will move when it finds a better home! - *- * Format documented here: - * http://git.gnupg.org/cgi-bin/gitweb.cgi?p=gnupg.git;a=blob;f=agent/keyformat.txt;h=42c4b1f06faf1bbe71ffadc2fee0fad6bec91a97;hb=refs/heads/master - *
- */ - public sealed class SXprUtilities - { - private SXprUtilities() - { - } - - private static int ReadLength(Stream input, int ch) - { - int len = ch - '0'; - - while ((ch = input.ReadByte()) >= 0 && ch != ':') - { - len = len * 10 + ch - '0'; - } - - return len; - } - - internal static string ReadString(Stream input, int ch) - { - int len = ReadLength(input, ch); - - char[] chars = new char[len]; - - for (int i = 0; i != chars.Length; i++) - { - chars[i] = (char)input.ReadByte(); - } - - return new string(chars); - } - - internal static byte[] ReadBytes(Stream input, int ch) - { - int len = ReadLength(input, ch); - - byte[] data = new byte[len]; - - Streams.ReadFully(input, data); - - return data; - } - - internal static S2k ParseS2k(Stream input) - { - SkipOpenParenthesis(input); - - string alg = ReadString(input, input.ReadByte()); - byte[] iv = ReadBytes(input, input.ReadByte()); - long iterationCount = Int64.Parse(ReadString(input, input.ReadByte())); - - SkipCloseParenthesis(input); - - // we have to return the actual iteration count provided. - return new MyS2k(HashAlgorithmTag.Sha1, iv, iterationCount); - } - - internal static void SkipOpenParenthesis(Stream input) - { - int ch = input.ReadByte(); - if (ch != '(') - throw new IOException("unknown character encountered"); - } - - internal static void SkipCloseParenthesis(Stream input) - { - int ch = input.ReadByte(); - if (ch != ')') - throw new IOException("unknown character encountered"); - } - - private class MyS2k : S2k - { - private readonly long mIterationCount64; - - internal MyS2k(HashAlgorithmTag algorithm, byte[] iv, long iterationCount64) - : base(algorithm, iv, (int)iterationCount64) - { - this.mIterationCount64 = iterationCount64; - } - - public override long IterationCount - { - get { return mIterationCount64; } - } - } - } -} diff --git a/crypto/src/openpgp/SXprWriter.cs b/crypto/src/openpgp/SXprWriter.cs new file mode 100644 index 0000000000..640c4b6d6e --- /dev/null +++ b/crypto/src/openpgp/SXprWriter.cs @@ -0,0 +1,50 @@ +using System; +using System.IO; +using System.Text; + +namespace Org.BouncyCastle.Bcpg.OpenPgp +{ + /** + * Writer for S-expression keys + *+ * Format documented here: + * http://people.csail.mit.edu/rivest/Sexp.txt + * + * Only canonical S expression format is used. + *
+ */ + class SXprWriter + { + Stream output; + + public SXprWriter(Stream output) + { + this.output = output; + } + + public void StartList() + { + output.WriteByte((byte)'('); + } + + public void EndList() + { + output.WriteByte((byte)')'); + } + + public void WriteString(string s) + { + byte[] stringBytes = Encoding.UTF8.GetBytes(s); + byte[] lengthBytes = Encoding.UTF8.GetBytes(stringBytes.Length + ":"); + output.Write(lengthBytes, 0, lengthBytes.Length); + output.Write(stringBytes, 0, stringBytes.Length); + } + + public void WriteBytes(byte[] b) + { + byte[] lengthBytes = Encoding.UTF8.GetBytes(b.Length + ":"); + output.Write(lengthBytes, 0, lengthBytes.Length); + output.Write(b, 0, b.Length); + } + } +} diff --git a/crypto/src/security/SignerUtilities.cs b/crypto/src/security/SignerUtilities.cs index 8a289897e9..1f263d17fa 100644 --- a/crypto/src/security/SignerUtilities.cs +++ b/crypto/src/security/SignerUtilities.cs @@ -610,6 +610,13 @@ public static ISigner GetSigner( return new DsaDigestSigner(new ECDsaSigner(), digest); } + if (Platform.EndsWith(mechanism, "WITHEDDSA")) + { + string digestName = mechanism.Substring(0, mechanism.LastIndexOf("WITH")); + IDigest digest = DigestUtilities.GetDigest(digestName); + return new DsaDigestSigner(new EdDsa22519Signer(), digest); + } + if (Platform.EndsWith(mechanism, "withCVC-ECDSA") || Platform.EndsWith(mechanism, "withPLAIN-ECDSA")) { diff --git a/crypto/test/src/openpgp/test/PgpECDHTest.cs b/crypto/test/src/openpgp/test/PgpECDHTest.cs index 94ab2a755e..9c307cb353 100644 --- a/crypto/test/src/openpgp/test/PgpECDHTest.cs +++ b/crypto/test/src/openpgp/test/PgpECDHTest.cs @@ -4,13 +4,16 @@ using System.Text; using NUnit.Framework; - +using Org.BouncyCastle.Asn1; +using Org.BouncyCastle.Asn1.Gnu; +using Org.BouncyCastle.Asn1.Misc; using Org.BouncyCastle.Asn1.Sec; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Parameters; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.Utilities.IO; using Org.BouncyCastle.Utilities.Test; namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests @@ -51,6 +54,40 @@ public class PgpECDHTest "6HiuFH7VKWcxPUBjXwf5+Z3uOKEp28tBgNyDrdbr1BbqlgYzIKq/pe9zUbUXfitn" + "vFc6HcGhvmRQreQ+Yw1x3x0HJeoPwg=="); + private static readonly byte[] testX25519PubKey = + Base64.Decode( + "mDMEX9XwXhYJKwYBBAHaRw8BAQdAR5ZghmMHL8wldNlOkmbaiAOdyF5V5bgZdKq7" + + "L+yb4A20HEVDREggPHRlc3QuZWNkaEBleGFtcGxlLmNvbT6IkAQTFggAOBYhBGoy" + + "UrxNv7c3S2JjGzewWiN8tfzXBQJf1fBeAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4B" + + "AheAAAoJEDewWiN8tfzX0ZMA/AhEvrIgu+29eMQeuHOwX1ZY/UssU5TdVROQzGTL" + + "n5cgAP9hIKtt/mZ112HiAHDuWk2JskdtsuopnrEccz4PSEkSDLg4BF/V8F4SCisG" + + "AQQBl1UBBQEBB0DLPhNt/6GHDbb7vZW/iMsbXTZpgJNQiT6QA/4EzgYQLwMBCAeI" + + "eAQYFggAIBYhBGoyUrxNv7c3S2JjGzewWiN8tfzXBQJf1fBeAhsMAAoJEDewWiN8" + + "tfzXU34BAKJJLDee+qJCmUI20sMy/YoKfWmMnH2RBBHmLV8FAJ7vAP0e2wGixEfs" + + "oPqe8fHmvjQGxSByOyQGn7yD+oq9nVzTAA=="); + + private static readonly byte[] testX25519PrivKey = + Base64.Decode( + "lIYEX9XwXhYJKwYBBAHaRw8BAQdAR5ZghmMHL8wldNlOkmbaiAOdyF5V5bgZdKq7" + + "L+yb4A3+BwMCMscozrXr93fOFmtxu/BJjEJrwRl20Jrv9lryfM+SF4UHgVMmJUpJ" + + "1RuTbSnM2KaqHwOgmdrvf2FJnpg1vMafBk1CmopqkRzzrbJ6xQhiPrQcRUNESCA8" + + "dGVzdC5lY2RoQGV4YW1wbGUuY29tPoiQBBMWCAA4FiEEajJSvE2/tzdLYmMbN7Ba" + + "I3y1/NcFAl/V8F4CGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQN7BaI3y1" + + "/NfRkwD8CES+siC77b14xB64c7BfVlj9SyxTlN1VE5DMZMuflyAA/2Egq23+ZnXX" + + "YeIAcO5aTYmyR22y6imesRxzPg9ISRIMnIsEX9XwXhIKKwYBBAGXVQEFAQEHQMs+" + + "E23/oYcNtvu9lb+IyxtdNmmAk1CJPpAD/gTOBhAvAwEIB/4HAwJ7ShSBrUuUAM5r" + + "G4I/gJKo+eBmbNC4NM81eALAF1vcovZPsGsiZ8IgXT64XiC1bpeAoINn6vM4vVbi" + + "LqNKqu6ll3ZgQ4po6vCW9GkhuEMmiHgEGBYIACAWIQRqMlK8Tb+3N0tiYxs3sFoj" + + "fLX81wUCX9XwXgIbDAAKCRA3sFojfLX811N+AQCiSSw3nvqiQplCNtLDMv2KCn1p" + + "jJx9kQQR5i1fBQCe7wD9HtsBosRH7KD6nvHx5r40BsUgcjskBp+8g/qKvZ1c0wA="); + + private static readonly byte[] testX25519Message = + Base64.Decode( + "hF4DbDc2fNL0VcUSAQdAqdV0v1D4X9cuGrT7+oQBpMFnw1wdfAcxH9xdO00s2HUw" + + "qB+XkIRETH7yesynLOKajmYftMWZRyTnW2tJUc1w5NFPjPxcbvd2bYmqkY57uAFg" + + "0kcBKhFklH2LRbBNThtQr3jn2YEFbNnhiGfOpoHfCn0oFh5RbXDwm+P3Q3tksvpZ" + + "wEGe2VkxLLe7BWnv/sRINQ2YpuaYshe8hw=="); + private void Generate() { SecureRandom random = SecureRandom.GetInstance("SHA1PRNG"); @@ -105,6 +142,71 @@ private void Generate() PgpPrivateKey pgpPrivKey = secRing.GetSecretKey().ExtractPrivateKey(passPhrase); } + private void Generate25519() + { + SecureRandom random = SecureRandom.GetInstance("SHA1PRNG"); + + // + // Generate a master key + // + IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator("Ed25519"); + keyGen.Init(new ECKeyGenerationParameters(GnuObjectIdentifiers.Ed25519, random)); + + AsymmetricCipherKeyPair kpSign = keyGen.GenerateKeyPair(); + + PgpKeyPair ecdsaKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.EdDsa, kpSign, DateTime.UtcNow); + + // + // Generate an encryption key + // + keyGen = GeneratorUtilities.GetKeyPairGenerator("X25519"); + keyGen.Init(new ECKeyGenerationParameters(MiscObjectIdentifiers.Curve25519, random)); + + AsymmetricCipherKeyPair kpEnc = keyGen.GenerateKeyPair(); + + PgpKeyPair ecdhKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.ECDH, kpEnc, DateTime.UtcNow); + + // + // Generate a key ring + // + char[] passPhrase = "test".ToCharArray(); + PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(PgpSignature.PositiveCertification, ecdsaKeyPair, + "test@bouncycastle.org", SymmetricKeyAlgorithmTag.Aes256, passPhrase, true, null, null, random); + keyRingGen.AddSubKey(ecdhKeyPair); + + PgpPublicKeyRing pubRing = keyRingGen.GeneratePublicKeyRing(); + + // TODO: add check of KdfParameters + DoBasicKeyRingCheck(pubRing); + + PgpSecretKeyRing secRing = keyRingGen.GenerateSecretKeyRing(); + + PgpPublicKeyRing pubRingEnc = new PgpPublicKeyRing(pubRing.GetEncoded()); + if (!Arrays.AreEqual(pubRing.GetEncoded(), pubRingEnc.GetEncoded())) + { + Fail("public key ring encoding failed"); + } + + PgpSecretKeyRing secRingEnc = new PgpSecretKeyRing(secRing.GetEncoded()); + if (!Arrays.AreEqual(secRing.GetEncoded(), secRingEnc.GetEncoded())) + { + Fail("secret key ring encoding failed"); + } + + // Extract back the ECDH key and verify the encoded values to ensure correct endianness + PgpSecretKey pgpSecretKey = secRing.GetSecretKey(ecdhKeyPair.KeyId); + PgpPrivateKey pgpPrivKey = pgpSecretKey.ExtractPrivateKey(passPhrase); + + if (!Arrays.AreEqual(((X25519PrivateKeyParameters)kpEnc.Private).GetEncoded(), ((X25519PrivateKeyParameters)pgpPrivKey.Key).GetEncoded())) + { + Fail("private key round trip failed"); + } + if (!Arrays.AreEqual(((X25519PublicKeyParameters)kpEnc.Public).GetEncoded(), ((X25519PublicKeyParameters)pgpSecretKey.PublicKey.GetKey()).GetEncoded())) + { + Fail("private key round trip failed"); + } + } + private void TestDecrypt(PgpSecretKeyRing secretKeyRing) { PgpObjectFactory pgpF = new PgpObjectFactory(testMessage); @@ -134,14 +236,14 @@ private void TestDecrypt(PgpSecretKeyRing secretKeyRing) // } } - private void EncryptDecryptTest() + private void EncryptDecryptTest(string algorithm, DerObjectIdentifier curve) { SecureRandom random = SecureRandom.GetInstance("SHA1PRNG"); byte[] text = Encoding.ASCII.GetBytes("hello world!"); - IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator("ECDH"); - keyGen.Init(new ECKeyGenerationParameters(SecObjectIdentifiers.SecP256r1, random)); + IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator(algorithm); + keyGen.Init(new ECKeyGenerationParameters(curve, random)); AsymmetricCipherKeyPair kpEnc = keyGen.GenerateKeyPair(); @@ -197,6 +299,108 @@ private void EncryptDecryptTest() } } + + private void EncryptDecryptX25519KeysTest() + { + SecureRandom random = SecureRandom.GetInstance("SHA1PRNG"); + + /*IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator(algorithm); + keyGen.Init(new ECKeyGenerationParameters(curve, random)); + + AsymmetricCipherKeyPair kpEnc = keyGen.GenerateKeyPair(); + + PgpKeyPair ecdhKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.ECDH, kpEnc, DateTime.UtcNow);*/ + PgpPublicKeyRing publicKeyRing = new PgpPublicKeyRing(testX25519PubKey); + + PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(testX25519PrivKey); + + PgpSecretKey secretKey = secretKeyRing.GetSecretKey(0x6c37367cd2f455c5); + + byte[] text = Encoding.ASCII.GetBytes("hello world!"); + + PgpLiteralDataGenerator lData = new PgpLiteralDataGenerator(); + MemoryStream ldOut = new MemoryStream(); + Stream pOut = lData.Open(ldOut, PgpLiteralDataGenerator.Utf8, PgpLiteralData.Console, text.Length, DateTime.UtcNow); + + pOut.Write(text, 0, text.Length); + + pOut.Close(); + + byte[] data = ldOut.ToArray(); + + MemoryStream cbOut = new MemoryStream(); + + PgpEncryptedDataGenerator cPk = new PgpEncryptedDataGenerator(SymmetricKeyAlgorithmTag.Cast5, random); + cPk.AddMethod(publicKeyRing.GetPublicKey(0x6c37367cd2f455c5)); + + Stream cOut = cPk.Open(new UncloseableStream(cbOut), data.Length); + + cOut.Write(data, 0, data.Length); + + cOut.Close(); + + PgpObjectFactory pgpF = new PgpObjectFactory(cbOut.ToArray()); + + PgpEncryptedDataList encList = (PgpEncryptedDataList)pgpF.NextPgpObject(); + + PgpPublicKeyEncryptedData encP = (PgpPublicKeyEncryptedData)encList[0]; + + Stream clear = encP.GetDataStream(secretKey.ExtractPrivateKey("test".ToCharArray())); + + pgpF = new PgpObjectFactory(clear); + + PgpLiteralData ld = (PgpLiteralData)pgpF.NextPgpObject(); + + clear = ld.GetInputStream(); + MemoryStream bOut = new MemoryStream(); + + int ch; + while ((ch = clear.ReadByte()) >= 0) + { + bOut.WriteByte((byte)ch); + } + + byte[] output = bOut.ToArray(); + + if (!AreEqual(output, text)) + { + Fail("wrong plain text in Generated packet"); + } + } + + private void GnuPGCrossCheck() + { + PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(testX25519PrivKey); + + PgpObjectFactory pgpF = new PgpObjectFactory(testX25519Message); + + PgpEncryptedDataList encList = (PgpEncryptedDataList)pgpF.NextPgpObject(); + + PgpPublicKeyEncryptedData encP = (PgpPublicKeyEncryptedData)encList[0]; + + PgpSecretKey secretKey = secretKeyRing.GetSecretKey(0x6c37367cd2f455c5); + + PgpPrivateKey pgpPrivKey = secretKey.ExtractPrivateKey("test".ToCharArray()); + + Stream clear = encP.GetDataStream(pgpPrivKey); + + pgpF = new PgpObjectFactory(clear); + + PgpCompressedData c1 = (PgpCompressedData)pgpF.NextPgpObject(); + + pgpF = new PgpObjectFactory(c1.GetDataStream()); + + PgpLiteralData ld = (PgpLiteralData)pgpF.NextPgpObject(); + + Stream inLd = ld.GetDataStream(); + byte[] bytes = Streams.ReadAll(inLd); + + if (!Arrays.AreEqual(bytes, Encoding.ASCII.GetBytes("hello world!"))) + { + Fail("wrong plain text in decrypted packet"); + } + } + public override void PerformTest() { // @@ -213,9 +417,17 @@ public override void PerformTest() TestDecrypt(secretKeyRing); - EncryptDecryptTest(); + EncryptDecryptTest("ECDH", SecObjectIdentifiers.SecP256r1); + + EncryptDecryptTest("X25519", MiscObjectIdentifiers.Curve25519); + + GnuPGCrossCheck(); Generate(); + + Generate25519(); + + EncryptDecryptX25519KeysTest(); } private void DoBasicKeyRingCheck(PgpPublicKeyRing pubKeyRing) diff --git a/crypto/test/src/openpgp/test/PgpEdDsaTest.cs b/crypto/test/src/openpgp/test/PgpEdDsaTest.cs new file mode 100644 index 0000000000..3a9d6c46b2 --- /dev/null +++ b/crypto/test/src/openpgp/test/PgpEdDsaTest.cs @@ -0,0 +1,231 @@ +using System; +using System.Collections; +using System.IO; +using System.Text; + +using NUnit.Framework; +using Org.BouncyCastle.Asn1.Gnu; +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.Security; +using Org.BouncyCastle.Utilities; +using Org.BouncyCastle.Utilities.Encoders; +using Org.BouncyCastle.Utilities.Test; + +namespace Org.BouncyCastle.Bcpg.OpenPgp.Tests +{ + [TestFixture] + public class PgpEdDsaTest + : SimpleTest + { + private static readonly byte[] testPubKey = + Base64.Decode( + "mDMEX9NCKBYJKwYBBAHaRw8BAQdASPhAQySGGPMjoquv5i1IwLRSDJ2QtmLLvER2" + + "Cm8UZyW0HkVkRFNBIDx0ZXN0LmVkZHNhQGV4YW1wbGUuY29tPoiQBBMWCAA4FiEE" + + "sh83FOYApIfZuLp0emj2ffveCqEFAl/TQigCGwMFCwkIBwIGFQoJCAsCBBYCAwEC" + + "HgECF4AACgkQemj2ffveCqF6XQEA2S08fb0Z6LCd9P+eajPNDm1Wrf/y/7nkNwhb" + + "DvwiU5kBAM16UvHrzX6CvQFvc7aKvPH+4wrvRewvAGK16a4fBHEE"); + + private static readonly byte[] testPrivKey = + Base64.Decode( + "lIYEX9NCKBYJKwYBBAHaRw8BAQdASPhAQySGGPMjoquv5i1IwLRSDJ2QtmLLvER2" + + "Cm8UZyX+BwMC7ubvoFJTTXfOpQ3tDoys52w6tb01rHHtjKVWjXMjiyN8tXHBDC9N" + + "UcMYViTDegBXOEgw4TIKn9mkkTDvP3xVFeMH2XBPzu9e9m8GlBODILQeRWREU0Eg" + + "PHRlc3QuZWRkc2FAZXhhbXBsZS5jb20+iJAEExYIADgWIQSyHzcU5gCkh9m4unR6" + + "aPZ9+94KoQUCX9NCKAIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRB6aPZ9" + + "+94KoXpdAQDZLTx9vRnosJ30/55qM80ObVat//L/ueQ3CFsO/CJTmQEAzXpS8evN" + + "foK9AW9ztoq88f7jCu9F7C8AYrXprh8EcQQ="); + + private static readonly char[] testPasswd = "test".ToCharArray(); + + private static readonly byte[] sExprKey = + Base64.Decode( + "KHByb3RlY3RlZC1wcml2YXRlLWtleSAoZWNjIChjdXJ2ZSBFZDI1NTE5KShmbGFn" + + "cyBlZGRzYSkocQogICM0MDQ4Rjg0MDQzMjQ4NjE4RjMyM0EyQUJBRkU2MkQ0OEMw" + + "QjQ1MjBDOUQ5MEI2NjJDQkJDNDQ3NjBBNkYxNDY3MjUjKQogKHByb3RlY3RlZCBv" + + "cGVucGdwLXMyazMtb2NiLWFlcyAoKHNoYTEgI0IwRkY2MDAzRUE4RkQ4QkIjCiAg" + + "Ijc2OTUzNjAiKSM5NDZEREU3QTUxMzAyRUEyRDc3NDNEOTQjKSM4NDBFMTIyRTdB" + + "RDI0RkY1MkE5RUY3QUFDQjgxRUE2CiAyMTkyQjZCMjlCOUI4N0QwNTZBOUE4MTEz" + + "QjIzNjlEREM4QUVGMTJDNjRBN0QwOTEwM0Q1MTU1Nzc0Q0Q5RkQ4NzczQTEzCiBD" + + "NTgwQ0Y4RkY5OEZERTU3RDVGIykocHJvdGVjdGVkLWF0ICIyMDIwMTIxMVQwOTU2" + + "MDEiKSkp"); + + private static readonly byte[] referencePubKey = + Base64.Decode( + "mDMEU/NfCxYJKwYBBAHaRw8BAQdAPwmJlL3ZFu1AUxl5NOSofIBzOhKA1i+AEJku" + + "Q+47JAY="); + + private static readonly string referenceMessage = "OpenPGP"; + + private static readonly byte[] referenceSignature = + Base64.Decode( + "iF4EABYIAAYFAlX5X5UACgkQjP3hIZeWWpr2IgEAVvkMypjiECY3vZg/2xbBMd/S" + + "ftgr9N3lYG4NdWrtM2YBANCcT6EVJ/A44PV/IgHYLy6iyQMyZfps60iehUuuYbQE"); + + private void ReferenceTest() + { + PgpPublicKeyRing pubKeyRing = new PgpPublicKeyRing(referencePubKey); + PgpPublicKey publicKey = pubKeyRing.GetPublicKey(); + + PgpObjectFactory pgpFact = new PgpObjectFactory(referenceSignature); + PgpSignatureList signatureList = (PgpSignatureList)pgpFact.NextPgpObject(); + PgpSignature signature = signatureList.Get(0); + signature.InitVerify(publicKey); + signature.Update(Encoding.ASCII.GetBytes(referenceMessage)); + if (!signature.Verify()) + { + Fail("signature failed to verify!"); + } + } + + private void GenerateAndSign() + { + SecureRandom random = SecureRandom.GetInstance("SHA1PRNG"); + + IAsymmetricCipherKeyPairGenerator keyGen = GeneratorUtilities.GetKeyPairGenerator("Ed25519"); + keyGen.Init(new ECKeyGenerationParameters(GnuObjectIdentifiers.Ed25519, random)); + + AsymmetricCipherKeyPair kpSign = keyGen.GenerateKeyPair(); + + PgpKeyPair eddsaKeyPair = new PgpKeyPair(PublicKeyAlgorithmTag.EdDsa, kpSign, DateTime.UtcNow); + + byte[] msg = Encoding.ASCII.GetBytes("hello world!"); + + // + // try a signature + // + PgpSignatureGenerator signGen = new PgpSignatureGenerator(PublicKeyAlgorithmTag.EdDsa, HashAlgorithmTag.Sha256); + signGen.InitSign(PgpSignature.BinaryDocument, eddsaKeyPair.PrivateKey); + + signGen.Update(msg); + + PgpSignature sig = signGen.Generate(); + + sig.InitVerify(eddsaKeyPair.PublicKey); + sig.Update(msg); + + if (!sig.Verify()) + { + Fail("signature failed to verify!"); + } + + // + // generate a key ring + // + char[] passPhrase = "test".ToCharArray(); + PgpKeyRingGenerator keyRingGen = new PgpKeyRingGenerator(PgpSignature.PositiveCertification, eddsaKeyPair, + "test@bouncycastle.org", SymmetricKeyAlgorithmTag.Aes256, passPhrase, true, null, null, random); + + PgpPublicKeyRing pubRing = keyRingGen.GeneratePublicKeyRing(); + PgpSecretKeyRing secRing = keyRingGen.GenerateSecretKeyRing(); + + PgpPublicKeyRing pubRingEnc = new PgpPublicKeyRing(pubRing.GetEncoded()); + if (!Arrays.AreEqual(pubRing.GetEncoded(), pubRingEnc.GetEncoded())) + { + Fail("public key ring encoding failed"); + } + + PgpSecretKeyRing secRingEnc = new PgpSecretKeyRing(secRing.GetEncoded()); + if (!Arrays.AreEqual(secRing.GetEncoded(), secRingEnc.GetEncoded())) + { + Fail("secret key ring encoding failed"); + } + + + // + // try a signature using encoded key + // + signGen = new PgpSignatureGenerator(PublicKeyAlgorithmTag.EdDsa, HashAlgorithmTag.Sha256); + signGen.InitSign(PgpSignature.BinaryDocument, secRing.GetSecretKey().ExtractPrivateKey(passPhrase)); + signGen.Update(msg); + + sig = signGen.Generate(); + sig.InitVerify(secRing.GetSecretKey().PublicKey); + sig.Update(msg); + + if (!sig.Verify()) + { + Fail("re-encoded signature failed to verify!"); + } + } + + public override void PerformTest() + { + ReferenceTest(); + + // + // Read the public key + // + PgpPublicKeyRing pubKeyRing = new PgpPublicKeyRing(testPubKey); + foreach (PgpSignature certification in pubKeyRing.GetPublicKey().GetSignatures()) + { + certification.InitVerify(pubKeyRing.GetPublicKey()); + + if (!certification.VerifyCertification((string)First(pubKeyRing.GetPublicKey().GetUserIds()), pubKeyRing.GetPublicKey())) + { + Fail("self certification does not verify"); + } + } + + /*if (pubKeyRing.GetPublicKey().BitStrength != 256) + { + Fail("incorrect bit strength returned"); + }*/ + + // + // Read the private key + // + PgpSecretKeyRing secretKeyRing = new PgpSecretKeyRing(testPrivKey); + + PgpPrivateKey privKey = secretKeyRing.GetSecretKey().ExtractPrivateKey(testPasswd); + + GenerateAndSign(); + + // + // sExpr + // + byte[] msg = Encoding.ASCII.GetBytes("hello world!"); + + PgpSecretKey key = PgpSecretKey.ParseSecretKeyFromSExpr(new MemoryStream(sExprKey, false), "test".ToCharArray()); + + PgpSignatureGenerator signGen = new PgpSignatureGenerator(PublicKeyAlgorithmTag.EdDsa, HashAlgorithmTag.Sha256); + signGen.InitSign(PgpSignature.BinaryDocument, key.ExtractPrivateKey(null)); + signGen.Update(msg); + + PgpSignature sig = signGen.Generate(); + sig.InitVerify(key.PublicKey); + sig.Update(msg); + + if (!sig.Verify()) + { + Fail("signature failed to verify!"); + } + } + + private static object First(IEnumerable e) + { + IEnumerator n = e.GetEnumerator(); + Assert.IsTrue(n.MoveNext()); + return n.Current; + } + + public override string Name + { + get { return "PgpEdDsaTest"; } + } + + public static void Main( + string[] args) + { + RunTest(new PgpECDsaTest()); + } + + [Test] + public void TestFunction() + { + string resultText = Perform().ToString(); + + Assert.AreEqual(Name + ": Okay", resultText); + } + } +}