Browse Source

Better key management.

pull/492/head
Sebastian 6 years ago
parent
commit
fd0804525d
  1. 23
      backend/src/Squidex.Domain.Users.MongoDb/MongoKey.cs
  2. 64
      backend/src/Squidex.Domain.Users.MongoDb/MongoKeyParameters.cs
  3. 125
      backend/src/Squidex.Domain.Users.MongoDb/MongoKeyStore.cs
  4. 29
      backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.crt
  5. 52
      backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.key
  6. BIN
      backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.pfx
  7. 19
      backend/src/Squidex/Areas/IdentityServer/Config/IdentityServerServices.cs
  8. 3
      backend/src/Squidex/Config/Domain/StoreServices.cs
  9. 2
      backend/src/Squidex/Squidex.csproj

23
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; }
}
}

64
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;
}
}
}

125
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<MongoKey>, 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<SigningCredentials> GetSigningCredentialsAsync()
{
var (_, key) = await GetOrCreateKeyAsync();
return key;
}
public async Task<IEnumerable<SecurityKeyInfo>> 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);
}
}
}

29
backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.crt

@ -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-----

52
backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.key

@ -1,52 +0,0 @@
-----BEGIN PRIVATE KEY-----
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
4VvGnkGGbUe3PWfxA+yBJqdyprlXbaY=
-----END PRIVATE KEY-----

BIN
backend/src/Squidex/Areas/IdentityServer/Config/Cert/IdentityCert.pfx

Binary file not shown.

19
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<IConfigureOptions<KeyManagementOptions>>(s =>
{
return new ConfigureOptions<KeyManagementOptions>(options =>
@ -77,8 +61,7 @@ namespace Squidex.Areas.IdentityServer.Config
})
.AddAspNetIdentity<IdentityUser>()
.AddInMemoryApiResources(GetApiResources())
.AddInMemoryIdentityResources(GetIdentityResources())
.AddSigningCredential(certificate);
.AddInMemoryIdentityResources(GetIdentityResources());
}
private static IEnumerable<ApiResource> GetApiResources()

3
backend/src/Squidex/Config/Domain/StoreServices.cs

@ -102,6 +102,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<MongoUserStore>()
.As<IUserStore<IdentityUser>>().As<IUserFactory>();
services.AddSingletonAs<MongoKeyStore>()
.As<ISigningCredentialStore>().As<IValidationKeysStore>();
services.AddSingletonAs<MongoAssetRepository>()
.As<IAssetRepository>().As<ISnapshotStore<AssetState, Guid>>();

2
backend/src/Squidex/Squidex.csproj

@ -85,7 +85,6 @@
<ItemGroup>
<EmbeddedResource Include="Areas\Api\Controllers\Users\Assets\Avatar.png" />
<EmbeddedResource Include="Areas\IdentityServer\Config\Cert\IdentityCert.pfx" />
<EmbeddedResource Include="Docs\schemabody.md" />
<EmbeddedResource Include="Docs\schemaquery.md" />
<EmbeddedResource Include="Docs\security.md" />
@ -107,7 +106,6 @@
<ItemGroup>
<None Remove="Areas\Api\Controllers\Users\Assets\Avatar.png" />
<None Remove="Areas\IdentityServer\Config\Cert\IdentityCert.pfx" />
<None Remove="Docs\schemabody.md" />
<None Remove="Docs\schemaquery.md" />
<None Remove="Docs\security.md" />

Loading…
Cancel
Save