Skip to main content

HMAC Auth SDK for Java

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

Installation

No external dependencies required for the core library! Uses only Java standard library.

Requirements:

  • Java 17+ (for text blocks and modern APIs)
<!-- For the example file only (optional), add Gson for JSON handling -->
<dependency>
    <groupId>com.google.code.gson</groupId>
    <artifactId>gson</artifactId>
    <version>2.10.1</version>
</dependency>

Quick Start

import java.util.Map;

public class QuickStart {
    public static void main(String[] args) {
        // Your SafeSky API key
        String apiKey = "YOUR_SAFESKY_API_KEY_HERE";
        
        // Generate authentication headers
        Map<String, String> headers = SafeSkyHmacAuth.generateAuthHeaders(
            apiKey,
            "GET",
            "https://sandbox-public-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908",
            // "https://public-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908",  // Production
            null  // body is null for GET requests
        );
        
        // Use with your HTTP client
        // headers contains: Authorization, X-SS-Date, X-SS-Nonce, X-SS-Alg
    }
}

Usage Examples

Example 1: GET Request (Nearby Aircraft) with HttpURLConnection

import java.net.HttpURLConnection;
import java.net.URI;
import java.util.Map;

String apiKey = "YOUR_SAFESKY_API_KEY_HERE";
String url = "https://api.safesky.app/v1/uav?lat=50.6970&lng=4.3908&rad=20000";

// Generate headers
Map<String, String> headers = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "GET", url, null);

// Make request
HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection();
conn.setRequestMethod("GET");

// Add auth headers
for (Map.Entry<String, String> header : headers.entrySet()) {
    conn.setRequestProperty(header.getKey(), header.getValue());
}

int statusCode = conn.getResponseCode();
// Read response...
conn.disconnect();

Example 2: POST Request (Publish UAV Position)

import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.google.gson.Gson;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

String apiKey = "YOUR_SAFESKY_API_KEY_HERE";
String url = "https://api.safesky.app/v1/uav";

// Build JSON body
JsonArray bodyArray = new JsonArray();
JsonObject uav = new JsonObject();
uav.addProperty("id", "my_uav_001");
uav.addProperty("altitude", 110);
uav.addProperty("latitude", 50.69378);
uav.addProperty("longitude", 4.39201);
uav.addProperty("last_update", System.currentTimeMillis() / 1000);
uav.addProperty("status", "AIRBORNE");
uav.addProperty("call_sign", "Test UAV");
uav.addProperty("ground_speed", 10);
uav.addProperty("course", 250);
uav.addProperty("vertical_rate", 5);
bodyArray.add(uav);

String body = new Gson().toJson(bodyArray);

// Generate headers (pass body for POST)
Map<String, String> headers = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "POST", url, body);

// Make request
HttpURLConnection conn = (HttpURLConnection) URI.create(url).toURL().openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setRequestProperty("Content-Type", "application/json");

for (Map.Entry<String, String> header : headers.entrySet()) {
    conn.setRequestProperty(header.getKey(), header.getValue());
}

// Write body
try (OutputStream os = conn.getOutputStream()) {
    byte[] input = body.getBytes(StandardCharsets.UTF_8);
    os.write(input, 0, input.length);
}

int statusCode = conn.getResponseCode();
conn.disconnect();

Complete Examples

The SDK includes 5 complete examples in SafeSkyHmacExample.java:

  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

Example 3: POST with Query Parameters

// URL includes query parameters
String url = "https://api.safesky.app/v1/uav?return_nearby_traffic=true";
String body = /* JSON body */;

// Query params are automatically included in signature
Map<String, String> headers = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "POST", url, body);

Example 4: POST Advisory (GeoJSON)

String url = "https://api.safesky.app/v1/advisory";
long timestamp = System.currentTimeMillis() / 1000;

String body = """
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "id": "advisory_001",
        "max_altitude": 111,
        "last_update": %d,
        "call_sign": "Test Advisory",
        "remarks": "Testing"
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [[
          [4.38, 50.69],
          [4.40, 50.69],
          [4.40, 50.70],
          [4.38, 50.70],
          [4.38, 50.69]
        ]]
      }
    }
  ]
}
""".formatted(timestamp);

Map<String, String> headers = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "POST", url, body);

Example 5: Using with Apache HttpClient

import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import java.util.Map;

String apiKey = "YOUR_SAFESKY_API_KEY_HERE";
String url = "https://api.safesky.app/v1/uav?lat=50.6970&lng=4.3908";

Map<String, String> headers = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "GET", url, null);

try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
    HttpGet request = new HttpGet(url);
    
    // Add auth headers
    for (Map.Entry<String, String> header : headers.entrySet()) {
        request.addHeader(header.getKey(), header.getValue());
    }
    
    httpClient.execute(request);
}

Example 6: Using with OkHttp

import okhttp3.*;
import java.util.Map;

String apiKey = "YOUR_SAFESKY_API_KEY_HERE";
String url = "https://api.safesky.app/v1/uav?lat=50.6970&lng=4.3908";

Map<String, String> headers = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "GET", url, null);

OkHttpClient client = new OkHttpClient();

Request.Builder requestBuilder = new Request.Builder()
    .url(url)
    .get();

// Add auth headers
for (Map.Entry<String, String> header : headers.entrySet()) {
    requestBuilder.addHeader(header.getKey(), header.getValue());
}

Response response = client.newCall(requestBuilder.build()).execute();

Example 7: Using with Spring RestTemplate

import org.springframework.http.*;
import org.springframework.web.client.RestTemplate;
import java.util.Map;

String apiKey = "YOUR_SAFESKY_API_KEY_HERE";
String url = "https://api.safesky.app/v1/uav?lat=50.6970&lng=4.3908";

Map<String, String> authHeaders = SafeSkyHmacAuth.generateAuthHeaders(apiKey, "GET", url, null);

RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();

// Add auth headers
authHeaders.forEach(headers::add);

HttpEntity<String> entity = new HttpEntity<>(headers);
ResponseEntity<String> response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);

API Reference

generateAuthHeaders(apiKey, method, url, body)Map<String, String>

Main function - Generates all HMAC authentication headers.

Parameters:

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

Returns: Map with authentication headers:

{
    "Authorization": "SS-HMAC Credential=...",
    "X-SS-Date": "2025-11-12T14:30:00.123Z",
    "X-SS-Nonce": "uuid-v4",
    "X-SS-Alg": "SS-HMAC-SHA256-V1"
}

Lower-Level Functions

For advanced use cases, you can use individual static methods:

  • deriveKid(String apiKey)String - Derives Key Identifier from API key
  • deriveHmacKey(String apiKey)byte[] - Derives HMAC signing key using HKDF
  • buildCanonicalRequest(...)String - Builds canonical request string
  • generateSignature(String canonicalRequest, byte[] hmacKey)String - Generates HMAC-SHA256 signature
  • generateNonce()String - Generates UUID v4 nonce
  • generateTimestamp()String - Generates ISO8601 timestamp with milliseconds

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: UUID v4 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

Compiling and Running

Compile the library:

javac SafeSkyHmacAuth.java

Compile and run the example (requires Gson):

# Download Gson jar
wget https://repo1.maven.org/maven2/com/google/code/gson/gson/2.10.1/gson-2.10.1.jar

# Compile
javac -cp gson-2.10.1.jar SafeSkyHmacAuth.java SafeSkyHmacExample.java

# Run
java -cp .:gson-2.10.1.jar SafeSkyHmacExample

Using with Maven:

Add to your pom.xml:

<dependencies>
    <!-- Optional: Only needed if using Gson for JSON handling -->
    <dependency>
        <groupId>com.google.code.gson</groupId>
        <artifactId>gson</artifactId>
        <version>2.10.1</version>
    </dependency>
</dependencies>

Security Notes

  • Keep API keys secret: Never commit them to version control
  • Use environment variables: Store keys in secure configuration
  • 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, encoding issues)
  • URL includes all query parameters
  • Host header matches URL (include port for non-standard ports)

Replay Attack Detected

  • Nonce was already used within 15-minute window
  • Generate a fresh nonce for each request

Timestamp Out of Range

  • Server time differs by more than ±5 minutes
  • Synchronize your system clock with NTP

Authentication Failed

  • Verify API key is correct (starts with ssk_live_ or ssk_test_)
  • Check API key has required permissions for the endpoint
  • Ensure all headers are present

Running Examples

# Make sure server is running on localhost:8080 for local testing
# or update API_ENDPOINT to https://api.safesky.app for production

java -cp .:gson-2.10.1.jar SafeSkyHmacExample

Support

For questions or issues, contact SafeSky API support.

License