| File: Signing\Utility\AttributeUtility.cs | Web Access |
| Project: src\nuget-client\src\NuGet.Core\NuGet.Packaging\NuGet.Packaging.csproj (NuGet.Packaging) |
// 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; using System.Security.Cryptography.Pkcs; using System.Security.Cryptography.X509Certificates; namespace NuGet.Packaging.Signing { public static class AttributeUtility { /// <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 <see langword="null" />.</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 <see langword="null" />.</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 <see langword="null" /> /// 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 <see langword="null" />.</returns> /// <exception cref="ArgumentNullException">Thrown if <paramref name="signedAttributes" /> is <see langword="null" />.</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> /// 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 <see langword="null" />, if no attribute was found.</returns> /// <exception cref="ArgumentNullException">Thrown if <paramref name="attributes" /> is <see langword="null" />.</exception> /// <exception cref="ArgumentException">Thrown if <paramref name="oid" /> is either <see langword="null" /> 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 <see langword="null" />, if no attribute was found.</returns> /// <exception cref="ArgumentNullException">Thrown if <paramref name="attributes" /> is <see langword="null" />.</exception> /// <exception cref="ArgumentException">Thrown if <paramref name="oid" /> is either <see langword="null" /> 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); } } }