Skip to content

Commit

Permalink
Allow API client to provide serial and detect duplicate entry error
Browse files Browse the repository at this point in the history
This mod adds access to `ClientProvidesSerialNumbers` cert profile field
from JSON profile config with `client_provides_serial_numbers` option.
When enabled, cert serial must be provided by API client in request.

This mod adds also specific API error code in case of sign request
failure caused by already taken serial number. For MySQL/MariaDB
and SQLite only (to be done separately for PostgreSQL by devs
using this DB engine).

Author-Change-Id: IB#1137304
Signed-off-by: Pawel Boguslawski <[email protected]>
  • Loading branch information
pboguslawski committed Sep 2, 2023
1 parent b4d0d87 commit 1431e72
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 2 deletions.
35 changes: 34 additions & 1 deletion certdb/sql/database_accessor.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@ package sql
import (
"errors"
"fmt"
"regexp"
"time"

"github.com/cloudflare/cfssl/certdb"
cferr "github.com/cloudflare/cfssl/errors"
"github.com/go-sql-driver/mysql"
"github.com/mattn/go-sqlite3"

"github.com/jmoiron/sqlx"
"github.com/kisielk/sqlstruct"
Expand Down Expand Up @@ -76,7 +79,37 @@ var _ certdb.Accessor = &Accessor{}

func wrapSQLError(err error) error {
if err != nil {
return cferr.Wrap(cferr.CertStoreError, cferr.Unknown, err)

reason := cferr.Unknown

// Use detailed reason on unique constraint errors (i.e. will allow API client
// to detect already used cert serial in DB when API client is
// allowed to provide cert serial on cert singing). We don't detect this
// kind of problems by querying table for exisitng key before insert/update
// to avoid races. Unique constraint errors have different codes in different
// DB engines so must be detected separately.

// MySQL/MariaDB
var mysqlErr *mysql.MySQLError
if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
reason = cferr.DuplicateEntry
}

// SQLite
var sqliteErr sqlite3.Error
if errors.As(err, &sqliteErr) && (sqliteErr.Code == sqlite3.ErrConstraint) {

// Parsing error message is probably the only way to detect duplicate key
// errors in SQLite now...
if regexp.MustCompile(`(^|\s)UNIQUE constraint failed .*`).MatchString(err.Error()) {
reason = cferr.DuplicateEntry
}
}

// PostgresSQL
// TBD. See also: https://github.com/go-gorm/gorm/issues/4135

return cferr.Wrap(cferr.CertStoreError, reason, err)
}
return nil
}
Expand Down
3 changes: 2 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/cloudflare/cfssl/helpers"
"github.com/cloudflare/cfssl/log"
ocspConfig "github.com/cloudflare/cfssl/ocsp/config"

// empty import of zlint/v3 required to have lints registered.
_ "github.com/zmap/zlint/v3"
"github.com/zmap/zlint/v3/lint"
Expand Down Expand Up @@ -121,7 +122,7 @@ type SigningProfile struct {
CSRWhitelist *CSRWhitelist
NameWhitelist *regexp.Regexp
ExtensionWhitelist map[string]bool
ClientProvidesSerialNumbers bool
ClientProvidesSerialNumbers bool `json:"client_provides_serial_numbers"`
// LintRegistry is the collection of lints that should be used if
// LintErrLevel is configured. By default all ZLint lints are used. If
// ExcludeLints or ExcludeLintSources are set then this registry will be
Expand Down
3 changes: 3 additions & 0 deletions errors/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,9 @@ const (
// RecordNotFound occurs when a SQL query targeting on one unique
// record failes to update the specified row in the table.
RecordNotFound
// DuplicateEntry occurs when SQL query tries to insert or update
// using key that must be unique in db table but already exists there.
DuplicateEntry
)

// The error interface implementation, which formats to a JSON object string.
Expand Down

0 comments on commit 1431e72

Please sign in to comment.