answer

package module
v1.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 20, 2026 License: MIT Imports: 3 Imported by: 0

README

validation-answer

Normalizes qualified signature-validation reports (EU DSS behind a signing API) into one stable, wire-ready answer shape — so every service and UI renders the same fields from one place, instead of hand-copying the answer struct per consumer.

go get github.com/gmb-lib/go-validation-answer

Zero dependencies (standard library only). Import path package name: answer.

Why

The upstream validation report is relayed verbatim by the validation service, and its layout varies across API versions: the signer identity may be nested under signerExt or sit at the signature entry's top level; timestamps may be flat (timeStamp, ocspResponceTime — upstream spelling) or nested under info (bestSignatureTime, ocspResponseCreationTime); the included-file list may be ["a.pdf"] or [{"filename":"a.pdf"}], under validatedDocument or at the data level. NormalizeReport reads the known layouts with fallback resolution and produces one answer.

The answer (also the JSON wire contract)

res, err := answer.NormalizeReport(rawReport) // the verbatim {data:{…}} bytes

answer.Validation — document-level verdict (PASSED / INDETERMINATE / FAILED), pass, container form, signed-file names, and signatures[] (answer.Signature: per-signature verdict, format profile, legal-meaning level, signer identity, signing / revocation / max-validity times, warnings, errors). The top-level per-signer fields mirror the first signature for single-signature callers. signatureId / documentId / reportId / validatedAt are caller context, set by the serving side — validatedAt is when the validation actually ran (RFC 3339): validation is time-anchored (revocation can post-date it), so an answer served later than it was produced renders "as of" that moment, never as current.

Rules baked in:

  • Verdicts are mapped, never recomputed: TOTAL-PASSED/TOTAL-FAILED map directly; anything else is INDETERMINATE. The overall verdict fails on any failed signature and passes only when every signature passed and the report's own counts agree.
  • Levels map to legal meaning (QESIGQES, QESEALSEAL, ADESIG*/ADESEAL*AdES); unknown codes pass through visibly rather than being guessed.
  • An organisation's registration number is preferred over a bare serial; a single PDF's placeholder "file list" is dropped rather than surfaced.
  • Timestamps pass through as RFC 3339 (a legacy locale format is converted).
  • In-process extras (SignaturesCount, ValidSignaturesCount, ValidationLevel — e.g. ARCHIVAL_DATA after an archive timestamp) are available on the struct but deliberately off the wire.

The wire key set is pinned by a test (TestWireContractKeys) — changing it is an explicit, reviewed act, never a side effect.

Scope / non-goals

  • No validation calls, no HTTP — bytes in, answer out.
  • No cryptographic interpretation; presence/verdict mapping only.
  • The verbatim report itself is not reshaped or re-served by this package.

Documentation

Overview

Package answer normalizes signature-validation reports into one stable, wire-ready answer shape.

A qualified validation service (EU DSS behind a signing API) relays a rich, verbatim validation report whose field layout varies between API versions — the signer identity may sit under a signerExt sub-object or at the entry's top level, timestamps may be flat or nested under an info object, and the included-file list may be strings or objects. This package reads the variants it knows and maps them onto ONE normalized answer (Validation), which is also the JSON wire contract every consumer serves and renders — so the answer shape lives in exactly one place instead of being hand-copied per service.

The package interprets nothing cryptographic: verdicts are mapped from the report's indications, never recomputed.

Index

Constants

View Source
const (
	VerdictPassed        = "PASSED"
	VerdictIndeterminate = "INDETERMINATE"
	VerdictFailed        = "FAILED"
)

The normalized verdict vocabulary. The provider relays the upstream report verbatim; NormalizeReport maps it onto this stable set so consumers never parse the raw report.

Variables

This section is empty.

Functions

This section is empty.

Types

type Signature

type Signature struct {
	Verdict         string   `json:"verdict"`
	Format          string   `json:"format,omitempty"` // signature profile, e.g. XAdES_BASELINE_LT
	Level           string   `json:"level,omitempty"`  // legal-meaning label, e.g. QES
	Signer          string   `json:"signer,omitempty"`
	SignerSerial    string   `json:"signerSerial,omitempty"`
	Organization    string   `json:"organization,omitempty"`
	SigningTime     string   `json:"signingTime,omitempty"`     // when this signature was created (RFC 3339)
	RevocationTime  string   `json:"revocationTime,omitempty"`  // revocation-data (OCSP) time (RFC 3339)
	MaxValidityTime string   `json:"maxValidityTime,omitempty"` // long-term-validity horizon; may be empty
	Warnings        []string `json:"warnings,omitempty"`
	Errors          []string `json:"errors,omitempty"`
}

Signature is the normalized answer for one signature within a signed document — a container can hold several (parallel co-signatures). Each carries its own party, profile, level, times, and verdict so every signature is rendered, not just the first. The identity fields (Signer/SignerSerial/Organization) are surfaced to the owner on request; keep them out of lean audit events.

type Validation

type Validation struct {
	SignatureID     string      `json:"signatureId,omitempty"`
	DocumentID      string      `json:"documentId,omitempty"`
	Verdict         string      `json:"verdict"`
	Format          string      `json:"format,omitempty"`
	Level           string      `json:"level,omitempty"`
	Signer          string      `json:"signer,omitempty"`
	SignerSerial    string      `json:"signerSerial,omitempty"`
	Organization    string      `json:"organization,omitempty"`
	ContainerForm   string      `json:"containerForm,omitempty"` // "PDF" | "ASiC-E"
	SigningTime     string      `json:"signingTime,omitempty"`
	RevocationTime  string      `json:"revocationTime,omitempty"`
	MaxValidityTime string      `json:"maxValidityTime,omitempty"`
	SignedFiles     []string    `json:"signedFiles,omitempty"` // container data-object names
	Warnings        []string    `json:"warnings,omitempty"`
	Errors          []string    `json:"errors,omitempty"`
	Signatures      []Signature `json:"signatures,omitempty"` // every signature, in report order
	Pass            bool        `json:"pass"`
	ReportID        string      `json:"reportId,omitempty"`
	ValidatedAt     string      `json:"validatedAt,omitempty"` // when the validation ran (RFC 3339)

	// SignaturesCount / ValidSignaturesCount are the report's own totals,
	// used to derive the overall verdict.
	SignaturesCount      int `json:"-"`
	ValidSignaturesCount int `json:"-"`
	// ValidationLevel is the depth the validator reported (e.g. ARCHIVAL_DATA
	// for an archive-timestamped document). Captured for consumers that want
	// to surface preservation state; not yet part of the wire contract.
	ValidationLevel string `json:"-"`
}

Validation is the normalized validation answer for one signed document, and the JSON wire shape consumers exchange. The container-level fields (Verdict, ContainerForm, SignedFiles, Pass) describe the whole document; Signatures lists every signature it holds. The top-level per-signer fields mirror the FIRST signature for single-signature callers — Signatures is the full set.

SignatureID / DocumentID are caller context (which recorded signature or which document was validated — at most one is set), ReportID is the persisted-answer id, and ValidatedAt is when the validation actually ran (RFC 3339) — validation is time-anchored (revocation can post-date it), so an answer served later than it was produced is presented "as of" this moment, never as current. All four are set by the serving side, not by NormalizeReport.

func NormalizeReport

func NormalizeReport(raw []byte) (*Validation, error)

NormalizeReport maps a verbatim provider validation report onto the normalized answer. The overall verdict is derived across all signatures (any failure → FAILED; all passed and all accounted-for → PASSED; otherwise INDETERMINATE). Every signature is normalized into Signatures (a container can hold several parallel co-signatures); the top-level per-signer fields mirror the first one for single-signature callers. Known level codes map to a legal-meaning label; an unknown code passes through rather than being guessed. Facts that different report layouts place differently (signer identity, times, file lists) are resolved by fallback across the known layouts.

Jump to

Keyboard shortcuts

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