diff --git a/certdb/sql/database_accessor.go b/certdb/sql/database_accessor.go index 63b0db8bf..d9ff78198 100644 --- a/certdb/sql/database_accessor.go +++ b/certdb/sql/database_accessor.go @@ -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" @@ -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 } diff --git a/config/config.go b/config/config.go index f97d64698..36baf10a5 100644 --- a/config/config.go +++ b/config/config.go @@ -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" @@ -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 diff --git a/errors/error.go b/errors/error.go index 9715a7cfb..3a3f1573d 100644 --- a/errors/error.go +++ b/errors/error.go @@ -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.