Ver código fonte

first commit

shayne_lo 5 meses atrás
commit
39c0aa8f65
39 arquivos alterados com 2331 adições e 0 exclusões
  1. 25 0
      .dockerignore
  2. 63 0
      .gitattributes
  3. 261 0
      .gitignore
  4. 716 0
      CAUtilLib/CAUtil.cs
  5. 14 0
      CAUtilLib/CAUtilLib.csproj
  6. 107 0
      CAUtilLib/CaUtil_1.cs
  7. 41 0
      CAUtilLib/Types.cs
  8. 31 0
      CertificateAutorityServer.sln
  9. 25 0
      CertificateAutorityServer/Assist/TempPhysicalFileResult.cs
  10. 27 0
      CertificateAutorityServer/CertificateAutorityServer.csproj
  11. 59 0
      CertificateAutorityServer/Controllers/CertificateController.cs
  12. 85 0
      CertificateAutorityServer/Controllers/cert/CrtController.cs
  13. 41 0
      CertificateAutorityServer/Controllers/cert/CsrController.cs
  14. 23 0
      CertificateAutorityServer/Controllers/cert/KeyController.cs
  15. 22 0
      CertificateAutorityServer/Dockerfile
  16. 20 0
      CertificateAutorityServer/Model/Config/CerServiceConfig.cs
  17. 7 0
      CertificateAutorityServer/Model/Rest/AddTrustedCertificateReq.cs
  18. 8 0
      CertificateAutorityServer/Model/Rest/CheckCrtReq.cs
  19. 9 0
      CertificateAutorityServer/Model/Rest/CreateCrtReq.cs
  20. 9 0
      CertificateAutorityServer/Model/Rest/CreateCsrReq.cs
  21. 17 0
      CertificateAutorityServer/Model/Rest/CreateRootCaReq.cs
  22. 17 0
      CertificateAutorityServer/Model/Rest/CreateSubCaReq.cs
  23. 13 0
      CertificateAutorityServer/Model/Rest/SignCsrReq.cs
  24. 28 0
      CertificateAutorityServer/Program.cs
  25. 51 0
      CertificateAutorityServer/Properties/launchSettings.json
  26. 278 0
      CertificateAutorityServer/Service/CertificateService.cs
  27. 74 0
      CertificateAutorityServer/Service/CertificateServiceFactory.cs
  28. 8 0
      CertificateAutorityServer/appsettings.Development.json
  29. 10 0
      CertificateAutorityServer/appsettings.json
  30. 28 0
      CertificateAutorityServer/temp/a42e0a2c-22a2-41d0-a278-36c64350572a.csr
  31. 28 0
      CertificateAutorityServer/temp/a88d41c1-685d-461a-8b4b-2723b51d8b3a.csr
  32. 28 0
      CertificateAutorityServer/temp/d21fcb46-5e5a-482b-9739-a83da69ef203.csr
  33. 28 0
      CertificateAutorityServer/temp/f6aedc09-7773-40b1-9c69-74642f8d9246.csr
  34. 7 0
      CertificateAutorityServer/zerova_v3.cnf
  35. 66 0
      DEV_Build.ps1
  36. 3 0
      Dev_Build.bat
  37. 36 0
      Dockerfile
  38. 6 0
      entrypoint.sh
  39. 12 0
      sshd_config

+ 25 - 0
.dockerignore

@@ -0,0 +1,25 @@
+**/.classpath
+**/.dockerignore
+**/.env
+**/.git
+**/.gitignore
+**/.project
+**/.settings
+**/.toolstarget
+**/.vs
+**/.vscode
+**/*.*proj.user
+**/*.dbmdl
+**/*.jfm
+**/azds.yaml
+**/bin
+**/charts
+**/docker-compose*
+**/Dockerfile*
+**/node_modules
+**/npm-debug.log
+**/obj
+**/secrets.dev.yaml
+**/values.dev.yaml
+LICENSE
+README.md

+ 63 - 0
.gitattributes

@@ -0,0 +1,63 @@
+###############################################################################
+# Set default behavior to automatically normalize line endings.
+###############################################################################
+* text=false
+
+###############################################################################
+# Set default behavior for command prompt diff.
+#
+# This is need for earlier builds of msysgit that does not have it on by
+# default for csharp files.
+# Note: This is only used by command line
+###############################################################################
+#*.cs     diff=csharp
+
+###############################################################################
+# Set the merge driver for project and solution files
+#
+# Merging from the command prompt will add diff markers to the files if there
+# are conflicts (Merging from VS is not affected by the settings below, in VS
+# the diff markers are never inserted). Diff markers may cause the following 
+# file extensions to fail to load in VS. An alternative would be to treat
+# these files as binary and thus will always conflict and require user
+# intervention with every merge. To do so, just uncomment the entries below
+###############################################################################
+#*.sln       merge=binary
+#*.csproj    merge=binary
+#*.vbproj    merge=binary
+#*.vcxproj   merge=binary
+#*.vcproj    merge=binary
+#*.dbproj    merge=binary
+#*.fsproj    merge=binary
+#*.lsproj    merge=binary
+#*.wixproj   merge=binary
+#*.modelproj merge=binary
+#*.sqlproj   merge=binary
+#*.wwaproj   merge=binary
+
+###############################################################################
+# behavior for image files
+#
+# image files are treated as binary by default.
+###############################################################################
+#*.jpg   binary
+#*.png   binary
+#*.gif   binary
+
+###############################################################################
+# diff behavior for common document formats
+# 
+# Convert binary document formats to text before diffing them. This feature
+# is only available from the command line. Turn it on by uncommenting the 
+# entries below.
+###############################################################################
+#*.doc   diff=astextplain
+#*.DOC   diff=astextplain
+#*.docx  diff=astextplain
+#*.DOCX  diff=astextplain
+#*.dot   diff=astextplain
+#*.DOT   diff=astextplain
+#*.pdf   diff=astextplain
+#*.PDF   diff=astextplain
+#*.rtf   diff=astextplain
+#*.RTF   diff=astextplain

+ 261 - 0
.gitignore

@@ -0,0 +1,261 @@
+## Ignore Visual Studio temporary files, build results, and
+## files generated by popular Visual Studio add-ons.
+
+# User-specific files
+*.suo
+*.user
+*.userosscache
+*.sln.docstates
+
+# User-specific files (MonoDevelop/Xamarin Studio)
+*.userprefs
+
+# Build results
+[Dd]ebug/
+[Dd]ebugPublic/
+[Rr]elease/
+[Rr]eleases/
+x64/
+x86/
+bld/
+[Bb]in/
+[Oo]bj/
+[Ll]og/
+
+# Visual Studio 2015 cache/options directory
+.vs/
+# Uncomment if you have tasks that create the project's static files in wwwroot
+#wwwroot/
+
+# MSTest test Results
+[Tt]est[Rr]esult*/
+[Bb]uild[Ll]og.*
+
+# NUNIT
+*.VisualState.xml
+TestResult.xml
+
+# Build Results of an ATL Project
+[Dd]ebugPS/
+[Rr]eleasePS/
+dlldata.c
+
+# DNX
+project.lock.json
+project.fragment.lock.json
+artifacts/
+
+*_i.c
+*_p.c
+*_i.h
+*.ilk
+*.meta
+*.obj
+*.pch
+*.pdb
+*.pgc
+*.pgd
+*.rsp
+*.sbr
+*.tlb
+*.tli
+*.tlh
+*.tmp
+*.tmp_proj
+*.log
+*.vspscc
+*.vssscc
+.builds
+*.pidb
+*.svclog
+*.scc
+
+# Chutzpah Test files
+_Chutzpah*
+
+# Visual C++ cache files
+ipch/
+*.aps
+*.ncb
+*.opendb
+*.opensdf
+*.sdf
+*.cachefile
+*.VC.db
+*.VC.VC.opendb
+
+# Visual Studio profiler
+*.psess
+*.vsp
+*.vspx
+*.sap
+
+# TFS 2012 Local Workspace
+$tf/
+
+# Guidance Automation Toolkit
+*.gpState
+
+# ReSharper is a .NET coding add-in
+_ReSharper*/
+*.[Rr]e[Ss]harper
+*.DotSettings.user
+
+# JustCode is a .NET coding add-in
+.JustCode
+
+# TeamCity is a build add-in
+_TeamCity*
+
+# DotCover is a Code Coverage Tool
+*.dotCover
+
+# NCrunch
+_NCrunch_*
+.*crunch*.local.xml
+nCrunchTemp_*
+
+# MightyMoose
+*.mm.*
+AutoTest.Net/
+
+# Web workbench (sass)
+.sass-cache/
+
+# Installshield output folder
+[Ee]xpress/
+
+# DocProject is a documentation generator add-in
+DocProject/buildhelp/
+DocProject/Help/*.HxT
+DocProject/Help/*.HxC
+DocProject/Help/*.hhc
+DocProject/Help/*.hhk
+DocProject/Help/*.hhp
+DocProject/Help/Html2
+DocProject/Help/html
+
+# Click-Once directory
+publish/
+
+# Publish Web Output
+*.[Pp]ublish.xml
+*.azurePubxml
+# TODO: Comment the next line if you want to checkin your web deploy settings
+# but database connection strings (with potential passwords) will be unencrypted
+#*.pubxml
+*.publishproj
+
+# Microsoft Azure Web App publish settings. Comment the next line if you want to
+# checkin your Azure Web App publish settings, but sensitive information contained
+# in these scripts will be unencrypted
+PublishScripts/
+
+# NuGet Packages
+*.nupkg
+# The packages folder can be ignored because of Package Restore
+**/packages/*
+# except build/, which is used as an MSBuild target.
+!**/packages/build/
+# Uncomment if necessary however generally it will be regenerated when needed
+#!**/packages/repositories.config
+# NuGet v3's project.json files produces more ignoreable files
+*.nuget.props
+*.nuget.targets
+
+# Microsoft Azure Build Output
+csx/
+*.build.csdef
+
+# Microsoft Azure Emulator
+ecf/
+rcf/
+
+# Windows Store app package directories and files
+AppPackages/
+BundleArtifacts/
+Package.StoreAssociation.xml
+_pkginfo.txt
+
+# Visual Studio cache files
+# files ending in .cache can be ignored
+*.[Cc]ache
+# but keep track of directories ending in .cache
+!*.[Cc]ache/
+
+# Others
+ClientBin/
+~$*
+*~
+*.dbmdl
+*.dbproj.schemaview
+*.jfm
+*.pfx
+*.publishsettings
+node_modules/
+orleans.codegen.cs
+
+# Since there are multiple workflows, uncomment next line to ignore bower_components
+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
+#bower_components/
+
+# RIA/Silverlight projects
+Generated_Code/
+
+# Backup & report files from converting an old project file
+# to a newer Visual Studio version. Backup files are not needed,
+# because we have git ;-)
+_UpgradeReport_Files/
+Backup*/
+UpgradeLog*.XML
+UpgradeLog*.htm
+
+# SQL Server files
+*.mdf
+*.ldf
+
+# Business Intelligence projects
+*.rdl.data
+*.bim.layout
+*.bim_*.settings
+
+# Microsoft Fakes
+FakesAssemblies/
+
+# GhostDoc plugin setting file
+*.GhostDoc.xml
+
+# Node.js Tools for Visual Studio
+.ntvs_analysis.dat
+
+# Visual Studio 6 build log
+*.plg
+
+# Visual Studio 6 workspace options file
+*.opt
+
+# Visual Studio LightSwitch build output
+**/*.HTMLClient/GeneratedArtifacts
+**/*.DesktopClient/GeneratedArtifacts
+**/*.DesktopClient/ModelManifest.xml
+**/*.Server/GeneratedArtifacts
+**/*.Server/ModelManifest.xml
+_Pvt_Extensions
+
+# Paket dependency manager
+.paket/paket.exe
+paket-files/
+
+# FAKE - F# Make
+.fake/
+
+# JetBrains Rider
+.idea/
+*.sln.iml
+
+# CodeRush
+.cr/
+
+# Python Tools for Visual Studio (PTVS)
+__pycache__/
+*.pyc

+ 716 - 0
CAUtilLib/CAUtil.cs

@@ -0,0 +1,716 @@
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using System;
+using System.Diagnostics;
+using System.Diagnostics.Metrics;
+using System.Reflection;
+using System.Runtime.ConstrainedExecution;
+using System.Security.AccessControl;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+using System.Text;
+using System.Xml.Linq;
+
+namespace CAUtilLib
+{
+    public partial class CaUtil
+    {
+        /// <summary>
+        /// 建構子生成路徑可透過SetPath進行路徑覆寫
+        /// </summary>
+        public CaUtil(ILogger<CaUtil> logger)
+        {
+            if (string.IsNullOrWhiteSpace(this.path))
+            {
+                string path = Directory.GetCurrentDirectory();
+                //string parentPath = Directory.GetParent(Directory.GetParent(Directory.GetParent(Directory.GetParent(path).ToString()).ToString()).ToString()) + @"\";
+                string filePath = Path.Combine(path, "temp");
+                bool exists = Directory.Exists(filePath);
+                if (!exists)
+                {
+                    CreateFolder(filePath);
+                    MoveFile(@"zerova_v3.cnf", filePath + @"\zerova_v3.cnf");
+                }
+                this.path = filePath;
+            }
+
+            this.logger = logger;
+        }
+
+        private string path = "";
+        private readonly ILogger<CaUtil> logger;
+
+        /// <summary>
+        /// 檔案路徑
+        /// </summary>
+        public string SavePath
+        {
+            get => path;
+            set
+            {
+                if (this.path == value)
+                {
+                    return;
+                }
+
+                this.path = value;
+                string filePath = this.path;
+                CreateFolder(filePath);
+                MoveFile(@"zerova_v3.cnf", Path.Combine(filePath, @"zerova_v3.cnf"));
+            }
+        }
+
+        public OSType OS { get; set; }
+
+        private string ExecuteShell => OS == OSType.Windows ? "cmd.exe" : "/bin/bash";
+        private string CatCmd => OS == OSType.Windows ? "type " : "cat";
+
+        /// <summary>
+        /// 生成RootCA憑證
+        /// </summary>
+        /// <param name="name">Root CA</param>
+        /// <param name="Days">憑證有效期限</param>
+        /// <param name="SerialNumber">序號</param>
+        /// <param name="Cn">主機名或網站地址</param>
+        /// <param name="organizationName">組織名稱</param>
+        /// <param name="Hash">使用SHA256/SHA512演算法進行簽名</param>
+        /// <param name="RsaKey">生成2048位元或4096位元的私鑰</param>
+        /// <param name="Caformat">生成crt 或者pem 格式</param>
+        /// <returns></returns>
+        public async Task<bool> CreateRootCA(
+            string name, 
+            string commonName,
+            string organizationName,
+            string Country = "TW",
+            string State = "Taipei",
+            int Days = 3650,
+            string SerialNumber = "",
+            HashAlgorithm Hash = HashAlgorithm.SHA512,
+            OpensslGenrsaRsa RsaKey = OpensslGenrsaRsa.Key4096)
+        {
+            if (string.IsNullOrEmpty(SerialNumber))
+            {
+                SerialNumber = await GetOpenSSLRandSn();
+            }
+
+            var status = false;
+            var keyName = "";
+            keyName = name;
+            string sn = "0x" + SerialNumber;
+            HashAlgorithm ha = Hash;
+            var sslKey = (int)RsaKey;
+
+            var subjString = CreateSubjectString(CommonName: commonName, Organization: organizationName, Country: Country, State: State);
+
+            if (await CreateKey(keyName, RsaKey) &&
+                await ExecShellCmd("openssl", $"req -x509 -new -nodes -set_serial {sn} -key {keyName}.key -{ha} -days {Days} -subj \"{subjString}\" -out {name}.crt ") &&
+                await MergeFile($"{name}.pem", $"{name}.crt", $"{keyName}.key")
+                //&& await ExecShellCmd("certutil", $"-f -addstore root {RootCa}.crt ") 
+                //&& await ExecShellCmd("certutil", $"-f -addstore root {RootCa}.crt ")
+                )
+            {
+                return true;
+            }
+
+            return false;
+            //create
+            string[] strs = new string[5];
+            strs[0] = "openssl genrsa  -out " + keyName + ".key " + sslKey + " ";
+            strs[1] = "openssl req -x509 -new -nodes  -set_serial " + sn + " -key " + keyName + ".key  -" + ha + " -days " + Days + "  -subj \"/C=TW/ST=Taipei/O=" + organizationName + "/OU=phihong_sub.IO/CN=" + commonName + "/emailAddress=phihong_sub@mail.com\" -out " + name + ".crt ";
+            strs[2] = CatCmd + name + ".crt " + keyName + ".key > " + name + ".pem ";
+            strs[3] = "certutil -f -addstore root " + name + ".crt ";
+            strs[4] = "certutil -f -addstore root " + name + ".crt ";
+
+            for (int i = 0; i < strs.Length; i++)
+            {
+                var process = new Process
+                {
+                    StartInfo = new ProcessStartInfo
+                    {
+                        FileName = ExecuteShell,
+                        Arguments = "/C " + strs[i],
+                        WorkingDirectory = this.path,
+                        StandardOutputEncoding = Encoding.UTF8,
+                        RedirectStandardOutput = true,
+                        RedirectStandardError = true,
+                        UseShellExecute = false,
+                        Verb = "runas"
+                    }
+                };
+
+                process.Start();
+                _ = process.StandardOutput.ReadToEndAsync().ContinueWith(( t => {
+                    logger.LogTrace(t.Result);
+                }));
+                _ = process.StandardError.ReadToEndAsync().ContinueWith((t => {
+                    logger.LogTrace(t.Result);
+                }));
+                await process.WaitForExitAsync();
+                if (process.ExitCode != 0)
+                {
+                    return false;
+                }
+            }
+
+            return status;
+        }
+
+        /// <summary>
+        /// 生成SubCA子憑證
+        /// </summary>
+        /// <param name="RootCaName">CA憑證名稱</param>
+        /// <param name="Days">憑證有效期限</param>
+        /// <param name="SubCA">子憑證名稱</param>
+        /// <param name="SerialNumber">序號</param>
+        /// <param name="commonName">主機名或網站地址</param>
+        /// <param name="organizationName">組織名稱</param>
+        /// <param name="Hash">使用SHA256/SHA512演算法進行簽名</param>
+        /// <param name="RsaKey">生成2048位元或4096位元的私鑰</param>
+        /// <param name="Caformat">生成crt 或者pem 格式</param>
+        /// <returns></returns>        
+        public async Task<bool> CreateSubCA(string RootCa, string Days,
+        string SubCA, string SerialNumber, string commonName, string organizationName, HashAlgorithm Hash, OpensslGenrsaRsa RsaKey, Certificateformat Caformat)
+        {
+
+            if (string.IsNullOrEmpty(SerialNumber))
+            {
+                SerialNumber = await GetOpenSSLRandSn();
+            }
+
+            var status = false;
+            var str = "";
+            string sn = "0x" + SerialNumber;
+            //hashAlgorithm ha = Hash;
+            var sslKey = (int)RsaKey;
+            Certificateformat format = Caformat;
+
+            if (await ExecShellCmd("openssl", $"genrsa -out {SubCA}.key {sslKey}") &&
+                await ExecShellCmd("openssl", $"req -new -{Hash} -nodes -key {SubCA}.key -out {SubCA}.csr -subj \"/C=TW/ST=Taipei/O={organizationName}/OU=phihong_sub.IO/CN={commonName}/emailAddress=phihong_sub@mail.com\" ") &&
+                await ExecShellCmd("openssl", $"x509 -set_serial {sn} -req -in {SubCA}.csr -CA {RootCa}.crt -CAkey {RootCa}.key -CAcreateserial -out {SubCA}.crt -days {Days} -sha256 -extfile zerova_v3.cnf  ") &&
+                await MergeFile($"{SubCA}.pem", $"{SubCA}.crt", $"{SubCA}.key"))
+            {
+                return true;
+            }
+            return false;
+
+
+            string[] strs = new string[4];
+            strs[0] = "openssl genrsa -out " + SubCA + ".key " + sslKey + "  ";
+            strs[1] = "openssl req -new -" + Hash + " -nodes -key " + SubCA + ".key -out " + SubCA + ".csr -subj \"/C=TW/ST=Taipei/O=" + organizationName + "/OU=phihong_sub.IO/CN=" + commonName + "/emailAddress=phihong_sub@mail.com\" ";
+            //var str8 = "openssl x509 -set_serial " + sn + " -req -in " + subKeyName + ".csr -CA " + caPath + "\\" + crtName + ".crt -CAkey " + this.path + "\\" + rootCaName + ".key -CAcreateserial -out " + subKeyName + ".crt -days " + days + "   -sha256 -extfile zerova_v3.cnf  ";
+            strs[2] = "openssl x509 -set_serial " + sn + " -req -in " + SubCA + ".csr -CA " + RootCa + ".crt -CAkey " + this.path + "\\" + RootCa + ".key -CAcreateserial -out " + SubCA + ".crt -days " + Days + "   -sha256 -extfile zerova_v3.cnf  ";
+            strs[3] = CatCmd + SubCA + ".crt " + SubCA + ".key > " + SubCA + ".pem ";
+
+
+            for (int i = 0; i < strs.Length; i++)
+            {
+                var process = new Process
+                {
+                    StartInfo = new ProcessStartInfo
+                    {
+                        FileName = ExecuteShell,
+                        Arguments = "/C " + strs[i],
+                        WorkingDirectory = this.path,//"D:\\project\\vs\\ConsoleApp2",
+                        RedirectStandardOutput = true,
+                        UseShellExecute = false,
+                        Verb = "runas"
+                    }
+                };
+
+                process.Start();
+                string output = process.StandardOutput.ReadToEnd();
+                await process.WaitForExitAsync();
+            }
+            return status;
+        }
+
+        public async Task<bool> SignCsr(string SubCA, string RootCa, int? Days = null, string? SerialNumber = null)
+        {
+            Days ??= 3650;
+
+            if (string.IsNullOrEmpty(SerialNumber))
+            {
+                SerialNumber = await GetOpenSSLRandSn();
+            }
+            string sn = "0x" + SerialNumber;
+
+            if (await ExecShellCmd("openssl", $"x509 -set_serial {sn} -req -in {SubCA}.csr -CA {RootCa}.crt -CAkey {RootCa}.key -CAcreateserial -out {SubCA}.crt -days {Days} -sha256 -extfile zerova_v3.cnf  "))
+            {
+                return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// 生成Csr 檔案
+        /// </summary>
+        /// <param name="Key">key 名稱</param>
+        /// <param name="Csr">csr 名稱</param>
+        /// <param name="commonName">主機名或網站地址</param>
+        /// <param name="organizationName">組織名稱</param>
+        /// <returns></returns>
+        public async Task<bool> CreateCsr(
+            string Key,
+            string Csr,
+            string commonName, 
+            string organizationName,
+            HashAlgorithm Hash = HashAlgorithm.SHA512)
+        {
+            var status = false;
+            var subjString = CreateSubjectString(CommonName: commonName, Organization: organizationName, Country: "TW", State: "Taipei");
+
+            if (await ExecShellCmd("openssl", $"req -new -{Hash} -nodes -key {Key}.key -out {Csr}.csr -subj \"{subjString}\" "))
+            {
+                return true;
+            }
+            return false;
+
+            string[] strs = new string[4];
+            strs[0] = "openssl req -new -SHA256 -nodes -key " + Key + ".key -out " + Csr + ".csr -subj \"/C=TW/ST=Taipei/O=" + organizationName + "/OU=phihong_sub.IO/CN=" + commonName + "/emailAddress=phihong_sub@mail.com\"  ";
+            for (int i = 0; i < strs.Length; i++)
+            {
+                var process = new Process
+                {
+                    StartInfo = new ProcessStartInfo
+                    {
+                        FileName = ExecuteShell,
+                        Arguments = "/C " + strs[i],
+                        WorkingDirectory = this.path,//"D:\\project\\vs\\ConsoleApp2",
+                        RedirectStandardOutput = true,
+                        UseShellExecute = false,
+                        Verb = "runas"
+                    }
+                };
+
+                process.Start();
+                string output = process.StandardOutput.ReadToEnd();
+                await process.WaitForExitAsync();
+            }
+            return status;
+        }
+
+        private object CreateSubjectString(string CommonName = "", string Organization = "", string Country = "", string State = "")
+        {
+            ///C=TW/ST=Taipei/O={organizationName}/OU=phihong_sub.IO/CN={commonName}/emailAddress=phihong_sub@mail.com\
+            var toReturn = string.Empty;
+            if (!string.IsNullOrEmpty(Country))
+            {
+                toReturn += $"/C={Country}";
+            }
+            if (!string.IsNullOrEmpty(State))
+            {
+                toReturn += $"/ST={State}";
+            }
+            if (!string.IsNullOrEmpty(Organization))
+            {
+                toReturn += $"/O={Organization}";
+            }
+            if (!string.IsNullOrEmpty(CommonName))
+            {
+                toReturn += $"/CN={CommonName}";
+            }
+            return toReturn;
+        }
+
+        /// <summary>
+        /// 讀取憑證資訊
+        /// </summary>
+        /// <param name="path">檔案名稱</param>
+        /// <param name="fileName">檔案路徑</param>
+        /// <returns></returns>
+        public string ReadCertificateHashData(string path, string fileName)//改成接收pem string 格式
+        {
+            var json = "";
+            var file = Path.Combine(path, fileName);
+            X509Certificate2 certificate = new X509Certificate2(file);
+
+
+            X509Extension extension = certificate.Extensions["2.5.29.35"];
+            var issuer_key_hash = "";
+            if (extension != null)
+            {
+                issuer_key_hash = extension.Format(true);
+            }
+
+            string hashAlgorithm = certificate.SignatureAlgorithm.FriendlyName;
+
+
+            string serialNumber = certificate.SerialNumber;
+            byte[] issuerDER = certificate.IssuerName.RawData;
+            SHA1 sha1 = SHA1.Create();
+            byte[] hashBytes = sha1.ComputeHash(issuerDER);
+
+            var data = new
+            {
+                hashAlgorithm = hashAlgorithm,
+                issuerNameHash = BitConverter.ToString(hashBytes).Replace("-", ""),
+                issuerKeyHash = issuer_key_hash,
+                serialNumber = serialNumber,
+                thumbprint = certificate.Thumbprint
+            };
+            string thumbprint = certificate.Thumbprint;
+
+            json = JsonConvert.SerializeObject(data);
+            return json;
+        }
+
+        /// <summary>
+        /// 讀取憑證資訊
+        /// </summary>
+        /// <param name="FileName">檔案名稱</param>
+        /// <returns></returns>
+        public string ReadCertificateHashData(string fileName)
+        {
+            var json = "";
+            var file = Path.Combine(this.path, fileName);
+            X509Certificate2 certificate = new X509Certificate2(file);
+
+            X509Extension extension = certificate.Extensions["2.5.29.35"];
+            var issuer_key_hash = "";
+            if (extension != null)
+            {
+                issuer_key_hash = extension.Format(true);
+            }
+
+            string hashAlgorithm = certificate.SignatureAlgorithm.FriendlyName;
+
+            string serialNumber = certificate.SerialNumber;
+            byte[] issuerDER = certificate.IssuerName.RawData;
+            SHA1 sha1 = SHA1.Create();
+            byte[] hashBytes = sha1.ComputeHash(issuerDER);
+
+            var data = new
+            {
+                hashAlgorithm,
+                issuerNameHash = BitConverter.ToString(hashBytes).Replace("-", ""),
+                issuerKeyHash = issuer_key_hash,
+                serialNumber,
+                thumbprint = certificate.Thumbprint
+            };
+            string thumbprint = certificate.Thumbprint;
+
+            json = JsonConvert.SerializeObject(data);
+            return json;
+        }
+
+        /// <summary>
+        /// PEM 格式的憑證的內容
+        /// </summary>
+        /// <param name="str">憑證內容</param>
+        /// <returns></returns>
+        public string ReadCertificateHashDataByString(string str)
+        {
+            var json = "";
+            byte[] UTF8bytes = Encoding.UTF8.GetBytes(str);
+            X509Certificate2 certificate = new X509Certificate2(UTF8bytes);
+
+
+            X509Extension extension = certificate.Extensions["2.5.29.35"];
+            var issuer_key_hash = "";
+            if (extension != null)
+            {
+                issuer_key_hash = extension.Format(true);
+            }
+
+            string hashAlgorithm = certificate.SignatureAlgorithm.FriendlyName;
+
+
+            string serialNumber = certificate.SerialNumber;
+            byte[] issuerDER = certificate.IssuerName.RawData;
+            SHA1 sha1 = SHA1.Create();
+            byte[] hashBytes = sha1.ComputeHash(issuerDER);
+
+            var data = new
+            {
+                hashAlgorithm = hashAlgorithm,
+                issuerNameHash = BitConverter.ToString(hashBytes).Replace("-", ""),
+                issuerKeyHash = issuer_key_hash,
+                serialNumber = serialNumber,
+                thumbprint = certificate.Thumbprint
+            };
+            string thumbprint = certificate.Thumbprint;
+
+            json = JsonConvert.SerializeObject(data);
+            return json;
+        }
+
+
+        /// <summary>
+        /// 驗證client憑證
+        /// </summary>
+        /// <param name="StrCA">CA憑證</param>
+        /// <param name="StrSub">子憑證</param>
+        /// <param name="Url">url localhost:3000</param>
+        /// <returns></returns>
+        public async Task<bool> VerifyClient(string CA, string SubCA, string Url)
+        {
+            var str = "";
+            bool isExists = false;
+            if (await ExecShellCmd("openssl", $"s_client -connect {Url} -CAfile {CA} -cert {SubCA} -tls1_2 -state"))
+            {
+                return true;
+            }
+            return false;
+
+
+            var str1 = "openssl s_client -connect " + Url + " -CAfile " + CA + " -cert " + SubCA + " -tls1_2 -state  ";
+
+            str = await ClientCmd(str1);
+            string searchString = "(ok)";
+            string line = GetLineFromString(str, searchString);
+            if (line != null)
+                isExists = line.Contains(searchString);
+            return isExists;
+        }
+
+        /// <summary>
+        /// Cmd字串指令
+        /// </summary>
+        /// <param name="str1">字串指令</param>
+        /// <returns></returns>
+        private async Task<string> ClientCmd(string str1)
+        {
+            var status = false;
+
+            var str = "";
+            string[] strs = { str1 };
+            for (int i = 0; i < strs.Length; i++)
+            {
+                var process = new Process
+                {
+                    StartInfo = new ProcessStartInfo
+                    {
+                        FileName = ExecuteShell,
+                        Arguments = "/C " + strs[i],
+                        WorkingDirectory = this.path,//"D:\\project\\vs\\ConsoleApp2",
+                        RedirectStandardOutput = true,
+                        UseShellExecute = false,
+                        //Verb = "runas"
+                    }
+                };
+
+                process.Start();
+                var output = new List<string>();
+                while (process.StandardOutput.Peek() > -1)
+                {
+                    output.Add(process.StandardOutput.ReadLine());
+                }
+
+                process.Kill();
+                str = string.Join("", output.ToArray());
+
+            }
+            return str;
+        }
+
+        /// <summary>
+        ///  將Root CA與Sub CA進行憑證驗證
+        /// </summary>
+        /// <param name="Ca">CA檔案名稱</param>
+        /// <param name="Sub">SubCA憑證名稱</param>
+        /// <returns>True/Flase</returns>
+        public async Task<bool> VerifyCertificateByCertificate(string Sub, string Ca)
+        {
+            if (await ExecShellCmd("openssl", $"verify -CAfile {Ca} {Sub}"))
+            {
+                return true;
+            }
+            return false;
+
+            var status = true;
+            var str = "";
+            var str1 = "openssl verify -CAfile " + Ca + " " + Sub;
+
+            bool isExists = false;
+            str = await ClientCmd(str1);
+            string searchString = "OK";
+            string line = GetLineFromString(str, searchString);
+            if (line != null)
+                isExists = line.Contains(searchString);
+            return isExists;
+        }
+
+        public async Task<bool> VerifyCertificateByCertificates(string Sub, string CaPath)
+        {
+            if (await ExecShellCmd("openssl", $"verify -CApath {CaPath} {Sub}"))
+            {
+                return true;
+            }
+            return false;
+        }
+
+        public async Task<bool> CalculateHash(string CrtName,string hashName)
+        {
+            if (await ExecShellCmd("openssl", $"x509 -in {CrtName} -hash -noout -out {hashName}"))
+            {
+                return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// Crt轉Pem
+        /// </summary>
+        /// <param name="CaName">檔案名稱</param>
+        /// <returns></returns>
+        public async Task<bool> CrtToPem(string CaName)
+        {
+            if (await ExecShellCmd("openssl", $"x509 -in {CaName}.crt -out {CaName}.pem"))
+            {
+                return true;
+            }
+            return false;
+
+            var status = false;
+            var str = "openssl x509 -in " + CaName + ".crt -out " + CaName + ".pem  ";
+
+            var process = new Process
+            {
+                StartInfo = new ProcessStartInfo
+                {
+                    FileName = ExecuteShell,
+                    Arguments = "/C " + str,
+                    WorkingDirectory = this.path,
+                    RedirectStandardOutput = true,
+                    UseShellExecute = false,
+                    Verb = "runas"
+                }
+            };
+
+            process.Start();
+            string output = process.StandardOutput.ReadToEnd();
+            await process.WaitForExitAsync();
+            return status;
+        }
+
+        /// <summary>
+        /// Pem轉Crt
+        /// </summary>
+        /// <param name="CaName">檔案名稱</param>
+        /// <returns></returns>
+        public async Task<bool> PemToCrt(string CaName)
+        {
+            if (await ExecShellCmd("openssl", $"x509 -outform der -in {CaName}.pem -out {CaName}.crt"))
+            {
+                return true;
+            }
+            return false;
+
+            var status = false;
+            var str = "openssl x509 -outform der -in " + CaName + ".pem -out " + CaName + ".crt  ";
+
+            var process = new Process
+            {
+                StartInfo = new ProcessStartInfo
+                {
+                    FileName = ExecuteShell,
+                    Arguments = "/C " + str,
+                    WorkingDirectory = this.path,
+                    RedirectStandardOutput = true,
+                    UseShellExecute = false,
+                    Verb = "runas"
+                }
+            };
+
+            process.Start();
+            string output = process.StandardOutput.ReadToEnd();
+            await process.WaitForExitAsync();
+            return status;
+        }
+
+        public async Task<bool> CreateKey(string keyName,
+            OpensslGenrsaRsa RsaKey = OpensslGenrsaRsa.Key4096)
+        {
+            var sslKey = (int)RsaKey;
+            if (await ExecShellCmd("openssl", $"genrsa -out {keyName}.key {sslKey}"))
+            {
+                return true;
+            }
+            return false;
+        }
+
+        /// <summary>
+        /// 產生資料夾
+        /// </summary>
+        /// <param name="path"></param>
+        /// <returns></returns>
+        private bool CreateFolder(string path)
+        {
+            string subPath = path;
+            bool status = true;
+            bool exists = Directory.Exists(subPath);
+            status = !exists;
+
+            if (status)
+            {
+                Directory.CreateDirectory(subPath);
+            }
+
+            return status;
+        }
+
+        /// <summary>
+        /// 確認檔案是否存在
+        /// </summary>
+        /// <param name="filePath"></param>
+        /// <returns></returns>
+        private bool CheckFiles(string FilePath)
+        {
+            bool status = true;
+            bool exists = File.Exists(FilePath);
+            status = exists;
+            return status;
+        }
+
+        /// <summary>
+        /// 檔案搬移
+        /// </summary>
+        /// <param name="file"></param>
+        /// <param name="moveTo"></param>
+        /// <returns></returns>
+        private bool MoveFile(string file, string moveTo)
+        {
+            bool status = false;
+            if (CheckFiles(file) &&
+                !CheckFiles(moveTo))
+            {
+                //File.Move(file, moveTo);
+                File.Copy(file, moveTo, true);
+                status = true;
+            }
+            else
+            {
+
+            }
+            return status;
+        }
+
+        /// <summary>
+        /// 取得第幾行的字串
+        /// </summary>
+        /// <param name="InputString"></param>
+        /// <param name="SearchString"></param>
+        /// <returns></returns>
+        private static string GetLineFromString(string InputString, string SearchString)
+        {
+            using (StringReader reader = new StringReader(InputString))
+            {
+                int lineNumber = 1;
+                string line = "";
+                while ((line = reader.ReadLine()) != null)
+                {
+                    if (line.Contains(SearchString))
+                    {
+                        return line;
+                    }
+                    lineNumber++;
+                }
+
+                return reader.ReadLine();
+            }
+        }
+
+
+
+    }
+}

+ 14 - 0
CAUtilLib/CAUtilLib.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.0" />
+    <PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
+  </ItemGroup>
+
+</Project>

+ 107 - 0
CAUtilLib/CaUtil_1.cs

@@ -0,0 +1,107 @@
+using Microsoft.Extensions.Logging;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CAUtilLib
+{
+    public class ExecShellCmdResult
+    {
+        public string StdOutPut { get; set; } = string.Empty;
+        public string StdErrOutPut { get; set; } = string.Empty;
+        public int? ExitCode { get; set; } = null;
+        public bool IsSuccess => ExitCode == 0;
+
+        public static implicit operator bool(ExecShellCmdResult result) => result.IsSuccess;
+    }
+
+    public partial class CaUtil
+    {
+        public async Task<bool> MergeFile(string outFile, string inFile1, string inFile2)
+        {
+            outFile = Path.Combine(path, outFile);
+            inFile1 = Path.Combine(path, inFile1);
+            inFile2 = Path.Combine(path, inFile2);
+            try
+            {
+                if (!File.Exists(inFile1) ||
+                    !File.Exists(inFile2))
+                {
+                    return false;
+                }
+
+                var oStream = File.OpenWrite(outFile);
+                await File.OpenRead(inFile1).CopyToAsync(oStream);
+                await File.OpenRead(inFile2).CopyToAsync(oStream);
+                oStream.Close();
+                return true;
+            }
+            catch (Exception e)
+            {
+                logger.LogCritical(e.Message);
+            }
+            return false;
+        }
+
+        private async Task<string> GetOpenSSLRandSn()
+        {
+            var result = await ExecShellCmd("openssl", "rand -hex 8");
+            return result ? result.StdOutPut.Trim() : "" ;
+        }
+
+        public async Task<ExecShellCmdResult> ExecShellCmd(string fileName, string arguments)
+        {
+            var toReturn = new ExecShellCmdResult();
+
+            Process process = new Process();
+            process.EnableRaisingEvents = true;
+            //process.OutputDataReceived += Process_OutputDataReceived;
+            //process.ErrorDataReceived += Process_ErrorDataReceived;
+            // set the process start info
+            process.StartInfo.FileName = fileName; // specify the command to run
+            process.StartInfo.Arguments = arguments; // specify the arguments
+
+            // set additional process start info as necessary
+            process.StartInfo.WorkingDirectory = this.path;
+            process.StartInfo.UseShellExecute = false;
+            process.StartInfo.RedirectStandardInput = true;
+            process.StartInfo.RedirectStandardOutput = true;
+            process.StartInfo.RedirectStandardError = true;
+
+            // start the process
+            var startResult = process.Start();
+
+            // wait for the process to exit
+            await process.WaitForExitAsync();
+            //process.BeginOutputReadLine();
+            //process.BeginErrorReadLine();
+
+            toReturn.StdOutPut = await process.StandardOutput.ReadToEndAsync();
+            toReturn.StdErrOutPut = await process.StandardError.ReadToEndAsync();
+            toReturn.ExitCode = process.ExitCode;
+
+            Console.WriteLine(toReturn.StdOutPut);
+            Console.WriteLine(toReturn.StdErrOutPut);
+
+            return toReturn;
+            //return process.ExitCode == 0;
+
+            //void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
+            //{
+            //    toReturn.StdOutPut += e.Data;
+            //    logger.LogTrace(e.Data);
+            //    Console.WriteLine(e.Data);
+            //}
+
+            //void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
+            //{
+            //    toReturn.StdErrOutPut += e.Data;
+            //    logger.LogTrace(e.Data);
+            //    Console.WriteLine(e.Data);
+            //}
+        }
+    }
+}

+ 41 - 0
CAUtilLib/Types.cs

@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CAUtilLib
+{
+    /// <summary>
+    /// 使用SHA256/SHA512演算法進行簽名
+    /// </summary>
+    public enum HashAlgorithm
+    {
+        SHA256,
+        SHA512
+    }
+
+    /// <summary>
+    /// 生成crt 或者pem 格式
+    /// </summary>
+    public enum Certificateformat
+    {
+        Crt,
+        Pem
+    }
+
+    /// <summary>
+    /// 生成2048位元或4096位元的私鑰
+    /// </summary>
+    public enum OpensslGenrsaRsa : int
+    {
+        Key2048 = 2048,
+        Key4096 = 4096
+    }
+
+    public enum OSType
+    {
+        Windows,
+        Linux,
+    }
+}

+ 31 - 0
CertificateAutorityServer.sln

@@ -0,0 +1,31 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.7.34031.279
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CertificateAutorityServer", "CertificateAutorityServer\CertificateAutorityServer.csproj", "{F629C7F3-1011-4827-8B8D-5F4CFFBEE486}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CAUtilLib", "CAUtilLib\CAUtilLib.csproj", "{C53BE81A-6599-46E5-98E3-BF04E7A54EE9}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{F629C7F3-1011-4827-8B8D-5F4CFFBEE486}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F629C7F3-1011-4827-8B8D-5F4CFFBEE486}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F629C7F3-1011-4827-8B8D-5F4CFFBEE486}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F629C7F3-1011-4827-8B8D-5F4CFFBEE486}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C53BE81A-6599-46E5-98E3-BF04E7A54EE9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C53BE81A-6599-46E5-98E3-BF04E7A54EE9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C53BE81A-6599-46E5-98E3-BF04E7A54EE9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C53BE81A-6599-46E5-98E3-BF04E7A54EE9}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {B088E38F-ACD3-4163-95D7-B6F0E4252048}
+	EndGlobalSection
+EndGlobal

+ 25 - 0
CertificateAutorityServer/Assist/TempPhysicalFileResult.cs

@@ -0,0 +1,25 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Net.Http.Headers;
+
+namespace CertificateAutorityServer.Assist
+{
+    public class TempPhysicalFileResult : PhysicalFileResult
+    {
+        public TempPhysicalFileResult(string fileName, string contentType)
+                     : base(fileName, contentType) { }
+        public TempPhysicalFileResult(string fileName, MediaTypeHeaderValue contentType)
+                     : base(fileName, contentType) { }
+
+        public override async Task ExecuteResultAsync(ActionContext context)
+        {
+            try
+            {
+                await base.ExecuteResultAsync(context);
+            }
+            finally
+            {
+                File.Delete(FileName);
+            }
+        }
+    }
+}

+ 27 - 0
CertificateAutorityServer/CertificateAutorityServer.csproj

@@ -0,0 +1,27 @@
+<Project Sdk="Microsoft.NET.Sdk.Web">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UserSecretsId>dc292c97-bc84-4a9d-80eb-a3d14efdcc4c</UserSecretsId>
+    <DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="7.0.11" />
+    <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.19.4" />
+    <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\CAUtilLib\CAUtilLib.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <None Update="zerova_v3.cnf">
+      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+    </None>
+  </ItemGroup>
+
+</Project>

+ 59 - 0
CertificateAutorityServer/Controllers/CertificateController.cs

@@ -0,0 +1,59 @@
+using CAUtilLib;
+using CertificateAutorityServer.Assist;
+using CertificateAutorityServer.Model.Rest;
+using CertificateAutorityServer.Service;
+using Microsoft.AspNetCore.Mvc;
+
+namespace CertificateAutorityServer.Controllers
+{
+    [ApiController]
+    [Route("cert")]
+    public class CertificateController : Controller
+    {
+        private readonly CertificateService certificateService;
+
+        public CertificateController(CertificateServiceFactory certificateServiceFactory)
+        {
+            this.certificateService = certificateServiceFactory.Create();
+        }
+
+        [HttpGet("RootCA")]
+        public IActionResult GetRootCAList()
+        {
+            return Ok();
+            //return Ok(certificateService.GetRootCAList());
+        }
+
+        [HttpGet("RootCA/{name}")]
+        public IActionResult GetRootCAPem(string name)
+        {
+            return Ok();
+        }
+
+        [HttpPost("RootCA")]
+        public async Task<IActionResult> CreateRootCA([FromBody] CreateRootCaReq req)
+        {
+            return Ok();
+            //var result = await certificateService.CreateRootCA(req);
+            //return result ? Ok() : Problem();
+        }
+
+        [HttpPost("SubCA")]
+        public async Task<IActionResult> CreateSubCA([FromBody] CreateSubCaReq req)
+        {
+            return Ok();
+            //var result = await certificateService.CreateSubCA(req);
+            //return result ? Ok() : Problem();
+        }
+
+        [HttpDelete("RootCA/{name}")]
+        public IActionResult RemoveRootCA(string name)
+        {
+            return Ok();
+            //certificateService.RemoveRootCA(name);
+            //return Ok();
+        }
+
+        /////////////////////////////////////////////
+    }
+}

+ 85 - 0
CertificateAutorityServer/Controllers/cert/CrtController.cs

@@ -0,0 +1,85 @@
+using CertificateAutorityServer.Model.Rest;
+using CertificateAutorityServer.Service;
+using Microsoft.AspNetCore.Mvc;
+
+namespace CertificateAutorityServer.Controllers.cert
+{
+    [Route("cert/crt")]
+    public class CrtController : Controller
+    {
+        private readonly CertificateService certificateService;
+
+        public CrtController(CertificateServiceFactory certificateServiceFactory)
+        {
+            this.certificateService = certificateServiceFactory.Create();
+        }
+
+        [HttpGet]
+        public async Task<IActionResult> CreateCrt([FromBody] CreateCrtReq req)
+        {
+            if (req is null)
+            {
+                return Problem();
+            }
+
+            bool createKey = string.IsNullOrEmpty(req.key);
+            string? createKeyResult = await GetOrCreateKey(req.key);
+            if (createKeyResult == null) { 
+                return Problem();
+            }
+            string key = createKeyResult;
+
+            string? createCsrResult = await certificateService.CreatCsr(new CreateCsrReq()
+            {
+                Key = key,
+                CommonName = req.CommonName,
+                OrganizationName = req.OrganizationName
+            });
+            if (createCsrResult is null)
+            {
+                return Problem();
+            }
+
+            string? signCsrResult = await certificateService.SignCsr(new SignCsrReq() { Csr = createCsrResult });
+            if (signCsrResult is null)
+            {
+                return Problem();
+            }
+            return Ok(createKey ? key + signCsrResult : signCsrResult);
+        }
+
+        [HttpPost]
+        public async Task<IActionResult> AddTrustedCertificate([FromBody] AddTrustedCertificateReq req)
+        {
+            bool result = await certificateService.AddTrustedCertificate(req.Crt);
+            return result ? Ok() : Problem();
+        }
+
+        [HttpPut]
+        public async Task<IActionResult> CheckCrt([FromBody] CheckCrtReq req)
+        {
+            if (string.IsNullOrEmpty(req.Crt))
+            {
+                return BadRequest();
+            }
+
+            int result = await certificateService.VerifyCrt(req);
+            return result != -1 ? Ok(result) : Problem(result.ToString());
+        }
+
+        private async ValueTask<string?> GetOrCreateKey(string key)
+        {
+            if (!string.IsNullOrEmpty(key))
+            {
+                return key;
+            }
+
+            var createKeyResult = await certificateService.CreatKey();
+            if (createKeyResult is null)
+            {
+                return null;
+            }
+            return createKeyResult;
+        }
+    }
+}

+ 41 - 0
CertificateAutorityServer/Controllers/cert/CsrController.cs

@@ -0,0 +1,41 @@
+using CertificateAutorityServer.Model.Rest;
+using CertificateAutorityServer.Service;
+using Microsoft.AspNetCore.Mvc;
+
+namespace CertificateAutorityServer.Controllers.cert
+{
+    [Route("cert/csr")]
+    public class CsrController : Controller
+    {
+        private readonly CertificateService certificateService;
+
+        public CsrController(CertificateServiceFactory certificateServiceFactory)
+        {
+            this.certificateService = certificateServiceFactory.Create();
+        }
+
+        [HttpGet]
+        public async Task<IActionResult> CreatCsr([FromBody] CreateCsrReq req)
+        {
+            if (string.IsNullOrEmpty(req.Key))
+            {
+                return BadRequest();
+            }
+
+            string? result = await certificateService.CreatCsr(req);
+            return result is null ? Problem() : Ok(result);
+        }
+
+        [HttpPost]
+        public async Task<IActionResult> SignCsr([FromBody] SignCsrReq req)
+        {
+            if (string.IsNullOrEmpty(req.Csr))
+            {
+                return BadRequest();
+            }
+
+            string? result = await certificateService.SignCsr(req);
+            return result is null ? Problem() : Ok(result);
+        }
+    }
+}

+ 23 - 0
CertificateAutorityServer/Controllers/cert/KeyController.cs

@@ -0,0 +1,23 @@
+using CertificateAutorityServer.Service;
+using Microsoft.AspNetCore.Mvc;
+
+namespace CertificateAutorityServer.Controllers.cert
+{
+    [Route("cert/key")]
+    public class KeyController : Controller
+    {
+        private readonly CertificateService certificateService;
+
+        public KeyController(CertificateServiceFactory certificateServiceFactory)
+        {
+            this.certificateService = certificateServiceFactory.Create();
+        }
+
+        [HttpGet]
+        public async Task<IActionResult> CreatKey()
+        {
+            string? result = await certificateService.CreatKey();
+            return result is null ? Problem() : Ok(result);
+        }
+    }
+}

+ 22 - 0
CertificateAutorityServer/Dockerfile

@@ -0,0 +1,22 @@
+#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
+WORKDIR /app
+EXPOSE 80
+EXPOSE 443
+
+FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
+WORKDIR /src
+COPY ["CertificateAutorityServer/CertificateAutorityServer.csproj", "CertificateAutorityServer/"]
+RUN dotnet restore "CertificateAutorityServer/CertificateAutorityServer.csproj"
+COPY . .
+WORKDIR "/src/CertificateAutorityServer"
+RUN dotnet build "CertificateAutorityServer.csproj" -c Release -o /app/build
+
+FROM build AS publish
+RUN dotnet publish "CertificateAutorityServer.csproj" -c Release -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+ENTRYPOINT ["dotnet", "CertificateAutorityServer.dll"]

+ 20 - 0
CertificateAutorityServer/Model/Config/CerServiceConfig.cs

@@ -0,0 +1,20 @@
+namespace CertificateAutorityServer.Model.Config
+{
+    public class CerServiceConfig
+    {
+        public string RootPath { get; set; } = "/home/cert";
+        internal string RcaDirectoryName { get; set; } = "rca";
+        internal string TrustedDirectoryName { get; set; } = "tcrt";
+        public RcaConfig DefaultRcaConfig { get; set; } = new RcaConfig();
+    }
+
+    public class RcaConfig
+    {
+        public string Name { get; set; } = "rca";
+        public int Days { get; set; } = 36500;
+        public string CommonName { get; set; } = "localhost";
+        public string OrganiaztionName { get; set; } = "zerovatech";
+        public string Country { get; set; } = "TW";
+        public string State { get; set; } = "Taipei";
+    }
+}

+ 7 - 0
CertificateAutorityServer/Model/Rest/AddTrustedCertificateReq.cs

@@ -0,0 +1,7 @@
+namespace CertificateAutorityServer.Model.Rest
+{
+    public class AddTrustedCertificateReq
+    {
+        public required string Crt { get; set; }
+    }
+}

+ 8 - 0
CertificateAutorityServer/Model/Rest/CheckCrtReq.cs

@@ -0,0 +1,8 @@
+namespace CertificateAutorityServer.Model.Rest
+{
+    public class CheckCrtReq
+    {
+        public required string Crt { get; set; }
+        public string? Rca { get; set; }
+    }
+}

+ 9 - 0
CertificateAutorityServer/Model/Rest/CreateCrtReq.cs

@@ -0,0 +1,9 @@
+namespace CertificateAutorityServer.Model.Rest
+{
+    public class CreateCrtReq
+    {
+        public string key { get; set; } = ""; 
+        public required string CommonName { get; set; }
+        public required string OrganizationName { get; set; }
+    }
+}

+ 9 - 0
CertificateAutorityServer/Model/Rest/CreateCsrReq.cs

@@ -0,0 +1,9 @@
+namespace CertificateAutorityServer.Model.Rest
+{
+    public class CreateCsrReq
+    {
+        public required string Key { get; set; }
+        public required string CommonName { get; set; }
+        public required string OrganizationName { get; set; }
+    }
+}

+ 17 - 0
CertificateAutorityServer/Model/Rest/CreateRootCaReq.cs

@@ -0,0 +1,17 @@
+using CAUtilLib;
+
+namespace CertificateAutorityServer.Model.Rest
+{
+    public class CreateRootCaReq
+    {
+        public required string Name { get; set; }
+        public required string CommonName { get; set; }
+        public int Days { get; set; } = 365;
+        public string SerialNumber { get; set; } = string.Empty;
+        public string Country { get; set; } = string.Empty;
+        public string State { get; set; } = string.Empty;
+        public string OrganizationName { get; set; } = string.Empty;
+        public HashAlgorithm Hash { get; set; } = HashAlgorithm.SHA512;
+        public OpensslGenrsaRsa RsaKey { get; set; } = OpensslGenrsaRsa.Key4096;
+    }
+}

+ 17 - 0
CertificateAutorityServer/Model/Rest/CreateSubCaReq.cs

@@ -0,0 +1,17 @@
+using CAUtilLib;
+
+namespace CertificateAutorityServer.Model.Rest
+{
+    public class CreateSubCaReq
+    {
+        public string Name { get; set; } = string.Empty;
+        public string RootCa { get; set; } = string.Empty;
+        public string Days { get; set; } = string.Empty;
+        public string SerialNumber { get; set; } = string.Empty;
+        public string CommonName { get; set; } = string.Empty;
+        public string OrganizationName { get; set; } = string.Empty;
+        public HashAlgorithm Hash { get; set; } = HashAlgorithm.SHA256;
+        public OpensslGenrsaRsa RsaKey { get; set; } = OpensslGenrsaRsa.Key2048;
+        public Certificateformat Caformat { get; set; } = Certificateformat.Pem;
+    }
+}

+ 13 - 0
CertificateAutorityServer/Model/Rest/SignCsrReq.cs

@@ -0,0 +1,13 @@
+namespace CertificateAutorityServer.Model.Rest
+{
+    public class SignCsrReq
+    {
+        public required string Csr { get; set; }
+        //public string? Rca { get; set; }
+        //public string? RcaKey { get; set; }
+        //public required string Name { get; set; }
+        //public IFormFile? CsrFile { get; set; }
+        //public int? Days { get; set; }
+        //public string? SerialNumber { get; set; }
+    }
+}

+ 28 - 0
CertificateAutorityServer/Program.cs

@@ -0,0 +1,28 @@
+WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
+
+// Add services to the container.
+builder.AddCertificateService();
+
+builder.Services.AddControllers();
+// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
+builder.Services.AddEndpointsApiExplorer();
+builder.Services.AddSwaggerGen();
+builder.Logging.AddConsole();
+
+var app = builder.Build();
+
+// Configure the HTTP request pipeline.
+if (app.Environment.IsDevelopment())
+{
+    app.UseSwagger();
+    app.UseSwaggerUI();
+}
+
+
+//app.UseHttpsRedirection();
+
+app.UseAuthorization();
+
+app.MapControllers();
+
+app.Run();

+ 51 - 0
CertificateAutorityServer/Properties/launchSettings.json

@@ -0,0 +1,51 @@
+{
+  "profiles": {
+    "http": {
+      "commandName": "Project",
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      },
+      "dotnetRunMessages": true,
+      "applicationUrl": "http://localhost:5156"
+    },
+    "https": {
+      "commandName": "Project",
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      },
+      "dotnetRunMessages": true,
+      "applicationUrl": "https://localhost:7062;http://localhost:5156"
+    },
+    "IIS Express": {
+      "commandName": "IISExpress",
+      "launchBrowser": true,
+      "launchUrl": "swagger",
+      "environmentVariables": {
+        "ASPNETCORE_ENVIRONMENT": "Development"
+      }
+    },
+    "Docker": {
+      "commandName": "Docker",
+      "launchBrowser": true,
+      "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger",
+      "environmentVariables": {
+        "ASPNETCORE_URLS": "https://+:443;http://+:80"
+      },
+      "publishAllPorts": true,
+      "useSSL": true
+    }
+  },
+  "$schema": "https://json.schemastore.org/launchsettings.json",
+  "iisSettings": {
+    "windowsAuthentication": false,
+    "anonymousAuthentication": true,
+    "iisExpress": {
+      "applicationUrl": "http://localhost:14346",
+      "sslPort": 44387
+    }
+  }
+}

+ 278 - 0
CertificateAutorityServer/Service/CertificateService.cs

@@ -0,0 +1,278 @@
+using CAUtilLib;
+using CertificateAutorityServer.Assist;
+using CertificateAutorityServer.Model.Config;
+using CertificateAutorityServer.Model.Rest;
+using Microsoft.AspNetCore.Mvc;
+
+namespace CertificateAutorityServer.Service
+{
+    public class CertificateService
+    {
+        public CertificateService(CaUtil caUtil, ILogger<CertificateService> logger)
+        {
+            this.caUtil = caUtil;
+            this.logger = logger;
+
+            RcaConfig = new RcaConfig();
+            RootPath = "/home/cert";
+        }
+
+        private string rootPath;
+        internal string RootPath
+        {
+            get => rootPath;
+            set
+            {
+                if (rootPath == value)
+                {
+                    return;
+                }
+                rootPath = value;
+                caUtil.SavePath = rootPath;
+            }
+        }
+
+        internal string RcaDirectoryName { get; set; } = "rca";
+        internal string TrustedDirectoryName { get; set; } = "tcrt";
+        internal RcaConfig RcaConfig { get; set; }
+        //private static string RcaDirectoryName = "rca";
+        //private static string RcaName = "rca";
+        private readonly CaUtil caUtil;
+        private readonly ILogger<CertificateService> logger;
+
+        public async Task CheckAndCreateRootCA(string savePath)
+        {
+            var rcaPath = Path.Combine(savePath, RcaDirectoryName);
+            if (File.Exists(Path.Combine(rcaPath, $"{RcaConfig.Name}.crt")))
+            {
+                caUtil.SavePath = savePath;
+                return;
+            }
+
+            caUtil.SavePath = Path.Combine(savePath, RcaDirectoryName);
+
+            var result = await CreateRootCA(new CreateRootCaReq()
+            {
+                Name = RcaConfig.Name,
+                CommonName = RcaConfig.CommonName, 
+                Country = RcaConfig.Country,
+                State = RcaConfig.State,
+                OrganizationName = RcaConfig.OrganiaztionName,
+                Days = RcaConfig.Days
+            });
+
+            caUtil.SavePath = savePath;
+
+            if (!result)
+            {
+                logger.LogCritical("create default root ca failed");
+                throw new Exception("create default root ca failed");
+            }
+        }
+
+        public List<string> GetRootCAList()
+        {
+            return Directory.GetFiles(caUtil.SavePath).ToList();
+        }
+
+        internal void RemoveRootCA(string name)
+        {
+            var files = Directory.GetFiles(caUtil.SavePath);
+            var toRemoveFiles = files.Where(x => x.StartsWith(name));
+            foreach (var fileName in toRemoveFiles)
+            {
+                File.Delete(fileName);
+            }
+        }
+
+        public async Task<bool> CreateRootCA(CreateRootCaReq req)
+        {
+            bool result = await caUtil.CreateRootCA(
+                name: req.Name,
+                Days: req.Days,
+                SerialNumber: req.SerialNumber,
+                commonName: req.CommonName,
+                organizationName: req.OrganizationName,
+                Country: req.Country,
+                State: req.State,
+                Hash: req.Hash,
+                RsaKey: req.RsaKey);
+            return result;
+        }
+
+        public async Task<bool> CreateSubCA([FromForm] CreateSubCaReq req)
+        {
+            bool result = await caUtil.CreateSubCA(
+                RootCa: req.RootCa,
+                Days: req.Days,
+                SubCA: req.Name,
+                SerialNumber: req.SerialNumber,
+                commonName: req.CommonName,
+                organizationName: req.OrganizationName,
+                Hash: req.Hash,
+                RsaKey: req.RsaKey,
+                Caformat: req.Caformat);
+            return result;
+        }
+
+        public async Task<string?> SignCsr(SignCsrReq req)
+        {
+            var tempName = Guid.NewGuid().ToString();
+            var tempFile = Path.Combine(caUtil.SavePath, $"{tempName}.csr");
+
+            await CreateTempFile(tempFile, null, req.Csr);
+
+            //string rca = req.RCA is null ? Path.Combine(RcaDirectoryName, RcaConfig.Name) : req.RCA;
+            string rca = Path.Combine(RcaDirectoryName, RcaConfig.Name);
+            string rcaCrt = File.ReadAllText(Path.Combine(RootPath, rca + ".crt"));
+
+            var result = await caUtil.SignCsr(
+                SubCA: tempName,
+                RootCa: rca,
+                Days: 365
+                );
+            if (!result)
+            {
+                return null;
+            }
+            var resultFile = Path.Combine(caUtil.SavePath, $"{tempName}.crt");
+            var crt = await File.ReadAllTextAsync(resultFile);
+            File.Delete(resultFile);
+            File.Delete(tempFile);
+
+            //return crt + rcaCrt; //new TempPhysicalFileResult(resultFile, "application/octet-stream") { FileDownloadName = $"gen.crt" };
+            return crt;
+        }
+
+        internal async Task<int> VerifyCrt(CheckCrtReq req)
+        {
+            var isSignedByRca = await VerifyCrtSignedByRCA(req);
+            if (isSignedByRca)
+            {
+                return 0;
+            }
+            var isSignedByTrusted = await VerifyCrtSignedByTrused(req);
+            if (isSignedByTrusted)
+            {
+                return 1;
+            }
+            return -1;
+        }
+
+        internal async Task<bool> VerifyCrtSignedByRCA(CheckCrtReq req)
+        {
+            var tempName = Guid.NewGuid().ToString();
+            var tempFile = Path.Combine(caUtil.SavePath, $"{tempName}.crt");
+            await CreateTempFile(tempFile, null, req.Crt);
+
+            string rca = req.Rca is null ? Path.Combine(RcaDirectoryName, RcaConfig.Name) : req.Rca;
+            rca += ".crt";
+
+            var isSignedByRca = await caUtil.VerifyCertificateByCertificate(tempFile, rca);
+
+            File.Delete(tempFile);
+
+            return isSignedByRca;
+        }
+
+        internal async Task<bool> VerifyCrtSignedByTrused(CheckCrtReq req)
+        {
+            var tempName = Guid.NewGuid().ToString();
+            var tempFile = Path.Combine(RootPath, $"{tempName}.crt");
+            await CreateTempFile(tempFile, null, req.Crt);
+
+            string rca = req.Rca is null ? Path.Combine(RcaDirectoryName, RcaConfig.Name) : req.Rca;
+            rca += ".crt";
+
+            var isSignedByRca = await caUtil.VerifyCertificateByCertificates(tempFile, Path.Combine(RootPath, TrustedDirectoryName));
+
+            File.Delete(tempFile);
+
+            return isSignedByRca;
+        }
+
+        internal async Task<bool> AddTrustedCertificate(string crt)
+        {
+            var tempName = Guid.NewGuid().ToString();
+            var tempFile = Path.Combine(RootPath, $"{tempName}.crt");
+            await CreateTempFile(tempFile, null, crt);
+            var tempName2 = Guid.NewGuid().ToString();
+            var tempFile2 = Path.Combine(RootPath, tempName2);
+            var calResult = await caUtil.CalculateHash(tempFile, tempName2);
+            if (!calResult)
+            {
+                return false;
+            }
+            var hashString = File.ReadAllText(tempFile2).Trim();
+            var storePosition = Path.Combine(RootPath ,TrustedDirectoryName, $"{hashString}.0");
+            File.Copy(tempFile, storePosition);
+            //CalculateHash
+            File.Delete(tempFile);
+            File.Delete(tempFile2);
+
+            return true;
+        }
+
+        public async Task<string?> CreatKey()
+        {
+            var tempName = Guid.NewGuid().ToString();
+            var tempFileName = $"{tempName}.key";
+            var tempFile = Path.Combine(caUtil.SavePath, tempFileName);
+
+            var result = await caUtil.CreateKey(tempName);
+            if (!result)
+            {
+                return null;
+            }
+            var key = await File.ReadAllTextAsync(tempFile);
+            File.Delete(tempFile);
+
+            return key;
+        }
+
+        public async Task<string?> CreatCsr(CreateCsrReq req)
+        {
+            var tempKeyName = Guid.NewGuid().ToString();
+            var tempKeyFile = Path.Combine(caUtil.SavePath, $"{tempKeyName}.key");
+
+            await CreateTempFile(tempKeyFile, null, req.Key);
+
+            var tempCsrName = Guid.NewGuid().ToString();
+            var tempCsrFile = Path.Combine(caUtil.SavePath, $"{tempCsrName}.csr");
+
+            var result = await caUtil.CreateCsr(tempKeyName, tempCsrName, req.CommonName, req.OrganizationName);
+            if (!result)
+            {
+                return null;
+            }
+            var key = await File.ReadAllTextAsync(tempCsrFile);
+            File.Delete(tempCsrFile);
+            File.Delete(tempKeyFile);
+
+            return key;
+        }
+
+        private async Task CreateTempFile(string tempFile, IFormFile? csrFile, string? csr)
+        {
+            using (var oStream = System.IO.File.OpenWrite(tempFile))
+            {
+                if (csrFile != null)
+                {
+                    using (var csrData = csrFile.OpenReadStream())
+                    {
+                        await csrData.CopyToAsync(oStream);
+                    }
+                }
+
+                if (!string.IsNullOrEmpty(csr))
+                {
+                    var debug = csr.Length;
+                    using (var sWreiter = new StreamWriter(oStream))
+                    {
+                        await sWreiter.WriteAsync(csr);
+                    }
+                }
+            }
+        }
+    }
+}

+ 74 - 0
CertificateAutorityServer/Service/CertificateServiceFactory.cs

@@ -0,0 +1,74 @@
+using CAUtilLib;
+using CertificateAutorityServer.Model.Config;
+using CertificateAutorityServer.Service;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+
+namespace Microsoft.AspNetCore.Builder
+{
+    public static class CertificateServiceServiceProviderExtention
+    {
+        public static WebApplicationBuilder AddCertificateService(this WebApplicationBuilder webApplicationBuilder)
+        {
+            var serviceProvider = webApplicationBuilder.Services;
+            var option = webApplicationBuilder.Configuration;
+
+            serviceProvider.Configure<CerServiceConfig>(option.GetSection("CerConfig"));
+
+            serviceProvider.AddTransient<CaUtil>();
+            serviceProvider.AddTransient<CertificateService>();
+            serviceProvider.AddSingleton<CertificateServiceFactory>();
+
+            return webApplicationBuilder;
+        }
+    }
+}
+
+namespace CertificateAutorityServer.Service
+{
+    public class CertificateServiceFactory
+    {
+        private readonly IServiceProvider serviceProvider;
+        //private readonly string cerPath;
+        private readonly CerServiceConfig config;
+
+        public CertificateServiceFactory(IOptions<CerServiceConfig> config, IServiceProvider serviceProvider)
+        {
+            this.serviceProvider = serviceProvider;
+            this.config = config.Value;
+
+            CheckAndCreateFolder(this.config);
+
+            var certificateService = Create();
+            certificateService.CheckAndCreateRootCA(config.Value.RootPath).Wait();
+        }
+
+        public CertificateService Create()
+        {
+            var toReturn = serviceProvider.GetRequiredService<CertificateService>();
+            toReturn.RootPath = this.config.RootPath;
+            toReturn.RcaDirectoryName = this.config.RcaDirectoryName;
+            toReturn.TrustedDirectoryName = this.config.TrustedDirectoryName;
+            return toReturn;
+        }
+
+        private void CheckAndCreateFolder(CerServiceConfig config)
+        {
+            if (!Directory.Exists(config.RootPath))
+            {
+                Directory.CreateDirectory(config.RootPath);
+            }
+            var caFolder = Path.Combine(config.RootPath, config.RcaDirectoryName);
+            if (!Directory.Exists(caFolder))
+            {
+                Directory.CreateDirectory(caFolder);
+            }
+            var tcFolder = Path.Combine(config.RootPath, config.TrustedDirectoryName);
+            if (!Directory.Exists(tcFolder))
+            {
+                Directory.CreateDirectory(tcFolder);
+            }
+        }
+    }
+}

+ 8 - 0
CertificateAutorityServer/appsettings.Development.json

@@ -0,0 +1,8 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  }
+}

+ 10 - 0
CertificateAutorityServer/appsettings.json

@@ -0,0 +1,10 @@
+{
+  "CerPath": "/home/cert",
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.AspNetCore": "Warning"
+    }
+  },
+  "AllowedHosts": "*"
+}

+ 28 - 0
CertificateAutorityServer/temp/a42e0a2c-22a2-41d0-a278-36c64350572a.csr

@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIEwjCCAqoCAQAwfTELMAkGA1UEBhMCVFcxDzANBgNVBAgMBlRhaXBlaTELMAkG
+A1UECgwCT04xFzAVBgNVBAsMDnBoaWhvbmdfc3ViLklPMRIwEAYDVQQDDAlDaGFy
+Z2VyMDExIzAhBgkqhkiG9w0BCQEWFHBoaWhvbmdfc3ViQG1haWwuY29tMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApGaxh9VmP/vTZYjhPJVtcrqbpq0c
+inpBoKHaz03kF5Crgy7pBVJ4M1PLwIDVEEU5PcwJcp+v+SxwIJdOYCr4SDXBBn7k
+dZwHZ5BvIYwSysftw355NCMhijK2/3CbsZrNSAu0xetJ4qVdQmwLu7GVxdjdIURn
+y1robolF0jjcvxifXBbW3ASipAFcRJWmuxOLcC/Q/uFk3EyOsiNWBa/14w4igWP/
+3yDTqWeSMM8k/BENWKIG7X2B5AZ2Cetox8dbCV6XpOTtxlVhkyW9vyPU3P4t3J9M
+HKV99/jEaaKviJUJbta3yj865XTRRAzZFw4gVSrtj/Nj1olnzUMqc/r+FJpzfU+4
+ms+TijWlpuGR6bVXHnMO5ZAMhqNIG7+3t3nI2wKIokeRzwlA3FNKgMWwFNKBf2+v
+MD0m05sGpWGPXvR6Dy7I22MuakRlUioBxokY3fv5u+UTPguNV/Ea8nusUyWWCz37
+Juih7WrZqr4yhvE/zp8AjwS748DynbKd9RXjlTF3y0z6Oe0g+wNnbawg1dgmPXlY
+LqhiBnEg051hJvMJUyeNG502ZZMdnvqM4BN+lpATZ1Rwae8icpQaf1tjIL3Ge0Ko
+hgOTd509r0Z9OXPbKjRAHt8t9oDPx4e10dvJzv5DE9gulJ6TFF7zkOmXFDdtsXTA
+PMcsVt9xEIraS1sCAwEAAaAAMA0GCSqGSIb3DQEBDQUAA4ICAQBxWER6o8d9/kWP
+5fdIHA+rSv3DzojGbGUfWal14PdZVj7o/qw40vILMrt1Sl3iBmJM2twfYFqL0Sae
+cpqrTFKEwq76q+7xAr+A1asKNIGjcayyQfcomDJ6A+9cU8JlVhwZe+KCU2HAoM41
+IcTFWYIcMx2vX5+C0c64w2SIe9n2TZE81rZtQzO+8ASBwkk0lbSAW4m0IzF3RGmV
+GQHo1HY3R7s8JRdhZhc/C9DY7hHORKb7ZQ3CSWVLKuc/2qX08Lj6YiStf5NL7HW5
+H1pxdZ6MSG3T3C0qI1wKQoWi5l8HIYnDwxnxmdjQdSNsEJpbR3D0LvhW6ggRU5is
+ov0UEZxzy/jrC+r3qU0K5RVT3Nck5e+2b+TAlg+3JNlb3H5N24fQm6+QmD1WAxu7
+xwEnL0nbP1EETzP7qDZuHLdXuJL+MovxF4DH1UXN13aa84+w+J7Ms6MsqtBENKbd
+EoToprKAxJT4ilyqMjNDeAqSAsFlbxz7057eBv77A+n/wCaR+aMx6WvORtrfmQmA
+mnfHdgdHttteqbDqDoVgJvvckOwos2+dEIP+qH73DUqiqOfEcg68cCkzH1vBPYSC
+uavw9tQkuVPZdwfqkXpGjK2GQi40rRk/Nn+K32qqibam0agnRFk/6dr07+cWdjga
+lAqAFKTGGqy/16BFiAEVUzlt10zWkQ==
+-----END CERTIFICATE REQUEST-----

+ 28 - 0
CertificateAutorityServer/temp/a88d41c1-685d-461a-8b4b-2723b51d8b3a.csr

@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIEwjCCAqoCAQAwfTELMAkGA1UEBhMCVFcxDzANBgNVBAgMBlRhaXBlaTELMAkG
+A1UECgwCT04xFzAVBgNVBAsMDnBoaWhvbmdfc3ViLklPMRIwEAYDVQQDDAlDaGFy
+Z2VyMDExIzAhBgkqhkiG9w0BCQEWFHBoaWhvbmdfc3ViQG1haWwuY29tMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApGaxh9VmP/vTZYjhPJVtcrqbpq0c
+inpBoKHaz03kF5Crgy7pBVJ4M1PLwIDVEEU5PcwJcp+v+SxwIJdOYCr4SDXBBn7k
+dZwHZ5BvIYwSysftw355NCMhijK2/3CbsZrNSAu0xetJ4qVdQmwLu7GVxdjdIURn
+y1robolF0jjcvxifXBbW3ASipAFcRJWmuxOLcC/Q/uFk3EyOsiNWBa/14w4igWP/
+3yDTqWeSMM8k/BENWKIG7X2B5AZ2Cetox8dbCV6XpOTtxlVhkyW9vyPU3P4t3J9M
+HKV99/jEaaKviJUJbta3yj865XTRRAzZFw4gVSrtj/Nj1olnzUMqc/r+FJpzfU+4
+ms+TijWlpuGR6bVXHnMO5ZAMhqNIG7+3t3nI2wKIokeRzwlA3FNKgMWwFNKBf2+v
+MD0m05sGpWGPXvR6Dy7I22MuakRlUioBxokY3fv5u+UTPguNV/Ea8nusUyWWCz37
+Juih7WrZqr4yhvE/zp8AjwS748DynbKd9RXjlTF3y0z6Oe0g+wNnbawg1dgmPXlY
+LqhiBnEg051hJvMJUyeNG502ZZMdnvqM4BN+lpATZ1Rwae8icpQaf1tjIL3Ge0Ko
+hgOTd509r0Z9OXPbKjRAHt8t9oDPx4e10dvJzv5DE9gulJ6TFF7zkOmXFDdtsXTA
+PMcsVt9xEIraS1sCAwEAAaAAMA0GCSqGSIb3DQEBDQUAA4ICAQBxWER6o8d9/kWP
+5fdIHA+rSv3DzojGbGUfWal14PdZVj7o/qw40vILMrt1Sl3iBmJM2twfYFqL0Sae
+cpqrTFKEwq76q+7xAr+A1asKNIGjcayyQfcomDJ6A+9cU8JlVhwZe+KCU2HAoM41
+IcTFWYIcMx2vX5+C0c64w2SIe9n2TZE81rZtQzO+8ASBwkk0lbSAW4m0IzF3RGmV
+GQHo1HY3R7s8JRdhZhc/C9DY7hHORKb7ZQ3CSWVLKuc/2qX08Lj6YiStf5NL7HW5
+H1pxdZ6MSG3T3C0qI1wKQoWi5l8HIYnDwxnxmdjQdSNsEJpbR3D0LvhW6ggRU5is
+ov0UEZxzy/jrC+r3qU0K5RVT3Nck5e+2b+TAlg+3JNlb3H5N24fQm6+QmD1WAxu7
+xwEnL0nbP1EETzP7qDZuHLdXuJL+MovxF4DH1UXN13aa84+w+J7Ms6MsqtBENKbd
+EoToprKAxJT4ilyqMjNDeAqSAsFlbxz7057eBv77A+n/wCaR+aMx6WvORtrfmQmA
+mnfHdgdHttteqbDqDoVgJvvckOwos2+dEIP+qH73DUqiqOfEcg68cCkzH1vBPYSC
+uavw9tQkuVPZdwfqkXpGjK2GQi40rRk/Nn+K32qqibam0agnRFk/6dr07+cWdjga
+lAqAFKTGGqy/16BFiAEVUzlt10zWkQ==
+-----END CERTIFICATE REQUEST-----

+ 28 - 0
CertificateAutorityServer/temp/d21fcb46-5e5a-482b-9739-a83da69ef203.csr

@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIEwjCCAqoCAQAwfTELMAkGA1UEBhMCVFcxDzANBgNVBAgMBlRhaXBlaTELMAkG
+A1UECgwCT04xFzAVBgNVBAsMDnBoaWhvbmdfc3ViLklPMRIwEAYDVQQDDAlDaGFy
+Z2VyMDExIzAhBgkqhkiG9w0BCQEWFHBoaWhvbmdfc3ViQG1haWwuY29tMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApGaxh9VmP/vTZYjhPJVtcrqbpq0c
+inpBoKHaz03kF5Crgy7pBVJ4M1PLwIDVEEU5PcwJcp+v+SxwIJdOYCr4SDXBBn7k
+dZwHZ5BvIYwSysftw355NCMhijK2/3CbsZrNSAu0xetJ4qVdQmwLu7GVxdjdIURn
+y1robolF0jjcvxifXBbW3ASipAFcRJWmuxOLcC/Q/uFk3EyOsiNWBa/14w4igWP/
+3yDTqWeSMM8k/BENWKIG7X2B5AZ2Cetox8dbCV6XpOTtxlVhkyW9vyPU3P4t3J9M
+HKV99/jEaaKviJUJbta3yj865XTRRAzZFw4gVSrtj/Nj1olnzUMqc/r+FJpzfU+4
+ms+TijWlpuGR6bVXHnMO5ZAMhqNIG7+3t3nI2wKIokeRzwlA3FNKgMWwFNKBf2+v
+MD0m05sGpWGPXvR6Dy7I22MuakRlUioBxokY3fv5u+UTPguNV/Ea8nusUyWWCz37
+Juih7WrZqr4yhvE/zp8AjwS748DynbKd9RXjlTF3y0z6Oe0g+wNnbawg1dgmPXlY
+LqhiBnEg051hJvMJUyeNG502ZZMdnvqM4BN+lpATZ1Rwae8icpQaf1tjIL3Ge0Ko
+hgOTd509r0Z9OXPbKjRAHt8t9oDPx4e10dvJzv5DE9gulJ6TFF7zkOmXFDdtsXTA
+PMcsVt9xEIraS1sCAwEAAaAAMA0GCSqGSIb3DQEBDQUAA4ICAQBxWER6o8d9/kWP
+5fdIHA+rSv3DzojGbGUfWal14PdZVj7o/qw40vILMrt1Sl3iBmJM2twfYFqL0Sae
+cpqrTFKEwq76q+7xAr+A1asKNIGjcayyQfcomDJ6A+9cU8JlVhwZe+KCU2HAoM41
+IcTFWYIcMx2vX5+C0c64w2SIe9n2TZE81rZtQzO+8ASBwkk0lbSAW4m0IzF3RGmV
+GQHo1HY3R7s8JRdhZhc/C9DY7hHORKb7ZQ3CSWVLKuc/2qX08Lj6YiStf5NL7HW5
+H1pxdZ6MSG3T3C0qI1wKQoWi5l8HIYnDwxnxmdjQdSNsEJpbR3D0LvhW6ggRU5is
+ov0UEZxzy/jrC+r3qU0K5RVT3Nck5e+2b+TAlg+3JNlb3H5N24fQm6+QmD1WAxu7
+xwEnL0nbP1EETzP7qDZuHLdXuJL+MovxF4DH1UXN13aa84+w+J7Ms6MsqtBENKbd
+EoToprKAxJT4ilyqMjNDeAqSAsFlbxz7057eBv77A+n/wCaR+aMx6WvORtrfmQmA
+mnfHdgdHttteqbDqDoVgJvvckOwos2+dEIP+qH73DUqiqOfEcg68cCkzH1vBPYSC
+uavw9tQkuVPZdwfqkXpGjK2GQi40rRk/Nn+K32qqibam0agnRFk/6dr07+cWdjga
+lAqAFKTGGqy/16BFiAEVUzlt10zWkQ==
+-----END CERTIFICATE REQUEST-----

+ 28 - 0
CertificateAutorityServer/temp/f6aedc09-7773-40b1-9c69-74642f8d9246.csr

@@ -0,0 +1,28 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIEwjCCAqoCAQAwfTELMAkGA1UEBhMCVFcxDzANBgNVBAgMBlRhaXBlaTELMAkG
+A1UECgwCT04xFzAVBgNVBAsMDnBoaWhvbmdfc3ViLklPMRIwEAYDVQQDDAlDaGFy
+Z2VyMDExIzAhBgkqhkiG9w0BCQEWFHBoaWhvbmdfc3ViQG1haWwuY29tMIICIjAN
+BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEApGaxh9VmP/vTZYjhPJVtcrqbpq0c
+inpBoKHaz03kF5Crgy7pBVJ4M1PLwIDVEEU5PcwJcp+v+SxwIJdOYCr4SDXBBn7k
+dZwHZ5BvIYwSysftw355NCMhijK2/3CbsZrNSAu0xetJ4qVdQmwLu7GVxdjdIURn
+y1robolF0jjcvxifXBbW3ASipAFcRJWmuxOLcC/Q/uFk3EyOsiNWBa/14w4igWP/
+3yDTqWeSMM8k/BENWKIG7X2B5AZ2Cetox8dbCV6XpOTtxlVhkyW9vyPU3P4t3J9M
+HKV99/jEaaKviJUJbta3yj865XTRRAzZFw4gVSrtj/Nj1olnzUMqc/r+FJpzfU+4
+ms+TijWlpuGR6bVXHnMO5ZAMhqNIG7+3t3nI2wKIokeRzwlA3FNKgMWwFNKBf2+v
+MD0m05sGpWGPXvR6Dy7I22MuakRlUioBxokY3fv5u+UTPguNV/Ea8nusUyWWCz37
+Juih7WrZqr4yhvE/zp8AjwS748DynbKd9RXjlTF3y0z6Oe0g+wNnbawg1dgmPXlY
+LqhiBnEg051hJvMJUyeNG502ZZMdnvqM4BN+lpATZ1Rwae8icpQaf1tjIL3Ge0Ko
+hgOTd509r0Z9OXPbKjRAHt8t9oDPx4e10dvJzv5DE9gulJ6TFF7zkOmXFDdtsXTA
+PMcsVt9xEIraS1sCAwEAAaAAMA0GCSqGSIb3DQEBDQUAA4ICAQBxWER6o8d9/kWP
+5fdIHA+rSv3DzojGbGUfWal14PdZVj7o/qw40vILMrt1Sl3iBmJM2twfYFqL0Sae
+cpqrTFKEwq76q+7xAr+A1asKNIGjcayyQfcomDJ6A+9cU8JlVhwZe+KCU2HAoM41
+IcTFWYIcMx2vX5+C0c64w2SIe9n2TZE81rZtQzO+8ASBwkk0lbSAW4m0IzF3RGmV
+GQHo1HY3R7s8JRdhZhc/C9DY7hHORKb7ZQ3CSWVLKuc/2qX08Lj6YiStf5NL7HW5
+H1pxdZ6MSG3T3C0qI1wKQoWi5l8HIYnDwxnxmdjQdSNsEJpbR3D0LvhW6ggRU5is
+ov0UEZxzy/jrC+r3qU0K5RVT3Nck5e+2b+TAlg+3JNlb3H5N24fQm6+QmD1WAxu7
+xwEnL0nbP1EETzP7qDZuHLdXuJL+MovxF4DH1UXN13aa84+w+J7Ms6MsqtBENKbd
+EoToprKAxJT4ilyqMjNDeAqSAsFlbxz7057eBv77A+n/wCaR+aMx6WvORtrfmQmA
+mnfHdgdHttteqbDqDoVgJvvckOwos2+dEIP+qH73DUqiqOfEcg68cCkzH1vBPYSC
+uavw9tQkuVPZdwfqkXpGjK2GQi40rRk/Nn+K32qqibam0agnRFk/6dr07+cWdjga
+lAqAFKTGGqy/16BFiAEVUzlt10zWkQ==
+-----END CERTIFICATE REQUEST-----

+ 7 - 0
CertificateAutorityServer/zerova_v3.cnf

@@ -0,0 +1,7 @@
+basicConstraints = CA:FALSE
+nsCertType = client
+nsComment = "OpenSSL Generated Client Certificate"
+subjectKeyIdentifier = hash
+authorityKeyIdentifier = keyid,issuer
+keyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment
+extendedKeyUsage = clientAuth

+ 66 - 0
DEV_Build.ps1

@@ -0,0 +1,66 @@
+# 設定 ASCII 藝術字的內容
+$asciiArt = @"
+  _____  ________      ________ _      ____  _____  __  __ ______ _   _ _______ 
+ |  __ \|  ____\ \    / /  ____| |    / __ \|  __ \|  \/  |  ____| \ | |__   __|
+ | |  | | |__   \ \  / /| |__  | |   | |  | | |__) | \  / | |__  |  \| |  | |   
+ | |  | |  __|   \ \/ / |  __| | |   | |  | |  ___/| |\/| |  __| | . ` |  | |   
+ | |__| | |____   \  /  | |____| |___| |__| | |    | |  | | |____| |\  |  | |   
+ |_____/|______|   \/   |______|______\____/|_|    |_|  |_|______|_| \_|  |_|   
+                                                                                
+                                                                                
+"@
+
+# 顯示 ASCII 藝術字
+Write-Host $asciiArt
+
+#第一次建立專案請先設定ACR Name
+$registryname="evdevcontainerregistry"
+$fullregistryname="evdevcontainerregistry.azurecr.io"
+#第一次建立專案請先設定專案名稱
+$imagerepositoryname="certserver"
+$dev_prefix = "CertServer_test_"
+
+$username = az account show --query user.name
+$username = $username.TrimStart("""").Split('@')[0]
+
+$tagname= $dev_prefix + $username
+
+$fulltag=$fullregistryname+"/"+$imagerepositoryname+":"+$tagname
+$imagename = $imagerepositoryname+":"+$tagname
+
+
+$response = read-host  "please confirm that what you are currently uploading is a test version[ $fulltag ]. (y/n)"
+
+
+if ($response -eq "y") {
+ write-host "upload processing....."
+
+
+ #解除image鎖定
+ az acr repository update --name $registryname --image $imagename --delete-enabled true --write-enabled true
+
+ $ssha = git rev-parse --short head
+
+ Write-Host "ACR Login....."
+ $token = az acr login --name $registryname --expose-token --output tsv --query accessToken
+ $user = "00000000-0000-0000-0000-000000000000"
+ podman login $fullregistryname -u $user -p $token
+ 
+ #wite ssha to file
+ $ssha | Out-File gitcommit
+ 
+ podman build ./ -t  $fulltag --label [gitcommit=$ssha,author=$username]
+ podman push $fulltag
+ 
+ #remove ssha file
+ Remove-Item gitcommit
+
+ #鎖定image
+ az acr repository update --name $registryname --image $imagename --delete-enabled false --write-enabled false
+} else {
+ write-host "please modify the parameters with scripts."
+}
+
+
+
+

+ 3 - 0
Dev_Build.bat

@@ -0,0 +1,3 @@
+for /f %%i in ('git rev-parse --short HEAD') do set ssha=%%i
+docker build ./ -t evdevcontainerregistry.azurecr.io/certserver:test --label "git-commit=%ssha%"
+docker push evdevcontainerregistry.azurecr.io/certserver:test

+ 36 - 0
Dockerfile

@@ -0,0 +1,36 @@
+#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
+
+FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
+WORKDIR /app
+EXPOSE 80
+EXPOSE 443
+
+RUN apt-get update \
+    && apt-get install -y --no-install-recommends dialog \
+    && apt-get install -y --no-install-recommends openssh-server \
+	&& apt-get install -y tcpdump\
+	&& mkdir -p /run/sshd \
+    && echo "root:Docker!" | chpasswd 
+	
+COPY sshd_config /etc/ssh/sshd_config
+
+FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
+WORKDIR /src
+COPY ["CAUtilLib/CAUtilLib.csproj", "CAUtilLib/"]
+COPY ["CertificateAutorityServer/CertificateAutorityServer.csproj", "CertificateAutorityServer/"]
+RUN dotnet restore "CAUtilLib/CAUtilLib.csproj"
+RUN dotnet restore "CertificateAutorityServer/CertificateAutorityServer.csproj"
+COPY . .
+WORKDIR "/src/CertificateAutorityServer"
+RUN dotnet build "CertificateAutorityServer.csproj" -c Release -o /app/build
+
+FROM build AS publish
+RUN dotnet publish "CertificateAutorityServer.csproj" -c Release -o /app/publish /p:UseAppHost=false
+
+FROM base AS final
+WORKDIR /app
+COPY --from=publish /app/publish .
+
+COPY entrypoint.sh .
+RUN chmod +x /app/entrypoint.sh
+CMD ["/app/entrypoint.sh"]

+ 6 - 0
entrypoint.sh

@@ -0,0 +1,6 @@
+#!/bin/bash
+set -e
+service ssh start
+#dotnet run --project ./EVCB_OCPP.WSServer/EVCB_OCPP.WSServer.csproj
+#/app/publish/EVCB_OCPP.WSServer
+dotnet CertificateAutorityServer.dll

+ 12 - 0
sshd_config

@@ -0,0 +1,12 @@
+Port 			2222
+ListenAddress 		0.0.0.0
+LoginGraceTime 		180
+X11Forwarding 		yes
+Ciphers aes128-cbc,3des-cbc,aes256-cbc,aes128-ctr,aes192-ctr,aes256-ctr
+MACs hmac-sha1,hmac-sha1-96
+StrictModes 		yes
+SyslogFacility 		DAEMON
+PasswordAuthentication 	yes
+PermitEmptyPasswords 	no
+PermitRootLogin 	yes
+Subsystem sftp internal-sftp