spdm

A pure-Go implementation of the SPDM (Security Protocol and Data Model) protocol, as defined in DMTF DSP0274.
WARNING: This library has NOT been tested against real hardware.
It has only been validated through unit tests, end-to-end loopback tests, and reference interoperability tests against the DMTF spdm-emu (libspdm) reference implementation running in Docker. Do not use in production environments that depend on hardware-verified correctness without performing your own hardware validation first.
Architecture
The codebase follows the three-layer architecture defined by the SPDM specification (DSP0274 Section 6):
graph TB
subgraph "Application Layer"
spdm["pkg/spdm<br/><i>High-level facade</i>"]
req["pkg/requester<br/><i>Requester (initiator) logic</i>"]
rsp["pkg/responder<br/><i>Responder logic</i>"]
end
subgraph "Protocol Layer"
msgs["pkg/gen/msgs<br/><i>Typed message structs</i>"]
raw["pkg/gen/raw<br/><i>Generated C-to-Go structs</i>"]
algo["pkg/gen/algo<br/><i>Algorithm identifiers</i>"]
caps["pkg/gen/caps<br/><i>Capability flags</i>"]
codes["pkg/gen/codes<br/><i>Request/Response/Error codes</i>"]
status["pkg/gen/status<br/><i>Status codes</i>"]
session["pkg/session<br/><i>Key derivation, encrypt/decrypt</i>"]
crypto["pkg/crypto<br/><i>Crypto abstraction layer</i>"]
end
subgraph "Transport Layer"
transport["pkg/transport<br/><i>Transport interface</i>"]
tcpt["pkg/transport/tcp<br/><i>SPDM-over-TCP (DSP0287)</i>"]
mctp["pkg/transport/mctp<br/><i>SPDM-over-MCTP (DSP0239)</i>"]
pcidoe["pkg/transport/pcidoe<br/><i>SPDM-over-PCI DOE</i>"]
storage["pkg/transport/storage<br/><i>SPDM-over-Storage (DSP0286)</i>"]
end
spdm --> req
spdm --> rsp
req --> msgs
req --> session
req --> crypto
req --> transport
rsp --> msgs
rsp --> session
rsp --> crypto
rsp --> transport
msgs --> raw
msgs --> codes
msgs --> algo
session --> crypto
session --> algo
tcpt --> transport
mctp --> transport
pcidoe --> transport
storage --> transport
Package Relationships
-
pkg/spdm is the consumer-facing facade. It wraps pkg/requester and pkg/responder with simplified types. Provider interfaces (e.g. CertProvider, MeasurementProvider) are type aliases to pkg/responder interfaces, so users only import pkg/spdm.
-
pkg/requester and pkg/responder implement the SPDM protocol state machines. They use pkg/gen/msgs for message serialization, pkg/crypto for cryptographic operations, pkg/session for secure session management, and pkg/transport for wire communication.
-
pkg/gen/msgs contains typed Go structs for every SPDM message (VERSION, CAPABILITIES, ALGORITHMS, CERTIFICATE, CHALLENGE, KEY_EXCHANGE, etc.) with Marshal()/Unmarshal() methods. The MessageHeader struct embeds raw.SPDMMessageHeader.
-
pkg/gen/raw contains machine-generated Go translations of every typedef struct in the libspdm C header. These are raw field-for-field translations used as embedded types by pkg/gen/msgs.
-
pkg/gen/algo, pkg/gen/caps, pkg/gen/codes contain generated constants mapping SPDM algorithm identifiers, capability flags, and request/response/error codes from the C #define values.
-
pkg/crypto defines interfaces (HashProvider, Verifier, KeyAgreement, AEAD, PSKProvider) and a Suite struct that groups them. pkg/crypto/stdlib provides a concrete implementation using Go's standard library.
-
pkg/session handles SPDM secure session lifecycle: HKDF key derivation, handshake/data key generation, AEAD encrypt/decrypt, and sequence number management.
-
pkg/transport defines the Transport interface (SendMessage/ReceiveMessage/HeaderSize). Sub-packages provide wire-format implementations for TCP, MCTP, PCI-DOE, and Storage bindings.
Code Generation
Type definitions are generated from the official DMTF libspdm C headers, pinned as a git submodule at spec/libspdm/ (v3.8.2).
flowchart LR
subgraph "Source (git submodule)"
header["spec/libspdm/include/<br/>industry_standard/spdm.h"]
end
subgraph "Parser"
cheader["internal/cheader<br/><i>C99 AST parser<br/>modernc.org/cc/v4</i>"]
end
subgraph "Generator"
spdmgen["tools/spdmgen<br/><i>Go code generator</i>"]
end
subgraph "Generated Output"
raw2["pkg/gen/raw/types.go<br/><i>Go struct translations</i>"]
algo2["pkg/gen/algo/*.go<br/><i>Algorithm constants</i>"]
caps2["pkg/gen/caps/*.go<br/><i>Capability flags</i>"]
codes2["pkg/gen/codes/*.go<br/><i>Request/Response/Error codes</i>"]
end
header --> cheader --> spdmgen
spdmgen --> raw2
spdmgen --> algo2
spdmgen --> caps2
spdmgen --> codes2
How It Works
-
internal/cheader uses modernc.org/cc/v4 (a pure-Go C99 compiler frontend) to parse the libspdm header into an AST. It extracts:
- All
#define constants with integer values (via cc.Translate() + macro evaluation)
- All
typedef struct definitions with field names, types, and array lengths
-
tools/spdmgen consumes the parsed result and generates Go source files through mapping tables that translate C define name prefixes to Go types. For example, SPDM_ALGORITHMS_BASE_HASH_ALGO_TPM_ALG_SHA_256 becomes algo.HashSHA256.
-
The generator produces four packages:
raw: Field-for-field Go struct translations of every C typedef struct
algo: Typed constants for hash, asymmetric, DHE, AEAD, and measurement algorithms
caps: Bitmask constants for requester and responder capabilities
codes: Request codes, response codes, and error codes with string representations
To regenerate after updating the libspdm submodule:
go generate ./pkg/gen/...
Usage Examples
Requester: Connect and Authenticate
package main
import (
"context"
"fmt"
"log"
"net"
"github.com/xaionaro-go/spdm/pkg/crypto/stdlib"
"github.com/xaionaro-go/spdm/pkg/gen/algo"
"github.com/xaionaro-go/spdm/pkg/gen/caps"
"github.com/xaionaro-go/spdm/pkg/spdm"
"github.com/xaionaro-go/spdm/pkg/transport/tcp"
)
func main() {
ctx := context.Background()
// Connect to an SPDM responder over TCP.
conn, err := net.Dial("tcp", "127.0.0.1:2323")
if err != nil {
log.Fatal(err)
}
defer conn.Close()
// Create an SPDM requester with desired algorithms.
req := spdm.NewRequester(spdm.RequesterConfig{
Versions: []algo.Version{algo.Version12},
Transport: tcp.New(conn),
Crypto: *stdlib.NewSuite(nil, nil),
Caps: caps.ReqCertCap | caps.ReqChalCap,
BaseAsymAlgo: algo.AsymECDSAP256,
BaseHashAlgo: algo.HashSHA256,
DHEGroups: algo.DHESECP256R1,
AEADSuites: algo.AEADAES128GCM,
DataTransferSize: 4096,
MaxSPDMmsgSize: 65536,
})
// Negotiate version, capabilities, and algorithms.
ci, err := req.InitConnection(ctx)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Connected: version=%s, hash=%s, asym=%s\n",
ci.Version, ci.HashAlgo, ci.AsymAlgo)
// Retrieve certificate digests and chain.
if _, err := req.GetDigests(ctx); err != nil {
log.Fatal(err)
}
cert, err := req.GetCertificate(ctx, 0)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Certificate chain: %d bytes\n", len(cert.Chain))
// Challenge the responder (proves identity).
result, err := req.Challenge(ctx, 0)
if err != nil {
log.Fatal(err)
}
fmt.Printf("Challenge succeeded for slot %d\n", result.SlotID)
}
Requester: Establish a Secure Session
// After InitConnection + GetDigests + GetCertificate...
// Perform DHE key exchange to establish a secure session.
sess, err := req.KeyExchange(ctx, spdm.KeyExchangeOpts{
SlotID: 0,
HashType: 0xFF,
})
if err != nil {
log.Fatal(err)
}
// Send/receive data within the encrypted session.
response, err := sess.SendReceive(ctx, []byte("hello from requester"))
if err != nil {
log.Fatal(err)
}
fmt.Printf("Received: %s\n", response)
// Send a heartbeat.
if err := sess.Heartbeat(ctx); err != nil {
log.Fatal(err)
}
// Close the session.
if err := sess.Close(ctx); err != nil {
log.Fatal(err)
}
Responder: Serve SPDM Requests
package main
import (
"context"
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"log"
"net"
"github.com/xaionaro-go/spdm/pkg/crypto/stdlib"
"github.com/xaionaro-go/spdm/pkg/gen/algo"
"github.com/xaionaro-go/spdm/pkg/gen/caps"
"github.com/xaionaro-go/spdm/pkg/spdm"
"github.com/xaionaro-go/spdm/pkg/transport/tcp"
)
func main() {
ctx := context.Background()
// Generate an ephemeral device identity key.
key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
// Build the SPDM certificate chain (see cmd/spdm-responder for full example).
certChainBytes, certPool := buildCertChain(key) // your implementation
ln, _ := net.Listen("tcp", "127.0.0.1:2323")
defer ln.Close()
for {
conn, _ := ln.Accept()
rsp := spdm.NewResponder(spdm.ResponderConfig{
Versions: []algo.Version{algo.Version12},
Transport: tcp.New(conn),
Crypto: *stdlib.NewSuite(key, certPool),
Caps: caps.RspCertCap | caps.RspChalCap | caps.RspMeasCapNoSig,
BaseAsymAlgo: algo.AsymECDSAP256,
BaseHashAlgo: algo.HashSHA256,
DHEGroups: algo.DHESECP256R1,
AEADSuites: algo.AEADAES128GCM,
DataTransferSize: 4096,
MaxSPDMmsgSize: 65536,
CertProvider: yourCertProvider, // implements spdm.CertProvider
MeasProvider: yourMeasProvider, // implements spdm.MeasurementProvider
})
// Serve blocks, handling all incoming SPDM requests.
if err := rsp.Serve(ctx); err != nil {
log.Printf("session ended: %v", err)
}
conn.Close()
}
}
Implementing Provider Interfaces
The responder requires provider implementations for device-specific data:
// CertProvider supplies certificate chains and digests.
type CertProvider interface {
CertChain(ctx context.Context, slotID uint8) ([]byte, error)
DigestForSlot(ctx context.Context, slotID uint8) ([]byte, error)
}
// MeasurementProvider supplies device measurements.
type MeasurementProvider interface {
Collect(ctx context.Context, index uint8) ([]msgs.MeasurementBlock, error)
SummaryHash(ctx context.Context, hashType uint8) ([]byte, error)
}
See cmd/spdm-responder/ for a complete working example with ephemeral certificates and static measurements.
Using Different Transports
// TCP (DSP0287)
import "github.com/xaionaro-go/spdm/pkg/transport/tcp"
t := tcp.New(conn)
// MCTP (DSP0239) — implement transport.Transport over your MCTP layer
import "github.com/xaionaro-go/spdm/pkg/transport/mctp"
// PCI DOE — implement transport.Transport over your DOE mailbox
import "github.com/xaionaro-go/spdm/pkg/transport/pcidoe"
// Storage (DSP0286) — implement transport.Transport over your storage interface
import "github.com/xaionaro-go/spdm/pkg/transport/storage"
All transports implement the same transport.Transport interface:
type Transport interface {
SendMessage(ctx context.Context, sessionID *uint32, msg []byte) error
ReceiveMessage(ctx context.Context) (sessionID *uint32, msg []byte, err error)
HeaderSize() int
}
The repository includes example CLI tools:
# Start a responder
go run ./cmd/spdm-responder -listen 127.0.0.1:2323 -v
# In another terminal, connect and authenticate
go run ./cmd/spdm-requester connect -addr 127.0.0.1:2323 -v
go run ./cmd/spdm-requester get-cert -addr 127.0.0.1:2323
go run ./cmd/spdm-requester challenge -addr 127.0.0.1:2323
go run ./cmd/spdm-requester get-meas -addr 127.0.0.1:2323
Tests
Unit Tests
Package-level tests covering message serialization, capability validation, algorithm mapping, error codes, crypto operations, session key derivation, and handler logic:
go test ./...
End-to-End Loopback Tests
SPDM handshake tests (test/e2e/) that connect a Go requester to a Go responder in-process, verifying the complete protocol flow including version negotiation, capabilities exchange, certificate retrieval, challenge-response authentication, and secure session establishment.
Reference Interoperability Tests
Tests against the DMTF spdm-emu reference implementation (libspdm) running in Docker. These validate that the Go implementation correctly interoperates with the official C reference:
cd tests/reference
make test
This builds a Docker image containing both the spdm-emu binaries and the Go test suite, then runs the interop tests. Requires Docker with BuildKit support.
The reference tests cover: transport-level communication, handshake flows, certificate exchange, measurements retrieval, and secured sessions.
License
MIT