mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 09:28:54 +00:00
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** The entire ACME feature branch merge # Checklist for submitter If some of the following don't apply, delete the relevant line. - [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/guides/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), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. - [x] Timeouts are implemented and retries are limited to avoid infinite loops ## Testing - [x] Added/updated automated tests - [x] Where appropriate, [automated tests simulate multiple hosts and test for host isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing) (updates to one hosts's records do not affect another) - [x] QA'd all new/changed functionality manually --------- Co-authored-by: Jordan Montgomery <elijah.jordan.montgomery@gmail.com> Co-authored-by: Martin Angers <martin.n.angers@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Gabriel Hernandez <ghernandez345@gmail.com> Co-authored-by: Sarah Gillespie <73313222+gillespi314@users.noreply.github.com>
106 lines
2.9 KiB
Go
106 lines
2.9 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 identity_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 identity_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 identity_certificates
|
|
(serial, name, not_valid_before, not_valid_after, certificate_pem)
|
|
VALUES
|
|
(?, ?, ?, ?, ?)`,
|
|
crt.SerialNumber.Int64(),
|
|
name,
|
|
crt.NotBefore,
|
|
crt.NotAfter,
|
|
certPEM,
|
|
)
|
|
return err
|
|
}
|