Skip to content

xaionaro-go/spdm

Repository files navigation

spdm

Go Reference Go Report Card License: MIT

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
Loading

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
Loading

How It Works

  1. 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
  2. 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.

  3. 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
}

CLI Tools

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

Releases

No releases published

Packages

 
 
 

Contributors

Languages