From 743b8e6853caefbc0b8ba7bd21ddce9783e2b510 Mon Sep 17 00:00:00 2001 From: Jan Dittberner Date: Mon, 14 Dec 2020 06:59:30 +0100 Subject: [PATCH] Implement CSR validation This commit adds CSR validation and generation of a baseline requirements compatible request token instead of a UUID. Logging has been configured to use timestamps. --- handlers/registry.go | 81 +++++++++++++++++++++++++++++++++----------- handlers/signing.go | 2 +- main.go | 24 +++++++------ templates/index.html | 2 +- 4 files changed, 78 insertions(+), 31 deletions(-) diff --git a/handlers/registry.go b/handlers/registry.go index 9e92307..0b7ca26 100644 --- a/handlers/registry.go +++ b/handlers/registry.go @@ -2,6 +2,7 @@ package handlers import ( "bytes" + "crypto/sha256" "crypto/x509" "encoding/pem" "errors" @@ -11,7 +12,6 @@ import ( "os/exec" "time" - "github.com/google/uuid" log "github.com/sirupsen/logrus" ) @@ -29,20 +29,58 @@ func NewSigningRequestRegistry(caCertificates []*x509.Certificate) *SigningReque } } +type SigningRequestAttributes struct { + CommonName string + CSRBytes []byte + RequestToken string +} + func (registry *SigningRequestRegistry) AddSigningRequest(request *requestData) (string, error) { - requestUuid, err := uuid.NewRandom() + requestToken, csrBytes, err := validateCsr(request.Csr) if err != nil { return "", err } + requestAttributes := &SigningRequestAttributes{ + CommonName: request.CommonName, + CSRBytes: csrBytes, + RequestToken: requestToken, + } go func() { responseChannel := make(chan *responseData, 1) - registry.requests[requestUuid.String()] = responseChannel - registry.signCertificate(responseChannel, request) + registry.requests[requestToken] = responseChannel + registry.signCertificate(responseChannel, requestAttributes) }() - return requestUuid.String(), nil + return requestToken, nil } -func (registry *SigningRequestRegistry) signCertificate(channel chan *responseData, request *requestData) { +func validateCsr(csr string) (string, []byte, error) { + csrBlock, _ := pem.Decode([]byte(csr)) + if csrBlock == nil { + return "", nil, errors.New("request data did not contain valid PEM data") + } + if csrBlock.Type != "CERTIFICATE REQUEST" { + return "", nil, fmt.Errorf("request is not valid, type in PEM data is %s", csrBlock.Type) + } + var err error + var csrContent *x509.CertificateRequest + csrContent, err = x509.ParseCertificateRequest(csrBlock.Bytes) + if err != nil { + return "", nil, err + } + if err = csrContent.CheckSignature(); err != nil { + log.Errorf("invalid CSR signature %v", err) + return "", nil, err + } + + // generate request token as defined in CAB Baseline Requirements 1.7.3 Request Token definition + requestToken := fmt.Sprintf( + "%s%x", time.Now().UTC().Format("200601021504"), sha256.Sum256(csrContent.Raw), + ) + log.Debugf("generated request token %s", requestToken) + return requestToken, csrContent.Raw, nil +} + +func (registry *SigningRequestRegistry) signCertificate(channel chan *responseData, request *SigningRequestAttributes) { responseData, err := registry.sign(request) if err != nil { log.Error(err) @@ -52,21 +90,26 @@ func (registry *SigningRequestRegistry) signCertificate(channel chan *responseDa channel <- responseData } -func (registry *SigningRequestRegistry) sign(request *requestData) (response *responseData, err error) { - log.Debugf("received CSR for %s:\n\n%s", request.CommonName, request.Csr) +func (registry *SigningRequestRegistry) sign(request *SigningRequestAttributes) (*responseData, error) { + log.Infof("handling signing request %s", request.RequestToken) subjectDN := fmt.Sprintf("/CN=%s", request.CommonName) + + var err error var csrFile *os.File if csrFile, err = ioutil.TempFile("", "*.csr.pem"); err != nil { log.Errorf("could not open temporary file: %s", err) - return + return nil, err } - if _, err = csrFile.Write([]byte(request.Csr)); err != nil { + if err = pem.Encode(csrFile, &pem.Block{ + Type: "CERTIFICATE REQUEST", + Bytes: request.CSRBytes, + }); err != nil { log.Errorf("could not write CSR to file: %s", err) - return + return nil, err } if err = csrFile.Close(); err != nil { log.Errorf("could not close CSR file: %s", err) - return + return nil, err } defer func(file *os.File) { err = os.Remove(file.Name()) @@ -81,7 +124,7 @@ func (registry *SigningRequestRegistry) sign(request *requestData) (response *re opensslCommand := exec.Command( "openssl", "ca", "-config", "ca.cnf", "-policy", "policy_match", "-extensions", "client_ext", - "-batch", "-subj", subjectDN, "-utf8", "-rand_serial", "-in", "in.pem") + "-batch", "-subj", subjectDN, "-utf8", "-rand_serial", "-in", csrFile.Name()) var out, cmdErr bytes.Buffer opensslCommand.Stdout = &out opensslCommand.Stderr = &cmdErr @@ -89,32 +132,32 @@ func (registry *SigningRequestRegistry) sign(request *requestData) (response *re if err != nil { log.Error(err) log.Error(cmdErr.String()) - return + return nil, err } var block *pem.Block if block, _ = pem.Decode(out.Bytes()); block == nil { err = fmt.Errorf("could not decode pem") - return + return nil, err } var certificate *x509.Certificate if certificate, err = x509.ParseCertificate(block.Bytes); err != nil { - return + return nil, err } var caChain []string if caChain, err = registry.getCAChain(certificate); err != nil { - return + return nil, err } - response = &responseData{ + response := &responseData{ Certificate: string(pem.EncodeToMemory(&pem.Block{ Type: "CERTIFICATE", Bytes: certificate.Raw, })), CAChain: caChain, } - return + return response, nil } func (registry *SigningRequestRegistry) GetResponseChannel(requestUuid string) (chan *responseData, error) { diff --git a/handlers/signing.go b/handlers/signing.go index 33144a1..481c609 100644 --- a/handlers/signing.go +++ b/handlers/signing.go @@ -53,7 +53,7 @@ func (h *CertificateSigningHandler) ServeHTTP(w http.ResponseWriter, r *http.Req type requestData struct { Csr string `json:"csr"` - CommonName string `json:"commonName"` + CommonName string `json:"common_name"` } type responseData struct { diff --git a/main.go b/main.go index 31b07d1..829096f 100644 --- a/main.go +++ b/main.go @@ -25,16 +25,9 @@ import ( ) func main() { - tlsConfig := &tls.Config{ - CipherSuites: []uint16{ - tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, - tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, - tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, - }, - NextProtos: []string{"h2"}, - PreferServerCipherSuites: true, - MinVersion: tls.VersionTLS12, - } + log.SetFormatter(&log.TextFormatter{ + FullTimestamp: true, + }) bundle := loadI18nBundle() mux := http.NewServeMux() @@ -49,6 +42,17 @@ func main() { mux.Handle("/js/", fileServer) mux.Handle("/locales/", handlers.NewJSLocalesHandler(bundle)) mux.Handle("/ws/", handlers.NewWebSocketHandler(signingRequestRegistry)) + + tlsConfig := &tls.Config{ + CipherSuites: []uint16{ + tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384, + tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, + tls.TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256, + }, + NextProtos: []string{"h2"}, + PreferServerCipherSuites: true, + MinVersion: tls.VersionTLS12, + } server := http.Server{ Addr: ":8000", Handler: csrf.Protect(csrfKey, csrf.FieldName("csrfToken"), csrf.RequestHeader("X-CSRF-Token"))(mux), diff --git a/templates/index.html b/templates/index.html index f215765..3852de6 100644 --- a/templates/index.html +++ b/templates/index.html @@ -150,7 +150,7 @@ progressBar.setAttribute("aria-valuenow", "3"); progressBar.classList.add('progress-bar-striped', 'progress-bar-animated'); progressBar.innerHTML = i18n.t('keygen.generated', {seconds: seconds}) + ', ' + i18n.t('certificate.waiting'); - postData("/sign/", {"csr": csrPem, "commonName": subject}, csrfToken) + postData("/sign/", {"csr": csrPem, "common_name": subject}, csrfToken) .then(data => { const request_id = data["request_id"] const webSocket = new WebSocket(