mirror of
https://github.com/micromdm/micromdm/
synced 2026-08-05 08:55:51 +08:00
184 lines
4.9 KiB
Go
184 lines
4.9 KiB
Go
package mdm
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"crypto/x509"
|
|
"encoding/base64"
|
|
"io/ioutil"
|
|
"net/http"
|
|
|
|
"github.com/go-kit/kit/endpoint"
|
|
"github.com/go-kit/kit/log"
|
|
httptransport "github.com/go-kit/kit/transport/http"
|
|
"github.com/gorilla/mux"
|
|
"github.com/micromdm/micromdm/pkg/crypto"
|
|
"github.com/micromdm/plist"
|
|
"github.com/pkg/errors"
|
|
"github.com/smallstep/pkcs7"
|
|
)
|
|
|
|
type Endpoints struct {
|
|
CheckinEndpoint endpoint.Endpoint
|
|
AcknowledgeEndpoint endpoint.Endpoint
|
|
}
|
|
|
|
func MakeServerEndpoints(s Service) Endpoints {
|
|
return Endpoints{
|
|
CheckinEndpoint: MakeCheckinEndpoint(s),
|
|
AcknowledgeEndpoint: MakeAcknowledgeEndpoint(s),
|
|
}
|
|
}
|
|
|
|
func RegisterHTTPHandlers(r *mux.Router, e Endpoints, v *crypto.PKCS7Verifier, logger log.Logger) {
|
|
options := []httptransport.ServerOption{
|
|
httptransport.ServerErrorEncoder(encodeError),
|
|
httptransport.ServerErrorLogger(logger),
|
|
httptransport.ServerBefore(httptransport.PopulateRequestContext),
|
|
httptransport.ServerBefore((verifier{PKCS7Verifier: v}).populateDeviceCertificateFromSignRequestHeader),
|
|
}
|
|
|
|
r.Methods(http.MethodPut).Path("/mdm/checkin").Handler(httptransport.NewServer(
|
|
e.CheckinEndpoint,
|
|
decodeCheckinRequest,
|
|
encodeResponse,
|
|
options...,
|
|
))
|
|
|
|
r.Methods(http.MethodPut).Path("/mdm/connect").Handler(httptransport.NewServer(
|
|
e.AcknowledgeEndpoint,
|
|
decodeAcknowledgeRequest,
|
|
encodeResponse,
|
|
options...,
|
|
))
|
|
}
|
|
|
|
type contextKey int
|
|
|
|
const (
|
|
ContextKeyDeviceCertificate contextKey = iota
|
|
ContextKeyDeviceCertificateVerifyError
|
|
)
|
|
|
|
func DeviceCertificateFromContext(ctx context.Context) (*x509.Certificate, error) {
|
|
cert := ctx.Value(ContextKeyDeviceCertificate).(*x509.Certificate)
|
|
err, _ := ctx.Value(ContextKeyDeviceCertificateVerifyError).(error)
|
|
return cert, err
|
|
}
|
|
|
|
type verifier struct {
|
|
*crypto.PKCS7Verifier
|
|
}
|
|
|
|
func (v verifier) populateDeviceCertificateFromSignRequestHeader(ctx context.Context, r *http.Request) context.Context {
|
|
bodyReader := r.Body
|
|
defer bodyReader.Close()
|
|
|
|
// We can't gracefully bubble up errors from this function,
|
|
// so we silently disregard them (terrible)
|
|
body, _ := ioutil.ReadAll(r.Body)
|
|
|
|
// Replace our body object with a fully buffered response
|
|
r.Body = ioutil.NopCloser(bytes.NewBuffer(body))
|
|
|
|
cert, err := v.verifySignature(r.Header.Get("Mdm-Signature"), body)
|
|
ctx = context.WithValue(ctx, ContextKeyDeviceCertificate, cert)
|
|
ctx = context.WithValue(ctx, ContextKeyDeviceCertificateVerifyError, err)
|
|
|
|
return ctx
|
|
}
|
|
|
|
// TODO: If we ever use Go client cert auth we can use
|
|
// r.TLS.PeerCertificates to return the client cert. Unnecessary
|
|
// now as default config is uses Mdm-Signature header method instead
|
|
// (for better compatilibity with proxies, etc.)
|
|
// func populateDeviceCertificateFromTLSPeerCertificates()
|
|
|
|
// Extract (raw) body bytes, parse property list
|
|
func mdmRequestBody(r *http.Request, s interface{}) ([]byte, error) {
|
|
defer r.Body.Close()
|
|
|
|
body, err := ioutil.ReadAll(r.Body)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "reading MDM acknowledge HTTP body")
|
|
}
|
|
|
|
err = plist.Unmarshal(body, s)
|
|
if err != nil {
|
|
return body, errors.Wrap(err, "unmarshal MDM acknowledge plist")
|
|
}
|
|
|
|
return body, nil
|
|
}
|
|
|
|
// Verify MDM header signature. Note: does NOT verify device certificate
|
|
func (v verifier) verifySignature(header string, body []byte) (*x509.Certificate, error) {
|
|
if header == "" {
|
|
return nil, errors.New("signature missing")
|
|
}
|
|
sig, err := base64.StdEncoding.DecodeString(header)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "decode MDM SignMessage header")
|
|
}
|
|
p7, err := pkcs7.Parse(sig)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "CMS parse decoded MDM SignMessage signature")
|
|
}
|
|
p7.Content = body
|
|
if err := v.Verify(p7); err != nil {
|
|
return nil, errors.Wrap(err, "CMS verify MDM Signed Message")
|
|
}
|
|
cert := p7.GetOnlySigner()
|
|
if cert == nil {
|
|
return nil, errors.New("invalid or missing CMS signer")
|
|
}
|
|
return cert, nil
|
|
}
|
|
|
|
// According to the MDM Check-in protocol, the server must respond with 200 OK
|
|
// to successful Check-in requests.
|
|
func encodeResponse(ctx context.Context, w http.ResponseWriter, response interface{}) error {
|
|
type failer interface {
|
|
Failed() error
|
|
}
|
|
|
|
if e, ok := response.(failer); ok && e.Failed() != nil {
|
|
return e.Failed()
|
|
}
|
|
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
type payloader interface {
|
|
Response() []byte
|
|
}
|
|
|
|
var err error
|
|
if r, ok := response.(payloader); ok {
|
|
_, err = w.Write(r.Response())
|
|
}
|
|
return errors.Wrap(err, "write acknowledge response")
|
|
}
|
|
|
|
func encodeError(ctx context.Context, err error, w http.ResponseWriter) {
|
|
err = errors.Cause(err)
|
|
type rejectUserAuthError interface {
|
|
error
|
|
UserAuthReject() bool
|
|
}
|
|
if e, ok := err.(rejectUserAuthError); ok && e.UserAuthReject() {
|
|
w.WriteHeader(http.StatusGone)
|
|
return
|
|
}
|
|
|
|
type checkoutErr interface {
|
|
error
|
|
Checkout() bool
|
|
}
|
|
if e, ok := err.(checkoutErr); ok && e.Checkout() {
|
|
w.WriteHeader(http.StatusUnauthorized)
|
|
return
|
|
}
|
|
|
|
w.WriteHeader(http.StatusInternalServerError)
|
|
}
|