fleet/server/datastore/mysql/wstep.go
Victor Lyuboslavsky 0180cc8086
Add SCEP endpoint for host identity. (#30589)
Fixes #30458 

Contributor docs PR: https://github.com/fleetdm/fleet/pull/30651

# Checklist for submitter

- We will add changes file later.
- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements)
- [x] If database migrations are included, checked table schema to
confirm autoupdate
- For database migrations:
- [x] Checked schema for all modified table for columns that will
auto-update timestamps during migration.
- [x] Confirmed that updating the timestamps is acceptable, and will not
cause unwanted side effects.
- [x] Ensured the correct collation is explicitly set for character
columns (`COLLATE utf8mb4_unicode_ci`).
- [x] Added/updated automated tests
- Did not do manual QA since the SCEP client I have doesn't support ECC.
Will rely on next subtasks for manual QA.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Introduced Host Identity SCEP (Simple Certificate Enrollment Protocol)
support, enabling secure host identity certificate enrollment and
management.
* Added new API endpoints for Host Identity SCEP, including certificate
issuance and retrieval.
* Implemented MySQL-backed storage and management for host identity SCEP
certificates and serials.
* Added new database tables for storing host identity SCEP certificates
and serial numbers.
* Provided utilities for encoding certificates and keys, and handling
ECDSA public keys.

* **Bug Fixes**
  * None.

* **Tests**
* Added comprehensive integration and unit tests for Host Identity SCEP
functionality, including certificate issuance, validation, and error
scenarios.

* **Chores**
* Updated test utilities to support unique test names and new SCEP
storage options.
* Extended mock datastore and interfaces for new host identity
certificate methods.

* **Documentation**
* Added comments and documentation for new SCEP-related interfaces,
methods, and database schema changes.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-07-11 11:44:07 -03:00

69 lines
2 KiB
Go

package mysql
import (
"context"
"crypto/sha256"
"crypto/x509"
"errors"
"fmt"
"math/big"
"strings"
"github.com/fleetdm/fleet/v4/pkg/certificate"
microsoft_mdm "github.com/fleetdm/fleet/v4/server/mdm/microsoft"
)
// CertStore implements storage tasks associated with MS-WSTEP messages in the MS-MDE2
// protocol. It is implemented by fleet.Datastore.
var _ microsoft_mdm.CertStore = (*Datastore)(nil)
// WSTEPStoreCertificate stores a certificate under the given name.
//
// If the provided certificate has empty crt.Subject.CommonName,
// then the hex sha256 of the crt.Raw is used as name.
func (ds *Datastore) WSTEPStoreCertificate(ctx context.Context, name string, crt *x509.Certificate) error {
if crt.Subject.CommonName == "" {
name = fmt.Sprintf("%x", sha256.Sum256(crt.Raw))
}
if !crt.SerialNumber.IsInt64() {
return errors.New("cannot represent serial number as int64")
}
certPEM := certificate.EncodeCertPEM(crt)
_, err := ds.writer(ctx).ExecContext(ctx, `
INSERT INTO wstep_certificates
(serial, name, not_valid_before, not_valid_after, certificate_pem)
VALUES
(?, ?, ?, ?, ?)`,
crt.SerialNumber.Int64(),
name,
crt.NotBefore,
crt.NotAfter,
certPEM,
)
return err
}
// WSTEPNewSerial allocates and returns a new (increasing) serial number.
func (ds *Datastore) WSTEPNewSerial(ctx context.Context) (*big.Int, error) {
result, err := ds.writer(ctx).ExecContext(ctx, `INSERT INTO wstep_serials () VALUES ();`)
if err != nil {
return nil, err
}
lid, err := result.LastInsertId() // TODO: ok if sequential and not random?
if err != nil {
return nil, err
}
// TODO: check maxSerialNumber?
return big.NewInt(lid), nil
}
func (ds *Datastore) WSTEPAssociateCertHash(ctx context.Context, deviceUUID string, hash string) error {
_, err := ds.writer(ctx).ExecContext(ctx, `
INSERT INTO wstep_cert_auth_associations (id, sha256) VALUES (?, ?) AS new
ON DUPLICATE KEY
UPDATE sha256 = new.sha256;`,
deviceUUID,
strings.ToUpper(hash), // TODO: confirm if this is necessary
)
return err
}