sdkey

package module
v0.4.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 31, 2026 License: MIT Imports: 15 Imported by: 0

README

sdkey-go

Official Go client for SDKey license authentication.

Implements the sealed session protocol: Ed25519-verified handshake, HKDF session keys, and AES-256-GCM envelopes for validate and register/login/upgrade. See PROTOCOL.md.

Install

go get github.com/SDKeyDev/sdkey-go@v0.4.0

Requires Go 1.22+.

Quick start

Embed these values from the SDKey dashboard when you ship your app. AppVersion must exactly match the application's configured version or session init returns APP_OUTDATED.

package main

import (
	"context"
	"fmt"
	"log"

	"github.com/SDKeyDev/sdkey-go"
)

func main() {
	client := sdkey.NewClient(sdkey.ClientOptions{
		APIBaseURL:      "https://api.sdkey.dev",
		AppID:           "YOUR_APP_ID",
		AppVersion:      "1.0.0",
		AppPublicKeyB64: "YOUR_APP_PUBLIC_KEY_BASE64",
	})

	hwid, err := sdkey.GetHardwareID() // desktop; use "" on web
	if err != nil {
		log.Fatal(err)
	}
	result, err := client.Validate(context.Background(), "SDKY-XXXX-XXXX-XXXX-XXXX", hwid)
	if err != nil {
		if se, ok := err.(*sdkey.Error); ok {
			log.Fatalf("%s %s (server=%s)", se.Code, se.Message, se.ServerCode)
		}
		log.Fatal(err)
	}
	if result.Success {
		fmt.Println("licensed", result.Status, result.SubscriptionTier, result.Message)
	} else {
		fmt.Println("denied", result.Code, result.Message)
	}
}

Validate / Register / Login / Upgrade call Init automatically when no session exists. Sessions last ~15 minutes server-side; on SESSION_EXPIRED the client clears local state so the next call re-handshakes.

Hardware ID

GetHardwareID() reads a stable OS machine identifier (Windows MachineGuid, Linux /etc/machine-id, macOS IOPlatformUUID), SHA-256-hashes the trimmed UTF-8 bytes, and returns lowercase hex. It is opt-in — pass the result into Validate / Register / Login / Upgrade for desktop binding; pass "" on web so the server skips HWID lock, mismatch, and HWID-ban checks (IP bans still apply). On unsupported platforms or missing IDs it returns *Error with code HWID_UNAVAILABLE (never invents a random ID).

Client auth (register / login / upgrade)

These calls use the same sealed wire model as validate: outer { sessionId, ivB64, ciphertextB64, tagB64 }. Application binding comes from the crypto session — do not put appId / clientVersion in the inner body. With CRYPTO_ENFORCE=true, plaintext client-auth bodies are rejected (CRYPTO_REQUIRED).

reg, err := client.Register(context.Background(), sdkey.RegisterOptions{
	Username:   "player1",
	Password:   "password123",
	LicenseKey: "SDKY-XXXX-XXXX-XXXX-XXXX", // may be required by app settings
	HWID:       "", // optional; sdkey.GetHardwareID() on desktop
})
if err != nil {
	log.Fatal(err)
}
if !reg.Success {
	log.Fatalf("%s: %s", reg.Code, reg.Error) // Error maps sealed plaintext message
}
fmt.Println("session", reg.SessionToken, reg.ExpiresAt)

login, err := client.Login(context.Background(), sdkey.LoginOptions{
	Username: "player1",
	Password: "password123",
})
_ = login

// Upgrade: username + license key only (no password). New tier must be strictly higher.
up, err := client.Upgrade(context.Background(), sdkey.UpgradeOptions{
	Username:   "player1",
	LicenseKey: "SDKY-YYYY-YYYY-YYYY-YYYY",
})
_ = up

The opaque sessionToken from auth is not the crypto sessionId used for sealed envelopes.

Where message vs error appears

Per-app responseMessages may customize these strings. The SDK returns them as-is.

Surface Success text field Failure text field
Session init (none) error (on *sdkey.Error.Message, plus ServerCode)
Sealed validate message message
Sealed register/login/upgrade message (wire) messageClientAuthResult.Error
Sealed validate success
{
  "success": true,
  "code": "OK",
  "message": "validated",
  "status": "active",
  "expiresAt": null,
  "subscriptionTier": 0,
  "sessionId": "...",
  "timestamp": 1720000001,
  "v": 1
}
Sealed validate failure
{
  "success": false,
  "code": "HWID_MISMATCH",
  "message": "Hardware ID mismatch",
  "status": null,
  "expiresAt": null,
  "sessionId": "...",
  "timestamp": 1720000001,
  "v": 1
}
Sealed client-auth failure
{
  "success": false,
  "code": "TIER_NOT_HIGHER",
  "message": "License tier must be higher than the current tier",
  "sessionId": "...",
  "timestamp": 1720000001,
  "v": 1
}
Session init failure
{
  "success": false,
  "error": "Client version outdated",
  "code": "APP_OUTDATED"
}

API

sdkey.NewClient(opts)
Option Type Description
APIBaseURL string API origin (no trailing slash)
AppID string Application UUID
AppVersion string Exact app version → sent as clientVersion on session init
AppPublicKeyB64 string Raw Ed25519 public key (32 bytes), base64
HTTPPost HTTPPost Optional HTTP POST override (tests / custom transport)
Methods
  • Init(ctx) — challenge handshake; verifies the signed hello; derives the AES session key
  • Validate(ctx, licenseKey, hwid) — sealed validate; empty hwid omits the field; always decrypts then verifies Ed25519 before trusting success
  • Register(ctx, opts) / Login(ctx, opts) / Upgrade(ctx, opts) — sealed /api/v1/client/* (same envelope + verify order as validate)
  • GetHardwareID() — SHA-256 hex of a stable OS machine ID (desktop opt-in)
  • Session() / GetSession() / ClearSession() — inspect or drop the local crypto session
Errors

Protocol / transport failures return *sdkey.Error with a Code and optional ServerCode:

INIT_FAILED · HELLO_SIGNATURE_INVALID · VALIDATE_RESPONSE_INVALID · RESPONSE_SIGNATURE_INVALID · SESSION_MISMATCH · CLOCK_SKEW · AUTH_FAILED · NETWORK · HWID_UNAVAILABLE

License denials return a normal ValidateResult with Success=false. Auth business failures return ClientAuthResult with Success=false and server Code / Error (from sealed message).

This package does not implement developer tooling / Bearer (sdk_live_…) management APIs.

Security notes

  • Never ship app private keys in a client.
  • Do not skip signature verification — that is the anti-spoof binding.
  • This package is open source; the SDKey server remains a separate product.

Breaking change (v0.4.0)

Plaintext register/login/upgrade bodies are no longer sent. Production APIs with CRYPTO_ENFORCE=true reject plaintext client-auth with CRYPTO_REQUIRED.

Development

go test ./...

License

MIT

Documentation

Overview

Package sdkey is the official Go client for SDKey license authentication.

It implements the sealed session protocol (Ed25519-verified handshake, HKDF session keys, AES-256-GCM envelopes) for validate and client auth (register / login / upgrade). See PROTOCOL.md.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetHardwareID added in v0.3.0

func GetHardwareID() (string, error)

GetHardwareID returns a SHA-256 hex digest of a stable OS machine identifier.

It reads a platform-specific machine ID (Windows MachineGuid, Linux machine-id, or macOS IOPlatformUUID), trims whitespace, hashes the UTF-8 bytes with SHA-256, and returns lowercase hex (64 characters).

Opt-in only — pass the result to Validate / Register / Login / Upgrade when binding a license to a machine. Omit (empty string) for web clients.

Returns *Error with Code HWID_UNAVAILABLE if the platform is unsupported or the machine ID is missing/empty. Does not invent a random ID.

Types

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client is the SDKey license client.

Flow: Init (session handshake) → sealed Validate / Register / Login / Upgrade. Sealed operations call Init automatically when no session exists.

func NewClient

func NewClient(opts ClientOptions) *Client

NewClient constructs a Client from options.

func (*Client) ClearSession

func (c *Client) ClearSession()

ClearSession drops the current session (next sealed call will re-init).

func (*Client) GetSession

func (c *Client) GetSession() *SessionState

GetSession is an alias for Session (API parity with Python get_session).

func (*Client) Init

func (c *Client) Init(ctx context.Context) (*SessionState, error)

Init performs the challenge handshake, verifies the signed hello, and derives the AES session key. Sends clientVersion from AppVersion. Failures surface the server error string and code.

func (*Client) Login added in v0.2.0

func (c *Client) Login(ctx context.Context, opts LoginOptions) (*ClientAuthResult, error)

Login authenticates via sealed POST /api/v1/client/login.

func (*Client) Register added in v0.2.0

func (c *Client) Register(ctx context.Context, opts RegisterOptions) (*ClientAuthResult, error)

Register creates a user account via sealed POST /api/v1/client/register. Business failures return ClientAuthResult with Success=false and server Code/Error (from message).

func (*Client) Session

func (c *Client) Session() *SessionState

Session returns the active session, if any.

func (*Client) Upgrade added in v0.2.0

func (c *Client) Upgrade(ctx context.Context, opts UpgradeOptions) (*ClientAuthResult, error)

Upgrade links a higher-tier license via sealed POST /api/v1/client/upgrade. Sends username + licenseKey only (no password).

func (*Client) Validate

func (c *Client) Validate(ctx context.Context, licenseKey, hwid string) (*ValidateResult, error)

Validate sealed-validates a license key. Pass an empty hwid to omit the HWID field (web clients). When present, HWID lock/mismatch/ban apply. Always decrypts then verifies the Ed25519 signature before trusting success.

type ClientAuthResult added in v0.2.0

type ClientAuthResult struct {
	Success      bool
	SessionToken string
	ExpiresAt    string
	User         *ClientUser
	License      *ClientLicense
	Session      *ClientSessionInfo
	// Code and Error are set on failure (server code + sealed plaintext message).
	Code  string
	Error string
}

ClientAuthResult is a sealed register/login/upgrade response. Failures map sealed plaintext message into Error (API field name unchanged).

type ClientLicense added in v0.2.0

type ClientLicense struct {
	ID               string
	Status           string
	ExpiresAt        *string
	SubscriptionTier int
}

ClientLicense is the linked license on an auth success (may be nil).

type ClientOptions

type ClientOptions struct {
	// APIBaseURL is the API origin (trailing slashes are stripped).
	APIBaseURL string
	// AppID is the application UUID from the SDKey dashboard.
	AppID string
	// AppVersion is the exact application version string. Sent as clientVersion
	// on session init; must match applications.version or the server returns APP_OUTDATED.
	// Sealed client-auth binds the app via the crypto session (no appId/clientVersion in inner body).
	AppVersion string
	// AppPublicKeyB64 is the raw Ed25519 public key (32 bytes), standard or URL-safe base64.
	AppPublicKeyB64 string
	// HTTPPost optionally overrides the default HTTPS JSON transport (tests / custom agents).
	HTTPPost HTTPPost
}

ClientOptions configures NewClient.

type ClientSessionInfo added in v0.2.0

type ClientSessionInfo struct {
	IP   string
	HWID *string
}

ClientSessionInfo is IP/HWID metadata from an auth success.

type ClientUser added in v0.2.0

type ClientUser struct {
	ID            string
	Username      string
	Email         *string
	ApplicationID string
}

ClientUser is the authenticated user returned by register/login/upgrade.

type Error

type Error struct {
	Code       ErrorCode
	Message    string
	ServerCode string // API response "code" when present (e.g. APP_OUTDATED)
	Cause      error
}

Error is a protocol or transport failure. License denials (banned, HWID mismatch, etc.) return ValidateResult, not Error. Client auth business failures return ClientAuthResult with Success=false, not Error.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() error

type ErrorCode

type ErrorCode string

ErrorCode identifies protocol / transport failures (not license denials).

const (
	ErrInitFailed               ErrorCode = "INIT_FAILED"
	ErrHelloSignatureInvalid    ErrorCode = "HELLO_SIGNATURE_INVALID"
	ErrValidateResponseInvalid  ErrorCode = "VALIDATE_RESPONSE_INVALID"
	ErrResponseSignatureInvalid ErrorCode = "RESPONSE_SIGNATURE_INVALID"
	ErrSessionMismatch          ErrorCode = "SESSION_MISMATCH"
	ErrClockSkew                ErrorCode = "CLOCK_SKEW"
	ErrAuthFailed               ErrorCode = "AUTH_FAILED"
	ErrNetwork                  ErrorCode = "NETWORK"
	ErrHWIDUnavailable          ErrorCode = "HWID_UNAVAILABLE"
	ErrUnknown                  ErrorCode = "UNKNOWN"
)

type HTTPPost

type HTTPPost func(ctx context.Context, url string, body map[string]any) (status int, response map[string]any, err error)

HTTPPost posts a JSON body and returns status code plus parsed JSON object. Used for injectable transport in tests and custom HTTP stacks.

type LoginOptions added in v0.2.0

type LoginOptions struct {
	Username string
	Password string
	HWID     string
}

LoginOptions is the sealed login inner body. HWID is omitted from the sealed JSON when empty.

type RegisterOptions added in v0.2.0

type RegisterOptions struct {
	Username   string
	Password   string
	Email      string
	LicenseKey string
	HWID       string
}

RegisterOptions is the sealed register inner body (password required). Optional fields are omitted from the sealed JSON when empty.

type SessionState

type SessionState struct {
	SessionID      string
	AESKey         []byte
	ServerNonceB64 string
	HKDFSaltB64    string
}

SessionState is the active sealed session after a successful Init.

type UpgradeOptions added in v0.2.0

type UpgradeOptions struct {
	Username   string
	LicenseKey string
	HWID       string
}

UpgradeOptions upgrades a user's linked license (username + license key only; no password). HWID is omitted from the sealed JSON when empty.

type ValidateResult

type ValidateResult struct {
	Success          bool
	Code             string
	Message          string
	Status           *string
	ExpiresAt        *string
	SubscriptionTier int
	Timestamp        int64
}

ValidateResult is a decrypted, signature-verified license validate response. License denials use Success=false with a code; they are not SdkeyError. User-facing text for both success and failure is in Message (not Error).

Directories

Path Synopsis
Package crypto implements SDKey sealed-session wire helpers.
Package crypto implements SDKey sealed-session wire helpers.
examples
basic command
Minimal usage example.
Minimal usage example.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL