77using System . Linq ;
88using System . Threading ;
99using System . Threading . Tasks ;
10+ using Microsoft . Extensions . Logging ;
1011using NuGet . Jobs . Validation . PackageSigning . Storage ;
1112using NuGet . Packaging . Signing ;
1213using NuGet . Services . Validation ;
@@ -17,16 +18,19 @@ public class SignaturePartsExtractor : ISignaturePartsExtractor
1718 {
1819 private readonly ICertificateStore _certificateStore ;
1920 private readonly IValidationEntitiesContext _entitiesContext ;
21+ private readonly ILogger < SignaturePartsExtractor > _logger ;
2022
2123 public SignaturePartsExtractor (
2224 ICertificateStore certificateStore ,
23- IValidationEntitiesContext entitiesContext )
25+ IValidationEntitiesContext entitiesContext ,
26+ ILogger < SignaturePartsExtractor > logger )
2427 {
2528 _certificateStore = certificateStore ?? throw new ArgumentNullException ( nameof ( certificateStore ) ) ;
2629 _entitiesContext = entitiesContext ?? throw new ArgumentNullException ( nameof ( entitiesContext ) ) ;
30+ _logger = logger ?? throw new ArgumentNullException ( nameof ( logger ) ) ;
2731 }
2832
29- public async Task ExtractAsync ( ISignedPackageReader signedPackageReader , CancellationToken token )
33+ public async Task ExtractAsync ( int packageKey , ISignedPackageReader signedPackageReader , CancellationToken token )
3034 {
3135 if ( ! await signedPackageReader . IsSignedAsync ( token ) )
3236 {
@@ -39,11 +43,14 @@ public async Task ExtractAsync(ISignedPackageReader signedPackageReader, Cancell
3943 // Extract the certificates found in the package signatures.
4044 var extractedCertificates = ExtractCertificates ( signature ) ;
4145
46+ // Prepare signature entities for the database (does not commit).
47+ await SaveSignatureToDatabaseAsync ( packageKey , signature , extractedCertificates ) ;
48+
4249 // Save the certificates to blob storage.
4350 await SaveCertificatesToStoreAsync ( extractedCertificates , token ) ;
4451
45- // Save the certificates to the database.
46- await SaveCertificatesToDatabaseAsync ( extractedCertificates ) ;
52+ // Commit the database changes .
53+ await _entitiesContext . SaveChangesAsync ( ) ;
4754 }
4855
4956 private ExtractedCertificates ExtractCertificates ( Signature signature )
@@ -90,14 +97,14 @@ private ExtractedCertificates ExtractCertificates(Signature signature)
9097 timestampParentCertificates ) ;
9198 }
9299
93- private async Task SaveCertificatesToDatabaseAsync ( ExtractedCertificates extractedCertificates )
100+ private async Task SaveSignatureToDatabaseAsync ( int packageKey , Signature signature , ExtractedCertificates extractedCertificates )
94101 {
95102 // Initialize the end and parent certificates.
96103 var thumbprintToEndCertificate = await InitializeEndCertificatesAsync (
97104 new [ ]
98105 {
99- extractedCertificates . SignatureEndCertificate ,
100- extractedCertificates . TimestampEndCertificate
106+ new CertificateAndUse ( extractedCertificates . SignatureEndCertificate , EndCertificateUse . CodeSigning ) ,
107+ new CertificateAndUse ( extractedCertificates . TimestampEndCertificate , EndCertificateUse . Timestamping ) ,
101108 } ) ;
102109
103110 var thumbprintToParentCertificate = await InitializeParentCertificatesAsync (
@@ -118,8 +125,140 @@ private async Task SaveCertificatesToDatabaseAsync(ExtractedCertificates extract
118125 thumbprintToEndCertificate ,
119126 thumbprintToParentCertificate ) ;
120127
121- // Commit
122- await _entitiesContext . SaveChangesAsync ( ) ;
128+ // Initialize the package signature record.
129+ var packageSignature = await InitializePackageSignatureAsync (
130+ packageKey ,
131+ extractedCertificates . SignatureEndCertificate ,
132+ thumbprintToEndCertificate ) ;
133+
134+ // Initialize the trusted timestamp record.
135+ InitializeTrustedTimestamp (
136+ packageSignature ,
137+ signature ,
138+ extractedCertificates . TimestampEndCertificate ,
139+ thumbprintToEndCertificate ) ;
140+ }
141+
142+ public async Task < PackageSignature > InitializePackageSignatureAsync (
143+ int packageKey ,
144+ HashedCertificate signatureEndCertificate ,
145+ IReadOnlyDictionary < string , EndCertificate > thumbprintToEndCertificate )
146+ {
147+ var packageSignatures = await _entitiesContext
148+ . PackageSignatures
149+ . Include ( x => x . TrustedTimestamps )
150+ . Include ( x => x . EndCertificate )
151+ . Where ( x => x . PackageKey == packageKey )
152+ . ToListAsync ( ) ;
153+
154+ if ( packageSignatures . Count > 1 )
155+ {
156+ _logger . LogError (
157+ "There are {Count} package signatures for package key {PackageKey}. There should be either zero or one." ,
158+ packageSignatures . Count ,
159+ packageKey ) ;
160+
161+ throw new InvalidOperationException ( "There should never be more than one package signature per package." ) ;
162+ }
163+
164+ PackageSignature packageSignature ;
165+ if ( packageSignatures . Count == 0 )
166+ {
167+ packageSignature = new PackageSignature
168+ {
169+ CreatedAt = DateTime . UtcNow ,
170+ EndCertificate = thumbprintToEndCertificate [ signatureEndCertificate . Thumbprint ] ,
171+ PackageKey = packageKey ,
172+ Status = PackageSignatureStatus . Unknown ,
173+ TrustedTimestamps = new List < TrustedTimestamp > ( ) ,
174+ } ;
175+ _entitiesContext . PackageSignatures . Add ( packageSignature ) ;
176+
177+ packageSignature . EndCertificateKey = packageSignature . EndCertificate . Key ;
178+ }
179+ else
180+ {
181+ packageSignature = packageSignatures . Single ( ) ;
182+
183+ if ( packageSignature . EndCertificate . Thumbprint != signatureEndCertificate . Thumbprint )
184+ {
185+ _logger . LogError (
186+ "The signature end certificate thumbprint cannot change for package {PackageKey}. The " +
187+ "existing signature end certificate is {ExistingThumbprint}. The new thumprint is " +
188+ "{NewThumbprint}." ,
189+ packageKey ,
190+ packageSignature . EndCertificate . Thumbprint ,
191+ signatureEndCertificate . Thumbprint ) ;
192+
193+ throw new InvalidOperationException ( "The thumbprint of the signature end certificate cannot change." ) ;
194+ }
195+ }
196+
197+ return packageSignature ;
198+ }
199+
200+ private void InitializeTrustedTimestamp (
201+ PackageSignature packageSignature ,
202+ Signature signature ,
203+ HashedCertificate timestampEndCertificate ,
204+ IReadOnlyDictionary < string , EndCertificate > thumbprintToEndCertificate )
205+ {
206+ if ( packageSignature . TrustedTimestamps . Count > 1 )
207+ {
208+ _logger . LogError (
209+ "There are {Count} trusted timestamps for signature on package {PackageKey}. There should be either zero or one." ,
210+ packageSignature . TrustedTimestamps . Count ,
211+ packageSignature . PackageKey ) ;
212+
213+ throw new InvalidOperationException ( "There should never be more than one trusted timestamp per package signature." ) ;
214+ }
215+
216+ // Determine the value of the timestamp.
217+ var value = signature . Timestamps . Single ( ) . UpperLimit . UtcDateTime ;
218+
219+ TrustedTimestamp trustedTimestamp ;
220+ if ( packageSignature . TrustedTimestamps . Count == 0 )
221+ {
222+ trustedTimestamp = new TrustedTimestamp
223+ {
224+ PackageSignature = packageSignature ,
225+ PackageSignatureKey = packageSignature . Key ,
226+ EndCertificate = thumbprintToEndCertificate [ timestampEndCertificate . Thumbprint ] ,
227+ Value = value ,
228+ } ;
229+ trustedTimestamp . EndCertificateKey = trustedTimestamp . EndCertificate . Key ;
230+ packageSignature . TrustedTimestamps . Add ( trustedTimestamp ) ;
231+ _entitiesContext . TrustedTimestamps . Add ( trustedTimestamp ) ;
232+ }
233+ else
234+ {
235+ trustedTimestamp = packageSignature . TrustedTimestamps . Single ( ) ;
236+
237+ if ( trustedTimestamp . EndCertificate . Thumbprint != timestampEndCertificate . Thumbprint )
238+ {
239+ _logger . LogError (
240+ "The timestamp end certificate thumbprint cannot change for package {PackageKey}. The " +
241+ "existing timestamp end certificate is {ExistingThumbprint}. The new thumprint is " +
242+ "{NewThumbprint}." ,
243+ packageSignature . PackageKey ,
244+ packageSignature . EndCertificate . Thumbprint ,
245+ timestampEndCertificate . Thumbprint ) ;
246+
247+ throw new InvalidOperationException ( "The thumbprint of the timestamp end certificate cannot change." ) ;
248+ }
249+
250+ if ( trustedTimestamp . Value != value )
251+ {
252+ _logger . LogError (
253+ "The trusted timestamp value cannot change for package {PackageKey}. The existing timestamp " +
254+ "value is {ExistingValue}. The new value is {NewValue}." ,
255+ packageSignature . PackageKey ,
256+ trustedTimestamp . Value ,
257+ value ) ;
258+
259+ throw new InvalidOperationException ( "The value of the trusted timestamp cannot change." ) ;
260+ }
261+ }
123262 }
124263
125264 private void ConnectCertificates (
@@ -160,10 +299,10 @@ private void ConnectCertificates(
160299 }
161300
162301 private async Task < IReadOnlyDictionary < string , EndCertificate > > InitializeEndCertificatesAsync (
163- IEnumerable < HashedCertificate > certificates )
302+ IEnumerable < CertificateAndUse > certificatesAndUses )
164303 {
165- var thumbprints = certificates
166- . Select ( x => x . Thumbprint )
304+ var thumbprints = certificatesAndUses
305+ . Select ( x => x . Certificate . Thumbprint )
167306 . Distinct ( )
168307 . ToList ( ) ;
169308
@@ -177,19 +316,30 @@ private async Task<IReadOnlyDictionary<string, EndCertificate>> InitializeEndCer
177316
178317 var thumbprintToEntity = existingEntities . ToDictionary ( x => x . Thumbprint ) ;
179318
180- foreach ( var certificate in certificates )
319+ foreach ( var certificateAndUse in certificatesAndUses )
181320 {
182- if ( ! thumbprintToEntity . TryGetValue ( certificate . Thumbprint , out var entity ) )
321+ if ( ! thumbprintToEntity . TryGetValue ( certificateAndUse . Certificate . Thumbprint , out var entity ) )
183322 {
184323 entity = new EndCertificate
185324 {
186325 Status = EndCertificateStatus . Unknown ,
187- Thumbprint = certificate . Thumbprint ,
326+ Use = certificateAndUse . Use ,
327+ Thumbprint = certificateAndUse . Certificate . Thumbprint ,
188328 CertificateChainLinks = new List < CertificateChainLink > ( ) ,
189329 } ;
190330 _entitiesContext . EndCertificates . Add ( entity ) ;
191331
192- thumbprintToEntity [ certificate . Thumbprint ] = entity ;
332+ thumbprintToEntity [ certificateAndUse . Certificate . Thumbprint ] = entity ;
333+ }
334+ else if ( entity . Use != certificateAndUse . Use )
335+ {
336+ _logger . LogError (
337+ "The use of end certificate {Thumbprint} cannot change. The existing use is {ExistingUse}. The new use is {NewUse}." ,
338+ certificateAndUse . Certificate . Thumbprint ,
339+ entity . Use ,
340+ certificateAndUse . Use ) ;
341+
342+ throw new InvalidOperationException ( "The use of an end certificate cannot change." ) ;
193343 }
194344 }
195345
@@ -256,5 +406,17 @@ private async Task SaveCertificateToStoreAsync(HashedCertificate certificate, Ca
256406
257407 await _certificateStore . SaveAsync ( certificate . Certificate , token ) ;
258408 }
409+
410+ private class CertificateAndUse
411+ {
412+ public CertificateAndUse ( HashedCertificate hashedCertificate , EndCertificateUse endCertificateUse )
413+ {
414+ Certificate = hashedCertificate ;
415+ Use = endCertificateUse ;
416+ }
417+
418+ public HashedCertificate Certificate { get ; }
419+ public EndCertificateUse Use { get ; }
420+ }
259421 }
260422}
0 commit comments