/
AttributeUtility.cs
397 lines (339 loc) · 16.4 KB
/
AttributeUtility.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Security.Cryptography;
#if IS_SIGNING_SUPPORTED
using System.Security.Cryptography.Pkcs;
#endif
using System.Security.Cryptography.X509Certificates;
namespace NuGet.Packaging.Signing
{
public static class AttributeUtility
{
#if IS_SIGNING_SUPPORTED
/// <summary>
/// Create a CommitmentTypeIndication attribute.
/// https://tools.ietf.org/html/rfc5126.html#section-5.11.1
/// </summary>
/// <param name="type">The signature type.</param>
public static CryptographicAttributeObject CreateCommitmentTypeIndication(SignatureType type)
{
// SignatureType -> Oid
var oid = GetSignatureTypeOid(type);
var commitmentTypeIndication = CommitmentTypeIndication.Create(new Oid(oid));
var value = new AsnEncodedData(Oids.CommitmentTypeIndication, commitmentTypeIndication.Encode());
return new CryptographicAttributeObject(
new Oid(Oids.CommitmentTypeIndication),
new AsnEncodedDataCollection(value));
}
/// <summary>
/// Gets the signature type from one or more commitment-type-indication attributes.
/// </summary>
/// <param name="signedAttributes">A <see cref="SignerInfo" /> signed attributes collection.</param>
/// <remarks>Unknown OIDs are ignored.</remarks>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="signedAttributes" /> is <c>null</c>.</exception>
/// <exception cref="SignatureException">Thrown if one or more attributes are invalid.</exception>
public static SignatureType GetSignatureType(CryptographicAttributeObjectCollection signedAttributes)
{
if (signedAttributes == null)
{
throw new ArgumentNullException(nameof(signedAttributes));
}
var values = signedAttributes.GetAttributes(Oids.CommitmentTypeIndication)
.SelectMany(attribute => GetCommitmentTypeIndicationRawValues(attribute))
.Distinct();
// Remove unknown values, these could be future values.
var knownValues = values.Where(e => e != SignatureType.Unknown).ToList();
if (knownValues.Count == 0)
{
return SignatureType.Unknown;
}
// Author and repository values are mutually exclusive in the same signature.
// If multiple distinct known values exist then the attribute is invalid.
if (knownValues.Count > 1)
{
throw new SignatureException(Strings.CommitmentTypeIndicationAttributeInvalidCombination);
}
return knownValues[0];
}
/// <summary>
/// Creates a nuget-v3-service-index-url attribute.
/// </summary>
/// <param name="v3ServiceIndexUrl">The V3 service index HTTPS URL.</param>
/// <returns>An attribute object.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="v3ServiceIndexUrl" /> is null.</exception>
/// <exception cref="ArgumentException">Thrown if <paramref name="v3ServiceIndexUrl" /> is neither absolute
/// nor HTTPS.</exception>
public static CryptographicAttributeObject CreateNuGetV3ServiceIndexUrl(Uri v3ServiceIndexUrl)
{
if (v3ServiceIndexUrl == null)
{
throw new ArgumentNullException(nameof(v3ServiceIndexUrl));
}
if (!v3ServiceIndexUrl.IsAbsoluteUri)
{
throw new ArgumentException(Strings.InvalidUrl, nameof(v3ServiceIndexUrl));
}
if (!string.Equals(v3ServiceIndexUrl.Scheme, "https", StringComparison.Ordinal))
{
throw new ArgumentException(Strings.InvalidUrl, nameof(v3ServiceIndexUrl));
}
var nugetV3ServiceIndexUrl = new NuGetV3ServiceIndexUrl(v3ServiceIndexUrl);
var bytes = nugetV3ServiceIndexUrl.Encode();
return new CryptographicAttributeObject(
new Oid(Oids.NuGetV3ServiceIndexUrl),
new AsnEncodedDataCollection(new AsnEncodedData(Oids.NuGetV3ServiceIndexUrl, bytes)));
}
/// <summary>
/// Gets the V3 service index HTTPS URL from the nuget-v3-service-index-url attribute.
/// </summary>
/// <param name="signedAttributes">A <see cref="SignerInfo" /> signed attributes collection.</param>
/// <returns>The V3 service index HTTPS URL.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="signedAttributes" />
/// is <c>null</c>.</exception>
/// <exception cref="SignatureException">Thrown if either exactly one attribute is not present or if
/// the attribute does not contain exactly one attribute value.</exception>
public static Uri GetNuGetV3ServiceIndexUrl(CryptographicAttributeObjectCollection signedAttributes)
{
if (signedAttributes == null)
{
throw new ArgumentNullException(nameof(signedAttributes));
}
const string attributeName = "nuget-v3-service-index-url";
var attribute = signedAttributes.GetAttribute(Oids.NuGetV3ServiceIndexUrl);
if (attribute == null)
{
throw new SignatureException(
string.Format(
CultureInfo.CurrentCulture,
Strings.ExactlyOneAttributeRequired,
attributeName));
}
if (attribute.Values.Count != 1)
{
throw new SignatureException(
string.Format(
CultureInfo.CurrentCulture,
Strings.ExactlyOneAttributeValueRequired,
attributeName));
}
var nugetV3ServiceIndexUrl = NuGetV3ServiceIndexUrl.Read(attribute.Values[0].RawData);
return nugetV3ServiceIndexUrl.V3ServiceIndexUrl;
}
/// <summary>
/// Creates a nuget-package-owners attribute.
/// </summary>
/// <param name="packageOwners">A read-only list of package owners.</param>
/// <returns>An attribute object.</returns>
/// <exception cref="ArgumentException">Thrown if <paramref name="packageOwners" /> is either <c>null</c>
/// or empty or if any package owner name is invalid.</exception>
public static CryptographicAttributeObject CreateNuGetPackageOwners(IReadOnlyList<string> packageOwners)
{
if (packageOwners == null || packageOwners.Count == 0)
{
throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(packageOwners));
}
if (packageOwners.Any(packageOwner => string.IsNullOrWhiteSpace(packageOwner)))
{
throw new ArgumentException(Strings.NuGetPackageOwnersInvalidValue, nameof(packageOwners));
}
var nugetPackageOwners = new NuGetPackageOwners(packageOwners);
var bytes = nugetPackageOwners.Encode();
return new CryptographicAttributeObject(
new Oid(Oids.NuGetPackageOwners),
new AsnEncodedDataCollection(new AsnEncodedData(Oids.NuGetPackageOwners, bytes)));
}
/// <summary>
/// Gets a read-only list of package owners from an optional nuget-package-owners attribute.
/// </summary>
/// <param name="signedAttributes">A <see cref="SignerInfo" /> signed attributes collection.</param>
/// <returns>A read-only list of package owners or <c>null</c>.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="signedAttributes" /> is <c>null</c>.</exception>
/// <exception cref="SignatureException">Thrown if the attribute does not contain exactly one
/// attribute value.</exception>
public static IReadOnlyList<string> GetNuGetPackageOwners(CryptographicAttributeObjectCollection signedAttributes)
{
if (signedAttributes == null)
{
throw new ArgumentNullException(nameof(signedAttributes));
}
var attribute = signedAttributes.GetAttribute(Oids.NuGetPackageOwners);
if (attribute == null)
{
return null;
}
if (attribute.Values.Count != 1)
{
throw new SignatureException(
string.Format(
CultureInfo.CurrentCulture,
Strings.ExactlyOneAttributeValueRequired,
"nuget-package-owners"));
}
var nugetPackageOwners = NuGetPackageOwners.Read(attribute.Values[0].RawData);
return nugetPackageOwners.PackageOwners;
}
/// <summary>
/// Oid -> SignatureType
/// </summary>
/// <param name="oid">The commitment-type-indication value.</param>
public static SignatureType GetSignatureType(string oid)
{
switch (oid)
{
case Oids.CommitmentTypeIdentifierProofOfOrigin:
return SignatureType.Author;
case Oids.CommitmentTypeIdentifierProofOfReceipt:
return SignatureType.Repository;
default:
return SignatureType.Unknown;
}
}
/// <summary>
/// SignatureType -> Oid
/// </summary>
/// <param name="signatureType">The signature type.</param>
public static string GetSignatureTypeOid(SignatureType signatureType)
{
switch (signatureType)
{
case SignatureType.Author:
return Oids.CommitmentTypeIdentifierProofOfOrigin;
case SignatureType.Repository:
return Oids.CommitmentTypeIdentifierProofOfReceipt;
default:
throw new ArgumentOutOfRangeException(paramName: nameof(signatureType));
}
}
/// <summary>
/// Create a signing-certificate-v2 from a certificate.
/// </summary>
/// <param name="certificate">The signing certificate.</param>
/// <param name="hashAlgorithm">The hash algorithm for the signing-certificate-v2 attribute.</param>
public static CryptographicAttributeObject CreateSigningCertificateV2(
X509Certificate2 certificate,
Common.HashAlgorithmName hashAlgorithm)
{
if (certificate == null)
{
throw new ArgumentNullException(nameof(certificate));
}
var signingCertificateV2 = SigningCertificateV2.Create(certificate, hashAlgorithm);
var bytes = signingCertificateV2.Encode();
var data = new AsnEncodedData(Oids.SigningCertificateV2, bytes);
return new CryptographicAttributeObject(
new Oid(Oids.SigningCertificateV2),
new AsnEncodedDataCollection(data));
}
/// <summary>
/// Returns the first attribute if the Oid is found.
/// Returns null if the attribute is not found.
/// </summary>
internal static CryptographicAttributeObject GetAttributeOrDefault(this CryptographicAttributeObjectCollection attributes, string oid)
{
if (oid == null)
{
throw new ArgumentNullException(nameof(oid));
}
foreach (var attribute in attributes)
{
if (StringComparer.Ordinal.Equals(oid, attribute.Oid.Value))
{
return attribute;
}
}
return null;
}
/// <summary>
/// Throw a signature exception due to an invalid attribute. This is used for unusual situations
/// where the format is corrupt.
/// </summary>
private static void ThrowInvalidAttributeException(CryptographicAttributeObject attribute)
{
throw new SignatureException(string.Format(CultureInfo.CurrentCulture, Strings.SignatureContainsInvalidAttribute, attribute.Oid.Value));
}
/// <summary>
/// Enumerate AsnEncodedDataCollection
/// </summary>
private static List<AsnEncodedData> ToList(this AsnEncodedDataCollection collection)
{
var values = new List<AsnEncodedData>();
foreach (var value in collection)
{
values.Add(value);
}
return values;
}
/// <summary>
/// Attribute -> SignatureType values with no validation.
/// </summary>
private static IEnumerable<SignatureType> GetCommitmentTypeIndicationRawValues(CryptographicAttributeObject attribute)
{
// Most packages should have either 0 or 1 signature types.
var values = new List<SignatureType>(capacity: 1);
foreach (var value in attribute.Values)
{
var indication = CommitmentTypeIndication.Read(value.RawData);
var signatureType = GetSignatureType(indication.CommitmentTypeId.Value);
values.Add(signatureType);
}
return values;
}
/// <summary>
/// Gets 0 or 1 attribute with the specified OID. If more than one attribute is found, an exception is thrown.
/// </summary>
/// <param name="attributes">A collection of attributes.</param>
/// <param name="oid">The attribute OID to search for.</param>
/// <returns>Either a <see cref="CryptographicAttributeObject" /> or <c>null</c>, if no attribute was found.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="attributes" /> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown if <paramref name="oid" /> is either <c>null</c> or an empty string.</exception>
/// <exception cref="CryptographicException">Thrown if multiple attribute instances with the specified OID were found.</exception>
public static CryptographicAttributeObject GetAttribute(this CryptographicAttributeObjectCollection attributes, string oid)
{
if (attributes == null)
{
throw new ArgumentNullException(nameof(attributes));
}
if (string.IsNullOrEmpty(oid))
{
throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(oid));
}
var matches = attributes.GetAttributes(oid);
var instanceCount = matches.Count();
if (instanceCount == 0)
{
return null;
}
if (instanceCount > 1)
{
throw new CryptographicException(string.Format(CultureInfo.CurrentCulture, Strings.MultipleAttributesDisallowed, oid));
}
return matches.Single();
}
/// <summary>
/// Gets 0 or 1 or many attributes with the specified OID.
/// </summary>
/// <param name="attributes">A collection of attributes.</param>
/// <param name="oid">The attribute OID to search for.</param>
/// <returns>Either a <see cref="CryptographicAttributeObject" /> or <c>null</c>, if no attribute was found.</returns>
/// <exception cref="ArgumentNullException">Thrown if <paramref name="attributes" /> is <c>null</c>.</exception>
/// <exception cref="ArgumentException">Thrown if <paramref name="oid" /> is either <c>null</c> or an empty string.</exception>
public static IEnumerable<CryptographicAttributeObject> GetAttributes(this CryptographicAttributeObjectCollection attributes, string oid)
{
if (attributes == null)
{
throw new ArgumentNullException(nameof(attributes));
}
if (string.IsNullOrEmpty(oid))
{
throw new ArgumentException(Strings.ArgumentCannotBeNullOrEmpty, nameof(oid));
}
return attributes.Cast<CryptographicAttributeObject>()
.Where(attribute => attribute.Oid.Value == oid);
}
#endif
}
}