Skip to content

Commit fefde0c

Browse files
committed
challenges done
1 parent 79700a9 commit fefde0c

3 files changed

Lines changed: 167 additions & 14 deletions

File tree

internal/challenges/dns.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
package challenges
22

3-
import "net"
3+
import (
4+
"net"
5+
6+
"github.com/KaiserWerk/CertMaker/internal/global"
7+
"github.com/KaiserWerk/CertMaker/internal/helper"
8+
)
49

510
func CheckDNS01Challenge(domain string, expectedToken string) (bool, error) {
611
// check TXT records for __certmaker_challenge.<domain> and see if expectedToken is present
7-
records, err := net.LookupTXT("__certmaker_challenge." + domain) // is a dot needed at the end?
12+
records, err := net.LookupTXT(global.DNS01ChallengeSubdomain + domain) // is a dot needed at the end?
813
if err != nil {
914
return false, err
1015
}
11-
for _, record := range records {
12-
if record == expectedToken {
13-
return true, nil
14-
}
16+
if helper.StringSliceContains(records, expectedToken) {
17+
return true, nil
1518
}
1619
return false, nil
1720
}

internal/global/global.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,11 @@ const (
4141

4242
DefaultChallengeValidity = 2 * time.Hour
4343
HTTP01ChallengeDefaultValidationPort uint16 = 80
44+
45+
DNS01ChallengeSubdomain = "__certmaker_challenge."
4446
)
4547

4648
var (
47-
DNSNamesToSkip = []string{"localhost", "127.0.0.1", "::1", "[::1]"}
49+
DNSNamesToSkip = []string{"localhost"}
50+
IPsToSkip = []string{"127.0.0.1", "::1", "[::1]"}
4851
)

internal/handler/api.go

Lines changed: 154 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -191,8 +191,7 @@ func (bh *BaseHandler) APIRequestCertificateWithCSRHandler(w http.ResponseWriter
191191
return
192192
}
193193

194-
val := r.Context().Value("user")
195-
u := val.(entity.User)
194+
u := r.Context().Value("user").(entity.User)
196195

197196
ri := entity.RequestInfo{
198197
CreatedFor: u.ID,
@@ -552,7 +551,7 @@ func (bh *BaseHandler) APISolveHTTP01ChallengeHandler(w http.ResponseWriter, r *
552551
ips = simpleRequest.IPs
553552
}
554553

555-
// check well known path for every domain
554+
// check well-known path for every domain
556555
for _, domain := range domains {
557556
if domain == "" {
558557
continue
@@ -662,10 +661,157 @@ func (bh *BaseHandler) APISolveHTTP01ChallengeHandler(w http.ResponseWriter, r *
662661

663662
func (bh *BaseHandler) APISolveDNS01ChallengeHandler(w http.ResponseWriter, r *http.Request) {
664663
defer r.Body.Close()
665-
// var (
666-
// logger = bh.ContextLogger("api")
667-
// vars = mux.Vars(r)
668-
// )
664+
var (
665+
logger = bh.ContextLogger("api")
666+
vars = mux.Vars(r)
667+
)
668+
669+
var response entity.CertificateResponse
670+
671+
// fetch challenge info from DB
672+
challenge, err := bh.DBSvc.FindChallenge("public_id = ?", vars["challengeID"])
673+
if err != nil {
674+
if err == sql.ErrNoRows {
675+
logger.Debugf("no challenge found for public ID %s", vars["challengeID"])
676+
w.WriteHeader(http.StatusNotFound)
677+
return
678+
}
679+
logger.Errorf("could not query challenge: %s", err.Error())
680+
w.WriteHeader(http.StatusInternalServerError)
681+
return
682+
}
683+
684+
// check if challenge type matches
685+
if challenge.ChallengeType != "dns-01" {
686+
logger.Debugf("challenge type is not dns-01: %s", challenge.ChallengeType)
687+
w.WriteHeader(http.StatusBadRequest)
688+
return
689+
}
690+
691+
// get requestInfo for challenge
692+
requestInfo, err := bh.DBSvc.GetRequestInfo(challenge.RequestInfoID)
693+
if err != nil {
694+
logger.Errorf("could not get request info for ID %v: %s", challenge.RequestInfoID, err.Error())
695+
w.WriteHeader(http.StatusInternalServerError)
696+
return
697+
}
698+
699+
var (
700+
domains []string
701+
fromCSR = requestInfo.CsrBytes != nil
702+
csr *x509.CertificateRequest
703+
simpleRequest entity.SimpleRequest
704+
)
705+
706+
if fromCSR {
707+
// determine DNS names and IPs from CSR
708+
csr, err = x509.ParseCertificateRequest(requestInfo.CsrBytes)
709+
if err != nil {
710+
logger.Errorf("could not parse CSR: %s", err.Error())
711+
w.WriteHeader(http.StatusBadRequest)
712+
return
713+
}
714+
715+
domains = append(domains, csr.DNSNames...)
716+
} else {
717+
err = json.Unmarshal(requestInfo.SimpleRequestBytes, &simpleRequest)
718+
if err != nil {
719+
logger.Errorf("could not unmarshal simple request: %s", err.Error())
720+
w.WriteHeader(http.StatusBadRequest)
721+
return
722+
}
723+
724+
domains = simpleRequest.Domains
725+
}
726+
727+
// check well known path for every domain
728+
for _, domain := range domains {
729+
if domain == "" {
730+
continue
731+
}
732+
733+
if helper.StringSliceContains(global.DNSNamesToSkip, domain) {
734+
continue
735+
}
736+
737+
ok, err := challenges.CheckDNS01Challenge(domain, challenge.Token)
738+
if err != nil {
739+
response.Error = fmt.Sprintf("error checking domain %s: %s", domain, err.Error())
740+
w.WriteHeader(http.StatusBadRequest)
741+
_ = json.NewEncoder(w).Encode(response)
742+
return
743+
}
744+
745+
if !ok {
746+
response.Error = fmt.Sprintf("DNS check of TXT record for %s%s did not match expected token", global.DNS01ChallengeSubdomain, domain)
747+
w.WriteHeader(http.StatusBadRequest)
748+
_ = json.NewEncoder(w).Encode(response)
749+
return
750+
}
751+
}
752+
753+
// from here on, we know that the challenge was successful for all domains
754+
// that means we can issue the certificate
755+
756+
var (
757+
certBytes, keyBytes []byte
758+
sn int64
759+
)
760+
if fromCSR {
761+
// issue certificate from CSR
762+
certBytes, sn, err = bh.CertMaker.GenerateCertificateByCSR(csr)
763+
if err != nil {
764+
logger.Errorf("error generating certificate from CSR: %s\n", err.Error())
765+
w.WriteHeader(http.StatusInternalServerError)
766+
return
767+
}
768+
} else {
769+
certBytes, keyBytes, sn, err = bh.CertMaker.GenerateLeafCertAndKey(simpleRequest)
770+
if err != nil {
771+
logger.Errorf("error generating key + certificate: %s\n", err.Error())
772+
w.WriteHeader(http.StatusInternalServerError)
773+
return
774+
}
775+
}
776+
777+
// update the request info status to "issued"
778+
requestInfo.Status = "issued"
779+
err = bh.DBSvc.UpdateRequestInfo(requestInfo)
780+
if err != nil {
781+
logger.Errorf("could not update request info status: %s", err.Error())
782+
w.WriteHeader(http.StatusInternalServerError)
783+
return
784+
}
785+
786+
// store certificate info in DB
787+
ci := entity.CertInfo{
788+
SerialNumber: sn,
789+
FromCSR: fromCSR,
790+
CreatedForUser: challenge.CreatedFor,
791+
Revoked: false,
792+
}
793+
794+
err = bh.DBSvc.AddCertInfo(&ci)
795+
if err != nil {
796+
logger.Errorf("could not insert cert info into DB: %s", err.Error())
797+
w.WriteHeader(http.StatusInternalServerError)
798+
return
799+
}
800+
801+
// set up response
802+
response.CertificatePem = string(certBytes)
803+
if !fromCSR {
804+
response.PrivateKeyPem = string(keyBytes)
805+
}
806+
807+
w.Header().Set("Content-Type", "application/json")
808+
w.WriteHeader(http.StatusCreated)
809+
err = json.NewEncoder(w).Encode(response)
810+
if err != nil {
811+
logger.Infof("could not encode response: %s", err.Error())
812+
w.WriteHeader(http.StatusInternalServerError)
813+
return
814+
}
669815
}
670816

671817
// APIRootCertificateDownloadHandler allows to programmatically obtain the root certificate
@@ -692,6 +838,7 @@ func (bh *BaseHandler) APIRootCertificateDownloadHandler(w http.ResponseWriter,
692838
_ = fh.Close()
693839
}
694840

841+
// APIRevokeCertificateHandler allows a user to revoke a certificate by its serial number
695842
func (bh *BaseHandler) APIRevokeCertificateHandler(w http.ResponseWriter, r *http.Request) {
696843
defer r.Body.Close()
697844
var (

0 commit comments

Comments
 (0)