From 28553dac91df3508de363cec842039d1a93a572c Mon Sep 17 00:00:00 2001 From: Arjan H Date: Sun, 31 Jul 2022 16:42:47 +0200 Subject: [PATCH] Determine issuer NameID value so we can set the correct AIA URL (#35) --- gui/apply-boulder | 4 +++ gui/certificate.go | 14 +++++----- gui/dashboard.go | 10 +++---- gui/main.go | 66 ++++++++++++++++++++++++++++----------------- install | 17 +++++++----- nginx.conf | 47 +++++++++++++++----------------- proxy.conf | 4 +++ utils/nameidtool.go | 46 +++++++++++++++++++++++++++++++ 8 files changed, 140 insertions(+), 68 deletions(-) create mode 100644 proxy.conf create mode 100644 utils/nameidtool.go diff --git a/gui/apply-boulder b/gui/apply-boulder index 3f367a3..4517994 100755 --- a/gui/apply-boulder +++ b/gui/apply-boulder @@ -64,6 +64,10 @@ if [ "$PKI_DOMAIN_MODE" == "lockdown" ] || [ "$PKI_DOMAIN_MODE" == "whitelist" ] cat rate-limit-policies.yml | tr '\n' '\r' | sed -e "s|\(certificatesPerFQDNSet:.*must-staple.le.wtf: 10000\).*\(certificatesPerFQDNSetFast:.*\)|\1\n${REPLACEMENT}rateLimitsURL: http://$PKI_FQDN/rate-limits\n\2|" | tr '\r' '\n' > rate-limit-policies.yml.bak && mv rate-limit-policies.yml.bak rate-limit-policies.yml fi + +sed -i -e "s|\"issuerURL\": \".*\"|\"issuerURL\": \"http://$PKI_FQDN/aia/issuer/$PKI_ISSUER_NAME_ID\"|" config/ca-a.json +sed -i -e "s|\"issuerURL\": \".*\"|\"issuerURL\": \"http://$PKI_FQDN/aia/issuer/$PKI_ISSUER_NAME_ID\"|" config/ca-b.json + if [ "$PKI_EXTENDED_TIMEOUT" == "1" ]; then sed -i -e "s/\"timeout\": \"15s\"/\"timeout\": \"30s\"/" config/ca-a.json sed -i -e "s/\"timeout\": \"15s\"/\"timeout\": \"30s\"/" config/ca-b.json diff --git a/gui/certificate.go b/gui/certificate.go index 3111b5d..c73ca9b 100644 --- a/gui/certificate.go +++ b/gui/certificate.go @@ -102,7 +102,7 @@ func reportError(err error) error { stop := len(lines) for i := 0; i < len(lines); i++ { - if strings.Index(lines[i], ".ServeHTTP(") >= 0 { + if strings.Contains(lines[i], ".ServeHTTP(") { stop = i break } @@ -218,14 +218,14 @@ func (ci *CertificateInfo) ImportPkcs12(tmpFile string, tmpKey string, tmpCert s } if out, err := exeCmd("openssl pkcs12 -in " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -password " + pwd + " -nocerts -nodes -out " + tmpKey); err != nil { - if strings.Index(string(out), "invalid password") >= 0 { + if strings.Contains(string(out), "invalid password") { return errors.New("incorrect password") } return reportError(err) } if out, err := exeCmd("openssl pkcs12 -in " + strings.Replace(tmpFile, " ", "\\\\", -1) + " -password " + pwd + " -nokeys -out " + tmpCert); err != nil { - if strings.Index(string(out), "invalid password") >= 0 { + if strings.Contains(string(out), "invalid password") { return errors.New("incorrect password") } @@ -311,7 +311,7 @@ func (ci *CertificateInfo) Upload(path string, certBase string, tmpKey string, t } if out, err := exeCmd("openssl pkey -passin " + pwd + " -in " + tmpKey + " -out " + tmpKey + "-out"); err != nil { - if strings.Index(string(out), ":bad decrypt:") >= 0 { + if strings.Contains(string(out), ":bad decrypt:") { return errors.New("incorrect password") } @@ -349,7 +349,7 @@ func (ci *CertificateInfo) ImportCerts(path string, rootCert string, rootKey str rootSubject = string(r[0 : len(r)-1]) fmt.Printf("Import root with subject '%s'\n", rootSubject) - r, err = exeCmd("openssl pkey -noout -in " + rootKey) + _, err = exeCmd("openssl pkey -noout -in " + rootKey) if err != nil { return reportError(err) } @@ -394,7 +394,7 @@ func (ci *CertificateInfo) ImportCerts(path string, rootCert string, rootKey str return errors.New("issuer not issued by our Root CA") } - r, err = exeCmd("openssl pkey -noout -in " + issuerKey) + _, err = exeCmd("openssl pkey -noout -in " + issuerKey) if err != nil { return reportError(err) } @@ -577,8 +577,6 @@ func exeCmd(cmd string) ([]byte, error) { out, err := exec.Command(head, parts...).CombinedOutput() if err != nil { fmt.Print(fmt.Sprint(err) + ": " + string(out)) - } else { - //fmt.Println(string(out)) } return out, err } diff --git a/gui/dashboard.go b/gui/dashboard.go index df43253..57e4185 100644 --- a/gui/dashboard.go +++ b/gui/dashboard.go @@ -86,10 +86,10 @@ func _parseLine(line string, loc *time.Location) Activity { if idx > -1 { message = message[0:idx] } - if strings.Index(message, "Checked CAA records for") > -1 { + if strings.Contains(message, "Checked CAA records for") { message = message[0:strings.Index(message, ",")] } - if strings.Index(message, "Validation result") > -1 { + if strings.Contains(message, "Validation result") { message = message[0:30] } idx = strings.Index(message, " csr=[") @@ -108,16 +108,16 @@ func _parseLine(line string, loc *time.Location) Activity { if idx > -1 { message = message[0:idx] } - if strings.Index(message, "Certificate request - ") > -1 { + if strings.Contains(message, "Certificate request - ") { idx = strings.Index(message, " JSON={") if idx > -1 { message = message[0:idx] } } - if strings.Index(message, "failed to complete security handshake") > -1 { + if strings.Contains(message, "failed to complete security handshake") { activity.Class = "warning" } - if strings.Index(message, "failed to receive the preface from client") > -1 { + if strings.Contains(message, "failed to receive the preface from client") { activity.Class = "warning" } activity.Message = message diff --git a/gui/main.go b/gui/main.go index c049411..bb4caa6 100644 --- a/gui/main.go +++ b/gui/main.go @@ -4,11 +4,14 @@ import ( "bufio" "bytes" "context" + "crypto" "crypto/aes" "crypto/cipher" "crypto/rand" + "crypto/x509" "encoding/base64" "encoding/json" + "encoding/pem" "errors" "fmt" "html/template" @@ -16,6 +19,7 @@ import ( "io/ioutil" "log" "math" + "math/big" "net" "net/http" "os" @@ -137,9 +141,9 @@ func (reg *User) Validate(isNew bool, isChange bool) bool { } if isNew || isChange { - re := regexp.MustCompile(".+@.+\\..+") + re := regexp.MustCompile(`.+@.+\..+`) matched := re.Match([]byte(reg.Email)) - if matched == false { + if !matched { reg.Errors["Email"] = "Please enter a valid email address" } } @@ -1386,6 +1390,27 @@ func _buildCI(r *http.Request, session *sessions.Session, isRoot bool) *Certific return ci } +func issuerNameID(certfile string) (int64, error) { + cf, err := ioutil.ReadFile(certfile) + if err != nil { + log.Printf("issuerNameID: could not read cert file: %v", err) + return 0, err + } + + cpb, _ := pem.Decode(cf) + crt, err := x509.ParseCertificate(cpb.Bytes) + if err != nil { + log.Printf("issuerNameID: could not parse x509 file: %v", err) + return 0, err + } + + // From issuance/issuance.go : func truncatedHash + h := crypto.SHA1.New() + h.Write(crt.RawSubject) + s := h.Sum(nil) + return int64(big.NewInt(0).SetBytes(s[:7]).Int64()), nil +} + func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot bool) bool { path := "data/" if !isRoot { @@ -1452,6 +1477,16 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot return false } + if !ci.IsRoot { + nameID, err := issuerNameID(path + certBase + ".pem") + if err == nil { + viper.Set("issuer_name_id", nameID) + viper.WriteConfig() + } else { + log.Printf("_certCreate: could not calculate IssuerNameID: %v", err) + } + } + if viper.Get("labca.organization") == nil { viper.Set("labca.organization", ci.Organization) viper.WriteConfig() @@ -1478,24 +1513,6 @@ func _certCreate(w http.ResponseWriter, r *http.Request, certBase string, isRoot return true } -func _parseLinuxIPRouteShow(output []byte) (net.IP, error) { - // Linux '/usr/bin/ip route show' format looks like this: - // default via 192.168.178.1 dev wlp3s0 metric 303 - // 192.168.178.0/24 dev wlp3s0 proto kernel scope link src 192.168.178.76 metric 303 - lines := strings.Split(string(output), "\n") - for _, line := range lines { - fields := strings.Fields(line) - if len(fields) >= 3 && fields[0] == "default" { - ip := net.ParseIP(fields[2]) - if ip != nil { - return ip, nil - } - } - } - - return nil, errors.New("no gateway found") -} - func _hostCommand(w http.ResponseWriter, r *http.Request, command string, params ...string) bool { conn, err := net.Dial("tcp", "control:3030") if err != nil { @@ -1563,6 +1580,7 @@ func _applyConfig() error { os.Setenv("PKI_DOMAIN_MODE", viper.GetString("labca.domain_mode")) os.Setenv("PKI_LOCKDOWN_DOMAINS", viper.GetString("labca.lockdown")) os.Setenv("PKI_WHITELIST_DOMAINS", viper.GetString("labca.whitelist")) + os.Setenv("PKI_ISSUER_NAME_ID", viper.GetString("issuer_name_id")) if viper.GetBool("labca.extended_timeout") { os.Setenv("PKI_EXTENDED_TIMEOUT", "1") } else { @@ -1698,7 +1716,7 @@ func _setupAdminUser(w http.ResponseWriter, r *http.Request) bool { // Restore a backup file if isMultipart { reg := &User{ - Errors: make(map[string]string), + Errors: make(map[string]string), RequestBase: r.Header.Get("X-Request-Base"), } file, header, err := r.FormFile("file") @@ -1918,14 +1936,14 @@ func setupHandler(w http.ResponseWriter, r *http.Request) { // 3. Setup root CA certificate if !_certCreate(w, r, "root-ca", true) { // Cleanup the cert (if it even exists) so we will retry on the next run - _ := os.Remove("data/root-ca.pem") + os.Remove("data/root-ca.pem") return } // 4. Setup issuer certificate if !_certCreate(w, r, "ca-int", false) { // Cleanup the cert (if it even exists) so we will retry on the next run - _ := os.Remove("data/issuer/ca-int.pem") + os.Remove("data/issuer/ca-int.pem") return } @@ -2083,7 +2101,7 @@ func accountsHandler(w http.ResponseWriter, r *http.Request) { Accounts, err := GetAccounts(w, r) if err == nil { - render(w, r, "list:accounts", map[string]interface{}{"List": Accounts, "Title": "ACME"}) + render(w, r, "list:accounts", map[string]interface{}{"List": Accounts, "Title": "ACME"}) } } diff --git a/install b/install index 840903f..c0f9c2e 100755 --- a/install +++ b/install @@ -455,6 +455,7 @@ static_web() { [ -d /home/labca/nginx_data/conf.d ] || mkdir -p /home/labca/nginx_data/conf.d [ -d /home/labca/nginx_data/ssl ] || mkdir -p /home/labca/nginx_data/ssl cp $cloneDir/nginx.conf /home/labca/nginx_data/conf.d/labca.conf + cp $cloneDir/proxy.conf /home/labca/nginx_data/conf.d/proxy.conf if [ -f "$boulderLabCADir/setup_complete" ]; then perl -i -p0e 's/\n # BEGIN temporary redirect\n location = \/ \{\n return 302 \/admin\/;\n }\n # END temporary redirect\n//igs' /home/labca/nginx_data/conf.d/labca.conf fi @@ -646,10 +647,6 @@ config_boulder() { sed -i -e "s/5001/443/g" config/va-remote-b.json sed -i -e "s/5002/80/g" config/va-remote-b.json sed -i -e "s|https://boulder:4431/terms/v7|https://$LABCA_FQDN/terms/v1|" config/wfe2.json - sed -i -e "s|http://boulder:4430/acme/issuer-cert|http://$LABCA_FQDN/certs/ca-int.der|" config/ca-a.json - sed -i -e "s|http://boulder:4430/acme/issuer-cert|http://$LABCA_FQDN/certs/ca-int.der|" config/ca-b.json - sed -i -e "s|http://127.0.0.1:4000/acme/issuer-cert|http://$LABCA_FQDN/certs/ca-int.der|" config/ca-a.json - sed -i -e "s|http://127.0.0.1:4000/acme/issuer-cert|http://$LABCA_FQDN/certs/ca-int.der|" config/ca-b.json sed -i -e "s|http://boulder:4430/acme/issuer-cert|http://$LABCA_FQDN/acme/issuer-cert|" config/wfe2.json sed -i -e "s|http://127.0.0.1:4000/acme/issuer-cert|https://$LABCA_FQDN/acme/issuer-cert|" config/wfe2.json sed -i -e "s|letsencrypt/boulder|hakwerk/labca|" config/wfe2.json @@ -661,8 +658,6 @@ config_boulder() { sed -i -e "s|1.2.3.4|1.3.6.1.4.1.44947.1.1.1|g" config/ca-b.json perl -i -p0e "s/(\s+\"crlURL\":[^\n]*)//igs" config/ca-a.json perl -i -p0e "s/(\s+\"crlURL\":[^\n]*)//igs" config/ca-b.json - sed -i -e "s/Do What Thou Wilt/This PKI is only meant for internal (lab) usage; do NOT use this on the open internet\!/g" config/ca-a.json - sed -i -e "s/Do What Thou Wilt/This PKI is only meant for internal (lab) usage; do NOT use this on the open internet\!/g" config/ca-b.json sed -i -e "s/ocspURL.Path = encodedReq/ocspURL.Path += encodedReq/" ocsp/helper/helper.go sed -i -e "s/\"dnsTimeout\": \".*\"/\"dnsTimeout\": \"3s\"/" config/ra.json sed -i -e "s/\"dnsTimeout\": \".*\"/\"dnsTimeout\": \"3s\"/" config/va.json @@ -693,6 +688,16 @@ config_boulder() { export PKI_DOMAIN_MODE=$(grep domain_mode $adminDir/data/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g') export PKI_LOCKDOWN_DOMAINS=$(grep lockdown $adminDir/data/config.json | grep -v domain_mode | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g') export PKI_WHITELIST_DOMAINS=$(grep whitelist $adminDir/data/config.json | grep -v domain_mode | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g') + export PKI_ISSUER_NAME_ID=$(grep issuer_name_id $adminDir/data/config.json | sed -e 's/.*:[ ]*//' | sed -e 's/\",//g' | sed -e 's/\"//g') + if [ -z "$PKI_ISSUER_NAME_ID" ] && [ -e "$adminDir/data/issuer/ca-int.pem" ]; then + local img=$(grep "&boulder_image" $boulderDir/docker-compose.yml | sed -e "s/.*boulder_image \(.*\)/\1/") + eval img=$img + docker run --rm -v $cloneDir/utils:/utils -w /utils $img go build nameidtool.go &>>$installLog + nmid=$($cloneDir/utils/nameidtool $adminDir/data/issuer/ca-int.pem) + if [ $? == 0 ]; then + export PKI_ISSUER_NAME_ID=$nmid + fi + fi enabled=$(grep "email\": {" $adminDir/data/config.json -A1 | grep enable | head -1 | perl -p0e 's/.*?:\s+(.*)/\1/' | sed -e 's/\",//g' | sed -e 's/\"//g') if [ "$enabled" == "true," ]; then diff --git a/nginx.conf b/nginx.conf index fdd2693..dc6d6d2 100644 --- a/nginx.conf +++ b/nginx.conf @@ -14,15 +14,17 @@ server { return 301 https://$host$request_uri; } + location /aia/issuer { + include conf.d/proxy.conf; + proxy_pass http://boulder:4001; + } + location /directory { return 301 https://$host$request_uri; } - location /ocsp/ { - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + location /ocsp { + include conf.d/proxy.conf; proxy_pass http://boulder:4002/; } @@ -55,20 +57,14 @@ server { } location /admin/ { - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + include conf.d/proxy.conf; proxy_set_header X-Request-Base "/admin"; proxy_pass http://labca:3000/; error_page 502 504 /502.html; } location /admin/ws { - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + include conf.d/proxy.conf; proxy_set_header X-Request-Base "/admin"; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade"; @@ -76,26 +72,27 @@ server { } location /acme/ { - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + include conf.d/proxy.conf; proxy_pass http://boulder:4001; } location /directory { - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + include conf.d/proxy.conf; + proxy_pass http://boulder:4001; + } + + location /build { + include conf.d/proxy.conf; + proxy_pass http://boulder:4001; + } + + location /aia/issuer { + include conf.d/proxy.conf; proxy_pass http://boulder:4001; } location /ocsp/ { - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; + include conf.d/proxy.conf; proxy_pass http://boulder:4002/; } diff --git a/proxy.conf b/proxy.conf new file mode 100644 index 0000000..df75bc5 --- /dev/null +++ b/proxy.conf @@ -0,0 +1,4 @@ +proxy_set_header Host $http_host; +proxy_set_header X-Real-IP $remote_addr; +proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; +proxy_set_header X-Forwarded-Proto $scheme; diff --git a/utils/nameidtool.go b/utils/nameidtool.go new file mode 100644 index 0000000..3465f4b --- /dev/null +++ b/utils/nameidtool.go @@ -0,0 +1,46 @@ +package main + +import ( + "crypto" + "crypto/x509" + "encoding/pem" + "fmt" + "io/ioutil" + "math/big" + "os" +) + +func issuerNameID(certfile string) (int64, error) { + cf, err := ioutil.ReadFile(certfile) + if err != nil { + fmt.Printf("issuerNameID: could not read cert file: %v", err) + return 0, err + } + + cpb, _ := pem.Decode(cf) + crt, err := x509.ParseCertificate(cpb.Bytes) + if err != nil { + fmt.Printf("issuerNameID: could not parse x509 file: %v", err) + return 0, err + } + + // From issuance/issuance.go : func truncatedHash + h := crypto.SHA1.New() + h.Write(crt.RawSubject) + s := h.Sum(nil) + return int64(big.NewInt(0).SetBytes(s[:7]).Int64()), nil +} + +func main() { + if len(os.Args[1:]) < 1 { + fmt.Printf("Usage:\n %s \n", os.Args[0]) + os.Exit(1) + } + nameID, err := issuerNameID(os.Args[1]) + if err != nil { + fmt.Printf("Error: %v\n", err) + os.Exit(1) + } + + fmt.Println(nameID) +}