DAT Go Library

GitHub / Test Code pkg.go.dev

Repository

bash

Requires: Go >= 1.25 (crypto/ecdsa raw key parsing replaced the deprecated elliptic.Marshal family)

Beispiel: DAT Zertifikatverwaltungsdienst

init

go
// logger example
opts := &slog.HandlerOptions{
    Level: slog.LevelDebug,
}
logger := slog.New(slog.NewTextHandler(os.Stdout, opts))

builder, err := dat.NewDatCmsManagerBuilder().
    Url("http://localhost:8088")
if err != nil {
    return err
}

manager, err := builder.
    // IntervalOff(). // disable auto sync
    Interval(60 * time.Second).
    Logger(logger).
    Token("12345678901b").
    Build()

if err != nil {
    return err
}

// Build() hands back the manager even when the first sync failed, so that a
// later tick can still recover. Ask for the failure instead of assuming success.
if syncErr := manager.LastError(); syncErr != nil {
    logger.Warn("certificates are not being refreshed yet", "code", dat.Code(syncErr))
}

// manual sync — returns error
// _ = manager.Sync()

issue / parse

go
plain := "Unicode 유니코드 ユニコード 万国码 يونيكود यूनिकोड Юникод 🦄💻"
secure := "Ciphertext 암호문 暗号文 密文 Шифротекст Texte chiffré Geheimtext نص مشفر सिफरपाठ 🔐"

datStr, err := manager.Issue(plain, secure)
if err != nil {
    return err
}

fmt.Printf("dat: %v\n", datStr)

payload, err := manager.Parse(datStr)
if err != nil {
    return err
}

if plain != payload.PlainText() {
    return fmt.Errorf("plain text mismatch: expected %q, got %q", plain, payload.PlainText())
}
if secure != payload.SecureText() {
    return fmt.Errorf("secure text mismatch: expected %q, got %q", secure, payload.SecureText())
}

fmt.Printf("payload plain: %q\n", payload.PlainText())
fmt.Printf("payload secure: %q\n", payload.SecureText())

Fehlerbehandlung

go
// needs "errors" in the file imports

// 1. Expiry, forgery and a malformed token each need a different response.
//    They used to share one sentinel, so callers could not tell them apart.
payload, err := manager.Parse(datStr)
switch {
case err == nil:
    fmt.Printf("plain: %q\n", payload.PlainText())
// Normal end of life. Let the caller refresh and try again.
case errors.Is(err, dat.ErrTokenExpired):
    fmt.Println("expired: ask the client to re-issue")
// DAT_SIG_MISMATCH / DAT_CRYPTO_TAG_MISMATCH — direct evidence of tampering.
case dat.SecurityEvent(err):
    logger.Warn("drop the session", "code", dat.Code(err))
// Anything else is simply a bad request.
default:
    fmt.Printf("rejected %s: %v\n", dat.Code(err), err)
}

// 2. Never retry a permanent failure. A wrong token answers 401 forever.
if err := manager.Sync(); err != nil {
    switch dat.Retry(err) {
    case dat.RetryTransient:
        logger.Info("retrying on the next tick", "code", dat.Code(err))
    case dat.RetryPermanent:
        logger.Error("fix the token, url or deployment", "code", dat.Code(err))
    case dat.RetryState:
        // not a failure — a sync was already running
    }
}

// 3. Sync failures are never returned to the caller of Build(). Poll the
//    manager to notice a stalled rollout.
if err := manager.LastError(); err != nil {
    logger.Error("certificates are not being refreshed", "code", dat.Code(err))
}

Beispiel: Manuelle Implementierung

go
manager := dat.NewManager()

// (cid, issuanceStart, issuanceDuration, datTtl, signatureAlgorithm, cryptoAlgorithm)
now := dat.NowUnixTimestamp()
cert, err := dat.GenerateCertificate(1, now-10, 3600, 1800, dat.EcdsaP256, dat.IvAes256Gcm)
if err != nil {
    t.Fatal(err)
}

_, err = manager.ImportCertificates([]*dat.Certificate{cert}, false)
if err != nil {
    t.Fatal(err)
}

plain := "Unicode 유니코드 ユニコード 万国码 يونيكود यूनिकोड Юникод 🦄💻"
secure := "Ciphertext 암호문 暗号文 密文 Шифротекст Texte chiffré Geheimtext نص مشفر सिफरपाठ 🔐"

datStr, err := manager.Issue(plain, secure)
if err != nil {
    t.Fatal(err)
}

payload, err := manager.Parse(datStr)
if err != nil {
    t.Fatal(err)
}

if payload.PlainText() != plain {
    t.Errorf("expected plain %s, got %s", plain, payload.PlainText())
}
if payload.SecureText() != secure {
    t.Errorf("expected secure %s, got %s", secure, payload.SecureText())
}

println(datStr)
println(payload.PlainText())
println(payload.SecureText())

export

go
// ExportCertificates and GenerateCryptoKey both return an error — a failing RNG
// must never be silently turned into an all-zero key.
certs, err := manager.ExportCertificates()
if err != nil {
    return err
}

crypto, err := dat.GenerateCryptoKey(dat.IvAes256Gcm)
if err != nil {
    return err
}