Skip to content
SDKs · ServerIn developmentView source

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-go

The 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) boolcrypto/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.