diff --git a/backend/src/Squidex.Domain.Users.MongoDb/MongoKey.cs b/backend/src/Squidex.Domain.Users.MongoDb/MongoKey.cs new file mode 100644 index 000000000..be77f9292 --- /dev/null +++ b/backend/src/Squidex.Domain.Users.MongoDb/MongoKey.cs @@ -0,0 +1,23 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson.Serialization.Attributes; + +namespace Squidex.Domain.Users.MongoDb +{ + public sealed class MongoKey + { + [BsonId] + public string Id { get; set; } + + [BsonElement] + public string Key { get; set; } + + [BsonElement] + public MongoKeyParameters Parameters { get; set; } + } +} diff --git a/backend/src/Squidex.Domain.Users.MongoDb/MongoKeyParameters.cs b/backend/src/Squidex.Domain.Users.MongoDb/MongoKeyParameters.cs new file mode 100644 index 000000000..16dad01cf --- /dev/null +++ b/backend/src/Squidex.Domain.Users.MongoDb/MongoKeyParameters.cs @@ -0,0 +1,64 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.Security.Cryptography; + +#pragma warning disable IDE0017 // Simplify object initialization + +namespace Squidex.Domain.Users.MongoDb +{ + public sealed class MongoKeyParameters + { + public byte[] D { get; set; } + + public byte[] DP { get; set; } + + public byte[] DQ { get; set; } + + public byte[] Exponent { get; set; } + + public byte[] InverseQ { get; set; } + + public byte[] Modulus { get; set; } + + public byte[] P { get; set; } + + public byte[] Q { get; set; } + + public static MongoKeyParameters Create(RSAParameters source) + { + var mongoParameters = new MongoKeyParameters(); + + mongoParameters.D = source.D; + mongoParameters.DP = source.DP; + mongoParameters.DQ = source.DQ; + mongoParameters.Exponent = source.Exponent; + mongoParameters.InverseQ = source.InverseQ; + mongoParameters.Modulus = source.Modulus; + mongoParameters.P = source.P; + mongoParameters.Q = source.Q; + + return mongoParameters; + } + + public RSAParameters ToParameters() + { + var parameters = default(RSAParameters); + + parameters.D = D; + parameters.DP = DP; + parameters.DQ = DQ; + parameters.Exponent = Exponent; + parameters.InverseQ = InverseQ; + parameters.Modulus = Modulus; + parameters.P = P; + parameters.Q = Q; + + return parameters; + } + } +} diff --git a/backend/src/Squidex.Domain.Users.MongoDb/MongoKeyStore.cs b/backend/src/Squidex.Domain.Users.MongoDb/MongoKeyStore.cs new file mode 100644 index 000000000..8faec7d51 --- /dev/null +++ b/backend/src/Squidex.Domain.Users.MongoDb/MongoKeyStore.cs @@ -0,0 +1,125 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System; +using System.Collections.Generic; +using System.Security.Cryptography; +using System.Threading.Tasks; +using IdentityModel; +using IdentityServer4.Models; +using IdentityServer4.Stores; +using Microsoft.IdentityModel.Tokens; +using MongoDB.Driver; +using Squidex.Infrastructure.MongoDb; + +namespace Squidex.Domain.Users.MongoDb +{ + public sealed class MongoKeyStore : MongoRepositoryBase, ISigningCredentialStore, IValidationKeysStore + { + private SigningCredentials? cachedKey; + private SecurityKeyInfo[]? cachedKeyInfo; + + public MongoKeyStore(IMongoDatabase database, bool setup = false) + : base(database, setup) + { + } + + protected override string CollectionName() + { + return "Key"; + } + + public async Task GetSigningCredentialsAsync() + { + var (_, key) = await GetOrCreateKeyAsync(); + + return key; + } + + public async Task> GetValidationKeysAsync() + { + var (info, _) = await GetOrCreateKeyAsync(); + + return info; + } + + private async Task<(SecurityKeyInfo[], SigningCredentials)> GetOrCreateKeyAsync() + { + if (cachedKey != null && cachedKeyInfo != null) + { + return (cachedKeyInfo, cachedKey); + } + + var key = await Collection.Find(x => x.Id == "Default").FirstOrDefaultAsync(); + + RsaSecurityKey securityKey; + + if (key == null) + { + securityKey = new RsaSecurityKey(RSA.Create(2048)) + { + KeyId = CryptoRandom.CreateUniqueId(16) + }; + + key = new MongoKey { Id = "Default", Key = securityKey.KeyId }; + + if (securityKey.Rsa != null) + { + var parameters = securityKey.Rsa.ExportParameters(includePrivateParameters: true); + + key.Parameters = MongoKeyParameters.Create(parameters); + } + else + { + key.Parameters = MongoKeyParameters.Create(securityKey.Parameters); + } + + try + { + await Collection.InsertOneAsync(key); + + return CreateCredentialsPair(securityKey); + } + catch (MongoWriteException ex) + { + if (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey) + { + key = await Collection.Find(x => x.Id == "Default").FirstOrDefaultAsync(); + } + else + { + throw ex; + } + } + } + + if (key == null) + { + throw new InvalidOperationException("Cannot read key."); + } + + securityKey = new RsaSecurityKey(key.Parameters.ToParameters()) + { + KeyId = key.Key + }; + + return CreateCredentialsPair(securityKey); + } + + private (SecurityKeyInfo[], SigningCredentials) CreateCredentialsPair(RsaSecurityKey securityKey) + { + cachedKey = new SigningCredentials(securityKey, SecurityAlgorithms.RsaSha256); + + cachedKeyInfo = new[] + { + new SecurityKeyInfo { Key = cachedKey.Key, SigningAlgorithm = cachedKey.Algorithm } + }; + + return (cachedKeyInfo, cachedKey); + } + } +} diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.crt b/backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.crt deleted file mode 100644 index 1f58f13a9..000000000 --- a/backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.crt +++ /dev/null @@ -1,29 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIFCzCCAvOgAwIBAgIULb5or4cjujSTMg9dZOFJtUU0h2MwDQYJKoZIhvcNAQEL -BQAwFTETMBEGA1UEAwwKc3F1aWRleC5pbzAeFw0xOTEwMjUxODAwMzJaFw0yOTEw -MjIxODAwMzJaMBUxEzARBgNVBAMMCnNxdWlkZXguaW8wggIiMA0GCSqGSIb3DQEB -AQUAA4ICDwAwggIKAoICAQDkCBwdntYhthsvwj7TobnKplejrvZvkMT79SGx4tXe -5eFDpuMGqljMUzZoUaaQsy4mRe/4c9bMmMlpRmIPpJfYAYDxGzSm9N0FjUZqGJPq -kRNTNKqTPeFwl7vdn+MuveLWyOc+mSYWkPWg9WURdN5yNtDi1IUrcj+XsUqH0AQi -eEiiQJlSUF8NkdvviGv906uOG/59NwpvCsyHwV3dOR2GFHvi2RIg/M+4d8Vz2AVm -KvXVoQtxySweXRXNKvoAeKdvQ4prHc0oHo57vhW/nDV++WUGZ7LOnpr4OgIyrvM/ -0LFfSIdgJ2rK/+yKNQLJKZbq0DD942PvQapEC9Xfuuqw1S6wHXgf3CzPuoNX4sDI -2rIw84AjH0gB7IuwC0DVqcbI++Xv6H3HzpD0i1ONaHztmp5tCx9v6dkJ0ctX6vzC -SwMxdUerB9XELifOF9CqnSjUzOiAuQ9yTE0iqb6jRu5JTGeKTtmFQ6T0YUuqiPip -H3zblloGKo3mQbVumvfELb0wTs3Ay0jaczjD0aM3fRKav+6b6Qu+tDnlxL/yPde2 -SMxDwIKIH8eCAa3+8OU9VSZ0+2DS/pu1vXdsXqa5EXJJ7Ej/NyQe+nKjOG/TfeYG -u+GAO5/pJYE4lWq77hZd9ylm21dbs+X4X3uZFOsGb0BCoQADi/QFwexcz+hOmQuX -BwIDAQABo1MwUTAdBgNVHQ4EFgQUuLxsPj+ueDfckXVeG6aRFM0KVc0wHwYDVR0j -BBgwFoAUuLxsPj+ueDfckXVeG6aRFM0KVc0wDwYDVR0TAQH/BAUwAwEB/zANBgkq -hkiG9w0BAQsFAAOCAgEA0ePG4Xp29yWkDHO2Zp6dG/uVy15sEVpG25VlwEuXzdnb -VDN1++spoTLZT0HWGs9OZtCkXF2wfFL3C/az5sSn40uXy5UzoHtldEjTchSFX2tN -ulVbSiEUyxp2xEzbELIPaELhecPUyJMKUTHOLfrLaKWFC26KQK+R5E4mdx0nIZ73 -9GZFDA7okfzqkl3CeLhHfkKrPy/dLcz9doBkca4scSmgJcMQvS2sC7wVrcTtfcsh -cefQ8hMR4vfVQHl0mU7cHUJR1U7sSrXh/pOjrzX/0k/VGO+pQtDVnT8YZXRx+w0S -4nRz60nUxIDbad/xld71YV6L3rWYy2/7MIbCb71mszc6SdQtV/+lc3yJJdvNmNtc -xlpirsI1vr3yfPcuYuS8i0dqPlh7Rn+wlrqFNlu6pgpB5uhVCHXfkf3TATGJpyi3 -lN/f98Du6ZDvsIFk6loWJ/SkRAgX4un3mVEeonDMSaWAHwfPdoMXE5ViCgKLPo+B -HHM5bmZmUk25mgFoiRYx/jnw2Ym+Vsyw6SI0+kQLLoAfP/pP39rWe+MbSIhhDXC6 -5lP5IebfzEI10PAg9UrgSDShAT2E4fFaMx0mRi0dwhRgBJEW/EEjjXd8+QjXPuPF -GqU6YTf/rcDQB4cT/GaBkUar3qanmBESAabMoabZ0EDVprwrrfbqx9bDsOz4J9k= ------END CERTIFICATE----- diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.key b/backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.key deleted file mode 100644 index 2fd9618e8..000000000 --- a/backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.key +++ /dev/null @@ -1,52 +0,0 @@ ------BEGIN PRIVATE KEY----- -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 -4VvGnkGGbUe3PWfxA+yBJqdyprlXbaY= ------END PRIVATE KEY----- diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.pfx b/backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.pfx deleted file mode 100644 index b3032a3e2..000000000 Binary files a/backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.pfx and /dev/null differ diff --git a/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs b/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs index 8127fbe1a..478ef3a3a 100644 --- a/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs +++ b/backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs @@ -6,7 +6,6 @@ // ========================================================================== using System.Collections.Generic; -using System.Security.Cryptography.X509Certificates; using IdentityModel; using IdentityServer4.Models; using IdentityServer4.Stores; @@ -28,21 +27,6 @@ namespace Squidex.Areas.IdentityServer.Config { public static void AddSquidexIdentityServer(this IServiceCollection services) { - X509Certificate2 certificate; - - var assembly = typeof(IdentityServerServices).Assembly; - - using (var certificateStream = assembly.GetManifestResourceStream("Squidex.Areas.IdentityServer.Config.Cert.IdentityCert.pfx")) - { - var certData = new byte[certificateStream!.Length]; - - certificateStream.Read(certData, 0, certData.Length); - certificate = new X509Certificate2(certData, "password", - X509KeyStorageFlags.MachineKeySet | - X509KeyStorageFlags.PersistKeySet | - X509KeyStorageFlags.Exportable); - } - services.AddSingleton>(s => { return new ConfigureOptions(options => @@ -77,8 +61,7 @@ namespace Squidex.Areas.IdentityServer.Config }) .AddAspNetIdentity() .AddInMemoryApiResources(GetApiResources()) - .AddInMemoryIdentityResources(GetIdentityResources()) - .AddSigningCredential(certificate); + .AddInMemoryIdentityResources(GetIdentityResources()); } private static IEnumerable GetApiResources() diff --git a/backend/src/Squidex/Config/Domain/StoreServices.cs b/backend/src/Squidex/Config/Domain/StoreServices.cs index 8fe6c1079..03f5cb54f 100644 --- a/backend/src/Squidex/Config/Domain/StoreServices.cs +++ b/backend/src/Squidex/Config/Domain/StoreServices.cs @@ -102,6 +102,9 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As>().As(); + services.AddSingletonAs() + .As().As(); + services.AddSingletonAs() .As().As>(); diff --git a/backend/src/Squidex/Squidex.csproj b/backend/src/Squidex/Squidex.csproj index 3351d0478..3cf8eb9f0 100644 --- a/backend/src/Squidex/Squidex.csproj +++ b/backend/src/Squidex/Squidex.csproj @@ -85,7 +85,6 @@ - @@ -107,7 +106,6 @@ -