Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GetToken check-in message framing #105

Merged
merged 6 commits into from
Jun 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion cmd/nanomdm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,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 != "" {
logger.Debug("msg", "declarative management setup", "url", *flDMURLPfx)
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 @@ -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)
Expand Down
25 changes: 25 additions & 0 deletions service/nanomdm/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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{
Expand Down Expand Up @@ -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 {
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