From 79e4806b08289edae4b296a21b089d00aa2a003a Mon Sep 17 00:00:00 2001 From: Jesse Peterson Date: Fri, 23 Feb 2024 00:42:54 -0800 Subject: [PATCH] GetToken check-in message scaffolding --- mdm/checkin.go | 46 ++++++++++++++++++++++++++++++++ mdm/checkin_test.go | 33 +++++++++++++++++++++++ service/certauth/helpers_test.go | 4 +++ service/certauth/service.go | 7 +++++ service/dump/dump.go | 11 ++++++++ service/microwebhook/service.go | 14 ++++++++++ service/multi/multi.go | 10 +++++++ service/nanomdm/service.go | 25 +++++++++++++++++ service/request.go | 16 +++++++++++ service/service.go | 7 +++++ 10 files changed, 173 insertions(+) diff --git a/mdm/checkin.go b/mdm/checkin.go index db9a38a..3df78e0 100644 --- a/mdm/checkin.go +++ b/mdm/checkin.go @@ -103,6 +103,50 @@ 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") + } + switch m.TokenServiceType { + case "com.apple.maid": + case "com.apple.watch.pairing": + if m.TokenParameters == nil { + return fmt.Errorf("empty GetToken TokenParameters for %s TokenServiceType", m.TokenServiceType) + } + default: + return fmt.Errorf("invalid GetToken TokenServiceType: %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 { @@ -120,6 +164,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 } diff --git a/mdm/checkin_test.go b/mdm/checkin_test.go index 6701a86..aaf4aca 100644 --- a/mdm/checkin_test.go +++ b/mdm/checkin_test.go @@ -109,3 +109,36 @@ func TestTokenUpdate(t *testing.T) { }) } } + +func TestGetTokenMAID(t *testing.T) { + test := ` + + + + MessageType + GetToken + UDID + test + TokenServiceType + com.apple.maid + + +` + 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) + } +} diff --git a/service/certauth/helpers_test.go b/service/certauth/helpers_test.go index cb4b4f9..27f38a9 100644 --- a/service/certauth/helpers_test.go +++ b/service/certauth/helpers_test.go @@ -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 } diff --git a/service/certauth/service.go b/service/certauth/service.go index fd0af1a..65670ea 100644 --- a/service/certauth/service.go +++ b/service/certauth/service.go @@ -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 diff --git a/service/dump/dump.go b/service/dump/dump.go index 9708ba0..8a70924 100644 --- a/service/dump/dump.go +++ b/service/dump/dump.go @@ -2,6 +2,7 @@ package dump import ( + "encoding/base64" "fmt" "os" @@ -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) diff --git a/service/microwebhook/service.go b/service/microwebhook/service.go index 7ca33d3..e1ba749 100644 --- a/service/microwebhook/service.go +++ b/service/microwebhook/service.go @@ -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) +} diff --git a/service/multi/multi.go b/service/multi/multi.go index 17df36b..b7c9a22 100644 --- a/service/multi/multi.go +++ b/service/multi/multi.go @@ -121,6 +121,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) diff --git a/service/nanomdm/service.go b/service/nanomdm/service.go index 2925ef0..c19cbfe 100644 --- a/service/nanomdm/service.go +++ b/service/nanomdm/service.go @@ -23,6 +23,9 @@ type Service struct { // UserAuthenticate processor ua service.UserAuthenticate + + // GetToken handler + gt service.GetToken } // normalize generates enrollment IDs that are used by other @@ -73,6 +76,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{ @@ -188,6 +198,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 { diff --git a/service/request.go b/service/request.go index 6139ebc..92e9af7 100644 --- a/service/request.go +++ b/service/request.go @@ -78,6 +78,22 @@ func CheckinRequest(svc Checkin, r *mdm.Request, bodyBytes []byte) ([]byte, erro if err != nil { err = fmt.Errorf("declarativemanagement service: %w", err) } + case *mdm.GetToken: + if err := m.Validate(); err != nil { + return nil, fmt.Errorf("gettoken validate: %w", err) + } + resp, err := svc.GetToken(r, m) + if err != nil { + return nil, fmt.Errorf("gettoken service: %w", err) + } + if resp == nil { + return nil, errors.New("gettoken service: no response") + } + respBytes, err = plist.Marshal(resp) + if err != nil { + return nil, fmt.Errorf("gettoken marshal: %w", err) + } + return respBytes, nil default: return nil, errors.New("unhandled check-in request type") } diff --git a/service/service.go b/service/service.go index 41f7225..ee57787 100644 --- a/service/service.go +++ b/service/service.go @@ -16,6 +16,12 @@ type UserAuthenticate interface { UserAuthenticate(*mdm.Request, *mdm.UserAuthenticate) ([]byte, error) } +// GetToken is the interface for handling a GetToken check-in message. +// See https://developer.apple.com/documentation/devicemanagement/get_token +type GetToken interface { + GetToken(*mdm.Request, *mdm.GetToken) (*mdm.GetTokenResponse, error) +} + // Checkin represents the various check-in requests. // See https://developer.apple.com/documentation/devicemanagement/check-in type Checkin interface { @@ -26,6 +32,7 @@ type Checkin interface { GetBootstrapToken(*mdm.Request, *mdm.GetBootstrapToken) (*mdm.BootstrapToken, error) UserAuthenticate DeclarativeManagement + GetToken } // CommandAndReportResults represents the command report and next-command request.