From e8f4bca2219721a9d931ca0d0d594357aa69cb87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Benouarets?= Date: Tue, 30 Sep 2025 11:44:10 +0200 Subject: [PATCH] feat: add comprehensive database models and type definitions - Define Certificate model with full X.509 attributes - Add CertificateAuthority model for CA management - Implement CertificateRequest model for CSR handling - Add CertificateRevocationList model for CRL support - Define User and Organization models for access control - Include comprehensive certificate type definitions (web, client, email, etc.) - Add status enums for certificates, requests, and organizations - Configure GORM relationships and constraints --- models/ca.go | 55 +++++++++++++++++++++ models/cert.go | 57 +++++++++++++++++++++ models/crl.go | 37 ++++++++++++++ models/csr.go | 50 +++++++++++++++++++ models/organization.go | 58 ++++++++++++++++++++++ models/types.go | 110 +++++++++++++++++++++++++++++++++++++++++ models/user.go | 39 +++++++++++++++ 7 files changed, 406 insertions(+) create mode 100644 models/ca.go create mode 100644 models/cert.go create mode 100644 models/crl.go create mode 100644 models/csr.go create mode 100644 models/organization.go create mode 100644 models/types.go create mode 100644 models/user.go diff --git a/models/ca.go b/models/ca.go new file mode 100644 index 0000000..f1cb5e0 --- /dev/null +++ b/models/ca.go @@ -0,0 +1,55 @@ +package models + +import ( + "time" + + "github.com/google/uuid" + "gorm.io/gorm" +) + +// CertificateAuthority represents a Certificate Authority (Root or Intermediate) +type CertificateAuthority struct { + ID uuid.UUID `json:"id" gorm:"type:uuid;primary_key;default:gen_random_uuid()"` // ID of the CA + Name string `json:"name" gorm:"not null"` // Name of the CA + Description string `json:"description"` // Description of the CA + SerialNumber string `json:"serial_number" gorm:"not null;unique"` // Serial number of the CA + AttributeCommonName string `json:"attribute_common_name" gorm:"not null"` // Common name of the CA + AttributeOrganization string `json:"attribute_organization" gorm:"not null"` // Organization of the CA + AttributeOrganizationUnit string `json:"attribute_organization_unit" gorm:"not null"` // Organization unit of the CA + AttributeCountry string `json:"attribute_country" gorm:"not null"` // Country of the CA + AttributeState string `json:"attribute_state" gorm:"not null"` // State of the CA + AttributeLocality string `json:"attribute_locality" gorm:"not null"` // Locality of the CA + AttributeStreet string `json:"attribute_street" gorm:"not null"` // Street of the CA + AttributeEmail string `json:"attribute_email" gorm:"not null"` // Email address of the CA + AttributeAddress string `json:"attribute_address" gorm:"not null"` // Address of the CA + AttributePostalCode string `json:"attribute_postal_code" gorm:"not null"` // Postal code of the CA + AttributeNotBefore time.Time `json:"attribute_not_before" gorm:"not null"` // Not before time of the CA + AttributeNotAfter time.Time `json:"attribute_not_after" gorm:"not null"` // Not after time of the CA + Root bool `json:"root" gorm:"not null;default:false"` // If the CA is a root CA + ParentID *uuid.UUID `json:"parent_id" gorm:"type:uuid"` // ID of the parent CA + OrganizationID uuid.UUID `json:"organization_id" gorm:"type:uuid;not null"` // ID of the organization + FileID string `json:"file" gorm:"not null"` // ID of the CA certificate file + PrivateKeyID string `json:"private_key" gorm:"not null"` // ID of the private key file + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` // Created at time + UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` // Updated at time + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` // Deleted at time + + // Relationships + Parent *CertificateAuthority `gorm:"foreignKey:ParentID"` // Parent CA + Organization *Organization `gorm:"foreignKey:OrganizationID"` // Organization of the CA + Children []*CertificateAuthority `gorm:"foreignKey:ParentID"` // Children of the CA + Certificates []*Certificate `gorm:"foreignKey:CertificateAuthorityID"` // Certificates of the CA + Requests []*CertificateRequest `gorm:"foreignKey:CertificateAuthorityID"` // Requests of the CA + CRLs []*CertificateRevocationList `gorm:"foreignKey:CertificateAuthorityID"` // CRLs of the CA +} + +// TableName returns the table name for CertificateAuthority +func (ca *CertificateAuthority) TableName() string { + return "certificate_authorities" +} + +// BeforeCreate generates a new UUID before creating the record +func (ca *CertificateAuthority) BeforeCreate(tx *gorm.DB) (err error) { + ca.ID = uuid.New() + return +} diff --git a/models/cert.go b/models/cert.go new file mode 100644 index 0000000..c969b23 --- /dev/null +++ b/models/cert.go @@ -0,0 +1,57 @@ +package models + +import ( + "time" + + "github.com/google/uuid" + "gorm.io/gorm" +) + +// Certificate represents a digital certificate +type Certificate struct { + ID uuid.UUID `json:"id" gorm:"type:uuid;primary_key;default:gen_random_uuid()"` + Name string `json:"name" gorm:"not null"` + Description string `json:"description"` + SerialNumber string `json:"serial_number" gorm:"not null;unique"` + AttributeCommonName string `json:"attribute_common_name" gorm:"not null"` + AttributeSubjectAlternativeName string `json:"attribute_subject_alternative_name"` // JSON array of SANs + AttributeOrganization string `json:"attribute_organization" gorm:"not null"` + AttributeOrganizationUnit string `json:"attribute_organization_unit" gorm:"not null"` + AttributeCountry string `json:"attribute_country" gorm:"not null"` + AttributeState string `json:"attribute_state" gorm:"not null"` + AttributeLocality string `json:"attribute_locality" gorm:"not null"` + AttributeStreet string `json:"attribute_street" gorm:"not null"` + AttributeEmail string `json:"attribute_email" gorm:"not null"` + AttributeAddress string `json:"attribute_address" gorm:"not null"` + AttributePostalCode string `json:"attribute_postal_code" gorm:"not null"` + AttributeNotBefore time.Time `json:"attribute_not_before" gorm:"not null"` + AttributeNotAfter time.Time `json:"attribute_not_after" gorm:"not null"` + Type CertificateType `json:"type" gorm:"not null"` + Status CertificateStatus `json:"status" gorm:"not null;default:'pending'"` + CertificateAuthorityID uuid.UUID `json:"ca_id" gorm:"type:uuid;not null"` + RequestID *uuid.UUID `json:"request_id" gorm:"type:uuid"` + FileID string `json:"file_id" gorm:"not null"` + PrivateKeyID string `json:"private_key_id" gorm:"not null"` + Generated bool `json:"generated" gorm:"not null;default:false"` + GeneratedAt *time.Time `json:"generated_at"` + RevocationReason string `json:"revocation_reason"` + RevokedAt *time.Time `json:"revoked_at"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` + UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` + + // Relationships + CertificateAuthority *CertificateAuthority `json:"certificate_authority" gorm:"foreignKey:CertificateAuthorityID"` + Request *CertificateRequest `json:"request" gorm:"foreignKey:RequestID"` +} + +// TableName returns the table name for Certificate +func (c *Certificate) TableName() string { + return "certificates" +} + +// BeforeCreate generates a new UUID before creating the record +func (c *Certificate) BeforeCreate(tx *gorm.DB) (err error) { + c.ID = uuid.New() + return +} diff --git a/models/crl.go b/models/crl.go new file mode 100644 index 0000000..6025f36 --- /dev/null +++ b/models/crl.go @@ -0,0 +1,37 @@ +package models + +import ( + "time" + + "github.com/google/uuid" + "gorm.io/gorm" +) + +// CertificateRevocationList represents a Certificate Revocation List (CRL) +type CertificateRevocationList struct { + ID uuid.UUID `json:"id" gorm:"type:uuid;primary_key;default:gen_random_uuid()"` + SerialNumber string `json:"serial_number" gorm:"not null;unique"` + CertificateAuthorityID uuid.UUID `json:"certificate_authority_id" gorm:"type:uuid;not null"` + Version int `json:"version" gorm:"not null;default:2"` + ThisUpdate time.Time `json:"this_update" gorm:"not null"` + NextUpdate time.Time `json:"next_update" gorm:"not null"` + FilePath string `json:"file_path" gorm:"not null"` // Path to CRL file + RevokedCertificates string `json:"revoked_certificates"` // JSON array of revoked certificate serial numbers with reasons + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` + UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` + + // Relationships + CertificateAuthority *CertificateAuthority `json:"certificate_authority" gorm:"foreignKey:CertificateAuthorityID"` +} + +// TableName returns the table name for CertificateRevocationList +func (crl *CertificateRevocationList) TableName() string { + return "certificate_revocation_lists" +} + +// BeforeCreate generates a new UUID before creating the record +func (crl *CertificateRevocationList) BeforeCreate(tx *gorm.DB) (err error) { + crl.ID = uuid.New() + return +} diff --git a/models/csr.go b/models/csr.go new file mode 100644 index 0000000..9494668 --- /dev/null +++ b/models/csr.go @@ -0,0 +1,50 @@ +package models + +import ( + "time" + + "github.com/google/uuid" + "gorm.io/gorm" +) + +// CertificateRequest represents a request for a certificate (CSR) +type CertificateRequest struct { + ID uuid.UUID `json:"id" gorm:"type:uuid;primary_key;default:gen_random_uuid()"` + CommonName string `json:"common_name" gorm:"not null"` + SubjectAlternativeName string `json:"subject_alternative_name"` // JSON array of SANs + Type CertificateType `json:"type" gorm:"not null"` + Status RequestStatus `json:"status" gorm:"not null;default:'pending'"` + CertificateAuthorityID uuid.UUID `json:"certificate_authority_id" gorm:"type:uuid;not null"` + RequesterName string `json:"requester_name" gorm:"not null"` + RequesterEmail string `json:"requester_email" gorm:"not null"` + RequesterPhone string `json:"requester_phone"` + Organization string `json:"organization"` + Department string `json:"department"` + City string `json:"city"` + State string `json:"state"` + Country string `json:"country"` + CSRFilePath string `json:"csr_file_path"` // Path to CSR file + CSRData []byte `json:"csr_data" gorm:"type:bytea"` // CSR data in DER format + ValidDays int `json:"valid_days" gorm:"not null;default:365"` + ApprovedBy *uuid.UUID `json:"approved_by" gorm:"type:uuid"` + ApprovedAt *time.Time `json:"approved_at"` + RejectionReason string `json:"rejection_reason"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` + UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` + + // Relationships + CertificateAuthority *CertificateAuthority `json:"certificate_authority" gorm:"foreignKey:CertificateAuthorityID"` + Certificates []*Certificate `json:"certificates" gorm:"foreignKey:RequestID"` +} + +// TableName returns the table name for CertificateRequest +func (cr *CertificateRequest) TableName() string { + return "certificate_requests" +} + +// BeforeCreate generates a new UUID before creating the record +func (cr *CertificateRequest) BeforeCreate(tx *gorm.DB) (err error) { + cr.ID = uuid.New() + return +} diff --git a/models/organization.go b/models/organization.go new file mode 100644 index 0000000..407a9ff --- /dev/null +++ b/models/organization.go @@ -0,0 +1,58 @@ +package models + +import ( + "fmt" + "time" + + "git.secnex.io/secnex/certman/utils" + "github.com/google/uuid" + "gorm.io/gorm" +) + +type Organization struct { + ID uuid.UUID `json:"id" gorm:"type:uuid;primary_key;default:gen_random_uuid()"` + Name string `json:"name" gorm:"not null;unique"` + Description string `json:"description"` + Address string `json:"address"` + City string `json:"city" gorm:"not null"` + State string `json:"state"` + Country string `json:"country" gorm:"not null"` + PostalCode string `json:"postal_code"` + Phone string `json:"phone"` + Email string `json:"email" gorm:"not null;unique"` + Website string `json:"website" gorm:"not null;unique"` + Logo string `json:"logo"` + Status OrganizationStatus `json:"status" gorm:"not null;default:'active'"` + CreatedBy uuid.UUID `json:"created_by" gorm:"type:uuid;not null"` + UpdatedBy uuid.UUID `json:"updated_by" gorm:"type:uuid;not null"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` + UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` + + CertificateAuthorities []*CertificateAuthority `gorm:"foreignKey:OrganizationID"` + + UserCreatedBy User `gorm:"foreignKey:CreatedBy"` + UserUpdatedBy User `gorm:"foreignKey:UpdatedBy"` + + // Members of organizations + Members []*User `gorm:"many2many:organization_members;"` +} + +func (o *Organization) TableName() string { + return "organizations" +} + +func (o *Organization) BeforeCreate(tx *gorm.DB) (err error) { + o.ID = uuid.New() + // Check if the email address matches the email regex and the domain is the same as the website + if !utils.IsEmailValid(o.Email) { + return fmt.Errorf("invalid email address") + } + if !utils.IsDomainValid(o.Website) { + return fmt.Errorf("invalid website domain") + } + if !utils.IsEmailDomainValid(o.Email, o.Website) { + return fmt.Errorf("email and website domains do not match") + } + return +} diff --git a/models/types.go b/models/types.go new file mode 100644 index 0000000..0329695 --- /dev/null +++ b/models/types.go @@ -0,0 +1,110 @@ +package models + +// CertificateType represents the type of certificate +type CertificateType string + +const ( + // Web certificates for HTTPS/TLS + CertificateTypeWeb CertificateType = "web" + CertificateTypeServer CertificateType = "server" + + // Client certificates for authentication + CertificateTypeClient CertificateType = "client" + CertificateTypeUser CertificateType = "user" + + // Email certificates for S/MIME + CertificateTypeEmail CertificateType = "email" + + // Code signing certificates + CertificateTypeCode CertificateType = "code" + + // Certificate Authority certificates + CertificateTypeCA CertificateType = "ca" + + // IoT and embedded device certificates + CertificateTypeIoT CertificateType = "iot" + CertificateTypeDevice CertificateType = "device" + CertificateTypeSensor CertificateType = "sensor" + + // VPN certificates + CertificateTypeVPN CertificateType = "vpn" + CertificateTypeOpenVPN CertificateType = "openvpn" + CertificateTypeWireGuard CertificateType = "wireguard" + + // Database certificates + CertificateTypeDatabase CertificateType = "database" + CertificateTypeMySQL CertificateType = "mysql" + CertificateTypePostgreSQL CertificateType = "postgresql" + CertificateTypeMongoDB CertificateType = "mongodb" + + // API and service certificates + CertificateTypeAPI CertificateType = "api" + CertificateTypeService CertificateType = "service" + CertificateTypeMicroservice CertificateType = "microservice" + + // Container and orchestration certificates + CertificateTypeDocker CertificateType = "docker" + CertificateTypeKubernetes CertificateType = "kubernetes" + CertificateTypeContainer CertificateType = "container" + + // Cloud and infrastructure certificates + CertificateTypeCloud CertificateType = "cloud" + CertificateTypeAWS CertificateType = "aws" + CertificateTypeAzure CertificateType = "azure" + CertificateTypeGCP CertificateType = "gcp" + + // Network and security certificates + CertificateTypeNetwork CertificateType = "network" + CertificateTypeFirewall CertificateType = "firewall" + CertificateTypeProxy CertificateType = "proxy" + CertificateTypeLoadBalancer CertificateType = "loadbalancer" + + // Mobile and application certificates + CertificateTypeMobile CertificateType = "mobile" + CertificateTypeAndroid CertificateType = "android" + CertificateTypeiOS CertificateType = "ios" + CertificateTypeApp CertificateType = "app" + + // Document and file signing + CertificateTypeDocument CertificateType = "document" + CertificateTypePDF CertificateType = "pdf" + CertificateTypeOffice CertificateType = "office" + + // Time stamping certificates + CertificateTypeTimestamp CertificateType = "timestamp" + + // OCSP responder certificates + CertificateTypeOCSP CertificateType = "ocsp" + + // Custom and specialized certificates + CertificateTypeCustom CertificateType = "custom" + CertificateTypeSpecial CertificateType = "special" +) + +// CertificateStatus represents the status of a certificate +type CertificateStatus string + +const ( + CertificateStatusActive CertificateStatus = "active" + CertificateStatusRevoked CertificateStatus = "revoked" + CertificateStatusExpired CertificateStatus = "expired" + CertificateStatusPending CertificateStatus = "pending" +) + +// RequestStatus represents the status of a certificate request +type RequestStatus string + +const ( + RequestStatusPending RequestStatus = "pending" + RequestStatusApproved RequestStatus = "approved" + RequestStatusRejected RequestStatus = "rejected" + RequestStatusCancelled RequestStatus = "cancelled" +) + +// OrganizationStatus represents the status of an organization +type OrganizationStatus string + +const ( + OrganizationStatusActive OrganizationStatus = "active" + OrganizationStatusInactive OrganizationStatus = "inactive" +) diff --git a/models/user.go b/models/user.go new file mode 100644 index 0000000..d62e727 --- /dev/null +++ b/models/user.go @@ -0,0 +1,39 @@ +package models + +import ( + "time" + + "git.secnex.io/secnex/certman/utils" + "github.com/google/uuid" + "gorm.io/gorm" +) + +type User struct { + ID uuid.UUID `json:"id" gorm:"type:uuid;primary_key;default:gen_random_uuid()"` + Username string `json:"username" gorm:"not null;unique"` + Email string `json:"email" gorm:"not null;unique"` + Password string `json:"password" gorm:"not null"` + ExternalID *string `json:"external_id" gorm:"unique"` + CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"` + UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"` + DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"` + + OrganizationsCreated []*Organization `json:"organizations_created" gorm:"foreignKey:CreatedBy"` + OrganizationsUpdated []*Organization `json:"organizations_updated" gorm:"foreignKey:UpdatedBy"` + + // Members of organizations + Organizations []*Organization `json:"organizations" gorm:"many2many:organization_members;"` +} + +func (u *User) TableName() string { + return "users" +} + +func (u *User) BeforeCreate(tx *gorm.DB) (err error) { + u.ID = uuid.New() + u.Password, err = utils.HashPassword(u.Password, utils.DefaultParams) + if err != nil { + return err + } + return +}