# How setupProtocol in ipatool Handles Plist-Encoded HTTPS Requests to Apple Servers

> Learn how ipatool's setupProtocol manages plist-encoded HTTPS requests to Apple servers. It uses a two-step SAP bootstrap and the howett.net/plist library to exchange XML-plist messages.

- Repository: [Majd/ipatool](https://github.com/majd/ipatool)
- Tags: internals
- Published: 2026-09-06

---

**The `setupProtocol` type in ipatool implements a two-step SAP (Signing Apple Protocol) bootstrap that wraps the standard `net/http` client to exchange XML-plist messages with Apple's signing services, extracting binary certificates and setup buffers via the `howett.net/plist` library.**

The `majd/ipatool` repository provides tools for interacting with Apple's infrastructure, and the `setupProtocol` struct—defined in [`internal/sap/protocol.go`](https://github.com/majd/ipatool/blob/main/internal/sap/protocol.go)—serves as the low-level transport layer responsible for plist-encoded HTTPS communication. This component manages the initial handshake required to establish a trusted signing session with Apple's servers, handling both certificate retrieval and encrypted buffer exchange using Apple's proprietary XML property list format.

## The Two-Step SAP Bootstrap Workflow

The `setupProtocol` type orchestrates a mandatory bootstrap sequence required by Apple's signing architecture. According to the source code in [`internal/sap/protocol.go`](https://github.com/majd/ipatool/blob/main/internal/sap/protocol.go), the implementation follows a strict two-phase pattern to initialize the SAP runtime.

### Step 1: Fetching the SAP Certificate

The first phase retrieves the cryptographic certificate needed for subsequent signing operations. The `certificate(ctx, endpoint)` method constructs a **GET** request with the User-Agent header set to `apphttp.DefaultUserAgent` (defined in [`pkg/http/constants.go`](https://github.com/majd/ipatool/blob/main/pkg/http/constants.go)) to mimic official Apple configuration tools.

```go
// Conceptual flow based on internal/sap/protocol.go
func (p *setupProtocol) certificate(ctx context.Context, endpoint string) ([]byte, error) {
    req, _ := http.NewRequestWithContext(ctx, "GET", endpoint, nil)
    req.Header.Set("User-Agent", apphttp.DefaultUserAgent)
    
    body, err := p.send(req) // Enforces 1MiB maxSetupBody limit
    if err != nil {
        return nil, fmt.Errorf("failed to fetch certificate: %w", err)
    }
    
    return plistBytes(body, setupCertificateKey) // Extracts "sign-sap-setup-cert"
}

```

The response body passes through `plistBytes(body, setupCertificateKey)`, which unmarshals the XML-plist using `howett.net/plist` and extracts the binary data associated with the **`sign-sap-setup-cert`** key. The implementation caps the response size at **1 MiB** (`maxSetupBody`) to prevent memory exhaustion from malformed server responses.

### Step 2: Exchanging the Setup Buffer

The second phase transmits the SAP runtime's initialization payload and receives the server's response. The `exchange(ctx, endpoint, input)` method marshals a `map[string]any` containing the **`sign-sap-setup-buffer`** key into an XML-plist format before transmission.

```go
// Based on internal/sap/protocol.go implementation
func (p *setupProtocol) exchange(ctx context.Context, endpoint string, input []byte) ([]byte, error) {
    payload := map[string]any{setupBufferKey: input} // "sign-sap-setup-buffer"
    plistData, _ := plist.Marshal(payload, plist.XMLFormat)
    
    req, _ := http.NewRequestWithContext(ctx, "POST", endpoint, bytes.NewReader(plistData))
    req.Header.Set("Content-Type", "application/x-plist")
    
    body, err := p.send(req)
    if err != nil {
        return nil, fmt.Errorf("failed to exchange setup message: %w", err)
    }
    
    return plistBytes(body, setupBufferKey) // Extracts "sign-sap-setup-buffer"
}

```

This method sends a **POST** request with `Content-Type: application/x-plist` and returns the binary contents of the `sign-sap-setup-buffer` key from the server's response plist.

## Internal Plumbing and Safety Mechanisms

### The send() Helper Function

Both steps rely on the unexported `send(request)` method to execute HTTP requests. This helper enforces several critical safety checks:

- Validates that the server returns **HTTP 200 OK**
- Applies a **1 MiB** maximum body size limit via `maxSetupBody`
- Returns raw response bytes for plist parsing

Errors at this layer receive context-rich wrapping (e.g., "apple returned ...") to aid debugging when Apple's servers respond with non-200 status codes.

### Plist Handling with howett.net/plist

The `plistBytes` helper function centralizes XML-plist parsing. As implemented in [`internal/sap/protocol.go`](https://github.com/majd/ipatool/blob/main/internal/sap/protocol.go), this function:

1. Unmarshals XML data into a `map[string]any` using `plist.Unmarshal`
2. Validates the presence of the expected key (either `sign-sap-setup-cert` or `sign-sap-setup-buffer`)
3. Type-asserts the value to `[]byte` and returns the binary payload

If the expected key is missing or contains non-binary data, the function returns a descriptive error stating "Apple plist is missing ...".

## Practical Usage Example

While higher-level components like `NewSigner` in [`internal/sap/signer_local.go`](https://github.com/majd/ipatool/blob/main/internal/sap/signer_local.go) automatically invoke `setupProtocol`, the following example demonstrates manual interaction:

```go
ctx := context.Background()
proto := internal.sap.setupProtocol{
    client: &http.Client{Timeout: 30 * time.Second},
}

// Example: Fetching the SAP certificate
cert, err := proto.certificate(ctx, "https://example.com/sap/certificate")
if err != nil {
    log.Fatalf("failed to get SAP certificate: %v", err)
}
fmt.Printf("Got %d-byte certificate\n", len(cert))

// Example: Exchanging a setup buffer
requestBuf := []byte{0x01, 0x02, 0x03} // Binary payload from the SAP runtime
reply, err := proto.exchange(ctx, "https://example.com/sap/setup", requestBuf)
if err != nil {
    log.Fatalf("SAP exchange failed: %v", err)
}
fmt.Printf("Received %d-byte reply\n", len(reply))

```

## Key Implementation Files

The SAP protocol implementation spans three primary files in the `majd/ipatool` repository:

- **[`internal/sap/protocol.go`](https://github.com/majd/ipatool/blob/main/internal/sap/protocol.go)**: Contains the core `setupProtocol` type with `certificate()`, `exchange()`, `send()`, and `plistBytes()` functions
- **[`internal/sap/signer_local.go`](https://github.com/majd/ipatool/blob/main/internal/sap/signer_local.go)**: Implements the high-level `Signer` that consumes `setupProtocol` during session initialization via `NewSigner`
- **[`pkg/http/constants.go`](https://github.com/majd/ipatool/blob/main/pkg/http/constants.go)**: Defines `DefaultUserAgent` and other HTTP-related constants required for Apple server compatibility

## Summary

- **`setupProtocol`** wraps the standard `net/http` client to handle Apple's SAP bootstrap sequence over HTTPS
- The implementation performs a **two-step handshake**: first fetching a certificate via GET, then exchanging binary buffers via POST
- All communication uses **XML-plist encoding** with `Content-Type: application/x-plist`, processed through the `howett.net/plist` library
- **Safety limits** include a 1 MiB response body cap (`maxSetupBody`) and strict HTTP 200 validation in the `send()` method
- Binary payloads extract from specific plist keys: **`sign-sap-setup-cert`** and **`sign-sap-setup-buffer`**
- The **User-Agent** header mimics official Apple tools to ensure server acceptance

## Frequently Asked Questions

### What is the SAP protocol used by ipatool?

The SAP (Signing Apple Protocol) is Apple's proprietary authentication mechanism required to establish trusted signing sessions. According to the `majd/ipatool` source code, `setupProtocol` implements the initial bootstrap phase of SAP, which involves retrieving a server certificate and exchanging encrypted setup buffers before any actual code signing can occur.

### Why does setupProtocol use XML-plist instead of JSON?

Apple's signing infrastructure mandates XML property lists (plists) for configuration and metadata exchange. The `setupProtocol` type in [`internal/sap/protocol.go`](https://github.com/majd/ipatool/blob/main/internal/sap/protocol.go) uses the `howett.net/plist` library to marshal Go maps into XML-plist format and unmarshal responses, specifically targeting keys like `sign-sap-setup-cert` that contain binary certificate data.

### How does ipatool prevent memory exhaustion during SAP setup?

The implementation enforces a **1 MiB** size limit on all SAP setup responses through the `maxSetupBody` constant. The `send()` method in [`internal/sap/protocol.go`](https://github.com/majd/ipatool/blob/main/internal/sap/protocol.go) reads the response body with `io.LimitReader`, ensuring that malformed or malicious server responses cannot cause out-of-memory errors during the plist unmarshaling process.

### Can I use setupProtocol independently of the Signer implementation?

While technically possible (as shown in the code examples), `setupProtocol` is designed as an internal unexported type consumed by `NewSigner` in [`internal/sap/signer_local.go`](https://github.com/majd/ipatool/blob/main/internal/sap/signer_local.go). Direct usage requires manual management of the SAP runtime state and proper binary payload construction, which the higher-level `Signer` abstraction handles automatically during `NewSigner` initialization.