certificate.go 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  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. RevokeOld bool `json:"revoke_old"`
  81. }
  82. func AddCert(c *gin.Context) {
  83. var json certJson
  84. if !cosy.BindAndValid(c, &json) {
  85. return
  86. }
  87. certModel := &model.Cert{
  88. Name: json.Name,
  89. SSLCertificatePath: json.SSLCertificatePath,
  90. SSLCertificateKeyPath: json.SSLCertificateKeyPath,
  91. KeyType: json.KeyType,
  92. ChallengeMethod: json.ChallengeMethod,
  93. DnsCredentialID: json.DnsCredentialID,
  94. ACMEUserID: json.ACMEUserID,
  95. SyncNodeIds: json.SyncNodeIds,
  96. }
  97. err := certModel.Insert()
  98. if err != nil {
  99. cosy.ErrHandler(c, err)
  100. return
  101. }
  102. content := &cert.Content{
  103. SSLCertificatePath: json.SSLCertificatePath,
  104. SSLCertificateKeyPath: json.SSLCertificateKeyPath,
  105. SSLCertificate: json.SSLCertificate,
  106. SSLCertificateKey: json.SSLCertificateKey,
  107. }
  108. err = content.WriteFile()
  109. if err != nil {
  110. cosy.ErrHandler(c, err)
  111. return
  112. }
  113. // Detect and set certificate type
  114. if len(json.SSLCertificate) > 0 {
  115. keyType, err := cert.GetKeyType(json.SSLCertificate)
  116. if err == nil && keyType != "" {
  117. // Set KeyType based on certificate type
  118. switch keyType {
  119. case "2048":
  120. certModel.KeyType = certcrypto.RSA2048
  121. case "3072":
  122. certModel.KeyType = certcrypto.RSA3072
  123. case "4096":
  124. certModel.KeyType = certcrypto.RSA4096
  125. case "P256":
  126. certModel.KeyType = certcrypto.EC256
  127. case "P384":
  128. certModel.KeyType = certcrypto.EC384
  129. }
  130. // Update certificate model
  131. err = certModel.Updates(&model.Cert{KeyType: certModel.KeyType})
  132. if err != nil {
  133. notification.Error("Update Certificate Type Error", err.Error(), nil)
  134. }
  135. }
  136. }
  137. err = cert.SyncToRemoteServer(certModel)
  138. if err != nil {
  139. notification.Error("Sync Certificate Error", err.Error(), nil)
  140. return
  141. }
  142. c.JSON(http.StatusOK, Transformer(certModel))
  143. }
  144. func ModifyCert(c *gin.Context) {
  145. id := cast.ToUint64(c.Param("id"))
  146. var json certJson
  147. if !cosy.BindAndValid(c, &json) {
  148. return
  149. }
  150. q := query.Cert
  151. certModel, err := q.FirstByID(id)
  152. if err != nil {
  153. cosy.ErrHandler(c, err)
  154. return
  155. }
  156. // Create update data object
  157. updateData := &model.Cert{
  158. Name: json.Name,
  159. SSLCertificatePath: json.SSLCertificatePath,
  160. SSLCertificateKeyPath: json.SSLCertificateKeyPath,
  161. ChallengeMethod: json.ChallengeMethod,
  162. KeyType: json.KeyType,
  163. DnsCredentialID: json.DnsCredentialID,
  164. ACMEUserID: json.ACMEUserID,
  165. SyncNodeIds: json.SyncNodeIds,
  166. RevokeOld: json.RevokeOld,
  167. }
  168. content := &cert.Content{
  169. SSLCertificatePath: json.SSLCertificatePath,
  170. SSLCertificateKeyPath: json.SSLCertificateKeyPath,
  171. SSLCertificate: json.SSLCertificate,
  172. SSLCertificateKey: json.SSLCertificateKey,
  173. }
  174. err = content.WriteFile()
  175. if err != nil {
  176. cosy.ErrHandler(c, err)
  177. return
  178. }
  179. // Detect and set certificate type
  180. if len(json.SSLCertificate) > 0 {
  181. keyType, err := cert.GetKeyType(json.SSLCertificate)
  182. if err == nil && keyType != "" {
  183. // Set KeyType based on certificate type
  184. switch keyType {
  185. case "2048":
  186. updateData.KeyType = certcrypto.RSA2048
  187. case "3072":
  188. updateData.KeyType = certcrypto.RSA3072
  189. case "4096":
  190. updateData.KeyType = certcrypto.RSA4096
  191. case "P256":
  192. updateData.KeyType = certcrypto.EC256
  193. case "P384":
  194. updateData.KeyType = certcrypto.EC384
  195. }
  196. }
  197. }
  198. err = certModel.Updates(updateData)
  199. if err != nil {
  200. cosy.ErrHandler(c, err)
  201. return
  202. }
  203. err = cert.SyncToRemoteServer(certModel)
  204. if err != nil {
  205. notification.Error("Sync Certificate Error", err.Error(), nil)
  206. return
  207. }
  208. GetCert(c)
  209. }
  210. func RemoveCert(c *gin.Context) {
  211. cosy.Core[model.Cert](c).Destroy()
  212. }
  213. func SyncCertificate(c *gin.Context) {
  214. var json cert.SyncCertificatePayload
  215. if !cosy.BindAndValid(c, &json) {
  216. return
  217. }
  218. certModel := &model.Cert{
  219. Name: json.Name,
  220. SSLCertificatePath: json.SSLCertificatePath,
  221. SSLCertificateKeyPath: json.SSLCertificateKeyPath,
  222. KeyType: json.KeyType,
  223. AutoCert: model.AutoCertSync,
  224. }
  225. db := model.UseDB()
  226. err := db.Where(certModel).FirstOrCreate(certModel).Error
  227. if err != nil {
  228. cosy.ErrHandler(c, err)
  229. return
  230. }
  231. content := &cert.Content{
  232. SSLCertificatePath: json.SSLCertificatePath,
  233. SSLCertificateKeyPath: json.SSLCertificateKeyPath,
  234. SSLCertificate: json.SSLCertificate,
  235. SSLCertificateKey: json.SSLCertificateKey,
  236. }
  237. err = content.WriteFile()
  238. if err != nil {
  239. cosy.ErrHandler(c, err)
  240. return
  241. }
  242. nginx.Reload()
  243. c.JSON(http.StatusOK, gin.H{
  244. "message": "ok",
  245. })
  246. }