Skip to main content

HMAC Auth SDK for C#

C# SDK for authenticating requests to the SafeSky API using HMAC-SHA256 signatures.

Click here to download the SDK

Installation

.NET 8.0+

Add the SDK files to your project:

  • SafeSkyHmacAuth.cs - Core authentication library
  • Program.cs - Example usage (optional)

No external dependencies required - uses only built-in .NET libraries.

NuGet Package (if published)
dotnet add package SafeSky.HmacAuth

Quick Start

using System;
using System.Net.Http;
using System.Threading.Tasks;
using SafeSky.Authentication;

class Program
{
    static async Task Main()
    {
        // Your SafeSky API key
        const string apiKey = "YOUR_SAFESKY_API_KEY_HEREHere";
        
        // Generate authentication headers for a GET request
        var headers = SafeSkyHmacAuth.GenerateAuthHeaders(
            apiKey,
            "GET",
            "https://api.safesky.app/v1/uav?lat=50.6970&lng=4.3908"
        );
        
        // Make authenticated request
        using var client = new HttpClient();
        var request = new HttpRequestMessage(HttpMethod.Get, 
            "https://api.safesky.app/v1/uav?lat=50.6970&lng=4.3908");
        
        foreach (var header in headers.ToDictionary())
        {
            request.Headers.Add(header.Key, header.Value);
        }
        
        var response = await client.SendAsync(request);
        var content = await response.Content.ReadAsStringAsync();
        
        Console.WriteLine(content);
    }
}

Usage Examples

The SDK includes 4 complete examples in Program.cs:

  1. GET nearby aircraft - Retrieve traffic in a specific area
  2. POST UAV position - Submit UAV telemetry data
  3. POST advisory (GeoJSON) - Publish GeoJSON FeatureCollection with polygon and point
  4. Manual step-by-step - Detailed authentication flow
Example 1: GET Request
using System;
using System.Net.Http;
using System.Threading.Tasks;
using SafeSky.Authentication;

async Task GetNearbyAircraft()
{
    const string apiKey = "YOUR_SAFESKY_API_KEY_HERE";
    const string url = "https://api.safesky.app/v1/uav?lat=50.6970&lng=4.3908&rad=20000";
    
    // Generate auth headers
    var authHeaders = SafeSkyHmacAuth.GenerateAuthHeaders(apiKey, "GET", url);
    
    // Make request
    using var client = new HttpClient();
    var request = new HttpRequestMessage(HttpMethod.Get, url);
    
    foreach (var header in authHeaders.ToDictionary())
    {
        request.Headers.Add(header.Key, header.Value);
    }
    request.Headers.Add("Content-Type", "application/json");
    
    var response = await client.SendAsync(request);
    var content = await response.Content.ReadAsStringAsync();
    
    Console.WriteLine($"Status: {(int)response.StatusCode}");
    Console.WriteLine(content);
}
Example 2: POST Request
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using SafeSky.Authentication;

async Task PostUavPosition()
{
    const string apiKey = "YOUR_SAFESKY_API_KEY_HERE";
    const string url = "https://api.safesky.app/v1/uav";
    
    // Build request body
    var bodyData = new[]
    {
        new
        {
            id = "my_uav_001",
            altitude = 110,
            latitude = 50.69378,
            longitude = 4.39201,
            last_update = DateTimeOffset.UtcNow.ToUnixTimeSeconds(),
            status = "AIRBORNE",
            call_sign = "Test UAV",
            ground_speed = 10,
            course = 250,
            vertical_rate = 5
        }
    };
    
    var body = JsonSerializer.Serialize(bodyData);
    
    // Generate auth headers (pass body for POST)
    var authHeaders = SafeSkyHmacAuth.GenerateAuthHeaders(apiKey, "POST", url, body);
    
    // Make request
    using var client = new HttpClient();
    var request = new HttpRequestMessage(HttpMethod.Post, url)
    {
        Content = new StringContent(body, Encoding.UTF8, "application/json")
    };
    
    foreach (var header in authHeaders.ToDictionary())
    {
        request.Headers.Add(header.Key, header.Value);
    }
    
    var response = await client.SendAsync(request);
    Console.WriteLine($"Status: {(int)response.StatusCode}");
}
Example 3: POST Advisory (GeoJSON)
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using SafeSky.Authentication;

async Task PostAdvisory()
{
    const string apiKey = "YOUR_SAFESKY_API_KEY_HERE";
    const string url = "https://api.safesky.app/v1/advisory";
    
    var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
    
    var bodyData = new
    {
        type = "FeatureCollection",
        features = new object[]
        {
            new
            {
                type = "Feature",
                properties = new
                {
                    id = "my_advisory_id1",
                    max_altitude = 111,
                    last_update = timestamp,
                    call_sign = "Advisory test with polygon",
                    remarks = "Inspection powerlines"
                },
                geometry = new
                {
                    type = "Polygon",
                    coordinates = new[]
                    {
                        new[]
                        {
                            new[] { 4.3948, 50.6831 }, new[] { 4.3952, 50.6832 },
                            // ... more coordinates ...
                            new[] { 4.3948, 50.6831 }
                        }
                    }
                }
            },
            new
            {
                type = "Feature",
                properties = new
                {
                    id = "my_advisory_id2",
                    max_altitude = 150,
                    max_distance = 500,
                    last_update = timestamp,
                    call_sign = "Advisory test with a point",
                    remarks = "Inspection rails"
                },
                geometry = new
                {
                    type = "Point",
                    coordinates = new[] { 4.4, 50.7 }
                }
            }
        }
    };
    
    var body = JsonSerializer.Serialize(bodyData);
    var authHeaders = SafeSkyHmacAuth.GenerateAuthHeaders(apiKey, "POST", url, body);
    
    using var client = new HttpClient();
    var request = new HttpRequestMessage(HttpMethod.Post, url)
    {
        Content = new StringContent(body, Encoding.UTF8, "application/json")
    };
    
    foreach (var header in authHeaders.ToDictionary())
    {
        request.Headers.Add(header.Key, header.Value);
    }
    
    var response = await client.SendAsync(request);
    Console.WriteLine($"Status: {(int)response.StatusCode}");
}
Example 4: Manual Step-by-Step
using System;
using System.Linq;
using SafeSky.Authentication;

void ManualAuthentication()
{
    const string apiKey = "YOUR_SAFESKY_API_KEY_HERE";
    
    // Step 1: Derive KID
    var kid = SafeSkyHmacAuth.DeriveKid(apiKey);
    Console.WriteLine($"KID: {kid}");
    
    // Step 2: Derive HMAC key
    var hmacKey = SafeSkyHmacAuth.DeriveHmacKey(apiKey);
    
    // Step 3: Generate timestamp and nonce
    var timestamp = SafeSkyHmacAuth.GenerateTimestamp();
    var nonce = SafeSkyHmacAuth.GenerateNonce();
    
    // Step 4: Build canonical request
    var canonicalRequest = SafeSkyHmacAuth.BuildCanonicalRequest(
        "GET",
        "/v1/uav",
        "lat=50.6970&lng=4.3908",
        "api.safesky.app",
        timestamp,
        nonce,
        ""
    );
    
    // Step 5: Generate signature
    var signature = SafeSkyHmacAuth.GenerateSignature(canonicalRequest, hmacKey);
    Console.WriteLine($"Signature: {signature}");
}

API Reference

Main Class: SafeSkyHmacAuth

Static class providing HMAC authentication functions.

GenerateAuthHeaders(apiKey, method, url, body = "")

Generates all HMAC authentication headers for a SafeSky API request.

Parameters:

  • apiKey (string): Your SafeSky API key (e.g., ssk_live_...)
  • method (string): HTTP method (GET, POST, etc.)
  • url (string): Full request URL including protocol, host, path, and query string
  • body (string, optional): Request body for POST/PUT requests (default: empty string)

Returns: SafeSkyAuthHeaders containing all required headers

Example:

var headers = SafeSkyHmacAuth.GenerateAuthHeaders(
    "YOUR_SAFESKY_API_KEY_HERE",
    "POST",
    "https://api.safesky.app/v1/uav",
    JsonSerializer.Serialize(data)
);
SafeSkyAuthHeaders Class

Container for authentication headers.

Properties:

  • Authorization (string): Authorization header value
  • XSsDate (string): X-SS-Date header value
  • XSsNonce (string): X-SS-Nonce header value
  • XSsAlg (string): X-SS-Alg header value

Methods:

  • ToDictionary()Dictionary<string, string>: Convert headers to dictionary for HTTP requests
Helper Methods
DeriveKid(apiKey)string

Derives the KID (Key Identifier) from an API key.

Formula: KID = base64url(SHA256("kid:" + api_key)[0:16])

DeriveHmacKey(apiKey)byte[]

Derives the HMAC signing key from an API key using HKDF-SHA256.

GenerateNonce()string

Generates a cryptographically secure nonce (GUID format).

GenerateTimestamp()string

Generates the current timestamp in ISO8601 format with milliseconds.

Format: yyyy-MM-ddTHH:mm:ss.fffZ

BuildCanonicalRequest(...)string

Builds the canonical request string for HMAC signature.

Parameters:

  • method (string): HTTP method
  • path (string): Request path
  • queryString (string): Query string without leading ?
  • host (string): Host header value
  • timestamp (string): ISO8601 timestamp
  • nonce (string): Unique nonce
  • body (string): Request body
GenerateSignature(canonicalRequest, hmacKey)string

Generates HMAC-SHA256 signature for the canonical request.

Returns: Base64-encoded signature

Authentication Flow

  1. Derive KID: KID = base64url(SHA256("kid:" + api_key)[0:16])
  2. Derive HMAC Key: HKDF-SHA256 with salt and info strings
  3. Generate Nonce: GUID for replay protection
  4. Generate Timestamp: ISO8601 format with milliseconds
  5. Build Canonical Request:
    METHOD
    /path
    query_string
    host:hostname:port
    x-ss-date:timestamp
    x-ss-nonce:nonce
    
    body_hash_sha256_hex
    
  6. Sign: HMAC-SHA256(hmac_key, canonical_request) → base64
  7. Build Headers: Authorization header with KID, signed headers, and signature

Running Examples

## Build and run
dotnet run

## Build only
dotnet build

## Run with specific runtime
dotnet run --framework net8.0

Framework Support

  • .NET 8.0+ (recommended)
  • .NET 6.0+ (compatible)
  • .NET Core 3.1+ (compatible)
  • .NET Framework 4.7.2+ (compatible with minor modifications)

ASP.NET Core Integration

using Microsoft.AspNetCore.Mvc;
using SafeSky.Authentication;

[ApiController]
[Route("api/[controller]")]
public class SafeSkyController : ControllerBase
{
    private readonly string _apiKey;
    private readonly HttpClient _httpClient;
    
    public SafeSkyController(IConfiguration config, IHttpClientFactory httpClientFactory)
    {
        _apiKey = config["SafeSky:ApiKey"];
        _httpClient = httpClientFactory.CreateClient();
    }
    
    [HttpGet("aircraft")]
    public async Task<IActionResult> GetNearbyAircraft(double lat, double lng)
    {
        var url = $"https://api.safesky.app/v1/uav?lat={lat}&lng={lng}&rad=20000";
        var headers = SafeSkyHmacAuth.GenerateAuthHeaders(_apiKey, "GET", url);
        
        var request = new HttpRequestMessage(HttpMethod.Get, url);
        foreach (var header in headers.ToDictionary())
        {
            request.Headers.Add(header.Key, header.Value);
        }
        
        var response = await _httpClient.SendAsync(request);
        var content = await response.Content.ReadAsStringAsync();
        
        return Content(content, "application/json");
    }
}

Security Notes

  • Keep API keys secret: Never commit them to version control
  • Use configuration: Store keys in appsettings.json, environment variables, or Azure Key Vault
  • HTTPS only: Always use HTTPS in production (api.safesky.app)
  • Replay protection: Server validates nonces within 15-minute window
  • Clock skew: Server allows ±5 minutes timestamp tolerance

Troubleshooting

Invalid Signature
  • Check body is identical (no extra whitespace)
  • Verify URL includes all query parameters
  • Ensure host header matches URL (include port for non-standard ports)
Replay Attack Detected
  • Nonce was already used within 15-minute window
  • Generate fresh nonce for each request
Timestamp Out of Range
  • Server time differs by more than ±5 minutes
  • Synchronize system clock with NTP

Dependencies

  • System.Security.Cryptography - Built-in (SHA-256, HMAC)
  • System.Net.Http - Built-in (HTTP client)
  • System.Text.Json - Built-in for .NET 8.0+ (JSON serialization in examples)

Support

For questions or issues:

  • Email: support@safesky.app
  • Documentation: https://docs.safesky.app