// Kyoo - A portable and vast media library solution. // Copyright (c) Kyoo. // // See AUTHORS.md and LICENSE file in the project root for full license information. // // Kyoo is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // any later version. // // Kyoo is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Kyoo. If not, see . using System; using System.IO; using System.Security.Cryptography.X509Certificates; using Kyoo.Authentication.Models; using Microsoft.Extensions.DependencyInjection; using Org.BouncyCastle.Asn1.X509; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.Crypto.Generators; using Org.BouncyCastle.Crypto.Operators; using Org.BouncyCastle.Math; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.Security; using Org.BouncyCastle.Utilities; using Org.BouncyCastle.X509; using X509Certificate = Org.BouncyCastle.X509.X509Certificate; namespace Kyoo.Authentication { /// /// A class containing multiple extensions methods to manage certificates. /// public static class Certificates { /// /// Add the certificate file to the identity server. If the certificate will expire soon, automatically renew it. /// If no certificate exists, one is generated. /// /// The identity server that will be modified. /// The certificate options /// The initial builder to allow chain-calls. public static IIdentityServerBuilder AddSigninKeys(this IIdentityServerBuilder builder, CertificateOption options) { X509Certificate2 certificate = _GetCertificate(options); builder.AddSigningCredential(certificate); if (certificate.NotAfter.AddDays(-7) <= DateTime.UtcNow) { Console.WriteLine("Signin certificate will expire soon, renewing it."); if (File.Exists(options.OldFile)) File.Delete(options.OldFile); File.Move(options.File, options.OldFile); builder.AddValidationKey(_GenerateCertificate(options.File, options.Password)); } else if (File.Exists(options.OldFile)) builder.AddValidationKey(_GetExistingCredential(options.OldFile, options.Password)); return builder; } /// /// Get or generate the sign-in certificate. /// /// The certificate options /// A valid certificate private static X509Certificate2 _GetCertificate(CertificateOption options) { return File.Exists(options.File) ? _GetExistingCredential(options.File, options.Password) : _GenerateCertificate(options.File, options.Password); } /// /// Load a certificate from a file /// /// The path of the certificate /// The password of the certificate /// The loaded certificate private static X509Certificate2 _GetExistingCredential(string file, string password) { X509KeyStorageFlags storeFlags = X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable; return new X509Certificate2(file, password, storeFlags); } /// /// Generate a new certificate key and put it in the file at . /// /// The path of the output file /// The password of the new certificate /// The generated certificate private static X509Certificate2 _GenerateCertificate(string file, string password) { SecureRandom random = new(); X509V3CertificateGenerator certificateGenerator = new(); certificateGenerator.SetSerialNumber(BigIntegers.CreateRandomInRange(BigInteger.One, BigInteger.ValueOf(long.MaxValue), random)); certificateGenerator.SetIssuerDN(new X509Name($"C=NL, O=SDG, CN=Kyoo")); certificateGenerator.SetSubjectDN(new X509Name($"C=NL, O=SDG, CN=Kyoo")); certificateGenerator.SetNotBefore(DateTime.UtcNow.Date); certificateGenerator.SetNotAfter(DateTime.UtcNow.Date.AddMonths(3)); KeyGenerationParameters keyGenerationParameters = new(random, 2048); RsaKeyPairGenerator keyPairGenerator = new(); keyPairGenerator.Init(keyGenerationParameters); AsymmetricCipherKeyPair subjectKeyPair = keyPairGenerator.GenerateKeyPair(); certificateGenerator.SetPublicKey(subjectKeyPair.Public); const string signatureAlgorithm = "MD5WithRSA"; Asn1SignatureFactory signatureFactory = new(signatureAlgorithm, subjectKeyPair.Private); X509Certificate bouncyCert = certificateGenerator.Generate(signatureFactory); Pkcs12Store store = new Pkcs12StoreBuilder().Build(); store.SetKeyEntry("Kyoo_key", new AsymmetricKeyEntry(subjectKeyPair.Private), new[] { new X509CertificateEntry(bouncyCert) }); using MemoryStream pfxStream = new(); store.Save(pfxStream, password.ToCharArray(), random); X509Certificate2 certificate = new(pfxStream.ToArray(), password, X509KeyStorageFlags.Exportable); using FileStream fileStream = File.OpenWrite(file); pfxStream.WriteTo(fileStream); return certificate; } } }