Codemaru
1.0.0.0
QR 코드, 모바일 랜딩 페이지, vCard 연락처 저장과 명함 디자인을 한 화면에서 제공하는 디지털 명함 서비스입니다.
Toggle main menu visibility
로딩중...
검색중...
일치하는것 없음
X509CertificateMonitorService.cs
이 파일의 문서화 페이지로 가기
1
using
Codemaru.Models.Certificates
;
2
using
Codemaru.Options
;
3
using
System;
4
using
System.Collections.Generic;
5
using
System.IO;
6
using
System.Linq;
7
using
System.Security.Cryptography;
8
using
System.Security.Cryptography.X509Certificates;
9
10
namespace
Codemaru.Services.Certificates
;
11
20
public
sealed
class
X509CertificateMonitorService
:
ICertificateMonitorService
21
{
54
public
Task<CertificateStatusInfo>
GetStatusAsync
(
55
CertificateMonitorOptions
options,
56
CancellationToken cancellationToken)
57
{
58
ArgumentNullException.ThrowIfNull(options);
59
cancellationToken.ThrowIfCancellationRequested();
60
61
if
(
string
.IsNullOrWhiteSpace(options.
CertificateDirectory
))
62
{
63
return
Task.FromResult(
Failure
(options.
CertificateDirectory
,
"Certificate directory is empty."
));
64
}
65
66
if
(!Directory.Exists(options.
CertificateDirectory
))
67
{
68
return
Task.FromResult(
Failure
(
69
options.
CertificateDirectory
,
70
$
"Certificate directory not found: {options.CertificateDirectory}"
));
71
}
72
73
try
74
{
75
string
[] files =
FindCertificateFiles
(options).ToArray();
76
77
if
(files.Length == 0)
78
{
79
return
Task.FromResult(
Failure
(
80
options.
CertificateDirectory
,
81
$
"No certificate files were found. Directory: {options.CertificateDirectory}"
));
82
}
83
84
List<string> readErrors = [];
85
List<CertificateStatusInfo> candidates = [];
86
87
foreach
(
string
file
in
files)
88
{
89
cancellationToken.ThrowIfCancellationRequested();
90
91
CertificateStatusInfo
? status =
TryReadCertificate
(file, options, readErrors);
92
if
(status is not
null
)
93
{
94
candidates.Add(status);
95
}
96
}
97
98
if
(candidates.Count == 0)
99
{
100
string
errorText = readErrors.Count == 0
101
?
"No detailed read errors were captured."
102
:
string
.Join(Environment.NewLine, readErrors);
103
104
return
Task.FromResult(
Failure
(
105
options.
CertificateDirectory
,
106
$
"Certificate files were found, but none could be loaded.{Environment.NewLine}{errorText}"
));
107
}
108
109
CertificateStatusInfo
selected =
SelectBestCertificate
(candidates);
110
111
return
Task.FromResult(selected);
112
}
113
catch
(OperationCanceledException)
114
{
115
throw
;
116
}
117
catch
(Exception ex)
118
{
119
return
Task.FromResult(
Failure
(options.
CertificateDirectory
, ex.Message));
120
}
121
}
122
147
private
static
IEnumerable<string>
FindCertificateFiles
(
CertificateMonitorOptions
options)
148
{
149
string
[] patterns = options.CertificateFilePatterns is { Length: > 0 }
150
? options.CertificateFilePatterns
151
: [
"*.pem"
,
"*.cer"
,
"*.crt"
,
"*.pfx"
,
"*.p12"
];
152
153
IEnumerable<string> files = patterns
154
.SelectMany(pattern =>
155
Directory.EnumerateFiles(
156
options.
CertificateDirectory
,
157
pattern,
158
SearchOption.AllDirectories));
159
160
return
files
161
.Where(
IsCertificateCandidate
)
162
.OrderByDescending(
GetCertificatePriority
)
163
.ThenByDescending(File.GetLastWriteTimeUtc);
164
}
165
190
private
static
bool
IsCertificateCandidate
(
string
path)
191
{
192
string
fileName = Path.GetFileName(path).ToLowerInvariant();
193
string
extension = Path.GetExtension(path).ToLowerInvariant();
194
195
if
(fileName.Contains(
"key"
, StringComparison.OrdinalIgnoreCase))
196
{
197
return
false
;
198
}
199
200
if
(fileName.Contains(
"privkey"
, StringComparison.OrdinalIgnoreCase))
201
{
202
return
false
;
203
}
204
205
if
(fileName.Contains(
"chain-only"
, StringComparison.OrdinalIgnoreCase))
206
{
207
return
false
;
208
}
209
210
return
extension is
".pem"
or
".crt"
or
".cer"
or
".pfx"
or
".p12"
;
211
}
212
237
private
static
int
GetCertificatePriority
(
string
path)
238
{
239
string
fileName = Path.GetFileName(path).ToLowerInvariant();
240
241
if
(fileName.Contains(
"-crt.pem"
, StringComparison.OrdinalIgnoreCase))
242
{
243
return
700;
244
}
245
246
if
(fileName.Contains(
"fullchain"
, StringComparison.OrdinalIgnoreCase))
247
{
248
return
600;
249
}
250
251
if
(fileName.EndsWith(
".crt"
, StringComparison.OrdinalIgnoreCase))
252
{
253
return
500;
254
}
255
256
if
(fileName.EndsWith(
".cer"
, StringComparison.OrdinalIgnoreCase))
257
{
258
return
400;
259
}
260
261
if
(fileName.EndsWith(
".pfx"
, StringComparison.OrdinalIgnoreCase) ||
262
fileName.EndsWith(
".p12"
, StringComparison.OrdinalIgnoreCase))
263
{
264
return
300;
265
}
266
267
if
(fileName.EndsWith(
".pem"
, StringComparison.OrdinalIgnoreCase))
268
{
269
return
100;
270
}
271
272
return
0;
273
}
274
315
private
static
CertificateStatusInfo
?
TryReadCertificate
(
316
string
path,
317
CertificateMonitorOptions
options,
318
List<string> readErrors)
319
{
320
try
321
{
322
using
X509Certificate2 certificate =
LoadCertificate
(path, options);
323
324
if
(
string
.IsNullOrWhiteSpace(certificate.Subject))
325
{
326
readErrors.Add($
"{Path.GetFileName(path)}: Certificate subject is empty."
);
327
return
null
;
328
}
329
330
DateTimeOffset notAfter =
new
(certificate.NotAfter);
331
DateTimeOffset notBefore =
new
(certificate.NotBefore);
332
int
remainingDays = (int)Math.Floor((notAfter - DateTimeOffset.Now).TotalDays);
333
CertificateHealthState
state =
ResolveState
(remainingDays, options);
334
335
return
new
CertificateStatusInfo
336
{
337
IsSuccess =
true
,
338
CertificateDirectory = options.
CertificateDirectory
,
339
CertificatePath = path,
340
Subject = certificate.Subject,
341
Issuer = certificate.Issuer,
342
NotBefore = notBefore,
343
NotAfter = notAfter,
344
RemainingDays = remainingDays,
345
State = state,
346
Message =
BuildMessage
(state, remainingDays, path)
347
};
348
}
349
catch
(Exception ex)
350
{
351
readErrors.Add($
"{Path.GetFileName(path)}: {ex.GetType().Name} - {ex.Message}"
);
352
return
null
;
353
}
354
}
355
388
private
static
X509Certificate2
LoadCertificate
(
389
string
path,
390
CertificateMonitorOptions
options)
391
{
392
string
extension = Path.GetExtension(path).ToLowerInvariant();
393
394
if
(extension is
".pfx"
or
".p12"
)
395
{
396
return
string
.IsNullOrEmpty(options.
PfxPassword
)
397
?
new
X509Certificate2(path)
398
:
new
X509Certificate2(path, options.
PfxPassword
);
399
}
400
401
if
(extension ==
".pem"
)
402
{
403
return
LoadCertificateFromPem
(path);
404
}
405
406
return
new
X509Certificate2(path);
407
}
408
441
private
static
X509Certificate2
LoadCertificateFromPem
(
string
path)
442
{
443
string
pem = File.ReadAllText(path);
444
445
X509Certificate2Collection collection =
new
();
446
collection.ImportFromPem(pem);
447
448
if
(collection.Count == 0)
449
{
450
throw
new
InvalidOperationException(
"No certificate block was found in the PEM file."
);
451
}
452
453
X509Certificate2? serverCertificate = collection
454
.OfType<X509Certificate2>()
455
.Where(
IsServerCertificate
)
456
.OrderByDescending(item => item.NotAfter)
457
.FirstOrDefault();
458
459
if
(serverCertificate is not
null
)
460
{
461
return
new
X509Certificate2(serverCertificate.RawData);
462
}
463
464
X509Certificate2 first = collection
465
.OfType<X509Certificate2>()
466
.OrderByDescending(item => item.NotAfter)
467
.First();
468
469
return
new
X509Certificate2(first.RawData);
470
}
471
496
private
static
bool
IsServerCertificate
(X509Certificate2 certificate)
497
{
498
foreach
(X509Extension extension
in
certificate.Extensions)
499
{
500
if
(extension is X509EnhancedKeyUsageExtension eku)
501
{
502
foreach
(Oid oid
in
eku.EnhancedKeyUsages)
503
{
504
if
(oid.Value ==
"1.3.6.1.5.5.7.3.1"
)
505
{
506
return
true
;
507
}
508
}
509
}
510
}
511
512
return
certificate.Subject.Contains(
"CN="
, StringComparison.OrdinalIgnoreCase);
513
}
514
539
private
static
CertificateStatusInfo
SelectBestCertificate
(
540
IReadOnlyCollection<CertificateStatusInfo> candidates)
541
{
542
DateTimeOffset now = DateTimeOffset.Now;
543
544
CertificateStatusInfo
? validCertificate = candidates
545
.Where(item => item.NotAfter is not
null
&& item.NotAfter.Value > now)
546
.OrderBy(item =>
GetSelectedFilePriority
(item.CertificatePath))
547
.ThenBy(item => item.NotAfter)
548
.FirstOrDefault();
549
550
if
(validCertificate is not
null
)
551
{
552
return
validCertificate;
553
}
554
555
return
candidates
556
.OrderByDescending(item => item.NotAfter ?? DateTimeOffset.MinValue)
557
.First();
558
}
559
584
private
static
int
GetSelectedFilePriority
(
string
? path)
585
{
586
if
(
string
.IsNullOrWhiteSpace(path))
587
{
588
return
int
.MaxValue;
589
}
590
591
string
fileName = Path.GetFileName(path).ToLowerInvariant();
592
593
if
(fileName.Contains(
"-crt.pem"
, StringComparison.OrdinalIgnoreCase))
594
{
595
return
0;
596
}
597
598
if
(fileName.Contains(
"fullchain"
, StringComparison.OrdinalIgnoreCase))
599
{
600
return
1;
601
}
602
603
if
(fileName.EndsWith(
".crt"
, StringComparison.OrdinalIgnoreCase))
604
{
605
return
2;
606
}
607
608
if
(fileName.EndsWith(
".cer"
, StringComparison.OrdinalIgnoreCase))
609
{
610
return
3;
611
}
612
613
if
(fileName.EndsWith(
".pfx"
, StringComparison.OrdinalIgnoreCase) ||
614
fileName.EndsWith(
".p12"
, StringComparison.OrdinalIgnoreCase))
615
{
616
return
4;
617
}
618
619
return
10;
620
}
621
654
private
static
CertificateHealthState
ResolveState
(
655
int
remainingDays,
656
CertificateMonitorOptions
options)
657
{
658
if
(remainingDays < 0)
659
{
660
return
CertificateHealthState
.Expired;
661
}
662
663
if
(remainingDays < options.
CriticalDays
)
664
{
665
return
CertificateHealthState
.Critical;
666
}
667
668
if
(remainingDays < options.
WarningDays
)
669
{
670
return
CertificateHealthState
.Warning;
671
}
672
673
return
CertificateHealthState
.Ok;
674
}
675
716
private
static
string
BuildMessage
(
717
CertificateHealthState
state,
718
int
remainingDays,
719
string
certificatePath)
720
{
721
string
fileName = Path.GetFileName(certificatePath);
722
723
return
state
switch
724
{
725
CertificateHealthState.Ok
=>
726
$
"Certificate is valid. Remaining days: {remainingDays}. File: {fileName}"
,
727
728
CertificateHealthState.Warning
=>
729
$
"Certificate renewal should be checked. Remaining days: {remainingDays}. File: {fileName}"
,
730
731
CertificateHealthState.Critical
=>
732
$
"Certificate expires soon. Remaining days: {remainingDays}. File: {fileName}"
,
733
734
CertificateHealthState.Expired
=>
735
$
"Certificate expired. Expired days: {Math.Abs(remainingDays)}. File: {fileName}"
,
736
737
_ =>
738
$
"Certificate state is unknown. Remaining days: {remainingDays}. File: {fileName}"
739
};
740
}
741
774
private
static
CertificateStatusInfo
Failure
(
string
? directory,
string
message)
775
{
776
return
new
CertificateStatusInfo
777
{
778
IsSuccess =
false
,
779
CertificateDirectory = directory ??
string
.Empty,
780
State =
CertificateHealthState
.Error,
781
Message = message
782
};
783
}
784
}
Codemaru.Models.Certificates
Definition
CertificateHealthState.cs:1
Codemaru.Models.Certificates.CertificateHealthState
CertificateHealthState
Definition
CertificateHealthState.cs:12
Codemaru.Models.Certificates.CertificateHealthState.Warning
@ Warning
Definition
CertificateHealthState.cs:41
Codemaru.Models.Certificates.CertificateHealthState.Expired
@ Expired
Definition
CertificateHealthState.cs:61
Codemaru.Models.Certificates.CertificateHealthState.Critical
@ Critical
Definition
CertificateHealthState.cs:51
Codemaru.Models.Certificates.CertificateHealthState.Ok
@ Ok
Definition
CertificateHealthState.cs:31
Codemaru.Options
Definition
CertificateMonitorOptions.cs:1
Codemaru.Services.Certificates
Definition
CertificateSettingsWriter.cs:6
Codemaru.Models.Certificates.CertificateStatusInfo
Definition
CertificateStatusInfo.cs:12
Codemaru.Options.CertificateMonitorOptions
Definition
CertificateMonitorOptions.cs:12
Codemaru.Options.CertificateMonitorOptions.CertificateDirectory
string CertificateDirectory
Definition
CertificateMonitorOptions.cs:21
Codemaru.Options.CertificateMonitorOptions.PfxPassword
string? PfxPassword
Definition
CertificateMonitorOptions.cs:41
Codemaru.Options.CertificateMonitorOptions.CriticalDays
int CriticalDays
Definition
CertificateMonitorOptions.cs:101
Codemaru.Options.CertificateMonitorOptions.WarningDays
int WarningDays
Definition
CertificateMonitorOptions.cs:91
Codemaru.Services.Certificates.ICertificateMonitorService
Definition
ICertificateMonitorService.cs:15
Codemaru.Services.Certificates.X509CertificateMonitorService
Definition
X509CertificateMonitorService.cs:21
Codemaru.Services.Certificates.X509CertificateMonitorService.GetCertificatePriority
static int GetCertificatePriority(string path)
Definition
X509CertificateMonitorService.cs:237
Codemaru.Services.Certificates.X509CertificateMonitorService.GetStatusAsync
Task< CertificateStatusInfo > GetStatusAsync(CertificateMonitorOptions options, CancellationToken cancellationToken)
Definition
X509CertificateMonitorService.cs:54
Codemaru.Services.Certificates.X509CertificateMonitorService.BuildMessage
static string BuildMessage(CertificateHealthState state, int remainingDays, string certificatePath)
Definition
X509CertificateMonitorService.cs:716
Codemaru.Services.Certificates.X509CertificateMonitorService.SelectBestCertificate
static CertificateStatusInfo SelectBestCertificate(IReadOnlyCollection< CertificateStatusInfo > candidates)
Definition
X509CertificateMonitorService.cs:539
Codemaru.Services.Certificates.X509CertificateMonitorService.IsCertificateCandidate
static bool IsCertificateCandidate(string path)
Definition
X509CertificateMonitorService.cs:190
Codemaru.Services.Certificates.X509CertificateMonitorService.FindCertificateFiles
static IEnumerable< string > FindCertificateFiles(CertificateMonitorOptions options)
Definition
X509CertificateMonitorService.cs:147
Codemaru.Services.Certificates.X509CertificateMonitorService.ResolveState
static CertificateHealthState ResolveState(int remainingDays, CertificateMonitorOptions options)
Definition
X509CertificateMonitorService.cs:654
Codemaru.Services.Certificates.X509CertificateMonitorService.LoadCertificate
static X509Certificate2 LoadCertificate(string path, CertificateMonitorOptions options)
Definition
X509CertificateMonitorService.cs:388
Codemaru.Services.Certificates.X509CertificateMonitorService.Failure
static CertificateStatusInfo Failure(string? directory, string message)
Definition
X509CertificateMonitorService.cs:774
Codemaru.Services.Certificates.X509CertificateMonitorService.GetSelectedFilePriority
static int GetSelectedFilePriority(string? path)
Definition
X509CertificateMonitorService.cs:584
Codemaru.Services.Certificates.X509CertificateMonitorService.TryReadCertificate
static ? CertificateStatusInfo TryReadCertificate(string path, CertificateMonitorOptions options, List< string > readErrors)
Definition
X509CertificateMonitorService.cs:315
Codemaru.Services.Certificates.X509CertificateMonitorService.IsServerCertificate
static bool IsServerCertificate(X509Certificate2 certificate)
Definition
X509CertificateMonitorService.cs:496
Codemaru.Services.Certificates.X509CertificateMonitorService.LoadCertificateFromPem
static X509Certificate2 LoadCertificateFromPem(string path)
Definition
X509CertificateMonitorService.cs:441
Services
Certificates
X509CertificateMonitorService.cs
다음에 의해 생성됨 :
1.17.0