Browse Source

feat: configurable cert key type #264

Jacky 1 year ago
parent
commit
22d62e420b

+ 11 - 7
api/certificate/certificate.go

@@ -7,6 +7,7 @@ import (
 	"github.com/0xJacky/Nginx-UI/model"
 	"github.com/0xJacky/Nginx-UI/query"
 	"github.com/gin-gonic/gin"
+	"github.com/go-acme/lego/v4/certcrypto"
 	"github.com/spf13/cast"
 	"net/http"
 	"os"
@@ -76,13 +77,14 @@ func GetCert(c *gin.Context) {
 }
 
 type certJson struct {
-	Name                  string `json:"name" binding:"required"`
-	SSLCertificatePath    string `json:"ssl_certificate_path" binding:"required,certificate_path"`
-	SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required,privatekey_path"`
-	SSLCertificate        string `json:"ssl_certificate" binding:"omitempty,certificate"`
-	SSLCertificateKey     string `json:"ssl_certificate_key" binding:"omitempty,privatekey"`
-	ChallengeMethod       string `json:"challenge_method"`
-	DnsCredentialID       int    `json:"dns_credential_id"`
+	Name                  string             `json:"name" binding:"required"`
+	SSLCertificatePath    string             `json:"ssl_certificate_path" binding:"required,certificate_path"`
+	SSLCertificateKeyPath string             `json:"ssl_certificate_key_path" binding:"required,privatekey_path"`
+	SSLCertificate        string             `json:"ssl_certificate" binding:"omitempty,certificate"`
+	SSLCertificateKey     string             `json:"ssl_certificate_key" binding:"omitempty,privatekey"`
+	KeyType               certcrypto.KeyType `json:"key_type" binding:"omitempty,auto_cert_key_type"`
+	ChallengeMethod       string             `json:"challenge_method"`
+	DnsCredentialID       int                `json:"dns_credential_id"`
 }
 
 func AddCert(c *gin.Context) {
@@ -96,6 +98,7 @@ func AddCert(c *gin.Context) {
 		Name:                  json.Name,
 		SSLCertificatePath:    json.SSLCertificatePath,
 		SSLCertificateKeyPath: json.SSLCertificateKeyPath,
+		KeyType:               json.KeyType,
 		ChallengeMethod:       json.ChallengeMethod,
 		DnsCredentialID:       json.DnsCredentialID,
 	}
@@ -146,6 +149,7 @@ func ModifyCert(c *gin.Context) {
 		SSLCertificatePath:    json.SSLCertificatePath,
 		SSLCertificateKeyPath: json.SSLCertificateKeyPath,
 		ChallengeMethod:       json.ChallengeMethod,
+		KeyType:               json.KeyType,
 		DnsCredentialID:       json.DnsCredentialID,
 	})
 

+ 1 - 0
api/certificate/issue.go

@@ -122,6 +122,7 @@ func IssueCert(c *gin.Context) {
 		SSLCertificatePath:    sslCertificatePath,
 		SSLCertificateKeyPath: sslCertificateKeyPath,
 		AutoCert:              model.AutoCertEnabled,
+		KeyType:               payload.KeyType,
 	})
 
 	if err != nil {

+ 1 - 0
app/src/api/cert.ts

@@ -14,6 +14,7 @@ export interface Cert extends ModelBase {
   challenge_method: string
   dns_credential_id: number
   dns_credential?: DnsCredential
+  key_type: string
   log: string
   certificate_info: CertificateInfo
 }

+ 1 - 1
app/src/views/certificate/RenewCert.vue

@@ -21,7 +21,7 @@ const data = inject('data') as Ref<Cert>
 const issueCert = () => {
   modalVisible.value = true
 
-  refObtainCertLive.value.issue_cert(data.value.name, data.value.domains).then(() => {
+  refObtainCertLive.value.issue_cert(data.value.name, data.value.domains, data.value.key_type).then(() => {
     message.success($gettext('Renew successfully'))
     emit('renewed')
   })

+ 6 - 4
app/src/views/certificate/WildcardCertificate.vue

@@ -44,10 +44,12 @@ const issueCert = () => {
   step.value++
   modalVisible.value = true
 
-  refObtainCertLive.value.issue_cert(computedDomain.value, [computedDomain.value, domain.value]).then(() => {
-    message.success($gettext('Renew successfully'))
-    emit('issued')
-  })
+  refObtainCertLive.value.issue_cert(computedDomain.value,
+    [computedDomain.value, domain.value])
+    .then(() => {
+      message.success($gettext('Renew successfully'))
+      emit('issued')
+    })
 }
 </script>
 

+ 45 - 1
app/src/views/domain/cert/components/AutoCertStepOne.vue

@@ -3,6 +3,7 @@ import { useGettext } from 'vue3-gettext'
 import type { Ref } from 'vue'
 import type { DnsChallenge } from '@/api/auto_cert'
 import DNSChallenge from '@/views/domain/cert/components/DNSChallenge.vue'
+import type { Cert } from '@/api/cert'
 
 defineProps<{
   hideNote?: boolean
@@ -13,7 +14,39 @@ const { $gettext } = useGettext()
 const no_server_name = inject('no_server_name')
 
 // Provide by ObtainCert.vue
-const data = inject('data') as Ref<DnsChallenge>
+const data = inject('data') as Ref<DnsChallenge & Cert>
+
+const keyType = shallowRef([
+  {
+    key: '2048',
+    name: 'RSA2048',
+  },
+  {
+    key: '3072',
+    name: 'RSA3072',
+  },
+  {
+    key: '4096',
+    name: 'RSA4096',
+  },
+  {
+    key: '8192',
+    name: 'RAS8192',
+  },
+  {
+    key: 'P256',
+    name: 'EC256',
+  },
+  {
+    key: 'P384',
+    name: 'EC384',
+  },
+])
+
+onMounted(() => {
+  if (data.value.key_type === '')
+    data.value.key_type = 'RSA2048'
+})
 </script>
 
 <template>
@@ -72,6 +105,17 @@ const data = inject('data') as Ref<DnsChallenge>
           </ASelectOption>
         </ASelect>
       </AFormItem>
+      <AFormItem :label="$gettext('Key Type')">
+        <ASelect v-model:value="data.key_type">
+          <ASelectOption
+            v-for="t in keyType"
+            :key="t.key"
+            :value="t.key"
+          >
+            {{ t.name }}
+          </ASelectOption>
+        </ASelect>
+      </AFormItem>
     </AForm>
     <DNSChallenge v-if="data.challenge_method === 'dns01'" />
   </div>

+ 2 - 1
app/src/views/domain/cert/components/ObtainCertLive.vue

@@ -59,7 +59,7 @@ function log(msg: string) {
   logContainer.value?.scroll({ top: 100000, left: 0, behavior: 'smooth' })
 }
 
-const issue_cert = async (config_name: string, server_name: string[]) => {
+const issue_cert = async (config_name: string, server_name: string[], key_type: string) => {
   return new Promise<CertificateResult>((resolve, reject) => {
     progressStatus.value = 'active'
     modalClosable.value = false
@@ -74,6 +74,7 @@ const issue_cert = async (config_name: string, server_name: string[]) => {
     ws.onopen = () => {
       ws.send(JSON.stringify({
         server_name,
+        key_type,
         ...data.value,
       }))
     }

+ 1 - 0
internal/cert/auto_cert.go

@@ -74,6 +74,7 @@ func renew(certModel *model.Cert) {
 		ServerName:      certModel.Domains,
 		ChallengeMethod: certModel.ChallengeMethod,
 		DNSCredentialID: certModel.DnsCredentialID,
+		KeyType:         certModel.GetKeyType(),
 	}
 
 	// errChan will be closed inside IssueCert

+ 5 - 4
internal/cert/cert.go

@@ -32,9 +32,10 @@ const (
 )
 
 type ConfigPayload struct {
-	ServerName      []string `json:"server_name"`
-	ChallengeMethod string   `json:"challenge_method"`
-	DNSCredentialID int      `json:"dns_credential_id"`
+	ServerName      []string           `json:"server_name"`
+	ChallengeMethod string             `json:"challenge_method"`
+	DNSCredentialID int                `json:"dns_credential_id"`
+	KeyType         certcrypto.KeyType `json:"key_type"`
 }
 
 func IssueCert(payload *ConfigPayload, logChan chan string, errChan chan error) {
@@ -93,7 +94,7 @@ func IssueCert(payload *ConfigPayload, logChan chan string, errChan chan error)
 		}
 	}
 
-	config.Certificate.KeyType = certcrypto.RSA2048
+	config.Certificate.KeyType = payload.KeyType
 
 	l.Println("[INFO] [Nginx UI] Creating client facilitates communication with the CA server")
 	// A client facilitates communication with the CA server.

+ 15 - 0
internal/validation/key_type.go

@@ -0,0 +1,15 @@
+package validation
+
+import (
+	"github.com/go-acme/lego/v4/certcrypto"
+	val "github.com/go-playground/validator/v10"
+)
+
+func autoCertKeyType(fl val.FieldLevel) bool {
+	switch certcrypto.KeyType(fl.Field().String()) {
+	case certcrypto.RSA2048, certcrypto.RSA3072, certcrypto.RSA4096,
+		certcrypto.EC256, certcrypto.EC384:
+		return true
+	}
+	return false
+}

+ 6 - 0
internal/validation/validation.go

@@ -42,5 +42,11 @@ func Init() {
 		logger.Fatal(err)
 	}
 
+	err = v.RegisterValidation("auto_cert_key_type", autoCertKeyType)
+
+	if err != nil {
+		logger.Fatal(err)
+	}
+
 	return
 }

+ 21 - 10
model/cert.go

@@ -2,6 +2,7 @@ package model
 
 import (
 	"github.com/0xJacky/Nginx-UI/internal/nginx"
+	"github.com/go-acme/lego/v4/certcrypto"
 	"github.com/lib/pq"
 	"os"
 )
@@ -17,16 +18,17 @@ type CertDomains []string
 
 type Cert struct {
 	Model
-	Name                  string         `json:"name"`
-	Domains               pq.StringArray `json:"domains" gorm:"type:text[]"`
-	Filename              string         `json:"filename"`
-	SSLCertificatePath    string         `json:"ssl_certificate_path"`
-	SSLCertificateKeyPath string         `json:"ssl_certificate_key_path"`
-	AutoCert              int            `json:"auto_cert"`
-	ChallengeMethod       string         `json:"challenge_method"`
-	DnsCredentialID       int            `json:"dns_credential_id"`
-	DnsCredential         *DnsCredential `json:"dns_credential,omitempty"`
-	Log                   string         `json:"log"`
+	Name                  string             `json:"name"`
+	Domains               pq.StringArray     `json:"domains" gorm:"type:text[]"`
+	Filename              string             `json:"filename"`
+	SSLCertificatePath    string             `json:"ssl_certificate_path"`
+	SSLCertificateKeyPath string             `json:"ssl_certificate_key_path"`
+	AutoCert              int                `json:"auto_cert"`
+	ChallengeMethod       string             `json:"challenge_method"`
+	DnsCredentialID       int                `json:"dns_credential_id"`
+	DnsCredential         *DnsCredential     `json:"dns_credential,omitempty"`
+	KeyType               certcrypto.KeyType `json:"key_type"`
+	Log                   string             `json:"log"`
 }
 
 func FirstCert(confName string) (c Cert, err error) {
@@ -90,3 +92,12 @@ func (c *Cert) Remove() error {
 
 	return db.Where("filename", c.Filename).Delete(c).Error
 }
+
+func (c *Cert) GetKeyType() certcrypto.KeyType {
+	switch c.KeyType {
+	case certcrypto.RSA2048, certcrypto.RSA3072, certcrypto.RSA4096,
+		certcrypto.EC256, certcrypto.EC384:
+		return c.KeyType
+	}
+	return certcrypto.RSA2048
+}