2222using Org . BouncyCastle . Asn1 . Pkcs ;
2323using Org . BouncyCastle . Asn1 . X509 ;
2424using Org . BouncyCastle . Pkcs ;
25+ using System . Security . Cryptography ;
2526
2627namespace Keyfactor . Extensions . CAPlugin . Acme
2728{
@@ -265,6 +266,9 @@ public async Task<EnrollmentResult> Enroll(
265266 _logger . LogInformation ( "Order created. OrderUrl: {OrderUrl}, Status: {Status}" ,
266267 order . OrderUrl , order . Payload ? . Status ) ;
267268
269+ // Extract order identifier BEFORE finalization to ensure we use the original order URL
270+ var orderIdentifier = ExtractOrderIdentifier ( order . OrderUrl ) ;
271+
268272 // Store pending order immediately
269273 var accountId = accountDetails . Kid . Split ( '/' ) . Last ( ) ;
270274
@@ -274,9 +278,6 @@ public async Task<EnrollmentResult> Enroll(
274278 // Finalize with original CSR bytes
275279 order = await acmeClient . FinalizeOrderAsync ( order , csrBytes ) ;
276280
277- // Extract order identifier (path only) for database storage
278- var orderIdentifier = ExtractOrderIdentifier ( order . OrderUrl ) ;
279-
280281 // If order is valid immediately, download cert
281282 if ( order . Payload ? . Status == "valid" && ! string . IsNullOrEmpty ( order . Payload . Certificate ) )
282283 {
@@ -325,30 +326,25 @@ public async Task<EnrollmentResult> Enroll(
325326
326327
327328 /// <summary>
328- /// Extracts the order path from the full ACME order URL for use as a unique identifier.
329- /// This removes the scheme, host, and port, keeping only the path portion.
329+ /// Generates a fixed-length SHA256 hash of the ACME order URL for database storage.
330+ /// Produces a consistent 40-char hex string regardless of URL length or ACME CA format.
331+ /// The full order URL is logged separately during enrollment for traceability.
330332 /// </summary>
331- /// <param name="orderUrl">Full order URL (e.g., https://dv.acme-v02.api.pki.goog/order/ABC123)</param>
332- /// <returns>Order path without leading slash (e.g., "order/ABC123")</returns>
333- /// <example>
334- /// Input: "https://dv.acme-v02.api.pki.goog/order/IlYl06mPl5VcAQpx3pzR6w"
335- /// Output: "order/IlYl06mPl5VcAQpx3pzR6w"
336- /// </example>
337333 private static string ExtractOrderIdentifier ( string orderUrl )
338334 {
339335 if ( string . IsNullOrWhiteSpace ( orderUrl ) )
340336 return orderUrl ;
341337
342- try
338+ using ( var sha256 = SHA256 . Create ( ) )
343339 {
344- var uri = new Uri ( orderUrl ) ;
345- // Remove leading slash and return the path
346- return uri . AbsolutePath . TrimStart ( '/' ) ;
347- }
348- catch ( Exception )
349- {
350- // If URL parsing fails, return the original (shouldn't happen with valid ACME URLs)
351- return orderUrl ;
340+ var hashBytes = sha256 . ComputeHash ( Encoding . UTF8 . GetBytes ( orderUrl ) ) ;
341+ // Take first 20 bytes (40 hex chars) — fits in DB column and is collision-safe
342+ var sb = new StringBuilder ( 40 ) ;
343+ for ( int i = 0 ; i < 20 ; i ++ )
344+ {
345+ sb . Append ( hashBytes [ i ] . ToString ( "x2" ) ) ;
346+ }
347+ return sb . ToString ( ) ;
352348 }
353349 }
354350
0 commit comments