Go
Dependency-free module over net/http. Construct once; safe for concurrent use.
In development
This SDK is implemented but not yet published to its package registry. Until it ships, collect an opaque evidence blob on the device and appraise it server-side with @rootherald/node (or any available server SDK). The API shown below is the planned surface and may change before release.
terminalbash
go get github.com/RootHerald/sdk-goThe calls
rootherald.NewClient(secretKey, WithBaseURL(…), WithHTTPClient(…))IssueChallengeWithOptions(ctx, ChallengeOptions{Ask, Policy, KeyPurpose, DeviceHint})→Challenge{ChallengeID, Challenge, Nonce, ExpiresAt}.IssueChallenge(ctx, deviceHint)asks for identity + posture.Verify(ctx, evidence, AttestOptions{ChallengeID, Policy, RequestedDisclosureClass})→AttestResult{Verdict, Device, AssuranceClaimsMet, EnrollmentRequired, Key, Raw}.RelayEnrollWithChallenge(ctx, blob, challengeID)/RelayActivate(ctx, activation).VerifyKeySignature(jwk, message, signature) bool—crypto/ecdsa, raw or DER, never panics.- Sentinels for
errors.Is:ErrInvalidSecretKey,ErrChallenge,ErrInvalidEvidence,ErrUnknownPolicy,ErrPolicyDowngrade,ErrAdmissionRefused,ErrQuotaExceeded.
Example
main.gogo
client, err := rootherald.NewClient(os.Getenv("RH_SECRET_KEY"))
if err != nil { log.Fatal(err) }
http.HandleFunc("/api/challenge", func(w http.ResponseWriter, r *http.Request) {
chal, err := client.IssueChallengeWithOptions(r.Context(), rootherald.ChallengeOptions{
Ask: []rootherald.Ask{rootherald.AskIdentity, rootherald.AskPosture},
Policy: "rootherald:builtin:strict-hardware",
})
if err != nil { http.Error(w, err.Error(), 502); return }
json.NewEncoder(w).Encode(chal) // the client reads ChallengeID and Challenge
})
http.HandleFunc("/api/verify", func(w http.ResponseWriter, r *http.Request) {
var body struct {
ChallengeID string `json:"challengeId"`
Evidence json.RawMessage `json:"evidence"`
}
json.NewDecoder(r.Body).Decode(&body)
res, err := client.Verify(r.Context(), body.Evidence, rootherald.AttestOptions{ChallengeID: body.ChallengeID})
switch {
case errors.Is(err, rootherald.ErrQuotaExceeded):
http.Error(w, "quota", 429); return
case err != nil:
http.Error(w, err.Error(), 502); return
}
if res.EnrollmentRequired { http.Error(w, "enrollment_required", 409); return }
if res.Verdict != rootherald.VerdictAllow { http.Error(w, "device_rejected", 403); return }
if res.Key != nil { keys.Put(res.Device.UEID, res.Key.JWK) } // only for a key ask, only on pass
json.NewEncoder(w).Encode(map[string]string{"deviceId": res.Device.UEID})
})A runnable server is at sdk-go/examples/hello.