Documentation
¶
Overview ¶
Package identity answers one question for a local service: which program is on the other end of this connection, and how strongly is that proven.
The second half is the point. A privileged service that grants narrow permissions to named applications - "allow LogViewer to read the System log" - is only as good as its ability to tell that the caller really is LogViewer. When it cannot, the honest outcome is a refusal, not a weaker grant made silently. So every attribute of a Peer arrives welded to a Proof, there is no way to read the value without the proof, and a service can ask CanEver at startup whether the policy it intends to enforce is achievable on this operating system at all.
Nothing here ever reads an identity out of a message. There is no API that accepts one. ProofClaimed exists only to name what is being refused.
Usage ¶
// Once, at startup: refuse to run a policy this machine cannot enforce.
if err := identity.CanEver(policy); err != nil {
return fmt.Errorf("this build cannot enforce its own permission model: %w", err)
}
for {
h := acceptPipeInstance()
at := time.Now() // the line after accept; see Options.ConnectedAt
// Windows will not identify the peer until the server has read.
frame := readOneBoundedFrame(h) // do not parse it yet
peer, err := identity.OfHandle(identity.Handle(h), &identity.Options{ConnectedAt: at})
if err != nil { reject(h); continue }
if err := peer.Check(policy); err != nil { reject(h); continue }
handle(peer, frame) // now the bytes may be interpreted, as a request
} // and never as a claim about who is making it
Platforms ¶
Windows named pipes, Linux AF_UNIX and macOS AF_UNIX are implemented. Every other GOOS is a seam that returns ErrUnimplemented rather than a weaker answer from a portable fallback.
The three do not answer the same question equally well, and the differences are not details:
- Windows names the process that opened the pipe and cannot verify the code it is running.
- Linux names the user unforgeably and has no general answer to what a program is; without SO_PEERPIDFD (Linux 6.5) it cannot even bind a path to the peer, and reports that at ProofPID rather than hiding it.
- macOS can verify a running program's signature, which neither of the others can, but over a plain socket the audit token that selects which program is resolved from the socket's current owner rather than stamped at connect - so the strong answer is bound to a weak binding.
Read CONTRACT.md before building a permission prompt on any of it. It is the document this package exists to make possible.
Index ¶
- Constants
- Variables
- func CanEver(n Need) error
- func CanonicalProgramPath(path string) string
- func PackageFamily(fullName string) (string, bool)
- func SubjectProgram(p *Peer, pathProof Proof) (string, error)
- func ValidSubjectProgram(program string) bool
- type Attr
- type Binding
- func (b *Binding) Alive() (bool, error)
- func (b *Binding) BoundAt() time.Time
- func (b *Binding) Captured() *Peer
- func (b *Binding) Check(n Need) error
- func (b *Binding) Close() error
- func (b *Binding) EvidenceValidUntil() time.Time
- func (b *Binding) Peer() (*Peer, error)
- func (b *Binding) Shared() *Binding
- type Code
- type Handle
- type Limits
- type Need
- type Options
- type Peer
- type Process
- type Proof
- type ProofError
- type Transport
- type User
Constants ¶
const PackagedProgramPrefix = "msix:"
PackagedProgramPrefix begins the subject program of a caller with MSIX package identity: msix:<package family name>.
const TransportLoopback = "tcp-loopback"
TransportLoopback names a loopback TCP connection in Peer.Transport and Limits.Transport.
Variables ¶
var ( // ErrNoBinding means this machine has no primitive that ties an // identity to the process that opened the connection. It is not a // weaker answer; it is the absence of one, and a service that needs a // peer bound must refuse the connection. Ceiling().Binding says what is // missing. ErrNoBinding = errors.New("identity: this machine cannot bind a peer to a connection") // ErrPeerGone means there was no live process to bind to. It comes from // [Bind] only. A peer that exits after it has been bound does not // invalidate the binding: "the process that opened this connection was // X" stays true after X exits, and every primitive here pins the process // so that no successor can take its place. Ask [Binding.Alive] when // liveness is what matters. ErrPeerGone = errors.New("identity: there is no live peer to bind to") // ErrPeerMoved means the connection no longer answers for the process // that was captured when it was accepted. ErrPeerMoved = errors.New("identity: the connection has moved to a different program since it was accepted") )
Errors a binding returns instead of an identity.
var ( // ErrRemotePeer means the peer is not on this machine. A Windows named // pipe is reachable over SMB unless the server passes // PIPE_REJECT_REMOTE_CLIENTS, and a remote peer has no local pid at all. ErrRemotePeer = errors.New("identity: peer is not on this machine") // ErrNotServerEnd means the handle is not the end a service accepted: // the client end of a Windows named pipe, or a listening Unix socket. // // Both mistakes answer, rather than fail, if they are not caught. On the // client end of a pipe every question below has an answer and every // answer is about the service itself. SO_PEERCRED on a listening socket // returns the credentials captured at listen(2) - also the service's // own. A service that identified itself and believed it had identified a // caller would authorise everything. ErrNotServerEnd = errors.New("identity: handle is not the end of the connection the service accepted") // ErrUnsupportedConn means the connection is of a kind that carries no // peer credentials, or one whose handle this package cannot reach. ErrUnsupportedConn = errors.New("identity: connection carries no peer credentials") // ErrMustReadFirst means Windows will not identify a named pipe's client // until the server has completed a read on that pipe. It is not enough // for the client to have written; the server must have read. // // The consequence is unpleasant and unavoidable: a service has to accept // and read bytes from a caller it cannot yet name. Read one bounded // frame, identify the peer, and only then decide what the bytes were // allowed to ask for. Do not parse the frame first, do not size a buffer // from it, and above all do not read a name out of it - see // CONTRACT.md, "You must listen before you can ask who is speaking". ErrMustReadFirst = errors.New("identity: windows will not identify a pipe client until the server has read from the pipe") // ErrImpersonationStuck means the operating system refused to undo an // impersonation. It is returned instead of an identity, because a // process that cannot drop a client's token has a privilege escalation, // not a naming problem. The thread that held the token is destroyed // rather than returned to the scheduler; see impersonate_windows.go. ErrImpersonationStuck = errors.New("identity: could not revert impersonation; thread abandoned") // ErrUnimplemented means this platform's answer has not been built yet. // It is returned instead of a weaker answer from a portable fallback, // deliberately: a permission system that silently degrades to "some // process on this machine" is worse than one that will not start. ErrUnimplemented = errors.New("identity: not implemented on this platform") )
Errors that say the channel itself cannot answer the question, as distinct from answering it weakly.
var ErrNotProven = errors.New("identity: not proven to the required level")
ErrNotProven is the root of every failure to meet a required proof level. Test for it with errors.Is.
Functions ¶
func CanEver ¶
CanEver reports whether a policy is satisfiable on this platform at all, under the best case for every attribute.
Call it at startup, not per connection. The answer does not depend on who is calling, and a service whose permission model rests on a proof this platform cannot produce should refuse to start rather than fail open once per connection - or, worse, decide it can relax the requirement.
func CanonicalProgramPath ¶ added in v0.4.0
CanonicalProgramPath resolves path to the one spelling it treats as the true name of the file it names: on Windows, a short DOS 8.3 name component expands to its long form. A short-name spelling and a long-name spelling of the same program compare equal under CanonicalProgramPath. A path this cannot resolve, including one that names no existing file, comes back unchanged: an exact comparison against it still refuses exactly what it refused before.
func PackageFamily ¶ added in v0.3.0
PackageFamily returns the family name of an MSIX package full name, <Name>_<Version>_<Architecture>_<ResourceId>_<PublisherId>, where ResourceId may be empty.
func SubjectProgram ¶ added in v0.3.0
SubjectProgram names the program a service's rights, credentials and inference decisions are about. A Windows caller whose package identity is proven at ProofSigned, and whose image lies inside that package's installed folder, is msix:<package family name>: the image path of a packaged app changes with every package version, and the family does not. The folder check is needed because any process of the account can start an arbitrary program with an installed package's identity (Invoke-CommandInDesktopPackage), while it cannot write into the package's folder under WindowsApps. Every other caller, including a descendant of a packaged app, which has no identity of its own, is its clean absolute image path at pathProof or better, canonicalized: a peer launched through a short DOS 8.3 alias names the same subject as one launched through the long spelling of the same file (CanonicalProgramPath).
func ValidSubjectProgram ¶ added in v0.3.0
ValidSubjectProgram reports whether program is a subject program as SubjectProgram names one: msix:<package family name>, or a clean absolute path of at most 4096 bytes without NUL, CR or LF.
Types ¶
type Attr ¶
type Attr[T any] struct { // contains filtered or unexported fields }
Attr is one attribute of a peer together with the strength of the evidence for it. There is deliberately no way to read the value without being handed the proof at the same time: a service that needs a verified signature has to see that it got something weaker instead of silently accepting it.
func (Attr[T]) AtLeast ¶
AtLeast returns the value only if it is proven at least as strongly as min, and otherwise a *ProofError explaining what was available instead.
func (Attr[T]) Get ¶
Get returns the value and its proof together. Use it when the decision depends on the proof; use AtLeast when a fixed minimum is required.
type Binding ¶
type Binding struct {
// contains filtered or unexported fields
}
A Binding is a peer identity captured when the connection was accepted, together with whatever handle the platform offers for asking, later, whether that capture is still true.
It exists because OfHandle answers by looking the peer up at the moment it is called, and a lookup is a race by construction: every platform here resolves a program from a number, and the thing that number refers to can change. A Binding takes the answer once, at the only instant the caller controls, and refuses afterwards rather than re-deriving.
When to call it differs by platform, and the difference is not cosmetic:
- Linux and macOS: at accept, before the service reads a single byte. On macOS this is load-bearing. LOCAL_PEERTOKEN follows the socket's most recent writer, so a byte written by another process before this call is a byte this call believes.
- Windows: after the first read, the same precondition OfHandle has. Nothing is lost by waiting: the client process id is written onto the pipe instance by the kernel when the client opens it and never moves, and a Windows process cannot replace its own image.
func Bind ¶
Bind captures the peer identity of an accepted local connection and keeps the platform handle needed to re-check it.
The returned Binding must be closed. See the type documentation for when to call this, which is not the same on all three platforms.
func BindConn ¶
BindConn is Bind for a net.Conn that is willing to give up its handle.
The returned Binding keeps a reference to that handle and is only valid while c is open: close the Binding before closing c. This is the one place this package holds a descriptor outside raw.Control, and it is unavoidable - a binding that has to be re-checked later cannot re-derive the handle it was taken from.
func BindLoopback ¶ added in v0.3.0
BindLoopback binds the process on the other end of an accepted IPv4 loopback TCP connection, through the operating system's socket-owner table.
A loopback socket carries no peer credentials: the kernel stamps nothing onto the connection that names the process at the other end. What it does keep is a table of the sockets each process owns. The binding looks the client's end of this connection up in that table, pins the process it names, and refuses afterwards when the table stops agreeing:
- Windows: GetExtendedTcpTable with TCP_TABLE_OWNER_PID_CONNECTIONS names the process that created the client socket. The binding opens that process, requires its creation time to predate the connection, and refuses with ErrPeerMoved once the process has exited while the connection is still established, because another process then holds the socket.
- Linux: /proc/net/tcp names the socket inode and its owning uid, and the binding finds the one same-uid process whose descriptor table holds that inode, pins it with pidfd_open and captures its image path. A socket held by two processes, or later held by a different one, is ErrPeerMoved.
- macOS: ErrNoBinding. The recorded identity refusal stands; see LoopbackCeiling.
Call it on the line after accept, before the service reads a byte, and re-check with Binding.Peer before acting. c must be the accepted end: this function cannot tell a connection the service dialed from one it accepted, and a dialed connection would name the listening service. The returned Binding must be closed before c.
func BindServer ¶ added in v0.2.1
BindServer captures the server of an already connected local CLIENT handle, before application bytes are sent. The caller must establish that the handle belongs to the local transport namespace; this raw-handle primitive does not validate the path used to open it. The caller owns the connection and closes the returned binding before that connection. ConnectedAt must describe the successful dial, when supplied. This identifies a process; the caller still needs independent trust evidence to authorize that process as its server.
func BindServerConn ¶ added in v0.2.1
BindServerConn is BindServer for a connection exposing syscall.Conn.
func (*Binding) Alive ¶
Alive reports whether the bound process still exists. It is a separate question from whether the identity is still trustworthy, and services that conflate the two end up refusing honest callers that happened to exit.
func (*Binding) Captured ¶
Captured returns the identity as it was at bind time without re-checking it. It is for log lines and for tests that need to compare the two answers. A permission decision uses Binding.Peer.
func (*Binding) Check ¶
Check is Peer.Check against the re-checked capture.
func (*Binding) Close ¶
Close releases the platform handle. A Binding that is not closed leaks a pidfd on Linux and a process handle on Windows, and on Linux a leaked pidfd holds the peer's pid reserved for as long as the service runs.
func (*Binding) EvidenceValidUntil ¶ added in v0.4.0
EvidenceValidUntil is the expiry of captured time-sensitive evidence. It is zero when this binding captured no evidence with a recorded expiry. On Windows it preserves the original Authenticode verdict deadline even when Bind reused a verdict verified for an earlier connection.
func (*Binding) Peer ¶
Peer returns the captured identity, having first confirmed that nothing has contradicted it. It returns ErrPeerMoved instead of a stale answer.
A peer that has merely exited is not a contradiction and is answered normally; see Binding.Alive.
type Code ¶
type Code struct {
// Subject is the name the signing certificate was issued to: the
// publisher, as a person reading a consent prompt would understand it.
Subject string
// Issuer is the certificate authority that vouched for the subject.
// Subject alone is not an identity; anyone can obtain a certificate
// naming themselves anything a CA will accept.
Issuer string
// Trusted is true when the platform's own trust policy accepted the
// signature and the chain, with revocation checking as configured in
// Options. It is the same verdict the Proof beside this Code carries, as
// a boolean for logs: a policy compares the Proof, and a Code with
// Trusted false never sits above ProofUnmet. Status says how it failed.
Trusted bool
// Status is the platform's verdict in its own words, always populated:
// "valid", "no signature", or the failing condition.
Status string
// Catalog is true on Windows when the signature came from a system
// catalog rather than from an embedded signature in the file itself.
Catalog bool
// TeamID is the Apple Developer team identifier out of the validated
// signature on macOS, and empty elsewhere.
//
// It is the field to write a rule against on that platform. Subject is a
// display name a certificate authority was willing to issue and two
// publishers can share one; a team id is assigned by Apple and is the
// stable half of the answer. Platform binaries have none: they validate
// against the system's own anchor instead, which is a different claim
// and should be checked with a requirement rather than a string compare.
TeamID string
}
Code is a code signing identity as some part of the operating system verified it - not as a file on disk claims it.
type Handle ¶
type Handle uintptr
Handle is an operating-system handle for one accepted local connection: on Windows the server end of a named pipe instance, on Unix the file descriptor of a connected socket.
The handle must still be open, and must be the end the service accepted, not the end the client opened. Ownership stays with the caller: nothing in this package closes it.
type Limits ¶
type Limits struct {
Platform string
Best Need
Why map[string]string // "user", "process", "path", "package", "code"
// Transport names the channel these limits are about. The same platform
// answers differently on different channels and a ceiling that omits
// which one is unreadable.
Transport string
// Bindable reports whether [Bind] can tie an identity to the process
// that opened the connection on this machine, as opposed to looking a
// program up from a number afterwards. It is a property of the running
// kernel, not of the GOOS: the same Linux binary answers true on 6.5 and
// false on 4.4.
Bindable bool
// Binding names the primitive behind Bindable, or states what is missing
// when it is false. Always populated.
Binding string
// Stronger names the transport that would prove more than this one, and
// what it would add. Empty when this transport is already the platform's
// best.
Stronger string
}
Limits is what a platform can prove at best, with the reason for each ceiling. Obtained from Ceiling.
func Ceiling ¶
func Ceiling() Limits
Ceiling reports what this platform can prove at best, and why it stops there. It does not depend on any connection.
func CeilingFor ¶ added in v0.4.0
CeilingFor reports the selected transport's capabilities without opening a connection. Unsupported transports return an error, including for empty policy. Ceiling retains its existing meaning: the default native stream transport.
func LoopbackCeiling ¶ added in v0.3.0
func LoopbackCeiling() Limits
LoopbackCeiling reports what this platform can prove at best about the process behind a loopback TCP connection. It does not depend on any connection.
type Need ¶
Need is a policy: the minimum proof a service requires for each attribute before it will act. The zero Need requires nothing.
type Options ¶
type Options struct {
// ConnectedAt is when the connection was accepted.
//
// Resolution refuses to report anything read out of the peer's pid
// unless it can first open the process and see that it was created
// before this instant. A process that inherited a recycled pid must have
// been created after the connection already existed, so the check
// excludes every one of them.
//
// Leave it zero and the check uses the moment of resolution instead,
// which still excludes any reuse after that moment but not reuse between
// accept and resolution. Set it in the accept loop, on the line after
// accept returns, and the window closes to the width of one accept.
ConnectedAt time.Time
// CheckRevocation asks the platform to contact revocation infrastructure
// while verifying a signature. It is off by default because it makes
// identification depend on the network, and a permission prompt that
// hangs is a permission prompt that gets clicked through.
CheckRevocation bool
// SkipCodeSignature skips signature verification entirely. On Windows
// the first verification for a process costs tens of milliseconds, and
// more for a large signed image; later connections from the same live
// process reuse that verdict (CONTRACT.md). A service that never asks
// about Code should not pay it.
SkipCodeSignature bool
// CodeRequirement is a macOS code signing requirement, such as
//
// anchor apple generic and certificate leaf[subject.OU] = "TEAMID"
//
// It is handed to SecCodeCheckValidity, so the operating system decides
// whether the peer satisfies it. That is worth more than reading
// Code.TeamID and comparing it here: the requirement language covers the
// anchor, the chain and the leaf together, and a comparison written in
// Go can only see what this package chose to extract.
//
// Empty means "verify the signature, tell me what it says, and let me
// decide". Ignored on Windows and Linux, neither of which has anything
// to give it to.
CodeRequirement string
}
Options tunes resolution. The zero value is safe. ConnectedAt is worth setting, and is the only field most callers need.
type Peer ¶
type Peer struct {
// Platform is the GOOS the answer came from. The answer differs per
// platform and a log line that omits which one is unreadable later.
Platform string
// Transport names the channel the identity was taken from: "npipe" on
// Windows, "unix" on Linux and macOS. The same platform gives different
// answers on different transports - on macOS, dramatically so, since an
// XPC peer can be identified to a standard a socket peer cannot - so the
// transport belongs next to the answer.
Transport string
// ObservedAt is when the identity was resolved, which is not when the
// connection was accepted. See Options.ConnectedAt.
ObservedAt time.Time
// User is the security principal the peer runs as.
User Attr[User]
// Process is which process it is.
Process Attr[Process]
// Path is the filesystem path of the running image.
Path Attr[string]
// Package is a package identity where the platform has one: an MSIX
// package full name on Windows, the signing identifier out of a
// validated signature on macOS, a sandbox application id on Linux
// ("flatpak:org.gnome.Calculator", "snap:chromium.chromium"). Unset for
// ordinary executables.
//
// The three are not comparable. The Windows one comes out of a
// certificate the OS validated; the macOS one out of a signature it
// validated; the Linux one out of a file in the peer's own mount
// namespace or a cgroup name, neither of which resists a hostile
// process running as the same user. See CONTRACT.md before writing a
// rule against this field.
Package Attr[string]
// Code is a code signing identity for the image, and never exceeds
// ProofBound on any platform this package supports.
//
// On Windows it is an Authenticode verification of the file the process
// was launched from, which is a weaker statement than it looks. On macOS
// it is a Security framework verification of the running code, which is
// a stronger statement - attached to a weaker binding, because a unix
// socket cannot prove which process it belongs to. On Linux it is always
// unset: the platform has nothing to verify. See CONTRACT.md.
//
// A signature the platform examined and did not accept is reported below
// ProofPID, at ProofInvalid, ProofUnsigned or ProofUnmet, so AtLeast
// refuses it for any policy that implies verification and admits it only
// for a policy that names the lower rung.
Code Attr[Code]
// Notes records everything a reader would otherwise get wrong about this
// particular answer: every place it came out weaker than this platform's
// best, and why. It is for humans, for logs and for tests.
Notes []string
// contains filtered or unexported fields
}
Peer is what a service was able to learn about the program on the other end of an accepted local connection, with the strength of the evidence attached to every part of it separately.
Nothing in a Peer was ever spoken by the peer. There is no field a caller can fill in from a request payload, and no constructor that takes one.
func OfConn ¶
OfConn is OfHandle for a net.Conn that is willing to give up its handle.
Most named-pipe libraries on Windows are not: they wrap the handle in an unexported struct with no accessor. When that is the case OfConn returns ErrUnsupportedConn and the caller must keep hold of the handle it created the pipe instance with and use OfHandle. This is not a workaround to be embarrassed about - the handle is the object the identity belongs to.
func OfHandle ¶
OfHandle identifies the program on the other end of an accepted local connection.
It never blocks on the peer and never reads from the connection itself.
On Windows there is a precondition that is easy to miss and impossible to work around: the server must already have completed a read on the pipe. Windows refuses to impersonate the client of a pipe nothing has been read from, and it is the read that matters, not the client's write. So the sequence is accept, read one bounded frame, identify, and only then decide what that frame was allowed to ask for. Calling OfHandle before the first read returns ErrMustReadFirst.
A returned error means the channel could not answer the question at all - a remote peer, the wrong end of the pipe, a refused impersonation. An answer that is merely weak comes back as a *Peer with low Proof values and a populated Notes, not as an error, because only the caller's policy knows whether weak is fatal.
func (*Peer) Check ¶
Check reports whether this Peer meets the policy. The error names every attribute that fell short, so a service does not learn about its second impossible requirement only after fixing the first.
type Process ¶
type Process struct {
// PID is the process id the kernel recorded for the peer when it
// connected. It names the process that opened the connection, which is
// not necessarily the process writing to it now: handles can be
// inherited and duplicated. See CONTRACT.md.
PID int
// StartTime is when that process was created. It is the thing that
// distinguishes a process from a later one that inherited its number,
// so it belongs in a log next to the pid. Zero when the process could
// not be opened.
StartTime time.Time
// Recycled is true when the pid was proven to belong to a different,
// later process than the one that connected. Nothing resolved from the
// pid is reported when it is set.
Recycled bool
// Generation distinguishes this process from a later one that inherits
// its pid, where the platform keeps a counter for exactly that: the
// pidversion inside a macOS audit token. Zero where the platform has
// none, which is everywhere else - Windows uses StartTime for the same
// job and Linux has neither.
//
// It is not a bound on its own. A pid and a pidversion read together
// describe one process instance consistently; whether that instance is
// the peer is a separate question, and on macOS sockets it is the
// unanswered one. See CONTRACT.md.
Generation uint32
}
Process identifies the process on the other end.
type Proof ¶
type Proof uint8
Proof says how hard it would be for a hostile caller to make one attribute of a Peer lie. It is a total order because a policy has to compare against a minimum, but the steps are qualitatively different, and the documentation of each one says what it actually rests on. Read those before writing a policy; the number alone is not the argument.
From ProofPID upwards a rung says how the value was bound to the peer. The three rungs below it are verdicts, produced only for Code: the platform examined the peer's signature and did not accept it, and the rung says how far it got. They sit below every rung that implies verification, so a policy that requires a verified signature refuses them by comparison alone, and a policy that knowingly admits an unverified caller names the lower rung.
const ( // ProofNone means the attribute was not determined: either the platform // cannot supply it at all, or this particular connection did not carry // it. The value is meaningless and must not be used. Attr refuses to // hand it out. ProofNone Proof = iota // ProofClaimed means the peer said so. This package never produces it // and has no API that accepts it. The level exists to name the thing // being refused, so a service tempted to read an "owner" field out of // the request payload has somewhere to look and be told no. ProofClaimed // ProofInvalid means the platform found a signature and refused it: the // code was modified after signing, the certificate is expired or revoked, // or the chain reaches a root this machine does not trust. It is the // lowest verdict, below ProofUnsigned, because no signature is no claim // and a refused signature is a claim the operating system rejected. The // value carries the platform's wording in Code.Status and no identity. ProofInvalid // ProofUnsigned means the platform looked and found no signature at all. // A policy that admits unsigned callers asks for this rung by name. ProofUnsigned // ProofUnmet means the signature is intact and the platform accepted it, // but the code does not satisfy the requirement the service asked for in // Options.CodeRequirement. It is the strongest verdict short of // verification: intact code, signed by somebody other than who was // required. Nothing is read out of a signature that failed the check it // was given, so the value still carries no identity. Only macOS produces // it; Windows has no requirement language. ProofUnmet // ProofPID means the attribute was read out of the operating system by // process id, after the connection was already up, without checking that // the process behind that id is still the one that connected. It is racy // by construction: pids are reused. Good enough for a log line, never // good enough for a permission decision. ProofPID // ProofBound means the attribute was read through something that ties // it to the process that connected, rather than to a number that // process once had. What that something is differs per platform, and so // does exactly what it excludes: // // - Windows: a process handle whose creation time predates the // connection, which excludes every process that could have // inherited the pid. The reuse race is closed. // - Linux: a pidfd the kernel derived from the connection itself // (SO_PEERPIDFD). While it is open the pid cannot be reassigned, so // /proc read behind it answers about the peer or about nothing. // - macOS: a pid and pidversion from the peer socket, cross-checked // against the connect-time credentials and refused if the process // started after the connection. That excludes pid reuse and any // program spawned to impersonate; it does not exclude a process // that already existed and was handed the socket. See CONTRACT.md. // // The attribute is still only the operating system's record about a // running process (an image path, say), not a statement about code - // except on macOS, where a verified code signature also lands here, // because over a socket the signature is only worth what the binding // above is worth. ProofBound // ProofKernel means the kernel stamped the attribute onto the connection // itself, out of its own records, at the moment the peer connected. The // peer never spoke it and cannot influence it. The access token behind // ImpersonateNamedPipeClient is of this kind, and so is the client // process id the kernel recorded on the pipe; on Linux, so are the uid, // gid and pid in SO_PEERCRED and the LSM label in SO_PEERSEC. // // "At the moment the peer connected" is the load-bearing half. A value // the kernel produces honestly but resolves when asked - LOCAL_PEERPID // and LOCAL_PEERTOKEN on macOS, which follow the peer socket's current // owner - is not of this kind, however official its source. ProofKernel // ProofSigned means the operating system itself validated a code // signature and the identity reported here came out of that validation - // not out of a filename. On Windows only MSIX package identity reaches // this level: the package full name contains a hash of the publisher // identity from the certificate the OS checked when it installed and // launched the package, and no unpackaged process can be given one. // // Authenticode on a plain executable does NOT reach this level, however // good the signature is. See Peer.Code and CONTRACT.md. // // Neither does macOS over a unix socket, which is the surprise in this // package: the Security framework really does verify running code, but // the audit token that selects which code is resolved from the peer // socket's current owner rather than stamped at connect. The transport // that reaches this level on macOS is XPC, whose messages carry a token // the kernel stamped on the sender. This package speaks sockets, so no // platform it supports reports ProofSigned for an ordinary program. ProofSigned )
type ProofError ¶
ProofError reports an attribute that is either unknown or known less firmly than the caller demanded. Why carries the reason the platform gave, which is usually the only actionable part of the message.
func (*ProofError) Error ¶
func (e *ProofError) Error() string
func (*ProofError) Is ¶
func (e *ProofError) Is(target error) bool
type Transport ¶ added in v0.4.0
type Transport string
Transport selects the channel whose identity limits are being queried.
type User ¶
type User struct {
Kind string // "windows" or "posix"
// Windows.
SID string // e.g. "S-1-5-21-...-1001"
Name string // account name, best effort, may be empty
Domain string // account domain, best effort, may be empty
Elevated bool // the peer's token is elevated
// Integrity is the peer's mandatory integrity level: "untrusted",
// "low", "medium", "high", "system", or "unknown".
//
// It matters more than it looks. Two processes at the same integrity
// level running as the same user are not isolated from each other on
// Windows: either can open the other for full access and write into
// it. So the answer this package gives about a same-user, same-level
// peer identifies a program but does not defend against one. The
// boundary that does hold is a lower level talking to a higher one - a
// medium-integrity process cannot touch a high-integrity one, and an
// AppContainer (low or untrusted) cannot touch either. See CONTRACT.md.
Integrity string
// IntegrityRID is the raw mandatory label RID behind Integrity, for
// comparisons the named levels are too coarse for. Zero when unknown.
IntegrityRID uint32
// POSIX.
UID int // -1 when unknown
GID int // -1 when unknown
// SecurityContext is the peer's LSM label on Linux: an SELinux context
// ("unconfined_u:unconfined_r:unconfined_t:s0") or an AppArmor profile
// ("/usr/bin/evince (enforce)"), read from SO_PEERSEC.
//
// It belongs to the principal rather than to the process because that
// is what it is - a label the kernel attaches to a subject at exec, by
// local policy - and it is stamped onto the socket at connect, so it
// carries the same ProofKernel strength as the uid beside it. On an
// AppArmor system it is worth more than the image path: the path names
// a file, the profile names the policy the kernel is actually enforcing
// against the peer. It is not a signature and never becomes one.
//
// Empty when no LSM labels processes on this system, and on every
// platform other than Linux.
SecurityContext string
// AuditSessionID is the macOS audit session the peer belongs to, from
// its audit token. Zero elsewhere. Two processes in the same audit
// session are, roughly, the same login session.
AuditSessionID uint32
}
User is a security principal. Which fields are populated depends on the platform; Kind says which.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
cmd
|
|
|
peerid
command
Command peerid shows what this machine can prove about the program on the other end of a local connection, and then attacks it.
|
Command peerid shows what this machine can prove about the program on the other end of a local connection, and then attacks it. |
|
go
|
|
|
internal
|
|
|
integrity
Package integrity reads the mandatory integrity level Windows stamps on a token.
|
Package integrity reads the mandatory integrity level Windows stamps on a token. |
|
Package remote supplies explicitly trusted, mutually authenticated TLS streams for the shared frame protocol.
|
Package remote supplies explicitly trusted, mutually authenticated TLS streams for the shared frame protocol. |