Skip to main content

HMAC Auth for GO

This SDK provides easy-to-use functions for authenticating requests to the SafeSky API using HMAC signatures.

Click here to download the SDK

Installation

No external dependencies required! Simply copy the SDK file to your project:

# Copy the SDK to your project
cp safesky_hmac_auth.go /path/to/your/project/

Requirements:

  • Go 1.18+

Quick Start

package main

import (
    "fmt"
    "net/http"
)

// Import the SDK (copy safesky_hmac_auth.go to your project)

func main() {
    // Your SafeSky API key - REPLACE WITH YOUR ACTUAL API KEY
    apiKey := "YOUR_SAFESKY_API_KEY_HERE"

    // Generate authentication headers for a GET request
    headers, err := GenerateAuthHeaders(
        apiKey,
        "GET",
        "https://sandbox-public-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908",
        "", // Empty body for GET requests
    )
    if err != nil {
        panic(err)
    }

    // Use the headers in your HTTP request
    fmt.Println(headers.ToMap())
    // map[Authorization:SS-HMAC Credential=xxx/v1, SignedHeaders=host;x-ss-date;x-ss-nonce, Signature=yyy
    //     X-SS-Date:2025-11-12T14:30:00.123Z
    //     X-SS-Nonce:550e8400-e29b-41d4-a716-446655440000
    //     X-SS-Alg:SS-HMAC-SHA256-V1]
}

API Reference

GenerateAuthHeaders(apiKey, method, url, body string) (*AuthHeaders, error)

Generates all required 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): Request body for POST/PUT requests (empty string for GET)

Returns: *AuthHeaders struct containing all required headers, or error

Example:

headers, err := GenerateAuthHeaders(
    "YOUR_SAFESKY_API_KEY_HERE",
    "POST",
    "https://sandbox-public-api.safesky.app/v1/uav",
    `[{"id":"uav1","lat":50.69,"lng":4.39}]`,
)
DeriveKID(apiKey string) string

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

Parameters:

  • apiKey (string): Your SafeSky API key

Returns: string - The derived KID in base64url format

DeriveHMACKey(apiKey string) []byte

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

Parameters:

  • apiKey (string): Your SafeSky API key

Returns: []byte - The derived HMAC key (32 bytes)

GenerateNonce() string

Generates a cryptographically secure nonce (UUID v4 format).

Returns: string - A UUID v4 nonce

GenerateTimestamp() string

Generates the current timestamp in ISO8601 format.

Returns: string - ISO8601 timestamp (e.g., 2025-11-12T14:30:00.123Z)

BuildCanonicalRequest(method, path, queryString, host, timestamp, nonce, body string) 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

Returns: string - The canonical request string

GenerateSignature(canonicalRequest string, hmacKey []byte) string

Generates HMAC-SHA256 signature for the canonical request.

Parameters:

  • canonicalRequest (string): The canonical request string
  • hmacKey ([]byte): The derived HMAC signing key

Returns: string - The signature in base64 format

AuthHeaders Struct

The AuthHeaders struct contains all authentication headers:

type AuthHeaders struct {
    Authorization string  // The full Authorization header
    XSSDate       string  // X-SS-Date header (timestamp)
    XSSNonce      string  // X-SS-Nonce header (unique nonce)
    XSSAlg        string  // X-SS-Alg header (algorithm identifier)
}

Use ToMap() to convert to a map[string]string for easy use with HTTP clients.

Complete Examples

The SDK includes 5 complete examples in example.go:

  1. GET nearby aircraft - Retrieve traffic in a specific area
  2. POST UAV position - Submit UAV telemetry data
  3. POST UAV with query parameters - Submit UAV and get nearby traffic
  4. POST advisory (GeoJSON) - Publish GeoJSON FeatureCollection with polygon and point
  5. Manual step-by-step - Detailed authentication flow

Run examples with:

go run example.go safesky_hmac_auth.go
Example 1: GET Request
package main

import (
    "fmt"
    "io"
    "net/http"
    "time"
)

func main() {
    apiKey := "YOUR_SAFESKY_API_KEY_HERE"
    url := "https://sandbox-public-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908&rad=20000"

    // Generate auth headers
    authHeaders, err := GenerateAuthHeaders(apiKey, "GET", url, "")
    if err != nil {
        panic(err)
    }

    // Create request
    req, _ := http.NewRequest("GET", url, nil)
    
    // Add headers
    for key, value := range authHeaders.ToMap() {
        req.Header.Set(key, value)
    }
    req.Header.Set("Content-Type", "application/json")

    // Execute
    client := &http.Client{Timeout: 30 * time.Second}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    body, _ := io.ReadAll(resp.Body)
    fmt.Printf("Status: %d\n", resp.StatusCode)
    fmt.Printf("Body: %s\n", string(body))
}
Example 2: POST Request
package main

import (
    "bytes"
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "time"
)

type UAVPosition struct {
    ID           string  `json:"id"`
    Latitude     float64 `json:"latitude"`
    Longitude    float64 `json:"longitude"`
    Altitude     int     `json:"altitude"`
    GroundSpeed  int     `json:"ground_speed"`
    Status       string  `json:"status"`
    LastUpdate   int64   `json:"last_update"`
    CallSign     string  `json:"call_sign"`
    Course       int     `json:"course"`
    VerticalRate int     `json:"vertical_rate"`
}

func main() {
    apiKey := "YOUR_SAFESKY_API_KEY_HERE"
    url := "https://sandbox-public-api.safesky.app/v1/uav"

    // Create UAV data
    uavData := []UAVPosition{{
        ID:           "my_uav_001",
        Latitude:     50.69378,
        Longitude:    4.39201,
        Altitude:     110,
        GroundSpeed:  10,
        Status:       "AIRBORNE",
        LastUpdate:   time.Now().Unix(),
        CallSign:     "Test UAV",
        Course:       250,
        VerticalRate: 5,
    }}

    bodyBytes, _ := json.Marshal(uavData)
    bodyStr := string(bodyBytes)

    // Generate auth headers with body
    authHeaders, err := GenerateAuthHeaders(apiKey, "POST", url, bodyStr)
    if err != nil {
        panic(err)
    }

    // Create request
    req, _ := http.NewRequest("POST", url, bytes.NewBufferString(bodyStr))
    
    // Add headers
    for key, value := range authHeaders.ToMap() {
        req.Header.Set(key, value)
    }
    req.Header.Set("Content-Type", "application/json")

    // Execute
    client := &http.Client{Timeout: 30 * time.Second}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    respBody, _ := io.ReadAll(resp.Body)
    fmt.Printf("Status: %d\n", resp.StatusCode)
    fmt.Printf("Body: %s\n", string(respBody))
}

Error Handling

The GenerateAuthHeaders function returns an error if the URL is invalid:

headers, err := GenerateAuthHeaders(apiKey, "GET", "invalid-url", "")
if err != nil {
    log.Fatalf("Failed to generate headers: %v", err)
}

Thread Safety

All functions in this SDK are stateless and thread-safe. They can be called concurrently from multiple goroutines.

Security Notes

  • Keep your API key secure and never commit it to version control
  • Use environment variables or secure configuration for API keys
  • The SDK uses cryptographically secure random number generation for nonces
  • All cryptographic operations use Go's standard library crypto packages