diff --git a/Component_Tests/Classes/Crypto/Omemo/Test_KeyHelper.cs b/Component_Tests/Classes/Crypto/Omemo/Test_KeyHelper.cs index 3c55217d..caa44c5f 100644 --- a/Component_Tests/Classes/Crypto/Omemo/Test_KeyHelper.cs +++ b/Component_Tests/Classes/Crypto/Omemo/Test_KeyHelper.cs @@ -63,5 +63,37 @@ public void Test_SignPreKey() Assert.AreEqual(sigBase64, sigRefBase64); } } + + [TestCategory("Crypto")] + [TestMethod] + public void Test_Signing() + { + ECPrivKeyModel identityPriv = new ECPrivKeyModel(SharedUtils.HexStringToByteArray("1498b5467a63dffa2dc9d9e069caf075d16fc33fdd4c3b01bfadae6433767d93")); + ECPubKeyModel identityPub = new ECPubKeyModel(SharedUtils.HexStringToByteArray("05b7a3c12dc0c8c748ab07525b701122b88bd78f600c76342d27f25e5f92444cde")); + + ECPrivKeyModel prePriv = new ECPrivKeyModel(SharedUtils.HexStringToByteArray("181c0ed79c361f2d773f3aa8d5934569395a1c1b4a8514d140a7dcde92688579")); + ECPubKeyModel prePub = new ECPubKeyModel(SharedUtils.HexStringToByteArray("05b30aad2471f7186bdb34951747cf81a67245144260e20ffe5bf7748202d6572c")); + PreKeyModel preKey = new PreKeyModel(prePriv, prePub, 2); + + byte[] sig = KeyHelper.SignPreKey(preKey, identityPriv); + string sig16 = SharedUtils.ToHexString(sig); + Assert.IsTrue(KeyHelper.VerifySignature(identityPub, prePub, sig)); + } + + [TestCategory("Crypto")] + [TestMethod] + public void Test_VerifySignature() + { + ECPubKeyModel identityPub = new ECPubKeyModel(SharedUtils.HexStringToByteArray("05b7a3c12dc0c8c748ab07525b701122b88bd78f600c76342d27f25e5f92444cde")); + ECPubKeyModel prePub = new ECPubKeyModel(SharedUtils.HexStringToByteArray("05b30aad2471f7186bdb34951747cf81a67245144260e20ffe5bf7748202d6572c")); + byte[][] sigs = { + SharedUtils.HexStringToByteArray("db0495131504b9acf87696613070088abff5310ad984f3b7623088af6310e2435381f2f726cfcc3edc754549d47e251d873d5b6777dd62a334b5d3fa8afb9f0a") + }; + + foreach (byte[] sig in sigs) + { + Assert.IsTrue(KeyHelper.VerifySignature(identityPub, prePub, sig)); + } + } } } diff --git a/Component_Tests/Classes/Crypto/Omemo/Test_Xeddsa25519.cs b/Component_Tests/Classes/Crypto/Omemo/Test_Xeddsa25519.cs new file mode 100644 index 00000000..01def49e --- /dev/null +++ b/Component_Tests/Classes/Crypto/Omemo/Test_Xeddsa25519.cs @@ -0,0 +1,22 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Omemo.Classes.Keys; +using Omemo.Classes.Xeddsa; +using Shared.Classes; + +namespace Component_Tests.Classes.Crypto.Omemo +{ + [TestClass] + public class Test_Xeddsa25519 + { + [TestCategory("Crypto")] + [TestMethod] + public void Test_Signing() + { + ECPrivKeyModel priv = new ECPrivKeyModel(SharedUtils.HexStringToByteArray("1498b5467a63dffa2dc9d9e069caf075d16fc33fdd4c3b01bfadae6433767d93")); + ECPubKeyModel pub = new ECPubKeyModel(SharedUtils.HexStringToByteArray("b7a3c12dc0c8c748ab07525b701122b88bd78f600c76342d27f25e5f92444cde")); + IdentityKeyPairModel identityKeyPair = new IdentityKeyPairModel(priv, pub); + // Xeddsa25519.Sign(identityKeyPair.privKey, pub new ); + Xeddsa25519.ToTwistedEdwardsKeyPair(priv); + } + } +} diff --git a/Component_Tests/Component_Tests.csproj b/Component_Tests/Component_Tests.csproj index d47a13e3..a0340c84 100644 --- a/Component_Tests/Component_Tests.csproj +++ b/Component_Tests/Component_Tests.csproj @@ -131,6 +131,7 @@ + diff --git a/Omemo/Classes/Keys/AbstractECKeyPairModel.cs b/Omemo/Classes/Keys/ECKeyPairModel.cs similarity index 91% rename from Omemo/Classes/Keys/AbstractECKeyPairModel.cs rename to Omemo/Classes/Keys/ECKeyPairModel.cs index 4fec3e58..d671790f 100644 --- a/Omemo/Classes/Keys/AbstractECKeyPairModel.cs +++ b/Omemo/Classes/Keys/ECKeyPairModel.cs @@ -6,7 +6,7 @@ namespace Omemo.Classes.Keys { - public abstract class AbstractECKeyPairModel: AbstractDataTemplate + public class ECKeyPairModel: AbstractDataTemplate { //--------------------------------------------------------Attributes:-----------------------------------------------------------------\\ #region --Attributes-- @@ -38,9 +38,9 @@ public ECPubKeyModel pubKey #endregion //--------------------------------------------------------Constructor:----------------------------------------------------------------\\ #region --Constructors-- - public AbstractECKeyPairModel() { } + public ECKeyPairModel() { } - public AbstractECKeyPairModel(ECPrivKeyModel privKey, ECPubKeyModel pubKey) + public ECKeyPairModel(ECPrivKeyModel privKey, ECPubKeyModel pubKey) { this.privKey = privKey; this.pubKey = pubKey; @@ -86,7 +86,7 @@ private void SetPubKeyProperty(ECPubKeyModel value) #region --Misc Methods (Public)-- public override bool Equals(object obj) { - return obj is AbstractECKeyPairModel pair && ((pair.privKey is null && privKey is null) || pair.privKey.Equals(privKey)) && ((pair.pubKey is null && pubKey is null) || pair.pubKey.Equals(pubKey)); + return obj is ECKeyPairModel pair && ((pair.privKey is null && privKey is null) || pair.privKey.Equals(privKey)) && ((pair.pubKey is null && pubKey is null) || pair.pubKey.Equals(pubKey)); } public override int GetHashCode() diff --git a/Omemo/Classes/Keys/ECPrivKeyModel.cs b/Omemo/Classes/Keys/ECPrivKeyModel.cs index 21773c0a..97edb4e7 100644 --- a/Omemo/Classes/Keys/ECPrivKeyModel.cs +++ b/Omemo/Classes/Keys/ECPrivKeyModel.cs @@ -16,14 +16,14 @@ public class ECPrivKeyModel: ECKeyModel /// public ECPrivKeyModel() { } - public ECPrivKeyModel(byte[] pubKey) : base(pubKey) + public ECPrivKeyModel(byte[] privKey) : base(privKey) { - if (pubKey.Length == KeyHelper.PUB_KEY_SIZE) + if (privKey.Length == KeyHelper.PUB_KEY_SIZE) { return; } - throw new OmemoKeyException($"Invalid key length {pubKey.Length} - expected {KeyHelper.PRIV_KEY_SIZE}."); + throw new OmemoKeyException($"Invalid key length {privKey.Length} - expected {KeyHelper.PRIV_KEY_SIZE}."); } #endregion diff --git a/Omemo/Classes/Keys/EphemeralKeyPairModel.cs b/Omemo/Classes/Keys/EphemeralKeyPairModel.cs index 9515a67c..8570b579 100644 --- a/Omemo/Classes/Keys/EphemeralKeyPairModel.cs +++ b/Omemo/Classes/Keys/EphemeralKeyPairModel.cs @@ -3,7 +3,7 @@ /// /// Represents a Ed25519 key pair. /// - public class EphemeralKeyPairModel: AbstractECKeyPairModel + public class EphemeralKeyPairModel: ECKeyPairModel { //--------------------------------------------------------Attributes:-----------------------------------------------------------------\\ #region --Attributes-- diff --git a/Omemo/Classes/Keys/GenericECKeyPairModel.cs b/Omemo/Classes/Keys/GenericECKeyPairModel.cs index 33a6f816..b7373121 100644 --- a/Omemo/Classes/Keys/GenericECKeyPairModel.cs +++ b/Omemo/Classes/Keys/GenericECKeyPairModel.cs @@ -3,7 +3,7 @@ /// /// Represents a generic EC key pair holding a and . /// - public class GenericECKeyPairModel: AbstractECKeyPairModel + public class GenericECKeyPairModel: ECKeyPairModel { //--------------------------------------------------------Attributes:-----------------------------------------------------------------\\ #region --Attributes-- diff --git a/Omemo/Classes/Keys/IdentityKeyPairModel.cs b/Omemo/Classes/Keys/IdentityKeyPairModel.cs index a2ee35ff..8f48fbc1 100644 --- a/Omemo/Classes/Keys/IdentityKeyPairModel.cs +++ b/Omemo/Classes/Keys/IdentityKeyPairModel.cs @@ -3,7 +3,7 @@ /// /// Represents a Ed25519 key pair. /// - public class IdentityKeyPairModel: AbstractECKeyPairModel + public class IdentityKeyPairModel: ECKeyPairModel { //--------------------------------------------------------Attributes:-----------------------------------------------------------------\\ #region --Attributes-- diff --git a/Omemo/Classes/Keys/PreKeyModel.cs b/Omemo/Classes/Keys/PreKeyModel.cs index 8d16e5db..d0f41328 100644 --- a/Omemo/Classes/Keys/PreKeyModel.cs +++ b/Omemo/Classes/Keys/PreKeyModel.cs @@ -3,7 +3,7 @@ namespace Omemo.Classes.Keys { - public class PreKeyModel: AbstractECKeyPairModel + public class PreKeyModel: ECKeyPairModel { //--------------------------------------------------------Attributes:-----------------------------------------------------------------\\ #region --Attributes-- diff --git a/Omemo/Classes/Xeddsa/ECPoint.cs b/Omemo/Classes/Xeddsa/ECPoint.cs new file mode 100644 index 00000000..cb0e82a8 --- /dev/null +++ b/Omemo/Classes/Xeddsa/ECPoint.cs @@ -0,0 +1,69 @@ +using Org.BouncyCastle.Math; + +namespace Omemo.Classes.Xeddsa +{ + public class ECPoint + { + //--------------------------------------------------------Attributes:-----------------------------------------------------------------\\ + #region --Attributes-- + public BigInteger y; + public bool s; + + #endregion + //--------------------------------------------------------Constructor:----------------------------------------------------------------\\ + #region --Constructors-- + public ECPoint(byte[] point) + { + // Decoding described in: https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.3 + BigInteger tmp = new BigInteger(point); + s = ((point[31] & 0x80) >> 7) == 1; + y = tmp.ClearBit(255); + } + + public ECPoint(BigInteger y, bool s) + { + this.y = y; + this.s = s; + } + + #endregion + //--------------------------------------------------------Set-, Get- Methods:---------------------------------------------------------\\ + #region --Set-, Get- Methods-- + + + #endregion + //--------------------------------------------------------Misc Methods:---------------------------------------------------------------\\ + #region --Misc Methods (Public)-- + public byte[] Encode() + { + // Encoding described in: https://datatracker.ietf.org/doc/html/rfc8032#section-5.1.2 + BigInteger point = new BigInteger(y.ToByteArray()); + if (s) + { + point.SetBit(255); + } + else + { + point.ClearBit(255); + } + return point.ToByteArray(); + } + + #endregion + + #region --Misc Methods (Private)-- + + + #endregion + + #region --Misc Methods (Protected)-- + + + #endregion + //--------------------------------------------------------Events:---------------------------------------------------------------------\\ + #region --Events-- + + + #endregion + } +} diff --git a/Omemo/Classes/Xeddsa/Xeddsa25519.cs b/Omemo/Classes/Xeddsa/Xeddsa25519.cs new file mode 100644 index 00000000..5ddcd903 --- /dev/null +++ b/Omemo/Classes/Xeddsa/Xeddsa25519.cs @@ -0,0 +1,114 @@ +using System.Diagnostics; +using NSec.Cryptography; +using Omemo.Classes.Keys; +using Org.BouncyCastle.Math; + +namespace Omemo.Classes.Xeddsa +{ + /// + /// Notes: + /// Curve25519 -> Montgomery + /// edwards25519 (Ed25519) -> Twisted Edwards + /// Ed25519 is a public-key signature system with several attractive features (https://ed25519.cr.yp.to/) + /// Ed25519 as defined in: + /// https://tools.ietf.org/html/rfc7748 + /// https://tools.ietf.org/html/rfc8032 + /// + public class Xeddsa25519 + { + //--------------------------------------------------------Attributes:-----------------------------------------------------------------\\ + #region --Attributes-- + private static readonly BigInteger p = BigInteger.Two.Pow(255).Subtract(BigInteger.ValueOf(19)); + + private static readonly byte[] MINUS_ONE = new byte[] {0xec, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, + 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10}; + + #endregion + //--------------------------------------------------------Constructor:----------------------------------------------------------------\\ + #region --Constructors-- + + + #endregion + //--------------------------------------------------------Set-, Get- Methods:---------------------------------------------------------\\ + #region --Set-, Get- Methods-- + + + #endregion + //--------------------------------------------------------Misc Methods:---------------------------------------------------------------\\ + #region --Misc Methods (Public)-- + /// + /// Signes the given and returns the result. + /// + /// The Curve25519 (Montgomery) private key used for signing. + /// The message that should be signed. + /// 64 bytes secure random data used for signing. + /// The generated signature for the given . + public static byte[] Sign(ECPrivKeyModel privKey, byte[] msg, byte[] random) + { + Debug.Assert(random.Length == 64); + Debug.Assert(msg.Length > 0); + + ECKeyPairModel edKeyPair = ToTwistedEdwardsKeyPair(privKey); + + Key key = Key.Import(SignatureAlgorithm.Ed25519, edKeyPair.privKey.key, KeyBlobFormat.RawPrivateKey); + return SignatureAlgorithm.Ed25519.Sign(key, msg); + } + + #endregion + + #region --Misc Methods (Private)-- + public static ECKeyPairModel ToTwistedEdwardsKeyPair(ECPrivKeyModel privKey) + { + byte[] A = new byte[Org.BouncyCastle.Math.EC.Rfc7748.X25519.PointSize]; + byte[] A2 = new byte[Org.BouncyCastle.Math.EC.Rfc7748.X25519.PointSize]; + Org.BouncyCastle.Math.EC.Rfc7748.X25519.ScalarMultBase(privKey.key, 0, A2, 0); + byte[] u = Shared.Classes.SharedUtils.HexStringToByteArray("5866666666666666666666666666666666666666666666666666666666666666"); + Org.BouncyCastle.Math.EC.Rfc7748.X25519.ScalarMult(privKey.key, 0, u, 0, A, 0); + + byte s = (byte)((A[31] & 0x80) >> 7); + byte[] a = new byte[Org.BouncyCastle.Math.EC.Rfc7748.X25519.PointSize]; + privKey.key.CopyTo(a, 0); + byte[] aNeg = new byte[Org.BouncyCastle.Math.EC.Rfc7748.X25519.PointSize]; + + Org.BouncyCastle.Math.EC.Rfc7748.X25519.ScalarMult(MINUS_ONE, 0, a, 0, aNeg, 0); + sc_cmov(a, aNeg, s); + A[31] &= 0x7F; + + return new ECKeyPairModel(new ECPrivKeyModel(a), new ECPubKeyModel(A)); + } + + private static void sc_cmov(byte[] f, byte[] g, byte b) + { + byte[] x = new byte[32]; + for (int count = 0; count < 32; count++) + { + x[count] = (byte)(f[count] ^ g[count]); + } + b = (byte)-b; + for (int count = 0; count < 32; count++) + { + x[count] &= b; + } + + for (int count = 0; count < 32; count++) + { + f[count] = (byte)(f[count] ^ x[count]); + } + } + + + #endregion + + #region --Misc Methods (Protected)-- + + + #endregion + //--------------------------------------------------------Events:---------------------------------------------------------------------\\ + #region --Events-- + + + #endregion + } +} diff --git a/Omemo/Omemo.csproj b/Omemo/Omemo.csproj index 88689cb5..ed299843 100644 --- a/Omemo/Omemo.csproj +++ b/Omemo/Omemo.csproj @@ -130,7 +130,7 @@ - + @@ -150,6 +150,8 @@ + + @@ -182,6 +184,7 @@ Visual C++ 2015-2019 UWP Desktop Runtime for native apps + 14.0