|
@@ -0,0 +1,660 @@
|
|
|
+diff --git a/EVCB_OCPP.WSServer/DLL/EVCB_OCPP.Packet.dll b/EVCB_OCPP.WSServer/DLL/EVCB_OCPP.Packet.dll
|
|
|
+index 42f3575..e8dce54 100644
|
|
|
+Binary files a/EVCB_OCPP.WSServer/DLL/EVCB_OCPP.Packet.dll and b/EVCB_OCPP.WSServer/DLL/EVCB_OCPP.Packet.dll differ
|
|
|
+diff --git a/EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs b/EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs
|
|
|
+index 1698606..0cb0fea 100644
|
|
|
+--- a/EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs
|
|
|
++++ b/EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs
|
|
|
+@@ -72,6 +72,7 @@ public partial class ProfileHandler
|
|
|
+ //private readonly IDbContextFactory<MeterValueDBContext> metervaluedbContextFactory;
|
|
|
+ private readonly IBusinessServiceFactory businessServiceFactory;
|
|
|
+ private readonly IMainDbService mainDbService;
|
|
|
++ private readonly CertificateService certService;
|
|
|
+ private OuterHttpClient httpClient;
|
|
|
+
|
|
|
+ public ProfileHandler(
|
|
|
+@@ -82,6 +83,7 @@ public partial class ProfileHandler
|
|
|
+ MeterValueDbService meterValueDbService,
|
|
|
+ IBusinessServiceFactory businessServiceFactory,
|
|
|
+ IMainDbService mainDbService,
|
|
|
++ CertificateService certService,
|
|
|
+ ILogger<ProfileHandler> logger,
|
|
|
+ OuterHttpClient httpClient)
|
|
|
+ {
|
|
|
+@@ -93,6 +95,7 @@ public partial class ProfileHandler
|
|
|
+ this.webDbConnectionFactory = webDbConnectionFactory;
|
|
|
+ this.meterValueDbService = meterValueDbService;
|
|
|
+ this.mainDbService = mainDbService;
|
|
|
++ this.certService = certService;
|
|
|
+ //this.metervaluedbContextFactory = metervaluedbContextFactory;
|
|
|
+ this.businessServiceFactory = businessServiceFactory;
|
|
|
+ this.httpClient = httpClient;
|
|
|
+diff --git a/EVCB_OCPP.WSServer/Message/SecurityProfileHandler.cs b/EVCB_OCPP.WSServer/Message/SecurityProfileHandler.cs
|
|
|
+index 96b07ba..cd277eb 100644
|
|
|
+--- a/EVCB_OCPP.WSServer/Message/SecurityProfileHandler.cs
|
|
|
++++ b/EVCB_OCPP.WSServer/Message/SecurityProfileHandler.cs
|
|
|
+@@ -3,12 +3,20 @@ using EVCB_OCPP.Packet.Messages;
|
|
|
+ using System;
|
|
|
+ using Microsoft.Extensions.Logging;
|
|
|
+ using EVCB_OCPP.WSServer.Service.WsService;
|
|
|
++using EVCB_OCPP.Packet.Messages.RemoteTrigger;
|
|
|
++using Microsoft.EntityFrameworkCore;
|
|
|
++using EVCB_OCPP.Packet.Messages.Security;
|
|
|
++using Microsoft.IdentityModel.Tokens;
|
|
|
++using System.Runtime.ConstrainedExecution;
|
|
|
++using System.Security.Cryptography.X509Certificates;
|
|
|
++using System.Security.Cryptography;
|
|
|
++using System.Text.RegularExpressions;
|
|
|
+
|
|
|
+ namespace EVCB_OCPP.WSServer.Message
|
|
|
+ {
|
|
|
+ public partial class ProfileHandler
|
|
|
+ {
|
|
|
+- internal MessageResult ExecuteSecurityRequest(Actions action, WsClientData session, IRequest request)
|
|
|
++ internal async Task<MessageResult> ExecuteSecurityRequest(Actions action, WsClientData session, IRequest request)
|
|
|
+ {
|
|
|
+ MessageResult result = new MessageResult() { Success = false };
|
|
|
+
|
|
|
+@@ -16,8 +24,68 @@ namespace EVCB_OCPP.WSServer.Message
|
|
|
+ {
|
|
|
+ switch (action)
|
|
|
+ {
|
|
|
++ case Actions.SignCertificate:
|
|
|
++ {
|
|
|
++ SignCertificateRequest _request = request as SignCertificateRequest;
|
|
|
++ SignCertificateConfirmation confirm = new();
|
|
|
++
|
|
|
++ if (string.IsNullOrEmpty(_request.csr))
|
|
|
++ {
|
|
|
++ result.Success = false;
|
|
|
++ return result;
|
|
|
++ }
|
|
|
+
|
|
|
++ bool isCsrValid = CheckCsr(session.ChargeBoxId, _request.csr);
|
|
|
++ if (!isCsrValid)
|
|
|
++ {
|
|
|
++ confirm.status = Packet.Messages.SubTypes.GenericStatusEnumType.Rejected;
|
|
|
++ result.Message = confirm;
|
|
|
++ result.Success = false;
|
|
|
++ return result;
|
|
|
++ }
|
|
|
++ _ = certService.SignCertificate(session.ChargeBoxId, _request.csr);
|
|
|
+
|
|
|
++ confirm.status = Packet.Messages.SubTypes.GenericStatusEnumType.Accepted;
|
|
|
++ result.Message = confirm;
|
|
|
++ result.Success = true;
|
|
|
++ return result;
|
|
|
++ }
|
|
|
++ case Actions.SecurityEventNotification:
|
|
|
++ {
|
|
|
++ SecurityEventNotificationRequest _request = request as SecurityEventNotificationRequest;
|
|
|
++ SecurityEventNotificationConfirmation confirm = new();
|
|
|
++
|
|
|
++ logger.LogInformation("{chargeBoxId} security notification {sects} {sectype} {secmsg}", session.ChargeBoxId, _request.timestamp, _request.type, _request.techInfo);
|
|
|
++
|
|
|
++ result.Message = confirm;
|
|
|
++ result.Success = true;
|
|
|
++ return result;
|
|
|
++ }
|
|
|
++ case Actions.LogStatusNotification:
|
|
|
++ {
|
|
|
++ LogStatusNotificationRequest _request = request as LogStatusNotificationRequest;
|
|
|
++ LogStatusNotificationConfirmation confirm = new();
|
|
|
++
|
|
|
++ if (_request.status != Packet.Messages.SubTypes.UploadLogStatusEnumType.Idle)
|
|
|
++ {
|
|
|
++ using (var db = await maindbContextFactory.CreateDbContextAsync())
|
|
|
++ {
|
|
|
++ var item = await db.MachineOperateRecords.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.Action == "GetLog" && x.RequestType == 1)
|
|
|
++ .OrderByDescending(x => x.CreatedOn).FirstOrDefaultAsync();
|
|
|
++ if (item != null)
|
|
|
++ {
|
|
|
++ item.EvseStatus = (int)_request.status;
|
|
|
++ item.FinishedOn = DateTime.UtcNow;
|
|
|
++ }
|
|
|
++
|
|
|
++ await db.SaveChangesAsync();
|
|
|
++ }
|
|
|
++
|
|
|
++ }
|
|
|
++ result.Message = confirm;
|
|
|
++ result.Success = true;
|
|
|
++ return result;
|
|
|
++ }
|
|
|
+ default:
|
|
|
+ {
|
|
|
+ logger.LogWarning(string.Format("Not Implement {0} Logic(ExecuteCoreRequest)", request.GetType().ToString().Replace("OCPPPackage.Messages.Core.", "")));
|
|
|
+@@ -37,13 +105,141 @@ namespace EVCB_OCPP.WSServer.Message
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+- internal MessageResult ExecuteSecurityConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
|
|
|
++
|
|
|
++ private bool CheckCsr(string chargeBoxId, string csrString)
|
|
|
++ {
|
|
|
++ string subject = certService.GetCertificateRequestSubject(csrString);
|
|
|
++ logger.LogInformation("{chargeBoxId} send scr {subject}", chargeBoxId, subject);
|
|
|
++ if (subject == null)
|
|
|
++ {
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++
|
|
|
++ Dictionary<string,string> csrInfo = certService.SubjectToDictionary(subject);
|
|
|
++ if (csrInfo == null ||
|
|
|
++ !csrInfo.ContainsKey("CN") ||
|
|
|
++ csrInfo["CN"] != chargeBoxId)
|
|
|
++ {
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++ return true;
|
|
|
++ }
|
|
|
++
|
|
|
++ internal async Task<MessageResult> ExecuteSecurityConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
|
|
|
+ {
|
|
|
+ MessageResult result = new MessageResult() { Success = false };
|
|
|
+
|
|
|
+ switch (action)
|
|
|
+ {
|
|
|
+-
|
|
|
++ case Actions.ExtendedTriggerMessage:
|
|
|
++ {
|
|
|
++ ExtendedTriggerMessageConfirmation _confirm = confirm as ExtendedTriggerMessageConfirmation;
|
|
|
++ //ExtendedTriggerMessageRequest _request = _confirm.GetRequest() as ExtendedTriggerMessageRequest;
|
|
|
++ using (var db = await maindbContextFactory.CreateDbContextAsync())
|
|
|
++ {
|
|
|
++ var operation = await db.MachineOperateRecords.Where(x => x.SerialNo == requestId &&
|
|
|
++ x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
|
|
|
++ if (operation != null)
|
|
|
++ {
|
|
|
++ operation.FinishedOn = DateTime.UtcNow;
|
|
|
++ operation.Status = 1;//電樁有回覆
|
|
|
++ operation.EvseStatus = (int)_confirm.status;//OK
|
|
|
++ operation.EvseValue = _confirm.status.ToString();
|
|
|
++ await db.SaveChangesAsync();
|
|
|
++ }
|
|
|
++ }
|
|
|
++ }
|
|
|
++ break;
|
|
|
++ case Actions.CertificateSigned:
|
|
|
++ {
|
|
|
++ CertificateSignedConfirmation _confirm = confirm as CertificateSignedConfirmation;
|
|
|
++ using (var db = await maindbContextFactory.CreateDbContextAsync())
|
|
|
++ {
|
|
|
++ var operation = await db.MachineOperateRecords.Where(x => x.SerialNo == requestId &&
|
|
|
++ x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
|
|
|
++ if (operation != null)
|
|
|
++ {
|
|
|
++ operation.FinishedOn = DateTime.UtcNow;
|
|
|
++ operation.Status = 1;//電樁有回覆
|
|
|
++ operation.EvseStatus = (int)_confirm.status;//OK
|
|
|
++ operation.EvseValue = _confirm.status.ToString();
|
|
|
++ await db.SaveChangesAsync();
|
|
|
++ }
|
|
|
++ }
|
|
|
++ }
|
|
|
++ break;
|
|
|
++ case Actions.GetInstalledCertificateIds:
|
|
|
++ {
|
|
|
++ GetInstalledCertificateIdsConfirmation _confirm = confirm as GetInstalledCertificateIdsConfirmation;
|
|
|
++ using (var db = await maindbContextFactory.CreateDbContextAsync())
|
|
|
++ {
|
|
|
++ var operation = await db.MachineOperateRecords.Where(x => x.SerialNo == requestId &&
|
|
|
++ x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
|
|
|
++ if (operation != null)
|
|
|
++ {
|
|
|
++ operation.FinishedOn = DateTime.UtcNow;
|
|
|
++ operation.Status = 1;//電樁有回覆
|
|
|
++ operation.EvseStatus = (int)_confirm.status;//OK
|
|
|
++ operation.EvseValue = _confirm.status.ToString();
|
|
|
++ await db.SaveChangesAsync();
|
|
|
++ }
|
|
|
++ }
|
|
|
++ }
|
|
|
++ break;
|
|
|
++ case Actions.DeleteCertificate:
|
|
|
++ {
|
|
|
++ DeleteCertificateConfirmation _confirm = confirm as DeleteCertificateConfirmation;
|
|
|
++ using (var db = await maindbContextFactory.CreateDbContextAsync())
|
|
|
++ {
|
|
|
++ var operation = await db.MachineOperateRecords.Where(x => x.SerialNo == requestId &&
|
|
|
++ x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
|
|
|
++ if (operation != null)
|
|
|
++ {
|
|
|
++ operation.FinishedOn = DateTime.UtcNow;
|
|
|
++ operation.Status = 1;//電樁有回覆
|
|
|
++ operation.EvseStatus = (int)_confirm.status;//OK
|
|
|
++ operation.EvseValue = _confirm.status.ToString();
|
|
|
++ await db.SaveChangesAsync();
|
|
|
++ }
|
|
|
++ }
|
|
|
++ }
|
|
|
++ break;
|
|
|
++ case Actions.InstallCertificate:
|
|
|
++ {
|
|
|
++ InstallCertificateConfirmation _confirm = confirm as InstallCertificateConfirmation;
|
|
|
++ using (var db = await maindbContextFactory.CreateDbContextAsync())
|
|
|
++ {
|
|
|
++ var operation = await db.MachineOperateRecords.Where(x => x.SerialNo == requestId &&
|
|
|
++ x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
|
|
|
++ if (operation != null)
|
|
|
++ {
|
|
|
++ operation.FinishedOn = DateTime.UtcNow;
|
|
|
++ operation.Status = 1;//電樁有回覆
|
|
|
++ operation.EvseStatus = (int)_confirm.status;//OK
|
|
|
++ operation.EvseValue = _confirm.status.ToString();
|
|
|
++ await db.SaveChangesAsync();
|
|
|
++ }
|
|
|
++ }
|
|
|
++ }
|
|
|
++ break;
|
|
|
++ case Actions.GetLog:
|
|
|
++ {
|
|
|
++ GetLogConfirmation _confirm = confirm as GetLogConfirmation;
|
|
|
++ using (var db = await maindbContextFactory.CreateDbContextAsync())
|
|
|
++ {
|
|
|
++ var operation = await db.MachineOperateRecords.Where(x => x.SerialNo == requestId &&
|
|
|
++ x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
|
|
|
++ if (operation != null)
|
|
|
++ {
|
|
|
++ operation.FinishedOn = DateTime.UtcNow;
|
|
|
++ operation.Status = 1;//電樁有回覆
|
|
|
++ operation.EvseStatus = (int)_confirm.status;//OK
|
|
|
++ operation.EvseValue = _confirm.status.ToString();
|
|
|
++ await db.SaveChangesAsync();
|
|
|
++ }
|
|
|
++ }
|
|
|
++ }
|
|
|
++ break;
|
|
|
+ default:
|
|
|
+ {
|
|
|
+ logger.LogWarning(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.RemoteTrigger.", "")));
|
|
|
+diff --git a/EVCB_OCPP.WSServer/Program.cs b/EVCB_OCPP.WSServer/Program.cs
|
|
|
+index a2b2b57..493bd08 100644
|
|
|
+--- a/EVCB_OCPP.WSServer/Program.cs
|
|
|
++++ b/EVCB_OCPP.WSServer/Program.cs
|
|
|
+@@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Hosting;
|
|
|
+ using EVCB_OCPP.WSServer.Service.WsService;
|
|
|
+ using EVCB_OCPP.Service;
|
|
|
+ using Microsoft.Extensions.Logging;
|
|
|
++using System.Net.Security;
|
|
|
++using System.Security.Cryptography.X509Certificates;
|
|
|
+
|
|
|
+ namespace EVCB_OCPP.WSServer
|
|
|
+ {
|
|
|
+@@ -51,11 +53,19 @@ namespace EVCB_OCPP.WSServer
|
|
|
+ //services.AddTransient<BlockingTreePrintService>();
|
|
|
+ //services.AddTransient<GoogleGetTimePrintService>();
|
|
|
+ });
|
|
|
++ builder.WebHost.ConfigureKestrel(opt => {
|
|
|
++ opt.ConfigureHttpsDefaults(opt =>
|
|
|
++ {
|
|
|
++ opt.ClientCertificateMode = Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode.AllowCertificate;
|
|
|
++ opt.ClientCertificateValidation = ValidateClientCertficatel;
|
|
|
++ });
|
|
|
++ });
|
|
|
+ var app = builder.Build();
|
|
|
+ app.UseHeaderRecordService();
|
|
|
+ app.UseOcppWsService();
|
|
|
+ app.MapApiServce();
|
|
|
+ app.Urls.Add("http://*:80");
|
|
|
++ app.Urls.Add("https://*:443");
|
|
|
+ app.Run();
|
|
|
+ }
|
|
|
+
|
|
|
+@@ -69,5 +79,10 @@ namespace EVCB_OCPP.WSServer
|
|
|
+ }
|
|
|
+ return timevalue;
|
|
|
+ }
|
|
|
++
|
|
|
++ private static bool ValidateClientCertficatel(X509Certificate2 clientCertificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
|
|
|
++ {
|
|
|
++ return true;
|
|
|
++ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+diff --git a/EVCB_OCPP.WSServer/ProtalServer.cs b/EVCB_OCPP.WSServer/ProtalServer.cs
|
|
|
+index 4a5cebc..a16a7f8 100644
|
|
|
+--- a/EVCB_OCPP.WSServer/ProtalServer.cs
|
|
|
++++ b/EVCB_OCPP.WSServer/ProtalServer.cs
|
|
|
+@@ -21,6 +21,8 @@ using System.Net;
|
|
|
+ using System.Text;
|
|
|
+ using EVCB_OCPP.WSServer.Service.WsService;
|
|
|
+ using System.ServiceModel.Channels;
|
|
|
++using EVCB_OCPP.Packet.Messages.RemoteTrigger;
|
|
|
++using EVCB_OCPP.Packet.Messages.Security;
|
|
|
+
|
|
|
+ namespace EVCB_OCPP.WSServer
|
|
|
+ {
|
|
|
+@@ -697,6 +699,18 @@ namespace EVCB_OCPP.WSServer
|
|
|
+ {
|
|
|
+ session.IsCheckIn = true;
|
|
|
+
|
|
|
++ if (session.IsSignedByCPO == false)
|
|
|
++ {
|
|
|
++ var _request = new ExtendedTriggerMessageRequest()
|
|
|
++ {
|
|
|
++ requestedMessage = Packet.Messages.SubTypes.MessageTriggerEnumType.SignChargePointCertificate
|
|
|
++ };
|
|
|
++
|
|
|
++ var uuid = session.queue.store(_request);
|
|
|
++ string rawRequest = BasicMessageHandler.GenerateRequest(uuid, _request.Action, _request);
|
|
|
++ SendMsg(session, rawRequest, string.Format("{0} {1}", _request.Action, "Request"), "");
|
|
|
++ }
|
|
|
++
|
|
|
+ await messageService.SendGetEVSEConfigureRequest(session.ChargeBoxId);
|
|
|
+
|
|
|
+ await messageService.SendChangeConfigurationRequest(
|
|
|
+@@ -831,7 +845,7 @@ namespace EVCB_OCPP.WSServer
|
|
|
+ break;
|
|
|
+ case "Security":
|
|
|
+ {
|
|
|
+- var replyResult = profileHandler.ExecuteSecurityRequest(action, session, (IRequest)analysisResult.Message);
|
|
|
++ var replyResult = await profileHandler.ExecuteSecurityRequest(action, session, (IRequest)analysisResult.Message);
|
|
|
+ if (replyResult.Success)
|
|
|
+ {
|
|
|
+ string response = BasicMessageHandler.GenerateConfirmation(analysisResult.UUID, (IConfirmation)replyResult.Message);
|
|
|
+@@ -905,7 +919,7 @@ namespace EVCB_OCPP.WSServer
|
|
|
+ break;
|
|
|
+ case "Security":
|
|
|
+ {
|
|
|
+- confirmResult = profileHandler.ExecuteSecurityConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
|
|
|
++ confirmResult = await profileHandler.ExecuteSecurityConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+diff --git a/EVCB_OCPP.WSServer/Service/CertificateService.cs b/EVCB_OCPP.WSServer/Service/CertificateService.cs
|
|
|
+new file mode 100644
|
|
|
+index 0000000..2f19f25
|
|
|
+--- /dev/null
|
|
|
++++ b/EVCB_OCPP.WSServer/Service/CertificateService.cs
|
|
|
+@@ -0,0 +1,142 @@
|
|
|
++using Microsoft.Extensions.Configuration;
|
|
|
++using Microsoft.Extensions.Logging;
|
|
|
++using System;
|
|
|
++using System.Collections.Generic;
|
|
|
++using System.Linq;
|
|
|
++using System.Net.Http.Json;
|
|
|
++using System.Runtime;
|
|
|
++using System.Runtime.ConstrainedExecution;
|
|
|
++using System.Security.Cryptography;
|
|
|
++using System.Security.Cryptography.X509Certificates;
|
|
|
++using System.Text;
|
|
|
++using System.Text.RegularExpressions;
|
|
|
++using System.Threading.Tasks;
|
|
|
++
|
|
|
++namespace EVCB_OCPP.WSServer.Service
|
|
|
++{
|
|
|
++ public class CertificateService
|
|
|
++ {
|
|
|
++
|
|
|
++ public CertificateService(
|
|
|
++ ServerMessageService messageService,
|
|
|
++ IConfiguration configuration,
|
|
|
++ ILogger<CertificateService> logger)
|
|
|
++ {
|
|
|
++ _serverUrl = configuration["CertificateServerUrl"];
|
|
|
++ _cpon = configuration["ChargingPointOperator"];
|
|
|
++ this.messageService = messageService;
|
|
|
++ this.logger = logger;
|
|
|
++ }
|
|
|
++ private readonly string _serverUrl;
|
|
|
++ private readonly string _cpon;
|
|
|
++ private readonly ServerMessageService messageService;
|
|
|
++ private readonly ILogger<CertificateService> logger;
|
|
|
++
|
|
|
++ public async Task SignCertificate(string chargeBoxId, string csr)
|
|
|
++ {
|
|
|
++ HttpClient client = new HttpClient();
|
|
|
++ client.BaseAddress = new Uri(_serverUrl);
|
|
|
++ var signResult = await client.PostAsJsonAsync("cert/csr", new { csr = csr });
|
|
|
++ if (!signResult.IsSuccessStatusCode)
|
|
|
++ {
|
|
|
++ logger.LogWarning("{chargeBoxId} csr failed {csr}", chargeBoxId, csr);
|
|
|
++ return;
|
|
|
++ }
|
|
|
++ var crt = await signResult.Content.ReadAsStringAsync();
|
|
|
++ await messageService.SendCertificateSignedRequest(chargeBoxId, crt);
|
|
|
++ client.Dispose();
|
|
|
++ }
|
|
|
++
|
|
|
++ public Task<int> CheckClientCertificate(string chargeboxId, X509Certificate2 cert)
|
|
|
++ {
|
|
|
++ return CheckClientCertificate(chargeboxId, cert.ExportCertificatePem());
|
|
|
++ }
|
|
|
++
|
|
|
++ public async Task<int> CheckClientCertificate(string chargeboxId, string certString)
|
|
|
++ {
|
|
|
++ var subject = GetCertificateSubject(certString);
|
|
|
++ logger.LogInformation("{chargeboxId} with cert subject {subject}", chargeboxId, subject);
|
|
|
++ if (string.IsNullOrEmpty(subject))
|
|
|
++ {
|
|
|
++ return -1;
|
|
|
++ }
|
|
|
++
|
|
|
++ var crtInfo = SubjectToDictionary(subject);
|
|
|
++ if (crtInfo == null ||
|
|
|
++ //!crtInfo.ContainsKey("O") ||
|
|
|
++ //crtInfo["O"] != _cpon ||
|
|
|
++ !crtInfo.ContainsKey("CN") ||
|
|
|
++ crtInfo["CN"] != chargeboxId)
|
|
|
++ {
|
|
|
++ return -1;
|
|
|
++ }
|
|
|
++
|
|
|
++ int certServerResult = await CheckClientCertificateRca(certString);
|
|
|
++ logger.LogInformation("{chargeboxId} with cert authenticate {subject}", chargeboxId, certServerResult);
|
|
|
++ return certServerResult;
|
|
|
++ }
|
|
|
++
|
|
|
++ public async Task<int> CheckClientCertificateRca(string certString)
|
|
|
++ {
|
|
|
++ using HttpClient client = new HttpClient();
|
|
|
++ client.BaseAddress = new Uri(_serverUrl);
|
|
|
++ var result = await client.PutAsJsonAsync("cert/crt", new { crt = certString });
|
|
|
++ if (!result.IsSuccessStatusCode)
|
|
|
++ {
|
|
|
++ return -1;
|
|
|
++ }
|
|
|
++ var resultContentString = await result.Content.ReadAsStringAsync();
|
|
|
++ return Convert.ToInt32(resultContentString);
|
|
|
++ }
|
|
|
++
|
|
|
++ public Dictionary<string, string> SubjectToDictionary(string subject)
|
|
|
++ {
|
|
|
++ try
|
|
|
++ {
|
|
|
++ MatchCollection regexResult = Regex.Matches(subject, "([a-zA-Z]*)=([a-zA-Z0-9]*)");
|
|
|
++ if (regexResult.Count == 0)
|
|
|
++ {
|
|
|
++ return null;
|
|
|
++ }
|
|
|
++ return regexResult.Where(x => x.Success == true && x.Groups.Count == 3).ToDictionary(x => x.Groups[1].Value, x => x.Groups[2].Value);
|
|
|
++ }
|
|
|
++ catch (Exception e)
|
|
|
++ {
|
|
|
++ logger.LogCritical(e.Message);
|
|
|
++ logger.LogCritical(e.StackTrace);
|
|
|
++ return null;
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ public string GetCertificateSubject(string crt)
|
|
|
++ {
|
|
|
++
|
|
|
++ try
|
|
|
++ {
|
|
|
++ byte[] bytes = Encoding.ASCII.GetBytes(crt);
|
|
|
++ var clientCertificate = new X509Certificate2(bytes);
|
|
|
++
|
|
|
++ return clientCertificate.Subject;
|
|
|
++ }
|
|
|
++ catch
|
|
|
++ {
|
|
|
++ return null;
|
|
|
++ }
|
|
|
++ }
|
|
|
++
|
|
|
++ public string GetCertificateRequestSubject(string csrString)
|
|
|
++ {
|
|
|
++
|
|
|
++ try
|
|
|
++ {
|
|
|
++ var csr = CertificateRequest.LoadSigningRequestPem(csrString, HashAlgorithmName.SHA512);
|
|
|
++ var test = csr.SubjectName.Name;
|
|
|
++ return test;
|
|
|
++ }
|
|
|
++ catch
|
|
|
++ {
|
|
|
++ return null;
|
|
|
++ }
|
|
|
++ }
|
|
|
++ }
|
|
|
++}
|
|
|
+diff --git a/EVCB_OCPP.WSServer/Service/ServerMessageService.cs b/EVCB_OCPP.WSServer/Service/ServerMessageService.cs
|
|
|
+index 9a3b0e2..6379c21 100644
|
|
|
+--- a/EVCB_OCPP.WSServer/Service/ServerMessageService.cs
|
|
|
++++ b/EVCB_OCPP.WSServer/Service/ServerMessageService.cs
|
|
|
+@@ -1,6 +1,9 @@
|
|
|
+ using EVCB_OCPP.Packet.Features;
|
|
|
+ using EVCB_OCPP.Packet.Messages.Core;
|
|
|
+ using Microsoft.Extensions.Logging;
|
|
|
++using static System.Runtime.InteropServices.JavaScript.JSType;
|
|
|
++using System.ServiceModel.Channels;
|
|
|
++using EVCB_OCPP.Packet.Messages.Security;
|
|
|
+
|
|
|
+ namespace EVCB_OCPP.WSServer.Service;
|
|
|
+
|
|
|
+@@ -52,4 +55,16 @@ public class ServerMessageService
|
|
|
+ }
|
|
|
+ );
|
|
|
+ }
|
|
|
++
|
|
|
++ internal Task SendCertificateSignedRequest(string chargeBoxId, string crt)
|
|
|
++ {
|
|
|
++ return mainDbService.AddServerMessage(
|
|
|
++ ChargeBoxId: chargeBoxId,
|
|
|
++ OutAction: Actions.SignCertificate.ToString(),
|
|
|
++ OutRequest: new CertificateSignedRequest()
|
|
|
++ {
|
|
|
++ certificateChain = crt
|
|
|
++ }
|
|
|
++ );
|
|
|
++ }
|
|
|
+ }
|
|
|
+diff --git a/EVCB_OCPP.WSServer/Service/WsService/OcppWebsocketService.cs b/EVCB_OCPP.WSServer/Service/WsService/OcppWebsocketService.cs
|
|
|
+index ba1df1c..8cd38ae 100644
|
|
|
+--- a/EVCB_OCPP.WSServer/Service/WsService/OcppWebsocketService.cs
|
|
|
++++ b/EVCB_OCPP.WSServer/Service/WsService/OcppWebsocketService.cs
|
|
|
+@@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
|
|
|
+ using Microsoft.Extensions.DependencyInjection;
|
|
|
+ using Microsoft.Extensions.Logging;
|
|
|
+ using System.Net.WebSockets;
|
|
|
++using System.Security.Cryptography.X509Certificates;
|
|
|
+ using System.Text;
|
|
|
+
|
|
|
+ namespace EVCB_OCPP.WSServer.Service.WsService;
|
|
|
+@@ -13,6 +14,7 @@ public static partial class AppExtention
|
|
|
+ {
|
|
|
+ public static void AddOcppWsServer(this IServiceCollection services)
|
|
|
+ {
|
|
|
++ services.AddTransient<CertificateService>();
|
|
|
+ services.AddTransient<WsClientData>();
|
|
|
+ services.AddSingleton<OcppWebsocketService>();
|
|
|
+ }
|
|
|
+@@ -45,6 +47,7 @@ public class OcppWebsocketService : WebsocketService<WsClientData>
|
|
|
+
|
|
|
+ private readonly IConfiguration configuration;
|
|
|
+ private readonly IMainDbService mainDbService;
|
|
|
++ private readonly CertificateService certificateService;
|
|
|
+ private readonly ILogger<OcppWebsocketService> logger;
|
|
|
+
|
|
|
+ private readonly QueueSemaphore handshakeSemaphore;
|
|
|
+@@ -53,11 +56,13 @@ public class OcppWebsocketService : WebsocketService<WsClientData>
|
|
|
+ IConfiguration configuration,
|
|
|
+ IServiceProvider serviceProvider,
|
|
|
+ IMainDbService mainDbService,
|
|
|
++ CertificateService certificateService,
|
|
|
+ ILogger<OcppWebsocketService> logger
|
|
|
+ ) : base(serviceProvider)
|
|
|
+ {
|
|
|
+ this.configuration = configuration;
|
|
|
+ this.mainDbService = mainDbService;
|
|
|
++ this.certificateService = certificateService;
|
|
|
+ this.logger = logger;
|
|
|
+
|
|
|
+ handshakeSemaphore = new QueueSemaphore(5);
|
|
|
+@@ -182,11 +187,46 @@ public class OcppWebsocketService : WebsocketService<WsClientData>
|
|
|
+ securityProfile = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+- if (securityProfile == 3 && session.UriScheme == "ws")
|
|
|
++ if (securityProfile == 3 )
|
|
|
+ {
|
|
|
+- context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
|
|
+- logger.LogTrace("{id} {func} {Statuscode}", context.TraceIdentifier, nameof(ValidateHandshake), context.Response.StatusCode);
|
|
|
+- return false;
|
|
|
++ if (session.UriScheme == "ws")
|
|
|
++ {
|
|
|
++ context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
|
|
++ logger.LogTrace("{id} {func} {Statuscode}", context.TraceIdentifier, nameof(ValidateHandshake), context.Response.StatusCode);
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++
|
|
|
++ X509Certificate2 clientCertificate = null;
|
|
|
++ if (context.Request.Headers.ContainsKey("X-ARR-ClientCert"))
|
|
|
++ {
|
|
|
++ byte[] bytes = Encoding.ASCII.GetBytes(context.Request.Headers["X-ARR-ClientCert"]);
|
|
|
++ clientCertificate = new X509Certificate2(bytes);
|
|
|
++ }
|
|
|
++ if (context.Connection.ClientCertificate is not null)
|
|
|
++ {
|
|
|
++ clientCertificate = context.Connection.ClientCertificate;
|
|
|
++ }
|
|
|
++ if (clientCertificate is null)
|
|
|
++ {
|
|
|
++ clientCertificate = await context.Connection.GetClientCertificateAsync();
|
|
|
++ }
|
|
|
++ if (clientCertificate == null)
|
|
|
++ {
|
|
|
++ context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
|
|
++ logger.LogTrace("{id} {func} {Statuscode}", context.TraceIdentifier, nameof(ValidateHandshake), context.Response.StatusCode);
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++
|
|
|
++ int checkResult = await certificateService.CheckClientCertificate(session.ChargeBoxId, clientCertificate);
|
|
|
++ if (checkResult == -1)
|
|
|
++ {
|
|
|
++ context.Response.StatusCode = StatusCodes.Status401Unauthorized;
|
|
|
++ logger.LogTrace("{id} {func} {Statuscode}", context.TraceIdentifier, nameof(ValidateHandshake), context.Response.StatusCode);
|
|
|
++ return false;
|
|
|
++ }
|
|
|
++
|
|
|
++ session.IsSignedByCPO = checkResult == 0;
|
|
|
++ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (securityProfile == 1 || securityProfile == 2)
|
|
|
+diff --git a/EVCB_OCPP.WSServer/Service/WsService/WsClientData.cs b/EVCB_OCPP.WSServer/Service/WsService/WsClientData.cs
|
|
|
+index 30b3365..486eaa3 100644
|
|
|
+--- a/EVCB_OCPP.WSServer/Service/WsService/WsClientData.cs
|
|
|
++++ b/EVCB_OCPP.WSServer/Service/WsService/WsClientData.cs
|
|
|
+@@ -74,6 +74,7 @@ public class WsClientData : WsSession
|
|
|
+ public string CustomerName { get; set; }
|
|
|
+
|
|
|
+ public string StationId { set; get; }
|
|
|
++ public bool? IsSignedByCPO { set; get; } = null;
|
|
|
+
|
|
|
+ public event EventHandler<string> m_ReceiveData;
|
|
|
+
|
|
|
+diff --git a/EVCB_OCPP.WSServer/appsettings.json b/EVCB_OCPP.WSServer/appsettings.json
|
|
|
+index b52e084..8e7b622 100644
|
|
|
+--- a/EVCB_OCPP.WSServer/appsettings.json
|
|
|
++++ b/EVCB_OCPP.WSServer/appsettings.json
|
|
|
+@@ -4,6 +4,7 @@
|
|
|
+ "LocalAuthAPI": "",
|
|
|
+ "apipass": "12345678",
|
|
|
+ "LogProvider": "NLog",
|
|
|
++ "CertificateServerUrl": "http://localhost:81/",
|
|
|
+ "OCPP20_WSUrl": "ws://ocpp.phihong.com.tw:5004",
|
|
|
+ "OCPP20_WSSUrl": "ws://ocpp.phihong.com.tw:5004",
|
|
|
+ "MaintainMode": 0,
|