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 libraryProgram.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:
- GET nearby aircraft - Retrieve traffic in a specific area
- POST UAV position - Submit UAV telemetry data
- POST advisory (GeoJSON) - Publish GeoJSON FeatureCollection with polygon and point
- 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 stringbody(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:
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 methodpath(string): Request pathqueryString(string): Query string without leading?host(string): Host header valuetimestamp(string): ISO8601 timestampnonce(string): Unique noncebody(string): Request body
########
GenerateSignature(canonicalRequest, hmacKey) → string
Generates HMAC-SHA256 signature for the canonical request.
Returns: Base64-encoded signature
Authentication Flow
- Derive KID:
KID = base64url(SHA256("kid:" + api_key)[0:16]) - Derive HMAC Key: HKDF-SHA256 with salt and info strings
- Generate Nonce: GUID for replay protection
- Generate Timestamp: ISO8601 format with milliseconds
- Build Canonical Request:
METHOD /path query_string host:hostname:port x-ss-date:timestamp x-ss-nonce:nonce body_hash_sha256_hex - Sign:
HMAC-SHA256(hmac_key, canonical_request)→ base64 - 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