mirror of
https://github.com/fleetdm/fleet
synced 2026-05-22 00:18:27 +00:00
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 -->
106 lines
2.8 KiB
Go
106 lines
2.8 KiB
Go
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"crypto/x509"
|
|
"database/sql"
|
|
_ "embed"
|
|
"errors"
|
|
"fmt"
|
|
"math/big"
|
|
|
|
"github.com/fleetdm/fleet/v4/pkg/certificate"
|
|
"github.com/fleetdm/fleet/v4/server/fleet"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/assets"
|
|
"github.com/fleetdm/fleet/v4/server/mdm/scep/depot"
|
|
)
|
|
|
|
// SCEPDepot is a MySQL-backed SCEP certificate depot.
|
|
type SCEPDepot struct {
|
|
db *sql.DB
|
|
ds fleet.Datastore
|
|
}
|
|
|
|
var _ depot.Depot = (*SCEPDepot)(nil)
|
|
|
|
// newSCEPDepot creates and returns a *SCEPDepot.
|
|
func newSCEPDepot(db *sql.DB, ds fleet.Datastore) (*SCEPDepot, error) {
|
|
if err := db.Ping(); err != nil {
|
|
return nil, err
|
|
}
|
|
return &SCEPDepot{
|
|
db: db,
|
|
ds: ds,
|
|
}, nil
|
|
}
|
|
|
|
// CA returns the CA's certificate and private key.
|
|
func (d *SCEPDepot) CA(_ []byte) ([]*x509.Certificate, *rsa.PrivateKey, error) {
|
|
// TODO(roberto): nano interfaces doesn't receive a context for this method.
|
|
cert, err := assets.CAKeyPair(context.Background(), d.ds)
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("getting assets: %w", err)
|
|
}
|
|
|
|
pk, ok := cert.PrivateKey.(*rsa.PrivateKey)
|
|
if !ok {
|
|
return nil, nil, errors.New("private key not in RSA format")
|
|
}
|
|
|
|
return []*x509.Certificate{cert.Leaf}, pk, nil
|
|
}
|
|
|
|
// Serial allocates and returns a new (increasing) serial number.
|
|
func (d *SCEPDepot) Serial() (*big.Int, error) {
|
|
result, err := d.db.Exec(`INSERT INTO scep_serials () VALUES ();`)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
lid, err := result.LastInsertId()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return big.NewInt(lid), nil
|
|
}
|
|
|
|
// HasCN returns whether the given certificate exists in the depot.
|
|
//
|
|
// TODO(lucas): Implement and use allowTime and revokeOldCertificate.
|
|
// - allowTime are the maximum days before expiration to allow clients to do certificate renewal.
|
|
// - revokeOldCertificate specifies whether to revoke the old certificate once renewed.
|
|
func (d *SCEPDepot) HasCN(cn string, allowTime int, cert *x509.Certificate, revokeOldCertificate bool) (bool, error) {
|
|
var ct int
|
|
row := d.db.QueryRow(`SELECT COUNT(*) FROM scep_certificates WHERE name = ?`, cn)
|
|
if err := row.Scan(&ct); err != nil {
|
|
return false, err
|
|
}
|
|
return ct >= 1, nil
|
|
}
|
|
|
|
// Put 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 (d *SCEPDepot) Put(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 := d.db.Exec(`
|
|
INSERT INTO scep_certificates
|
|
(serial, name, not_valid_before, not_valid_after, certificate_pem)
|
|
VALUES
|
|
(?, ?, ?, ?, ?)`,
|
|
crt.SerialNumber.Int64(),
|
|
name,
|
|
crt.NotBefore,
|
|
crt.NotAfter,
|
|
certPEM,
|
|
)
|
|
return err
|
|
}
|