Browse Source

EAD Configuration for ACME

Alan Yeung 11 months ago
parent
commit
1e583ba8c9
5 changed files with 178 additions and 7 deletions
  1. 1 1
      acme.go
  2. 1 0
      api.go
  3. 67 5
      mod/acme/acme.go
  4. 31 0
      mod/acme/autorenew.go
  5. 78 1
      web/snippet/acme.html

+ 1 - 1
acme.go

@@ -38,7 +38,7 @@ func initACME() *acme.ACMEHandler {
 		port = getRandomPort(30000)
 	}
 
-	return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port))
+	return acme.NewACME("https://acme-v02.api.letsencrypt.org/directory", strconv.Itoa(port), sysdb)
 }
 
 // create the special routing rule for ACME

+ 1 - 0
api.go

@@ -183,6 +183,7 @@ func initAPIs() {
 	authRouter.HandleFunc("/api/acme/autoRenew/ca", HandleACMEPreferredCA)
 	authRouter.HandleFunc("/api/acme/autoRenew/email", acmeAutoRenewer.HandleACMEEmail)
 	authRouter.HandleFunc("/api/acme/autoRenew/setDomains", acmeAutoRenewer.HandleSetAutoRenewDomains)
+	authRouter.HandleFunc("/api/acme/autoRenew/setEAB", acmeAutoRenewer.HanldeSetEAB)
 	authRouter.HandleFunc("/api/acme/autoRenew/listDomains", acmeAutoRenewer.HandleLoadAutoRenewDomains)
 	authRouter.HandleFunc("/api/acme/autoRenew/renewPolicy", acmeAutoRenewer.HandleRenewPolicy)
 	authRouter.HandleFunc("/api/acme/autoRenew/renewNow", acmeAutoRenewer.HandleRenewNow)

+ 67 - 5
mod/acme/acme.go

@@ -9,6 +9,7 @@ import (
 	"crypto/x509"
 	"encoding/json"
 	"encoding/pem"
+	"errors"
 	"fmt"
 	"log"
 	"net"
@@ -24,6 +25,7 @@ import (
 	"github.com/go-acme/lego/v4/challenge/http01"
 	"github.com/go-acme/lego/v4/lego"
 	"github.com/go-acme/lego/v4/registration"
+	"imuslab.com/zoraxy/mod/database"
 	"imuslab.com/zoraxy/mod/utils"
 )
 
@@ -40,6 +42,11 @@ type ACMEUser struct {
 	key          crypto.PrivateKey
 }
 
+type EABConfig struct {
+	Kid     string `json:"kid"`
+	HmacKey string `json:"HmacKey"`
+}
+
 // GetEmail returns the email of the ACMEUser.
 func (u *ACMEUser) GetEmail() string {
 	return u.Email
@@ -59,13 +66,15 @@ func (u *ACMEUser) GetPrivateKey() crypto.PrivateKey {
 type ACMEHandler struct {
 	DefaultAcmeServer string
 	Port              string
+	Database          *database.Database
 }
 
 // NewACME creates a new ACMEHandler instance.
-func NewACME(acmeServer string, port string) *ACMEHandler {
+func NewACME(acmeServer string, port string, database *database.Database) *ACMEHandler {
 	return &ACMEHandler{
 		DefaultAcmeServer: acmeServer,
 		Port:              port,
+		Database:          database,
 	}
 }
 
@@ -143,10 +152,63 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
 	}
 
 	// New users will need to register
-	reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
-	if err != nil {
-		log.Println(err)
-		return false, err
+	/*
+		reg, err := client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
+		if err != nil {
+			log.Println(err)
+			return false, err
+		}
+	*/
+	var reg *registration.Resource
+	// New users will need to register
+	if client.GetExternalAccountRequired() {
+		log.Println("External Account Required for this ACME Provider.")
+		// IF KID and HmacEncoded is overidden
+
+		if !a.Database.TableExists("acme") {
+			a.Database.NewTable("acme")
+			return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -1)")
+		}
+
+		if !a.Database.KeyExists("acme", config.CADirURL+"_kid") || !a.Database.KeyExists("acme", config.CADirURL+"_hmacEncoded") {
+			return false, errors.New("kid and HmacEncoded configuration required for ACME Provider (Error -2)")
+		}
+
+		var kid string
+		var hmacEncoded string
+		err := a.Database.Read("acme", config.CADirURL+"_kid", &kid)
+
+		if err != nil {
+			log.Println(err)
+			return false, err
+		}
+
+		err = a.Database.Read("acme", config.CADirURL+"_hmacEncoded", &hmacEncoded)
+
+		if err != nil {
+			log.Println(err)
+			return false, err
+		}
+
+		log.Println("EAB Credential retrieved.", kid, hmacEncoded)
+		if kid != "" && hmacEncoded != "" {
+			reg, err = client.Registration.RegisterWithExternalAccountBinding(registration.RegisterEABOptions{
+				TermsOfServiceAgreed: true,
+				Kid:                  kid,
+				HmacEncoded:          hmacEncoded,
+			})
+		}
+		if err != nil {
+			log.Println(err)
+			return false, err
+		}
+		//return false, errors.New("External Account Required for this ACME Provider.")
+	} else {
+		reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true})
+		if err != nil {
+			log.Println(err)
+			return false, err
+		}
 	}
 	adminUser.Registration = reg
 

+ 31 - 0
mod/acme/autorenew.go

@@ -373,3 +373,34 @@ func (a *AutoRenewer) saveRenewConfigToFile() error {
 	js, _ := json.MarshalIndent(a.RenewerConfig, "", " ")
 	return os.WriteFile(a.ConfigFilePath, js, 0775)
 }
+
+// Handle update auto renew EAD configuration
+func (a *AutoRenewer) HanldeSetEAB(w http.ResponseWriter, r *http.Request) {
+	kid, err := utils.GetPara(r, "kid")
+	if err != nil {
+		utils.SendErrorResponse(w, "kid not set")
+		return
+	}
+
+	hmacEncoded, err := utils.GetPara(r, "hmacEncoded")
+	if err != nil {
+		utils.SendErrorResponse(w, "hmacEncoded not set")
+		return
+	}
+
+	acmeDirectoryURL, err := utils.GetPara(r, "acmeDirectoryURL")
+	if err != nil {
+		utils.SendErrorResponse(w, "acmeDirectoryURL not set")
+		return
+	}
+
+	if !a.AcmeHandler.Database.TableExists("acme") {
+		a.AcmeHandler.Database.NewTable("acme")
+	}
+
+	a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_kid", kid)
+	a.AcmeHandler.Database.Write("acme", acmeDirectoryURL+"_hmacEncoded", hmacEncoded)
+
+	utils.SendOK(w)
+
+}

+ 78 - 1
web/snippet/acme.html

@@ -118,6 +118,14 @@
       <label>ACME Server URL</label>
       <input id="caURL" type="text" placeholder="https://example.com/acme/dictionary">
     </div>
+    <div class="field" id="kidInput" style="display:none;">
+      <label>EAB Credentials (KID) for current provider</label>
+      <input id="eab_kid" type="text" placeholder="Leave this field blank to keep the current configuration">
+    </div>
+    <div class="field" id="hmacInput" style="display:none;">
+      <label>EAB HMAC Key for current provider</label>
+      <input id="eab_hmac" type="text" placeholder="Leave this field blank to keep the current configuration">
+    </div>
     <div class="field" id="skipTLS" style="display:none;">
       <div class="ui checkbox">
         <input type="checkbox" id="skipTLSCheckbox">
@@ -314,19 +322,88 @@
     // Button click event handler for obtaining certificate
     $("#obtainButton").click(function() {
       $("#obtainButton").addClass("loading").addClass("disabled");
+      updateCertificateEAB();
       obtainCertificate();
     });
 
     $("input[name=ca]").on('change', function() {
       if(this.value == "Custom ACME Server") {
         $("#caInput").show();
+        $("#kidInput").show();
+        $("#hmacInput").show();
         $("#skipTLS").show();
-      } else {
+      } else if (this.value == "ZeroSSL") {
+        $("#kidInput").show();
+        $("#hmacInput").show();
+      } else if (this.value == "Buypass") {
+        $("#kidInput").show();
+        $("#hmacInput").show();
+      }else {
         $("#caInput").hide();
         $("#skipTLS").hide();
+        $("#kidInput").hide();
+        $("#hmacInput").hide();
       }
     })
 
+
+    // Obtain certificate from API
+    function updateCertificateEAB() {
+      var ca = $("#ca").dropdown("get value");
+      var caURL = "";
+      if (ca == "Custom ACME Server") {
+        ca = "custom";
+        caURL = $("#caURL").val();
+      }else if(ca == "Buypass") {
+        caURL = "https://api.buypass.com/acme/directory";
+      }else if(ca == "ZeroSSL") {
+        caURL = "https://acme.zerossl.com/v2/DV90";
+      }
+
+      if(caURL == "") {
+        return;
+      }
+
+      var kid = $("#eab_kid").val();
+      var hmac = $("#eab_hmac").val();
+      
+      if(kid == "" || hmac == "") {
+        return;
+      }
+
+      console.log(caURL + " " + kid + " " + hmac);
+
+      $.ajax({
+        url: "/api/acme/autoRenew/setEAB",
+        method: "GET",
+        data: {
+          acmeDirectoryURL: caURL,
+          kid: kid,
+          hmacEncoded: hmac,
+        },
+        success: function(response) {
+          //$("#obtainButton").removeClass("loading").removeClass("disabled");
+          if (response.error) {
+            console.log("Error:", response.error);
+            // Show error message
+            parent.msgbox(response.error, false, 12000);
+          } else {
+            console.log("Certificate EAB updated successfully");
+            // Show success message
+            parent.msgbox("Certificate EAB updated successfully");
+            
+            // Renew the parent certificate list
+            parent.initManagedDomainCertificateList();
+          }
+        },
+        error: function(error) {
+          //$("#obtainButton").removeClass("loading").removeClass("disabled");
+          console.log("Failed to update EAB configuration:", error);
+          parent.msgbox("Failed to update EAB configuration");
+        }
+      });
+    }
+
     // Obtain certificate from API
     function obtainCertificate() {
       var domains = $("#domainsInput").val();