Verify KDPika payload authenticity in Go without losing the server's signed numeric tokens.
What you will learn
- Build and run the complete Go verifier.
- Reject altered, ambiguous and untrusted envelopes.
- Keep signature checking separate from permission to use a license.
Prerequisites
- Read signed responses.
- Install Go 1.22+; standard library only.
- Download the complete verification source archive and extract it.
- Open a terminal in the extracted security directory; all paths below are relative to it.
- Use Python 3.9+ only for the optional shared test runner, not as a dependency of this verifier.
Install and build
mkdir -p build
go build -o build/go-verify go/verify.go
Expected result: dependencies resolve and the build exits successfully. Version commands print the installed runtime. Use a maintained runtime compatible with the minimum; these are tested compatibility floors, not claims of ongoing vendor support.
The program uses no OS-specific identity or machine hardware identifier. File paths and executable suffixes may differ on Windows; use your shell's path quoting.
Run the positive and negative cases
./build/go-verify vectors/positive.key.json vectors/positive.envelope.json
./build/go-verify vectors/tampered.key.json vectors/tampered.envelope.json
./build/go-verify vectors/expired.key.json vectors/expired.envelope.json
python3 selftest.py --command ./build/go-verify
Expected output for the first three commands is signature_valid=true, signature_valid=false and signature_valid=true. The tampered command exits 1 intentionally; run commands separately rather than stopping a batch at that expected refusal. The shared runner prints PASS23 and exits 0 when every expected acceptance/refusal matches.
Expired remains cryptographically authentic but must not unlock access after its expiry. This program returns signature validity only. It does not replace a complete license client, network transport, offline policy or the official SDK.
Complete source
// KDPika signature authenticity only. Go 1.22+, standard library only.
// Run: go run verify.go key.json envelope.json. Output signature_valid=true/false.
// Independently trust key.json. Check expiry and binding before granting access.
package main
import (
"bytes"
"crypto/ed25519"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
"sort"
"strconv"
"strings"
"unicode/utf8"
)
// encoding/json replaces lone surrogate escapes with U+FFFD. Reject them first
// so malformed input cannot verify as a signed literal replacement character.
func validEscapes(raw []byte) bool {
inString:=false
for i:=0;i<len(raw);i++ {
if raw[i]=='"' { inString=!inString;continue }
if !inString || raw[i]!='\\' { continue }
i++;if i>=len(raw) { return false }
if raw[i]!='u' { continue }
if i+4>=len(raw) { return false }
cp,err:=strconv.ParseUint(string(raw[i+1:i+5]),16,16);if err!=nil { return false };i+=4
if cp>=0xd800 && cp<=0xdbff {
if i+6>=len(raw) || raw[i+1]!='\\' || raw[i+2]!='u' { return false }
low,err:=strconv.ParseUint(string(raw[i+3:i+7]),16,16)
if err!=nil || low<0xdc00 || low>0xdfff { return false };i+=6
} else if cp>=0xdc00 && cp<=0xdfff { return false }
}
return true
}
func parse(raw []byte) (any, error) {
if len(raw) > 1048576 || !utf8.Valid(raw) || !validEscapes(raw) { return nil, fmt.Errorf("invalid JSON size or Unicode") }
decoder := json.NewDecoder(bytes.NewReader(raw)); decoder.UseNumber()
var value func(int) (any, error)
value = func(depth int) (any, error) {
if depth > 64 { return nil, fmt.Errorf("JSON exceeds 64 levels") }
token, err := decoder.Token(); if err != nil { return nil, err }
if delim, ok := token.(json.Delim); ok {
switch delim {
case '{':
result := map[string]any{}
for decoder.More() {
keyToken, err := decoder.Token(); if err != nil { return nil, err }
key, ok := keyToken.(string); if !ok { return nil, fmt.Errorf("invalid key") }
if _, exists := result[key]; exists { return nil, fmt.Errorf("duplicate property") }
child, err := value(depth+1); if err != nil { return nil, err }; result[key] = child
}
end, err := decoder.Token(); if err != nil || end != json.Delim('}') { return nil, fmt.Errorf("invalid object") }
return result, nil
case '[':
result := []any{}
for decoder.More() { child, err := value(depth+1); if err != nil { return nil, err }; result = append(result, child) }
end, err := decoder.Token(); if err != nil || end != json.Delim(']') { return nil, fmt.Errorf("invalid array") }
return result, nil
default: return nil, fmt.Errorf("unexpected delimiter")
}
}
return token, nil
}
result, err := value(0); if err != nil { return nil, err }
if _, err = decoder.Token(); err != io.EOF { return nil, fmt.Errorf("trailing JSON") }
return result, nil
}
func quote(value string) string {
var out bytes.Buffer; encoder := json.NewEncoder(&out); encoder.SetEscapeHTML(false)
if err := encoder.Encode(value); err != nil { panic(err) }; return strings.TrimSuffix(out.String(), "\n")
}
func canonical(value any) string {
switch v := value.(type) {
case map[string]any:
keys := make([]string,0,len(v)); for k := range v { keys=append(keys,k) }; sort.Strings(keys)
parts := make([]string,0,len(keys)); for _,k := range keys { parts=append(parts,quote(k)+":"+canonical(v[k])) }
return "{"+strings.Join(parts,",")+"}"
case []any:
parts:=make([]string,len(v)); for i,child:=range v { parts[i]=canonical(child) }; return "["+strings.Join(parts,",")+"]"
case json.Number: return string(v)
case string: return quote(v)
case bool: if v { return "true" }; return "false"
case nil: return "null"
default: panic("unsupported JSON value")
}
}
func decode(value any, size int) ([]byte,error) {
text,ok:=value.(string); if !ok { return nil,fmt.Errorf("invalid base64url") }
raw,err:=base64.RawURLEncoding.Strict().DecodeString(text)
if err!=nil || len(raw)!=size || base64.RawURLEncoding.EncodeToString(raw)!=text { return nil,fmt.Errorf("invalid base64url length") }; return raw,nil
}
func VerifyEnvelope(keyRaw, envRaw []byte) (valid bool) {
defer func(){ if recover()!=nil { valid=false } }()
k,err:=parse(keyRaw); if err!=nil { return false }; e,err:=parse(envRaw); if err!=nil { return false }
key,ok:=k.(map[string]any); if !ok { return false }; env,ok:=e.(map[string]any); if !ok { return false }
pub,err:=decode(key["public_key"],32); if err!=nil { return false }; sum:=sha256.Sum256(pub); id:="ed25519_"+hex.EncodeToString(sum[:])[:16]
payload,ok:=env["payload"].(map[string]any)
if !ok || key["algorithm"]!="Ed25519" || key["encoding"]!="base64url" || key["key_id"]!=id || env["algorithm"]!="Ed25519" || env["key_id"]!=id { return false }
signature,err:=decode(env["signature"],64); if err!=nil { return false }
return ed25519.Verify(pub,[]byte(canonical(payload)),signature)
}
func main() {
valid:=false
if len(os.Args)==3 {
key,err1:=os.ReadFile(os.Args[1]); env,err2:=os.ReadFile(os.Args[2]); if err1==nil && err2==nil { valid=VerifyEnvelope(key,env) }
}
fmt.Printf("signature_valid=%t\n",valid); if !valid { os.Exit(1) }
}
Expected result
All 23 cases pass in the shared runner, including integral floats, non-BMP key order, Unicode separators, duplicate JSON names, invalid signatures and excessive depth. The original response's numeric tokens are retained, not recalculated from binary floating-point values.
Common mistakes
- Taking the verification key from the same untrusted message being checked.
- Treating a successful signature as proof of active access, correct installation or recent payment.
- Parsing and reserializing numbers before passing the envelope into this sample.
Security notes
The key input must come from your trusted pin or deliberate fixed-origin HTTPS trust policy. Public vector keys are test anchors, never production configuration. This sample fails closed on malformed data; never fall back to unverified features.
After signature success, check product, environment, installation hash, active state, start/expiry, application-version policy and signed validation/grace deadlines. See offline mode. The current official SDKs have documented canonicalization limitations; this independent verifier does not silently repair their caches or alter their public APIs.
Troubleshooting
| Symptom | Cause | Action |
|---|---|---|
| Positive case fails | Wrong trusted key, changed numeric tokens or missing dependency | Restore the exact archive/vector files and rerun the build |
| Tampered case returns false | Expected rejection | Keep the refusal; do not bypass verification |
| Expired case returns true | Authenticity and expiry are separate | Reject access in the license policy layer |
| Cannot open a file | Terminal is outside the extracted security directory | Change to that directory and retry using quoted paths |
Next steps
Run all test vectors and apply the signed-response policy checks.