cert.patch 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. diff --git a/EVCB_OCPP.WSServer/DLL/EVCB_OCPP.Packet.dll b/EVCB_OCPP.WSServer/DLL/EVCB_OCPP.Packet.dll
  2. index 42f3575..e8dce54 100644
  3. Binary files a/EVCB_OCPP.WSServer/DLL/EVCB_OCPP.Packet.dll and b/EVCB_OCPP.WSServer/DLL/EVCB_OCPP.Packet.dll differ
  4. diff --git a/EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs b/EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs
  5. index 1698606..0cb0fea 100644
  6. --- a/EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs
  7. +++ b/EVCB_OCPP.WSServer/Message/CoreProfileHandler.cs
  8. @@ -72,6 +72,7 @@ public partial class ProfileHandler
  9. //private readonly IDbContextFactory<MeterValueDBContext> metervaluedbContextFactory;
  10. private readonly IBusinessServiceFactory businessServiceFactory;
  11. private readonly IMainDbService mainDbService;
  12. + private readonly CertificateService certService;
  13. private OuterHttpClient httpClient;
  14. public ProfileHandler(
  15. @@ -82,6 +83,7 @@ public partial class ProfileHandler
  16. MeterValueDbService meterValueDbService,
  17. IBusinessServiceFactory businessServiceFactory,
  18. IMainDbService mainDbService,
  19. + CertificateService certService,
  20. ILogger<ProfileHandler> logger,
  21. OuterHttpClient httpClient)
  22. {
  23. @@ -93,6 +95,7 @@ public partial class ProfileHandler
  24. this.webDbConnectionFactory = webDbConnectionFactory;
  25. this.meterValueDbService = meterValueDbService;
  26. this.mainDbService = mainDbService;
  27. + this.certService = certService;
  28. //this.metervaluedbContextFactory = metervaluedbContextFactory;
  29. this.businessServiceFactory = businessServiceFactory;
  30. this.httpClient = httpClient;
  31. diff --git a/EVCB_OCPP.WSServer/Message/SecurityProfileHandler.cs b/EVCB_OCPP.WSServer/Message/SecurityProfileHandler.cs
  32. index 96b07ba..cd277eb 100644
  33. --- a/EVCB_OCPP.WSServer/Message/SecurityProfileHandler.cs
  34. +++ b/EVCB_OCPP.WSServer/Message/SecurityProfileHandler.cs
  35. @@ -3,12 +3,20 @@ using EVCB_OCPP.Packet.Messages;
  36. using System;
  37. using Microsoft.Extensions.Logging;
  38. using EVCB_OCPP.WSServer.Service.WsService;
  39. +using EVCB_OCPP.Packet.Messages.RemoteTrigger;
  40. +using Microsoft.EntityFrameworkCore;
  41. +using EVCB_OCPP.Packet.Messages.Security;
  42. +using Microsoft.IdentityModel.Tokens;
  43. +using System.Runtime.ConstrainedExecution;
  44. +using System.Security.Cryptography.X509Certificates;
  45. +using System.Security.Cryptography;
  46. +using System.Text.RegularExpressions;
  47. namespace EVCB_OCPP.WSServer.Message
  48. {
  49. public partial class ProfileHandler
  50. {
  51. - internal MessageResult ExecuteSecurityRequest(Actions action, WsClientData session, IRequest request)
  52. + internal async Task<MessageResult> ExecuteSecurityRequest(Actions action, WsClientData session, IRequest request)
  53. {
  54. MessageResult result = new MessageResult() { Success = false };
  55. @@ -16,8 +24,68 @@ namespace EVCB_OCPP.WSServer.Message
  56. {
  57. switch (action)
  58. {
  59. + case Actions.SignCertificate:
  60. + {
  61. + SignCertificateRequest _request = request as SignCertificateRequest;
  62. + SignCertificateConfirmation confirm = new();
  63. +
  64. + if (string.IsNullOrEmpty(_request.csr))
  65. + {
  66. + result.Success = false;
  67. + return result;
  68. + }
  69. + bool isCsrValid = CheckCsr(session.ChargeBoxId, _request.csr);
  70. + if (!isCsrValid)
  71. + {
  72. + confirm.status = Packet.Messages.SubTypes.GenericStatusEnumType.Rejected;
  73. + result.Message = confirm;
  74. + result.Success = false;
  75. + return result;
  76. + }
  77. + _ = certService.SignCertificate(session.ChargeBoxId, _request.csr);
  78. + confirm.status = Packet.Messages.SubTypes.GenericStatusEnumType.Accepted;
  79. + result.Message = confirm;
  80. + result.Success = true;
  81. + return result;
  82. + }
  83. + case Actions.SecurityEventNotification:
  84. + {
  85. + SecurityEventNotificationRequest _request = request as SecurityEventNotificationRequest;
  86. + SecurityEventNotificationConfirmation confirm = new();
  87. +
  88. + logger.LogInformation("{chargeBoxId} security notification {sects} {sectype} {secmsg}", session.ChargeBoxId, _request.timestamp, _request.type, _request.techInfo);
  89. +
  90. + result.Message = confirm;
  91. + result.Success = true;
  92. + return result;
  93. + }
  94. + case Actions.LogStatusNotification:
  95. + {
  96. + LogStatusNotificationRequest _request = request as LogStatusNotificationRequest;
  97. + LogStatusNotificationConfirmation confirm = new();
  98. +
  99. + if (_request.status != Packet.Messages.SubTypes.UploadLogStatusEnumType.Idle)
  100. + {
  101. + using (var db = await maindbContextFactory.CreateDbContextAsync())
  102. + {
  103. + var item = await db.MachineOperateRecords.Where(x => x.ChargeBoxId == session.ChargeBoxId && x.Action == "GetLog" && x.RequestType == 1)
  104. + .OrderByDescending(x => x.CreatedOn).FirstOrDefaultAsync();
  105. + if (item != null)
  106. + {
  107. + item.EvseStatus = (int)_request.status;
  108. + item.FinishedOn = DateTime.UtcNow;
  109. + }
  110. +
  111. + await db.SaveChangesAsync();
  112. + }
  113. +
  114. + }
  115. + result.Message = confirm;
  116. + result.Success = true;
  117. + return result;
  118. + }
  119. default:
  120. {
  121. logger.LogWarning(string.Format("Not Implement {0} Logic(ExecuteCoreRequest)", request.GetType().ToString().Replace("OCPPPackage.Messages.Core.", "")));
  122. @@ -37,13 +105,141 @@ namespace EVCB_OCPP.WSServer.Message
  123. return result;
  124. }
  125. - internal MessageResult ExecuteSecurityConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
  126. +
  127. + private bool CheckCsr(string chargeBoxId, string csrString)
  128. + {
  129. + string subject = certService.GetCertificateRequestSubject(csrString);
  130. + logger.LogInformation("{chargeBoxId} send scr {subject}", chargeBoxId, subject);
  131. + if (subject == null)
  132. + {
  133. + return false;
  134. + }
  135. +
  136. + Dictionary<string,string> csrInfo = certService.SubjectToDictionary(subject);
  137. + if (csrInfo == null ||
  138. + !csrInfo.ContainsKey("CN") ||
  139. + csrInfo["CN"] != chargeBoxId)
  140. + {
  141. + return false;
  142. + }
  143. + return true;
  144. + }
  145. +
  146. + internal async Task<MessageResult> ExecuteSecurityConfirm(Actions action, WsClientData session, IConfirmation confirm, string requestId)
  147. {
  148. MessageResult result = new MessageResult() { Success = false };
  149. switch (action)
  150. {
  151. -
  152. + case Actions.ExtendedTriggerMessage:
  153. + {
  154. + ExtendedTriggerMessageConfirmation _confirm = confirm as ExtendedTriggerMessageConfirmation;
  155. + //ExtendedTriggerMessageRequest _request = _confirm.GetRequest() as ExtendedTriggerMessageRequest;
  156. + using (var db = await maindbContextFactory.CreateDbContextAsync())
  157. + {
  158. + var operation = await db.MachineOperateRecords.Where(x => x.SerialNo == requestId &&
  159. + x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
  160. + if (operation != null)
  161. + {
  162. + operation.FinishedOn = DateTime.UtcNow;
  163. + operation.Status = 1;//電樁有回覆
  164. + operation.EvseStatus = (int)_confirm.status;//OK
  165. + operation.EvseValue = _confirm.status.ToString();
  166. + await db.SaveChangesAsync();
  167. + }
  168. + }
  169. + }
  170. + break;
  171. + case Actions.CertificateSigned:
  172. + {
  173. + CertificateSignedConfirmation _confirm = confirm as CertificateSignedConfirmation;
  174. + using (var db = await maindbContextFactory.CreateDbContextAsync())
  175. + {
  176. + var operation = await db.MachineOperateRecords.Where(x => x.SerialNo == requestId &&
  177. + x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
  178. + if (operation != null)
  179. + {
  180. + operation.FinishedOn = DateTime.UtcNow;
  181. + operation.Status = 1;//電樁有回覆
  182. + operation.EvseStatus = (int)_confirm.status;//OK
  183. + operation.EvseValue = _confirm.status.ToString();
  184. + await db.SaveChangesAsync();
  185. + }
  186. + }
  187. + }
  188. + break;
  189. + case Actions.GetInstalledCertificateIds:
  190. + {
  191. + GetInstalledCertificateIdsConfirmation _confirm = confirm as GetInstalledCertificateIdsConfirmation;
  192. + using (var db = await maindbContextFactory.CreateDbContextAsync())
  193. + {
  194. + var operation = await db.MachineOperateRecords.Where(x => x.SerialNo == requestId &&
  195. + x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
  196. + if (operation != null)
  197. + {
  198. + operation.FinishedOn = DateTime.UtcNow;
  199. + operation.Status = 1;//電樁有回覆
  200. + operation.EvseStatus = (int)_confirm.status;//OK
  201. + operation.EvseValue = _confirm.status.ToString();
  202. + await db.SaveChangesAsync();
  203. + }
  204. + }
  205. + }
  206. + break;
  207. + case Actions.DeleteCertificate:
  208. + {
  209. + DeleteCertificateConfirmation _confirm = confirm as DeleteCertificateConfirmation;
  210. + using (var db = await maindbContextFactory.CreateDbContextAsync())
  211. + {
  212. + var operation = await db.MachineOperateRecords.Where(x => x.SerialNo == requestId &&
  213. + x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
  214. + if (operation != null)
  215. + {
  216. + operation.FinishedOn = DateTime.UtcNow;
  217. + operation.Status = 1;//電樁有回覆
  218. + operation.EvseStatus = (int)_confirm.status;//OK
  219. + operation.EvseValue = _confirm.status.ToString();
  220. + await db.SaveChangesAsync();
  221. + }
  222. + }
  223. + }
  224. + break;
  225. + case Actions.InstallCertificate:
  226. + {
  227. + InstallCertificateConfirmation _confirm = confirm as InstallCertificateConfirmation;
  228. + using (var db = await maindbContextFactory.CreateDbContextAsync())
  229. + {
  230. + var operation = await db.MachineOperateRecords.Where(x => x.SerialNo == requestId &&
  231. + x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
  232. + if (operation != null)
  233. + {
  234. + operation.FinishedOn = DateTime.UtcNow;
  235. + operation.Status = 1;//電樁有回覆
  236. + operation.EvseStatus = (int)_confirm.status;//OK
  237. + operation.EvseValue = _confirm.status.ToString();
  238. + await db.SaveChangesAsync();
  239. + }
  240. + }
  241. + }
  242. + break;
  243. + case Actions.GetLog:
  244. + {
  245. + GetLogConfirmation _confirm = confirm as GetLogConfirmation;
  246. + using (var db = await maindbContextFactory.CreateDbContextAsync())
  247. + {
  248. + var operation = await db.MachineOperateRecords.Where(x => x.SerialNo == requestId &&
  249. + x.ChargeBoxId == session.ChargeBoxId && x.Status == 0).FirstOrDefaultAsync();
  250. + if (operation != null)
  251. + {
  252. + operation.FinishedOn = DateTime.UtcNow;
  253. + operation.Status = 1;//電樁有回覆
  254. + operation.EvseStatus = (int)_confirm.status;//OK
  255. + operation.EvseValue = _confirm.status.ToString();
  256. + await db.SaveChangesAsync();
  257. + }
  258. + }
  259. + }
  260. + break;
  261. default:
  262. {
  263. logger.LogWarning(string.Format("Not Implement {0} Logic", confirm.GetType().ToString().Replace("OCPPPackage.Messages.RemoteTrigger.", "")));
  264. diff --git a/EVCB_OCPP.WSServer/Program.cs b/EVCB_OCPP.WSServer/Program.cs
  265. index a2b2b57..493bd08 100644
  266. --- a/EVCB_OCPP.WSServer/Program.cs
  267. +++ b/EVCB_OCPP.WSServer/Program.cs
  268. @@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Hosting;
  269. using EVCB_OCPP.WSServer.Service.WsService;
  270. using EVCB_OCPP.Service;
  271. using Microsoft.Extensions.Logging;
  272. +using System.Net.Security;
  273. +using System.Security.Cryptography.X509Certificates;
  274. namespace EVCB_OCPP.WSServer
  275. {
  276. @@ -51,11 +53,19 @@ namespace EVCB_OCPP.WSServer
  277. //services.AddTransient<BlockingTreePrintService>();
  278. //services.AddTransient<GoogleGetTimePrintService>();
  279. });
  280. + builder.WebHost.ConfigureKestrel(opt => {
  281. + opt.ConfigureHttpsDefaults(opt =>
  282. + {
  283. + opt.ClientCertificateMode = Microsoft.AspNetCore.Server.Kestrel.Https.ClientCertificateMode.AllowCertificate;
  284. + opt.ClientCertificateValidation = ValidateClientCertficatel;
  285. + });
  286. + });
  287. var app = builder.Build();
  288. app.UseHeaderRecordService();
  289. app.UseOcppWsService();
  290. app.MapApiServce();
  291. app.Urls.Add("http://*:80");
  292. + app.Urls.Add("https://*:443");
  293. app.Run();
  294. }
  295. @@ -69,5 +79,10 @@ namespace EVCB_OCPP.WSServer
  296. }
  297. return timevalue;
  298. }
  299. +
  300. + private static bool ValidateClientCertficatel(X509Certificate2 clientCertificate, X509Chain chain, SslPolicyErrors sslPolicyErrors)
  301. + {
  302. + return true;
  303. + }
  304. }
  305. }
  306. diff --git a/EVCB_OCPP.WSServer/ProtalServer.cs b/EVCB_OCPP.WSServer/ProtalServer.cs
  307. index 4a5cebc..a16a7f8 100644
  308. --- a/EVCB_OCPP.WSServer/ProtalServer.cs
  309. +++ b/EVCB_OCPP.WSServer/ProtalServer.cs
  310. @@ -21,6 +21,8 @@ using System.Net;
  311. using System.Text;
  312. using EVCB_OCPP.WSServer.Service.WsService;
  313. using System.ServiceModel.Channels;
  314. +using EVCB_OCPP.Packet.Messages.RemoteTrigger;
  315. +using EVCB_OCPP.Packet.Messages.Security;
  316. namespace EVCB_OCPP.WSServer
  317. {
  318. @@ -697,6 +699,18 @@ namespace EVCB_OCPP.WSServer
  319. {
  320. session.IsCheckIn = true;
  321. + if (session.IsSignedByCPO == false)
  322. + {
  323. + var _request = new ExtendedTriggerMessageRequest()
  324. + {
  325. + requestedMessage = Packet.Messages.SubTypes.MessageTriggerEnumType.SignChargePointCertificate
  326. + };
  327. +
  328. + var uuid = session.queue.store(_request);
  329. + string rawRequest = BasicMessageHandler.GenerateRequest(uuid, _request.Action, _request);
  330. + SendMsg(session, rawRequest, string.Format("{0} {1}", _request.Action, "Request"), "");
  331. + }
  332. +
  333. await messageService.SendGetEVSEConfigureRequest(session.ChargeBoxId);
  334. await messageService.SendChangeConfigurationRequest(
  335. @@ -831,7 +845,7 @@ namespace EVCB_OCPP.WSServer
  336. break;
  337. case "Security":
  338. {
  339. - var replyResult = profileHandler.ExecuteSecurityRequest(action, session, (IRequest)analysisResult.Message);
  340. + var replyResult = await profileHandler.ExecuteSecurityRequest(action, session, (IRequest)analysisResult.Message);
  341. if (replyResult.Success)
  342. {
  343. string response = BasicMessageHandler.GenerateConfirmation(analysisResult.UUID, (IConfirmation)replyResult.Message);
  344. @@ -905,7 +919,7 @@ namespace EVCB_OCPP.WSServer
  345. break;
  346. case "Security":
  347. {
  348. - confirmResult = profileHandler.ExecuteSecurityConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
  349. + confirmResult = await profileHandler.ExecuteSecurityConfirm(action, session, (IConfirmation)analysisResult.Message, analysisResult.RequestId);
  350. }
  351. break;
  352. default:
  353. diff --git a/EVCB_OCPP.WSServer/Service/CertificateService.cs b/EVCB_OCPP.WSServer/Service/CertificateService.cs
  354. new file mode 100644
  355. index 0000000..2f19f25
  356. --- /dev/null
  357. +++ b/EVCB_OCPP.WSServer/Service/CertificateService.cs
  358. @@ -0,0 +1,142 @@
  359. +using Microsoft.Extensions.Configuration;
  360. +using Microsoft.Extensions.Logging;
  361. +using System;
  362. +using System.Collections.Generic;
  363. +using System.Linq;
  364. +using System.Net.Http.Json;
  365. +using System.Runtime;
  366. +using System.Runtime.ConstrainedExecution;
  367. +using System.Security.Cryptography;
  368. +using System.Security.Cryptography.X509Certificates;
  369. +using System.Text;
  370. +using System.Text.RegularExpressions;
  371. +using System.Threading.Tasks;
  372. +
  373. +namespace EVCB_OCPP.WSServer.Service
  374. +{
  375. + public class CertificateService
  376. + {
  377. +
  378. + public CertificateService(
  379. + ServerMessageService messageService,
  380. + IConfiguration configuration,
  381. + ILogger<CertificateService> logger)
  382. + {
  383. + _serverUrl = configuration["CertificateServerUrl"];
  384. + _cpon = configuration["ChargingPointOperator"];
  385. + this.messageService = messageService;
  386. + this.logger = logger;
  387. + }
  388. + private readonly string _serverUrl;
  389. + private readonly string _cpon;
  390. + private readonly ServerMessageService messageService;
  391. + private readonly ILogger<CertificateService> logger;
  392. +
  393. + public async Task SignCertificate(string chargeBoxId, string csr)
  394. + {
  395. + HttpClient client = new HttpClient();
  396. + client.BaseAddress = new Uri(_serverUrl);
  397. + var signResult = await client.PostAsJsonAsync("cert/csr", new { csr = csr });
  398. + if (!signResult.IsSuccessStatusCode)
  399. + {
  400. + logger.LogWarning("{chargeBoxId} csr failed {csr}", chargeBoxId, csr);
  401. + return;
  402. + }
  403. + var crt = await signResult.Content.ReadAsStringAsync();
  404. + await messageService.SendCertificateSignedRequest(chargeBoxId, crt);
  405. + client.Dispose();
  406. + }
  407. +
  408. + public Task<int> CheckClientCertificate(string chargeboxId, X509Certificate2 cert)
  409. + {
  410. + return CheckClientCertificate(chargeboxId, cert.ExportCertificatePem());
  411. + }
  412. +
  413. + public async Task<int> CheckClientCertificate(string chargeboxId, string certString)
  414. + {
  415. + var subject = GetCertificateSubject(certString);
  416. + logger.LogInformation("{chargeboxId} with cert subject {subject}", chargeboxId, subject);
  417. + if (string.IsNullOrEmpty(subject))
  418. + {
  419. + return -1;
  420. + }
  421. +
  422. + var crtInfo = SubjectToDictionary(subject);
  423. + if (crtInfo == null ||
  424. + //!crtInfo.ContainsKey("O") ||
  425. + //crtInfo["O"] != _cpon ||
  426. + !crtInfo.ContainsKey("CN") ||
  427. + crtInfo["CN"] != chargeboxId)
  428. + {
  429. + return -1;
  430. + }
  431. +
  432. + int certServerResult = await CheckClientCertificateRca(certString);
  433. + logger.LogInformation("{chargeboxId} with cert authenticate {subject}", chargeboxId, certServerResult);
  434. + return certServerResult;
  435. + }
  436. +
  437. + public async Task<int> CheckClientCertificateRca(string certString)
  438. + {
  439. + using HttpClient client = new HttpClient();
  440. + client.BaseAddress = new Uri(_serverUrl);
  441. + var result = await client.PutAsJsonAsync("cert/crt", new { crt = certString });
  442. + if (!result.IsSuccessStatusCode)
  443. + {
  444. + return -1;
  445. + }
  446. + var resultContentString = await result.Content.ReadAsStringAsync();
  447. + return Convert.ToInt32(resultContentString);
  448. + }
  449. +
  450. + public Dictionary<string, string> SubjectToDictionary(string subject)
  451. + {
  452. + try
  453. + {
  454. + MatchCollection regexResult = Regex.Matches(subject, "([a-zA-Z]*)=([a-zA-Z0-9]*)");
  455. + if (regexResult.Count == 0)
  456. + {
  457. + return null;
  458. + }
  459. + return regexResult.Where(x => x.Success == true && x.Groups.Count == 3).ToDictionary(x => x.Groups[1].Value, x => x.Groups[2].Value);
  460. + }
  461. + catch (Exception e)
  462. + {
  463. + logger.LogCritical(e.Message);
  464. + logger.LogCritical(e.StackTrace);
  465. + return null;
  466. + }
  467. + }
  468. +
  469. + public string GetCertificateSubject(string crt)
  470. + {
  471. +
  472. + try
  473. + {
  474. + byte[] bytes = Encoding.ASCII.GetBytes(crt);
  475. + var clientCertificate = new X509Certificate2(bytes);
  476. +
  477. + return clientCertificate.Subject;
  478. + }
  479. + catch
  480. + {
  481. + return null;
  482. + }
  483. + }
  484. +
  485. + public string GetCertificateRequestSubject(string csrString)
  486. + {
  487. +
  488. + try
  489. + {
  490. + var csr = CertificateRequest.LoadSigningRequestPem(csrString, HashAlgorithmName.SHA512);
  491. + var test = csr.SubjectName.Name;
  492. + return test;
  493. + }
  494. + catch
  495. + {
  496. + return null;
  497. + }
  498. + }
  499. + }
  500. +}
  501. diff --git a/EVCB_OCPP.WSServer/Service/ServerMessageService.cs b/EVCB_OCPP.WSServer/Service/ServerMessageService.cs
  502. index 9a3b0e2..6379c21 100644
  503. --- a/EVCB_OCPP.WSServer/Service/ServerMessageService.cs
  504. +++ b/EVCB_OCPP.WSServer/Service/ServerMessageService.cs
  505. @@ -1,6 +1,9 @@
  506. using EVCB_OCPP.Packet.Features;
  507. using EVCB_OCPP.Packet.Messages.Core;
  508. using Microsoft.Extensions.Logging;
  509. +using static System.Runtime.InteropServices.JavaScript.JSType;
  510. +using System.ServiceModel.Channels;
  511. +using EVCB_OCPP.Packet.Messages.Security;
  512. namespace EVCB_OCPP.WSServer.Service;
  513. @@ -52,4 +55,16 @@ public class ServerMessageService
  514. }
  515. );
  516. }
  517. +
  518. + internal Task SendCertificateSignedRequest(string chargeBoxId, string crt)
  519. + {
  520. + return mainDbService.AddServerMessage(
  521. + ChargeBoxId: chargeBoxId,
  522. + OutAction: Actions.SignCertificate.ToString(),
  523. + OutRequest: new CertificateSignedRequest()
  524. + {
  525. + certificateChain = crt
  526. + }
  527. + );
  528. + }
  529. }
  530. diff --git a/EVCB_OCPP.WSServer/Service/WsService/OcppWebsocketService.cs b/EVCB_OCPP.WSServer/Service/WsService/OcppWebsocketService.cs
  531. index ba1df1c..8cd38ae 100644
  532. --- a/EVCB_OCPP.WSServer/Service/WsService/OcppWebsocketService.cs
  533. +++ b/EVCB_OCPP.WSServer/Service/WsService/OcppWebsocketService.cs
  534. @@ -5,6 +5,7 @@ using Microsoft.Extensions.Configuration;
  535. using Microsoft.Extensions.DependencyInjection;
  536. using Microsoft.Extensions.Logging;
  537. using System.Net.WebSockets;
  538. +using System.Security.Cryptography.X509Certificates;
  539. using System.Text;
  540. namespace EVCB_OCPP.WSServer.Service.WsService;
  541. @@ -13,6 +14,7 @@ public static partial class AppExtention
  542. {
  543. public static void AddOcppWsServer(this IServiceCollection services)
  544. {
  545. + services.AddTransient<CertificateService>();
  546. services.AddTransient<WsClientData>();
  547. services.AddSingleton<OcppWebsocketService>();
  548. }
  549. @@ -45,6 +47,7 @@ public class OcppWebsocketService : WebsocketService<WsClientData>
  550. private readonly IConfiguration configuration;
  551. private readonly IMainDbService mainDbService;
  552. + private readonly CertificateService certificateService;
  553. private readonly ILogger<OcppWebsocketService> logger;
  554. private readonly QueueSemaphore handshakeSemaphore;
  555. @@ -53,11 +56,13 @@ public class OcppWebsocketService : WebsocketService<WsClientData>
  556. IConfiguration configuration,
  557. IServiceProvider serviceProvider,
  558. IMainDbService mainDbService,
  559. + CertificateService certificateService,
  560. ILogger<OcppWebsocketService> logger
  561. ) : base(serviceProvider)
  562. {
  563. this.configuration = configuration;
  564. this.mainDbService = mainDbService;
  565. + this.certificateService = certificateService;
  566. this.logger = logger;
  567. handshakeSemaphore = new QueueSemaphore(5);
  568. @@ -182,11 +187,46 @@ public class OcppWebsocketService : WebsocketService<WsClientData>
  569. securityProfile = 0;
  570. }
  571. - if (securityProfile == 3 && session.UriScheme == "ws")
  572. + if (securityProfile == 3 )
  573. {
  574. - context.Response.StatusCode = StatusCodes.Status401Unauthorized;
  575. - logger.LogTrace("{id} {func} {Statuscode}", context.TraceIdentifier, nameof(ValidateHandshake), context.Response.StatusCode);
  576. - return false;
  577. + if (session.UriScheme == "ws")
  578. + {
  579. + context.Response.StatusCode = StatusCodes.Status401Unauthorized;
  580. + logger.LogTrace("{id} {func} {Statuscode}", context.TraceIdentifier, nameof(ValidateHandshake), context.Response.StatusCode);
  581. + return false;
  582. + }
  583. +
  584. + X509Certificate2 clientCertificate = null;
  585. + if (context.Request.Headers.ContainsKey("X-ARR-ClientCert"))
  586. + {
  587. + byte[] bytes = Encoding.ASCII.GetBytes(context.Request.Headers["X-ARR-ClientCert"]);
  588. + clientCertificate = new X509Certificate2(bytes);
  589. + }
  590. + if (context.Connection.ClientCertificate is not null)
  591. + {
  592. + clientCertificate = context.Connection.ClientCertificate;
  593. + }
  594. + if (clientCertificate is null)
  595. + {
  596. + clientCertificate = await context.Connection.GetClientCertificateAsync();
  597. + }
  598. + if (clientCertificate == null)
  599. + {
  600. + context.Response.StatusCode = StatusCodes.Status401Unauthorized;
  601. + logger.LogTrace("{id} {func} {Statuscode}", context.TraceIdentifier, nameof(ValidateHandshake), context.Response.StatusCode);
  602. + return false;
  603. + }
  604. +
  605. + int checkResult = await certificateService.CheckClientCertificate(session.ChargeBoxId, clientCertificate);
  606. + if (checkResult == -1)
  607. + {
  608. + context.Response.StatusCode = StatusCodes.Status401Unauthorized;
  609. + logger.LogTrace("{id} {func} {Statuscode}", context.TraceIdentifier, nameof(ValidateHandshake), context.Response.StatusCode);
  610. + return false;
  611. + }
  612. +
  613. + session.IsSignedByCPO = checkResult == 0;
  614. + return true;
  615. }
  616. if (securityProfile == 1 || securityProfile == 2)
  617. diff --git a/EVCB_OCPP.WSServer/Service/WsService/WsClientData.cs b/EVCB_OCPP.WSServer/Service/WsService/WsClientData.cs
  618. index 30b3365..486eaa3 100644
  619. --- a/EVCB_OCPP.WSServer/Service/WsService/WsClientData.cs
  620. +++ b/EVCB_OCPP.WSServer/Service/WsService/WsClientData.cs
  621. @@ -74,6 +74,7 @@ public class WsClientData : WsSession
  622. public string CustomerName { get; set; }
  623. public string StationId { set; get; }
  624. + public bool? IsSignedByCPO { set; get; } = null;
  625. public event EventHandler<string> m_ReceiveData;
  626. diff --git a/EVCB_OCPP.WSServer/appsettings.json b/EVCB_OCPP.WSServer/appsettings.json
  627. index b52e084..8e7b622 100644
  628. --- a/EVCB_OCPP.WSServer/appsettings.json
  629. +++ b/EVCB_OCPP.WSServer/appsettings.json
  630. @@ -4,6 +4,7 @@
  631. "LocalAuthAPI": "",
  632. "apipass": "12345678",
  633. "LogProvider": "NLog",
  634. + "CertificateServerUrl": "http://localhost:81/",
  635. "OCPP20_WSUrl": "ws://ocpp.phihong.com.tw:5004",
  636. "OCPP20_WSSUrl": "ws://ocpp.phihong.com.tw:5004",
  637. "MaintainMode": 0,