< Summary

Class:Azure.Messaging.ServiceBus.Authorization.SharedAccessSignature
Assembly:Azure.Messaging.ServiceBus
File(s):C:\Git\azure-sdk-for-net\sdk\servicebus\Azure.Messaging.ServiceBus\src\Authorization\SharedAccessSignature.cs
Covered lines:35
Uncovered lines:79
Coverable lines:114
Total lines:386
Line coverage:30.7% (35 of 114)
Covered branches:2
Total branches:44
Branch coverage:4.5% (2 of 44)

Metrics

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

File(s)

C:\Git\azure-sdk-for-net\sdk\servicebus\Azure.Messaging.ServiceBus\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.ServiceBus.Authorization
 12{
 13    /// <summary>
 14    ///   A shared access signature, which can be used for authorization to an Service Bus namespace
 15    ///   or a specific Service Bus entity.
 16    /// </summary>
 17    ///
 18    internal class SharedAccessSignature
 19    {
 20        /// <summary>The maximum allowed length of the SAS key name.</summary>
 21        internal 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>
 251        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>
 254        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 Service Bus namespace
 58        ///   or the Service Bus entity.
 59        /// </summary>
 60        ///
 061        public string SharedAccessKeyName { get; private set; }
 62
 63        /// <summary>
 64        ///   The value of the shared access key, either for the Service Bus namespace
 65        ///   or the Service Bus entity.
 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        ///
 79674        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        ///
 15681        public string Resource { get; private set; }
 82
 83        /// <summary>
 84        ///   The shared access signature to be used for authorization, either for the Service Bus namespace
 85        ///   or the Service Bus entity.
 86        /// </summary>
 87        ///
 39888        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="serviceBusResource">The Service Bus resource to which the token is intended to serve as authori
 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        ///
 7899        public SharedAccessSignature(
 78100            string serviceBusResource,
 78101            string sharedAccessKeyName,
 78102            string sharedAccessKey,
 78103            TimeSpan? signatureValidityDuration = default)
 104        {
 78105            signatureValidityDuration ??= DefaultSignatureValidityDuration;
 106
 78107            Argument.AssertNotNullOrEmpty(serviceBusResource, nameof(serviceBusResource));
 78108            Argument.AssertNotNullOrEmpty(sharedAccessKeyName, nameof(sharedAccessKeyName));
 78109            Argument.AssertNotNullOrEmpty(sharedAccessKey, nameof(sharedAccessKey));
 110
 78111            Argument.AssertNotTooLong(sharedAccessKeyName, MaximumKeyNameLength, nameof(sharedAccessKeyName));
 78112            Argument.AssertNotTooLong(sharedAccessKey, MaximumKeyLength, nameof(sharedAccessKey));
 78113            Argument.AssertNotNegative(signatureValidityDuration.Value, nameof(signatureValidityDuration));
 114
 78115            SharedAccessKeyName = sharedAccessKeyName;
 78116            SharedAccessKey = sharedAccessKey;
 78117            SignatureExpiration = DateTimeOffset.UtcNow.Add(signatureValidityDuration.Value);
 78118            Resource = serviceBusResource;
 78119            Value = BuildSignature(Resource, sharedAccessKeyName, sharedAccessKey, SignatureExpiration);
 78120        }
 121
 122        /// <summary>
 123        ///   Initializes a new instance of the <see cref="SharedAccessSignature"/> class.
 124        /// </summary>
 125        ///
 126        /// <param name="sharedAccessSignature">The shared access signature that will be parsed as the basis of this ins
 127        /// <param name="sharedAccessKey">The value of the shared access key for the signature.</param>
 128        ///
 0129        public SharedAccessSignature(
 0130            string sharedAccessSignature,
 0131            string sharedAccessKey)
 132        {
 0133            Argument.AssertNotNullOrEmpty(sharedAccessSignature, nameof(sharedAccessSignature));
 0134            Argument.AssertNotTooLong(sharedAccessKey, MaximumKeyLength, nameof(sharedAccessKey));
 135
 0136            (SharedAccessKeyName, Resource, SignatureExpiration) = ParseSignature(sharedAccessSignature);
 137
 0138            SharedAccessKey = sharedAccessKey;
 0139            Value = sharedAccessSignature;
 0140        }
 141
 142        /// <summary>
 143        ///   Initializes a new instance of the <see cref="SharedAccessSignature"/> class.
 144        /// </summary>
 145        ///
 146        /// <param name="sharedAccessSignature">The shared access signature that will be parsed as the basis of this ins
 147        ///
 0148        public SharedAccessSignature(string sharedAccessSignature) : this(sharedAccessSignature, null)
 149        {
 0150        }
 151
 152        /// <summary>
 153        ///   Initializes a new instance of the <see cref="SharedAccessSignature" /> class.
 154        /// </summary>
 155        ///
 156        /// <param name="eventHubResource">The Service Bus resource to which the token is intended to serve as authoriza
 157        /// <param name="sharedAccessKeyName">The name of the shared access key that the signature should be based on.</
 158        /// <param name="sharedAccessKey">The value of the shared access key for the signature.</param>
 159        /// <param name="value">The shared access signature to be used for authorization.</param>
 160        /// <param name="signatureExpiration">The date and time that the shared access signature expires, in UTC.</param
 161        ///
 0162        public SharedAccessSignature(
 0163            string eventHubResource,
 0164            string sharedAccessKeyName,
 0165            string sharedAccessKey,
 0166            string value,
 0167            DateTimeOffset signatureExpiration)
 168        {
 0169            Argument.AssertNotNullOrEmpty(eventHubResource, nameof(eventHubResource));
 0170            Argument.AssertNotNullOrEmpty(sharedAccessKeyName, nameof(sharedAccessKeyName));
 0171            Argument.AssertNotNullOrEmpty(sharedAccessKey, nameof(sharedAccessKey));
 172
 0173            Argument.AssertNotTooLong(sharedAccessKeyName, MaximumKeyNameLength, nameof(sharedAccessKeyName));
 0174            Argument.AssertNotTooLong(sharedAccessKey, MaximumKeyLength, nameof(sharedAccessKey));
 175
 0176            Resource = eventHubResource;
 0177            SharedAccessKeyName = sharedAccessKeyName;
 0178            SharedAccessKey = sharedAccessKey;
 0179            Value = value;
 0180            SignatureExpiration = signatureExpiration;
 0181        }
 182
 183        /// <summary>
 184        ///   Creates a new signature with the specified period for which the shared access signature is considered vali
 185        /// </summary>
 186        ///
 187        /// <param name="signatureValidityDuration">The duration that the signature should be considered valid.</param>
 188        ///
 189        /// <returns>A new <see cref="SharedAccessSignature" /> based on the same key, but with a new expiration time.</
 190        ///
 191        public SharedAccessSignature CloneWithNewExpiration(TimeSpan signatureValidityDuration)
 192        {
 0193            Argument.AssertNotNegative(signatureValidityDuration, nameof(signatureValidityDuration));
 194
 195            // The key must have been provided at construction in order to manipulate the signature.
 196
 0197            if (string.IsNullOrEmpty(SharedAccessKey))
 198            {
 0199                throw new InvalidOperationException(Resources.SharedAccessKeyIsRequired);
 200            }
 201
 0202            return new SharedAccessSignature(Resource, SharedAccessKeyName, SharedAccessKey, signatureValidityDuration);
 203        }
 204
 205        /// <summary>
 206        ///   Converts the instance to string representation.
 207        /// </summary>
 208        ///
 209        /// <returns>A <see cref="System.String" /> that represents this instance.</returns>
 210        ///
 0211        public override string ToString() => Value;
 212
 213        /// <summary>
 214        ///   Parses a shared access signature into its component parts.
 215        /// </summary>
 216        ///
 217        /// <param name="sharedAccessSignature">The shared access signature to parse.</param>
 218        ///
 219        /// <returns>The set of composite properties parsed from the signature.</returns>
 220        ///
 221        private static (string KeyName, string Resource, DateTimeOffset ExpirationTime) ParseSignature(string sharedAcce
 222        {
 0223            int tokenPositionModifier = (sharedAccessSignature[0] == TokenValuePairDelimiter) ? 0 : 1;
 0224            int lastPosition = 0;
 0225            int currentPosition = 0;
 226            int valueStart;
 227
 228            string slice;
 229            string token;
 230            string value;
 231
 0232            var parsedValues =
 0233            (
 0234                KeyName: default(string),
 0235                Resource: default(string),
 0236                ExpirationTime: default(DateTimeOffset)
 0237            );
 238
 0239            while (currentPosition != -1)
 240            {
 241                // Slice the string into the next token/value pair.
 242
 0243                currentPosition = sharedAccessSignature.IndexOf(TokenValuePairDelimiter, lastPosition + 1);
 244
 0245                if (currentPosition >= 0)
 246                {
 0247                    slice = sharedAccessSignature.Substring(lastPosition, (currentPosition - lastPosition));
 248                }
 249                else
 250                {
 0251                    slice = sharedAccessSignature.Substring(lastPosition);
 252                }
 253
 254                // Break the token and value apart, if this is a legal pair.
 255
 0256                valueStart = slice.IndexOf(TokenValueSeparator);
 257
 0258                if (valueStart >= 0)
 259                {
 0260                    token = slice.Substring((1 - tokenPositionModifier), (valueStart - 1 + tokenPositionModifier));
 0261                    value = slice.Substring(valueStart + 1);
 262
 263                    // Guard against leading and trailing spaces, only trimming if there is a need.
 264
 0265                    if ((!string.IsNullOrEmpty(token)) && (char.IsWhiteSpace(token[0])) || char.IsWhiteSpace(token[token
 266                    {
 0267                        token = token.Trim();
 268                    }
 269
 0270                    if ((!string.IsNullOrEmpty(value)) && (char.IsWhiteSpace(value[0]) || char.IsWhiteSpace(value[value.
 271                    {
 0272                        value = value.Trim();
 273                    }
 274
 275                    // If there was no value for a key, then consider the signature to be malformed.
 276
 0277                    if (string.IsNullOrEmpty(value))
 278                    {
 0279                        throw new ArgumentException(
 0280                            Resources.InvalidSharedAccessSignature,
 0281                            nameof(sharedAccessSignature));
 282                    }
 283
 284                    // Compare the token against the known signature properties and capture the
 285                    // pair if they are a known attribute.
 286
 0287                    if (string.Compare(SignedResourceFullIdentifierToken, token, StringComparison.OrdinalIgnoreCase) == 
 288                    {
 0289                        parsedValues.Resource = WebUtility.UrlDecode(value);
 290                    }
 0291                    else if (string.Compare(SignedKeyNameToken, token, StringComparison.OrdinalIgnoreCase) == 0)
 292                    {
 0293                        parsedValues.KeyName = WebUtility.UrlDecode(value);
 294                    }
 0295                    else if (string.Compare(SignedExpiryToken, token, StringComparison.OrdinalIgnoreCase) == 0)
 296                    {
 0297                        if (!long.TryParse(WebUtility.UrlDecode(value), out var unixTime))
 298                        {
 0299                            throw new ArgumentException(Resources.InvalidSharedAccessSignature, nameof(sharedAccessSigna
 300                        }
 301
 0302                        parsedValues.ExpirationTime = ConvertFromUnixTime(unixTime);
 303                    }
 304                }
 0305                else if ((slice.Length != 1) || (slice[0] != TokenValuePairDelimiter))
 306                {
 307                    // This wasn't a legal pair and it is not simply a trailing delimiter; consider
 308                    // the signature to be malformed.
 309
 0310                    throw new ArgumentException(Resources.InvalidSharedAccessSignature, nameof(sharedAccessSignature));
 311                }
 312
 0313                tokenPositionModifier = 0;
 0314                lastPosition = currentPosition;
 315            }
 316
 317            // Validate that the required components were able to be parsed from the
 318            // signature.
 319
 0320            if ((string.IsNullOrEmpty(parsedValues.Resource))
 0321                || (string.IsNullOrEmpty(parsedValues.KeyName))
 0322                || (parsedValues.ExpirationTime == default))
 323            {
 0324                throw new ArgumentException(Resources.InvalidSharedAccessSignature, nameof(sharedAccessSignature));
 325            }
 326
 0327            return parsedValues;
 328        }
 329
 330        /// <summary>
 331        ///   Builds the shared access signature value, which can be used as a token for
 332        ///   access to the Service Bus service.
 333        /// </summary>
 334        ///
 335        /// <param name="audience">The audience scope to which this signature applies.</param>
 336        /// <param name="sharedAccessKeyName">The name of the shared access key that the signature should be based on.</
 337        /// <param name="sharedAccessKey">The value of the shared access key for the signature.</param>
 338        /// <param name="expirationTime">The date/time, in UTC, that the signature expires.</param>
 339        ///
 340        /// <returns>The value of the shared access signature.</returns>
 341        ///
 342        private static string BuildSignature(
 343            string audience,
 344            string sharedAccessKeyName,
 345            string sharedAccessKey,
 346            DateTimeOffset expirationTime)
 347        {
 78348            using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(sharedAccessKey)))
 349            {
 78350                var encodedAudience = WebUtility.UrlEncode(audience);
 78351                var expiration = Convert.ToString(ConvertToUnixTime(expirationTime), CultureInfo.InvariantCulture);
 78352                var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes($"{ encodedAudience }\n{ 
 353
 78354                return string.Format(CultureInfo.InvariantCulture, "{0} {1}={2}&{3}={4}&{5}={6}&{7}={8}",
 78355                    AuthenticationTypeToken,
 78356                    SignedResourceToken, encodedAudience,
 78357                    SignatureToken, WebUtility.UrlEncode(signature),
 78358                    SignedExpiryToken, WebUtility.UrlEncode(expiration),
 78359                    SignedKeyNameToken, WebUtility.UrlEncode(sharedAccessKeyName));
 360            }
 78361        }
 362
 363        /// <summary>
 364        ///   Converts a Unix-style timestamp into the corresponding <see cref="DateTimeOffset" />
 365        ///   value.
 366        /// </summary>
 367        ///
 368        /// <param name="unixTime">The timestamp to convert.</param>
 369        ///
 370        /// <returns>The date/time, in UTC, which corresponds to the specified timestamp.</returns>
 371        ///
 372        private static DateTimeOffset ConvertFromUnixTime(long unixTime) =>
 0373            Epoch.AddSeconds(unixTime);
 374
 375        /// <summary>
 376        ///   Converts a <see cref="DateTimeOffset" /> value to the corresponding Unix-style timestamp.
 377        /// </summary>
 378        ///
 379        /// <param name="dateTimeOffset">The date/time to convert.</param>
 380        ///
 381        /// <returns>The Unix-style timestamp which corresponds to the specified date/time.</returns>
 382        ///
 383        private static long ConvertToUnixTime(DateTimeOffset dateTimeOffset) =>
 78384            Convert.ToInt64((dateTimeOffset - Epoch).TotalSeconds);
 385    }
 386}