From 3aed16024f6e55de2541a42edb44c4de1a0302dd Mon Sep 17 00:00:00 2001 From: Nicolas Constant Date: Thu, 4 Jun 2020 22:53:38 -0400 Subject: [PATCH] messing with crypto --- src/BirdsiteLive.Cryptography/ASN1.cs | 49 ++++ .../BirdsiteLive.Cryptography.csproj | 6 + src/BirdsiteLive.Cryptography/MagicKey.cs | 175 ++++++++++++++ src/BirdsiteLive.Cryptography/RsaKeys.cs | 225 ++++++++++++++++++ src/BirdsiteLive.sln | 11 +- .../BirdsiteLive.Cryptography.Tests.csproj | 20 ++ .../MagicKeyTests.cs | 19 ++ .../RsaGeneratorTests.cs | 15 ++ .../RsaKeysTests.cs | 34 +++ 9 files changed, 553 insertions(+), 1 deletion(-) create mode 100644 src/BirdsiteLive.Cryptography/ASN1.cs create mode 100644 src/BirdsiteLive.Cryptography/MagicKey.cs create mode 100644 src/BirdsiteLive.Cryptography/RsaKeys.cs create mode 100644 src/Tests/BirdsiteLive.Cryptography.Tests/BirdsiteLive.Cryptography.Tests.csproj create mode 100644 src/Tests/BirdsiteLive.Cryptography.Tests/MagicKeyTests.cs create mode 100644 src/Tests/BirdsiteLive.Cryptography.Tests/RsaGeneratorTests.cs create mode 100644 src/Tests/BirdsiteLive.Cryptography.Tests/RsaKeysTests.cs diff --git a/src/BirdsiteLive.Cryptography/ASN1.cs b/src/BirdsiteLive.Cryptography/ASN1.cs new file mode 100644 index 0000000..cd31579 --- /dev/null +++ b/src/BirdsiteLive.Cryptography/ASN1.cs @@ -0,0 +1,49 @@ +using System.Linq; +using System.Security.Cryptography; +using Asn1; +using Asn1Sequence = Asn1.Asn1Sequence; +using Asn1Null = Asn1.Asn1Null; + +namespace BirdsiteLive.Cryptography +{ + public class ASN1 + { + public static RSA ToRSA(byte[] data) + { + var node = Asn1Node.ReadNode(data); + + var rsaSequence = Asn1Node.ReadNode((node.Nodes[1] as Asn1BitString).Data); + + var modulus = (rsaSequence.Nodes[0] as Asn1Integer).Value; + var exponent = (rsaSequence.Nodes[1] as Asn1Integer).Value; + var prms = new RSAParameters { Modulus = modulus, Exponent = exponent }; + var rsa = RSA.Create(); + rsa.ImportParameters(prms); + return rsa; + } + + public static byte[] FromRSA(RSA rsa) + { + var prms = rsa.ExportParameters(false); + + var modulus = new Asn1Integer((new byte[] { 0x00 }.Concat(prms.Modulus)).ToArray()); + var exponent = new Asn1Integer(prms.Exponent); + + var oidheader = new Asn1Sequence(); + oidheader.Nodes.Add(new Asn1ObjectIdentifier("1.2.840.113549.1.1.1")); + oidheader.Nodes.Add(new Asn1Null()); + + var rsaSequence = new Asn1Sequence(); + rsaSequence.Nodes.Add(modulus); + rsaSequence.Nodes.Add(exponent); + + var bitString = new Asn1BitString(rsaSequence.GetBytes()); + + var result = new Asn1Sequence(); + result.Nodes.Add(oidheader); + result.Nodes.Add(bitString); + + return result.GetBytes(); + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.Cryptography/BirdsiteLive.Cryptography.csproj b/src/BirdsiteLive.Cryptography/BirdsiteLive.Cryptography.csproj index 9f5c4f4..f0d9f4f 100644 --- a/src/BirdsiteLive.Cryptography/BirdsiteLive.Cryptography.csproj +++ b/src/BirdsiteLive.Cryptography/BirdsiteLive.Cryptography.csproj @@ -4,4 +4,10 @@ netstandard2.0 + + + + + + diff --git a/src/BirdsiteLive.Cryptography/MagicKey.cs b/src/BirdsiteLive.Cryptography/MagicKey.cs new file mode 100644 index 0000000..24f7af2 --- /dev/null +++ b/src/BirdsiteLive.Cryptography/MagicKey.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Text; +using Newtonsoft.Json; + +namespace BirdsiteLive.Cryptography +{ + public class MagicKey + { + //public class WebfingerLink + //{ + // public string rel { get; set; } + // public string type { get; set; } + // public string href { get; set; } + // public string template { get; set; } + //} + + //public class WebfingerResult + //{ + // public string subject { get; set; } + // public List aliases { get; set; } + // public List links { get; set; } + //} + + private string[] _parts; + private RSA _rsa; + + private static byte[] _decodeBase64Url(string data) + { + return Convert.FromBase64String(data.Replace('-', '+').Replace('_', '/')); + } + + private static string _encodeBase64Url(byte[] data) + { + return Convert.ToBase64String(data).Replace('+', '-').Replace('/', '_'); + } + + private class RSAKeyParms + { + public byte[] D; + public byte[] DP; + public byte[] DQ; + public byte[] Exponent; + public byte[] InverseQ; + public byte[] Modulus; + public byte[] P; + public byte[] Q; + + public static RSAKeyParms From(RSAParameters parms) + { + var a = new RSAKeyParms(); + a.D = parms.D; + a.DP = parms.DP; + a.DQ = parms.DQ; + a.Exponent = parms.Exponent; + a.InverseQ = parms.InverseQ; + a.Modulus = parms.Modulus; + a.P = parms.P; + a.Q = parms.Q; + return a; + } + + public RSAParameters Make() + { + var a = new RSAParameters(); + a.D = D; + a.DP = DP; + a.DQ = DQ; + a.Exponent = Exponent; + a.InverseQ = InverseQ; + a.Modulus = Modulus; + a.P = P; + a.Q = Q; + return a; + } + } + + public MagicKey(string key) + { + if (key[0] == '{') + { + _rsa = RSA.Create(); + _rsa.ImportParameters(JsonConvert.DeserializeObject(key).Make()); + } + else + { + _parts = key.Split('.'); + if (_parts[0] != "RSA") throw new Exception("Unknown magic key!"); + + var rsaParams = new RSAParameters(); + rsaParams.Modulus = _decodeBase64Url(_parts[1]); + rsaParams.Exponent = _decodeBase64Url(_parts[2]); + + _rsa = RSA.Create(); + _rsa.ImportParameters(rsaParams); + } + } + + public static MagicKey Generate() + { + var rsa = RSA.Create(); + rsa.KeySize = 2048; + + return new MagicKey(JsonConvert.SerializeObject(RSAKeyParms.From(rsa.ExportParameters(true)))); + } + + //public static async Task KeyForAuthor(ASObject obj) + //{ + // var authorId = (string)obj["email"].FirstOrDefault()?.Primitive; + // if (authorId == null) + // { + // authorId = obj["name"].FirstOrDefault()?.Primitive + "@" + new Uri(obj.Id).Host; + // } + + // var domain = authorId.Split('@')[1]; + // var hc = new HttpClient(); + // var wf = JsonConvert.DeserializeObject(await hc.GetStringAsync($"https://{domain}/.well-known/webfinger?resource=acct:{Uri.EscapeDataString(authorId)}")); + // var link = wf.links.FirstOrDefault(a => a.rel == "magic-public-key"); + // if (link == null) return null; + + // if (!link.href.StartsWith("data:")) return null; // does this happen? + + // return new MagicKey(link.href.Split(new char[] { ',' }, 2)[1]); + //} + + public byte[] BuildSignedData(string data, string dataType, string encoding, string algorithm) + { + var sig = data + "." + _encodeBase64Url(Encoding.UTF8.GetBytes(dataType)) + "." + _encodeBase64Url(Encoding.UTF8.GetBytes(encoding)) + "." + _encodeBase64Url(Encoding.UTF8.GetBytes(algorithm)); + return Encoding.UTF8.GetBytes(sig); + } + + public bool Verify(string signature, byte[] data) + { + return _rsa.VerifyData(data, _decodeBase64Url(signature), HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } + + public byte[] Sign(byte[] data) + { + return _rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1); + } + + public string AsPEM + { + get + { + var data = ASN1.FromRSA(_rsa); + var baseData = Convert.ToBase64String(data); + var builder = new StringBuilder(baseData); + for (int i = 72; i < builder.Length; i += 73) + builder.Insert(i, "\n"); + + builder.Insert(0, "-----BEGIN PUBLIC KEY-----\n"); + builder.Append("\n-----END PUBLIC KEY-----"); + + return builder.ToString(); + } + } + + public string PrivateKey + { + get { return JsonConvert.SerializeObject(RSAKeyParms.From(_rsa.ExportParameters(true))); } + } + + public string PublicKey + { + get + { + var parms = _rsa.ExportParameters(false); + + return string.Join(".", "RSA", _encodeBase64Url(parms.Modulus), _encodeBase64Url(parms.Exponent)); + } + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.Cryptography/RsaKeys.cs b/src/BirdsiteLive.Cryptography/RsaKeys.cs new file mode 100644 index 0000000..0bc4a90 --- /dev/null +++ b/src/BirdsiteLive.Cryptography/RsaKeys.cs @@ -0,0 +1,225 @@ +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.Crypto.Parameters; +using Org.BouncyCastle.OpenSsl; +using Org.BouncyCastle.Security; +using System; +using System.IO; +using System.Security.Cryptography; + +namespace MyProject.Data.Encryption +{ + public class RSAKeys + { + /// + /// Import OpenSSH PEM private key string into MS RSACryptoServiceProvider + /// + /// + /// + public static RSACryptoServiceProvider ImportPrivateKey(string pem) + { + PemReader pr = new PemReader(new StringReader(pem)); + AsymmetricCipherKeyPair KeyPair = (AsymmetricCipherKeyPair)pr.ReadObject(); + RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaPrivateCrtKeyParameters)KeyPair.Private); + + RSACryptoServiceProvider csp = new RSACryptoServiceProvider();// cspParams); + csp.ImportParameters(rsaParams); + return csp; + } + + /// + /// Import OpenSSH PEM public key string into MS RSACryptoServiceProvider + /// + /// + /// + public static RSACryptoServiceProvider ImportPublicKey(string pem) + { + PemReader pr = new PemReader(new StringReader(pem)); + AsymmetricKeyParameter publicKey = (AsymmetricKeyParameter)pr.ReadObject(); + RSAParameters rsaParams = DotNetUtilities.ToRSAParameters((RsaKeyParameters)publicKey); + + RSACryptoServiceProvider csp = new RSACryptoServiceProvider();// cspParams); + csp.ImportParameters(rsaParams); + return csp; + } + + /// + /// Export private (including public) key from MS RSACryptoServiceProvider into OpenSSH PEM string + /// slightly modified from https://stackoverflow.com/a/23739932/2860309 + /// + /// + /// + public static string ExportPrivateKey(RSACryptoServiceProvider csp) + { + StringWriter outputStream = new StringWriter(); + if (csp.PublicOnly) throw new ArgumentException("CSP does not contain a private key", "csp"); + var parameters = csp.ExportParameters(true); + using (var stream = new MemoryStream()) + { + var writer = new BinaryWriter(stream); + writer.Write((byte)0x30); // SEQUENCE + using (var innerStream = new MemoryStream()) + { + var innerWriter = new BinaryWriter(innerStream); + EncodeIntegerBigEndian(innerWriter, new byte[] { 0x00 }); // Version + EncodeIntegerBigEndian(innerWriter, parameters.Modulus); + EncodeIntegerBigEndian(innerWriter, parameters.Exponent); + EncodeIntegerBigEndian(innerWriter, parameters.D); + EncodeIntegerBigEndian(innerWriter, parameters.P); + EncodeIntegerBigEndian(innerWriter, parameters.Q); + EncodeIntegerBigEndian(innerWriter, parameters.DP); + EncodeIntegerBigEndian(innerWriter, parameters.DQ); + EncodeIntegerBigEndian(innerWriter, parameters.InverseQ); + var length = (int)innerStream.Length; + EncodeLength(writer, length); + writer.Write(innerStream.GetBuffer(), 0, length); + } + + var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray(); + // WriteLine terminates with \r\n, we want only \n + outputStream.Write("-----BEGIN RSA PRIVATE KEY-----\n"); + // Output as Base64 with lines chopped at 64 characters + for (var i = 0; i < base64.Length; i += 64) + { + outputStream.Write(base64, i, Math.Min(64, base64.Length - i)); + outputStream.Write("\n"); + } + outputStream.Write("-----END RSA PRIVATE KEY-----"); + } + + return outputStream.ToString(); + } + + /// + /// Export public key from MS RSACryptoServiceProvider into OpenSSH PEM string + /// slightly modified from https://stackoverflow.com/a/28407693 + /// + /// + /// + public static string ExportPublicKey(RSACryptoServiceProvider csp) + { + StringWriter outputStream = new StringWriter(); + var parameters = csp.ExportParameters(false); + using (var stream = new MemoryStream()) + { + var writer = new BinaryWriter(stream); + writer.Write((byte)0x30); // SEQUENCE + using (var innerStream = new MemoryStream()) + { + var innerWriter = new BinaryWriter(innerStream); + innerWriter.Write((byte)0x30); // SEQUENCE + EncodeLength(innerWriter, 13); + innerWriter.Write((byte)0x06); // OBJECT IDENTIFIER + var rsaEncryptionOid = new byte[] { 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x01 }; + EncodeLength(innerWriter, rsaEncryptionOid.Length); + innerWriter.Write(rsaEncryptionOid); + innerWriter.Write((byte)0x05); // NULL + EncodeLength(innerWriter, 0); + innerWriter.Write((byte)0x03); // BIT STRING + using (var bitStringStream = new MemoryStream()) + { + var bitStringWriter = new BinaryWriter(bitStringStream); + bitStringWriter.Write((byte)0x00); // # of unused bits + bitStringWriter.Write((byte)0x30); // SEQUENCE + using (var paramsStream = new MemoryStream()) + { + var paramsWriter = new BinaryWriter(paramsStream); + EncodeIntegerBigEndian(paramsWriter, parameters.Modulus); // Modulus + EncodeIntegerBigEndian(paramsWriter, parameters.Exponent); // Exponent + var paramsLength = (int)paramsStream.Length; + EncodeLength(bitStringWriter, paramsLength); + bitStringWriter.Write(paramsStream.GetBuffer(), 0, paramsLength); + } + var bitStringLength = (int)bitStringStream.Length; + EncodeLength(innerWriter, bitStringLength); + innerWriter.Write(bitStringStream.GetBuffer(), 0, bitStringLength); + } + var length = (int)innerStream.Length; + EncodeLength(writer, length); + writer.Write(innerStream.GetBuffer(), 0, length); + } + + var base64 = Convert.ToBase64String(stream.GetBuffer(), 0, (int)stream.Length).ToCharArray(); + // WriteLine terminates with \r\n, we want only \n + outputStream.Write("-----BEGIN PUBLIC KEY-----\n"); + for (var i = 0; i < base64.Length; i += 64) + { + outputStream.Write(base64, i, Math.Min(64, base64.Length - i)); + outputStream.Write("\n"); + } + outputStream.Write("-----END PUBLIC KEY-----"); + } + + return outputStream.ToString(); + } + + /// + /// https://stackoverflow.com/a/23739932/2860309 + /// + /// + /// + private static void EncodeLength(BinaryWriter stream, int length) + { + if (length < 0) throw new ArgumentOutOfRangeException("length", "Length must be non-negative"); + if (length < 0x80) + { + // Short form + stream.Write((byte)length); + } + else + { + // Long form + var temp = length; + var bytesRequired = 0; + while (temp > 0) + { + temp >>= 8; + bytesRequired++; + } + stream.Write((byte)(bytesRequired | 0x80)); + for (var i = bytesRequired - 1; i >= 0; i--) + { + stream.Write((byte)(length >> (8 * i) & 0xff)); + } + } + } + + /// + /// https://stackoverflow.com/a/23739932/2860309 + /// + /// + /// + /// + private static void EncodeIntegerBigEndian(BinaryWriter stream, byte[] value, bool forceUnsigned = true) + { + stream.Write((byte)0x02); // INTEGER + var prefixZeros = 0; + for (var i = 0; i < value.Length; i++) + { + if (value[i] != 0) break; + prefixZeros++; + } + if (value.Length - prefixZeros == 0) + { + EncodeLength(stream, 1); + stream.Write((byte)0); + } + else + { + if (forceUnsigned && value[prefixZeros] > 0x7f) + { + // Add a prefix zero to force unsigned if the MSB is 1 + EncodeLength(stream, value.Length - prefixZeros + 1); + stream.Write((byte)0); + } + else + { + EncodeLength(stream, value.Length - prefixZeros); + } + for (var i = prefixZeros; i < value.Length; i++) + { + stream.Write(value[i]); + } + } + } + } +} \ No newline at end of file diff --git a/src/BirdsiteLive.sln b/src/BirdsiteLive.sln index 0efc944..b743257 100644 --- a/src/BirdsiteLive.sln +++ b/src/BirdsiteLive.sln @@ -11,7 +11,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Domain", "Domain", "{4FEAD6 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Twitter", "BirdsiteLive.Twitter\BirdsiteLive.Twitter.csproj", "{77C559D1-80A2-4B1C-A566-AE2D156944A4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BirdsiteLive.Common", "BirdsiteLive.Common\BirdsiteLive.Common.csproj", "{E64E7501-5DB8-4620-BA35-BA59FD746ABA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BirdsiteLive.Common", "BirdsiteLive.Common\BirdsiteLive.Common.csproj", "{E64E7501-5DB8-4620-BA35-BA59FD746ABA}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{A32D3458-09D0-4E0A-BA4B-8C411B816B94}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BirdsiteLive.Cryptography.Tests", "Tests\BirdsiteLive.Cryptography.Tests\BirdsiteLive.Cryptography.Tests.csproj", "{155D46A4-2D05-47F2-8FFC-0B7C412A7652}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -35,6 +39,10 @@ Global {E64E7501-5DB8-4620-BA35-BA59FD746ABA}.Debug|Any CPU.Build.0 = Debug|Any CPU {E64E7501-5DB8-4620-BA35-BA59FD746ABA}.Release|Any CPU.ActiveCfg = Release|Any CPU {E64E7501-5DB8-4620-BA35-BA59FD746ABA}.Release|Any CPU.Build.0 = Release|Any CPU + {155D46A4-2D05-47F2-8FFC-0B7C412A7652}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {155D46A4-2D05-47F2-8FFC-0B7C412A7652}.Debug|Any CPU.Build.0 = Debug|Any CPU + {155D46A4-2D05-47F2-8FFC-0B7C412A7652}.Release|Any CPU.ActiveCfg = Release|Any CPU + {155D46A4-2D05-47F2-8FFC-0B7C412A7652}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -43,6 +51,7 @@ Global {160AD138-4E29-4706-8546-9826B529E9B2} = {4FEAD6BC-3C8E-451A-8CA1-FF1AF47D26CC} {77C559D1-80A2-4B1C-A566-AE2D156944A4} = {4FEAD6BC-3C8E-451A-8CA1-FF1AF47D26CC} {E64E7501-5DB8-4620-BA35-BA59FD746ABA} = {4FEAD6BC-3C8E-451A-8CA1-FF1AF47D26CC} + {155D46A4-2D05-47F2-8FFC-0B7C412A7652} = {A32D3458-09D0-4E0A-BA4B-8C411B816B94} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {69E8DCAD-4C37-4010-858F-5F94E6FBABCE} diff --git a/src/Tests/BirdsiteLive.Cryptography.Tests/BirdsiteLive.Cryptography.Tests.csproj b/src/Tests/BirdsiteLive.Cryptography.Tests/BirdsiteLive.Cryptography.Tests.csproj new file mode 100644 index 0000000..0c9c0a6 --- /dev/null +++ b/src/Tests/BirdsiteLive.Cryptography.Tests/BirdsiteLive.Cryptography.Tests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + diff --git a/src/Tests/BirdsiteLive.Cryptography.Tests/MagicKeyTests.cs b/src/Tests/BirdsiteLive.Cryptography.Tests/MagicKeyTests.cs new file mode 100644 index 0000000..d77b67a --- /dev/null +++ b/src/Tests/BirdsiteLive.Cryptography.Tests/MagicKeyTests.cs @@ -0,0 +1,19 @@ +using System.Threading.Tasks; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace BirdsiteLive.Cryptography.Tests +{ + [TestClass] + public class MagicKeyTests + { + [TestMethod] + public async Task Test() + { + var g = MagicKey.Generate(); + + var magicKey = new MagicKey(g.PrivateKey); + + + } + } +} \ No newline at end of file diff --git a/src/Tests/BirdsiteLive.Cryptography.Tests/RsaGeneratorTests.cs b/src/Tests/BirdsiteLive.Cryptography.Tests/RsaGeneratorTests.cs new file mode 100644 index 0000000..639c392 --- /dev/null +++ b/src/Tests/BirdsiteLive.Cryptography.Tests/RsaGeneratorTests.cs @@ -0,0 +1,15 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace BirdsiteLive.Cryptography.Tests +{ + [TestClass] + public class RsaGeneratorTests + { + [TestMethod] + public void TestMethod1() + { + var rsaGen = new RsaGenerator(); + var rsa = rsaGen.GetRsa(); + } + } +} diff --git a/src/Tests/BirdsiteLive.Cryptography.Tests/RsaKeysTests.cs b/src/Tests/BirdsiteLive.Cryptography.Tests/RsaKeysTests.cs new file mode 100644 index 0000000..50c77b9 --- /dev/null +++ b/src/Tests/BirdsiteLive.Cryptography.Tests/RsaKeysTests.cs @@ -0,0 +1,34 @@ +using System.Security.Cryptography; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using MyProject.Data.Encryption; + +namespace BirdsiteLive.Cryptography.Tests +{ + [TestClass] + public class RsaKeysTests + { + [TestMethod] + public void TestMethod1() + { + var rsa = RSA.Create(); + + var cspParams = new CspParameters(); + cspParams.ProviderType = 1; // PROV_RSA_FULL + cspParams.Flags = CspProviderFlags.CreateEphemeralKey; + var rsaProvider = new RSACryptoServiceProvider(2048, cspParams); + + var rsaPublicKey = RSAKeys.ExportPublicKey(rsaProvider); + var rsaPrivateKey = RSAKeys.ExportPrivateKey(rsaProvider); + + //rsaProvider. + + var pem = RSAKeys.ImportPublicKey(rsaPrivateKey); + } + + [TestMethod] + public void TestMethod2() + { + + } + } +} \ No newline at end of file