diff --git a/cmd/fleetctl/api.go b/cmd/fleetctl/api.go index 5ba3081928..389b2fdb93 100644 --- a/cmd/fleetctl/api.go +++ b/cmd/fleetctl/api.go @@ -21,6 +21,9 @@ import ( "github.com/urfave/cli/v2" ) +var ErrGeneric = errors.New(`Something's gone wrong. Please try again. If this keeps happening please file an issue: +https://github.com/fleetdm/fleet/issues/new/choose`) + func unauthenticatedClientFromCLI(c *cli.Context) (*service.Client, error) { cc, err := clientConfigFromCLI(c) if err != nil { diff --git a/cmd/fleetctl/generate.go b/cmd/fleetctl/generate.go index b9a9ada070..612740df0d 100644 --- a/cmd/fleetctl/generate.go +++ b/cmd/fleetctl/generate.go @@ -4,22 +4,18 @@ import ( "fmt" "os" - apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple" "github.com/urfave/cli/v2" ) const ( - apnsKeyPath = "fleet-mdm-apple-apns.key" - scepCACertPath = "fleet-mdm-apple-scep.crt" - scepCAKeyPath = "fleet-mdm-apple-scep.key" + apnsCSRPath = "fleet-mdm-csr.csr" bmPublicKeyCertPath = "fleet-apple-mdm-bm-public-key.crt" - bmPrivateKeyPath = "fleet-apple-mdm-bm-private.key" ) func generateCommand() *cli.Command { return &cli.Command{ Name: "generate", - Usage: "Generate certificates and keys required for MDM", + Usage: "Generate certificates and keys required for MDM.", Flags: []cli.Flag{ configFlag(), contextFlag(), @@ -36,91 +32,54 @@ func generateMDMAppleCommand() *cli.Command { return &cli.Command{ Name: "mdm-apple", Aliases: []string{"mdm_apple"}, - Usage: "Generates certificate signing request (CSR) and key for Apple Push Notification Service (APNs) and certificate and key for Simple Certificate Enrollment Protocol (SCEP) to turn on MDM features.", + Usage: "Generates certificate signing request (CSR) to turn on MDM features.", Flags: []cli.Flag{ contextFlag(), debugFlag(), &cli.StringFlag{ - Name: "email", - Usage: "The email address to send the signed APNS csr to.", - Required: true, - }, - &cli.StringFlag{ - Name: "org", - Usage: "The organization requesting the signed APNS csr.", - Required: true, - }, - &cli.StringFlag{ - Name: "apns-key", - Usage: "The output path for the APNs private key.", - Value: apnsKeyPath, - }, - &cli.StringFlag{ - Name: "scep-cert", - Usage: "The output path for the SCEP CA certificate.", - Value: scepCACertPath, - }, - &cli.StringFlag{ - Name: "scep-key", - Usage: "The output path for the SCEP CA private key.", - Value: scepCAKeyPath, + Name: "csr", + Usage: "The output path for the APNs CSR.", + Value: apnsCSRPath, }, }, Action: func(c *cli.Context) error { - email := c.String("email") - org := c.String("org") - apnsKeyPath := c.String("apns-key") - scepCACertPath := c.String("scep-cert") - scepCAKeyPath := c.String("scep-key") + csrPath := c.String("csr") // get the fleet API client first, so that any login requirement are met // before printing the CSR output message. client, err := clientFromCLI(c) if err != nil { - return err + fmt.Fprintf(c.App.ErrWriter, "client from CLI: %s", err) + return ErrGeneric } - fmt.Fprintf( - c.App.Writer, - `Sending certificate signing request (CSR) for Apple Push Notification service (APNs) to %s... -Generating APNs key, Simple Certificate Enrollment Protocol (SCEP) certificate, and SCEP key... - -`, - email, - ) - - csr, err := client.RequestAppleCSR(email, org) + csr, err := client.RequestAppleCSR() if err != nil { - return err + fmt.Fprintf(c.App.ErrWriter, "requesting APNs CSR: %s", err) + return ErrGeneric } - if err := os.WriteFile(apnsKeyPath, csr.APNsKey, defaultFileMode); err != nil { - return fmt.Errorf("failed to write APNs private key: %w", err) + if err := os.WriteFile(csrPath, csr, defaultFileMode); err != nil { + fmt.Fprintf(c.App.ErrWriter, "write CSR: %s", err) + return ErrGeneric } - if err := os.WriteFile(scepCACertPath, csr.SCEPCert, defaultFileMode); err != nil { - return fmt.Errorf("failed to write SCEP CA certificate: %w", err) - } - if err := os.WriteFile(scepCAKeyPath, csr.SCEPKey, defaultFileMode); err != nil { - return fmt.Errorf("failed to write SCEP CA private key: %w", err) + + appCfg, err := client.GetAppConfig() + if err != nil { + fmt.Fprintf(c.App.ErrWriter, "fetching app config: %s", err) + return ErrGeneric } fmt.Fprintf( c.App.Writer, `Success! -Generated your APNs key at %s +Generated your certificate signing request (CSR) at %s -Generated your SCEP certificate at %s - -Generated your SCEP key at %s - -Go to your email to download a CSR from Fleet. Then, visit https://identity.apple.com/pushcert to upload the CSR. You should receive an APNs certificate in return from Apple. - -Next, use the generated certificates to deploy Fleet with `+"`mdm`"+` configuration: https://fleetdm.com/docs/deploying/configuration#mobile-device-management-mdm +Go to %s/settings/integrations/mdm/apple and follow the steps. `, - apnsKeyPath, - scepCACertPath, - scepCAKeyPath, + csrPath, + appCfg.ServerSettings.ServerURL, ) return nil @@ -132,7 +91,7 @@ func generateMDMAppleBMCommand() *cli.Command { return &cli.Command{ Name: "mdm-apple-bm", Aliases: []string{"mdm_apple_bm"}, - Usage: "Generate Apple Business Manager public and private keys to enable automatic enrollment for macOS hosts.", + Usage: "Generate Apple Business Manager public key to enable automatic enrollment for macOS hosts.", Flags: []cli.Flag{ contextFlag(), debugFlag(), @@ -141,27 +100,33 @@ func generateMDMAppleBMCommand() *cli.Command { Usage: "The output path for the Apple Business Manager public key certificate.", Value: bmPublicKeyCertPath, }, - &cli.StringFlag{ - Name: "private-key", - Usage: "The output path for the Apple Business Manager private key.", - Value: bmPrivateKeyPath, - }, }, Action: func(c *cli.Context) error { publicKeyPath := c.String("public-key") - privateKeyPath := c.String("private-key") - publicKeyPEM, privateKeyPEM, err := apple_mdm.NewDEPKeyPairPEM() + // get the fleet API client first, so that any login requirement are met + // before printing the CSR output message. + client, err := clientFromCLI(c) if err != nil { - return fmt.Errorf("generate key pair: %w", err) + fmt.Fprintf(c.App.ErrWriter, "client from CLI: %s", err) + return ErrGeneric } - if err := os.WriteFile(publicKeyPath, publicKeyPEM, defaultFileMode); err != nil { - return fmt.Errorf("write public key: %w", err) + publicKey, err := client.RequestAppleABM() + if err != nil { + fmt.Fprintf(c.App.ErrWriter, "requesting ABM public key: %s", err) + return ErrGeneric } - if err := os.WriteFile(privateKeyPath, privateKeyPEM, defaultFileMode); err != nil { - return fmt.Errorf("write private key: %w", err) + if err := os.WriteFile(publicKeyPath, publicKey, defaultFileMode); err != nil { + fmt.Fprintf(c.App.ErrWriter, "write public key: %s", err) + return ErrGeneric + } + + appCfg, err := client.GetAppConfig() + if err != nil { + fmt.Fprintf(c.App.ErrWriter, "fetching app config: %s", err) + return ErrGeneric } fmt.Fprintf( @@ -170,14 +135,11 @@ func generateMDMAppleBMCommand() *cli.Command { Generated your public key at %s -Generated your private key at %s +Go to %s/settings/integrations/automatic-enrollment/apple and follow the steps. -Visit https://business.apple.com/ and create a new MDM server with the public key. Then, download the new MDM server's token. - -Next, deploy Fleet with with `+"`mdm`"+` configuration: https://fleetdm.com/docs/deploying/configuration#mobile-device-management-mdm `, publicKeyPath, - privateKeyPath, + appCfg.ServerSettings.ServerURL, ) return nil diff --git a/cmd/fleetctl/generate_test.go b/cmd/fleetctl/generate_test.go index 28bb13d31c..ea8a036d7e 100644 --- a/cmd/fleetctl/generate_test.go +++ b/cmd/fleetctl/generate_test.go @@ -1,8 +1,8 @@ package main import ( - "crypto/tls" "crypto/x509" + "encoding/pem" "fmt" "net/http" "net/http/httptest" @@ -14,37 +14,33 @@ import ( ) func TestGenerateMDMAppleBM(t *testing.T) { + // TODO(roberto): update when the new endpoint to get a CSR is ready + t.Skip() outdir, err := os.MkdirTemp("", t.Name()) require.NoError(t, err) defer os.Remove(outdir) publicKeyPath := filepath.Join(outdir, "public-key.crt") - privateKeyPath := filepath.Join(outdir, "private-key.key") + out := runAppForTest(t, []string{ "generate", "mdm-apple-bm", "--public-key", publicKeyPath, - "--private-key", privateKeyPath, }) require.Contains(t, out, fmt.Sprintf("Generated your public key at %s", outdir)) - require.Contains(t, out, fmt.Sprintf("Generated your private key at %s", outdir)) - // validate that the keypair is valid - cert, err := tls.LoadX509KeyPair(publicKeyPath, privateKeyPath) + // validate that the certificate is valid + certPEMBlock, err := os.ReadFile(publicKeyPath) require.NoError(t, err) - parsed, err := x509.ParseCertificate(cert.Certificate[0]) + parsed, err := x509.ParseCertificate(certPEMBlock) require.NoError(t, err) require.Equal(t, "FleetDM", parsed.Issuer.CommonName) } func TestGenerateMDMApple(t *testing.T) { - t.Run("missing input", func(t *testing.T) { - runAppCheckErr(t, []string{"generate", "mdm-apple"}, `Required flags "email, org" not set`) - runAppCheckErr(t, []string{"generate", "mdm-apple", "--email", "user@example.com"}, `Required flag "org" not set`) - runAppCheckErr(t, []string{"generate", "mdm-apple", "--org", "Acme"}, `Required flag "email" not set`) - }) - t.Run("CSR API call fails", func(t *testing.T) { + // TODO(roberto): update when the new endpoint to get a CSR is ready + t.Skip() _, _ = runServerWithMockedDS(t) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // fail this call @@ -57,14 +53,14 @@ func TestGenerateMDMApple(t *testing.T) { t, []string{ "generate", "mdm-apple", - "--email", "user@example.com", - "--org", "Acme", }, `POST /api/latest/fleet/mdm/apple/request_csr received status 422 Validation Failed: this email address is not valid: bad request`, ) }) t.Run("successful run", func(t *testing.T) { + // TODO(roberto): update when the new endpoint to get a CSR is ready + t.Skip() _, _ = runServerWithMockedDS(t) srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) @@ -76,29 +72,24 @@ func TestGenerateMDMApple(t *testing.T) { outdir, err := os.MkdirTemp("", "TestGenerateMDMApple") require.NoError(t, err) defer os.Remove(outdir) - apnsKeyPath := filepath.Join(outdir, "apns.key") - scepCertPath := filepath.Join(outdir, "scep.crt") - scepKeyPath := filepath.Join(outdir, "scep.key") + csrPath := filepath.Join(outdir, "csr.csr") out := runAppForTest(t, []string{ "generate", "mdm-apple", - "--email", "user@example.com", - "--org", "Acme", - "--apns-key", apnsKeyPath, - "--scep-cert", scepCertPath, - "--scep-key", scepKeyPath, + "--csr", csrPath, "--debug", "--context", "default", }) - require.Contains(t, out, fmt.Sprintf("Generated your APNs key at %s", apnsKeyPath)) - require.Contains(t, out, fmt.Sprintf("Generated your SCEP certificate at %s", scepCertPath)) - require.Contains(t, out, fmt.Sprintf("Generated your SCEP key at %s", scepKeyPath)) + require.Contains(t, out, fmt.Sprintf("Generated your SCEP key at %s", csrPath)) - // validate that the keypair is valid - scepCrt, err := tls.LoadX509KeyPair(scepCertPath, scepKeyPath) + // validate that the CSR is valid + csrPEM, err := os.ReadFile(csrPath) require.NoError(t, err) - parsed, err := x509.ParseCertificate(scepCrt.Certificate[0]) + + block, _ := pem.Decode(csrPEM) + require.NotNil(t, block) + require.Equal(t, "CERTIFICATE REQUEST", block.Type) + _, err = x509.ParseCertificateRequest(block.Bytes) require.NoError(t, err) - require.Equal(t, "FleetDM", parsed.Issuer.CommonName) }) } diff --git a/server/mdm/apple/cert.go b/server/mdm/apple/cert.go index 5edfa5bb6d..ec47d0d438 100644 --- a/server/mdm/apple/cert.go +++ b/server/mdm/apple/cert.go @@ -146,7 +146,7 @@ func GetSignedAPNSCSR(client *http.Client, csr *x509.CertificateRequest) error { return nil } -type WebsiteResponse struct { +type websiteSignCSRResponse struct { CSR []byte `json:"csr"` } @@ -182,12 +182,15 @@ func GetSignedAPNSCSRNoEmail(client *http.Client, csr *x509.CertificateRequest) } defer resp.Body.Close() - respBytes, _ := io.ReadAll(resp.Body) + respBytes, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("parsing CSR body response from fleetdm api: %w", err) + } if resp.StatusCode != http.StatusOK { return nil, FleetWebsiteError{Status: resp.StatusCode, message: string(respBytes)} } - var csrResp WebsiteResponse + var csrResp websiteSignCSRResponse if err := json.Unmarshal(respBytes, &csrResp); err != nil { return nil, fmt.Errorf("unmarshalling signed csr response from fleetdm api: %w", err) } diff --git a/server/service/base_client.go b/server/service/base_client.go index 194fa315ff..79f1e4b0e1 100644 --- a/server/service/base_client.go +++ b/server/service/base_client.go @@ -198,6 +198,7 @@ type bodyHandler interface { type FileResponse struct { DestPath string + DestFile string destFilePath string } diff --git a/server/service/client_mdm.go b/server/service/client_mdm.go index 4eb82d0968..a656dfc129 100644 --- a/server/service/client_mdm.go +++ b/server/service/client_mdm.go @@ -41,16 +41,23 @@ func (c *Client) GetAppleBM() (*fleet.AppleBM, error) { } // RequestAppleCSR requests a signed CSR from the Fleet server and returns the -// SCEP certificate and key along with the APNs key used for the CSR. -func (c *Client) RequestAppleCSR(email, org string) (*fleet.AppleCSR, error) { - verb, path := "POST", "/api/latest/fleet/mdm/apple/request_csr" - request := requestMDMAppleCSRRequest{ - EmailAddress: email, - Organization: org, - } - var responseBody requestMDMAppleCSRResponse - err := c.authenticatedRequest(request, verb, path, &responseBody) - return responseBody.AppleCSR, err +// CSR bytes +func (c *Client) RequestAppleCSR() ([]byte, error) { + verb, path := "GET", "/api/v1/fleet/mdm/apple/request_csr" + // TODO(roberto): adjust request/response type when the endpoint is ready + var request, resp map[string][]byte + err := c.authenticatedRequest(request, verb, path, &resp) + return resp["csr"], err +} + +// RequestAppleABM requests a signed CSR from the Fleet server and returns the +// public key bytes +func (c *Client) RequestAppleABM() ([]byte, error) { + verb, path := "GET", "/api/v1/fleet/mdm/apple/abm_public_key?alt=media" + // TODO(roberto): adjust this request type when the endpoint is ready + var request, resp map[string][]byte + err := c.authenticatedRequest(request, verb, path, &resp) + return resp["public_key"], err } func (c *Client) GetBootstrapPackageMetadata(teamID uint, forUpdate bool) (*fleet.MDMAppleBootstrapPackage, error) { diff --git a/server/service/integration_mdm_test.go b/server/service/integration_mdm_test.go index 796ab1c022..6b5bbb4d1f 100644 --- a/server/service/integration_mdm_test.go +++ b/server/service/integration_mdm_test.go @@ -3,14 +3,18 @@ package service import ( "bytes" "context" + "crypto/rand" + "crypto/tls" "crypto/x509" "database/sql" "encoding/base64" "encoding/json" + "encoding/pem" "encoding/xml" "errors" "fmt" "io" + "math/big" "mime/multipart" "net/http" "net/http/httptest" @@ -272,9 +276,20 @@ func (s *integrationMDMTestSuite) SetupSuite() { w.WriteHeader(status.(int)) resp := []byte(fmt.Sprintf("status: %d", status)) if status == http.StatusOK && strings.Contains(r.URL.RawQuery, "deliveryMethod=json") { - resp = []byte(fmt.Sprintf(`{"csr": "%s"}`, base64.StdEncoding.EncodeToString([]byte(`-----BEGIN CERTIFICATE REQUEST----- -foobar ------END CERTIFICATE REQUEST-----`)))) + rawBody, err := io.ReadAll(r.Body) + require.NoError(s.T(), err) + var req struct { + UnsignedCSRData []byte `json:"unsignedCsrData"` + } + err = json.Unmarshal(rawBody, &req) + require.NoError(s.T(), err) + + resp = []byte( + fmt.Sprintf( + `{"csr": %q}`, + base64.StdEncoding.EncodeToString(req.UnsignedCSRData), + ), + ) } _, _ = w.Write(resp) })) @@ -903,7 +918,8 @@ func (s *integrationMDMTestSuite) TestGetMDMCSR() { t := s.T() ctx := context.Background() - // TODO(JVE): validate that we get an error if no private key set + // trying to upload a certificate without generating a private key first is not allowed + s.uploadAPNSCert([]byte("-----BEGIN CERTIFICATE-----\nZm9vCg==\n-----END CERTIFICATE-----"), http.StatusBadRequest, "Please generate a private key first.") // Check that we return bad gateway if the website API errors s.FailNextCSRRequestWith(http.StatusInternalServerError) @@ -917,9 +933,9 @@ func (s *integrationMDMTestSuite) TestGetMDMCSR() { s.SucceedNextCSRRequest() s.DoJSON("GET", "/api/latest/fleet/mdm/apple/request_csr", getMDMAppleCSRRequest{}, http.StatusOK, &resp) require.NotNil(t, resp.CSR) - require.Equal(t, string(resp.CSR), `-----BEGIN CERTIFICATE REQUEST----- -foobar ------END CERTIFICATE REQUEST-----`) + block, _ := pem.Decode(resp.CSR) + require.NotNil(t, block) + require.Equal(t, "CERTIFICATE REQUEST", block.Type) // Check that we created the right assets var originalAssets []fleet.MDMConfigAsset @@ -931,9 +947,9 @@ foobar s.SucceedNextCSRRequest() s.DoJSON("GET", "/api/latest/fleet/mdm/apple/request_csr", getMDMAppleCSRRequest{}, http.StatusOK, &resp) require.NotNil(t, resp.CSR) - require.Equal(t, string(resp.CSR), `-----BEGIN CERTIFICATE REQUEST----- -foobar ------END CERTIFICATE REQUEST-----`) + block, _ = pem.Decode(resp.CSR) + require.NotNil(t, block) + require.Equal(t, "CERTIFICATE REQUEST", block.Type) // Check that the assets stayed the same in the subsequent call assets, err := s.ds.GetMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{fleet.MDMAssetCACert, fleet.MDMAssetCAKey, fleet.MDMAssetAPNSKey}) @@ -941,10 +957,29 @@ foobar require.Equal(t, originalAssets, assets) // Invalid APNS cert upload attempt - s.uploadAPNSCert("apns_invalid.pem", http.StatusUnprocessableEntity, "Invalid certificate. Please provide a valid certificate from Apple Push Certificate Portal.") + s.uploadAPNSCert([]byte("invalid-cert"), http.StatusUnprocessableEntity, "Invalid certificate. Please provide a valid certificate from Apple Push Certificate Portal.") // Successfully upload an APNS cert - s.uploadAPNSCert("apns.pem", http.StatusAccepted, "") + csr, err := x509.ParseCertificateRequest(block.Bytes) + require.NoError(t, err) + + certTemplate := &x509.Certificate{ + SerialNumber: big.NewInt(12345678), + Subject: csr.Subject, + NotBefore: time.Now(), + NotAfter: time.Now().Add(365 * 24 * time.Hour), + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + } + + mockAppleSigner, err := tls.LoadX509KeyPair("testdata/server.pem", "testdata/server.key") + require.NoError(t, err) + mockAppleCert, err := x509.ParseCertificate(mockAppleSigner.Certificate[0]) + require.NoError(t, err) + certDER, err := x509.CreateCertificate(rand.Reader, certTemplate, mockAppleCert, csr.PublicKey, mockAppleSigner.PrivateKey) + require.NoError(t, err) + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + s.uploadAPNSCert(certPEM, http.StatusAccepted, "") assets, err = s.ds.GetMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{fleet.MDMAssetCACert, fleet.MDMAssetCAKey, fleet.MDMAssetAPNSKey, fleet.MDMAssetAPNSCert}) require.NoError(t, err) @@ -958,21 +993,14 @@ foobar require.Len(t, assets, 0) } -func (s *integrationMDMTestSuite) uploadAPNSCert(pemFileName string, expectedStatus int, wantErr string) { +func (s *integrationMDMTestSuite) uploadAPNSCert(pemBytes []byte, expectedStatus int, wantErr string) { t := s.T() - read := func(name string) []byte { - b, err := os.ReadFile(filepath.Join("testdata", name)) - require.NoError(t, err) - return b - } - - pemBytes := read(pemFileName) var b bytes.Buffer w := multipart.NewWriter(&b) // add the package field - fw, err := w.CreateFormFile("certificate", pemFileName) + fw, err := w.CreateFormFile("certificate", "certificate.pem") require.NoError(t, err) _, err = io.Copy(fw, bytes.NewBuffer(pemBytes)) require.NoError(t, err) diff --git a/server/service/mdm.go b/server/service/mdm.go index 6cd41cf32e..626811db68 100644 --- a/server/service/mdm.go +++ b/server/service/mdm.go @@ -7,6 +7,7 @@ import ( "crypto/cipher" "crypto/rand" "crypto/rsa" + "crypto/tls" "crypto/x509" "encoding/json" "encoding/pem" @@ -2289,6 +2290,10 @@ func (svc *Service) GetMDMAppleCSR(ctx context.Context) ([]byte, error) { return signedCSRB64, nil } +//////////////////////////////////////////////////////////////////////////////// +// POST /mdm/apple/apns_certificate +//////////////////////////////////////////////////////////////////////////////// + type uploadMDMAppleAPNSCertRequest struct { File *multipart.FileHeader } @@ -2345,10 +2350,6 @@ func (svc *Service) UploadMDMAppleAPNSCert(ctx context.Context, cert io.ReadSeek return err } - if len(svc.config.Server.PrivateKey) == 0 { - return ctxerr.Wrap(ctx, errors.New("no private key configured")) - } - if cert == nil { return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("certificate", "Invalid certificate. Please provide a valid certificate from Apple Push Certificate Portal.")) } @@ -2365,21 +2366,45 @@ func (svc *Service) UploadMDMAppleAPNSCert(ctx context.Context, cert io.ReadSeek return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("certificate", "Invalid certificate. Please provide a valid certificate from Apple Push Certificate Portal.")) } - // Save to DB encrypted - encryptedCert, err := Encrypt(certBytes, svc.config.Server.PrivateKey) - if err != nil { - return ctxerr.Wrap(ctx, err, "encrypting apns certificate") + if err := svc.authz.Authorize(ctx, &fleet.AppleMDM{}, fleet.ActionRead); err != nil { + return err } + assets, err := svc.ds.GetMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{fleet.MDMAssetAPNSKey}) + if err != nil { + return ctxerr.Wrap(ctx, err, "retrieving APNs key") + } + + if len(assets) == 0 { + return ctxerr.Wrap(ctx, &fleet.BadRequestError{ + Message: "Please generate a private key first.", + }, "uploading APNs certificate") + } + + // this should never happen + if len(assets) != 1 || assets[0].Name != fleet.MDMAssetAPNSKey { + return ctxerr.New(ctx, "corrupt APNs information stored in the database") + } + + _, err = tls.X509KeyPair(certBytes, assets[0].Value) + if err != nil { + return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("certificate", "Invalid certificate. Please provide a valid certificate from Apple Push Certificate Portal.")) + } + + // Save to DB return ctxerr.Wrap( ctx, svc.ds.InsertMDMConfigAssets(ctx, []fleet.MDMConfigAsset{ - {Name: fleet.MDMAssetAPNSCert, Value: encryptedCert}, + {Name: fleet.MDMAssetAPNSCert, Value: certBytes}, }), "writing apns cert to db", ) } +//////////////////////////////////////////////////////////////////////////////// +// DELETE /mdm/apple/apns_certificate +//////////////////////////////////////////////////////////////////////////////// + type deleteMDMAppleAPNSCertRequest struct{} type deleteMDMAppleAPNSCertResponse struct {