< Summary

Class:Azure.Messaging.EventHubs.Authorization.SharedAccessSignature
Assembly:Azure.Messaging.EventHubs.Processor
File(s):C:\Git\azure-sdk-for-net\sdk\eventhub\Azure.Messaging.EventHubs.Shared\src\Authorization\SharedAccessSignature.cs
Covered lines:0
Uncovered lines:109
Coverable lines:109
Total lines:380
Line coverage:0% (0 of 109)
Covered branches:0
Total branches:44
Branch coverage:0% (0 of 44)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.cctor()-0%100%
get_SharedAccessKeyName()-0%100%
get_SharedAccessKey()-0%100%
get_SignatureExpiration()-0%100%
get_Resource()-0%100%
get_Value()-0%100%
.ctor(...)-0%0%
.ctor(...)-0%100%
.ctor(...)-0%100%
.ctor(...)-0%100%
CloneWithNewExpiration(...)-0%0%
ToString()-0%100%
ParseSignature(...)-0%0%
BuildSignature(...)-0%100%
ConvertFromUnixTime(...)-0%100%
ConvertToUnixTime(...)-0%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\eventhub\Azure.Messaging.EventHubs.Shared\src\Authorization\SharedAccessSignature.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Globalization;
 6using System.Net;
 7using System.Security.Cryptography;
 8using System.Text;
 9using Azure.Core;
 10
 11namespace Azure.Messaging.EventHubs.Authorization
 12{
 13    /// <summary>
 14    ///   A shared access signature, which can be used for authorization to an Event Hubs namespace
 15    ///   or a specific Event Hub.
 16    /// </summary>
 17    ///
 18    internal class SharedAccessSignature
 19    {
 20        /// <summary>The maximum allowed length of the SAS key name.</summary>
 21        private const int MaximumKeyNameLength = 256;
 22
 23        /// <summary>The maximum allowed length of the SAS key.</summary>
 24        private const int MaximumKeyLength = 256;
 25
 26        /// <summary>The token that represents the type of authentication used.</summary>
 27        private const string AuthenticationTypeToken = "SharedAccessSignature";
 28
 29        /// <summary>The token that identifies the signed component of the shared access signature.</summary>
 30        private const string SignedResourceToken = "sr";
 31
 32        /// <summary>The token that identifies the signature component of the shared access signature.</summary>
 33        private const string SignatureToken = "sig";
 34
 35        /// <summary>The token that identifies the signed SAS key component of the shared access signature.</summary>
 36        private const string SignedKeyNameToken = "skn";
 37
 38        /// <summary>The token that identifies the signed expiration time of the shared access signature.</summary>
 39        private const string SignedExpiryToken = "se";
 40
 41        /// <summary>The token that fully identifies the signed resource within the signature.</summary>
 42        private const string SignedResourceFullIdentifierToken = AuthenticationTypeToken + " " + SignedResourceToken;
 43
 44        /// <summary>The character used to separate a token and its value in the connection string.</summary>
 45        private const char TokenValueSeparator = '=';
 46
 47        /// <summary>The character used to mark the beginning of a new token/value pair in the signature.</summary>
 48        private const char TokenValuePairDelimiter = '&';
 49
 50        /// <summary>The default length of time to consider a signature valid, if not otherwise specified.</summary>
 051        private static readonly TimeSpan DefaultSignatureValidityDuration = TimeSpan.FromMinutes(30);
 52
 53        /// <summary>Represents the Unix epoch time value, January 1, 1970 12:00:00, UTC.</summary>
 054        private static readonly DateTimeOffset Epoch = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero);
 55
 56        /// <summary>
 57        ///   The name of the shared access key, either for the Event Hubs namespace
 58        ///   or the Event Hub.
 59        /// </summary>
 60        ///
 061        public string SharedAccessKeyName { get; private set; }
 62
 63        /// <summary>
 64        ///   The value of the shared access key, either for the Event Hubs namespace
 65        ///   or the Event Hub.
 66        /// </summary>
 67        ///
 068        public string SharedAccessKey { get; private set; }
 69
 70        /// <summary>
 71        ///   The date and time that the shared access signature expires, in UTC.
 72        /// </summary>
 73        ///
 074        public DateTimeOffset SignatureExpiration { get; private set; }
 75
 76        /// <summary>
 77        ///   The resource to which the shared access signature is intended to serve as
 78        ///   authorization.
 79        /// </summary>
 80        ///
 081        public string Resource { get; private set; }
 82
 83        /// <summary>
 84        ///   The shared access signature to be used for authorization, either for the Event Hubs namespace
 85        ///   or the Event Hub.
 86        /// </summary>
 87        ///
 088        public string Value { get; private set; }
 89
 90        /// <summary>
 91        ///   Initializes a new instance of the <see cref="SharedAccessSignature"/> class.
 92        /// </summary>
 93        ///
 94        /// <param name="eventHubResource">The Event Hubs resource to which the token is intended to serve as authorizat
 95        /// <param name="sharedAccessKeyName">The name of the shared access key that the signature should be based on.</
 96        /// <param name="sharedAccessKey">The value of the shared access key for the signature.</param>
 97        /// <param name="signatureValidityDuration">The duration that the signature should be considered valid; if not s
 98        ///
 099        public SharedAccessSignature(string eventHubResource,
 0100                                     string sharedAccessKeyName,
 0101                                     string sharedAccessKey,
 0102                                     TimeSpan? signatureValidityDuration = default)
 103        {
 0104            signatureValidityDuration ??= DefaultSignatureValidityDuration;
 105
 0106            Argument.AssertNotNullOrEmpty(eventHubResource, nameof(eventHubResource));
 0107            Argument.AssertNotNullOrEmpty(sharedAccessKeyName, nameof(sharedAccessKeyName));
 0108            Argument.AssertNotNullOrEmpty(sharedAccessKey, nameof(sharedAccessKey));
 109
 0110            Argument.AssertNotTooLong(sharedAccessKeyName, MaximumKeyNameLength, nameof(sharedAccessKeyName));
 0111            Argument.AssertNotTooLong(sharedAccessKey, MaximumKeyLength, nameof(sharedAccessKey));
 0112            Argument.AssertNotNegative(signatureValidityDuration.Value, nameof(signatureValidityDuration));
 113
 0114            SharedAccessKeyName = sharedAccessKeyName;
 0115            SharedAccessKey = sharedAccessKey;
 0116            SignatureExpiration = DateTimeOffset.UtcNow.Add(signatureValidityDuration.Value);
 0117            Resource = eventHubResource;
 0118            Value = BuildSignature(Resource, sharedAccessKeyName, sharedAccessKey, SignatureExpiration);
 0119        }
 120
 121        /// <summary>
 122        ///   Initializes a new instance of the <see cref="SharedAccessSignature"/> class.
 123        /// </summary>
 124        ///
 125        /// <param name="sharedAccessSignature">The shared access signature that will be parsed as the basis of this ins
 126        /// <param name="sharedAccessKey">The value of the shared access key for the signature.</param>
 127        ///
 0128        public SharedAccessSignature(string sharedAccessSignature,
 0129                                     string sharedAccessKey)
 130        {
 0131            Argument.AssertNotNullOrEmpty(sharedAccessSignature, nameof(sharedAccessSignature));
 0132            Argument.AssertNotTooLong(sharedAccessKey, MaximumKeyLength, nameof(sharedAccessKey));
 133
 0134            (SharedAccessKeyName, Resource, SignatureExpiration) = ParseSignature(sharedAccessSignature);
 135
 0136            SharedAccessKey = sharedAccessKey;
 0137            Value = sharedAccessSignature;
 0138        }
 139
 140        /// <summary>
 141        ///   Initializes a new instance of the <see cref="SharedAccessSignature"/> class.
 142        /// </summary>
 143        ///
 144        /// <param name="sharedAccessSignature">The shared access signature that will be parsed as the basis of this ins
 145        ///
 0146        public SharedAccessSignature(string sharedAccessSignature) : this(sharedAccessSignature, null)
 147        {
 0148        }
 149
 150        /// <summary>
 151        ///   Initializes a new instance of the <see cref="SharedAccessSignature" /> class.
 152        /// </summary>
 153        ///
 154        /// <param name="eventHubResource">The Event Hubs resource to which the token is intended to serve as authorizat
 155        /// <param name="sharedAccessKeyName">The name of the shared access key that the signature should be based on.</
 156        /// <param name="sharedAccessKey">The value of the shared access key for the signature.</param>
 157        /// <param name="value">The shared access signature to be used for authorization.</param>
 158        /// <param name="signatureExpiration">The date and time that the shared access signature expires, in UTC.</param
 159        ///
 0160        public SharedAccessSignature(string eventHubResource,
 0161                                     string sharedAccessKeyName,
 0162                                     string sharedAccessKey,
 0163                                     string value,
 0164                                     DateTimeOffset signatureExpiration)
 165        {
 0166            Argument.AssertNotNullOrEmpty(eventHubResource, nameof(eventHubResource));
 0167            Argument.AssertNotNullOrEmpty(sharedAccessKeyName, nameof(sharedAccessKeyName));
 0168            Argument.AssertNotNullOrEmpty(sharedAccessKey, nameof(sharedAccessKey));
 169
 0170            Argument.AssertNotTooLong(sharedAccessKeyName, MaximumKeyNameLength, nameof(sharedAccessKeyName));
 0171            Argument.AssertNotTooLong(sharedAccessKey, MaximumKeyLength, nameof(sharedAccessKey));
 172
 0173            Resource = eventHubResource;
 0174            SharedAccessKeyName = sharedAccessKeyName;
 0175            SharedAccessKey = sharedAccessKey;
 0176            Value = value;
 0177            SignatureExpiration = signatureExpiration;
 0178        }
 179
 180        /// <summary>
 181        ///   Creates a new signature with the specified period for which the shared access signature is considered vali
 182        /// </summary>
 183        ///
 184        /// <param name="signatureValidityDuration">The duration that the signature should be considered valid.</param>
 185        ///
 186        /// <returns>A new <see cref="SharedAccessSignature" /> based on the same key, but with a new expiration time.</
 187        ///
 188        public SharedAccessSignature CloneWithNewExpiration(TimeSpan signatureValidityDuration)
 189        {
 0190            Argument.AssertNotNegative(signatureValidityDuration, nameof(signatureValidityDuration));
 191
 192            // The key must have been provided at construction in order to manipulate the signature.
 193
 0194            if (string.IsNullOrEmpty(SharedAccessKey))
 195            {
 0196                throw new InvalidOperationException(Resources.SharedAccessKeyIsRequired);
 197            }
 198
 0199            return new SharedAccessSignature(Resource, SharedAccessKeyName, SharedAccessKey, signatureValidityDuration);
 200        }
 201
 202        /// <summary>
 203        ///   Converts the instance to string representation.
 204        /// </summary>
 205        ///
 206        /// <returns>A <see cref="System.String" /> that represents this instance.</returns>
 207        ///
 0208        public override string ToString() => Value;
 209
 210        /// <summary>
 211        ///   Parses a shared access signature into its component parts.
 212        /// </summary>
 213        ///
 214        /// <param name="sharedAccessSignature">The shared access signature to parse.</param>
 215        ///
 216        /// <returns>The set of composite properties parsed from the signature.</returns>
 217        ///
 218        private static (string KeyName, string Resource, DateTimeOffset ExpirationTime) ParseSignature(string sharedAcce
 219        {
 0220            int tokenPositionModifier = (sharedAccessSignature[0] == TokenValuePairDelimiter) ? 0 : 1;
 0221            int lastPosition = 0;
 0222            int currentPosition = 0;
 223            int valueStart;
 224
 225            string slice;
 226            string token;
 227            string value;
 228
 0229            var parsedValues =
 0230            (
 0231                KeyName: default(string),
 0232                Resource: default(string),
 0233                ExpirationTime: default(DateTimeOffset)
 0234            );
 235
 0236            while (currentPosition != -1)
 237            {
 238                // Slice the string into the next token/value pair.
 239
 0240                currentPosition = sharedAccessSignature.IndexOf(TokenValuePairDelimiter, lastPosition + 1);
 241
 0242                if (currentPosition >= 0)
 243                {
 0244                    slice = sharedAccessSignature.Substring(lastPosition, (currentPosition - lastPosition));
 245                }
 246                else
 247                {
 0248                    slice = sharedAccessSignature.Substring(lastPosition);
 249                }
 250
 251                // Break the token and value apart, if this is a legal pair.
 252
 0253                valueStart = slice.IndexOf(TokenValueSeparator);
 254
 0255                if (valueStart >= 0)
 256                {
 0257                    token = slice.Substring((1 - tokenPositionModifier), (valueStart - 1 + tokenPositionModifier));
 0258                    value = slice.Substring(valueStart + 1);
 259
 260                    // Guard against leading and trailing spaces, only trimming if there is a need.
 261
 0262                    if ((!string.IsNullOrEmpty(token)) && (char.IsWhiteSpace(token[0])) || char.IsWhiteSpace(token[token
 263                    {
 0264                        token = token.Trim();
 265                    }
 266
 0267                    if ((!string.IsNullOrEmpty(value)) && (char.IsWhiteSpace(value[0]) || char.IsWhiteSpace(value[value.
 268                    {
 0269                        value = value.Trim();
 270                    }
 271
 272                    // If there was no value for a key, then consider the signature to be malformed.
 273
 0274                    if (string.IsNullOrEmpty(value))
 275                    {
 0276                        throw new ArgumentException(Resources.InvalidSharedAccessSignature, nameof(sharedAccessSignature
 277                    }
 278
 279                    // Compare the token against the known signature properties and capture the
 280                    // pair if they are a known attribute.
 281
 0282                    if (string.Compare(SignedResourceFullIdentifierToken, token, StringComparison.OrdinalIgnoreCase) == 
 283                    {
 0284                        parsedValues.Resource = WebUtility.UrlDecode(value);
 285                    }
 0286                    else if (string.Compare(SignedKeyNameToken, token, StringComparison.OrdinalIgnoreCase) == 0)
 287                    {
 0288                        parsedValues.KeyName = WebUtility.UrlDecode(value);
 289                    }
 0290                    else if (string.Compare(SignedExpiryToken, token, StringComparison.OrdinalIgnoreCase) == 0)
 291                    {
 0292                        if (!long.TryParse(WebUtility.UrlDecode(value), NumberStyles.Integer, CultureInfo.InvariantCultu
 293                        {
 0294                            throw new ArgumentException(Resources.InvalidSharedAccessSignature, nameof(sharedAccessSigna
 295                        }
 296
 0297                        parsedValues.ExpirationTime = ConvertFromUnixTime(unixTime);
 298                    }
 299                }
 0300                else if ((slice.Length != 1) || (slice[0] != TokenValuePairDelimiter))
 301                {
 302                    // This wasn't a legal pair and it is not simply a trailing delimiter; consider
 303                    // the signature to be malformed.
 304
 0305                    throw new ArgumentException(Resources.InvalidSharedAccessSignature, nameof(sharedAccessSignature));
 306                }
 307
 0308                tokenPositionModifier = 0;
 0309                lastPosition = currentPosition;
 310            }
 311
 312            // Validate that the required components were able to be parsed from the
 313            // signature.
 314
 0315            if ((string.IsNullOrEmpty(parsedValues.Resource))
 0316                || (string.IsNullOrEmpty(parsedValues.KeyName))
 0317                || (parsedValues.ExpirationTime == default))
 318            {
 0319                throw new ArgumentException(Resources.InvalidSharedAccessSignature, nameof(sharedAccessSignature));
 320            }
 321
 0322            return parsedValues;
 323        }
 324
 325        /// <summary>
 326        ///   Builds the shared access signature value, which can be used as a token for
 327        ///   access to the Event Hubs service.
 328        /// </summary>
 329        ///
 330        /// <param name="audience">The audience scope to which this signature applies.</param>
 331        /// <param name="sharedAccessKeyName">The name of the shared access key that the signature should be based on.</
 332        /// <param name="sharedAccessKey">The value of the shared access key for the signature.</param>
 333        /// <param name="expirationTime">The date/time, in UTC, that the signature expires.</param>
 334        ///
 335        /// <returns>The value of the shared access signature.</returns>
 336        ///
 337        private static string BuildSignature(string audience,
 338                                             string sharedAccessKeyName,
 339                                             string sharedAccessKey,
 340                                             DateTimeOffset expirationTime)
 341        {
 0342            using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(sharedAccessKey)))
 343            {
 0344                var encodedAudience = WebUtility.UrlEncode(audience);
 0345                var expiration = Convert.ToString(ConvertToUnixTime(expirationTime), CultureInfo.InvariantCulture);
 0346                var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes($"{ encodedAudience }\n{ 
 347
 0348                return string.Format(CultureInfo.InvariantCulture, "{0} {1}={2}&{3}={4}&{5}={6}&{7}={8}",
 0349                    AuthenticationTypeToken,
 0350                    SignedResourceToken, encodedAudience,
 0351                    SignatureToken, WebUtility.UrlEncode(signature),
 0352                    SignedExpiryToken, WebUtility.UrlEncode(expiration),
 0353                    SignedKeyNameToken, WebUtility.UrlEncode(sharedAccessKeyName));
 354            }
 0355        }
 356
 357        /// <summary>
 358        ///   Converts a Unix-style timestamp into the corresponding <see cref="DateTimeOffset" />
 359        ///   value.
 360        /// </summary>
 361        ///
 362        /// <param name="unixTime">The timestamp to convert.</param>
 363        ///
 364        /// <returns>The date/time, in UTC, which corresponds to the specified timestamp.</returns>
 365        ///
 366        private static DateTimeOffset ConvertFromUnixTime(long unixTime) =>
 0367            Epoch.AddSeconds(unixTime);
 368
 369        /// <summary>
 370        ///   Converts a <see cref="DateTimeOffset" /> value to the corresponding Unix-style timestamp.
 371        /// </summary>
 372        ///
 373        /// <param name="dateTimeOffset">The date/time to convert.</param>
 374        ///
 375        /// <returns>The Unix-style timestamp which corresponds to the specified date/time.</returns>
 376        ///
 377        private static long ConvertToUnixTime(DateTimeOffset dateTimeOffset) =>
 0378            Convert.ToInt64((dateTimeOffset - Epoch).TotalSeconds);
 379    }
 380}