certificate.go 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. package certificate
  2. import (
  3. "net/http"
  4. "os"
  5. "github.com/0xJacky/Nginx-UI/internal/cert"
  6. "github.com/0xJacky/Nginx-UI/internal/helper"
  7. "github.com/0xJacky/Nginx-UI/internal/nginx"
  8. "github.com/0xJacky/Nginx-UI/internal/notification"
  9. "github.com/0xJacky/Nginx-UI/model"
  10. "github.com/0xJacky/Nginx-UI/query"
  11. "github.com/gin-gonic/gin"
  12. "github.com/go-acme/lego/v4/certcrypto"
  13. "github.com/spf13/cast"
  14. "github.com/uozi-tech/cosy"
  15. )
  16. type APICertificate struct {
  17. *model.Cert
  18. SSLCertificate string `json:"ssl_certificate,omitempty"`
  19. SSLCertificateKey string `json:"ssl_certificate_key,omitempty"`
  20. CertificateInfo *cert.Info `json:"certificate_info,omitempty"`
  21. }
  22. func Transformer(certModel *model.Cert) (certificate *APICertificate) {
  23. var sslCertificationBytes, sslCertificationKeyBytes []byte
  24. var certificateInfo *cert.Info
  25. if certModel.SSLCertificatePath != "" &&
  26. helper.IsUnderDirectory(certModel.SSLCertificatePath, nginx.GetConfPath()) {
  27. if _, err := os.Stat(certModel.SSLCertificatePath); err == nil {
  28. sslCertificationBytes, _ = os.ReadFile(certModel.SSLCertificatePath)
  29. if !cert.IsCertificate(string(sslCertificationBytes)) {
  30. sslCertificationBytes = []byte{}
  31. }
  32. }
  33. certificateInfo, _ = cert.GetCertInfo(certModel.SSLCertificatePath)
  34. }
  35. if certModel.SSLCertificateKeyPath != "" &&
  36. helper.IsUnderDirectory(certModel.SSLCertificateKeyPath, nginx.GetConfPath()) {
  37. if _, err := os.Stat(certModel.SSLCertificateKeyPath); err == nil {
  38. sslCertificationKeyBytes, _ = os.ReadFile(certModel.SSLCertificateKeyPath)
  39. if !cert.IsPrivateKey(string(sslCertificationKeyBytes)) {
  40. sslCertificationKeyBytes = []byte{}
  41. }
  42. }
  43. }
  44. return &APICertificate{
  45. Cert: certModel,
  46. SSLCertificate: string(sslCertificationBytes),
  47. SSLCertificateKey: string(sslCertificationKeyBytes),
  48. CertificateInfo: certificateInfo,
  49. }
  50. }
  51. func GetCertList(c *gin.Context) {
  52. cosy.Core[model.Cert](c).SetFussy("name", "domain").SetTransformer(func(m *model.Cert) any {
  53. info, _ := cert.GetCertInfo(m.SSLCertificatePath)
  54. return APICertificate{
  55. Cert: m,
  56. CertificateInfo: info,
  57. }
  58. }).PagingList()
  59. }
  60. func GetCert(c *gin.Context) {
  61. q := query.Cert
  62. certModel, err := q.FirstByID(cast.ToUint64(c.Param("id")))
  63. if err != nil {
  64. cosy.ErrHandler(c, err)
  65. return
  66. }
  67. c.JSON(http.StatusOK, Transformer(certModel))
  68. }
  69. type certJson struct {
  70. Name string `json:"name" binding:"required"`
  71. SSLCertificatePath string `json:"ssl_certificate_path" binding:"required,certificate_path"`
  72. SSLCertificateKeyPath string `json:"ssl_certificate_key_path" binding:"required,privatekey_path"`
  73. SSLCertificate string `json:"ssl_certificate" binding:"omitempty,certificate"`
  74. SSLCertificateKey string `json:"ssl_certificate_key" binding:"omitempty,privatekey"`
  75. KeyType certcrypto.KeyType `json:"key_type" binding:"omitempty,auto_cert_key_type"`
  76. ChallengeMethod string `json:"challenge_method"`
  77. DnsCredentialID uint64 `json:"dns_credential_id"`
  78. ACMEUserID uint64 `json:"acme_user_id"`
  79. SyncNodeIds []uint64 `json:"sync_node_ids"`
  80. }
  81. func AddCert(c *gin.Context) {
  82. var json certJson
  83. if !cosy.BindAndValid(c, &json) {
  84. return
  85. }
  86. certModel := &model.Cert{
  87. Name: json.Name,
  88. SSLCertificatePath: json.SSLCertificatePath,
  89. SSLCertificateKeyPath: json.SSLCertificateKeyPath,
  90. KeyType: json.KeyType,
  91. ChallengeMethod: json.ChallengeMethod,
  92. DnsCredentialID: json.DnsCredentialID,
  93. ACMEUserID: json.ACMEUserID,
  94. SyncNodeIds: json.SyncNodeIds,
  95. }
  96. err := certModel.Insert()
  97. if err != nil {
  98. cosy.ErrHandler(c, err)
  99. return
  100. }
  101. content := &cert.Content{
  102. SSLCertificatePath: json.SSLCertificatePath,
  103. SSLCertificateKeyPath: json.SSLCertificateKeyPath,
  104. SSLCertificate: json.SSLCertificate,
  105. SSLCertificateKey: json.SSLCertificateKey,
  106. }
  107. err = content.WriteFile()
  108. if err != nil {
  109. cosy.ErrHandler(c, err)
  110. return
  111. }
  112. // Detect and set certificate type
  113. if len(json.SSLCertificate) > 0 {
  114. keyType, err := cert.GetKeyType(json.SSLCertificate)
  115. if err == nil && keyType != "" {
  116. // Set KeyType based on certificate type
  117. switch keyType {
  118. case "2048":
  119. certModel.KeyType = certcrypto.RSA2048
  120. case "3072":
  121. certModel.KeyType = certcrypto.RSA3072
  122. case "4096":
  123. certModel.KeyType = certcrypto.RSA4096
  124. case "P256":
  125. certModel.KeyType = certcrypto.EC256
  126. case "P384":
  127. certModel.KeyType = certcrypto.EC384
  128. }
  129. // Update certificate model
  130. err = certModel.Updates(&model.Cert{KeyType: certModel.KeyType})
  131. if err != nil {
  132. notification.Error("Update Certificate Type Error", err.Error(), nil)
  133. }
  134. }
  135. }
  136. err = cert.SyncToRemoteServer(certModel)
  137. if err != nil {
  138. notification.Error("Sync Certificate Error", err.Error(), nil)
  139. return
  140. }
  141. c.JSON(http.StatusOK, Transformer(certModel))
  142. }
  143. func ModifyCert(c *gin.Context) {
  144. id := cast.ToUint64(c.Param("id"))
  145. var json certJson
  146. if !cosy.BindAndValid(c, &json) {
  147. return
  148. }
  149. q := query.Cert
  150. certModel, err := q.FirstByID(id)
  151. if err != nil {
  152. cosy.ErrHandler(c, err)
  153. return
  154. }
  155. // Create update data object
  156. updateData := &model.Cert{
  157. Name: json.Name,
  158. SSLCertificatePath: json.SSLCertificatePath,
  159. SSLCertificateKeyPath: json.SSLCertificateKeyPath,
  160. ChallengeMethod: json.ChallengeMethod,
  161. KeyType: json.KeyType,
  162. DnsCredentialID: json.DnsCredentialID,
  163. ACMEUserID: json.ACMEUserID,
  164. SyncNodeIds: json.SyncNodeIds,
  165. }
  166. content := &cert.Content{
  167. SSLCertificatePath: json.SSLCertificatePath,
  168. SSLCertificateKeyPath: json.SSLCertificateKeyPath,
  169. SSLCertificate: json.SSLCertificate,
  170. SSLCertificateKey: json.SSLCertificateKey,
  171. }
  172. err = content.WriteFile()
  173. if err != nil {
  174. cosy.ErrHandler(c, err)
  175. return
  176. }
  177. // Detect and set certificate type
  178. if len(json.SSLCertificate) > 0 {
  179. keyType, err := cert.GetKeyType(json.SSLCertificate)
  180. if err == nil && keyType != "" {
  181. // Set KeyType based on certificate type
  182. switch keyType {
  183. case "2048":
  184. updateData.KeyType = certcrypto.RSA2048
  185. case "3072":
  186. updateData.KeyType = certcrypto.RSA3072
  187. case "4096":
  188. updateData.KeyType = certcrypto.RSA4096
  189. case "P256":
  190. updateData.KeyType = certcrypto.EC256
  191. case "P384":
  192. updateData.KeyType = certcrypto.EC384
  193. }
  194. }
  195. }
  196. err = certModel.Updates(updateData)
  197. if err != nil {
  198. cosy.ErrHandler(c, err)
  199. return
  200. }
  201. err = cert.SyncToRemoteServer(certModel)
  202. if err != nil {
  203. notification.Error("Sync Certificate Error", err.Error(), nil)
  204. return
  205. }
  206. GetCert(c)
  207. }
  208. func RemoveCert(c *gin.Context) {
  209. cosy.Core[model.Cert](c).Destroy()
  210. }
  211. func SyncCertificate(c *gin.Context) {
  212. var json cert.SyncCertificatePayload
  213. if !cosy.BindAndValid(c, &json) {
  214. return
  215. }
  216. certModel := &model.Cert{
  217. Name: json.Name,
  218. SSLCertificatePath: json.SSLCertificatePath,
  219. SSLCertificateKeyPath: json.SSLCertificateKeyPath,
  220. KeyType: json.KeyType,
  221. AutoCert: model.AutoCertSync,
  222. }
  223. db := model.UseDB()
  224. err := db.Where(certModel).FirstOrCreate(certModel).Error
  225. if err != nil {
  226. cosy.ErrHandler(c, err)
  227. return
  228. }
  229. content := &cert.Content{
  230. SSLCertificatePath: json.SSLCertificatePath,
  231. SSLCertificateKeyPath: json.SSLCertificateKeyPath,
  232. SSLCertificate: json.SSLCertificate,
  233. SSLCertificateKey: json.SSLCertificateKey,
  234. }
  235. err = content.WriteFile()
  236. if err != nil {
  237. cosy.ErrHandler(c, err)
  238. return
  239. }
  240. nginx.Reload()
  241. c.JSON(http.StatusOK, gin.H{
  242. "message": "ok",
  243. })
  244. }