Documentation
¶
Overview ¶
Package cap reads, checks, writes and signs OASIS Common Alerting Protocol 1.2 alert messages, and reads and writes the Atom and RSS feeds that carry them.
CAP 1.1 and 1.0 alerts (and alerts without a namespace) are read as CAP 1.2, and Validate lists every problem rather than stopping at the first, so an application can store an alert that strays from the schema with its problems noted (Problem.Essential tells which problems leave an alert unusable). Profiles add a community's rules to validation (Options.Profiles): the Canadian profile CAP-CP (CAPCP), the US IPAWS profile (IPAWS), the conventions of India's SACHET feed (India), or the application's own (Profile, NewProfile); Check lists warnings too. ValidateSchema checks a document's structure against the CAP 1.2 schema's content model (schema.go). Signatures are enveloped XML signatures over the whole alert, verified against pinned certificates or chains to trusted roots, with a revocation hook (sign.go, chain.go). Areas convert to GeoJSON (geo.go). ParseEDXLDE reads alerts in EDXL-DE envelopes (edxl.go).
The message model follows the CAP 1.2 schema; no schema or specification text is copied here.
Example ¶
A CAP 1.1 alert is read as 1.2; Validate lists every problem, and Essential tells whether the alert can still be kept.
package main
import (
"fmt"
"github.com/cB-Sigma/cap"
)
func main() {
doc := []byte(`<alert xmlns="urn:oasis:names:tc:emergency:cap:1.1">
<identifier>DEMO-1</identifier><sender>demo@example.test</sender>
<sent>2026-09-24T10:00:00Z</sent><status>Actual</status><msgType>Alert</msgType><scope>Public</scope>
<info><category>Met</category><event>Heavy Rainfall</event><urgency>Expected</urgency>
<severity>Severe</severity><certainty>Likely</certainty>
<area><areaDesc>Demo district</areaDesc><circle>18.52,73.85 5</circle></area></info>
</alert>`)
a, err := cap.Parse(doc)
if err != nil {
panic(err)
}
fmt.Println(a.Version, a.Identifier, a.MostSevere())
if err := a.Validate(cap.Options{}); err != nil {
fmt.Println(err, "- essential:", err.(cap.ValidationError).Essential())
}
fmt.Println(a.Validate(cap.Options{LenientTime: true}) == nil)
}
Output: 1.1 DEMO-1 Severe the CAP alert is not valid: sent: "2026-09-24T10:00:00Z" is not in the form 2026-09-24T10:00:00+05:30 - essential: true true
Index ¶
- Constants
- Variables
- func CRLCheck(crls ...*x509.RevocationList) func(chain []*x509.Certificate) error
- func CirclePolygon(center []float64, radiusKm float64) [][]float64
- func CirclePolygonN(center []float64, radiusKm float64, n int) [][]float64
- func FormatTime(t time.Time) string
- func IndianLanguageTag(code string) (tag string, ok bool)
- func OCSPCheck(o OCSPOptions) func(chain []*x509.Certificate) error
- func ParseCRLs(inputs ...[]byte) ([]*x509.RevocationList, error)
- func ParseCircle(s string) (center []float64, radiusKm float64, err error)
- func ParsePolygon(s string) ([][]float64, error)
- func ParseTime(s string) (time.Time, error)
- func PolygonURL(in Info) string
- func ProfileNames() []string
- func RegisterProfile(name string, make func() Profile)
- func RepairIndia(a *Alert) []string
- func ValidateSchema(doc []byte, extra ...SchemaValidator) error
- func Version(space string) (string, bool)
- func WriteAtom(m FeedMeta, alerts []*Alert, now time.Time) ([]byte, error)
- func WriteEDXLDE(d Distribution, docs ...[]byte) ([]byte, error)
- func WriteEDXLDE2(d Distribution, contents ...EDXLContent) ([]byte, error)
- type Alert
- func (a *Alert) Check(o Options) []Problem
- func (a *Alert) Expires() time.Time
- func (a *Alert) GeoJSON() map[string]any
- func (a *Alert) GeoJSONWith(segments int) map[string]any
- func (a *Alert) Key() string
- func (a *Alert) Marshal() ([]byte, error)
- func (a *Alert) MarshalJSONCAP() ([]byte, error)
- func (a *Alert) MostSevere() string
- func (a *Alert) Validate(o Options) error
- type Area
- type Distribution
- type EDXLContent
- type Entry
- type FeedMeta
- type History
- type Info
- type NamedValue
- type OCSPOptions
- type Options
- type Problem
- type Profile
- type Reference
- type Resource
- type SchemaValidator
- type SchemaValidatorFunc
- type SignSettings
- type Signer
- type ValidationError
- type Verifier
- func (v *Verifier) Required() bool
- func (v *Verifier) Roots() *x509.CertPool
- func (v *Verifier) Trusted() []*x509.Certificate
- func (v *Verifier) Verify(doc []byte) (verified []byte, state string, err error)
- func (v *Verifier) VerifyChain(doc []byte) (verified []byte, state string, chain []*x509.Certificate, err error)
- type VerifySettings
Examples ¶
Constants ¶
const ( Namespace11 = "urn:oasis:names:tc:emergency:cap:1.1" Namespace10 = "http://www.incident.com/cap/1.0" )
Namespaces of the earlier CAP versions, read like CAP 1.2 (Parse).
const ( EDXLDE10Namespace = "urn:oasis:names:tc:emergency:EDXL:DE:1.0" EDXLDE20Namespace = "urn:oasis:names:tc:emergency:EDXL:DE:2.0" )
EDXL-DE namespaces.
const ( SignatureValid = "valid" SignatureNone = "none" )
Signature states.
const AtomNamespace = "http://www.w3.org/2005/Atom"
AtomNamespace is the Atom (RFC 4287) namespace.
const LGDDistrictCode = "LGD District Code"
LGD geocode valueName of SACHET alerts.
const MediaType = "application/cap+xml"
MediaType is the media type of a CAP document.
const Namespace = "urn:oasis:names:tc:emergency:cap:1.2"
Namespace is the CAP 1.2 XML namespace.
const XMLDSigNamespace = "http://www.w3.org/2000/09/xmldsig#"
XMLDSigNamespace is the XML Signature namespace.
Variables ¶
var ( Statuses = []string{"Actual", "Exercise", "System", "Test", "Draft"} MsgTypes = []string{"Alert", "Update", "Cancel", "Ack", "Error"} Scopes = []string{"Public", "Restricted", "Private"} Categories = []string{"Geo", "Met", "Safety", "Security", "Rescue", "Fire", "Health", "Env", "Transport", "Infra", "CBRNE", "Other"} Responses = []string{"Shelter", "Evacuate", "Prepare", "Execute", "Avoid", "Monitor", "Assess", "AllClear", "None"} Urgencies = []string{"Immediate", "Expected", "Future", "Past", "Unknown"} SeverityList = []string{"Extreme", "Severe", "Moderate", "Minor", "Unknown"} Certainties = []string{"Observed", "Likely", "Possible", "Unlikely", "Unknown"} )
Enumerations of the CAP 1.2 schema.
var CAPCPEvents = []string{}/* 147 elements not displayed */
CAPCPEvents are the event codes of the CAP-CP event lists 0.4 and 1.0 (a few are misspelt in the list itself and must be used as listed).
var ErrRevoked = errors.New("certificate revoked")
ErrRevoked is wrapped by the error of a revocation check that found a certificate of the chain revoked.
var ErrUnsigned = errors.New("the alert is not signed and signatures are required")
ErrUnsigned is an unsigned alert where signatures are required.
var IPAWSMediaTypes = []string{"audio/x-ipaws-audio", "audio/x-ipaws-streaming-audio", "video/x-ipaws-video", "video/x-ipaws-streaming-video"}
IPAWSMediaTypes are the mimeType values of the profile for resources of broadcast content.
Functions ¶
func CRLCheck ¶ added in v0.2.0
func CRLCheck(crls ...*x509.RevocationList) func(chain []*x509.Certificate) error
CRLCheck returns a revocation check for Verifier.Revoked that refuses a chain when one of its certificates is listed on one of the CRLs. A CRL counts for a certificate only when it names the certificate's issuer and is signed by the next certificate of the chain; other CRLs are ignored. A CRL past its next update is still used (its entries do not expire); refresh the list and the check to keep it current.
func CirclePolygon ¶
CirclePolygon approximates a circle as a closed GeoJSON ring (spherical Earth, 6371 km).
func CirclePolygonN ¶ added in v0.3.0
CirclePolygonN is CirclePolygon with n sides (at least 3; 32 when n is smaller), for maps that need a finer or coarser outline.
func FormatTime ¶
FormatTime writes t in CAP's date-time form: seconds precision with an explicit offset ("2026-09-24T10:00:00+05:30"); UTC is written "-00:00".
func IndianLanguageTag ¶ added in v0.2.0
IndianLanguageTag turns a SACHET language code into a language tag ("HI" is hi-IN; "PN" is pa-IN, Punjabi; "TL" is te-IN, Telugu, as the script of SACHET's TL blocks shows). ok is false for a code it does not know; a well-formed tag is returned unchanged with ok true.
func OCSPCheck ¶ added in v0.2.0
func OCSPCheck(o OCSPOptions) func(chain []*x509.Certificate) error
OCSPCheck returns a revocation check for Verifier.Revoked that asks the OCSP responder each certificate of the chain names (its Authority Information Access extension) about it, signed by or for its issuer, the next certificate of the chain (RFC 6960, via golang.org/x/crypto/ocsp). A "revoked" answer refuses the chain with ErrRevoked; see OCSPOptions.Strict for the other cases. Combine it with CRLCheck in a function of your own to use both.
func ParseCRLs ¶ added in v0.2.0
func ParseCRLs(inputs ...[]byte) ([]*x509.RevocationList, error)
ParseCRLs reads certificate revocation lists given as PEM ("X509 CRL" blocks, several per input allowed) or DER.
func ParseCircle ¶
ParseCircle reads a CAP circle "lat,lon radius" (radius in km).
func ParsePolygon ¶
ParsePolygon reads a CAP polygon: at least four space-separated "lat,lon" pairs whose first and last are the same point. It returns the ring as GeoJSON positions.
func ParseTime ¶
ParseTime reads a CAP date-time. Strict CAP has seconds precision and a numeric offset; feeds in the field also use "Z" and fractions, which are accepted too.
func PolygonURL ¶ added in v0.2.0
PolygonURL returns the "Polygon URL" parameter of a SACHET info block ("" when there is none): the address of the polygon its areas leave out.
func ProfileNames ¶ added in v0.2.0
func ProfileNames() []string
ProfileNames lists the registered profiles, sorted.
func RegisterProfile ¶ added in v0.2.0
RegisterProfile makes a profile available to ProfileByName (for configurations that name profiles). The built-in profiles are registered as "CAP-CP", "IPAWS" and "India". Registering a name again replaces it.
func RepairIndia ¶ added in v0.2.0
RepairIndia fixes the departures from CAP 1.2 seen in SACHET alerts, in place, and lists what it changed: SACHET language codes become language tags, and a point given as latitude in altitude and longitude in ceiling becomes a circle of radius 0 (altitude and ceiling are cleared). Run it before Validate and GeoJSON.
Example ¶
SACHET alerts use their own language codes and sometimes put a point in altitude and ceiling; RepairIndia turns them into CAP 1.2.
package main
import (
"fmt"
"github.com/cB-Sigma/cap"
)
func main() {
a := &cap.Alert{Info: []cap.Info{{Language: "TL", Area: []cap.Area{{AreaDesc: "River gauge", Altitude: "17.0", Ceiling: "81.8"}}}}}
for _, c := range cap.RepairIndia(a) {
fmt.Println(c)
}
}
Output: info[0].language: TL -> te-IN info[0].area[0]: altitude 17.0 and ceiling 81.8 -> circle 17,81.8 0
func ValidateSchema ¶ added in v0.2.0
func ValidateSchema(doc []byte, extra ...SchemaValidator) error
ValidateSchema checks the document against the CAP 1.2 schema's content model and, when given, each extra validator (a full XSD engine, say), and returns every problem found as a ValidationError (nil when there is none). It checks the document as written: unlike Parse, it does not read CAP 1.1 or 1.0 as 1.2, and reports their namespace instead.
Example ¶
ValidateSchema checks a document's structure against the CAP 1.2 schema's content model, which Parse does not.
package main
import (
"fmt"
"github.com/cB-Sigma/cap"
)
func main() {
doc := []byte(`<alert xmlns="urn:oasis:names:tc:emergency:cap:1.2">
<sender>ops@example.test</sender><identifier>X-1</identifier>
<sent>2026-09-24T10:00:00+05:30</sent><status>Exercise</status><msgType>Alert</msgType><scope>Public</scope>
</alert>`)
_, err := cap.Parse(doc)
fmt.Println("parse:", err)
fmt.Println(cap.ValidateSchema(doc))
}
Output: parse: <nil> the CAP alert is not valid: alert/identifier: is required before sender; alert/identifier: is out of order
func WriteAtom ¶
WriteAtom renders alerts (newest first) as an Atom feed whose entries embed each CAP message and link to its own document.
func WriteEDXLDE ¶ added in v0.2.0
func WriteEDXLDE(d Distribution, docs ...[]byte) ([]byte, error)
WriteEDXLDE writes an EDXL-DE 1.0 envelope with one content object per CAP document, embedded as XML. The documents are embedded byte for byte (their XML declarations dropped), so signed alerts stay verifiable. d's ID, SenderID, Sent, Status and Type are required (Type is Report, Update, Cancel and so on, as EDXL-DE 1.0 lists them); Version and Entries are ignored.
func WriteEDXLDE2 ¶ added in v0.3.0
func WriteEDXLDE2(d Distribution, contents ...EDXLContent) ([]byte, error)
WriteEDXLDE2 writes an EDXL-DE 2.0 envelope: the envelope elements, a descriptor when d.Confidentiality is set ("Unclassified" or "Classified" in 2.0), and one content object per CAP document or link. Embedded documents go in byte for byte (their XML declarations dropped) so signed alerts stay verifiable. d's ID, SenderID, Sent, Status and Type (written as distributionKind: Report, Update, Cancel and so on) are required; Expires, required by 2.0, defaults to the latest expiry of the embedded alerts' info blocks. The xlink:type attributes the 2.0 schema requires are written.
Types ¶
type Alert ¶
type Alert struct {
XMLName xml.Name `xml:"urn:oasis:names:tc:emergency:cap:1.2 alert"`
Identifier string `xml:"identifier"`
Sender string `xml:"sender"`
Sent string `xml:"sent"`
Status string `xml:"status"`
MsgType string `xml:"msgType"`
Source string `xml:"source,omitempty"`
Scope string `xml:"scope"`
Restriction string `xml:"restriction,omitempty"`
Addresses string `xml:"addresses,omitempty"`
Code []string `xml:"code,omitempty"`
Note string `xml:"note,omitempty"`
References string `xml:"references,omitempty"`
Incidents string `xml:"incidents,omitempty"`
Info []Info `xml:"info,omitempty"`
// Version is the CAP version the document was written in ("1.2",
// "1.1", "1.0", or "none" without a namespace); it is always
// marshalled as 1.2.
Version string `xml:"-"`
}
Alert is a CAP 1.2 alert message.
func Parse ¶
Parse decodes a CAP document: CAP 1.2, and also CAP 1.1, 1.0 or an alert without a namespace, read as 1.2 (Alert.Version says which). It checks the root element but not the content; call Validate for that.
func ParseJSON ¶ added in v0.2.0
ParseJSON reads an alert written by MarshalJSONCAP (or any JSON with CAP element names). Like Parse it does not validate; Version is "1.2".
func ParseStrict ¶ added in v0.2.0
func ParseStrict(doc []byte, extra ...SchemaValidator) (*Alert, error)
ParseStrict reads a CAP 1.2 document only when it passes ValidateSchema (and the extra validators): for receivers that refuse a non-conforming document outright instead of reading it and listing its problems.
func (*Alert) Check ¶ added in v0.2.0
Check is Validate that returns every problem, warnings included (nil when there is none).
Example ¶
Profiles add a community's rules: the profiles an alert declares are found with DetectProfiles, Check lists errors and warnings, and Validate fails only on errors.
package main
import (
"fmt"
"github.com/cB-Sigma/cap"
)
func main() {
doc := []byte(`<alert xmlns="urn:oasis:names:tc:emergency:cap:1.2">
<identifier>CA-DEMO-1</identifier><sender>alerts.example.test</sender>
<sent>2026-09-24T10:00:00-04:00</sent><status>Actual</status><msgType>Alert</msgType><scope>Public</scope>
<code>profile:CAP-CP:0.4</code>
<info><language>en-CA</language><category>Met</category><event>rainfall</event>
<urgency>Future</urgency><severity>Moderate</severity><certainty>Likely</certainty>
<eventCode><valueName>profile:CAP-CP:Event:0.4</valueName><value>rainfall</value></eventCode>
<expires>2026-09-25T10:00:00-04:00</expires>
<area><areaDesc>Demo county</areaDesc></area></info>
</alert>`)
a, _ := cap.Parse(doc)
o := cap.Options{Profiles: cap.DetectProfiles(a)}
for _, p := range a.Check(o) {
fmt.Println(p)
}
fmt.Println("valid:", a.Validate(o) == nil)
}
Output: info[0].area[0].geocode: a profile:CAP-CP:Location geocode is required (CAP-CP 0.4 rule 9) warning: info[0].senderName: should be given (CAP-CP 0.4 rule 14) valid: false
func (*Alert) Expires ¶
Expires is the latest expiry of the alert's info blocks, or zero when one of them has none (it stays in effect).
func (*Alert) GeoJSON ¶
GeoJSON renders every area of the alert as a GeoJSON FeatureCollection: one Feature per area, with a Polygon, a MultiPolygon, or no geometry when the area is described by geocodes only. Circles become 32-sided polygons and keep their centre and radius in the properties.
func (*Alert) GeoJSONWith ¶ added in v0.3.0
GeoJSONWith is GeoJSON with circles drawn as polygons of the given number of sides.
func (*Alert) Key ¶
Key identifies a message: sender, identifier and sent together are unique (the same triple a references entry uses).
func (*Alert) MarshalJSONCAP ¶ added in v0.2.0
MarshalJSONCAP encodes the alert as JSON with CAP element names.
func (*Alert) MostSevere ¶
MostSevere is the highest severity among the info blocks ("Unknown" when none says).
func (*Alert) Validate ¶
Validate checks the alert against the CAP 1.2 rules: required elements, enumerations, date-time form, references, identifier characters, the restriction and addresses conditions of the scope, and area geometry; then against each of o.Profiles. It returns the problems that are not warnings as a ValidationError, or nil.
type Area ¶
type Area struct {
AreaDesc string `xml:"areaDesc" json:"areaDesc"`
Polygon []string `xml:"polygon,omitempty" json:"polygon,omitempty"`
Circle []string `xml:"circle,omitempty" json:"circle,omitempty"`
Geocode []NamedValue `xml:"geocode,omitempty" json:"geocode,omitempty"`
Altitude string `xml:"altitude,omitempty" json:"altitude,omitempty"`
Ceiling string `xml:"ceiling,omitempty" json:"ceiling,omitempty"`
}
Area is an affected area. The JSON names are the CAP element names, so an area can be given in a JSON configuration.
type Distribution ¶ added in v0.2.0
type Distribution struct {
// Version is "1.0" or "2.0".
Version string
ID string // distributionID
SenderID string
Sent string // dateTimeSent
// Expires is dateTimeExpires (2.0, where it is required).
Expires string
Status string // distributionStatus
Type string // distributionType
// Confidentiality is combinedConfidentiality (1.0); WriteEDXLDE writes
// "UNCLASSIFIED AND NOT SENSITIVE" when it is empty.
Confidentiality string
// Entries are the CAP alerts the content objects carry, in document
// order: embedded or base64 alerts with Alert and Raw set, alerts given
// by URI with Link set. Each entry's ID is the distribution ID with the
// content object's position ("ID#1" for the first).
Entries []Entry
}
Distribution is an EDXL-DE envelope as far as a CAP reader needs it.
func ParseEDXLDE ¶ added in v0.2.0
func ParseEDXLDE(b []byte) (*Distribution, error)
ParseEDXLDE reads an EDXL-DE 1.0 or 2.0 envelope and the CAP alerts in it. Content that is not CAP is skipped.
Example ¶
CAP alerts in an EDXL-DE envelope are read with their routing facts.
package main
import (
"fmt"
"github.com/cB-Sigma/cap"
)
func main() {
doc := []byte(`<EDXLDistribution xmlns="urn:oasis:names:tc:emergency:EDXL:DE:1.0">
<distributionID>DIST-1</distributionID><senderID>hub@example.test</senderID>
<dateTimeSent>2026-09-24T10:01:00+05:30</dateTimeSent>
<distributionStatus>Exercise</distributionStatus><distributionType>Report</distributionType>
<contentObject><xmlContent><embeddedXMLContent>
<alert xmlns="urn:oasis:names:tc:emergency:cap:1.2"><identifier>X-1</identifier><sender>ops@example.test</sender>
<sent>2026-09-24T10:00:00+05:30</sent><status>Exercise</status><msgType>Alert</msgType><scope>Public</scope></alert>
</embeddedXMLContent></xmlContent></contentObject>
</EDXLDistribution>`)
d, err := cap.ParseEDXLDE(doc)
if err != nil {
panic(err)
}
fmt.Println(d.Version, d.ID, d.SenderID, len(d.Entries), d.Entries[0].Alert.Identifier)
}
Output: 1.0 DIST-1 hub@example.test 1 X-1
type EDXLContent ¶ added in v0.3.0
type EDXLContent struct {
Doc []byte
Base64 bool
Link string
Size int64
Digest []byte
Description string // contentDescription
Language string // contentLanguage
}
EDXLContent is one content object of an EDXL-DE 2.0 envelope written by WriteEDXLDE2: a CAP document embedded as XML (Doc), the same carried in base64 (Doc with Base64), or an alert given by its URI (Link, with Size and Digest, the SHA-1 of the document, when known).
type Entry ¶
type Entry struct {
ID string
Updated string
Title string
Link string // the CAP document's URL when it is not embedded
Alert *Alert // embedded alert, if any
// Raw is the embedded alert element's text (for signature checks); a
// bare CAP document's Raw is the whole document.
Raw []byte
}
Entry is one feed entry: an embedded CAP alert, or a link to one.
type FeedMeta ¶
type FeedMeta struct {
ID, Title, Author, Self string
// AlertURL gives the URL a CAP message is also served at.
AlertURL func(identifier string) string
}
FeedMeta describes a published Atom feed.
type History ¶ added in v0.2.0
type History struct {
// contains filtered or unexported fields
}
History remembers the messages a receiver has seen, to check a rule that one message cannot show on its own: CAP-CP (0.4 rule 12, 1.0 rule 9) and the IPAWS profile want an Update or Cancel to reference every message of its chain that is still in effect. A chain is the set of messages joined by references; a message is in effect until the latest expiry of its info blocks, and for good when one has none.
Add every message received (valid or not, in any order); Missing tells what a new message leaves out; Profile wraps that as a profile for Options.Profiles. A History is safe for concurrent use.
func (*History) Add ¶ added in v0.2.0
Add records a message and joins it to the chains it references.
func (*History) Forget ¶ added in v0.2.0
Forget drops messages that expired before t (messages without expiry stay), to bound the history's size.
func (*History) Missing ¶ added in v0.2.0
Missing returns the messages in effect at the time at, in the chains a references, that a does not reference itself, sorted by key. Messages the history has not seen cannot be reported.
type Info ¶
type Info struct {
Language string `xml:"language,omitempty"`
Category []string `xml:"category"`
Event string `xml:"event"`
ResponseType []string `xml:"responseType,omitempty"`
Urgency string `xml:"urgency"`
Severity string `xml:"severity"`
Certainty string `xml:"certainty"`
Audience string `xml:"audience,omitempty"`
EventCode []NamedValue `xml:"eventCode,omitempty"`
Effective string `xml:"effective,omitempty"`
Onset string `xml:"onset,omitempty"`
Expires string `xml:"expires,omitempty"`
SenderName string `xml:"senderName,omitempty"`
Headline string `xml:"headline,omitempty"`
Description string `xml:"description,omitempty"`
Instruction string `xml:"instruction,omitempty"`
Web string `xml:"web,omitempty"`
Contact string `xml:"contact,omitempty"`
Parameter []NamedValue `xml:"parameter,omitempty"`
Resource []Resource `xml:"resource,omitempty"`
Area []Area `xml:"area,omitempty"`
}
Info is one info block (one language, one event).
type NamedValue ¶
type NamedValue struct {
ValueName string `xml:"valueName" json:"valueName"`
Value string `xml:"value" json:"value"`
}
NamedValue is an eventCode, parameter or geocode.
type OCSPOptions ¶ added in v0.2.0
type OCSPOptions struct {
// Client sends the requests; nil is a client with a 10 second timeout.
Client *http.Client
// Strict fails a chain when a certificate names no responder, the
// responder cannot be reached, its answer is "unknown" or stale.
// Without it those cases pass (soft fail) and only a "revoked" answer
// refuses the chain.
Strict bool
// LeafOnly asks only about the signing certificate, not about the
// intermediate CAs above it.
LeafOnly bool
// Now is the clock for freshness (tests); nil is time.Now.
Now func() time.Time
}
OCSPOptions configures OCSPCheck.
type Options ¶
type Options struct {
// LenientTime accepts "Z" and fractional seconds in date-times.
LenientTime bool
// Profiles are checked after CAP 1.2 itself (CAPCP, IPAWS, India, or
// one of the application's own); their problems carry the profile's
// name.
Profiles []Profile
}
Options relax validation for feeds that stray from the schema, and add profiles.
type Problem ¶
type Problem struct {
Field string `json:"field"`
Issue string `json:"issue"`
// Profile names the profile whose rule this is ("" for CAP 1.2
// itself).
Profile string `json:"profile,omitempty"`
// Rule is the rule's reference in the profile or standard, such as a
// clause or rule number ("CAP 1.2 3.2.2" for the base checks of an info
// block, "XSD" for the schema check).
Rule string `json:"rule,omitempty"`
// Warning marks a recommendation rather than a requirement: Check
// lists it, Validate does not fail on it.
Warning bool `json:"warning,omitempty"`
}
Problem is one validation finding.
type Profile ¶ added in v0.2.0
type Profile interface {
// Name identifies the profile in problems, such as "CAP-CP".
Name() string
// Check returns the alert's problems under the profile's rules (nil
// when it complies). The alert has already been read; it may not be
// valid CAP 1.2.
Check(a *Alert) []Problem
}
Profile is a set of rules a community adds to CAP 1.2, such as a national alerting authority's profile. Pass profiles in Options.Profiles; Check and Validate run them after the CAP 1.2 rules. The profiles this module provides (CAPCP, IPAWS, India) are written from each profile's public document in our own words; an application adds its own by implementing Profile or with NewProfile.
func CAPCP ¶ added in v0.2.0
func CAPCP() Profile
CAPCP returns the Canadian profile. It follows the version the alert declares in its code (profile:CAP-CP:0.4 or profile:CAP-CP:1.0); an alert that declares neither is checked as 1.0 and told to declare it.
func CAPCPWithLocations ¶ added in v0.2.0
CAPCPWithLocations is CAPCP that also checks each CAP-CP location code against a list the application loads (Statistics Canada's SGC codes and the profile's location annex, which are published separately and not included here): known reports whether a code is on it. "00" and "none" are always accepted.
func DetectProfiles ¶ added in v0.2.0
DetectProfiles returns the built-in profiles an alert declares in its code elements (CAP-CP and IPAWS name themselves there). India's profile is not declared in alerts and is never detected.
func India ¶ added in v0.2.0
func India() Profile
India returns the profile of the conventions SACHET alerts follow (see above; all findings are warnings).
func NewProfile ¶ added in v0.2.0
NewProfile makes a profile from a name and a check function.
Example ¶
An application adds its own profile with NewProfile (or by implementing Profile) and can register it for configurations that name profiles.
package main
import (
"fmt"
"github.com/cB-Sigma/cap"
)
func main() {
control := cap.NewProfile("Control room", func(a *cap.Alert) []cap.Problem {
if a.Source == "" {
return []cap.Problem{{Field: "source", Issue: "must name the duty officer", Rule: "CR-1"}}
}
return nil
})
cap.RegisterProfile("control-room", func() cap.Profile { return control })
p, _ := cap.ProfileByName("Control-Room")
a := &cap.Alert{Identifier: "X-1", Sender: "ops@example.test", Sent: "2026-09-24T10:00:00+05:30",
Status: "Exercise", MsgType: "Alert", Scope: "Public"}
fmt.Println(a.Validate(cap.Options{Profiles: []cap.Profile{p}}))
}
Output: the CAP alert is not valid: source: must name the duty officer (Control room CR-1)
func ProfileByName ¶ added in v0.2.0
ProfileByName returns a registered profile; the name's case is ignored.
type Reference ¶
type Reference struct {
Sender, Identifier, Sent string
}
Reference is one entry of an alert's references.
func ParseReferences ¶
ParseReferences splits a references value into its entries.
type Resource ¶
type Resource struct {
ResourceDesc string `xml:"resourceDesc"`
MimeType string `xml:"mimeType"`
Size string `xml:"size,omitempty"`
URI string `xml:"uri,omitempty"`
DerefURI string `xml:"derefUri,omitempty"`
Digest string `xml:"digest,omitempty"`
}
Resource is a file attached to an info block.
type SchemaValidator ¶ added in v0.2.0
SchemaValidator validates a whole document, for example an XSD engine loaded with the OASIS cap-v1.2.xsd file the application provides.
type SchemaValidatorFunc ¶ added in v0.2.0
SchemaValidatorFunc adapts a function to SchemaValidator.
func (SchemaValidatorFunc) ValidateSchema ¶ added in v0.2.0
func (f SchemaValidatorFunc) ValidateSchema(doc []byte) error
ValidateSchema calls f.
type SignSettings ¶
type SignSettings struct {
// Key is the PEM private key (PKCS #8, PKCS #1 RSA or SEC 1 EC).
Key string `json:"key"`
// Certificate is the PEM certificate of the key, followed by any
// intermediates; the first goes in each signature's KeyInfo.
Certificate string `json:"certificate"`
// ECDSAEncoding is how an ECDSA SignatureValue is written: "" or "raw",
// the two integers r and s side by side as XML Signature requires
// (RFC 6931 2.3.6), which xmlsec1 and Apache Santuario expect; "der",
// an ASN.1 sequence, for receivers built on goxmldsig (such as
// tannerryan/cap), which read only that. RSA signatures are unaffected.
ECDSAEncoding string `json:"ecdsaEncoding,omitempty"`
}
SignSettings configures signing of published messages.
type Signer ¶
type Signer struct {
// contains filtered or unexported fields
}
Signer signs CAP documents.
func NewSigner ¶
func NewSigner(s SignSettings) (*Signer, error)
NewSigner reads a key and certificate chain.
func (*Signer) Certificates ¶
func (s *Signer) Certificates() []*x509.Certificate
Certificates is the signing certificate followed by its intermediates.
type ValidationError ¶
type ValidationError []Problem
ValidationError lists every problem of an alert.
func (ValidationError) Error ¶
func (v ValidationError) Error() string
func (ValidationError) Essential ¶
func (v ValidationError) Essential() bool
Essential tells whether any of the problems is essential.
type Verifier ¶
type Verifier struct {
// Now is the clock for certificate validity (tests); nil is time.Now.
Now func() time.Time
// Revoked, when set, is asked about every chain a signature is
// accepted on (the signing certificate first, then its issuers; a
// pinned certificate is a chain of one). An error refuses the
// signature. CRLCheck builds one from certificate revocation lists;
// an OCSP or other check can be plugged in the same way.
Revoked func(chain []*x509.Certificate) error
// KeyUsages are the extended key usages a chained signing certificate
// must allow; empty accepts any. Pinned certificates are not checked.
KeyUsages []x509.ExtKeyUsage
// contains filtered or unexported fields
}
Verifier checks CAP signatures against trusted certificates.
func NewVerifier ¶
func NewVerifier(s VerifySettings) (*Verifier, error)
NewVerifier reads the trusted certificates.
func NewVerifierSkipping ¶
func NewVerifierSkipping(s VerifySettings, skip func(i int, err error)) *Verifier
NewVerifierSkipping reads the trusted certificates, roots and intermediates, passing each entry that cannot be used to skip (i is its index in its own list, which the error names) and leaving it out. With none left, every signed alert fails verification (it stays refused).
func (*Verifier) Roots ¶ added in v0.2.0
Roots is the pool of CA certificates chains are built to; nil when the verifier has none and accepts only pinned certificates.
func (*Verifier) Trusted ¶
func (v *Verifier) Trusted() []*x509.Certificate
Trusted is the certificates whose signatures are accepted.
func (*Verifier) Verify ¶
Verify checks the document's signature. It returns the verified alert element as a document of its own (signature removed): callers parse that, never the original bytes, so content outside the signed element cannot be slipped in. An unsigned document is returned as it is with state "none" unless signatures are required.
func (*Verifier) VerifyChain ¶ added in v0.2.0
func (v *Verifier) VerifyChain(doc []byte) (verified []byte, state string, chain []*x509.Certificate, err error)
VerifyChain is Verify that also returns the chain the signature was accepted on: the signing certificate first, then its issuers up to a root (only the signing certificate when it is pinned). The chain is nil for an unsigned alert.
type VerifySettings ¶
type VerifySettings struct {
// Trusted are PEM certificates whose signatures are accepted; a
// signature is valid only when its KeyInfo certificate is one of them
// (and within its validity period).
Trusted []string `json:"trusted"`
// Roots are PEM CA certificates. A signature whose KeyInfo certificate
// is not pinned in Trusted is accepted when that certificate chains to
// one of them (and to nothing else), through intermediates carried in
// KeyInfo or listed in Intermediates, with every certificate within
// its validity period.
Roots []string `json:"roots,omitempty"`
// Intermediates are PEM CA certificates used to build chains to Roots
// for signers that leave them out of KeyInfo. They are not trusted on
// their own.
Intermediates []string `json:"intermediates,omitempty"`
// SystemRoots adds the operating system's trusted CAs to Roots.
SystemRoots bool `json:"systemRoots,omitempty"`
// Require refuses alerts without a valid signature. Without it an
// unsigned alert is accepted and marked "signature": "none"; a
// signature that does not verify is refused unless acceptInvalid.
Require bool `json:"require,omitempty"`
// AcceptInvalid stores an alert whose signature does not verify,
// marked "signature": "invalid", instead of refusing it (default
// false). Risk: a forged or tampered alert is stored and can raise
// alarms. Every such alert is also reported as a warning.
AcceptInvalid bool `json:"acceptInvalid,omitempty"`
}
VerifySettings configures checking of received signatures.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package capnet is the network side of CAP: a feed poller for consumers, a feed server and a push receiver as http.Handlers for publishers and gateways, a push sender, and a client for the TCP alert stream of Canada's NAAD System.
|
Package capnet is the network side of CAP: a feed poller for consumers, a feed server and a push receiver as http.Handlers for publishers and gateways, a push sender, and a client for the TCP alert stream of Canada's NAAD System. |
|
Package capxsd validates CAP 1.2 documents against the OASIS schema itself, CAP-v1.2.xsd, shipped unmodified in this package with its OASIS copyright notice, on the pure-Go XSD engine github.com/cB-Sigma/xsd.
|
Package capxsd validates CAP 1.2 documents against the OASIS schema itself, CAP-v1.2.xsd, shipped unmodified in this package with its OASIS copyright notice, on the pure-Go XSD engine github.com/cB-Sigma/xsd. |