fleet/server/mdm/assets/assets.go
Victor Lyuboslavsky 567803955e
NDES SCEP proxy backend (#22542)
#21955

<div>
<a href="https://www.loom.com/share/ba40b440502845d2861fd3ec7611bade">
<p>[Demo] Deploy SCEP certificates from Network Device Enrollment
Service (NDES) #21955 - Watch Video</p>
    </a>
<a href="https://www.loom.com/share/ba40b440502845d2861fd3ec7611bade">
<img style="max-width:300px;"
src="https://cdn.loom.com/sessions/thumbnails/ba40b440502845d2861fd3ec7611bade-84f2d88c9f5106c2-full-play.gif">
    </a>
  </div>

Note: A few remaining subtasks will be done in a follow-up PR. See
#22123 for a detailed list.

# Checklist for submitter

If some of the following don't apply, delete the relevant line.

<!-- Note that API documentation changes are now addressed by the
product design team. -->

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files)
for more information.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [x] Added/updated tests
- [x] Manual QA for all new/changed functionality
2024-10-09 13:47:27 -05:00

125 lines
3.8 KiB
Go

package assets
import (
"context"
"crypto/tls"
"crypto/x509"
"encoding/json"
"encoding/pem"
"fmt"
"github.com/fleetdm/fleet/v4/server/fleet"
nanodep_client "github.com/fleetdm/fleet/v4/server/mdm/nanodep/client"
"github.com/fleetdm/fleet/v4/server/mdm/nanodep/tokenpki"
"github.com/fleetdm/fleet/v4/server/mdm/nanomdm/cryptoutil"
)
func CAKeyPair(ctx context.Context, ds fleet.MDMAssetRetriever) (*tls.Certificate, error) {
return KeyPair(ctx, ds, fleet.MDMAssetCACert, fleet.MDMAssetCAKey)
}
func APNSKeyPair(ctx context.Context, ds fleet.MDMAssetRetriever) (*tls.Certificate, error) {
return KeyPair(ctx, ds, fleet.MDMAssetAPNSCert, fleet.MDMAssetAPNSKey)
}
func KeyPair(ctx context.Context, ds fleet.MDMAssetRetriever, certName, keyName fleet.MDMAssetName) (*tls.Certificate, error) {
assets, err := ds.GetAllMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{
certName,
keyName,
}, nil)
if err != nil {
return nil, fmt.Errorf("loading %s, %s keypair from the database: %w", certName, keyName, err)
}
cert, err := tls.X509KeyPair(assets[certName].Value, assets[keyName].Value)
if err != nil {
return nil, fmt.Errorf("parsing %s, %s keypair: %w", certName, keyName, err)
}
cert.Leaf, err = x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return nil, fmt.Errorf("parsing %s certificate leaf: %w", certName, err)
}
return &cert, nil
}
func X509Cert(ctx context.Context, ds fleet.MDMAssetRetriever, certName fleet.MDMAssetName) (*x509.Certificate, error) {
assets, err := ds.GetAllMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{certName}, nil)
if err != nil {
return nil, fmt.Errorf("loading certificate %s from the database: %w", certName, err)
}
block, _ := pem.Decode(assets[certName].Value)
if block == nil || block.Type != "CERTIFICATE" {
return nil, fmt.Errorf("decoding certificate PEM data: %w", err)
}
return x509.ParseCertificate(block.Bytes)
}
func APNSTopic(ctx context.Context, ds fleet.MDMAssetRetriever) (string, error) {
cert, err := X509Cert(ctx, ds, fleet.MDMAssetAPNSCert)
if err != nil {
return "", fmt.Errorf("retrieving APNs cert: %w", err)
}
mdmPushCertTopic, err := cryptoutil.TopicFromCert(cert)
if err != nil {
return "", fmt.Errorf("extracting topic from APNs certificate: %w", err)
}
return mdmPushCertTopic, nil
}
func ABMToken(ctx context.Context, ds fleet.MDMAssetRetriever, abmOrgName string) (*nanodep_client.OAuth1Tokens, error) {
assets, err := ds.GetAllMDMConfigAssetsByName(ctx, []fleet.MDMAssetName{
fleet.MDMAssetABMKey,
fleet.MDMAssetABMCert,
}, nil)
if err != nil {
return nil, fmt.Errorf("loading ABM assets from the database: %w", err)
}
abmTok, err := ds.GetABMTokenByOrgName(ctx, abmOrgName)
if err != nil {
return nil, fmt.Errorf("get ABM token by name: %w", err)
}
cert, err := tls.X509KeyPair(assets[fleet.MDMAssetABMCert].Value, assets[fleet.MDMAssetABMKey].Value)
if err != nil {
return nil, fmt.Errorf("parsing ABM keypair: %w", err)
}
leaf, err := x509.ParseCertificate(cert.Certificate[0])
if err != nil {
return nil, fmt.Errorf("parsing ABM certificate: %w", err)
}
oAuthTok, err := DecryptRawABMToken(
abmTok.EncryptedToken,
leaf,
assets[fleet.MDMAssetABMKey].Value,
)
if err != nil {
return nil, fmt.Errorf("decrypting ABM token: %w", err)
}
return oAuthTok, nil
}
func DecryptRawABMToken(tokenBytes []byte, cert *x509.Certificate, keyPEM []byte) (*nanodep_client.OAuth1Tokens, error) {
bmKey, err := tokenpki.RSAKeyFromPEM(keyPEM)
if err != nil {
return nil, fmt.Errorf("parse private key: %w", err)
}
token, err := tokenpki.DecryptTokenJSON(tokenBytes, cert, bmKey)
if err != nil {
return nil, fmt.Errorf("decrypt token: %w", err)
}
var jsonTok nanodep_client.OAuth1Tokens
if err := json.Unmarshal(token, &jsonTok); err != nil {
return nil, fmt.Errorf("unmarshal JSON token: %w", err)
}
return &jsonTok, nil
}