Skip to content

Commit 3edec93

Browse files
GetToken check-in message framing (#105)
1 parent 3544fbb commit 3edec93

File tree

14 files changed

+340
-1
lines changed

14 files changed

+340
-1
lines changed

cmd/nanomdm/main.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -112,10 +112,13 @@ func main() {
112112
stdlog.Fatal(err)
113113
}
114114

115+
tokenMux := nanomdm.NewTokenMux()
116+
115117
// create 'core' MDM service
116118
nanoOpts := []nanomdm.Option{
117-
nanomdm.WithLogger(logger.With("service", "nanomdm")),
118119
nanomdm.WithUserAuthenticate(nanomdm.NewUAService(mdmStorage, *flUAZLChal)),
120+
nanomdm.WithGetToken(tokenMux),
121+
nanomdm.WithLogger(logger.With("service", "nanomdm")),
119122
}
120123
if *flDMURLPfx != "" {
121124
var warningText string

docs/enroll.mobileconfig

+1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
<array>
4747
<string>com.apple.mdm.per-user-connections</string>
4848
<string>com.apple.mdm.bootstraptoken</string>
49+
<string>com.apple.mdm.token</string>
4950
</array>
5051
<key>ServerURL</key>
5152
<string>https://mdm.example.org/mdm</string>

mdm/checkin.go

+40
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,44 @@ type DeclarativeManagement struct {
103103
Raw []byte `plist:"-"` // Original XML plist
104104
}
105105

106+
// TokenParameters is a representation of a "GetTokenRequest.TokenParameters" structure.
107+
// See https://developer.apple.com/documentation/devicemanagement/gettokenrequest/tokenparameters
108+
type TokenParameters struct {
109+
PhoneUDID string
110+
SecurityToken string
111+
WatchUDID string
112+
}
113+
114+
// GetTokenResponse is a representation of a "GetTokenResponse" structure.
115+
// See https://developer.apple.com/documentation/devicemanagement/gettokenresponse
116+
type GetTokenResponse struct {
117+
TokenData []byte
118+
}
119+
120+
// GetToken is a representation of a "GetToken" check-in message type.
121+
// See https://developer.apple.com/documentation/devicemanagement/get_token
122+
type GetToken struct {
123+
Enrollment
124+
MessageType
125+
TokenServiceType string
126+
TokenParameters *TokenParameters `plist:",omitempty"`
127+
Raw []byte `plist:"-"` // Original XML plist
128+
}
129+
130+
// Validate validates a GetToken check-in message.
131+
func (m *GetToken) Validate() error {
132+
if m == nil {
133+
return errors.New("nil GetToken")
134+
}
135+
if m.TokenServiceType == "" {
136+
return errors.New("empty GetToken TokenServiceType")
137+
}
138+
if m.TokenServiceType == "com.apple.watch.pairing" && m.TokenParameters == nil {
139+
return fmt.Errorf("nil TokenParameters for GetToken: %s", m.TokenServiceType)
140+
}
141+
return nil
142+
}
143+
106144
// newCheckinMessageForType returns a pointer to a check-in struct for MessageType t
107145
func newCheckinMessageForType(t string, raw []byte) interface{} {
108146
switch t {
@@ -120,6 +158,8 @@ func newCheckinMessageForType(t string, raw []byte) interface{} {
120158
return &UserAuthenticate{Raw: raw}
121159
case "DeclarativeManagement":
122160
return &DeclarativeManagement{Raw: raw}
161+
case "GetToken":
162+
return &GetToken{Raw: raw}
123163
default:
124164
return nil
125165
}

mdm/checkin_test.go

+33
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,36 @@ func TestTokenUpdate(t *testing.T) {
109109
})
110110
}
111111
}
112+
113+
func TestGetTokenMAID(t *testing.T) {
114+
test := `<?xml version="1.0" encoding="UTF-8"?>
115+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
116+
<plist version="1.0">
117+
<dict>
118+
<key>MessageType</key>
119+
<string>GetToken</string>
120+
<key>UDID</key>
121+
<string>test</string>
122+
<key>TokenServiceType</key>
123+
<string>com.apple.maid</string>
124+
</dict>
125+
</plist>
126+
`
127+
m, err := DecodeCheckin([]byte(test))
128+
if err != nil {
129+
t.Fatal(err)
130+
}
131+
msg, ok := m.(*GetToken)
132+
if !ok {
133+
t.Fatal("incorrect decoded check-in message type")
134+
}
135+
if err := msg.Validate(); err != nil {
136+
t.Fatal(err)
137+
}
138+
if msg, want, have := "invalid UDID", "test", msg.UDID; have != want {
139+
t.Errorf("%s: %q, want: %q", msg, have, want)
140+
}
141+
if msg, want, have := "invalid TokenServiceType", "com.apple.maid", msg.TokenServiceType; have != want {
142+
t.Errorf("%s: %q, want: %q", msg, have, want)
143+
}
144+
}

service/certauth/helpers_test.go

+4
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,10 @@ func (s *NopService) DeclarativeManagement(r *mdm.Request, m *mdm.DeclarativeMan
8181
return nil, nil
8282
}
8383

84+
func (s *NopService) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
85+
return nil, nil
86+
}
87+
8488
func (s *NopService) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
8589
return nil, nil
8690
}

service/certauth/service.go

+7
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ func (s *CertAuth) DeclarativeManagement(r *mdm.Request, m *mdm.DeclarativeManag
5353
return s.next.DeclarativeManagement(r, m)
5454
}
5555

56+
func (s *CertAuth) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
57+
if err := s.validateOrAssociateForExistingEnrollment(r, &m.Enrollment); err != nil {
58+
return nil, err
59+
}
60+
return s.next.GetToken(r, m)
61+
}
62+
5663
func (s *CertAuth) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
5764
if err := s.validateOrAssociateForExistingEnrollment(r, &results.Enrollment); err != nil {
5865
return nil, err

service/dump/dump.go

+11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
package dump
33

44
import (
5+
"encoding/base64"
56
"fmt"
67
"os"
78

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

74+
func (svc *Dumper) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
75+
svc.file.Write(m.Raw)
76+
token, err := svc.next.GetToken(r, m)
77+
if token != nil && len(token.TokenData) > 0 {
78+
b64 := base64.StdEncoding.EncodeToString(token.TokenData)
79+
svc.file.WriteString("GetToken TokenData: " + b64 + "\n")
80+
}
81+
return token, err
82+
}
83+
7384
func (svc *Dumper) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
7485
svc.file.Write(results.Raw)
7586
cmd, err := svc.next.CommandAndReportResults(r, results)

service/microwebhook/service.go

+14
Original file line numberDiff line numberDiff line change
@@ -143,3 +143,17 @@ func (w *MicroWebhook) DeclarativeManagement(r *mdm.Request, m *mdm.DeclarativeM
143143
}
144144
return nil, postWebhookEvent(r.Context, w.client, w.url, ev)
145145
}
146+
147+
func (w *MicroWebhook) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
148+
ev := &Event{
149+
Topic: "mdm.GetToken",
150+
CreatedAt: time.Now(),
151+
CheckinEvent: &CheckinEvent{
152+
UDID: m.UDID,
153+
EnrollmentID: m.EnrollmentID,
154+
RawPayload: m.Raw,
155+
Params: r.Params,
156+
},
157+
}
158+
return nil, postWebhookEvent(r.Context, w.client, w.url, ev)
159+
}

service/multi/multi.go

+10
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,16 @@ func (ms *MultiService) DeclarativeManagement(r *mdm.Request, m *mdm.Declarative
122122
return retBytes, err
123123
}
124124

125+
func (ms *MultiService) GetToken(r *mdm.Request, m *mdm.GetToken) (*mdm.GetTokenResponse, error) {
126+
resp, err := ms.svcs[0].GetToken(r, m)
127+
rc := ms.RequestWithContext(r)
128+
ms.runOthers(r.Context, func(svc service.CheckinAndCommandService) error {
129+
_, err := svc.GetToken(rc, m)
130+
return err
131+
})
132+
return resp, err
133+
}
134+
125135
func (ms *MultiService) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
126136
cmd, err := ms.svcs[0].CommandAndReportResults(r, results)
127137
rc := ms.RequestWithContext(r)

service/nanomdm/service.go

+25
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ type Service struct {
2424

2525
// UserAuthenticate processor
2626
ua service.UserAuthenticate
27+
28+
// GetToken handler
29+
gt service.GetToken
2730
}
2831

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

80+
// WithGetToken configures a GetToken check-in message handler.
81+
func WithGetToken(gt service.GetToken) Option {
82+
return func(s *Service) {
83+
s.gt = gt
84+
}
85+
}
86+
7787
// New returns a new NanoMDM main service.
7888
func New(store storage.ServiceStore, opts ...Option) *Service {
7989
nanomdm := &Service{
@@ -189,6 +199,21 @@ func (s *Service) DeclarativeManagement(r *mdm.Request, message *mdm.Declarative
189199
return s.dm.DeclarativeManagement(r, message)
190200
}
191201

202+
// GetToken implements the GetToken Check-in message interface.
203+
func (s *Service) GetToken(r *mdm.Request, message *mdm.GetToken) (*mdm.GetTokenResponse, error) {
204+
if err := s.setupRequest(r, &message.Enrollment); err != nil {
205+
return nil, err
206+
}
207+
ctxlog.Logger(r.Context, s.logger).Info(
208+
"msg", "GetToken",
209+
"token_service_type", message.TokenServiceType,
210+
)
211+
if s.gt == nil {
212+
return nil, errors.New("no GetToken handler")
213+
}
214+
return s.gt.GetToken(r, message)
215+
}
216+
192217
// CommandAndReportResults command report and next-command request implementation.
193218
func (s *Service) CommandAndReportResults(r *mdm.Request, results *mdm.CommandResults) (*mdm.Command, error) {
194219
if err := s.setupRequest(r, &results.Enrollment); err != nil {

service/nanomdm/token.go

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package nanomdm
2+
3+
import (
4+
"fmt"
5+
"sync"
6+
7+
"github.com/micromdm/nanomdm/mdm"
8+
"github.com/micromdm/nanomdm/service"
9+
)
10+
11+
// StaticToken holds static token bytes.
12+
type StaticToken struct {
13+
token []byte
14+
}
15+
16+
// NewStaticToken creates a new static token handler.
17+
func NewStaticToken(token []byte) *StaticToken {
18+
return &StaticToken{token: token}
19+
}
20+
21+
// GetToken always responds with the static token bytes.
22+
func (t *StaticToken) GetToken(_ *mdm.Request, _ *mdm.GetToken) (*mdm.GetTokenResponse, error) {
23+
return &mdm.GetTokenResponse{TokenData: t.token}, nil
24+
}
25+
26+
// TokenMux is a middleware multiplexer for GetToken check-in messages.
27+
// A TokenServiceType string is associated with a GetToken handler and
28+
// then dispatched appropriately.
29+
type TokenMux struct {
30+
typesMu sync.RWMutex
31+
types map[string]service.GetToken
32+
}
33+
34+
// NewTokenMux creates a new TokenMux.
35+
func NewTokenMux() *TokenMux { return &TokenMux{} }
36+
37+
// Handle registers a GetToken handler for the given service type.
38+
// See https://developer.apple.com/documentation/devicemanagement/gettokenrequest
39+
func (mux *TokenMux) Handle(serviceType string, handler service.GetToken) {
40+
if serviceType == "" {
41+
panic("tokenmux: invalid service type")
42+
}
43+
if handler == nil {
44+
panic("tokenmux: invalid handler")
45+
}
46+
mux.typesMu.Lock()
47+
defer mux.typesMu.Unlock()
48+
if mux.types == nil {
49+
mux.types = make(map[string]service.GetToken)
50+
} else if _, exists := mux.types[serviceType]; exists {
51+
panic("tokenmux: multiple registrations for " + serviceType)
52+
}
53+
mux.types[serviceType] = handler
54+
}
55+
56+
// GetToken is the middleware that dispatches a GetToken handler based on service type.
57+
func (mux *TokenMux) GetToken(r *mdm.Request, t *mdm.GetToken) (*mdm.GetTokenResponse, error) {
58+
if t == nil {
59+
return nil, fmt.Errorf("nil MDM GetToken")
60+
}
61+
var next service.GetToken
62+
mux.typesMu.RLock()
63+
if mux.types != nil {
64+
next = mux.types[t.TokenServiceType]
65+
}
66+
mux.typesMu.RUnlock()
67+
if next == nil {
68+
return nil, fmt.Errorf("no handler for TokenServiceType: %v", t.TokenServiceType)
69+
}
70+
return next.GetToken(r, t)
71+
}

0 commit comments

Comments
 (0)