acme.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. package acme
  2. import (
  3. "crypto"
  4. "crypto/ecdsa"
  5. "crypto/elliptic"
  6. "crypto/rand"
  7. "crypto/tls"
  8. "crypto/x509"
  9. "encoding/json"
  10. "encoding/pem"
  11. "errors"
  12. "fmt"
  13. "log"
  14. "net"
  15. "net/http"
  16. "os"
  17. "path/filepath"
  18. "strconv"
  19. "strings"
  20. "time"
  21. "github.com/go-acme/lego/v4/certcrypto"
  22. "github.com/go-acme/lego/v4/certificate"
  23. "github.com/go-acme/lego/v4/challenge/http01"
  24. "github.com/go-acme/lego/v4/lego"
  25. "github.com/go-acme/lego/v4/registration"
  26. "imuslab.com/zoraxy/mod/database"
  27. "imuslab.com/zoraxy/mod/utils"
  28. )
  29. type CertificateInfoJSON struct {
  30. AcmeName string `json:"acme_name"`
  31. AcmeUrl string `json:"acme_url"`
  32. SkipTLS bool `json:"skip_tls"`
  33. UseDNS bool `json:"dns"`
  34. }
  35. // ACMEUser represents a user in the ACME system.
  36. type ACMEUser struct {
  37. Email string
  38. Registration *registration.Resource
  39. key crypto.PrivateKey
  40. }
  41. type EABConfig struct {
  42. Kid string `json:"kid"`
  43. HmacKey string `json:"HmacKey"`
  44. }
  45. // GetEmail returns the email of the ACMEUser.
  46. func (u *ACMEUser) GetEmail() string {
  47. return u.Email
  48. }
  49. // GetRegistration returns the registration resource of the ACMEUser.
  50. func (u ACMEUser) GetRegistration() *registration.Resource {
  51. return u.Registration
  52. }
  53. // GetPrivateKey returns the private key of the ACMEUser.
  54. func (u *ACMEUser) GetPrivateKey() crypto.PrivateKey {
  55. return u.key
  56. }
  57. // ACMEHandler handles ACME-related operations.
  58. type ACMEHandler struct {
  59. DefaultAcmeServer string
  60. Port string
  61. Database *database.Database
  62. }
  63. // NewACME creates a new ACMEHandler instance.
  64. func NewACME(acmeServer string, port string, database *database.Database) *ACMEHandler {
  65. return &ACMEHandler{
  66. DefaultAcmeServer: acmeServer,
  67. Port: port,
  68. Database: database,
  69. }
  70. }
  71. // ObtainCert obtains a certificate for the specified domains.
  72. func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email string, caName string, caUrl string, skipTLS bool, useDNS bool) (bool, error) {
  73. log.Println("[ACME] Obtaining certificate...")
  74. // generate private key
  75. privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
  76. if err != nil {
  77. log.Println(err)
  78. return false, err
  79. }
  80. // create a admin user for our new generation
  81. adminUser := ACMEUser{
  82. Email: email,
  83. key: privateKey,
  84. }
  85. // create config
  86. config := lego.NewConfig(&adminUser)
  87. // skip TLS verify if need
  88. // Ref: https://github.com/go-acme/lego/blob/6af2c756ac73a9cb401621afca722d0f4112b1b8/lego/client_config.go#L74
  89. if skipTLS {
  90. log.Println("[INFO] Ignore TLS/SSL Verification Error for ACME Server")
  91. config.HTTPClient.Transport = &http.Transport{
  92. Proxy: http.ProxyFromEnvironment,
  93. DialContext: (&net.Dialer{
  94. Timeout: 30 * time.Second,
  95. KeepAlive: 30 * time.Second,
  96. }).DialContext,
  97. TLSHandshakeTimeout: 30 * time.Second,
  98. ResponseHeaderTimeout: 30 * time.Second,
  99. TLSClientConfig: &tls.Config{
  100. InsecureSkipVerify: true,
  101. },
  102. }
  103. }
  104. // setup the custom ACME url endpoint.
  105. if caUrl != "" {
  106. config.CADirURL = caUrl
  107. }
  108. // if not custom ACME url, load it from ca.json
  109. if caName == "custom" {
  110. log.Println("[INFO] Using Custom ACME " + caUrl + " for CA Directory URL")
  111. } else {
  112. caLinkOverwrite, err := loadCAApiServerFromName(caName)
  113. if err == nil {
  114. config.CADirURL = caLinkOverwrite
  115. log.Println("[INFO] Using " + caLinkOverwrite + " for CA Directory URL")
  116. } else {
  117. // (caName == "" || caUrl == "") will use default acme
  118. config.CADirURL = a.DefaultAcmeServer
  119. log.Println("[INFO] Using Default ACME " + a.DefaultAcmeServer + " for CA Directory URL")
  120. }
  121. }
  122. config.Certificate.KeyType = certcrypto.RSA2048
  123. client, err := lego.NewClient(config)
  124. if err != nil {
  125. log.Println(err)
  126. return false, err
  127. }
  128. // setup how to receive challenge
  129. if useDNS {
  130. if !a.Database.TableExists("acme") {
  131. a.Database.NewTable("acme")
  132. return false, errors.New("DNS Provider and DNS Credenital configuration required for ACME Provider (Error -1)")
  133. }
  134. if !a.Database.KeyExists("acme", certificateName+"_dns_provider") || !a.Database.KeyExists("acme", certificateName+"_dns_credentials") {
  135. return false, errors.New("DNS Provider and DNS Credenital configuration required for ACME Provider (Error -2)")
  136. }
  137. var dnsCredentials string
  138. err := a.Database.Read("acme", certificateName+"_dns_credentials", &dnsCredentials)
  139. if err != nil {
  140. log.Println(err)
  141. return false, err
  142. }
  143. var dnsProvider string
  144. err = a.Database.Read("acme", certificateName+"_dns_provider", &dnsProvider)
  145. if err != nil {
  146. log.Println(err)
  147. return false, err
  148. }
  149. provider, err := GetDnsChallengeProviderByName(dnsProvider, dnsCredentials)
  150. if err != nil {
  151. log.Println(err)
  152. return false, err
  153. }
  154. err = client.Challenge.SetDNS01Provider(provider)
  155. if err != nil {
  156. log.Println(err)
  157. return false, err
  158. }
  159. } else {
  160. err = client.Challenge.SetHTTP01Provider(http01.NewProviderServer("", a.Port))
  161. if err != nil {
  162. log.Println(err)
  163. return false, err
  164. }
  165. }
  166. // New users will need to register
  167. /*
  168. reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
  169. if err != nil {
  170. log.Println(err)
  171. return false, err
  172. }
  173. */
  174. var reg *registration.Resource
  175. // New users will need to register
  176. if client.GetExternalAccountRequired() {
  177. log.Println("External Account Required for this ACME Provider.")
  178. // IF KID and HmacEncoded is overidden
  179. if !a.Database.TableExists("acme") {
  180. a.Database.NewTable("acme")
  181. return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -1)")
  182. }
  183. if !a.Database.KeyExists("acme", config.CADirURL+"_kid") || !a.Database.KeyExists("acme", config.CADirURL+"_hmacEncoded") {
  184. return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -2)")
  185. }
  186. var kid string
  187. var hmacEncoded string
  188. err := a.Database.Read("acme", config.CADirURL+"_kid", &kid)
  189. if err != nil {
  190. log.Println(err)
  191. return false, err
  192. }
  193. err = a.Database.Read("acme", config.CADirURL+"_hmacEncoded", &hmacEncoded)
  194. if err != nil {
  195. log.Println(err)
  196. return false, err
  197. }
  198. log.Println("EAB Credential retrieved.", kid, hmacEncoded)
  199. if kid != "" && hmacEncoded != "" {
  200. reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
  201. TermsOfServiceAgreed: true,
  202. Kid: kid,
  203. HmacEncoded: hmacEncoded,
  204. })
  205. }
  206. if err != nil {
  207. log.Println(err)
  208. return false, err
  209. }
  210. //return false, errors.New("External Account Required for this ACME Provider.")
  211. } else {
  212. reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
  213. if err != nil {
  214. log.Println(err)
  215. return false, err
  216. }
  217. }
  218. adminUser.Registration = reg
  219. // obtain the certificate
  220. request := certificate.ObtainRequest{
  221. Domains: domains,
  222. Bundle: true,
  223. }
  224. certificates, err := client.Certificate.Obtain(request)
  225. if err != nil {
  226. log.Println(err)
  227. return false, err
  228. }
  229. // Each certificate comes back with the cert bytes, the bytes of the client's
  230. // private key, and a certificate URL.
  231. err = os.WriteFile("./conf/certs/"+certificateName+".pem", certificates.Certificate, 0777)
  232. if err != nil {
  233. log.Println(err)
  234. return false, err
  235. }
  236. err = os.WriteFile("./conf/certs/"+certificateName+".key", certificates.PrivateKey, 0777)
  237. if err != nil {
  238. log.Println(err)
  239. return false, err
  240. }
  241. // Save certificate's ACME info for renew usage
  242. certInfo := &CertificateInfoJSON{
  243. AcmeName: caName,
  244. AcmeUrl: caUrl,
  245. SkipTLS: skipTLS,
  246. UseDNS: useDNS,
  247. }
  248. certInfoBytes, err := json.Marshal(certInfo)
  249. if err != nil {
  250. log.Println(err)
  251. return false, err
  252. }
  253. err = os.WriteFile("./conf/certs/"+certificateName+".json", certInfoBytes, 0777)
  254. if err != nil {
  255. log.Println(err)
  256. return false, err
  257. }
  258. return true, nil
  259. }
  260. // CheckCertificate returns a list of domains that are in expired certificates.
  261. // It will return all domains that is in expired certificates
  262. // *** if there is a vaild certificate contains the domain and there is a expired certificate contains the same domain
  263. // it will said expired as well!
  264. func (a *ACMEHandler) CheckCertificate() []string {
  265. // read from dir
  266. filenames, err := os.ReadDir("./conf/certs/")
  267. expiredCerts := []string{}
  268. if err != nil {
  269. log.Println(err)
  270. return []string{}
  271. }
  272. for _, filename := range filenames {
  273. certFilepath := filepath.Join("./conf/certs/", filename.Name())
  274. certBytes, err := os.ReadFile(certFilepath)
  275. if err != nil {
  276. // Unable to load this file
  277. continue
  278. } else {
  279. // Cert loaded. Check its expiry time
  280. block, _ := pem.Decode(certBytes)
  281. if block != nil {
  282. cert, err := x509.ParseCertificate(block.Bytes)
  283. if err == nil {
  284. elapsed := time.Since(cert.NotAfter)
  285. if elapsed > 0 {
  286. // if it is expired then add it in
  287. // make sure it's uniqueless
  288. for _, dnsName := range cert.DNSNames {
  289. if !contains(expiredCerts, dnsName) {
  290. expiredCerts = append(expiredCerts, dnsName)
  291. }
  292. }
  293. if !contains(expiredCerts, cert.Subject.CommonName) {
  294. expiredCerts = append(expiredCerts, cert.Subject.CommonName)
  295. }
  296. }
  297. }
  298. }
  299. }
  300. }
  301. return expiredCerts
  302. }
  303. // return the current port number
  304. func (a *ACMEHandler) Getport() string {
  305. return a.Port
  306. }
  307. // contains checks if a string is present in a slice.
  308. func contains(slice []string, str string) bool {
  309. for _, s := range slice {
  310. if s == str {
  311. return true
  312. }
  313. }
  314. return false
  315. }
  316. // HandleGetExpiredDomains handles the HTTP GET request to retrieve the list of expired domains.
  317. // It calls the CheckCertificate method to obtain the expired domains and sends a JSON response
  318. // containing the list of expired domains.
  319. func (a *ACMEHandler) HandleGetExpiredDomains(w http.ResponseWriter, r *http.Request) {
  320. type ExpiredDomains struct {
  321. Domain []string `json:"domain"`
  322. }
  323. info := ExpiredDomains{
  324. Domain: a.CheckCertificate(),
  325. }
  326. js, _ := json.MarshalIndent(info, "", " ")
  327. utils.SendJSONResponse(w, string(js))
  328. }
  329. // HandleRenewCertificate handles the HTTP GET request to renew a certificate for the provided domains.
  330. // It retrieves the domains and filename parameters from the request, calls the ObtainCert method
  331. // to renew the certificate, and sends a JSON response indicating the result of the renewal process.
  332. func (a *ACMEHandler) HandleRenewCertificate(w http.ResponseWriter, r *http.Request) {
  333. domainPara, err := utils.PostPara(r, "domains")
  334. if err != nil {
  335. utils.SendErrorResponse(w, jsonEscape(err.Error()))
  336. return
  337. }
  338. filename, err := utils.PostPara(r, "filename")
  339. if err != nil {
  340. utils.SendErrorResponse(w, jsonEscape(err.Error()))
  341. return
  342. }
  343. //Make sure the wildcard * do not goes into the filename
  344. filename = strings.ReplaceAll(filename, "*", "_")
  345. email, err := utils.PostPara(r, "email")
  346. if err != nil {
  347. utils.SendErrorResponse(w, jsonEscape(err.Error()))
  348. return
  349. }
  350. var caUrl string
  351. ca, err := utils.PostPara(r, "ca")
  352. if err != nil {
  353. log.Println("[INFO] CA not set. Using default")
  354. ca, caUrl = "", ""
  355. }
  356. if ca == "custom" {
  357. caUrl, err = utils.PostPara(r, "caURL")
  358. if err != nil {
  359. log.Println("[INFO] Custom CA set but no URL provide, Using default")
  360. ca, caUrl = "", ""
  361. }
  362. }
  363. if ca == "" {
  364. //default. Use Let's Encrypt
  365. ca = "Let's Encrypt"
  366. }
  367. var skipTLS bool
  368. if skipTLSString, err := utils.PostPara(r, "skipTLS"); err != nil {
  369. skipTLS = false
  370. } else if skipTLSString != "true" {
  371. skipTLS = false
  372. } else {
  373. skipTLS = true
  374. }
  375. var dns bool
  376. if dnsString, err := utils.PostPara(r, "dns"); err != nil {
  377. dns = false
  378. } else if dnsString != "true" {
  379. dns = false
  380. } else {
  381. dns = true
  382. }
  383. domains := strings.Split(domainPara, ",")
  384. result, err := a.ObtainCert(domains, filename, email, ca, caUrl, skipTLS, dns)
  385. if err != nil {
  386. utils.SendErrorResponse(w, jsonEscape(err.Error()))
  387. return
  388. }
  389. utils.SendJSONResponse(w, strconv.FormatBool(result))
  390. }
  391. // Escape JSON string
  392. func jsonEscape(i string) string {
  393. b, err := json.Marshal(i)
  394. if err != nil {
  395. log.Println("Unable to escape json data: " + err.Error())
  396. return i
  397. }
  398. s := string(b)
  399. return s[1 : len(s)-1]
  400. }
  401. // Helper function to check if a port is in use
  402. func IsPortInUse(port int) bool {
  403. address := fmt.Sprintf(":%d", port)
  404. listener, err := net.Listen("tcp", address)
  405. if err != nil {
  406. return true // Port is in use
  407. }
  408. defer listener.Close()
  409. return false // Port is not in use
  410. }
  411. // Load cert information from json file
  412. func LoadCertInfoJSON(filename string) (*CertificateInfoJSON, error) {
  413. certInfoBytes, err := os.ReadFile(filename)
  414. if err != nil {
  415. return nil, err
  416. }
  417. certInfo := &CertificateInfoJSON{}
  418. if err = json.Unmarshal(certInfoBytes, certInfo); err != nil {
  419. return nil, err
  420. }
  421. return certInfo, nil
  422. }