Documentation
¶
Overview ¶
Package iotsec holds the small interfaces and value types through which protocol libraries (DNP3, IEC 60870-5-104, IEC 61850 and others) reach the security services an application provides:
- TLS: TLSClient and TLSServer secure a connection under whatever TLS profile the application selected (for power systems usually the IEC 62351-3 profile); TLSServerProfile narrows a profile to the cipher suites a device declares; KeepaliveConn lets a profile ask the protocol for a keepalive on an idle link. StdTLS implements TLSClient and TLSServer on crypto/tls for applications without a profile of their own.
- Audit: Auditor receives SecurityEvent values (refusals, limits reached), which an application writes to its security log.
- Keys and roles: KeySource supplies the current identity (key and certificate, as IEC 62351-9 key management renews it), CertValidator checks a peer certificate beyond its signature (validity, revocation), and RoleAuthority reads and evaluates IEC 62351-8 access tokens.
- Network: Dialer opens outgoing connections, so an application can put its own egress rules in front of a library.
- Crypto policy: CryptoPolicy accepts or refuses an algorithm before a library uses it.
- State: Store keeps a small value durably for an adapter.
Beyond StdTLS and a few helpers the package has ready-made implementations for the plain case (MemoryStore, FileStore, SlogAuditor, PolicyRules, FileKeySource, ValidityPeriod); the revocation package checks CRLs and OCSP. This package depends on the standard library only. Every seam is optional: a library documents what it does when a seam is nil (usually the plain standard-library behaviour, or refusing the feature that needs it).
Index ¶
- Constants
- Variables
- func ServerNameFor(c TLSClient, addr string) string
- func SetKeepalive(conn any, f Keepalive) bool
- func TLSListener(l net.Listener, s TLSServer) net.Listener
- type AccessToken
- type Algorithm
- type AuditFunc
- type Auditor
- type CertValidator
- type CertValidatorFunc
- type CertificateRoleAuthority
- type CryptoPolicy
- type CryptoPolicyFunc
- type Dialer
- type DialerFunc
- type FileKeySource
- type FileStore
- type Identity
- type Keepalive
- type KeepaliveConn
- type KeySource
- type MemorySequences
- type MemoryStore
- type NewRoleAuthority
- type PolicyRules
- type RoleAuthority
- type RoleEntry
- type RoleGrant
- type RoleRequest
- type RoleScope
- type SecurityEvent
- type SequenceStore
- type StaticKeySource
- type StdTLS
- type Store
- type TLSClient
- type TLSNamedClient
- type TLSSelection
- type TLSServer
- type TLSServerProfile
Examples ¶
Constants ¶
const ( OutcomeSuccess = "success" OutcomeFailure = "failure" )
Outcomes of a SecurityEvent.
const ( AlgHash = "hash" // Name: "SHA-1", "SHA-256" AlgMAC = "mac" // Name: "HMAC-SHA-256", "AES-GMAC", "AES-CMAC"; KeyBits, TagBits AlgCipher = "cipher" // Name: "AES-128-CCM", "AES-128-GCM"; KeyBits AlgKeyWrap = "key-wrap" // Name: "AES-128-KW"; KeyBits AlgPublicKey = "public-key" // Key: the public key (*rsa.PublicKey, *ecdsa.PublicKey, ...) AlgDH = "dh" // Name: "ECDH-P-256", "FFDH"; KeyBits )
Kinds of Algorithm.
const ( RoleOpNone = 0 RoleOpAdd = 1 RoleOpDelete = 2 RoleOpChange = 3 )
Role operations an access token entry may ask for (IEC 62351-8 10.4.4.3).
Variables ¶
var ErrNotStored = errors.New("no saved state")
ErrNotStored is what Store.Load returns when nothing was saved yet.
var ErrPolicy = errors.New("refused by the crypto policy")
ErrPolicy is wrapped by every refusal of PolicyRules.
var ErrRevoked = errors.New("certificate revoked")
ErrRevoked is matched (errors.Is) by the error of a RoleAuthority or CertificateRoleAuthority when the certificate, or the attribute certificate holding the access token, is revoked. A protocol may treat that differently from a token that grants nothing (IEC 62351-5 drops the association's keys, for example).
Functions ¶
func ServerNameFor ¶
ServerNameFor is the name to check the certificate of a server reached at addr ("host:port") through c: c's configured name when it has one, else the host part of addr.
func SetKeepalive ¶
SetKeepalive registers f on conn when conn is a KeepaliveConn, and reports whether it was.
Types ¶
type AccessToken ¶
type AccessToken struct {
// Holder is the subject: the common name of an X.509 public key
// certificate, or the holder of an attribute certificate.
Holder string
Roles []RoleEntry // entries as carried, before evaluation
NotBefore, NotAfter time.Time
// Legacy is set when the token used the IEC/TS 62351-8:2011 OID.
Legacy bool
// For an attribute certificate: the signed part, its signature
// algorithm and the signature, so a protocol with signature algorithms
// of its own (DSA in IEEE 1815, say) can check it itself.
TBS []byte
SignatureAlgorithm asn1.ObjectIdentifier
Signature []byte
// Impl is the implementation's own form of the token; Authorize gets
// it back.
Impl any
}
AccessToken is an access token read from a certificate.
type Algorithm ¶
type Algorithm struct {
Kind string // one of the Alg constants
Name string
KeyBits int
TagBits int
Key any
}
Algorithm describes one cryptographic algorithm a library is about to use, in enough detail for a policy to judge it.
type AuditFunc ¶
type AuditFunc func(ctx context.Context, e SecurityEvent)
AuditFunc adapts a function to Auditor.
Example ¶
An application without a security log of its own can send a library's security events to slog.
package main
import (
"context"
"fmt"
"log/slog"
"os"
"github.com/cB-Sigma/iotsec"
)
func main() {
log := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{
ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
if a.Key == slog.TimeKey {
return slog.Attr{}
}
return a
}}))
var a iotsec.Auditor = iotsec.AuditFunc(func(ctx context.Context, e iotsec.SecurityEvent) {
log.WarnContext(ctx, "security event", "event", e.Event, "outcome", e.Outcome, "subject", e.Subject)
})
// A library calls it like this:
a.RecordSecurity(context.Background(), iotsec.SecurityEvent{Event: "dnp3_broadcast_refused", Outcome: iotsec.OutcomeFailure, Subject: "rtu-9"})
fmt.Println("done")
}
Output: level=WARN msg="security event" event=dnp3_broadcast_refused outcome=failure subject=rtu-9 done
func (AuditFunc) RecordSecurity ¶
func (f AuditFunc) RecordSecurity(ctx context.Context, e SecurityEvent)
RecordSecurity calls f.
type Auditor ¶
type Auditor interface {
RecordSecurity(ctx context.Context, e SecurityEvent)
}
Auditor receives security events. Implementations must not block for long; libraries call them from protocol goroutines.
func MultiAuditor ¶ added in v0.2.0
MultiAuditor sends every event to each auditor in turn (nil ones are skipped).
func SlogAuditor ¶ added in v0.2.0
SlogAuditor writes security events to a slog.Logger: failures at Warn, successes at Info, every field as an attribute (Detail as a group). A nil logger is slog.Default().
type CertValidator ¶
type CertValidator interface {
ValidateCertificate(ctx context.Context, c *x509.Certificate) error
}
CertValidator checks a peer certificate whose signature already verified: validity period, revocation (CRL, OCSP) and local policy. An error refuses the certificate.
func AllValidators ¶ added in v0.2.0
func AllValidators(vs ...CertValidator) CertValidator
AllValidators runs each validator in turn; the first error refuses.
func ValidityPeriod ¶ added in v0.2.0
func ValidityPeriod(now func() time.Time) CertValidator
ValidityPeriod is a CertValidator refusing a certificate outside its validity period; now nil is time.Now.
type CertValidatorFunc ¶
type CertValidatorFunc func(ctx context.Context, c *x509.Certificate) error
CertValidatorFunc adapts a function to CertValidator.
func (CertValidatorFunc) ValidateCertificate ¶
func (f CertValidatorFunc) ValidateCertificate(ctx context.Context, c *x509.Certificate) error
ValidateCertificate calls f.
type CertificateRoleAuthority ¶
type CertificateRoleAuthority interface {
// CertificateRoles evaluates the DER certificate der, whose signature
// the protocol already checked against issuer (nil when it was
// trusted another way). The returned roles are the ones the peer may
// enact here; an error matching ErrRevoked refuses the certificate.
CertificateRoles(ctx context.Context, der []byte, issuer *x509.Certificate, req RoleRequest) ([]RoleGrant, error)
}
CertificateRoleAuthority evaluates the roles of a peer that authenticated with a public key certificate, as a whole: the access token carried in the certificate (IEC 62351-8 Profile A), or else an attribute certificate the authority holds for it (Profile B), with the revocation state of either. A certificate without a token gets no role and no error.
type CryptoPolicy ¶
CryptoPolicy accepts or refuses an algorithm for a use ("dnp3 session key wrap", say). A nil error accepts it; an error refuses it and the library fails the operation with that error. A policy that only records what is used returns nil.
type CryptoPolicyFunc ¶
CryptoPolicyFunc adapts a function to CryptoPolicy.
type Dialer ¶
type Dialer interface {
DialContext(ctx context.Context, network, address string) (net.Conn, error)
}
Dialer opens outgoing connections. *net.Dialer implements it; an application passes its own to apply egress rules (allowed hosts and ports, no internal addresses) to a library's connections.
type DialerFunc ¶
DialerFunc adapts a function to Dialer.
func (DialerFunc) DialContext ¶
DialContext calls f.
type FileKeySource ¶ added in v0.2.0
type FileKeySource struct {
CertFile, KeyFile string
// contains filtered or unexported fields
}
FileKeySource is a KeySource reading a PEM certificate chain (leaf first) and its private key from files, and reading them again when either file changes (a renewal by another program).
type FileStore ¶ added in v0.2.0
type FileStore struct {
Path string
// contains filtered or unexported fields
}
FileStore is a Store in one file: JSON, written to a temporary file in the same directory, synced and renamed over the old one, so a crash leaves either the old or the new value. The file is created with mode 0600 (it may hold wrapped keys).
type Identity ¶
type Identity struct {
Certificates []*x509.Certificate
Key crypto.Signer
}
Identity is a private key with its certificate chain, leaf first.
type Keepalive ¶
type Keepalive = func() error
Keepalive sends the protocol's keepalive request on a connection: a message valid at any time in the association that the peer answers. It must not block for long and returns an error when it cannot send one now. It is an alias so that a profile's connection type can implement KeepaliveConn without importing this package.
type KeepaliveConn ¶
type KeepaliveConn interface {
SetKeepalive(f Keepalive)
}
KeepaliveConn is a connection under a TLS profile that may need traffic on an idle link (before a timed key update, say). The protocol registers its keepalive; nil removes it.
type KeySource ¶
KeySource supplies the current identity. Implementations renew it (IEC 62351-9 enrolment, say); callers ask again rather than keep it.
type MemorySequences ¶ added in v0.2.0
type MemorySequences struct {
// contains filtered or unexported fields
}
MemorySequences is a SequenceStore in memory.
func (*MemorySequences) Last ¶ added in v0.2.0
func (s *MemorySequences) Last(subject string) (uint32, bool)
Last implements SequenceStore.
func (*MemorySequences) Store ¶ added in v0.2.0
func (s *MemorySequences) Store(subject string, n uint32)
Store implements SequenceStore.
type MemoryStore ¶ added in v0.2.0
type MemoryStore struct {
// contains filtered or unexported fields
}
MemoryStore is a Store that keeps the value in memory (as JSON, so it behaves like a durable store: Load gives a copy).
type NewRoleAuthority ¶
type NewRoleAuthority func(scope RoleScope) (RoleAuthority, error)
NewRoleAuthority builds a RoleAuthority for one relying party. A library takes one of these when the authority needs state the library holds (its areas, its users' sequence numbers).
type PolicyRules ¶ added in v0.2.0
type PolicyRules struct {
// Allowed maps an algorithm kind (AlgHash, AlgCipher, ...) to the
// names it may use; names are compared without regard to case.
Allowed map[string][]string
// Denied names are refused whatever their kind (for example "SHA-1",
// "MD5").
Denied []string
// MinKeyBits maps a kind to its smallest key size (AlgCipher: 128,
// AlgDH: 2048, say).
MinKeyBits map[string]int
// MinTagBits is the smallest MAC tag.
MinTagBits int
// MinRSABits and MinECBits bound public keys (AlgPublicKey).
MinRSABits, MinECBits int
// AllowEd25519 accepts Ed25519 public keys (refused when MinECBits is
// set and this is false).
AllowEd25519 bool
}
PolicyRules is a CryptoPolicy from allow lists and minimum sizes. A kind with an allow list accepts only the names on it; minimum sizes apply where an Algorithm states a size or carries a public key. The zero value accepts everything.
type RoleAuthority ¶
type RoleAuthority interface {
// ReadToken reads the access token in a DER X.509 public key
// certificate (attribute false) or attribute certificate (true). It
// does not check the signature.
ReadToken(der []byte, attribute bool) (AccessToken, error)
// CheckSignature checks an attribute certificate token's signature
// against its issuer's certificate; allowSHA1 permits SHA-1 for
// protocols that still require it.
CheckSignature(tok AccessToken, issuer *x509.Certificate, allowSHA1 bool) error
// MatchArea reports whether a token entry's area of responsibility
// applies to a system whose own areas are local (remote access).
MatchArea(aor string, local []string) bool
// Authorize evaluates the token (role definitions and revisions, area
// of responsibility, status change sequence numbers, revocation) and
// returns the roles granted, most specific first. Refusals are the
// implementation's security events.
Authorize(ctx context.Context, tok AccessToken, req RoleRequest) ([]RoleGrant, error)
}
RoleAuthority reads and evaluates IEC 62351-8 access tokens.
type RoleEntry ¶
type RoleEntry struct {
IDs []int // role identifiers; 0 to 6 are the pre-defined roles
AoR string // area of responsibility
Revision int // role definition revision
Definition string // role definition name; empty for the standard one
Operation int // one of the RoleOp constants
Sequence uint32 // status change sequence number, when HasSequence
HasSequence bool
}
RoleEntry is one entry of an access token, as carried: one or more role identifiers with their area of responsibility, role definition and optional operation.
type RoleGrant ¶
type RoleGrant struct {
ID int // role identifier
AoR string // area of responsibility
Revision int // role definition revision
Definition string // role definition name; empty for the standard one
Operation int // one of the RoleOp constants
Sequence uint32 // status change sequence number, when HasSequence
HasSequence bool
// Name and Rights describe an operator defined role: its name and
// the domain specific rights its definition grants (a protocol maps
// them to its own permissions). Empty for the pre-defined roles.
Name string
Rights []string
}
RoleGrant is one role the subject may enact here, after evaluation (RoleAuthority.Authorize), with the token entry it came from.
type RoleRequest ¶
type RoleRequest struct {
Peer string // the subject's name as the protocol knows it
// Remote is set when the subject reaches the device over a network
// (it selects the "remote" area of responsibility keyword).
Remote bool
// TokenVerified: the protocol already checked the certificate's
// signature against the authority it trusts.
TokenVerified bool
// SubjectAuthenticated: the subject proved it holds the token (a
// signature, a challenge answered, TLS client authentication).
SubjectAuthenticated bool
}
RoleRequest is what the protocol knows about how a token reached it.
type RoleScope ¶
type RoleScope struct {
Subject string // names the relying party in events, e.g. "dnp3 outstation rtu-9"
Areas []string // the system's own areas of responsibility
Sequences SequenceStore // nil: the authority keeps them
Log *slog.Logger // nil: slog.Default()
}
RoleScope describes the relying party a RoleAuthority is built for.
type SecurityEvent ¶
type SecurityEvent struct {
Tenant string // the application's tenant, when it has them
Category string // for example "communication"
Event string // a stable name, for example "dnp3_max_authentication_failures"
Outcome string // OutcomeSuccess or OutcomeFailure
Actor string // who acted ("system" for the library itself)
Subject string // what it concerns: an association, a user
Peer string // the remote party, when known
Method string // the security mechanism, for example "dnp3-sav5"
Detail map[string]string
}
SecurityEvent is one entry for an application's security log: a refused request, a limit reached, a key change. It never carries key material.
type SequenceStore ¶
type SequenceStore interface {
Last(subject string) (uint32, bool)
Store(subject string, n uint32)
}
SequenceStore gives a RoleAuthority the last status change sequence number accepted per subject, when the protocol keeps them itself.
type StaticKeySource ¶ added in v0.2.0
type StaticKeySource Identity
StaticKeySource is a KeySource that always gives the same identity.
type StdTLS ¶
StdTLS implements TLSClient and TLSServer with crypto/tls and Config, for applications that have no TLS profile of their own. Config is cloned for each connection and must not be nil.
func (StdTLS) ServerName ¶
ServerName is the configured Config.ServerName ("" without a Config).
type Store ¶
type Store interface {
Load(ctx context.Context, v any) error
Save(ctx context.Context, v any) error
}
Store keeps one small value durably for an adapter (keys wrapped under a storage key, counters, the last sequence numbers). Load decodes the stored value into v (a pointer); when nothing was saved yet it returns an error matching ErrNotStored and leaves v unchanged. Save replaces the value. Values are JSON-encodable.
type TLSClient ¶
type TLSClient interface {
Client(ctx context.Context, raw net.Conn, serverName string) (net.Conn, error)
}
TLSClient secures an outgoing connection as the client of a TLS session. Client runs the handshake over raw and returns the secured connection. serverName is the name the server certificate is checked against; when it is empty the implementation uses its own configured name, or else the host of raw's remote address.
type TLSNamedClient ¶
TLSNamedClient is a TLSClient with a configured server name. A library that dials an address asks for it through ServerNameFor, so that a profile's own name takes precedence and a host name the user gave is not replaced by the address it resolved to.
type TLSSelection ¶
TLSSelection narrows a TLS profile for one peer, for example to the cipher suites a device profile declares. Suites are named as in the IANA TLS registry (TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256). An empty list keeps the profile's own offer for that version.
type TLSServer ¶
TLSServer secures an accepted connection as the server of a TLS session. Server wraps raw; the handshake runs on the first Read or Write, as with crypto/tls.Server.
type TLSServerProfile ¶
type TLSServerProfile func(sel TLSSelection) (TLSServer, error)
TLSServerProfile builds a server profile narrowed by sel. A library that emulates a device with a fixed suite list takes one of these instead of a TLSServer.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package revocation is a ready-made iotsec.CertValidator that checks a certificate's validity period and revocation: certificate revocation lists given to it or fetched from the certificate's distribution points (RFC 5280), and OCSP (RFC 6960, through golang.org/x/crypto/ocsp).
|
Package revocation is a ready-made iotsec.CertValidator that checks a certificate's validity period and revocation: certificate revocation lists given to it or fetched from the certificate's distribution points (RFC 5280), and OCSP (RFC 6960, through golang.org/x/crypto/ocsp). |