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 stringbody(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 methodpath(string): Request pathqueryString(string): Query string without leading?host(string): Host header valuetimestamp(string): ISO8601 timestampnonce(string): Unique noncebody(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 stringhmacKey([]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:
- GET nearby aircraft - Retrieve traffic in a specific area
- POST UAV position - Submit UAV telemetry data
- POST UAV with query parameters - Submit UAV and get nearby traffic
- POST advisory (GeoJSON) - Publish GeoJSON FeatureCollection with polygon and point
- 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