Skip to content

Commit ec9a785

Browse files
committed
Revoked cert sync fixed
1 parent 4eba434 commit ec9a785

1 file changed

Lines changed: 123 additions & 90 deletions

File tree

aws-pca-caplugin/AWSPCACAPlugin.cs

Lines changed: 123 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -87,10 +87,12 @@ public async Task<AnyCAPluginCertificate> GetSingleRecord(string caRequestID)
8787
throw;
8888
}
8989
}
90-
91-
//done
92-
public async Task Synchronize(BlockingCollection<AnyCAPluginCertificate> blockingBuffer, DateTime? lastSync,
93-
bool fullSync, CancellationToken cancelToken)
90+
// done
91+
public async Task Synchronize(
92+
BlockingCollection<AnyCAPluginCertificate> blockingBuffer,
93+
DateTime? lastSync,
94+
bool fullSync,
95+
CancellationToken cancelToken)
9496
{
9597
Logger.MethodEntry();
9698
Logger.LogTrace(
@@ -114,6 +116,7 @@ public async Task Synchronize(BlockingCollection<AnyCAPluginCertificate> blockin
114116
var caRequestId = SafeRequestIdFromArn(audit.certificateArn);
115117

116118
if (string.IsNullOrWhiteSpace(caRequestId) && !string.IsNullOrWhiteSpace(audit.certificateSerial))
119+
{
117120
try
118121
{
119122
caRequestId = await _certificateDataReader
@@ -125,6 +128,7 @@ public async Task Synchronize(BlockingCollection<AnyCAPluginCertificate> blockin
125128
Logger.LogTrace(
126129
$"Could not map serial to requestId. serial={audit.certificateSerial}. {ex.Message}");
127130
}
131+
}
128132

129133
if (string.IsNullOrWhiteSpace(caRequestId))
130134
{
@@ -160,7 +164,8 @@ public async Task Synchronize(BlockingCollection<AnyCAPluginCertificate> blockin
160164
{
161165
var exp = _certificateDataReader.GetExpirationDateByRequestId(caRequestId);
162166
if (exp.HasValue && exp.Value.ToUniversalTime() <= DateTime.UtcNow &&
163-
newStatus == (int)EndEntityStatus.GENERATED) newStatus = (int)EndEntityStatus.HISTORICAL;
167+
newStatus == (int)EndEntityStatus.GENERATED)
168+
newStatus = (int)EndEntityStatus.HISTORICAL;
164169
}
165170
catch (Exception ex)
166171
{
@@ -184,6 +189,7 @@ public async Task Synchronize(BlockingCollection<AnyCAPluginCertificate> blockin
184189
}
185190

186191
if (exists)
192+
{
187193
try
188194
{
189195
var oldStatus = await _certificateDataReader.GetStatusByRequestID(caRequestId)
@@ -200,65 +206,92 @@ public async Task Synchronize(BlockingCollection<AnyCAPluginCertificate> blockin
200206
Logger.LogWarning(
201207
$"GetStatusByRequestID failed for {caRequestId}: {ex.Message}. Proceeding to emit.");
202208
}
209+
}
203210
}
204211

205-
// If we only need to update status (revoked/historical) and you don't want to fetch the cert again:
206-
// - For revoked/historical, Keyfactor usually still accepts just status update, but depending on your pipeline,
207-
// you may want to always include Certificate for GENERATED.
208-
// Here: fetch cert for GENERATED/HISTORICAL; skip fetch for REVOKED if ARN missing.
209-
string? certB64 = null;
212+
// Fetch certificate material when needed:
213+
// - GENERATED/HISTORICAL: must fetch (otherwise Keyfactor can't ingest new cert)
214+
// - REVOKED: best-effort fetch if ARN exists (so revoked updates can include PEM); otherwise emit status-only
215+
string? certPayload = null;
210216
string? productId = null;
211217

212-
if (newStatus == (int)EndEntityStatus.GENERATED || newStatus == (int)EndEntityStatus.HISTORICAL)
213-
{
214-
if (string.IsNullOrWhiteSpace(audit.certificateArn))
215-
{
216-
Logger.LogTrace($"Skipping {caRequestId}: no certificateArn to retrieve certificate.");
217-
continue;
218-
}
218+
bool mustFetchForIngest =
219+
newStatus == (int)EndEntityStatus.GENERATED ||
220+
newStatus == (int)EndEntityStatus.HISTORICAL;
219221

220-
var certResp = await AwsClient.SubmitGetCertificateByArnAsync(audit.certificateArn, cancelToken)
221-
.ConfigureAwait(false);
222+
bool bestEffortFetch =
223+
newStatus == (int)EndEntityStatus.REVOKED;
222224

223-
if (certResp?.Status == (int)EndEntityStatus.INPROCESS)
225+
bool shouldAttemptFetch = mustFetchForIngest || bestEffortFetch;
226+
227+
if (shouldAttemptFetch)
228+
{
229+
if (string.IsNullOrWhiteSpace(audit.certificateArn))
224230
{
225-
// Emit in-process update without cert payload
226-
blockingBuffer.Add(new AnyCAPluginCertificate
231+
if (mustFetchForIngest)
227232
{
228-
CARequestID = caRequestId,
229-
Status = (int)EndEntityStatus.INPROCESS,
230-
ProductID = certResp.CertificateType
231-
}, cancelToken);
233+
Logger.LogTrace($"Skipping {caRequestId}: no certificateArn to retrieve certificate.");
234+
continue; // can't ingest a new/active cert without payload
235+
}
232236

233-
continue;
237+
// REVOKED (best-effort): status-only update
238+
Logger.LogTrace($"Revoked status-only for {caRequestId}: no certificateArn present.");
234239
}
235-
236-
if (certResp?.RegistrationError != null)
240+
else
237241
{
238-
Logger.LogTrace(
239-
$"Skipping {caRequestId}: GetCertificate error: {certResp.RegistrationError.Description}");
240-
continue;
241-
}
242+
var certResp = await AwsClient
243+
.SubmitGetCertificateByArnAsync(audit.certificateArn, cancelToken)
244+
.ConfigureAwait(false);
242245

243-
certB64 = certResp.Certificate;
244-
productId = certResp.CertificateType;
246+
if (certResp?.Status == (int)EndEntityStatus.INPROCESS)
247+
{
248+
// Emit in-process update without cert payload
249+
blockingBuffer.Add(new AnyCAPluginCertificate
250+
{
251+
CARequestID = caRequestId,
252+
Status = (int)EndEntityStatus.INPROCESS,
253+
ProductID = certResp.CertificateType
254+
}, cancelToken);
245255

256+
continue;
257+
}
246258

247-
if (string.IsNullOrWhiteSpace(certB64))
248-
{
249-
Logger.LogTrace($"Skipping {caRequestId}: unable to obtain end-entity certificate payload.");
250-
continue;
259+
if (certResp?.RegistrationError != null)
260+
{
261+
if (mustFetchForIngest)
262+
{
263+
Logger.LogTrace(
264+
$"Skipping {caRequestId}: GetCertificate error: {certResp.RegistrationError.Description}");
265+
continue;
266+
}
267+
268+
// REVOKED best-effort: proceed status-only
269+
Logger.LogTrace(
270+
$"Revoked status-only for {caRequestId}: GetCertificate error: {certResp.RegistrationError.Description}");
271+
}
272+
else
273+
{
274+
certPayload = certResp?.Certificate;
275+
productId = certResp?.CertificateType;
276+
277+
if (mustFetchForIngest && string.IsNullOrWhiteSpace(certPayload))
278+
{
279+
Logger.LogTrace($"Skipping {caRequestId}: unable to obtain end-entity certificate payload.");
280+
continue;
281+
}
282+
}
251283
}
252284
}
253285

254286
var finalsubmit = new AnyCAPluginCertificate
255287
{
256288
CARequestID = caRequestId,
257-
Certificate =
258-
GetEndEntityCertificate(certB64), // null is OK for REVOKED updates if your pipeline accepts it
289+
// For REVOKED: this will now be populated when ARN exists + GetCertificate succeeds; otherwise null (status-only).
290+
Certificate = GetEndEntityCertificate(certPayload),
259291
Status = newStatus,
260292
ProductID = productId
261293
};
294+
262295
// Emit to buffer as AnyGateway expects.
263296
blockingBuffer.Add(finalsubmit, cancelToken);
264297
}
@@ -382,60 +415,60 @@ public async Task<EnrollmentResult> Enroll(
382415
switch (enrollmentType)
383416
{
384417
case EnrollmentType.New:
385-
{
386-
return await IssueAndFetchAsync(
387-
csr,
388-
productInfo.ProductID,
389-
days,
390-
signingAlgorithm,
391-
"Certificate Issued")
392-
.ConfigureAwait(false);
393-
}
394-
395-
case EnrollmentType.RenewOrReissue:
396-
{
397-
if (productInfo.ProductParameters == null ||
398-
!TryGetProductParam(productInfo.ProductParameters, "PriorCertSN", out var priorSn) ||
399-
string.IsNullOrWhiteSpace(priorSn))
400-
return new EnrollmentResult
401-
{
402-
Status = (int)EndEntityStatus.FAILED,
403-
StatusMessage =
404-
"Renew/Reissue requires ProductParameters['PriorCertSN'] (hex serial number)."
405-
};
406-
407-
string priorRequestId;
408-
try
409418
{
410-
priorRequestId = await _certificateDataReader
411-
.GetRequestIDBySerialNumber(priorSn)
419+
return await IssueAndFetchAsync(
420+
csr,
421+
productInfo.ProductID,
422+
days,
423+
signingAlgorithm,
424+
"Certificate Issued")
412425
.ConfigureAwait(false);
413426
}
414-
catch (Exception ex)
427+
428+
case EnrollmentType.RenewOrReissue:
415429
{
416-
return new EnrollmentResult
430+
if (productInfo.ProductParameters == null ||
431+
!TryGetProductParam(productInfo.ProductParameters, "PriorCertSN", out var priorSn) ||
432+
string.IsNullOrWhiteSpace(priorSn))
433+
return new EnrollmentResult
434+
{
435+
Status = (int)EndEntityStatus.FAILED,
436+
StatusMessage =
437+
"Renew/Reissue requires ProductParameters['PriorCertSN'] (hex serial number)."
438+
};
439+
440+
string priorRequestId;
441+
try
417442
{
418-
Status = (int)EndEntityStatus.FAILED,
419-
StatusMessage = $"Could not resolve PriorCertSN to request id: {ex.Message}"
420-
};
421-
}
443+
priorRequestId = await _certificateDataReader
444+
.GetRequestIDBySerialNumber(priorSn)
445+
.ConfigureAwait(false);
446+
}
447+
catch (Exception ex)
448+
{
449+
return new EnrollmentResult
450+
{
451+
Status = (int)EndEntityStatus.FAILED,
452+
StatusMessage = $"Could not resolve PriorCertSN to request id: {ex.Message}"
453+
};
454+
}
422455

423-
var expiration = _certificateDataReader.GetExpirationDateByRequestId(priorRequestId);
424-
var isRenewal = expiration.HasValue && expiration.Value.ToUniversalTime() <= DateTime.UtcNow;
425-
426-
var msg = isRenewal ? "Certificate Renewed" : "Certificate Reissued";
427-
var token = BuildIdempotencyToken(isRenewal ? "renew" : "reissue", priorRequestId, csr);
428-
429-
// Still "IssueCertificate" under the hood; PCA doesn't have first-class renew/reissue.
430-
return await IssueAndFetchAsync(
431-
csr,
432-
productInfo.ProductID,
433-
days,
434-
msg,
435-
// Optional: stable-ish idempotency (helps avoid duplicates if caller retries quickly)
436-
token)
437-
.ConfigureAwait(false);
438-
}
456+
var expiration = _certificateDataReader.GetExpirationDateByRequestId(priorRequestId);
457+
var isRenewal = expiration.HasValue && expiration.Value.ToUniversalTime() <= DateTime.UtcNow;
458+
459+
var msg = isRenewal ? "Certificate Renewed" : "Certificate Reissued";
460+
var token = BuildIdempotencyToken(isRenewal ? "renew" : "reissue", priorRequestId, csr);
461+
462+
// Still "IssueCertificate" under the hood; PCA doesn't have first-class renew/reissue.
463+
return await IssueAndFetchAsync(
464+
csr,
465+
productInfo.ProductID,
466+
days,
467+
msg,
468+
// Optional: stable-ish idempotency (helps avoid duplicates if caller retries quickly)
469+
token)
470+
.ConfigureAwait(false);
471+
}
439472

440473
default:
441474
return new EnrollmentResult
@@ -652,7 +685,7 @@ public Dictionary<string, PropertyConfigInfo> GetCAConnectorAnnotations()
652685
DefaultValue = "",
653686
Type = "String"
654687
},
655-
[Constants.Enabled] = new ()
688+
[Constants.Enabled] = new()
656689
{
657690
Comments = "Flag to Enable or Disable gateway functionality. Disabling is primarily used to allow creation of the CA prior to configuration information being available.",
658691
Hidden = false,

0 commit comments

Comments
 (0)