/// <summary> /// Gets a new Bewit for Single URI authorization /// </summary> /// <param name="host">Host name</param> /// <param name="uri">Request uri</param> /// <param name="credential">Hawk credential</param> /// <param name="ttlSec">Time to live in seconds for the Bewit</param> /// <param name="ext">Extension attributes</param> /// <returns>A fresh Bewit</returns> public static string GetBewit(string host, Uri uri, HawkCredential credential, int ttlSec, string ext = null) { var now = ConvertToUnixTimestamp(DateTime.Now); var expiration = Math.Floor(now) + ttlSec; var mac = CalculateMac(host, "GET", uri, ext, expiration.ToString(), "", credential, "bewit"); var bewit = Convert.ToBase64String( Encoding.UTF8.GetBytes(credential.Id + '\\' + expiration + '\\' + mac + '\\' + ext)); return(bewit); }
/// <summary> /// Authenticates a request message using a bewit /// </summary> /// <param name="bewit"></param> /// <param name="host"></param> /// <param name="uri"></param> /// <param name="credentials"></param> /// <param name="timestampSkewSec"></param> /// <returns></returns> public static IPrincipal AuthenticateBewit(string bewit, string host, Uri uri, Func <string, HawkCredential> credentials, int timestampSkewSec = 60) { if (Trace.CorrelationManager.ActivityId == Guid.Empty) { Trace.CorrelationManager.ActivityId = Guid.NewGuid(); } var bewitParts = ValidateBewit(bewit); HawkCredential credential = null; try { credential = credentials(bewitParts[0]); } catch (Exception ex) { TraceSource.TraceData(TraceEventType.Warning, 0, string.Format("{0} - Unknow user {1} in bewit", Trace.CorrelationManager.ActivityId, bewitParts[0])); throw new SecurityException("Unknown user", ex); } ValidateCredentials(credential); var mac = CalculateMac(uri.Host, "GET", RemoveBewitFromQuery(uri), bewitParts[3], bewitParts[1], "", credential, "bewit"); if (!IsEqual(mac, bewitParts[2])) { TraceSource.TraceData(TraceEventType.Warning, 0, string.Format("{0} - Bad mac in bewit. Received mac {1}. Calculated mac {2}", Trace.CorrelationManager.ActivityId, bewitParts[2], mac)); throw new SecurityException("Bad mac"); } var userClaim = new Claim(ClaimTypes.Name, credential.User); var allClaims = Enumerable.Concat(new Claim[] { userClaim }, (credential.AdditionalClaims != null) ? credential.AdditionalClaims : Enumerable.Empty <Claim>()); var identity = new ClaimsIdentity(allClaims, "Hawk"); var principal = new ClaimsPrincipal(new ClaimsIdentity[] { identity }); return(principal); }
private static void ValidateCredentials(HawkCredential credential) { if (credential == null) { throw new SecurityException("Missing credentials"); } if (string.IsNullOrEmpty(credential.Algorithm) || string.IsNullOrEmpty(credential.Key)) { throw new SecurityException("Invalid credentials"); } if (!SupportedAlgorithms.Any(a => string.Equals(a, credential.Algorithm, StringComparison.InvariantCultureIgnoreCase))) { throw new SecurityException("Unknown algorithm"); } }
/// <summary> /// Generates a mac hash using the supplied payload and credentials /// </summary> /// <param name="payload"></param> /// <param name="credential"></param> /// <returns></returns> public static string CalculatePayloadHash(string payload, string mediaType, HawkCredential credential) { var normalized = "hawk.1.payload\n" + mediaType + "\n" + payload + "\n"; TraceSource.TraceInformation(string.Format("Normalized Payload String: {0}", normalized)); var algorithm = HashAlgorithm.Create(credential.Algorithm); var encodedMac = Convert.ToBase64String(algorithm .ComputeHash(Encoding.UTF8.GetBytes(normalized))); TraceSource.TraceInformation(string.Format("Calculated payload hash: {0}", encodedMac)); return(encodedMac); }
/// <summary> /// Computes a mac following the Hawk rules /// </summary> /// <param name="host">Host header</param> /// <param name="method">Request method</param> /// <param name="uri">Request uri</param> /// <param name="ext">Extesion attribute</param> /// <param name="ts">Timestamp</param> /// <param name="nonce">Nonce</param> /// <param name="credential">Credential</param> /// <param name="payload">Hash of the request payload</param> /// <returns>Generated mac</returns> public static string CalculateMac(string host, string method, Uri uri, string ext, string ts, string nonce, HawkCredential credential, string type, string payloadHash = null) { HMAC hmac = null; if (credential.Algorithm.Equals("sha1", StringComparison.InvariantCultureIgnoreCase)) { hmac = new HMACSHA1(); } else if (credential.Algorithm.Equals("sha256", StringComparison.InvariantCultureIgnoreCase)) { hmac = new HMACSHA256(); } else { throw new Exception("Not supported algorithm"); } hmac.Key = Encoding.UTF8.GetBytes(credential.Key); var sanitizedHost = (host.IndexOf(':') > 0) ? host.Substring(0, host.IndexOf(':')) : host; var normalized = "hawk.1." + type + "\n" + ts + "\n" + nonce + "\n" + method.ToUpper() + "\n" + uri.PathAndQuery + "\n" + sanitizedHost + "\n" + uri.Port.ToString() + "\n" + ((!string.IsNullOrEmpty(payloadHash)) ? payloadHash : "") + "\n" + ((!string.IsNullOrEmpty(ext)) ? ext : "") + "\n"; TraceSource.TraceInformation(string.Format("Normalized String: {0}", normalized)); var messageBytes = Encoding.UTF8.GetBytes(normalized); var mac = hmac.ComputeHash(messageBytes); var encodedMac = Convert.ToBase64String(mac); TraceSource.TraceInformation(string.Format("Calculated mac: {0}", encodedMac)); return(encodedMac); }
/// <summary> /// Authenticates an upcoming request message /// </summary> /// <param name="authorization">Authorization header</param> /// <param name="host">Host header</param> /// <param name="method">Request method</param> /// <param name="uri">Request Uri</param> /// <param name="credentials">A method for searching across the available credentials</param> /// <param name="timestampSkewSec">Accepted Time skew for timestamp verification</param> /// <param name="payloadHash">Hash of the request payload</param> /// <returns></returns> public static async Task <IPrincipal> AuthenticateAsync(string authorization, string host, string method, Uri uri, Func <string, Task <HawkCredential> > credentials, int timestampSkewSec = 60, Func <Task <string> > requestPayload = null, string mediaType = null) { if (Trace.CorrelationManager.ActivityId == Guid.Empty) { Trace.CorrelationManager.ActivityId = Guid.NewGuid(); } TraceSource.TraceInformation(string.Format("{0} - Received Auth header: {1}", Trace.CorrelationManager.ActivityId, authorization)); if (string.IsNullOrEmpty(authorization)) { TraceSource.TraceData(TraceEventType.Warning, 0, string.Format("{0} - Authorization parameter can not be null or empty", Trace.CorrelationManager.ActivityId)); throw new ArgumentException("Authorization parameter can not be null or empty", "authorization"); } if (string.IsNullOrEmpty(host)) { TraceSource.TraceData(TraceEventType.Warning, 0, string.Format("{0} - Host header can not be null or empty", Trace.CorrelationManager.ActivityId)); throw new ArgumentException("Host header can not be null or empty", "host"); } var attributes = ParseAttributes(authorization); ValidateAttributes(timestampSkewSec, attributes); HawkCredential credential = null; try { credential = await credentials(attributes["id"]); } catch (Exception ex) { TraceSource.TraceData(TraceEventType.Warning, 0, string.Format("{0} - Unknown user", Trace.CorrelationManager.ActivityId)); throw new SecurityException("Unknown user", ex); } ValidateCredentials(credential); if (!string.IsNullOrEmpty(attributes["hash"])) { if (requestPayload != null && string.IsNullOrEmpty(mediaType)) { TraceSource.TraceData(TraceEventType.Warning, 0, string.Format("{0} - Media Type can not be null when the payload hash must be calculated", Trace.CorrelationManager.ActivityId)); throw new ArgumentException("MediaType can not be null or empty", "mediaType"); } var hash = CalculatePayloadHash(await requestPayload(), mediaType, credential); if (attributes["hash"] != hash) { TraceSource.TraceData(TraceEventType.Warning, 0, string.Format("{0} - Bad payload hash. Received hash {1}. Calculated hash {2}", Trace.CorrelationManager.ActivityId, attributes["hash"], hash)); throw new SecurityException("Bad payload hash"); } } var mac = CalculateMac(host, method, uri, attributes["ext"], attributes["ts"], attributes["nonce"], credential, "header", attributes["hash"]); if (!IsEqual(mac, attributes["mac"])) { TraceSource.TraceData(TraceEventType.Warning, 0, string.Format("{0} - Bad Mac. Received mac {1}. Calculated Mac {2}", Trace.CorrelationManager.ActivityId, attributes["mac"], mac)); throw new SecurityException("Bad mac"); } var userClaim = new Claim(ClaimTypes.Name, credential.User); var allClaims = Enumerable.Concat(new Claim[] { userClaim }, (credential.AdditionalClaims != null) ? credential.AdditionalClaims : Enumerable.Empty <Claim>()); var identity = new ClaimsIdentity(allClaims, "Hawk"); var principal = new ClaimsPrincipal(new ClaimsIdentity[] { identity }); return(principal); }
/// <summary> /// Creates a new Hawk Authorization header based on the provided parameters /// </summary> /// <param name="host">Host header</param> /// <param name="method">Request method</param> /// <param name="uri">Request uri</param> /// <param name="credential">Credential used to calculate the MAC</param> /// <param name="ext">Optional extension attribute</param> /// <param name="ts">Timestamp</param> /// <param name="nonce">Random Nonce</param> /// <param name="payloadHash">Hash of the request payload</param> /// <param name="type">Type used as header for the normalized string. Default value is 'header'</param> /// <returns>Hawk authorization header</returns> public static string GetAuthorizationHeader(string host, string method, Uri uri, HawkCredential credential, string ext = null, DateTime?ts = null, string nonce = null, string payloadHash = null, string type = null) { if (string.IsNullOrEmpty(host)) { throw new ArgumentException("The host can not be null or empty", "host"); } if (string.IsNullOrEmpty(method)) { throw new ArgumentException("The method can not be null or empty", "method"); } if (credential == null) { throw new ArgumentNullException("The credential can not be null", "credential"); } if (string.IsNullOrEmpty(nonce)) { nonce = GetRandomString(6); } if (string.IsNullOrEmpty(type)) { type = "header"; } var normalizedTs = ((int)Math.Floor((ConvertToUnixTimestamp((ts.HasValue) ? ts.Value : DateTime.UtcNow)))).ToString(); var mac = CalculateMac(host, method, uri, ext, normalizedTs, nonce, credential, type, payloadHash); var authorization = string.Format("id=\"{0}\", ts=\"{1}\", nonce=\"{2}\", mac=\"{3}\", ext=\"{4}\"", credential.Id, normalizedTs, nonce, mac, ext); if (!string.IsNullOrEmpty(payloadHash)) { authorization += string.Format(", hash=\"{0}\"", payloadHash); } return(authorization); }