diff --git a/Dockerfile b/Dockerfile index bb77b917..1345ba6e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,22 +3,26 @@ WORKDIR /app COPY ./Game.Engine/wwwroot ./ RUN ["npm", "i", "--unsafe-perm"] + RUN ["npm", "run", "build"] -FROM microsoft/dotnet:2.1-sdk +FROM mcr.microsoft.com/dotnet/sdk:5.0 WORKDIR /app COPY . ./ COPY --from=0 /app/dist ./Game.Engine/wwwroot/dist WORKDIR /app/Game.Engine -RUN ["dotnet", "publish", "-c", "Release"] +RUN dotnet publish -c Release -o out + +WORKDIR /app/Game.Engine +RUN dotnet publish -c Release -o out WORKDIR /app/Game.Util -RUN ["dotnet", "publish", "-c", "Release"] +RUN dotnet publish -c Release -o out -WORKDIR /app/Game.Registry -RUN ["dotnet", "publish", "-c", "Release"] +FROM mcr.microsoft.com/dotnet/aspnet:5.0 +WORKDIR /app +COPY --from=1 /app/Game.Engine/out ./ -WORKDIR /app/Game.Engine/bin/Release/netcoreapp2.1/publish -EXPOSE 5000 -CMD ["dotnet", "Game.Engine.dll"] +EXPOSE 80 +CMD ["dotnet", "Game.Engine.dll"] \ No newline at end of file diff --git a/Game.API.Client/Game.API.Client.csproj b/Game.API.Client/Game.API.Client.csproj index 001d64fb..fd50b5cb 100644 --- a/Game.API.Client/Game.API.Client.csproj +++ b/Game.API.Client/Game.API.Client.csproj @@ -1,10 +1,10 @@  - netcoreapp2.1 + netcoreapp5.0 7.1 - + diff --git a/Game.API.Common/Game.API.Common.csproj b/Game.API.Common/Game.API.Common.csproj index 83ae3d7b..71244c46 100644 --- a/Game.API.Common/Game.API.Common.csproj +++ b/Game.API.Common/Game.API.Common.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp5.0 7.1 diff --git a/Game.Engine.Networking.FlatBuffers/Game.Engine.Networking.FlatBuffers.csproj b/Game.Engine.Networking.FlatBuffers/Game.Engine.Networking.FlatBuffers.csproj index 61ce65a3..3a2c3b66 100644 --- a/Game.Engine.Networking.FlatBuffers/Game.Engine.Networking.FlatBuffers.csproj +++ b/Game.Engine.Networking.FlatBuffers/Game.Engine.Networking.FlatBuffers.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp5.0 7.1 diff --git a/Game.Engine.sln b/Game.Engine.sln index f7a151a7..9136e2ba 100644 --- a/Game.Engine.sln +++ b/Game.Engine.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27428.2037 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29613.14 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Game.Engine", "Game.Engine\Game.Engine.csproj", "{3AD01CCA-F212-4EE1-8718-2250D4869A53}" EndProject @@ -13,8 +13,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Game.Robots", "Game.Robots\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Game.Registry", "Game.Registry\Game.Registry.csproj", "{BD231600-BDBE-48AE-89A8-366E22DF86D0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PKISharp.SimplePKI", "PKISharp.SimplePKI\PKISharp.SimplePKI.csproj", "{6EFC6827-A34A-4908-B413-A0286DDB83E3}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Game.API.Client", "Game.API.Client\Game.API.Client.csproj", "{40BBE777-A59D-411F-BD61-34A2BB08D858}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Game.API.Common", "Game.API.Common\Game.API.Common.csproj", "{0FC74086-5FDC-477E-93C2-927E11918AEE}" diff --git a/Game.Engine/ChatBot/DiscordBotModule.cs b/Game.Engine/ChatBot/DiscordBotModule.cs index 2063b913..8053e29e 100644 --- a/Game.Engine/ChatBot/DiscordBotModule.cs +++ b/Game.Engine/ChatBot/DiscordBotModule.cs @@ -1,6 +1,5 @@ namespace Game.Engine.ChatBot { - using Docker.DotNet; using Discord; using Discord.Commands; using Discord.Rest; @@ -9,8 +8,6 @@ namespace Game.Engine.ChatBot using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; - using Docker.DotNet.Models; - using Game.Engine.Hosting; // Modules must be public and inherit from an IModuleBase public class DiscordBotModule : ModuleBase @@ -51,12 +48,7 @@ public async Task ResetAsync() public async Task DeployAsync(string url, string tag) { if (url == GameConfiguration.PublicURL || url == "*") - { - await DockerUpgrade.UpgradeAsync(GameConfiguration, tag, async (message) => - { - await ReplyAsync(message); - }); - } + await ReplyAsync("I'm sorry Dave, I can't do that."); } diff --git a/Game.Engine/Common/DnsUtil.cs b/Game.Engine/Common/DnsUtil.cs deleted file mode 100644 index 780b2cd8..00000000 --- a/Game.Engine/Common/DnsUtil.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Threading; -using System.Threading.Tasks; -using DnsClient; -using DnsClient.Protocol; - -namespace Game.Engine.Common -{ - public static class DnsUtil - { - private static LookupClient _Client; - - public static string[] DnsServers { get; set; } - - public static LookupClient Client - { - get - { - if (_Client == null) - { - lock (typeof(DnsUtil)) - { - if (_Client == null) - { - if (DnsServers?.Length > 0) - { - var nameServers = DnsServers.SelectMany(x => Dns.GetHostAddresses(x)).ToArray(); - _Client = new DnsClient.LookupClient(nameServers); - } - else - { - _Client = new DnsClient.LookupClient(); - } - } - } - } - return _Client; - } - } - - public static async Task> LookupRecordAsync(string type, string name) - { - var dnsType = (DnsClient.QueryType)Enum.Parse(typeof(DnsClient.QueryType), type); - var dnsResp = await Client.QueryAsync(name, dnsType); - - if (dnsResp.HasError) - { - if ("Non-Existent Domain".Equals(dnsResp.ErrorMessage, - StringComparison.OrdinalIgnoreCase)) - return null; - throw new Exception("DNS lookup error: " + dnsResp.ErrorMessage); - } - - return dnsResp.AllRecords.SelectMany(x => x.ValueAsStrings()); - } - - public static IEnumerable ValueAsStrings(this DnsResourceRecord drr) - { - switch (drr) - { - case TxtRecord txt: - return txt.Text; - default: - return new[] { drr.ToString() }; - } - } - } -} \ No newline at end of file diff --git a/Game.Engine/Common/DohUtil.cs b/Game.Engine/Common/DohUtil.cs deleted file mode 100644 index 05720a6b..00000000 --- a/Game.Engine/Common/DohUtil.cs +++ /dev/null @@ -1,91 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using Makaretu.Dns; - -namespace Game.Engine.Common -{ - /// - /// Utility class to resolve DNS queries using DNS-over-HTTP (DoH). - /// - public static class DohUtil - { - private static DohClient _Client; - - /// - /// Defaults to "https://cloudflare-dns.com/dns-query" - /// - public static string DohServerUrl { get; set; } - - public static HttpClient HttpClient { get; set; } - - public static DohClient Client - { - get - { - if (_Client == null) - { - lock (typeof(DohUtil)) - { - if (_Client == null) - { - _Client = new DohClient(); - - if (HttpClient != null) - _Client.HttpClient = HttpClient; - if (DohServerUrl != null) - _Client.ServerUrl = DohServerUrl; - } - } - } - return _Client; - } - } - - public static async Task> LookupRawRecordsAsync(string type, string name) - { - if (!Enum.TryParse(type, out var dnsType)) - throw new Exception("invalid or unsupported DNS type"); - - var response = await Client.QueryAsync(name, dnsType); - return response.Answers.Select(x => x.ToString()); - } - - public static async Task> LookupTxtRecordsAsync(string name) - { - var response = await Client.QueryAsync(name, Makaretu.Dns.DnsType.TXT); - return response.Answers - .OfType() - .SelectMany(x => x.Strings); - } - - public static async Task> LookupIpv4RecordsAsync(string name) - { - var response = await Client.QueryAsync(name, Makaretu.Dns.DnsType.A); - return response.Answers - .OfType() - .Select(x => x.Address); - } - - public static async Task> LookupIpv6RecordsAsync(string name) - { - var response = await Client.QueryAsync(name, Makaretu.Dns.DnsType.AAAA); - return response.Answers - .OfType() - .Select(x => x.Address); - } - - public static async Task> LookupCnameRecordsAsync(string name) - { - var dns = new Makaretu.Dns.DnsClient(); - var response = await Client.QueryAsync(name, Makaretu.Dns.DnsType.CNAME); - return response.Answers - .OfType() - .Select(x => x.Target); - } - } -} \ No newline at end of file diff --git a/Game.Engine/Common/PKI/AccountKey.cs b/Game.Engine/Common/PKI/AccountKey.cs deleted file mode 100644 index 24fd21fe..00000000 --- a/Game.Engine/Common/PKI/AccountKey.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using ACMESharp.Crypto.JOSE; - -namespace Game.Engine.Common.PKI -{ - public class AccountKey - { - public string KeyType { get; set; } - public string KeyExport { get; set; } - - public IJwsTool GenerateTool() - { - if (KeyType.StartsWith("ES")) - { - var tool = new ACMESharp.Crypto.JOSE.Impl.ESJwsTool(); - tool.HashSize = int.Parse(KeyType.Substring(2)); - tool.Init(); - tool.Import(KeyExport); - return tool; - } - - if (KeyType.StartsWith("RS")) - { - var tool = new ACMESharp.Crypto.JOSE.Impl.RSJwsTool(); - tool.KeySize = int.Parse(KeyType.Substring(2)); - tool.Init(); - tool.Import(KeyExport); - return tool; - } - - throw new Exception($"Unknown or unsupported KeyType [{KeyType}]"); - } - } -} diff --git a/Game.Engine/Common/PKI/PkiJwsTool.cs b/Game.Engine/Common/PKI/PkiJwsTool.cs deleted file mode 100644 index 63df4bb9..00000000 --- a/Game.Engine/Common/PKI/PkiJwsTool.cs +++ /dev/null @@ -1,85 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Security.Cryptography; -using System.Threading.Tasks; -using ACMESharp.Crypto; -using ACMESharp.Crypto.JOSE; -using Org.BouncyCastle.Crypto.Parameters; -using PKISharp.SimplePKI; - -namespace Game.Engine.Common.PKI -{ - public class PkiJwsTool : IJwsTool - { - private PkiKeyPair _keys; - - private object _jwk; - - public PkiJwsTool(int bits) - { - Bits = bits; - } - - public int Bits { get; } - - public string JwsAlg => $"ES{Bits}"; - - public PkiKeyPair KeyPair => _keys; - - public void Init() - { - _jwk = null; - _keys = PkiKeyPair.GenerateEcdsaKeyPair(Bits); - } - - public void Dispose() - { - _keys = null; - _jwk = null; - } - - public string Export() - { - using (var ms = new MemoryStream()) - { - _keys.Save(ms); - return Convert.ToBase64String(ms.ToArray()); - } - } - - public void Import(string exported) - { - _jwk = null; - using (var ms = new MemoryStream(Convert.FromBase64String(exported))) - { - _keys = PkiKeyPair.Load(ms); - } - } - - public object ExportJwk(bool canonical = false) - { - // Note, we only produce a canonical form of the JWK - // for export therefore we ignore the canonical param - - if (_jwk == null) - { - _jwk = _keys.ExportJwk(); - - } - - return _jwk; - } - - public byte[] Sign(byte[] raw) - { - return _keys.Sign(raw); - } - - public bool Verify(byte[] raw, byte[] sig) - { - return _keys.Verify(raw, sig); - } - } -} diff --git a/Game.Engine/Controllers/RegistryController.cs b/Game.Engine/Controllers/RegistryController.cs new file mode 100644 index 00000000..d0b9fd95 --- /dev/null +++ b/Game.Engine/Controllers/RegistryController.cs @@ -0,0 +1,168 @@ +namespace Game.Engine.Controllers +{ + using Game.API.Client; + using Game.API.Common.Models; + using Game.API.Common.Models.Auditing; + using Game.API.Common.Security; + using Microsoft.AspNetCore.Authorization; + using Microsoft.AspNetCore.Mvc; + using Nest; + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + using System.Net.Http; + using System.Threading; + using System.Threading.Tasks; + + public class RegistryController : APIControllerBase + { + private readonly GameConfiguration Config; + + private readonly ElasticClient ElasticClient; + private static Dictionary Reports = new Dictionary(); + private DateTime LastCleaning = DateTime.MinValue; + private const int MAX_AGE = 10000; + + public RegistryController( + ISecurityContext securityContext, + GameConfiguration config, + ElasticClient elasticClient + ) : base(securityContext) + { + this.Config = config; + this.ElasticClient = elasticClient; + } + + [ + AllowAnonymous, + HttpGet + ] + public List GetList() + { + lock (Reports) + { + if (DateTime.Now.Subtract(LastCleaning).TotalMilliseconds > 2000) + { + LastCleaning = DateTime.Now; + + var stale = new List(); + + foreach (var key in Reports.Keys) + { + var report = Reports[key]; + if (DateTime.Now.Subtract(report.Received).TotalMilliseconds > MAX_AGE) + stale.Add(key); + } + + foreach (var key in stale) + Reports.Remove(key); + } + + return Reports.Values + .ToList(); + } + } + + [ + AllowAnonymous, + HttpGet, + Route("suggestion") + ] + public async Task SuggestDomainsAsync(string configuredName = null) + { + if (configuredName != null) + return configuredName; + else + return await RecommendHostNameAsync(); + } + + private async Task RecommendHostNameAsync() + { + // I should probably support some kind of x-forwarded for headers etc. + var ipAddress = ControllerContext.HttpContext.Connection.RemoteIpAddress.MapToIPv4().ToString(); + + if (Config.DisableSuggestionLookup) + return ipAddress; + + try + { + + var entry = await Dns.GetHostEntryAsync(ipAddress); + var apiClient = new APIClient(new Uri($"http://{ipAddress}")); + + var cts = new CancellationTokenSource(); + cts.CancelAfter(2000); + var server = await apiClient.Server.ServerGetAsync(cts.Token); + if (server == null) + { + Console.WriteLine($"Suggesting localhost to {ipAddress}"); + return "localhost"; + } + } + catch (Exception) + { + Console.WriteLine($"Suggesting localhost to {ipAddress}"); + return "localhost"; + } + + var address = $"daud-{ipAddress.Replace(".", "-")}.sslip.io"; + Console.WriteLine($"Suggesting {address} to {ipAddress}"); + + return address; + } + + [ + AllowAnonymous, + HttpPost, + Route("report") + ] + public async Task PostReportAsync([FromBody]RegistryReport registryReport) + { + if (registryReport != null) + { + if (registryReport.URL == null) + registryReport.URL = await RecommendHostNameAsync(); + + var url = registryReport.URL; + lock (Reports) + { + registryReport.Received = DateTime.Now; + if (Reports.ContainsKey(url)) + Reports[url] = registryReport; + else + Reports.Add(url, registryReport); + + return true; + } + } + else + return false; + } + + [ + AllowAnonymous, + HttpPost, + Route("events") + ] + public bool PostEvents([FromBody]IEnumerable events) + { + if (Config.ElasticSearchURI == null) + return false; + + var waitHandle = new CountdownEvent(1); + + var bulkAll = ElasticClient.BulkAll(events, e => e.Size(1000)); + + bulkAll.Subscribe(new BulkAllObserver( + onNext: (b) => { Console.Write("."); }, + onError: (e) => { throw e; }, + onCompleted: () => waitHandle.Signal() + )); + + waitHandle.Wait(); + + return true; + } + } +} \ No newline at end of file diff --git a/Game.Engine/Crypto/ArchiveFormat.cs b/Game.Engine/Crypto/ArchiveFormat.cs deleted file mode 100644 index edc363f0..00000000 --- a/Game.Engine/Crypto/ArchiveFormat.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Game.Engine.Crypto -{ - public enum ArchiveFormat - { - /// - /// The PCKS#12 (.PFX) format. - /// - PKCS12 = 3, - } -} \ No newline at end of file diff --git a/Game.Engine/Crypto/CertHelper.cs b/Game.Engine/Crypto/CertHelper.cs deleted file mode 100644 index 7ebc1058..00000000 --- a/Game.Engine/Crypto/CertHelper.cs +++ /dev/null @@ -1,488 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using SysHashAlgorName = System.Security.Cryptography.HashAlgorithmName; -using System.Text; -using Org.BouncyCastle.Asn1.X509; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Generators; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.OpenSsl; -using Org.BouncyCastle.Pkcs; -using Org.BouncyCastle.Security; -using Org.BouncyCastle.X509; -using BcCertificate = Org.BouncyCastle.X509.X509Certificate; -using Org.BouncyCastle.Asn1; -using System.Linq; -using Org.BouncyCastle.Asn1.Pkcs; -using Org.BouncyCastle.Crypto.Prng; -using Org.BouncyCastle.Utilities; - -namespace Game.Engine.Crypto -{ - /// - /// Wrapper class around a native BouncyCastle Asymmetric Key Pair. - /// - public class CertPrivateKey - { - public AsymmetricCipherKeyPair KeyPair { get; set; } - } - - /// - /// Collection of static routines for working with basic entities needs to - /// support X509 Certificate operations, including request generation, - /// private key management, standards-based serialization and export. - /// - /// - /// Unfortunately there is not yet enough out-of-the-box support for - /// general certificate management in .NET Standard, so we rely on a 3rd-party - /// library to handle most of this work for us, in this case the very capable - /// BouncyCastle C# library. - /// - public static class CertHelper - { - // Useful references and examples for BC: - // CSR: - // http://www.bouncycastle.org/wiki/display/JA1/X.509+Public+Key+Certificate+and+Certification+Request+Generation - // https://gist.github.com/Venomed/5337717aadfb61b09e58 - // http://codereview.stackexchange.com/questions/84752/net-bouncycastle-csr-and-private-key-generation - // Other: - // https://www.txedo.com/blog/java-read-rsa-keys-pem-file/ - - public const int RSA_BITS_DEFAULT = 2048; - public const int RSA_BITS_MINIMUM = 1024 + 1; // LE no longer allows 1024-bit PrvKeys - - public static readonly BigInteger RSA_E_3 = BigInteger.Three; - public static readonly BigInteger RSA_E_F4 = BigInteger.ValueOf(0x10001); - - // This is based on the BC RSA Generator code: - // https://github.com/bcgit/bc-csharp/blob/fba5af528ce7dcd0ac0513363196a62639b82a86/crypto/src/crypto/generators/RsaKeyPairGenerator.cs#L37 - const int DEFAULT_CERTAINTY = 100; - - public static CertPrivateKey GenerateRsaPrivateKey(int bits, string PubExp = null) - { - // Bits less than 1024 are weak Ref: http://openssl.org/docs/manmaster/crypto/RSA_generate_key_ex.html - if (bits < RSA_BITS_MINIMUM) - bits = RSA_BITS_DEFAULT; - - BigInteger e; - if (string.IsNullOrEmpty(PubExp)) - e = RSA_E_F4; - else if (PubExp.StartsWith("0x", StringComparison.OrdinalIgnoreCase)) - e = new BigInteger(PubExp, 16); - else - e = new BigInteger(PubExp); - - var rsaKgp = new RsaKeyGenerationParameters( - e, new SecureRandom(), bits, DEFAULT_CERTAINTY); - var rkpg = new RsaKeyPairGenerator(); - rkpg.Init(rsaKgp); - AsymmetricCipherKeyPair ackp = rkpg.GenerateKeyPair(); - - return new CertPrivateKey - { - KeyPair = ackp, - }; - } - - public static CertPrivateKey GenerateEcPrivateKey(int bits) - { - // From: - // http://www.bouncycastle.org/wiki/display/JA1/Elliptic+Curve+Key+Pair+Generation+and+Key+Factories - // var csr = new Pkcs10CertificationRequest() - // string curveName; - // switch (bits) - // { - // case 256: - // curveName = "P-256"; - // break; - // case 384: - // curveName = "P-384"; - // break; - // default: - // throw new ArgumentException("bit length is unsupported or unknown", nameof(bits)); - - // } - // var ecParamSpec = ECNamedCurveTable.GetByName(curveName); - - // From: - // https://www.codeproject.com/Tips/1150485/Csharp-Elliptical-Curve-Cryptography-with-Bouncy-C - var ecKpg = new ECKeyPairGenerator("ECDSA"); - ecKpg.Init(new KeyGenerationParameters(new SecureRandom(), bits)); - var ecKp = ecKpg.GenerateKeyPair(); - - return new CertPrivateKey - { - KeyPair = ecKp - }; - } - - - public static void ExportPrivateKey(CertPrivateKey pk, EncodingFormat fmt, Stream target) - { - switch (fmt) - { - case EncodingFormat.PEM: - var pem = ToPrivatePem(pk.KeyPair); - var bytes = Encoding.UTF8.GetBytes(pem); - target.Write(bytes, 0, bytes.Length); - break; - - case EncodingFormat.DER: - var der = PrivateKeyInfoFactory.CreatePrivateKeyInfo(pk.KeyPair.Private).GetDerEncoded(); - target.Write(der, 0, der.Length); - break; - - default: - throw new NotSupportedException("unsupported encoding format"); - } - } - - public static CertPrivateKey ImportPrivateKey(EncodingFormat fmt, Stream source) - { - if (fmt != EncodingFormat.PEM) - throw new NotSupportedException("Unsupported archive format"); - - using (var tr = new StreamReader(source)) - { - var pr = new PemReader(tr); - var pem = pr.ReadObject(); - var ackp = pem as AsymmetricCipherKeyPair; - - if (ackp != null) - { - var rsa = ackp.Private as RsaPrivateCrtKeyParameters; - if (rsa != null) - { - return new CertPrivateKey - { - KeyPair = ackp, - }; - } - } - - throw new InvalidDataException("could not read source as PEM private key"); - } - } - - public static byte[] GenerateRsaCsr(IEnumerable names, - CertPrivateKey pk, SysHashAlgorName? hashAlgor = null) - { - if (hashAlgor == null) - hashAlgor = SysHashAlgorName.SHA256; - - var attrs = new Dictionary - { - [X509Name.CN] = names.First(), - }; - var subj = new X509Name(attrs.Keys.ToList(), attrs.Values.ToList()); - - var ackp = pk.KeyPair; - - var sigAlg = $"{hashAlgor.Value.Name}withRSA"; - var csrAttrs = new List(); - - var gnames = new List( - names.Select(x => new GeneralName(GeneralName.DnsName, x))); - - var altNames = new GeneralNames(gnames.ToArray()); -#pragma warning disable CS0612 // Type or member is obsolete - var x509Ext = new X509Extensions(new Hashtable - { - [X509Extensions.SubjectAlternativeName] = new X509Extension( - false, new DerOctetString(altNames)) - }); -#pragma warning restore CS0612 // Type or member is obsolete - - csrAttrs.Add(new Org.BouncyCastle.Asn1.Cms.Attribute( - PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, - new DerSet(x509Ext))); - -#pragma warning disable CS0618 // Type or member is obsolete - var csr = new Pkcs10CertificationRequest(sigAlg, - subj, ackp.Public, new DerSet(csrAttrs.ToArray()), ackp.Private); -#pragma warning restore CS0618 // Type or member is obsolete - - return csr.GetDerEncoded(); - } - - - public static byte[] GenerateEcCsr(IEnumerable names, - CertPrivateKey pk, SysHashAlgorName? hashAlgor = null) - { - if (hashAlgor == null) - hashAlgor = SysHashAlgorName.SHA256; - - var attrs = new Dictionary - { - [X509Name.CN] = names.First(), - }; - var subj = new X509Name(attrs.Keys.ToList(), attrs.Values.ToList()); - - var ackp = pk.KeyPair; - - var sigAlg = $"{hashAlgor.Value.Name}withECDSA"; - var csrAttrs = new List(); - - var gnames = new List( - names.Select(x => new GeneralName(GeneralName.DnsName, x))); - - var altNames = new GeneralNames(gnames.ToArray()); -#pragma warning disable CS0612 // Type or member is obsolete - var x509Ext = new X509Extensions(new Hashtable - { - [X509Extensions.SubjectAlternativeName] = new X509Extension( - false, new DerOctetString(altNames)) - }); -#pragma warning restore CS0612 // Type or member is obsolete - - csrAttrs.Add(new Org.BouncyCastle.Asn1.Cms.Attribute( - PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, - new DerSet(x509Ext))); - -#pragma warning disable CS0618 // Type or member is obsolete - var csr = new Pkcs10CertificationRequest(sigAlg, - subj, ackp.Public, new DerSet(csrAttrs.ToArray()), ackp.Private); -#pragma warning restore CS0618 // Type or member is obsolete - - return csr.GetDerEncoded(); - } - - public static (CertPrivateKey, BcCertificate) GenerateRsaCACertificate(string subjectName, int keyStrength = 2048) - { - // Generating Random Numbers - var randomGenerator = new CryptoApiRandomGenerator(); - var random = new SecureRandom(randomGenerator); - - // The Certificate Generator - var certificateGenerator = new X509V3CertificateGenerator(); - - // Serial Number - var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random); - certificateGenerator.SetSerialNumber(serialNumber); - - // Signature Algorithm - const string signatureAlgorithm = "SHA256WithRSA"; -#pragma warning disable CS0618 // Type or member is obsolete - certificateGenerator.SetSignatureAlgorithm(signatureAlgorithm); -#pragma warning restore CS0618 // Type or member is obsolete - - // Issuer and Subject Name - var subjectDN = new X509Name(subjectName); - var issuerDN = subjectDN; - certificateGenerator.SetIssuerDN(issuerDN); - certificateGenerator.SetSubjectDN(subjectDN); - - // Valid For - var notBefore = DateTime.UtcNow.Date; - var notAfter = notBefore.AddYears(2); - - certificateGenerator.SetNotBefore(notBefore); - certificateGenerator.SetNotAfter(notAfter); - - // Subject Public Key - AsymmetricCipherKeyPair subjectKeyPair; - var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength); - var keyPairGenerator = new RsaKeyPairGenerator(); - keyPairGenerator.Init(keyGenerationParameters); - subjectKeyPair = keyPairGenerator.GenerateKeyPair(); - - certificateGenerator.SetPublicKey(subjectKeyPair.Public); - - // Generating the Certificate - var issuerKeyPair = subjectKeyPair; - - // selfsign certificate -#pragma warning disable CS0618 // Type or member is obsolete - var certificate = certificateGenerator.Generate(issuerKeyPair.Private, random); -#pragma warning restore CS0618 // Type or member is obsolete - - // var x509 = new System.Security.Cryptography.X509Certificates.X509Certificate2(certificate.GetEncoded()); - // // Add CA certificate to Root store - // addCertToStore(cert, StoreName.Root, StoreLocation.CurrentUser); - - var key = new CertPrivateKey - { - KeyPair = issuerKeyPair, - }; - - return (key, certificate); - } - - public static (CertPrivateKey, BcCertificate) GenerateRsaSelfSignedCertificate(string subjectName, string issuerName, - AsymmetricKeyParameter issuerPrivKey, int keyStrength = 2048) - { - // Generating Random Numbers - var randomGenerator = new CryptoApiRandomGenerator(); - var random = new SecureRandom(randomGenerator); - - // The Certificate Generator - var certificateGenerator = new X509V3CertificateGenerator(); - - // Serial Number - var serialNumber = BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(Int64.MaxValue), random); - certificateGenerator.SetSerialNumber(serialNumber); - - // Signature Algorithm - const string signatureAlgorithm = "SHA256WithRSA"; -#pragma warning disable CS0618 // Type or member is obsolete - certificateGenerator.SetSignatureAlgorithm(signatureAlgorithm); -#pragma warning restore CS0618 // Type or member is obsolete - - // Issuer and Subject Name - var subjectDN = new X509Name(subjectName); - var issuerDN = new X509Name(issuerName); - certificateGenerator.SetIssuerDN(issuerDN); - certificateGenerator.SetSubjectDN(subjectDN); - - // Valid For - var notBefore = DateTime.UtcNow.AddMonths(-1).Date; - var notAfter = DateTime.UtcNow.AddMonths(1).Date; - - certificateGenerator.SetNotBefore(notBefore); - certificateGenerator.SetNotAfter(notAfter); - - // Subject Public Key - AsymmetricCipherKeyPair subjectKeyPair; - var keyGenerationParameters = new KeyGenerationParameters(random, keyStrength); - var keyPairGenerator = new RsaKeyPairGenerator(); - keyPairGenerator.Init(keyGenerationParameters); - subjectKeyPair = keyPairGenerator.GenerateKeyPair(); - - certificateGenerator.SetPublicKey(subjectKeyPair.Public); - - // Generating the Certificate - var issuerKeyPair = subjectKeyPair; - - // selfsign certificate -#pragma warning disable CS0618 // Type or member is obsolete - var certificate = certificateGenerator.Generate(issuerPrivKey, random); -#pragma warning restore CS0618 // Type or member is obsolete - - var key = new CertPrivateKey - { - KeyPair = subjectKeyPair, - }; - return (key, certificate); - } - - public static System.Security.Cryptography.X509Certificates.X509Certificate2 - ToDotNetCert(CertPrivateKey key, BcCertificate certificate) - { - // merge into X509Certificate2 - - // correcponding private key - PrivateKeyInfo info = PrivateKeyInfoFactory.CreatePrivateKeyInfo(key.KeyPair.Private); - - var x509 = new System.Security.Cryptography.X509Certificates.X509Certificate2( - certificate.GetEncoded()); - -#pragma warning disable CS0618 // Type or member is obsolete - var seq = (Asn1Sequence)Asn1Object.FromByteArray(info.PrivateKey.GetDerEncoded()); -#pragma warning restore CS0618 // Type or member is obsolete - if (seq.Count != 9) - throw new PemException("malformed sequence in RSA private key"); - -#pragma warning disable CS0618 // Type or member is obsolete - var rsa = new RsaPrivateKeyStructure(seq); -#pragma warning restore CS0618 // Type or member is obsolete - RsaPrivateCrtKeyParameters rsaparams = new RsaPrivateCrtKeyParameters( - rsa.Modulus, rsa.PublicExponent, rsa.PrivateExponent, - rsa.Prime1, rsa.Prime2, rsa.Exponent1, rsa.Exponent2, rsa.Coefficient); - - x509.PrivateKey = DotNetUtilities.ToRSA(rsaparams); - return x509; - } - - public static BcCertificate ImportCertificate(EncodingFormat fmt, Stream source) - { - X509Certificate bcCert = null; - - if (fmt == EncodingFormat.DER) - { - var certParser = new X509CertificateParser(); - bcCert = certParser.ReadCertificate(source); - } - else if (fmt == EncodingFormat.PEM) - { - using (var tr = new StreamReader(source)) - { - var pr = new PemReader(tr); - bcCert = (X509Certificate)pr.ReadObject(); - } - } - else - { - throw new NotSupportedException("encoding format has not been implemented"); - } - - return bcCert; - } - - public static void ExportCertificate(BcCertificate cert, EncodingFormat fmt, Stream target) - { - if (fmt == EncodingFormat.PEM) - { - using (var tw = new StringWriter()) - { - var pw = new PemWriter(tw); - pw.WriteObject(cert); - var pemBytes = Encoding.UTF8.GetBytes(tw.GetStringBuilder().ToString()); - target.Write(pemBytes, 0, pemBytes.Length); - } - } - else if (fmt == EncodingFormat.DER) - { - var der = cert.GetEncoded(); - target.Write(der, 0, der.Length); - } - else - { - throw new NotSupportedException("unsupported encoding format"); - } - } - - public static void ExportArchive(CertPrivateKey pk, IEnumerable certs, - ArchiveFormat fmt, Stream target, string password = null) - { - if (fmt == ArchiveFormat.PKCS12) - { - var bcCerts = certs.Select(x => - new X509CertificateEntry(x)).ToArray(); - var pfx = new Pkcs12Store(); - pfx.SetCertificateEntry(bcCerts[0].Certificate.SubjectDN.ToString(), bcCerts[0]); - pfx.SetKeyEntry(bcCerts[0].Certificate.SubjectDN.ToString(), - new AsymmetricKeyEntry(pk.KeyPair.Private), new[] { bcCerts[0] }); - - for (int i = 1; i < bcCerts.Length; ++i) - { - //pfx.SetCertificateEntry(bcCerts[i].Certificate.SubjectDN.ToString(), - pfx.SetCertificateEntry(i.ToString(), bcCerts[i]); - } - - // It used to be pretty straight forward to export this... - pfx.Save(target, password?.ToCharArray(), new SecureRandom()); - } - else - { - throw new NotSupportedException("unsupported archive format"); - } - } - - private static string ToPrivatePem(AsymmetricCipherKeyPair ackp) - { - string pem; - using (var tw = new StringWriter()) - { - var pw = new PemWriter(tw); - pw.WriteObject(ackp.Private); - pem = tw.GetStringBuilder().ToString(); - tw.GetStringBuilder().Clear(); - } - - return pem; - } - } -} \ No newline at end of file diff --git a/Game.Engine/Crypto/EncodingFormat.cs b/Game.Engine/Crypto/EncodingFormat.cs deleted file mode 100644 index 7ac8d91f..00000000 --- a/Game.Engine/Crypto/EncodingFormat.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Game.Engine.Crypto -{ - public enum EncodingFormat - { - /// - /// Format encoding suitable for human-friendly printing. - /// - PRINT = 0, - - /// - /// PEM text encoding. - /// - PEM = 1, - - /// - /// DER binary encoding. - /// - DER = 2, - } -} \ No newline at end of file diff --git a/Game.Engine/Crypto/LetsEncrypt/AcmeExtensions.cs b/Game.Engine/Crypto/LetsEncrypt/AcmeExtensions.cs deleted file mode 100644 index cd1f39a0..00000000 --- a/Game.Engine/Crypto/LetsEncrypt/AcmeExtensions.cs +++ /dev/null @@ -1,55 +0,0 @@ -namespace Game.Engine.Crypto.LetsEncrypt -{ - using Microsoft.AspNetCore.Builder; - using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.DependencyInjection; - using System.Collections.Generic; - - public static class AcmeExtensions - { - public static IWebHostBuilder AddAcmeServices(this IWebHostBuilder builder, - IEnumerable dnsNames, - IEnumerable contactEmails = null, - bool acceptTos = false, - string rootDir = null) - { - var acmeOptions = new AcmeOptions - { - AccountContactEmails = contactEmails, - AcceptTermsOfService = acceptTos, - DnsNames = dnsNames, - }; - if (rootDir != null) - acmeOptions.AcmeRootDir = rootDir; - return AddAcmeServices(builder, acmeOptions); - } - - public static IWebHostBuilder AddAcmeServices(this IWebHostBuilder builder, AcmeOptions acmeOptions) - { - builder.ConfigureServices(services => - { - services.AddSingleton(acmeOptions); - services.AddSingleton(AcmeState.Instance); - services.AddHostedService(); - }); - builder.UseKestrel((context, options) => options.ConfigureHttpsDefaults(configOpts => - { - configOpts.ServerCertificateSelector = (cc, x) => - { - return AcmeState.Instance.Certificate; - }; - })); - return builder; - } - - public static IApplicationBuilder UseAcmeChallengeHandler(this IApplicationBuilder app) - { - app.Map($"/{AcmeHttp01ChallengeHandler.AcmeHttp01PathPrefix}", appBuilder => - { - appBuilder.Run(AcmeHttp01ChallengeHandler.HandleHttp01ChallengeRequest); - }); - - return app; - } - } -} \ No newline at end of file diff --git a/Game.Engine/Crypto/LetsEncrypt/AcmeHostedService.cs b/Game.Engine/Crypto/LetsEncrypt/AcmeHostedService.cs deleted file mode 100644 index 9dc54f55..00000000 --- a/Game.Engine/Crypto/LetsEncrypt/AcmeHostedService.cs +++ /dev/null @@ -1,599 +0,0 @@ -namespace Game.Engine.Crypto.LetsEncrypt -{ - using ACMESharp.Authorizations; - using ACMESharp.Protocol; - using ACMESharp.Protocol.Resources; - using Game.API.Client; - using Game.Engine.Common.PKI; - using Game.Engine.Crypto; - using Microsoft.Extensions.Hosting; - using Microsoft.Extensions.Logging; - using Newtonsoft.Json; - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Security.Cryptography.X509Certificates; - using System.Threading; - using System.Threading.Tasks; - - public class AcmeHostedService : IHostedService, IDisposable - { - private readonly ILogger _logger; - private readonly IServiceProvider _services; - private readonly AcmeOptions _options; - private readonly AcmeState _state; - private readonly RegistryClient _registryClient; - private readonly GameConfiguration _gameConfiguration; - private Timer _timer; - - private bool WarnedLocalMode = false; - - private readonly static string CERT_PASSWORD = "AHHH!Dauds!"; - - public AcmeHostedService(ILogger logger, - IServiceProvider services, - AcmeOptions options, AcmeState state, - RegistryClient registryClient, - GameConfiguration gameConfiguration) - { - _logger = logger; - _services = services; - _options = options; - _state = state; - _registryClient = registryClient; - _gameConfiguration = gameConfiguration; - } - - public Task StartAsync(CancellationToken cancellationToken) - { - - if (!_gameConfiguration.RegistryEnabled || !_gameConfiguration.LetsEncryptEnabled) - return Task.FromResult(0); - - Reinitialize(); - - _logger.LogInformation("Preparing to launch background task..."); - - // We delay for 5 seconds just to give other parts of - // the service (like request handling) to get in place - _timer = new Timer(DoTheWork, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(300)); - - return Task.FromResult(0); - } - - private void Reinitialize() - { - _logger.LogInformation("ACME Hosted Service is staring"); - - _state.RootDir = _gameConfiguration.ACMEStateDirectory; - if (_state.RootDir == null) - _state.RootDir = Path.Combine(Directory.GetCurrentDirectory(), _options.AcmeRootDir ?? "."); - - _state.ServiceDirectoryFile = Path.Combine(_state.RootDir, "00-ServiceDirectory.json"); - _state.TermsOfServiceFile = Path.Combine(_state.RootDir, "05-TermsOfService"); - _state.AccountFile = Path.Combine(_state.RootDir, "10-Account.json"); - _state.AccountKeyFile = Path.Combine(_state.RootDir, "15-AccountKey.json"); - _state.OrderFile = Path.Combine(_state.RootDir, "50-Order.json"); - _state.AuthorizationsFile = Path.Combine(_state.RootDir, "52-Authorizations.json"); - _state.CertificateKeysFile = Path.Combine(_state.RootDir, "70-CertificateKeys.pem"); - _state.CertificateRequestFile = Path.Combine(_state.RootDir, "72-CertificateRequest.der"); - _state.CertificateChainFile = Path.Combine(_state.RootDir, "74-CertificateChain.pem"); - _state.CertificateFile = Path.Combine(_state.RootDir, "80-Certificate.pfx"); - - if (!Directory.Exists(_state.RootDir)) - Directory.CreateDirectory(_state.RootDir); - - (_, _state.ServiceDirectory) = Load(_state.ServiceDirectoryFile); - (_, _state.Account) = Load(_state.AccountFile); - (_, _state.AccountKey) = Load(_state.AccountKeyFile); - (_, _state.Order) = Load(_state.OrderFile); - (_, _state.Authorizations) = Load>( - _state.AuthorizationsFile); - (_, var certRaw) = Load(_state.CertificateFile); - if (certRaw?.Length > 0) - _state.Certificate = new X509Certificate2(certRaw, CERT_PASSWORD); - - } - - public Task StopAsync(CancellationToken cancellationToken) - { - _logger.LogInformation("ACME Hosted Service is stopping"); - _timer?.Change(Timeout.Infinite, 0); - - _state.Certificate?.Dispose(); - - return Task.CompletedTask; - } - - protected void DoTheWork(object state) - { - var task = DoTheWorkAsync(state); - task.Wait(); - } - - protected async Task DoTheWorkAsync(object state) - { - var cts = new CancellationTokenSource(); - cts.CancelAfter(15000); - try - { - var suggestion = await _registryClient.Registry.SuggestAsync(_gameConfiguration.PublicURL, cts.Token); - - if (suggestion != "localhost") - _options.DnsNames = new[] { suggestion }; - else - { - if (!WarnedLocalMode) - _logger.LogWarning("registry reports we are not accessible on http TCP/80 on our public IP. If you're in development mode, this is fine."); - - WarnedLocalMode = true; - return; - } - } - catch (Exception e) - { - _logger.LogError("** failed to get suggestion from registry: " + e); - } - - if (_options.DnsNames == null) - return; - - _logger.LogInformation("** Checking LetsEncrypt status *****************************************"); - _logger.LogInformation($"DNS Names: {string.Join(",", _options.DnsNames)}"); - - if (_state.Certificate != null) - { - var now = DateTime.Now; - if (_state.Certificate.NotAfter > now) - { - _logger.LogInformation("Existing certificate is Good!"); - return; - } - else - { - _logger.LogWarning($"Existing Certificate is Expired! {_state.Certificate.NotAfter} > {now}"); - } - } - else - { - _logger.LogWarning("Missing Certificate"); - } - - try - { - Directory.Delete(_state.RootDir, true); - } - catch (Exception) { } - Reinitialize(); - - - try - { - var acmeUrl = new Uri(_options.CaUrl); - using (var acme = new AcmeProtocolClient(acmeUrl)) - { - _state.ServiceDirectory = await acme.GetDirectoryAsync(); - - Save(_state.ServiceDirectoryFile, _state.ServiceDirectory); - acme.Directory = _state.ServiceDirectory; - - Save(_state.TermsOfServiceFile, - await acme.GetTermsOfServiceAsync()); - - await acme.GetNonceAsync(); - - if (!await ResolveAccount(acme)) - return; - - if (!await ResolveOrder(acme)) - return; - - if (!await ResolveChallenges(acme)) - return; - - if (!await ResolveAuthorizations(acme)) - return; - - if (!await ResolveCertificate(acme)) - return; - } - } - catch (Exception e) - { - _logger.LogWarning($"Exception while getting SSL Certificate: {e}"); - } - } - - protected async Task ResolveAccount(AcmeProtocolClient acme) - { - // TODO: All this ASSUMES a fixed key type/size for now - if (_state.Account == null || _state.AccountKey == null) - { - var contacts = _options.AccountContactEmails.Select(x => $"mailto:{x}"); - _logger.LogInformation("Creating ACME Account"); - _state.Account = await acme.CreateAccountAsync( - contacts: contacts, - termsOfServiceAgreed: _options.AcceptTermsOfService); - _state.AccountKey = new AccountKey - { - KeyType = acme.Signer.JwsAlg, - KeyExport = acme.Signer.Export(), - }; - Save(_state.AccountFile, _state.Account); - Save(_state.AccountKeyFile, _state.AccountKey); - acme.Account = _state.Account; - } - else - { - acme.Account = _state.Account; - acme.Signer.Import(_state.AccountKey.KeyExport); - } - - return true; - } - - protected async Task ResolveOrder(AcmeProtocolClient acme) - { - var now = DateTime.Now; - if (!string.IsNullOrEmpty(_state.Order?.OrderUrl)) - { - _logger.LogInformation("Existing Order found; refreshing"); - _state.Order = await acme.GetOrderDetailsAsync( - _state.Order.OrderUrl, _state.Order); - } - - if (_state.Order?.Payload?.Error != null) - { - _logger.LogWarning("Existing Order reported an Error:"); - _logger.LogWarning(JsonConvert.SerializeObject(_state.Order.Payload.Error)); - _logger.LogWarning("Resetting existing order"); - _state.Order = null; - } - - if (AcmeState.InvalidStatus == _state.Order?.Payload.Status) - { - _logger.LogInformation("Existing Order is INVALID; resetting"); - _state.Order = null; - } - - if (!DateTime.TryParse(_state.Order?.Payload?.Expires, out var orderExpires) - || orderExpires < now) - { - _logger.LogInformation("Existing Order is EXPIRED; resetting"); - _state.Order = null; - } - - if (DateTime.TryParse(_state.Order?.Payload?.NotAfter, out var orderNotAfter) - && orderNotAfter < now) - { - _logger.LogInformation("Existing Order is OUT-OF-DATE; resetting"); - _state.Order = null; - } - - if (_state.Order?.Payload == null) - { - _logger.LogInformation("Creating NEW Order"); - _state.Order = await acme.CreateOrderAsync(_options.DnsNames); - } - - Save(_state.OrderFile, _state.Order); - return true; - } - - protected async Task ResolveChallenges(AcmeProtocolClient acme) - { - if (AcmeState.PendingStatus == _state.Order?.Payload?.Status) - { - _logger.LogInformation("Order is pending, resolving Authorizations"); - if (_state.Authorizations == null) - _state.Authorizations = new Dictionary(); - foreach (var authzUrl in _state.Order.Payload.Authorizations) - { - var authz = await acme.GetAuthorizationDetailsAsync(authzUrl); - _state.Authorizations[authzUrl] = authz; - - if (AcmeState.PendingStatus == authz.Status) - { - foreach (var chlng in authz.Challenges) - { - if (string.IsNullOrEmpty(_options.ChallengeType) - || _options.ChallengeType == chlng.Type) - { - var chlngValidation = AuthorizationDecoder.DecodeChallengeValidation( - authz, chlng.Type, acme.Signer); - if (_options.ChallengeHandler(_services, chlngValidation)) - { - _logger.LogInformation("Challenge Handler has handled challenge:"); - _logger.LogInformation(JsonConvert.SerializeObject(chlngValidation, Formatting.Indented)); - var chlngUpdated = await acme.AnswerChallengeAsync(chlng.Url); - if (chlngUpdated.Error != null) - { - _logger.LogError("Submitting Challenge Answer reported an error:"); - _logger.LogError(JsonConvert.SerializeObject(chlngUpdated.Error)); - } - } - - _logger.LogInformation("Refreshing Authorization status"); - authz = await acme.GetAuthorizationDetailsAsync(authzUrl); - if (AcmeState.PendingStatus != authz.Status) - break; - } - } - } - } - Save(_state.AuthorizationsFile, _state.Authorizations); - - _logger.LogInformation("Refreshing Order status"); - _state.Order = await acme.GetOrderDetailsAsync(_state.Order.OrderUrl, _state.Order); - Save(_state.OrderFile, _state.Order); - } - return true; - } - - protected async Task ResolveAuthorizations(AcmeProtocolClient acme) - { - if (AcmeState.InvalidStatus == _state.Order?.Payload?.Status) - { - _logger.LogError("Current Order is INVALID; aborting"); - _logger.LogInformation(JsonConvert.SerializeObject(_state.Order, Formatting.Indented)); - - return false; - } - - if (AcmeState.ValidStatus == _state.Order?.Payload?.Status) - { - _logger.LogError("Current Order is already VALID; skipping"); - return true; - } - - var now = DateTime.Now; - do - { - // Wait for all Authorizations to be valid or any one to go invalid - int validCount = 0; - int invalidCount = 0; - foreach (var authz in _state.Authorizations) - { - switch (authz.Value.Status) - { - case AcmeState.ValidStatus: - ++validCount; - break; - case AcmeState.InvalidStatus: - ++invalidCount; - break; - } - } - - if (validCount == _state.Authorizations.Count) - { - _logger.LogInformation("All Authorizations ({0}) are valid", validCount); - break; - } - - if (invalidCount > 0) - { - _logger.LogError("Found {0} invalid Authorization(s); ABORTING", invalidCount); - - _logger.LogError(JsonConvert.SerializeObject(_state)); - return false; - } - - _logger.LogWarning("Found {0} Authorization(s) NOT YET valid", - _state.Authorizations.Count - validCount); - - if (now.AddSeconds(_options.WaitForAuthorizations) < DateTime.Now) - { - _logger.LogError("Timed out waiting for Authorizations; ABORTING"); - return false; - } - - // We wait in 5s increments - await Task.Delay(5000); - foreach (var authzUrl in _state.Order.Payload.Authorizations) - { - // Update all the Authorizations still pending - if (AcmeState.PendingStatus == _state.Authorizations[authzUrl].Status) - _state.Authorizations[authzUrl] = - await acme.GetAuthorizationDetailsAsync(authzUrl); - } - } while (true); - - return true; - } - - protected async Task ResolveCertificate(AcmeProtocolClient acme) - { - if (_state.Certificate != null) - { - _logger.LogInformation("Certificate is already resolved"); - return true; - } - - CertPrivateKey key = null; - _logger.LogInformation("Refreshing Order status"); - _state.Order = await acme.GetOrderDetailsAsync(_state.Order.OrderUrl, _state.Order); - Save(_state.OrderFile, _state.Order); - - if (AcmeState.PendingStatus == _state.Order.Payload.Status - || AcmeState.ReadyStatus == _state.Order.Payload.Status) - { - _logger.LogInformation("Generating CSR"); - byte[] csr; - switch (_options.CertificateKeyAlgor) - { - case "rsa": - key = CertHelper.GenerateRsaPrivateKey( - _options.CertificateKeySize ?? AcmeOptions.DefaultRsaKeySize); - csr = CertHelper.GenerateRsaCsr(_options.DnsNames, key); - break; - case "ec": - key = CertHelper.GenerateEcPrivateKey( - _options.CertificateKeySize ?? AcmeOptions.DefaultEcKeySize); - csr = CertHelper.GenerateEcCsr(_options.DnsNames, key); - break; - default: - throw new Exception("Unknown Certificate Key Algorithm: " - + _options.CertificateKeyAlgor); - } - - using (var keyPem = new MemoryStream()) - { - CertHelper.ExportPrivateKey(key, EncodingFormat.PEM, keyPem); - keyPem.Position = 0L; - Save(_state.CertificateKeysFile, keyPem); - } - Save(_state.CertificateRequestFile, csr); - - _logger.LogInformation("Finalizing Order"); - _state.Order = await acme.FinalizeOrderAsync(_state.Order.Payload.Finalize, csr); - Save(_state.OrderFile, _state.Order); - } - - if (AcmeState.ValidStatus != _state.Order.Payload.Status) - { - _logger.LogWarning("Order is NOT VALID"); - _logger.LogInformation(JsonConvert.SerializeObject(_state.Order, Formatting.Indented)); - return false; - } - - if (string.IsNullOrEmpty(_state.Order.Payload.Certificate)) - { - _logger.LogWarning("Order Certificate is NOT READY YET"); - var now = DateTime.Now; - do - { - _logger.LogInformation("Waiting..."); - // We wait in 5s increments - await Task.Delay(5000); - - _state.Order = await acme.GetOrderDetailsAsync(_state.Order.OrderUrl, _state.Order); - Save(_state.OrderFile, _state.Order); - - if (!string.IsNullOrEmpty(_state.Order.Payload.Certificate)) - break; - - if (DateTime.Now < now.AddSeconds(_options.WaitForCertificate)) - { - _logger.LogWarning("Timed Out!"); - return false; - } - } while (true); - } - - _logger.LogInformation("Retreiving Certificate"); - var certBytes = await acme.GetOrderCertificateAsync(_state.Order); - Save(_state.CertificateChainFile, certBytes); - - if (key == null) - { - _logger.LogInformation("Loading private key"); - key = CertHelper.ImportPrivateKey(EncodingFormat.PEM, - Load(_state.CertificateKeysFile).value); - } - - using (var crtStream = new MemoryStream(certBytes)) - using (var pfxStream = new MemoryStream()) - { - _logger.LogInformation("Reading in Certificate chain (PEM)"); - var cert = CertHelper.ImportCertificate(EncodingFormat.PEM, crtStream); - _logger.LogInformation("Writing out Certificate archive (PKCS12)"); - CertHelper.ExportArchive(key, new[] { cert }, ArchiveFormat.PKCS12, pfxStream, CERT_PASSWORD); - pfxStream.Position = 0L; - Save(_state.CertificateFile, pfxStream); - } - - _logger.LogInformation("Loading PKCS12 archive as active certificate"); - _logger.LogInformation(JsonConvert.SerializeObject(_state, Formatting.Indented)); - - (var exists, var value) = Load(_state.CertificateFile); - - _logger.LogInformation($"Loading cert: exists: {exists} value[{value.Length}]"); - - _state.Certificate = new X509Certificate2(value, CERT_PASSWORD); - - return true; - } - - protected (bool exists, T value) Load(string path, T def = default(T)) - { - if (!File.Exists(path)) - { - return (false, def); - } - - if (typeof(T) == typeof(Stream)) - { - return (true, (T)(object)new FileStream(path, FileMode.Open)); - } - - if (typeof(T) == typeof(byte[])) - { - return (true, (T)(object)File.ReadAllBytes(path)); - } - - return (true, JsonConvert.DeserializeObject( - File.ReadAllText(path))); - } - - protected void Save(string path, T value) - { - if (typeof(Stream).IsAssignableFrom(typeof(T))) - { - var rs = (Stream)(object)value; - using (var ws = new FileStream(path, FileMode.Create)) - { - rs.CopyTo(ws); - } - } - else if (typeof(T) == typeof(byte[])) - { - var ba = (byte[])(object)value; - File.WriteAllBytes(path, ba); - } - else - { - File.WriteAllText(path, - JsonConvert.SerializeObject(value, Formatting.Indented)); - } - } - - #region IDisposable Support - public bool IsDisposed { get; private set; } // To detect redundant calls - - protected virtual void Dispose(bool disposing) - { - if (!IsDisposed) - { - if (disposing) - { - // TODO: dispose managed state (managed objects). - _timer?.Dispose(); - _timer = null; - } - - // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. - // TODO: set large fields to null. - - IsDisposed = true; - } - } - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~AcmeHostedService() { - // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - // Dispose(false); - // } - - // This code added to correctly implement the disposable pattern. - public void Dispose() - { - // Do not change this code. Put cleanup code in Dispose(bool disposing) above. - Dispose(true); - // TODO: uncomment the following line if the finalizer is overridden above. - // GC.SuppressFinalize(this); - } - #endregion - } -} \ No newline at end of file diff --git a/Game.Engine/Crypto/LetsEncrypt/AcmeHttp01ChallengeHandler.cs b/Game.Engine/Crypto/LetsEncrypt/AcmeHttp01ChallengeHandler.cs deleted file mode 100644 index e1b53c40..00000000 --- a/Game.Engine/Crypto/LetsEncrypt/AcmeHttp01ChallengeHandler.cs +++ /dev/null @@ -1,55 +0,0 @@ -namespace Game.Engine.Crypto.LetsEncrypt -{ - using System; - using System.Net; - using System.Threading.Tasks; - using ACMESharp.Authorizations; - using Microsoft.AspNetCore.Http; - using Microsoft.Extensions.DependencyInjection; - using Microsoft.Extensions.Logging; - - public class AcmeHttp01ChallengeHandler - { - public static readonly string AcmeHttp01PathPrefix = - ACMESharp.Authorizations.Http01ChallengeValidationDetails.HttpPathPrefix.Trim('/'); - - public static async Task HandleHttp01ChallengeRequest(HttpContext context) - { - var logger = context.RequestServices.GetService>(); - var state = context.RequestServices.GetService(); - var fullPath = $"{context.Request.PathBase}{context.Request.Path}".Trim('/'); - - logger.LogInformation("Running ACME Challenge Request Handler"); - if (state.Http01Responses.TryGetValue(fullPath, out var httpDetails)) - { - logger.LogInformation("Found match on [{0}] with response [{1}]", - fullPath, httpDetails.HttpResourceValue); - context.Response.ContentType = httpDetails.HttpResourceContentType; - await context.Response.WriteAsync(httpDetails.HttpResourceValue); - } - else - { - logger.LogInformation("NO MATCH FOUND ON [{0}]", fullPath); - context.Response.StatusCode = (int)HttpStatusCode.NotFound; - await context.Response.WriteAsync("No matching ACME response path"); - } - } - - public static bool AddChallengeHandling(IServiceProvider services, IChallengeValidationDetails chlngDetails) - { - var logger = services.GetService>(); - var state = services.GetService(); - var httpDetails = chlngDetails as Http01ChallengeValidationDetails; - if (httpDetails == null) - { - logger.LogInformation("Unable to handle non-Http01 Challenge details"); - return false; - } - - var fullPath = httpDetails.HttpResourcePath.Trim('/'); - logger.LogInformation("Handling Challenges with HTTP full path of [{0}]", fullPath); - state.Http01Responses[fullPath] = httpDetails; - return true; - } - } -} \ No newline at end of file diff --git a/Game.Engine/Crypto/LetsEncrypt/AcmeOptions.cs b/Game.Engine/Crypto/LetsEncrypt/AcmeOptions.cs deleted file mode 100644 index b4976c4a..00000000 --- a/Game.Engine/Crypto/LetsEncrypt/AcmeOptions.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Game.Engine.Crypto.LetsEncrypt -{ - using ACMESharp.Authorizations; - using System; - using System.Collections.Generic; - - public class AcmeOptions - { - public const string LetsEncryptV2StagingEndpoint = "https://acme-staging-v02.api.letsencrypt.org/"; - - public const string LetsEncryptV2Endpoint = "https://acme-v02.api.letsencrypt.org/"; - - public const int DefaultRsaKeySize = 2048; - public const int DefaultEcKeySize = 256; - - public string AcmeRootDir { get; set; } = "_acmesharp"; - - public string CaUrl { get; set; } = LetsEncryptV2Endpoint; - - public string AccountKeyAlgor { get; set; } = "ec"; - - public int? AccountKeySize { get; set; } - - public IEnumerable AccountContactEmails { get; set; } - - public bool AcceptTermsOfService { get; set; } - - public IEnumerable DnsNames { get; set; } - - public string ChallengeType { get; } = AcmeState.Http01ChallengeType; - - public Func ChallengeHandler { get; set; } - = AcmeHttp01ChallengeHandler.AddChallengeHandling; - - public bool TestChallenges { get; } - - public string CertificateKeyAlgor { get; set; } = "ec"; - - public int? CertificateKeySize { get; set; } - - public int WaitForAuthorizations { get; set; } = 60; - - public int WaitForCertificate { get; set; } = 60; - } -} \ No newline at end of file diff --git a/Game.Engine/Crypto/LetsEncrypt/AcmeState.cs b/Game.Engine/Crypto/LetsEncrypt/AcmeState.cs deleted file mode 100644 index c96cf3b1..00000000 --- a/Game.Engine/Crypto/LetsEncrypt/AcmeState.cs +++ /dev/null @@ -1,60 +0,0 @@ -namespace Game.Engine.Crypto.LetsEncrypt -{ - using ACMESharp.Authorizations; - using ACMESharp.Protocol; - using ACMESharp.Protocol.Resources; - using Game.Engine.Common.PKI; - using System.Collections.Generic; - using System.Security.Cryptography.X509Certificates; - - public class AcmeState - { - public static AcmeState Instance { get; private set; } = new AcmeState(); - - public const string PendingStatus = "pending"; - public const string ValidStatus = "valid"; - public const string ReadyStatus = "ready"; - public const string InvalidStatus = "invalid"; - - public const string Http01ChallengeType = "http-01"; - public const string Dns01ChallengeType = "dns-01"; - - public string RootDir { get; set; } - - public string ServiceDirectoryFile { get; set; } - - public ServiceDirectory ServiceDirectory { get; set; } - - public string TermsOfServiceFile { get; set; } - - public string AccountFile { get; set; } - - public AccountDetails Account { get; set; } - - public string AccountKeyFile { get; set; } - - public AccountKey AccountKey { get; set; } - - public string OrderFile { get; set; } - - public OrderDetails Order { get; set; } - - public string AuthorizationsFile { get; set; } - - public Dictionary Authorizations { get; set; } - - public string CertificateKeysFile { get; set; } - - public string CertificateRequestFile { get; set; } - - public string CertificateChainFile { get; set; } - - public string CertificateFile { get; set; } - - public X509Certificate2 Certificate { get; set; } - - public IDictionary Http01Responses { get; set; } - = new Dictionary(); - - } -} \ No newline at end of file diff --git a/Game.Engine/Game.Engine.csproj b/Game.Engine/Game.Engine.csproj index fede8c48..00d35f12 100644 --- a/Game.Engine/Game.Engine.csproj +++ b/Game.Engine/Game.Engine.csproj @@ -1,21 +1,25 @@  - netcoreapp2.1 + netcoreapp5.0 latest + true - - - - - + + + + + + + - - - - + + + + + @@ -23,7 +27,6 @@ - @@ -37,8 +40,4 @@ PreserveNewest - - - - diff --git a/Game.Engine/GameConfiguration.cs b/Game.Engine/GameConfiguration.cs index e11f81bf..22749064 100644 --- a/Game.Engine/GameConfiguration.cs +++ b/Game.Engine/GameConfiguration.cs @@ -8,8 +8,6 @@ public class GameConfiguration public int TokenExpirationSeconds { get; set; } = 100000; public string AdministratorPassword { get; set; } - public string ACMEStateDirectory { get; set; } - public string DiscordToken { get; set; } = null; public ulong? DiscordGuildID { get; set; } = null; @@ -19,15 +17,16 @@ public class GameConfiguration public bool NoWorlds { get; set; } = false; - public bool RegistryEnabled { get; set; } + public bool RegistryEnabled { get; set; } = false; public string RegistryUri { get; set; } public string RegistryUserKey { get; set; } public string RegistryPassword { get; set; } - public bool LetsEncryptEnabled { get; set; } - public string PublicURL { get; set; } public string DuelBotURL { get; set; } = "https://daud-discord.glitch.me/"; + + public string ElasticSearchURI { get; set; } + public bool DisableSuggestionLookup { get; set; } = false; } } diff --git a/Game.Engine/Hosting/DockerUpgrade.cs b/Game.Engine/Hosting/DockerUpgrade.cs deleted file mode 100644 index 4352caab..00000000 --- a/Game.Engine/Hosting/DockerUpgrade.cs +++ /dev/null @@ -1,110 +0,0 @@ -namespace Game.Engine.Hosting -{ - using Docker.DotNet; - using Docker.DotNet.Models; - using System; - using System.Threading.Tasks; - - public class DockerUpgrade - { - public static async Task UpgradeAsync(GameConfiguration gameConfiguration, string tag, Func status) - { - // for this to work, the container must be launched with - // a mount for the docker socket - // -v /var/run/docker.sock:/var/run/docker.sock - - using (DockerClient client = new DockerClientConfiguration( - new Uri("unix:///var/run/docker.sock")) - .CreateClient()) - { - - var container = await client.Containers.InspectContainerAsync(Environment.MachineName); - - var config = container.Config; - var oldImage = config.Image; - - await status($"{gameConfiguration.PublicURL} pulling image"); - - ImagesCreateParameters imagesCreateParameters = new ImagesCreateParameters(); - - if (tag.IndexOf(':') == -1) - { - imagesCreateParameters.FromImage = "iodaud/daud"; - imagesCreateParameters.Tag = tag; - } - else - { - var parts = tag.Split(':'); - imagesCreateParameters.FromImage = parts[0]; - imagesCreateParameters.Tag = parts[1]; - - } - - await client.Images.CreateImageAsync(imagesCreateParameters, null, new Progress()); - - config.Image = $"{imagesCreateParameters.FromImage}:{imagesCreateParameters.Tag}"; - config.WorkingDir = null; - - var createContainerParameters = new CreateContainerParameters(config) - { - HostConfig = container.HostConfig - }; - - createContainerParameters.Hostname = null; - var response = await client.Containers.CreateContainerAsync(createContainerParameters); - - await status($"{gameConfiguration.PublicURL} {oldImage}->{config.Image}"); - - - var newID = response.ID; - var oldID = container.ID; - - // would be nice if we could just start and stop the containers here... - // but there's a huge gotcha. this container is exposing the same ports - // that the new one will. They cannot be running at the same time. - // So we use a 3rd container with no hostconfig (portmapping) - // to do the switcheroo - createContainerParameters.HostConfig.PortBindings = null; - - createContainerParameters.Env.Add("GAME_DOCKER_UPGRADE=1"); - createContainerParameters.Env.Add("GAME_DOCKER_UPGRADE_OLD=" + oldID); - createContainerParameters.Env.Add("GAME_DOCKER_UPGRADE_NEW=" + newID); - createContainerParameters.HostConfig.AutoRemove = true; - createContainerParameters.HostConfig.RestartPolicy = null; - - response = await client.Containers.CreateContainerAsync(createContainerParameters); - - await client.Containers.StartContainerAsync(response.ID, new ContainerStartParameters { }); - } - } - - public static async Task FinalSwitchAsync(GameConfiguration gameConfiguration, string oldID, string newID) - { - using (DockerClient client = new DockerClientConfiguration( - new Uri("unix:///var/run/docker.sock")) - .CreateClient()) - { - try - { - // stop old container - await client.Containers.StopContainerAsync(oldID, new ContainerStopParameters - { - WaitBeforeKillSeconds = 1 - }); - // start new contianer - await client.Containers.StartContainerAsync(newID, new ContainerStartParameters { }); - // delete old container - await client.Containers.RemoveContainerAsync(oldID, new ContainerRemoveParameters - { - Force = true - }); - } - catch (Exception) - { - // uh-oh, try to restart the old container - await client.Containers.StartContainerAsync(oldID, new ContainerStartParameters { }); - } - } - } - } -} diff --git a/Game.Engine/Program.cs b/Game.Engine/Program.cs index 7fd3198c..bed1a474 100644 --- a/Game.Engine/Program.cs +++ b/Game.Engine/Program.cs @@ -1,12 +1,8 @@ namespace Game.Engine { using Game.Engine.ChatBot; - using Game.Engine.Crypto.LetsEncrypt; - using Game.Engine.Hosting; using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; - using Microsoft.Extensions.Configuration; - using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; @@ -23,7 +19,7 @@ public static void Abort() public async static Task Main(string[] args) { - var host = (await CreateWebHostBuilderAsync(args)).Build(); + var host = CreateWebHostBuilder(args).Build(); if (cts.IsCancellationRequested) return; @@ -34,51 +30,24 @@ public async static Task Main(string[] args) await host.RunAsync(cts.Token); } - public async static Task CreateWebHostBuilderAsync(string[] args) + public static IWebHostBuilder CreateWebHostBuilder(string[] args) { ThreadPool.SetMinThreads(50, 50); var builder = WebHost.CreateDefaultBuilder(args); - var config = new GameConfiguration(); - Configuration.Load("config", instance: config); + var configContext = Configuration.Load("config"); - if (Environment.GetEnvironmentVariable("GAME_DOCKER_UPGRADE") != null) - { - var oldID = Environment.GetEnvironmentVariable("GAME_DOCKER_UPGRADE_OLD"); - var newID = Environment.GetEnvironmentVariable("GAME_DOCKER_UPGRADE_NEW"); - - await DockerUpgrade.FinalSwitchAsync(config, oldID, newID); - Program.Abort(); - } + builder.UseConfiguration(configContext.ConfigurationRoot); var port = System.Environment.GetEnvironmentVariable("PORT"); - if (!string.IsNullOrEmpty(port)) - { builder.UseUrls($"http://*:{port}"); - } - else - builder.UseConfiguration(new ConfigurationBuilder() - .AddJsonFile("hosting.json", optional: true) - .Build() - ); builder .UseWebRoot("wwwroot/dist") .UseStartup(); - - if (config.LetsEncryptEnabled && config.RegistryEnabled) - // Full Form with access to All Options: - builder.AddAcmeServices(new AcmeOptions - { - AcmeRootDir = "_IGNORE/_acmesharp", - AccountContactEmails = new[] { "info@daud.io" }, - AcceptTermsOfService = true, - CertificateKeyAlgor = "rsa", - }); - return builder; } } diff --git a/Game.Engine/Startup.cs b/Game.Engine/Startup.cs index 2e57251b..fcfea5ad 100644 --- a/Game.Engine/Startup.cs +++ b/Game.Engine/Startup.cs @@ -9,7 +9,6 @@ using Game.Engine.Authentication; using Game.Engine.ChatBot; using Game.Engine.Core; - using Game.Engine.Crypto.LetsEncrypt; using Game.Engine.Networking; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -18,6 +17,10 @@ using Newtonsoft.Json; using System; using System.Net.Http; + using Microsoft.AspNetCore.Mvc.NewtonsoftJson; + using Elasticsearch.Net; + using Nest; + using Nest.JsonNetSerializer; public class Startup { @@ -33,8 +36,8 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(); services.AddScoped(); - services.AddMvc(). - AddJsonOptions(options => + services.AddMvc(options => options.EnableEndpointRouting = false). + AddNewtonsoftJson(options => { options.SerializerSettings.Formatting = Newtonsoft.Json.Formatting.Indented; options.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Serialize; @@ -47,10 +50,11 @@ public void ConfigureServices(IServiceCollection services) builder => builder.AllowAnyOrigin() .AllowAnyMethod() .AllowAnyHeader() - .AllowCredentials() ); }); + services.AddResponseCompression(); + services .AddSingleton() .AddSingleton() @@ -62,6 +66,22 @@ public void ConfigureServices(IServiceCollection services) services.AddSingleton(new RegistryClient(new Uri(config.RegistryUri))); if (config.RegistryEnabled) services.AddSingleton(); + + services + .AddSingleton(); + + if (config.ElasticSearchURI != null) + { + // choose the appropriate IConnectionPool for your use case + var pool = new SingleNodeConnectionPool(new Uri(config.ElasticSearchURI)); + var connectionSettings = + new ConnectionSettings(pool, JsonNetSerializer.Default) + .DefaultIndex("daud"); + services.AddSingleton(new ElasticClient(connectionSettings)); + } + else + services.AddSingleton(new ElasticClient()); + } private GameConfiguration LoadConfiguration(IServiceCollection services) @@ -75,7 +95,7 @@ private GameConfiguration LoadConfiguration(IServiceCollection services) public void Configure( IApplicationBuilder app, - IHostingEnvironment env, + IWebHostEnvironment env, IServiceProvider provider, GameConfiguration config, RegistryClient registryClient @@ -92,6 +112,7 @@ RegistryClient registryClient }; app.UseAuthentication(); + app.UseResponseCompression(); app.UseMvc(); @@ -115,7 +136,6 @@ RegistryClient registryClient } } }); - app.UseAcmeChallengeHandler(); app.UseWebSockets(new WebSocketOptions { @@ -133,4 +153,4 @@ RegistryClient registryClient } } } -} +} \ No newline at end of file diff --git a/Game.Engine/appsettings.json b/Game.Engine/appsettings.json index e68b97b5..005c11fe 100644 --- a/Game.Engine/appsettings.json +++ b/Game.Engine/appsettings.json @@ -1,8 +1,8 @@ { "config": { - "RegistryEnabled": true, - "LetsEncryptEnabled": true, - "RegistryUri": "http://daud.io:5001", + "RegistryEnabled": false, + "LetsEncryptEnabled": false, + "RegistryUri": "https://registry.daud.io/", "RegistryUserKey": "Administrator", "RegistryPassword": null }, diff --git a/Game.Engine/build.cmd b/Game.Engine/build.cmd index faf54f53..2eada0a1 100644 --- a/Game.Engine/build.cmd +++ b/Game.Engine/build.cmd @@ -5,7 +5,7 @@ call npm run build popd rmdir /s /q bin\Release dotnet publish -c release -pushd bin\Release\netcoreapp2.1\publish +pushd bin\Release\netcoreapp5.0\publish docker build . -t andylippitt/iogame:%1 docker push andylippitt/iogame:%1 popd diff --git a/Game.Engine/wwwroot/css/game.css b/Game.Engine/wwwroot/css/game.css index 2e567e97..807be13f 100644 --- a/Game.Engine/wwwroot/css/game.css +++ b/Game.Engine/wwwroot/css/game.css @@ -457,7 +457,6 @@ body { } .change-log-popup .header { background: #000; - position: fixed; width: inherit; height: 60px; } @@ -1278,4 +1277,13 @@ footer .div_add_blocked { -moz-box-shadow: 0px 0px 30px 0px #c0186f; box-shadow: 0px 0px 30px 0px #c0186f; animation: none; +} + +#betainfo +{ + color: white +} +#betainfo:visited +{ + color: white } \ No newline at end of file diff --git a/Game.Engine/wwwroot/index.html b/Game.Engine/wwwroot/index.html index 90808fc8..19a26368 100644 --- a/Game.Engine/wwwroot/index.html +++ b/Game.Engine/wwwroot/index.html @@ -246,9 +246,21 @@

Leaderboard

+
- This remake is still in development. Join here for more info. + This remake is still in development. Click here for more info.
diff --git a/Game.Engine/wwwroot/js/hintbox.ts b/Game.Engine/wwwroot/js/hintbox.ts index f6944d8e..a3b88e9b 100644 --- a/Game.Engine/wwwroot/js/hintbox.ts +++ b/Game.Engine/wwwroot/js/hintbox.ts @@ -43,4 +43,13 @@ $("#instructions").click(function(){ $("#instructionsClose, #instructionsBack").click(function(){ $("#popupInstructions").fadeOut(500); -}); \ No newline at end of file +}); +$("#daudClose, #daudBack").click(function(){ + $("#popupDaud").fadeOut(500); +}); + +$("#betainfo").click(function(){ + $("#popupDaud").fadeIn(500); +}); + +$("#popupDaud").fadeIn(500); \ No newline at end of file diff --git a/Game.Registry/Controllers/IEnumberable.cs b/Game.Registry/Controllers/IEnumberable.cs deleted file mode 100644 index fb06d5bd..00000000 --- a/Game.Registry/Controllers/IEnumberable.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Game.Registry.Controllers -{ - public interface IEnumberable - { - } -} \ No newline at end of file diff --git a/Game.Registry/Dockerfile b/Game.Registry/Dockerfile index c9c818e4..03df2a88 100644 --- a/Game.Registry/Dockerfile +++ b/Game.Registry/Dockerfile @@ -1,6 +1,6 @@ FROM microsoft/dotnet:2.1-sdk COPY . ./ RUN ["dotnet", "publish", "-c", "Release"] -WORKDIR ./bin/Release/netcoreapp2.1/publish +WORKDIR ./bin/Release/netcoreapp5.0/publish CMD ["dotnet", "Game.Registry.dll"] diff --git a/Game.Registry/Game.Registry.csproj b/Game.Registry/Game.Registry.csproj index cb0aa9b5..2d372d56 100644 --- a/Game.Registry/Game.Registry.csproj +++ b/Game.Registry/Game.Registry.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp5.0 latest diff --git a/Game.Registry/build.cmd b/Game.Registry/build.cmd index 0f0528ec..68db0299 100644 --- a/Game.Registry/build.cmd +++ b/Game.Registry/build.cmd @@ -1,5 +1,5 @@ dotnet publish -c release -pushd bin\Release\netcoreapp2.1\publish +pushd bin\Release\netcoreapp5.0\publish docker build . -t andylippitt/iogame-registry:%1 docker push andylippitt/iogame-registry:%1 popd diff --git a/Game.Robots/Game.Robots.csproj b/Game.Robots/Game.Robots.csproj index ac57fc22..46df958b 100644 --- a/Game.Robots/Game.Robots.csproj +++ b/Game.Robots/Game.Robots.csproj @@ -1,7 +1,7 @@  - netcoreapp2.1 + netcoreapp5.0 7.1 diff --git a/Game.Robots/RoboMath.cs b/Game.Robots/RoboMath.cs index e3077cd3..c63d2894 100644 --- a/Game.Robots/RoboMath.cs +++ b/Game.Robots/RoboMath.cs @@ -169,7 +169,6 @@ public static float ProjectClosestIntersectionDist(HookComputer hook, API.Client var aimSpot = targetPosition + targetMomentum * t; var aimMinusS = aimSpot - start; var desMinusS = destination - start; - var willHit = false; var disss = float.MaxValue; var bulletPath = aimSpot - fromPosition; var timeToImpact = (int)(bulletPath.Length() / bulletSpeed);//speed must be in units per second diff --git a/Game.Robots/TreeRobot.cs b/Game.Robots/TreeRobot.cs index f28ff7af..971572b1 100644 --- a/Game.Robots/TreeRobot.cs +++ b/Game.Robots/TreeRobot.cs @@ -147,8 +147,6 @@ private void Behave() var searchPaths = new List { baseS }; var newSearchPaths = new List(); - var searched = 0; - for (var i = 0; i < this.Depth; i++) { newSearchPaths = new List(); diff --git a/Game.Util/Dockerfile b/Game.Util/Dockerfile index 39fb41b8..56ccbe2b 100644 --- a/Game.Util/Dockerfile +++ b/Game.Util/Dockerfile @@ -1,6 +1,6 @@ FROM microsoft/dotnet:2.1-sdk COPY . ./ RUN ["dotnet", "publish", "-c", "Release"] -WORKDIR ./bin/Release/netcoreapp2.1/publish +WORKDIR ./bin/Release/netcoreapp5.0/publish CMD ["dotnet", "Game.Util.dll"] \ No newline at end of file diff --git a/Game.Util/Game.Util.csproj b/Game.Util/Game.Util.csproj index e2983cf8..b859687a 100644 --- a/Game.Util/Game.Util.csproj +++ b/Game.Util/Game.Util.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.1 + netcoreapp5.0 latest diff --git a/Game.Util/build.cmd b/Game.Util/build.cmd index 0baaac7a..6c3ab0f1 100644 --- a/Game.Util/build.cmd +++ b/Game.Util/build.cmd @@ -1,6 +1,6 @@ rmdir /s /q bin\Release dotnet publish -c release -pushd bin\Release\netcoreapp2.1\publish +pushd bin\Release\netcoreapp5.0\publish docker build . -t andylippitt/iogame-util:%1 docker push andylippitt/iogame-util:%1 popd diff --git a/Game.Util/readme.md b/Game.Util/readme.md index 30e037c5..5dfc684b 100644 --- a/Game.Util/readme.md +++ b/Game.Util/readme.md @@ -24,11 +24,11 @@ Copyright (C) Microsoft Corporation. All rights reserved. Restore completed in 2.55 sec for C:\code\IOGame\Game.Robots\Game.Robots.csproj. Restore completed in 2.55 sec for C:\code\IOGame\Game.Util\Game.Util.csproj. C:\Program Files\dotnet\sdk\2.1.401\Sdks\Microsoft.NET.Sdk\targets\Microsoft.PackageDependencyResolution.targets(198,5): message NETSDK1062: Unable to use package assets cache due to I/O error. This can occur when the same project is built more than once in parallel. Performance may be degraded, but the build result will not be impacted. [C:\code\IOGame\Game.Robots\Game.Robots.csproj] - Game.API.Common -> C:\code\IOGame\Game.API.Common\bin\Debug\netcoreapp2.1\Game.API.Common.dll - Game.Engine.Networking.FlatBuffers -> C:\code\IOGame\Game.Engine.Networking.FlatBuffers\bin\Debug\netcoreapp2.1\Game.Engine.Networking.FlatBuffers.dll - Game.API.Client -> C:\code\IOGame\Game.API.Client\bin\Debug\netcoreapp2.1\Game.API.Client.dll - Game.Robots -> C:\code\IOGame\Game.Robots\bin\Debug\netcoreapp2.1\Game.Robots.dll - Game.Util -> C:\code\IOGame\Game.Util\bin\Debug\netcoreapp2.1\Game.Util.dll + Game.API.Common -> C:\code\IOGame\Game.API.Common\bin\Debug\netcoreapp5.0\Game.API.Common.dll + Game.Engine.Networking.FlatBuffers -> C:\code\IOGame\Game.Engine.Networking.FlatBuffers\bin\Debug\netcoreapp5.0\Game.Engine.Networking.FlatBuffers.dll + Game.API.Client -> C:\code\IOGame\Game.API.Client\bin\Debug\netcoreapp5.0\Game.API.Client.dll + Game.Robots -> C:\code\IOGame\Game.Robots\bin\Debug\netcoreapp5.0\Game.Robots.dll + Game.Util -> C:\code\IOGame\Game.Util\bin\Debug\netcoreapp5.0\Game.Util.dll Build succeeded. 0 Warning(s) diff --git a/Game.Util/scripts/game.cmd b/Game.Util/scripts/game.cmd index 0df34272..dc430565 100644 --- a/Game.Util/scripts/game.cmd +++ b/Game.Util/scripts/game.cmd @@ -1 +1 @@ -@dotnet C:\code\IOGame\Game.Util\bin\Debug\netcoreapp2.1\Game.Util.dll %* \ No newline at end of file +@dotnet C:\code\IOGame\Game.Util\bin\Debug\netcoreapp5.0\Game.Util.dll %* \ No newline at end of file diff --git a/PKISharp.SimplePKI/PKISharp.SimplePKI.csproj b/PKISharp.SimplePKI/PKISharp.SimplePKI.csproj deleted file mode 100644 index 30d21d33..00000000 --- a/PKISharp.SimplePKI/PKISharp.SimplePKI.csproj +++ /dev/null @@ -1,36 +0,0 @@ - - - - netstandard2.0 - $(NoWarn);CS1591 - - - - - PKISharp.SimplePKI - Simple collection of PKI certificate management primitives for .NET Standard - Copyright (C) Eugene Bekker. - https://github.com/PKISharp/ACMESharpCore/blob/master/LICENSE - https://github.com/PKISharp/ACMESharpCore/ - https://raw.githubusercontent.com/PKISharp/ACMESharpCore/master/docs/pkisharp-logo-color.png - pki;ssl;tls;security;certificates;letsencrypt;acme;acmesharp - https://github.com/PKISharp/ACMESharpCore.git - git - - - - $(APPVEYOR_BUILD_NUMBER) - 0 - 1.0.0.$(BuildNumber) - beta1 - 7.1 - - - - - - - diff --git a/PKISharp.SimplePKI/PkiArchiveFormat.cs b/PKISharp.SimplePKI/PkiArchiveFormat.cs deleted file mode 100644 index f0e07c63..00000000 --- a/PKISharp.SimplePKI/PkiArchiveFormat.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace PKISharp.SimplePKI -{ - public enum PkiArchiveFormat - { - Unknown = 0, - - /// - /// The PKCS#12 (.PFX) format. - /// - Pkcs12 = 3, - - /// - /// PEM-encoded archive format. - /// - Pem = 4, - } -} \ No newline at end of file diff --git a/PKISharp.SimplePKI/PkiAsymmetricAlgorithm.cs b/PKISharp.SimplePKI/PkiAsymmetricAlgorithm.cs deleted file mode 100644 index 4cd6db96..00000000 --- a/PKISharp.SimplePKI/PkiAsymmetricAlgorithm.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace PKISharp.SimplePKI -{ - public enum PkiAsymmetricAlgorithm - { - Unknown = 0, - - /// - /// RSA (Rivest–Shamir–Adleman) is one of the first public-key cryptosystems - /// and is widely used for secure data transmission. - /// - Rsa = 1, - - /// - /// The Elliptic Curve Digital Signature Algorithm (ECDSA) offers a variant - /// of the Digital Signature Algorithm (DSA) which uses elliptic curve - /// cryptography. - /// - Ecdsa = 2, - } -} \ No newline at end of file diff --git a/PKISharp.SimplePKI/PkiCertificate.cs b/PKISharp.SimplePKI/PkiCertificate.cs deleted file mode 100644 index 247f1640..00000000 --- a/PKISharp.SimplePKI/PkiCertificate.cs +++ /dev/null @@ -1,160 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Xml.Serialization; -using Org.BouncyCastle.Asn1.X509; -using Org.BouncyCastle.OpenSsl; -using Org.BouncyCastle.Pkcs; -using Org.BouncyCastle.Security; -using Org.BouncyCastle.X509; -using BclCertificate = System.Security.Cryptography.X509Certificates.X509Certificate2; - -namespace PKISharp.SimplePKI -{ - public class PkiCertificate - { - internal PkiCertificate() - { } - - public string SubjectName => NativeCertificate.SubjectDN.ToString(); - - public IEnumerable SubjectAlternativeNames => - NativeCertificate.GetSubjectAlternativeNames()?.Cast() - .SelectMany(x => x.Cast().Where(y => y is string) - .Select(y => (string)y)); - - internal X509Certificate NativeCertificate { get; set; } - - public BclCertificate ToBclCertificate() - { - return new BclCertificate(NativeCertificate.GetEncoded()); - } - - public byte[] Export(PkiEncodingFormat format) - { - switch (format) - { - case PkiEncodingFormat.Pem: - using (var sw = new StringWriter()) - { - var pemWriter = new PemWriter(sw); - pemWriter.WriteObject(NativeCertificate); - return Encoding.UTF8.GetBytes(sw.GetStringBuilder().ToString()); - } - - case PkiEncodingFormat.Der: - return NativeCertificate.GetEncoded(); - - default: - throw new NotSupportedException(); - } - } - - public byte[] Export(PkiArchiveFormat format, PkiKey privateKey = null, - IEnumerable chain = null, - char[] password = null) - { - // Based on: - // https://stackoverflow.com/a/44798441/5428506 - - switch (format) - { - case PkiArchiveFormat.Pem: - using (var buff = new MemoryStream()) - { - byte[] bytes = privateKey?.Export(PkiEncodingFormat.Pem, password); - if (bytes != null) - buff.Write(bytes, 0, bytes.Length); - bytes = Export(PkiEncodingFormat.Pem); - buff.Write(bytes, 0, bytes.Length); - if (chain != null) - { - foreach (var c in chain) - { - bytes = c.Export(PkiEncodingFormat.Pem); - buff.Write(bytes, 0, bytes.Length); - } - } - return buff.ToArray(); - } - - case PkiArchiveFormat.Pkcs12: - var alias = AliasOf(this); - var store = new Pkcs12StoreBuilder().Build(); - if (privateKey != null) - store.SetKeyEntry(alias, new AsymmetricKeyEntry(privateKey.NativeKey), - new[] { new X509CertificateEntry(NativeCertificate) }); - else - store.SetCertificateEntry(alias, new X509CertificateEntry(NativeCertificate)); - - var chainIndex = 1; - if (chain != null) - { - foreach (var c in chain) - { - store.SetCertificateEntry(AliasOf(c), - new X509CertificateEntry(c.NativeCertificate)); - } - } - using (var buff = new MemoryStream()) - { - store.Save(buff, password ?? new char[0], new SecureRandom()); - return buff.ToArray(); - } - - default: - throw new NotSupportedException(); - } - } - - internal string AliasOf(PkiCertificate cert) - { - var x509Name = new X509Name(cert.SubjectName); - return (x509Name.GetValueList(X509Name.CN)?[0] ?? cert.SubjectName) as string; - } - - public void Save(Stream stream) - { - var xmlser = new XmlSerializer(typeof(RecoverableSerialForm)); - var ser = new RecoverableSerialForm(this); - xmlser.Serialize(stream, ser); - } - - public static PkiCertificate Load(Stream stream) - { - var xmlser = new XmlSerializer(typeof(RecoverableSerialForm)); - var ser = (RecoverableSerialForm)xmlser.Deserialize(stream); - return ser.Recover(); - } - - [XmlType(nameof(PkiCertificate))] - public class RecoverableSerialForm - { - public RecoverableSerialForm() - { } - - public RecoverableSerialForm(PkiCertificate cert) - { - _certificate = cert.Export(PkiEncodingFormat.Der); - _sn = cert.SubjectName; - _san = cert.SubjectAlternativeNames?.ToArray(); - } - - public int _ver = 1; - public byte[] _certificate; - public string _sn; - public string[] _san; - - public PkiCertificate Recover() - { - return new PkiCertificate - { - NativeCertificate = new X509CertificateParser().ReadCertificate(_certificate), - }; - } - } - } -} \ No newline at end of file diff --git a/PKISharp.SimplePKI/PkiCertificateExtension.cs b/PKISharp.SimplePKI/PkiCertificateExtension.cs deleted file mode 100644 index 106f7302..00000000 --- a/PKISharp.SimplePKI/PkiCertificateExtension.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Asn1.X509; - -namespace PKISharp.SimplePKI -{ - public class PkiCertificateExtension : IComparable - { - internal PkiCertificateExtension() - { } - - internal DerObjectIdentifier Identifier { get; set; } - - internal bool IsCritical { get; set; } - - internal Asn1Encodable Value { get; set; } - // internal X509Extension Value { get; set; } - - public int CompareTo(object obj) - { - var that = obj as PkiCertificateExtension; - if (that == null) - return -1; - - var thisVal = this.Identifier.ToString() - + this.IsCritical - + Convert.ToBase64String(this.Value.GetDerEncoded()); - var thatVal = that.Identifier.ToString() - + that.IsCritical - + Convert.ToBase64String(that.Value.GetDerEncoded()); - return thisVal.CompareTo(thatVal); - } - - public static PkiCertificateExtension CreateDnsSubjectAlternativeNames(IEnumerable dnsNames) - { - // Based on: - // https://boredwookie.net/blog/bouncy-castle-add-a-subject-alternative-name-when-creating-a-cer - - var gnames = new List( - dnsNames.Select(x => new GeneralName(GeneralName.DnsName, x))); - - var altNames = new GeneralNames(gnames.ToArray()); - - return new PkiCertificateExtension - { - Identifier = X509Extensions.SubjectAlternativeName, - IsCritical = false, - Value = altNames, - //Value = new X509Extension(false, new DerOctetString(altNames)), - }; - } - } -} \ No newline at end of file diff --git a/PKISharp.SimplePKI/PkiCertificateSigningRequest.cs b/PKISharp.SimplePKI/PkiCertificateSigningRequest.cs deleted file mode 100644 index 1962010e..00000000 --- a/PKISharp.SimplePKI/PkiCertificateSigningRequest.cs +++ /dev/null @@ -1,444 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using System.Linq; -using System.Text; -using System.Xml.Serialization; -using Org.BouncyCastle.Asn1; -using Org.BouncyCastle.Asn1.Pkcs; -using Org.BouncyCastle.Asn1.X509; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Operators; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.OpenSsl; -using Org.BouncyCastle.Pkcs; -using Org.BouncyCastle.Security; -using Org.BouncyCastle.X509; - -namespace PKISharp.SimplePKI -{ - public class PkiCertificateSigningRequest - { - private PkiKeyPair _keyPair; - - /// - /// Creates a new instance of a PKI Certificate Signing Request. - /// - /// The Subject Name of the Certificate Request in X509 - /// directory format, e.g. CN=app.example.com. - /// A public/private key pair. - /// The hash algorithm to be used. - public PkiCertificateSigningRequest(string subjectName, PkiKeyPair keyPair, - PkiHashAlgorithm hashAlgorithm) - { - SubjectName = subjectName; - _keyPair = keyPair; - PublicKey = _keyPair.PublicKey; - HashAlgorithm = hashAlgorithm; - } - - public PkiCertificateSigningRequest(PkiEncodingFormat format, byte[] encoded, - PkiHashAlgorithm hashAlgorithm) - { - Pkcs10CertificationRequest pkcs10; - switch (format) - { - case PkiEncodingFormat.Pem: - var encodedString = Encoding.UTF8.GetString(encoded); - using (var sr = new StringReader(encodedString)) - { - var pemReader = new PemReader(sr); - pkcs10 = pemReader.ReadObject() as Pkcs10CertificationRequest; - if (pkcs10 == null) - throw new Exception("invalid PEM object is not PKCS#10 archive"); - } - break; - case PkiEncodingFormat.Der: - pkcs10 = new Pkcs10CertificationRequest(encoded); - break; - default: - throw new NotSupportedException(); - } - - var info = pkcs10.GetCertificationRequestInfo(); - var nativePublicKey = pkcs10.GetPublicKey(); - var rsaKey = nativePublicKey as RsaKeyParameters; - var ecdsaKey = nativePublicKey as ECPublicKeyParameters; - - if (rsaKey != null) - { - PublicKey = new PkiKey(nativePublicKey, PkiAsymmetricAlgorithm.Rsa); - } - else if (ecdsaKey != null) - { - PublicKey = new PkiKey(nativePublicKey, PkiAsymmetricAlgorithm.Ecdsa); - } - else - { - throw new NotSupportedException("unsupported asymmetric algorithm key"); - } - SubjectName = info.Subject.ToString(); - HashAlgorithm = hashAlgorithm; - - - // // // Based on: - // // // http://forum.rebex.net/4284/pkcs10-certificate-request-example-provided-castle-working - - // // var extGen = new X509ExtensionsGenerator(); - // // foreach (var ext in CertificateExtensions) - // // { - // // extGen.AddExtension(ext.Identifier, ext.IsCritical, ext.Value); - // // } - // // var attr = new AttributeX509(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, - // // new DerSet(extGen.Generate())); - - - // Based on: - // http://unitstep.net/blog/2008/10/27/extracting-x509-extensions-from-a-csr-using-the-bouncy-castle-apis/ - // https://stackoverflow.com/q/24448909/5428506 - foreach (var attr in info.Attributes.ToArray()) - { - if (attr is DerSequence derSeq && derSeq.Count == 2) - { - var attrX509 = AttributeX509.GetInstance(attr); - if (object.Equals(attrX509.AttrType, PkcsObjectIdentifiers.Pkcs9AtExtensionRequest)) - { - // The `Extension Request` attribute is present. - // The X509Extensions are contained as a value of the ASN.1 Set. - // Assume that it is the first value of the set. - if (attrX509.AttrValues.Count >= 1) - { - var csrExts = X509Extensions.GetInstance(attrX509.AttrValues[0]); - foreach (var extOid in csrExts.GetExtensionOids()) - { - if (object.Equals(extOid, X509Extensions.SubjectAlternativeName)) - { - var ext = csrExts.GetExtension(extOid); - var extVal = ext.Value; - var der = extVal.GetDerEncoded(); - // The ext value, which is an ASN.1 Octet String, **MIGHT** be tagged with - // a leading indicator that it's an Octet String and its length, so we want - // to remove it if that's the case to extract the GeneralNames collection - if (der.Length > 2 && der[0] == 4 && der[1] == der.Length - 2) - der = der.Skip(2).ToArray(); - var asn1obj = Asn1Object.FromByteArray(der); - var gnames = GeneralNames.GetInstance(asn1obj); - CertificateExtensions.Add(new PkiCertificateExtension - { - Identifier = extOid, - IsCritical = ext.IsCritical, - Value = gnames, - }); - } - } - - // No need to search any more. - break; - } - } - } - } - } - - public string SubjectName { get; } - - public PkiKey PublicKey { get; } - - public bool HasPrivateKey => _keyPair != null; - - public PkiHashAlgorithm HashAlgorithm { get; } - - public Collection CertificateExtensions { get; } - = new Collection(); - - /// - /// Creates an ASN.1 DER-encoded PKCS#10 CertificationRequest object representing - /// the current state of this CertificateRequest object. - /// - /// An ASN.1 DER-encoded certificate signing request. - public byte[] ExportSigningRequest(PkiEncodingFormat format) - { - if (!HasPrivateKey) - throw new InvalidOperationException("cannot export CSR without a private key"); - - // Based on: - // https://github.com/bcgit/bc-csharp/blob/master/crypto/test/src/pkcs/test/PKCS10Test.cs - // https://stackoverflow.com/questions/46182659/how-to-delay-sign-the-certificate-request-using-bouncy-castle-with-ecdsa-signatu - // http://www.bouncycastle.org/wiki/display/JA1/X.509+Public+Key+Certificate+and+Certification+Request+Generation: - // #X.509PublicKeyCertificateandCertificationRequestGeneration-EllipticCurve(ECDSA) - // #X.509PublicKeyCertificateandCertificationRequestGeneration-RSA - // #X.509PublicKeyCertificateandCertificationRequestGeneration-CreatingCertificationRequests - // https://stackoverflow.com/a/37563051/5428506 - - var x509name = new X509Name(SubjectName); - var pubKey = _keyPair.PublicKey.NativeKey; - var prvKey = _keyPair.PrivateKey.NativeKey; - - // Asn1Set attrSet = null; - // if (CertificateExtensions.Count > 0) - // { - // var certExts = CertificateExtensions.ToDictionary( - // ext => ext.Identifier, ext => ext.Value); - // var csrAttrs = new[] - // { - // new Org.BouncyCastle.Asn1.Cms.Attribute( - // PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, - // new DerSet(new X509Extensions(certExts))), - // }; - // attrSet = new DerSet(csrAttrs); - // } - - // Based on: - // http://forum.rebex.net/4284/pkcs10-certificate-request-example-provided-castle-working - - var extGen = new X509ExtensionsGenerator(); - foreach (var ext in CertificateExtensions) - { - extGen.AddExtension(ext.Identifier, ext.IsCritical, ext.Value); - } - var attr = new AttributeX509(PkcsObjectIdentifiers.Pkcs9AtExtensionRequest, - new DerSet(extGen.Generate())); - - var sigFactory = ComputeSignatureAlgorithm(prvKey); - var pkcs10 = new Pkcs10CertificationRequest(sigFactory, x509name, - pubKey, new DerSet(attr), prvKey); - - switch (format) - { - case PkiEncodingFormat.Pem: - using (var sw = new StringWriter()) - { - var pemWriter = new PemWriter(sw); - pemWriter.WriteObject(pkcs10); - return Encoding.UTF8.GetBytes(sw.GetStringBuilder().ToString()); - } - - case PkiEncodingFormat.Der: - return pkcs10.GetDerEncoded(); - - default: - throw new NotSupportedException(); - } - } - - /// - /// Creates a self-signed certificate using the established subject, key, - /// and optional extensions. - /// - /// The oldest date and time when this certificate is considered - /// valid. Typically UtcNow, plus or minus a few seconds. - /// The date and time when this certificate is no longer considered - /// valid. - /// A Certificate with the specified values. The returned object - /// will assert HasPrivateKey. - public PkiCertificate CreateSelfSigned(DateTimeOffset notBefore, DateTimeOffset notAfter) - { - var name = new X509Name(SubjectName); - var snum = Org.BouncyCastle.Utilities.BigIntegers.CreateRandomInRange( - BigInteger.One, BigInteger.ValueOf(long.MaxValue), - new SecureRandom()).ToByteArrayUnsigned(); - return Create(name, _keyPair.PrivateKey, name, notBefore, notAfter, snum); - } - - public PkiCertificate CreateCa(DateTimeOffset notBefore, DateTimeOffset notAfter) - { - var name = new X509Name(SubjectName); - var snum = Org.BouncyCastle.Utilities.BigIntegers.CreateRandomInRange( - BigInteger.One, BigInteger.ValueOf(long.MaxValue), - new SecureRandom()).ToByteArrayUnsigned(); - - // Key Usage: - // Digital Signature, Certificate Signing, Off-line CRL Signing, CRL Signing (86) - - return Create(name, _keyPair.PrivateKey, name, notBefore, notAfter, snum, - new X509KeyUsage( - X509KeyUsage.DigitalSignature | - X509KeyUsage.KeyCertSign | - X509KeyUsage.CrlSign)); - } - - /// - /// Creates a certificate using the established subject, key, and optional - /// extensions using the specified certificate as the issuer. - /// - /// Certificate instance representing the issuing - /// Certificate Authority (CA). - /// Key representing the private key of the issuing - /// certificate authority. - /// The oldest date and time when this certificate is considered - /// valid. Typically UtcNow, plus or minus a few seconds. - /// The date and time when this certificate is no longer considered - /// valid. - /// The serial number to use for the new certificate. - /// This value should be unique per issuer. The value is interpreted as - /// an unsigned integer of arbitrary size in big-endian byte ordering. - /// RFC 3280 recommends confining it to 20 bytes or less. - /// A Certificate with the specified values. The returned object - /// won't assert HasPrivateKey. - public PkiCertificate Create(PkiCertificate issuerCertificate, PkiKey issuerPrivateKey, - DateTimeOffset notBefore, DateTimeOffset notAfter, byte[] serialNumber) - { - var isur = new X509Name(issuerCertificate.SubjectName); - var name = new X509Name(SubjectName); - return Create(isur, issuerPrivateKey, name, notBefore, notAfter, serialNumber); - } - - internal ISignatureFactory ComputeSignatureAlgorithm(AsymmetricKeyParameter privateKey) - { - var hashAlgorName = HashAlgorithm.ToString().ToUpper(); - var asymAlgorName = (_keyPair?.Algorithm ?? PublicKey.Algorithm).ToString().ToUpper(); - var sigAlgor = $"{hashAlgorName}with{asymAlgorName}"; - var sigFactory = new Asn1SignatureFactory(sigAlgor, privateKey); - - return sigFactory; - } - - internal PkiCertificate Create(X509Name issuerName, PkiKey issuerPrivateKey, - X509Name subjectName, DateTimeOffset notBefore, DateTimeOffset notAfter, - byte[] serialNumber, X509KeyUsage keyUsage = null, KeyPurposeID[] extKeyUsage = null) - { - // Based on: - // https://stackoverflow.com/a/39456955/5428506 - // https://github.com/bcgit/bc-csharp/blob/master/crypto/test/src/test/CertTest.cs - - var pubKey = _keyPair?.PublicKey.NativeKey ?? PublicKey.NativeKey; - - var sigFactory = ComputeSignatureAlgorithm(issuerPrivateKey.NativeKey); - var certGen = new X509V3CertificateGenerator(); - certGen.SetSerialNumber(new BigInteger(serialNumber)); - certGen.SetIssuerDN(issuerName); - certGen.SetSubjectDN(subjectName); - certGen.SetNotBefore(notBefore.UtcDateTime); - certGen.SetNotAfter(notAfter.UtcDateTime); - certGen.SetPublicKey(pubKey); - - if (keyUsage == null) - keyUsage = new X509KeyUsage(X509KeyUsage.KeyEncipherment | - X509KeyUsage.DigitalSignature); - if (extKeyUsage == null) - extKeyUsage = new[] { - KeyPurposeID.IdKPClientAuth, - KeyPurposeID.IdKPServerAuth - }; - - certGen.AddExtension("2.5.29.15", true, keyUsage); - certGen.AddExtension("2.5.29.37", true, new DerSequence(extKeyUsage)); - - // Based on: - // https://boredwookie.net/blog/bouncy-castle-add-a-subject-alternative-name-when-creating-a-cer - - foreach (var ext in CertificateExtensions) - { - // certGen.AddExtension(ext.Identifier, ext.Value.IsCritical, ext.Value.Value); - certGen.AddExtension(ext.Identifier, ext.IsCritical, ext.Value); - } - - var bcCert = certGen.Generate(sigFactory); - - return new PkiCertificate - { - NativeCertificate = bcCert, - }; - - // Compare to LE-issued Certs: - // Enhanced Key Usage: - // Server Authentication (1.3.6.1.5.5.7.3.1) - // Client Authentication (1.3.6.1.5.5.7.3.2) - // Subject Key Identifier: - // e05bf2ba81d8d3845ff45b5638551e64ca19133d - // Authority Key Identifier: - // KeyID=a84a6a63047dddbae6d139b7a64565eff3a8eca1 - // Authority Information Access: - // [1]Authority Info Access - // Access Method=On-line Certificate Status Protocol (1.3.6.1.5.5.7.48.1) - // Alternative Name: - // URL=http://ocsp.int-x3.letsencrypt.org - // [2]Authority Info Access - // Access Method=Certification Authority Issuer (1.3.6.1.5.5.7.48.2) - // Alternative Name: - // URL=http://cert.int-x3.letsencrypt.org/ - // Certificate Policies: - // ... - // SCT List: - // ... - // Key Usage: - // Digital Signature, Key Encipherment (a0) - // Basic Constraints: - // Subject Type=End Entity - // Path Length Constraint=None - - // CA: - // Key Usage: - // Digital Signature, Certificate Signing, Off-line CRL Signing, CRL Signing (86) - } - - /// - /// Saves this CSR instance to the target stream, - /// in a recoverable serialization format. - /// - /// - public void Save(Stream stream) - { - var xmlSer = new XmlSerializer(typeof(RecoverableSerialForm)); - var ser = new RecoverableSerialForm(this); - xmlSer.Serialize(stream, ser); - } - - /// - /// Recovers a serialized CSR previously saved using - /// a recoverable serialization format. - /// - /// - /// - public static PkiCertificateSigningRequest Load(Stream stream) - { - var xmlSer = new System.Xml.Serialization.XmlSerializer(typeof(RecoverableSerialForm)); - var ser = (RecoverableSerialForm)xmlSer.Deserialize(stream); - return ser.Recover(); - } - - [XmlType(nameof(PkiCertificateSigningRequest))] - public class RecoverableSerialForm - { - public RecoverableSerialForm() - { } - - public RecoverableSerialForm(PkiCertificateSigningRequest csr) - { - _subject = csr.SubjectName; - _keypair = csr._keyPair == null ? null : new PkiKeyPair.RecoverableSerialForm(csr._keyPair); - _pubkey = new PkiKey.RecoverableSerialForm(csr.PublicKey); - _hashalgor = csr.HashAlgorithm; - _exts = csr.CertificateExtensions.Select(x => - (x.Identifier.Id, x.IsCritical, - x.Value.ToAsn1Object().GetDerEncoded())).ToArray(); - } - - public int _ver = 1; - public string _subject; - public PkiKeyPair.RecoverableSerialForm _keypair; - public PkiKey.RecoverableSerialForm _pubkey; - public PkiHashAlgorithm _hashalgor; - public (string id, bool crit, byte[] value)[] _exts; - - public PkiCertificateSigningRequest Recover() - { - var csr = new PkiCertificateSigningRequest(_subject, _keypair?.Recover(), _hashalgor); - - foreach (var e in _exts) - { - csr.CertificateExtensions.Add(new PkiCertificateExtension - { - Identifier = new DerObjectIdentifier(e.id), - IsCritical = e.crit, - Value = Asn1Object.FromByteArray(e.value), - }); - } - - return csr; - } - } - } -} \ No newline at end of file diff --git a/PKISharp.SimplePKI/PkiEncodingFormat.cs b/PKISharp.SimplePKI/PkiEncodingFormat.cs deleted file mode 100644 index 9111e65b..00000000 --- a/PKISharp.SimplePKI/PkiEncodingFormat.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace PKISharp.SimplePKI -{ - public enum PkiEncodingFormat - { - Unknown = 0, - - /// - /// DER binary encoding. - /// - Der = 1, - - /// - /// PEM text encoding. - /// - Pem = 2, - } -} \ No newline at end of file diff --git a/PKISharp.SimplePKI/PkiHashAlgorithm.cs b/PKISharp.SimplePKI/PkiHashAlgorithm.cs deleted file mode 100644 index 8549f6a8..00000000 --- a/PKISharp.SimplePKI/PkiHashAlgorithm.cs +++ /dev/null @@ -1,11 +0,0 @@ -namespace PKISharp.SimplePKI -{ - public enum PkiHashAlgorithm - { - Unknown = 0, - - Sha256 = 10, - Sha384 = 11, - Sha512 = 12, - } -} \ No newline at end of file diff --git a/PKISharp.SimplePKI/PkiKey.cs b/PKISharp.SimplePKI/PkiKey.cs deleted file mode 100644 index 9b399397..00000000 --- a/PKISharp.SimplePKI/PkiKey.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System; -using System.IO; -using System.Security.Cryptography; -using System.Text; -using System.Xml.Serialization; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.OpenSsl; -using Org.BouncyCastle.Pkcs; -using Org.BouncyCastle.Security; -using Org.BouncyCastle.X509; -using static PKISharp.SimplePKI.PkiKeyPair; - -namespace PKISharp.SimplePKI -{ - /// - /// A general abstraction for an asymmetric algorithm key, either public or private. - /// - public class PkiKey - { - internal PkiKey(AsymmetricKeyParameter nativeKey, - PkiAsymmetricAlgorithm algorithm = PkiAsymmetricAlgorithm.Unknown) - { - NativeKey = nativeKey; - Algorithm = algorithm; - } - - public PkiAsymmetricAlgorithm Algorithm { get; } - - /// - /// True if this key is the private key component of a key pair. - /// - public bool IsPrivate => NativeKey.IsPrivate; - - internal AsymmetricKeyParameter NativeKey { get; } - - /// - /// Exports the key into a supported key format. - /// - public byte[] Export(PkiEncodingFormat format, char[] password = null) - { - switch (format) - { - case PkiEncodingFormat.Pem: - using (var sw = new StringWriter()) - { - object pemObject = NativeKey; - if (IsPrivate && password != null) - { - var pkcs8Gen = new Pkcs8Generator(NativeKey, - Pkcs8Generator.PbeSha1_3DES); - pkcs8Gen.Password = password; - pemObject = pkcs8Gen.Generate(); - } - var pemWriter = new PemWriter(sw); - pemWriter.WriteObject(pemObject); - return Encoding.UTF8.GetBytes(sw.GetStringBuilder().ToString()); - } - - case PkiEncodingFormat.Der: - if (IsPrivate) - { - var keyInfo = PrivateKeyInfoFactory.CreatePrivateKeyInfo(NativeKey); - return keyInfo.GetDerEncoded(); - } - else - { - var keyInfo = SubjectPublicKeyInfoFactory.CreateSubjectPublicKeyInfo(NativeKey); - return keyInfo.GetDerEncoded(); - } - - default: - throw new NotSupportedException(); - } - } - - - [XmlType(nameof(PkiKey))] - [XmlInclude(typeof(PkiKeyPairRsaParams))] - [XmlInclude(typeof(PkiKeyPairEcdsaParams))] - public class RecoverableSerialForm - { - public RecoverableSerialForm() - { } - - public RecoverableSerialForm(PkiKey key) - { - _algorithm = key.Algorithm; - _key = key.Export(PkiEncodingFormat.Der); - _isPrivate = key.IsPrivate; - } - - public int _ver = 2; - public PkiAsymmetricAlgorithm _algorithm; - public bool _isPrivate; - public byte[] _key; - public PkiKey Recover() - { - if (_isPrivate) - { - return new PkiKey(PrivateKeyFactory.CreateKey(_key), _algorithm); - } - else - { - return new PkiKey(PublicKeyFactory.CreateKey(_key), _algorithm); - } - } - } - } -} \ No newline at end of file diff --git a/PKISharp.SimplePKI/PkiKeyPair.cs b/PKISharp.SimplePKI/PkiKeyPair.cs deleted file mode 100644 index f93b3ed4..00000000 --- a/PKISharp.SimplePKI/PkiKeyPair.cs +++ /dev/null @@ -1,435 +0,0 @@ -using System; -using System.IO; -using System.Security.Cryptography; -using System.Xml.Serialization; -using Org.BouncyCastle.Asn1.Nist; -using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.Crypto.Parameters; -using Org.BouncyCastle.Math; -using Org.BouncyCastle.Security; -using PKISharp.SimplePKI.Util; - -namespace PKISharp.SimplePKI -{ - /// - /// A general abstraction of a public/private key pair for an asymmetric encryption algorithm. - /// - public class PkiKeyPair - { - private PkiKey _PublicKey; - private PkiKey _PrivateKey; - private Func _signer; - private Func _verifier; - private Func _jwkExporter; - - internal PkiKeyPair(AsymmetricCipherKeyPair nativeKeyPair, PkiKeyPairParams kpParams) - { - NativeKeyPair = nativeKeyPair; - Parameters = kpParams; - Init(); - } - - public PkiKeyPairParams Parameters { get; } - - public PkiAsymmetricAlgorithm Algorithm => Parameters.Algorithm; - - public PkiKey PublicKey - { - get - { - if (_PublicKey == null) - _PublicKey = new PkiKey(NativeKeyPair.Public, Algorithm); - return _PublicKey; - } - } - - public PkiKey PrivateKey - { - get - { - if (_PrivateKey == null) - _PrivateKey = new PkiKey(NativeKeyPair.Private, Algorithm); - return _PrivateKey; - } - } - - internal AsymmetricCipherKeyPair NativeKeyPair { get; set; } - - private void Init() - { - switch (Algorithm) - { - case PkiAsymmetricAlgorithm.Rsa: - // SHA + ECDSA algor selection based on: - // https://github.com/bcgit/bc-csharp/blob/master/crypto/src/security/SignerUtilities.cs - var sigAlgor = $"SHA{Parameters.HashBits}WITHRSA"; - _signer = (pkey, data) => Sign(sigAlgor, pkey, data); - _verifier = (pkey, data, sig) => Verify(sigAlgor, pkey, data, sig); - _jwkExporter = (keys, prv) => ExportRsJwk(keys, prv); - break; - case PkiAsymmetricAlgorithm.Ecdsa: - // SHA + ECDSA algor selection based on: - // https://github.com/bcgit/bc-csharp/blob/master/crypto/src/security/SignerUtilities.cs - // Transcode Length: - // * lengths are specified as in: - // https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-24#section-3.4 - // * see explanation in the docs for "TranscodeSignatureToConcat" for what this is all about - var hashBits = Parameters.HashBits; - var transcodeLength = 0; - if (hashBits == -1) - { - switch (Parameters.Bits) - { - case 521: hashBits = 512; transcodeLength = 132; break; - case 384: hashBits = 384; transcodeLength = 96; break; - default: hashBits = 256; transcodeLength = 64; break; - } - } - sigAlgor = $"SHA{hashBits}WITHECDSA"; - _signer = (prv, data) => Sign(sigAlgor, prv, data, transcodeLength); - _verifier = (pub, data, sig) => Verify(sigAlgor, pub, data, sig); - _jwkExporter = (keys, prv) => ExportEcJwk(Parameters.Bits, keys, prv); - break; - default: - throw new NotSupportedException("Unsupported Algorithm"); - } - } - - /// - /// Generates an RSA key pair for the argument bit length. - /// Some typical bit lengths include 1024, 2048 and 4096. - /// It is generally agreed upon that modern use of RSA should - /// require a minimum of 2048-bit key size. - /// - public static PkiKeyPair GenerateRsaKeyPair(int bits, int hashBits = 256) - { - // Based on: - // https://github.com/bcgit/bc-csharp/blob/master/crypto/test/src/pkcs/test/PKCS10Test.cs - - var rsaParams = new RsaKeyGenerationParameters( - BigInteger.ValueOf(0x10001), new SecureRandom(), bits, 25); - var rsaKpGen = GeneratorUtilities.GetKeyPairGenerator("RSA"); - rsaKpGen.Init(rsaParams); - var nativeKeyPair = rsaKpGen.GenerateKeyPair(); - - return new PkiKeyPair(nativeKeyPair, - new PkiKeyPairRsaParams(bits) { HashBits = hashBits }); - } - - public static PkiKeyPair GenerateEcdsaKeyPair(int bits, int hashBits = -1) - { - // Based on: - // https://github.com/bcgit/bc-csharp/blob/master/crypto/test/src/crypto/test/ECTest.cs#L331 - // https://www.codeproject.com/Tips/1150485/Csharp-Elliptical-Curve-Cryptography-with-Bouncy-C - - // This produced the following error against Let's Encrypt CA: - // ACMESharp.Protocol.AcmeProtocolException : Error parsing certificate request: asn1: structure error: tags don't match (6 vs {class:0 tag:16 length:247 isCompound:true}) {optional:false explicit:false application:false defaultValue: tag: stringType:0 timeType:0 set:false omitEmpty:false} ObjectIdentifier @3 - - // var ecNistParams = NistNamedCurves.GetByName("P-" + bits); - // var ecDomainParams = new ECDomainParameters(ecNistParams.Curve, - // ecNistParams.G, ecNistParams.N, ecNistParams.H, ecNistParams.GetSeed()); - // var ecParams = new ECKeyGenerationParameters(ecDomainParams, new SecureRandom()); - - // So according to [this](https://github.com/golang/go/issues/18634#issuecomment-272527314) - // it seems we were passing in arbitrary curve details instead of a named curve OID as we do here: - - var ecCurveOid = NistNamedCurves.GetOid("P-" + bits); ; - var ecParams = new ECKeyGenerationParameters(ecCurveOid, new SecureRandom()); - var ecKpGen = GeneratorUtilities.GetKeyPairGenerator("ECDSA"); - ecKpGen.Init(ecParams); - var nativeKeyPair = ecKpGen.GenerateKeyPair(); - - return new PkiKeyPair(nativeKeyPair, - new PkiKeyPairEcdsaParams(bits) { HashBits = hashBits }); - } - - /// - /// Returns true if the underlying key pair algorithm supports signing. - /// - public bool CanSign => _signer != null; - - /// - /// Signs the input data using the private key of this key pair if the - /// underlying key pair algorithm supports signing. - /// - /// - /// When the underlying key pair implementation - /// does not support signing - public byte[] Sign(byte[] data) - { - if (_signer == null) - throw new NotSupportedException(); - - return _signer(this.PrivateKey, data); - } - - public bool Verify(byte[] data, byte[] sig) - { - if (_verifier == null) - throw new NotSupportedException(); - - return _verifier(this.PublicKey, data, sig); - } - - internal static byte[] Sign(string algor, PkiKey prv, byte[] input, int transcodeLength = 0) - { - // Based on: - // http://mytenpennies.wikidot.com/blog:using-bouncy-castle - - var signer = SignerUtilities.GetSigner(algor); - signer.Init(true, prv.NativeKey); - signer.BlockUpdate(input, 0, input.Length); - var sig = signer.GenerateSignature(); - - if (transcodeLength != 0) - { - sig = TranscodeSignatureToConcat(sig, transcodeLength); - } - - return sig; - } - - internal static bool Verify(string algor, PkiKey pub, byte[] input, byte[] sig) - { - // Based on: - // http://mytenpennies.wikidot.com/blog:using-bouncy-castle - - var signer = SignerUtilities.GetSigner(algor); - signer.Init(false, pub.NativeKey); - signer.BlockUpdate(input, 0, input.Length); - return signer.VerifySignature(sig); - } - - /// - /// Transcodes the JCA ASN.1/DER-encoded signature into the concatenated - /// R + S format expected by ECDSA JWS. - /// - /// - /// @param derSignature The ASN1./DER-encoded. Must not be {@code null}. - /// @param outputLength The expected length of the ECDSA JWS signature. - /// @return The ECDSA JWS encoded signature. - /// If the ASN.1/DER signature format is invalid. - /// - public static byte[] TranscodeSignatureToConcat(byte[] derSignature, int outputLength) - { - // We discovered the long and hard way that not all ECDSA signatures are alike! - // Turns out that BouncyCastle's implementation returns the ASN1/DER encoded - // form which in some ways is the correct-est form, but also turns out this is - // not what the .NET BCL libraries produce and it is not what the JWS form used - // by ACME expects. - // - // Based on the following sources, we figured out the discrepency and also how - // to convert it (using the code below which was originally a Java function - // and left completely intact as it was copied over to C#!): - // * https://tools.ietf.org/html/draft-ietf-jose-json-web-algorithms-24#section-3.4 - // * https://crypto.stackexchange.com/a/1797/59470 - // * http://bouncy-castle.1462172.n4.nabble.com/Signature-wrong-length-using-ECDSA-using-P-521-td4658010.html - // * Java source code copied from down at the bottom of: - // * http://www.ssekhon.com/blog/2017/08/02/sign-data-using-ecdsa-and-bouncy-castle - - if (derSignature.Length < 8 || derSignature[0] != 48) - { - throw new Exception("Invalid ECDSA signature format"); - } - - int offset; - if (derSignature[1] > 0) - { - offset = 2; - } - else if (derSignature[1] == (byte)0x81) - { - offset = 3; - } - else - { - throw new Exception("Invalid ECDSA signature format"); - } - - byte rLength = derSignature[offset + 1]; - - int i = rLength; - while ((i > 0) - && (derSignature[(offset + 2 + rLength) - i] == 0)) - i--; - - byte sLength = derSignature[offset + 2 + rLength + 1]; - - int j = sLength; - while ((j > 0) - && (derSignature[(offset + 2 + rLength + 2 + sLength) - j] == 0)) - j--; - - int rawLen = Math.Max(i, j); - rawLen = Math.Max(rawLen, outputLength / 2); - - if ((derSignature[offset - 1] & 0xff) != derSignature.Length - offset - || (derSignature[offset - 1] & 0xff) != 2 + rLength + 2 + sLength - || derSignature[offset] != 2 - || derSignature[offset + 2 + rLength] != 2) - { - throw new Exception("Invalid ECDSA signature format"); - } - - byte[] concatSignature = new byte[2 * rawLen]; - - Array.Copy(derSignature, (offset + 2 + rLength) - i, concatSignature, rawLen - i, i); - Array.Copy(derSignature, (offset + 2 + rLength + 2 + sLength) - j, concatSignature, 2 * rawLen - j, j); - - return concatSignature; - } - - public object ExportJwk(bool @private = false) - { - return _jwkExporter == null ? null : _jwkExporter(this, @private); - } - - // Helpful for debugging: - // public object ExportEcParameters() - // { - // var pub = (ECPublicKeyParameters)_PublicKey.NativeKey; - // var prv = (ECPrivateKeyParameters)_PrivateKey.NativeKey; - - // var exp = new - // { - // HashSize = prv.D.ToByteArrayUnsigned().Length * 8, - // D = prv.D.ToByteArrayUnsigned(), - // X = pub.Q.XCoord.GetEncoded(), - // Y = pub.Q.YCoord.GetEncoded(), - // }; - // return exp; - - // } - - internal static object ExportRsJwk(PkiKeyPair keys, bool @private) - { - if (@private) - throw new NotImplementedException(); - - var pub = (RsaKeyParameters)keys.PublicKey.NativeKey; - return new - { - // As per RFC 7638 Section 3, these are the *required* elements of the - // JWK and are sorted in lexicographic order to produce a canonical form - - e = Base64Tool.Instance.UrlEncode(pub.Exponent.ToByteArray()), - kty = "RSA", // https://tools.ietf.org/html/rfc7518#section-6.3 - n = Base64Tool.Instance.UrlEncode(pub.Modulus.ToByteArray()), - }; - } - - internal static object ExportEcJwk(int bits, PkiKeyPair keys, bool @private) - { - if (@private) - throw new NotImplementedException(); - - var pub = (ECPublicKeyParameters)keys.PublicKey.NativeKey; - return new - { - // As per RFC 7638 Section 3, these are the *required* elements of the - // JWK and are sorted in lexicographic order to produce a canonical form - - crv = $"P-{bits}", - kty = "EC", // https://tools.ietf.org/html/rfc7518#section-6.2 - x = Base64Tool.Instance.UrlEncode(pub.Q.XCoord.GetEncoded()), - y = Base64Tool.Instance.UrlEncode(pub.Q.YCoord.GetEncoded()), - }; - } - - /// - /// Saves this key pair instance to the target stream, - /// in a recoverable serialization format. - /// - /// - public void Save(Stream stream) - { - var xmlSer = new XmlSerializer(typeof(RecoverableSerialForm)); - var ser = new RecoverableSerialForm(this); - xmlSer.Serialize(stream, ser); - } - - /// - /// Recovers a serialized key pair previously saved using - /// a recoverable serialization format. - /// - /// - /// - public static PkiKeyPair Load(Stream stream) - { - var xmlSer = new System.Xml.Serialization.XmlSerializer(typeof(RecoverableSerialForm)); - var ser = (RecoverableSerialForm)xmlSer.Deserialize(stream); - return ser.Recover(); - } - - [XmlType(nameof(PkiKeyPair))] - [XmlInclude(typeof(PkiKeyPairRsaParams))] - [XmlInclude(typeof(PkiKeyPairEcdsaParams))] - public class RecoverableSerialForm - { - public RecoverableSerialForm() - { } - - public RecoverableSerialForm(PkiKeyPair keyPair) - { - _algorithm = keyPair.Algorithm; - _privateKey = keyPair.PrivateKey.Export(PkiEncodingFormat.Der); - _publicKey = keyPair.PublicKey.Export(PkiEncodingFormat.Der); - _kpParams = keyPair.Parameters; - } - - public int _ver = 2; - public PkiAsymmetricAlgorithm _algorithm; - public byte[] _privateKey; - public byte[] _publicKey; - public PkiKeyPairParams _kpParams; - public PkiKeyPair Recover() - { - var pubKey = PublicKeyFactory.CreateKey(_publicKey); - var prvKey = PrivateKeyFactory.CreateKey(_privateKey); - - return new PkiKeyPair(null, _kpParams) - { - _PrivateKey = new PkiKey(prvKey, _algorithm), - _PublicKey = new PkiKey(pubKey, _algorithm), - }; - } - } - - public class PkiKeyPairParams - { - internal PkiKeyPairParams() - { } - - public PkiAsymmetricAlgorithm Algorithm { get; set; } = PkiAsymmetricAlgorithm.Unknown; - - public int Bits { get; set; } - - public int HashBits { get; set; } - } - - public class PkiKeyPairRsaParams : PkiKeyPairParams - { - internal PkiKeyPairRsaParams() - { } - - public PkiKeyPairRsaParams(int bits) - { - Algorithm = PkiAsymmetricAlgorithm.Rsa; - Bits = bits; - HashBits = 256; - } - } - - public class PkiKeyPairEcdsaParams : PkiKeyPairParams - { - internal PkiKeyPairEcdsaParams() - { } - - public PkiKeyPairEcdsaParams(int bits) - { - Algorithm = PkiAsymmetricAlgorithm.Ecdsa; - Bits = bits; - } - } - } -} \ No newline at end of file diff --git a/PKISharp.SimplePKI/README.md b/PKISharp.SimplePKI/README.md deleted file mode 100644 index 174e1341..00000000 --- a/PKISharp.SimplePKI/README.md +++ /dev/null @@ -1,57 +0,0 @@ -# README - `PKISharp.SimplePKI` - -The purpose of this support library is to provide a small, targeted -API that implements the specific operations that are typically needed on the -client side when working with certificates and certificate requests. - - -[![NuGet Pre Release](https://img.shields.io/nuget/vpre/PKISharp.SimplePKI.svg)](https://www.nuget.org/packages/PKISharp.SimplePKI) - - -## Background - -This library originated out of a need to handle various client-side operations -in support of the ACMESharp client. - -In order to use the [ACMESharp](https://github.com/PKISharp/ACMESharpCore) library -to properly interact with an ACME CA in producing signed certificates it is necessary to -perform various PKI certificate management operations on the client side such as selecting -and generating key pairs for supported asymmetric encryption algorithms, generating a -certificate signing request in a DER encoded format, and exporting -a certificate and its private key in a usable archive format. - -Eventually, it is hoped that one would be able to do all these operations out of the box -with the .NET platform, specifically with .NET Standard, but today there is a limited -set of these operations that are possible. Work is already underway to expand this -support (such as [here](https://github.com/dotnet/corefx/issues/21833), -[here](https://github.com/dotnet/corefx/issues/20414) and -[here](https://github.com/dotnet/designs/issues/11)). - -In the meantime, support can be found *outside of the box* with the help of the -excellent [Bouncy Castle](https://www.bouncycastle.org/csharp/index.html) crypto -library. - -While this library grew out of a need to support the ACME protocol and process, -it is independent of the ACMESharp library and stands on its own as a useful tool. - -## Features - -The following primary PKI-supporting entities and operations are supported by SimplePKI: - -* Asymmetric Keys and Key Pairs: - * Generate RSA and ECDSA key pairs using common key lengths and named curves (EC) - * Export to DER/PEM formats -* Certificate Signing Requests: - * Conforms to PKCS#10 request formats - * Export to DER/PEM formats - * Generate Signed certs - * Generate Self-signed and CA-like certs - * Support for Subject Alternative Name (SAN) extension -* Certificates: - * Export to DER/PEM formats - * Export to PKCS#12 format archive with optional private key and certificate chain - * Conversion to standard BCL `X509Certificate2` - -Additionally, all the primary entities listed above support saving to/loading from an -opaque persistent format that can be useful when needing to support long-running -operations that require durable storage. diff --git a/PKISharp.SimplePKI/Util/Base64Tool.cs b/PKISharp.SimplePKI/Util/Base64Tool.cs deleted file mode 100644 index 7c0f4522..00000000 --- a/PKISharp.SimplePKI/Util/Base64Tool.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Text; - -namespace PKISharp.SimplePKI.Util -{ - // TODO:!!!!!! - // This needs to be reconciled with the version in ACMESharp!!!! - - /// - /// Collection of convenient crypto operations working - /// with URL-safe Base64 encoding. - /// - internal class Base64Tool - { - public static Base64Tool Instance = new Base64Tool(); - - - /// - /// URL-safe Base64 encoding as prescribed in RFC 7515 Appendix C. - /// - public string UrlEncode(string raw, Encoding encoding = null) - { - if (encoding == null) - encoding = Encoding.UTF8; - return UrlEncode(encoding.GetBytes(raw)); - } - - /// - /// URL-safe Base64 encoding as prescribed in RFC 7515 Appendix C. - /// - public string UrlEncode(byte[] raw) - { - string enc = Convert.ToBase64String(raw); // Regular base64 encoder - enc = enc.Split('=')[0]; // Remove any trailing '='s - enc = enc.Replace('+', '-'); // 62nd char of encoding - enc = enc.Replace('/', '_'); // 63rd char of encoding - return enc; - } - - /// - /// URL-safe Base64 decoding as prescribed in RFC 7515 Appendix C. - /// - public byte[] UrlDecode(string enc) - { - string raw = enc; - raw = raw.Replace('-', '+'); // 62nd char of encoding - raw = raw.Replace('_', '/'); // 63rd char of encoding - switch (raw.Length % 4) // Pad with trailing '='s - { - case 0: break; // No pad chars in this case - case 2: raw += "=="; break; // Two pad chars - case 3: raw += "="; break; // One pad char - default: - throw new System.Exception("Illegal base64url string!"); - } - return Convert.FromBase64String(raw); // Standard base64 decoder - } - - public string UrlDecodeToString(string enc, Encoding encoding = null) - { - if (encoding == null) - encoding = Encoding.UTF8; - return encoding.GetString(UrlDecode(enc)); - } - } -} diff --git a/Samples/Robots/HelloMeatbags/HelloMeatbags.csproj b/Samples/Robots/HelloMeatbags/HelloMeatbags.csproj index 3328ed0a..a3d1d0f1 100644 --- a/Samples/Robots/HelloMeatbags/HelloMeatbags.csproj +++ b/Samples/Robots/HelloMeatbags/HelloMeatbags.csproj @@ -2,7 +2,7 @@ Exe - netcoreapp2.1 + netcoreapp5.0 latest diff --git a/Samples/kube/game.yml b/Samples/kube/game.yml index e0e502dd..7434c921 100644 --- a/Samples/kube/game.yml +++ b/Samples/kube/game.yml @@ -85,7 +85,7 @@ spec: #requests: # cpu: "0.057" command: ["dotnet"] - args: ["/app/Game.Util/bin/Release/netcoreapp2.1/publish/Game.Util.dll", + args: ["/app/Game.Util/bin/Release/netcoreapp5.0/publish/Game.Util.dll", "--server", "http://game-internal/", "--user-key", "Administrator", "player", "robots", @@ -128,7 +128,7 @@ spec: #requests: # cpu: "0.057" command: ["dotnet"] - args: ["/app/Game.Registry/bin/Release/netcoreapp2.1/publish/Game.Registry.dll"] + args: ["/app/Game.Registry/bin/Release/netcoreapp5.0/publish/Game.Registry.dll"] env: - name: config__ElasticSearchURI value: "http://game-elastic:9200" diff --git a/Samples/kube/hosting/ban.ip.sh b/Samples/kube/hosting/ban.ip.sh new file mode 100644 index 00000000..a9c657a0 --- /dev/null +++ b/Samples/kube/hosting/ban.ip.sh @@ -0,0 +1,2 @@ +#!/bin/bash +iptables -I DOCKER-USER -i ens3 -s $1 -j DROP \ No newline at end of file diff --git a/Samples/kube/hosting/daud/.helmignore b/Samples/kube/hosting/daud/.helmignore new file mode 100644 index 00000000..0e8a0eb3 --- /dev/null +++ b/Samples/kube/hosting/daud/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/Samples/kube/hosting/daud/Chart.yaml b/Samples/kube/hosting/daud/Chart.yaml new file mode 100644 index 00000000..11acc22e --- /dev/null +++ b/Samples/kube/hosting/daud/Chart.yaml @@ -0,0 +1,23 @@ +apiVersion: v2 +name: daud +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +appVersion: latest diff --git a/Samples/kube/hosting/daud/templates/NOTES.txt b/Samples/kube/hosting/daud/templates/NOTES.txt new file mode 100644 index 00000000..82a655e5 --- /dev/null +++ b/Samples/kube/hosting/daud/templates/NOTES.txt @@ -0,0 +1,21 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "daud.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "daud.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "daud.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "daud.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80 +{{- end }} diff --git a/Samples/kube/hosting/daud/templates/_helpers.tpl b/Samples/kube/hosting/daud/templates/_helpers.tpl new file mode 100644 index 00000000..0e918b96 --- /dev/null +++ b/Samples/kube/hosting/daud/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "daud.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "daud.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "daud.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "daud.labels" -}} +helm.sh/chart: {{ include "daud.chart" . }} +{{ include "daud.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "daud.selectorLabels" -}} +app.kubernetes.io/name: {{ include "daud.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "daud.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "daud.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/Samples/kube/hosting/daud/templates/deployment.yaml b/Samples/kube/hosting/daud/templates/deployment.yaml new file mode 100644 index 00000000..84d10685 --- /dev/null +++ b/Samples/kube/hosting/daud/templates/deployment.yaml @@ -0,0 +1,94 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "daud.fullname" . }} + labels: + {{- include "daud.labels" . | nindent 4 }} +spec: +{{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} +{{- end }} + selector: + matchLabels: + {{- include "daud.selectorLabels" . | nindent 6 }} + component: game + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "daud.selectorLabels" . | nindent 8 }} + component: game + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "daud.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + env: + + # daud + - name: config__PublicURL + value: "{{ .Values.daud.publicURL }}" + - name: config__AdministratorPassword + value: "{{ .Values.daud.administratorPassword }}" + - name: config__NoWorlds + value: "{{ .Values.daud.noWorlds }}" + + {{- if .Values.discord.guildID }} + # discord + - name: config__DiscordGuildID + value: "{{ .Values.discord.guildID }}" + - name: config__DiscordToken + value: "{{ .Values.discord.token }}" + {{- end }} + + # registry + - name: config__RegistryEnabled + value: "{{ .Values.registry.enabled }}" + - name: config__RegistryUri + value: "{{ .Values.registry.uri }}" + - name: config__RegistryUserKey + value: "{{ .Values.registry.user }}" + - name: config__RegistryPassword + value: "{{ .Values.registry.password }}" + + - name: PORT + value: "80" + + ports: + - name: http + containerPort: 80 + protocol: TCP + livenessProbe: + httpGet: + path: / + port: http + readinessProbe: + httpGet: + path: / + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/Samples/kube/hosting/daud/templates/ingress.yaml b/Samples/kube/hosting/daud/templates/ingress.yaml new file mode 100644 index 00000000..6067233e --- /dev/null +++ b/Samples/kube/hosting/daud/templates/ingress.yaml @@ -0,0 +1,41 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "daud.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "daud.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ . }} + backend: + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} diff --git a/Samples/kube/hosting/daud/templates/letsencrypt/letsencrypt-prod.yaml b/Samples/kube/hosting/daud/templates/letsencrypt/letsencrypt-prod.yaml new file mode 100644 index 00000000..cbfe247f --- /dev/null +++ b/Samples/kube/hosting/daud/templates/letsencrypt/letsencrypt-prod.yaml @@ -0,0 +1,14 @@ +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: {{ include "daud.fullname" . }}-letsencrypt-prod +spec: + acme: + server: https://acme-v02.api.letsencrypt.org/directory + email: andylippitt@gmail.com + privateKeySecretRef: + name: {{ include "daud.fullname" . }}-letsencrypt-prod + solvers: + - http01: + ingress: + class: public \ No newline at end of file diff --git a/Samples/kube/hosting/daud/templates/letsencrypt/letsencrypt-staging.yaml b/Samples/kube/hosting/daud/templates/letsencrypt/letsencrypt-staging.yaml new file mode 100644 index 00000000..d27cfe86 --- /dev/null +++ b/Samples/kube/hosting/daud/templates/letsencrypt/letsencrypt-staging.yaml @@ -0,0 +1,14 @@ +apiVersion: cert-manager.io/v1 +kind: ClusterIssuer +metadata: + name: {{ include "daud.fullname" . }}-letsencrypt-staging +spec: + acme: + email: andylippitt@gmail.com + server: https://acme-staging-v02.api.letsencrypt.org/directory + privateKeySecretRef: + name: {{ include "daud.fullname" . }}-letsencrypt-staging + solvers: + - http01: + ingress: + class: public \ No newline at end of file diff --git a/Samples/kube/hosting/daud/templates/service.yaml b/Samples/kube/hosting/daud/templates/service.yaml new file mode 100644 index 00000000..d9578af2 --- /dev/null +++ b/Samples/kube/hosting/daud/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "daud.fullname" . }} + labels: + {{- include "daud.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "daud.selectorLabels" . | nindent 4 }} diff --git a/Samples/kube/hosting/daud/templates/serviceaccount.yaml b/Samples/kube/hosting/daud/templates/serviceaccount.yaml new file mode 100644 index 00000000..497d540f --- /dev/null +++ b/Samples/kube/hosting/daud/templates/serviceaccount.yaml @@ -0,0 +1,12 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "daud.serviceAccountName" . }} + labels: + {{- include "daud.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/Samples/kube/hosting/daud/templates/tests/test-connection.yaml b/Samples/kube/hosting/daud/templates/tests/test-connection.yaml new file mode 100644 index 00000000..71666990 --- /dev/null +++ b/Samples/kube/hosting/daud/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "daud.fullname" . }}-test-connection" + labels: + {{- include "daud.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test-success +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "daud.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/Samples/kube/hosting/daud/values.yaml b/Samples/kube/hosting/daud/values.yaml new file mode 100644 index 00000000..27e81d77 --- /dev/null +++ b/Samples/kube/hosting/daud/values.yaml @@ -0,0 +1,96 @@ +replicaCount: 1 + +daud: + publicURL: "" + administratorPassword: "" + noWorlds: true + +discord: + guildID: "" + token: "" + +registry: + enabled: false + uri: "http://localhost:5001/" + user: "" + password: "" + + server: + enabled: true + disableSuggestionLookup: false + elasticURI: http://game-elastic:9200 + +image: + repository: iodaud/daud + pullPolicy: IfNotPresent + # Overrides the image tag whose default is the chart appVersion. + tag: "" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 80 + +ingress: + enabled: true + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: k1.daud.io + paths: [ '/' ] + + tls: [] + # - secretName: chart-example-tls + # hosts: + # - chart-example.local + +resources: {} + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + # limits: + # cpu: 100m + # memory: 128Mi + # requests: + # cpu: 100m + # memory: 128Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/Samples/kube/robots-live.yml b/Samples/kube/robots-live.yml index d0f13a69..e7ba5a47 100644 --- a/Samples/kube/robots-live.yml +++ b/Samples/kube/robots-live.yml @@ -20,7 +20,7 @@ spec: image: iodaud/daud:version-1.22 resources: command: ["dotnet"] - args: ["/app/Game.Util/bin/Release/netcoreapp2.1/publish/Game.Util.dll", + args: ["/app/Game.Util/bin/Release/netcoreapp5.0/publish/Game.Util.dll", "--server", "https://us.daud.io/", "player", "robots", "--world", "robo", diff --git a/Samples/kube/superduel.yml b/Samples/kube/superduel.yml index e3ae4e08..a4d31274 100644 --- a/Samples/kube/superduel.yml +++ b/Samples/kube/superduel.yml @@ -20,7 +20,7 @@ spec: image: iodaud/daud:version-1.32 resources: command: ["dotnet"] - args: ["/app/Game.Util/bin/Release/netcoreapp2.1/publish/Game.Util.dll", + args: ["/app/Game.Util/bin/Release/netcoreapp5.0/publish/Game.Util.dll", "--server", "https://us.daud.io/", "--user-key", "Administrator", "--password", "DaudsEverywhere", diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..73bc64b4 --- /dev/null +++ b/build.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +docker build . -t iodaud/daud:$1 +docker push iodaud/daud:$1 + diff --git a/docker-compose.yml b/docker-compose.yml index 7973540d..f3e95d43 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,6 +3,6 @@ version: '3' services: game: build: - context: ./Game.Engine/bin/Debug/netcoreapp2.1/publish + context: ./Game.Engine/bin/Debug/netcoreapp5.0/publish ports: - "8080:80" \ No newline at end of file