Skip to content

Commit

Permalink
GetToken check-in message framing (#105)
Browse files Browse the repository at this point in the history
  • Loading branch information
jessepeterson authored Jun 13, 2024
1 parent 3544fbb commit 3edec93
Show file tree
Hide file tree
Showing 14 changed files with 340 additions and 1 deletion.
5 changes: 4 additions & 1 deletion cmd/nanomdm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,13 @@ func main() {
stdlog.Fatal(err)
}

tokenMux := nanomdm.NewTokenMux()

// create 'core' MDM service
nanoOpts := []nanomdm.Option{
nanomdm.WithLogger(logger.With("service", "nanomdm")),
nanomdm.WithUserAuthenticate(nanomdm.NewUAService(mdmStorage, *flUAZLChal)),
nanomdm.WithGetToken(tokenMux),
nanomdm.WithLogger(logger.With("service", "nanomdm")),
}
if *flDMURLPfx != "" {
var warningText string
Expand Down
1 change: 1 addition & 0 deletions docs/enroll.mobileconfig
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<array>
<string>com.apple.mdm.per-user-connections</string>
<string>com.apple.mdm.bootstraptoken</string>
<string>com.apple.mdm.token</string>
</array>
<key>ServerURL</key>
<string>https://mdm.example.org/mdm</string>
Expand Down
40 changes: 40 additions & 0 deletions mdm/checkin.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,44 @@ type DeclarativeManagement struct {
Raw []byte `plist:"-"` // Original XML plist
}

// TokenParameters is a representation of a "GetTokenRequest.TokenParameters" structure.
// See https://developer.apple.com/documentation/devicemanagement/gettokenrequest/tokenparameters
type TokenParameters struct {
PhoneUDID string
SecurityToken string
WatchUDID string
}

// GetTokenResponse is a representation of a "GetTokenResponse" structure.
// See https://developer.apple.com/documentation/devicemanagement/gettokenresponse
type GetTokenResponse struct {
TokenData []byte
}

// GetToken is a representation of a "GetToken" check-in message type.
// See https://developer.apple.com/documentation/devicemanagement/get_token
type GetToken struct {
Enrollment
MessageType
TokenServiceType string
TokenParameters *TokenParameters `plist:",omitempty"`
Raw []byte `plist:"-"` // Original XML plist
}

// Validate validates a GetToken check-in message.
func (m *GetToken) Validate() error {
if m == nil {
return errors.New("nil GetToken")
}
if m.TokenServiceType == "" {
return errors.New("empty GetToken TokenServiceType")
}
if m.TokenServiceType == "com.apple.watch.pairing" && m.TokenParameters == nil {
return fmt.Errorf("nil TokenParameters for GetToken: %s", m.TokenServiceType)
}
return nil
}

// newCheckinMessageForType returns a pointer to a check-in struct for MessageType t
func newCheckinMessageForType(t string, raw []byte) interface{} {
switch t {
Expand All @@ -120,6 +158,8 @@ func newCheckinMessageForType(t string, raw []byte) interface{} {
return &UserAuthenticate{Raw: raw}
case "DeclarativeManagement":
return &DeclarativeManagement{Raw: raw}
case "GetToken":
return &GetToken{Raw: raw}
default:
return nil
}
Expand Down
33 changes: 33 additions & 0 deletions mdm/checkin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,36 @@ func TestTokenUpdate(t *testing.T) {
})
}
}

func TestGetTokenMAID(t *testing.T) {
test := `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>MessageType</key>
<string>GetToken</string>
<key>UDID</key>
<string>test</string>
<key>TokenServiceType</key>
<string>com.apple.maid</string>
</dict>
</plist>
`
m, err := DecodeCheckin([]byte(test))
if err != nil {
t.Fatal(err)
}
msg, ok := m.(*GetToken)
if !ok {
t.Fatal("incorrect decoded check-in message type")
}
if err := msg.Validate(); err != nil {
t.Fatal(err)
}
if msg, want, have := "invalid UDID", "test", msg.UDID; have != want {
t.Errorf("%s: %q, want: %q", msg, have, want)
}
if msg, want, have := "invalid TokenServiceType", "com.apple.maid", msg.TokenServiceType; have != want {
t.Errorf("%s: %q, want: %q", msg, have, want)
}
}
4 changes: 4 additions & 0 deletions service/certauth/helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ func (s *NopService) DeclarativeManagement(r *mdm.Request, m *mdm.DeclarativeMan
return nil, nil
}

func (s *NopService) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
return nil, nil
}

func (s *NopService) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
return nil, nil
}
7 changes: 7 additions & 0 deletions service/certauth/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ func (s *CertAuth) DeclarativeManagement(r *mdm.Request, m *mdm.DeclarativeManag
return s.next.DeclarativeManagement(r, m)
}

func (s *CertAuth) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
if err := s.validateOrAssociateForExistingEnrollment(r, &m.Enrollment); err != nil {
return nil, err
}
return s.next.GetToken(r, m)
}

func (s *CertAuth) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
if err := s.validateOrAssociateForExistingEnrollment(r, &results.Enrollment); err != nil {
return nil, err
Expand Down
11 changes: 11 additions & 0 deletions service/dump/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package dump

import (
"encoding/base64"
"fmt"
"os"

Expand Down Expand Up @@ -70,6 +71,16 @@ func (svc *Dumper) GetBootstrapToken(r *mdm.Request, m *mdm.GetBootstrapToken) (
return bsToken, err
}

func (svc *Dumper) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
svc.file.Write(m.Raw)
token, err := svc.next.GetToken(r, m)
if token != nil && len(token.TokenData) > 0 {
b64 := base64.StdEncoding.EncodeToString(token.TokenData)
svc.file.WriteString("GetToken TokenData: " + b64 + "\n")
}
return token, err
}

func (svc *Dumper) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
svc.file.Write(results.Raw)
cmd, err := svc.next.CommandAndReportResults(r, results)
Expand Down
14 changes: 14 additions & 0 deletions service/microwebhook/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,3 +143,17 @@ func (w *MicroWebhook) DeclarativeManagement(r *mdm.Request, m *mdm.DeclarativeM
}
return nil, postWebhookEvent(r.Context, w.client, w.url, ev)
}

func (w *MicroWebhook) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
ev := &Event{
Topic: "mdm.GetToken",
CreatedAt: time.Now(),
CheckinEvent: &CheckinEvent{
UDID: m.UDID,
EnrollmentID: m.EnrollmentID,
RawPayload: m.Raw,
Params: r.Params,
},
}
return nil, postWebhookEvent(r.Context, w.client, w.url, ev)
}
10 changes: 10 additions & 0 deletions service/multi/multi.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,16 @@ func (ms *MultiService) DeclarativeManagement(r *mdm.Request, m *mdm.Declarative
return retBytes, err
}

func (ms *MultiService) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
resp, err := ms.svcs[0].GetToken(r, m)
rc := ms.RequestWithContext(r)
ms.runOthers(r.Context, func(svc service.CheckinAndCommandService) error {
_, err := svc.GetToken(rc, m)
return err
})
return resp, err
}

func (ms *MultiService) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
cmd, err := ms.svcs[0].CommandAndReportResults(r, results)
rc := ms.RequestWithContext(r)
Expand Down
25 changes: 25 additions & 0 deletions service/nanomdm/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ type Service struct {

// UserAuthenticate processor
ua service.UserAuthenticate

// GetToken handler
gt service.GetToken
}

// normalize generates enrollment IDs that are used by other
Expand Down Expand Up @@ -74,6 +77,13 @@ func WithUserAuthenticate(ua service.UserAuthenticate) Option {
}
}

// WithGetToken configures a GetToken check-in message handler.
func WithGetToken(gt service.GetToken) Option {
return func(s *Service) {
s.gt = gt
}
}

// New returns a new NanoMDM main service.
func New(store storage.ServiceStore, opts ...Option) *Service {
nanomdm := &Service{
Expand Down Expand Up @@ -189,6 +199,21 @@ func (s *Service) DeclarativeManagement(r *mdm.Request, message *mdm.Declarative
return s.dm.DeclarativeManagement(r, message)
}

// GetToken implements the GetToken Check-in message interface.
func (s *Service) GetToken(r *mdm.Request, message *mdm.GetToken) (*mdm.GetTokenResponse, error) {
if err := s.setupRequest(r, &message.Enrollment); err != nil {
return nil, err
}
ctxlog.Logger(r.Context, s.logger).Info(
"msg", "GetToken",
"token_service_type", message.TokenServiceType,
)
if s.gt == nil {
return nil, errors.New("no GetToken handler")
}
return s.gt.GetToken(r, message)
}

// CommandAndReportResults command report and next-command request implementation.
func (s *Service) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
if err := s.setupRequest(r, &results.Enrollment); err != nil {
Expand Down
71 changes: 71 additions & 0 deletions service/nanomdm/token.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package nanomdm

import (
"fmt"
"sync"

"github.com/micromdm/nanomdm/mdm"
"github.com/micromdm/nanomdm/service"
)

// StaticToken holds static token bytes.
type StaticToken struct {
token []byte
}

// NewStaticToken creates a new static token handler.
func NewStaticToken(token []byte) *StaticToken {
return &StaticToken{token: token}
}

// GetToken always responds with the static token bytes.
func (t *StaticToken) GetToken(_ *mdm.Request, _ *mdm.GetToken) (*mdm.GetTokenResponse, error) {
return &mdm.GetTokenResponse{TokenData: t.token}, nil
}

// TokenMux is a middleware multiplexer for GetToken check-in messages.
// A TokenServiceType string is associated with a GetToken handler and
// then dispatched appropriately.
type TokenMux struct {
typesMu sync.RWMutex
types map[string]service.GetToken
}

// NewTokenMux creates a new TokenMux.
func NewTokenMux() *TokenMux { return &TokenMux{} }

// Handle registers a GetToken handler for the given service type.
// See https://developer.apple.com/documentation/devicemanagement/gettokenrequest
func (mux *TokenMux) Handle(serviceType string, handler service.GetToken) {
if serviceType == "" {
panic("tokenmux: invalid service type")
}
if handler == nil {
panic("tokenmux: invalid handler")
}
mux.typesMu.Lock()
defer mux.typesMu.Unlock()
if mux.types == nil {
mux.types = make(map[string]service.GetToken)
} else if _, exists := mux.types[serviceType]; exists {
panic("tokenmux: multiple registrations for " + serviceType)
}
mux.types[serviceType] = handler
}

// GetToken is the middleware that dispatches a GetToken handler based on service type.
func (mux *TokenMux) GetToken(r *mdm.Request, t *mdm.GetToken) (*mdm.GetTokenResponse, error) {
if t == nil {
return nil, fmt.Errorf("nil MDM GetToken")
}
var next service.GetToken
mux.typesMu.RLock()
if mux.types != nil {
next = mux.types[t.TokenServiceType]
}
mux.typesMu.RUnlock()
if next == nil {
return nil, fmt.Errorf("no handler for TokenServiceType: %v", t.TokenServiceType)
}
return next.GetToken(r, t)
}
Loading

0 comments on commit 3edec93

Please sign in to comment.