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.
This commit is contained in:
		
							parent
							
								
									2093bf2429
								
							
						
					
					
						commit
						743b8e6853
					
				
					 4 changed files with 78 additions and 31 deletions
				
			
		|  | @ -2,6 +2,7 @@ package handlers | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"bytes" | 	"bytes" | ||||||
|  | 	"crypto/sha256" | ||||||
| 	"crypto/x509" | 	"crypto/x509" | ||||||
| 	"encoding/pem" | 	"encoding/pem" | ||||||
| 	"errors" | 	"errors" | ||||||
|  | @ -11,7 +12,6 @@ import ( | ||||||
| 	"os/exec" | 	"os/exec" | ||||||
| 	"time" | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/google/uuid" |  | ||||||
| 	log "github.com/sirupsen/logrus" | 	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) { | func (registry *SigningRequestRegistry) AddSigningRequest(request *requestData) (string, error) { | ||||||
| 	requestUuid, err := uuid.NewRandom() | 	requestToken, csrBytes, err := validateCsr(request.Csr) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
|  | 	requestAttributes := &SigningRequestAttributes{ | ||||||
|  | 		CommonName:   request.CommonName, | ||||||
|  | 		CSRBytes:     csrBytes, | ||||||
|  | 		RequestToken: requestToken, | ||||||
|  | 	} | ||||||
| 	go func() { | 	go func() { | ||||||
| 		responseChannel := make(chan *responseData, 1) | 		responseChannel := make(chan *responseData, 1) | ||||||
| 		registry.requests[requestUuid.String()] = responseChannel | 		registry.requests[requestToken] = responseChannel | ||||||
| 		registry.signCertificate(responseChannel, request) | 		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) | 	responseData, err := registry.sign(request) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Error(err) | 		log.Error(err) | ||||||
|  | @ -52,21 +90,26 @@ func (registry *SigningRequestRegistry) signCertificate(channel chan *responseDa | ||||||
| 	channel <- responseData | 	channel <- responseData | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (registry *SigningRequestRegistry) sign(request *requestData) (response *responseData, err error) { | func (registry *SigningRequestRegistry) sign(request *SigningRequestAttributes) (*responseData, error) { | ||||||
| 	log.Debugf("received CSR for %s:\n\n%s", request.CommonName, request.Csr) | 	log.Infof("handling signing request %s", request.RequestToken) | ||||||
| 	subjectDN := fmt.Sprintf("/CN=%s", request.CommonName) | 	subjectDN := fmt.Sprintf("/CN=%s", request.CommonName) | ||||||
|  | 
 | ||||||
|  | 	var err error | ||||||
| 	var csrFile *os.File | 	var csrFile *os.File | ||||||
| 	if csrFile, err = ioutil.TempFile("", "*.csr.pem"); err != nil { | 	if csrFile, err = ioutil.TempFile("", "*.csr.pem"); err != nil { | ||||||
| 		log.Errorf("could not open temporary file: %s", err) | 		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) | 		log.Errorf("could not write CSR to file: %s", err) | ||||||
| 		return | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	if err = csrFile.Close(); err != nil { | 	if err = csrFile.Close(); err != nil { | ||||||
| 		log.Errorf("could not close CSR file: %s", err) | 		log.Errorf("could not close CSR file: %s", err) | ||||||
| 		return | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	defer func(file *os.File) { | 	defer func(file *os.File) { | ||||||
| 		err = os.Remove(file.Name()) | 		err = os.Remove(file.Name()) | ||||||
|  | @ -81,7 +124,7 @@ func (registry *SigningRequestRegistry) sign(request *requestData) (response *re | ||||||
| 	opensslCommand := exec.Command( | 	opensslCommand := exec.Command( | ||||||
| 		"openssl", "ca", "-config", "ca.cnf", | 		"openssl", "ca", "-config", "ca.cnf", | ||||||
| 		"-policy", "policy_match", "-extensions", "client_ext", | 		"-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 | 	var out, cmdErr bytes.Buffer | ||||||
| 	opensslCommand.Stdout = &out | 	opensslCommand.Stdout = &out | ||||||
| 	opensslCommand.Stderr = &cmdErr | 	opensslCommand.Stderr = &cmdErr | ||||||
|  | @ -89,32 +132,32 @@ func (registry *SigningRequestRegistry) sign(request *requestData) (response *re | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		log.Error(err) | 		log.Error(err) | ||||||
| 		log.Error(cmdErr.String()) | 		log.Error(cmdErr.String()) | ||||||
| 		return | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var block *pem.Block | 	var block *pem.Block | ||||||
| 	if block, _ = pem.Decode(out.Bytes()); block == nil { | 	if block, _ = pem.Decode(out.Bytes()); block == nil { | ||||||
| 		err = fmt.Errorf("could not decode pem") | 		err = fmt.Errorf("could not decode pem") | ||||||
| 		return | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 	var certificate *x509.Certificate | 	var certificate *x509.Certificate | ||||||
| 	if certificate, err = x509.ParseCertificate(block.Bytes); err != nil { | 	if certificate, err = x509.ParseCertificate(block.Bytes); err != nil { | ||||||
| 		return | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	var caChain []string | 	var caChain []string | ||||||
| 	if caChain, err = registry.getCAChain(certificate); err != nil { | 	if caChain, err = registry.getCAChain(certificate); err != nil { | ||||||
| 		return | 		return nil, err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	response = &responseData{ | 	response := &responseData{ | ||||||
| 		Certificate: string(pem.EncodeToMemory(&pem.Block{ | 		Certificate: string(pem.EncodeToMemory(&pem.Block{ | ||||||
| 			Type:  "CERTIFICATE", | 			Type:  "CERTIFICATE", | ||||||
| 			Bytes: certificate.Raw, | 			Bytes: certificate.Raw, | ||||||
| 		})), | 		})), | ||||||
| 		CAChain: caChain, | 		CAChain: caChain, | ||||||
| 	} | 	} | ||||||
| 	return | 	return response, nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| func (registry *SigningRequestRegistry) GetResponseChannel(requestUuid string) (chan *responseData, error) { | func (registry *SigningRequestRegistry) GetResponseChannel(requestUuid string) (chan *responseData, error) { | ||||||
|  |  | ||||||
|  | @ -53,7 +53,7 @@ func (h *CertificateSigningHandler) ServeHTTP(w http.ResponseWriter, r *http.Req | ||||||
| 
 | 
 | ||||||
| type requestData struct { | type requestData struct { | ||||||
| 	Csr        string `json:"csr"` | 	Csr        string `json:"csr"` | ||||||
| 	CommonName string `json:"commonName"` | 	CommonName string `json:"common_name"` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| type responseData struct { | type responseData struct { | ||||||
|  |  | ||||||
							
								
								
									
										24
									
								
								main.go
									
										
									
									
									
								
							
							
						
						
									
										24
									
								
								main.go
									
										
									
									
									
								
							|  | @ -25,16 +25,9 @@ import ( | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func main() { | func main() { | ||||||
| 	tlsConfig := &tls.Config{ | 	log.SetFormatter(&log.TextFormatter{ | ||||||
| 		CipherSuites: []uint16{ | 		FullTimestamp: true, | ||||||
| 			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, |  | ||||||
| 	} |  | ||||||
| 
 | 
 | ||||||
| 	bundle := loadI18nBundle() | 	bundle := loadI18nBundle() | ||||||
| 	mux := http.NewServeMux() | 	mux := http.NewServeMux() | ||||||
|  | @ -49,6 +42,17 @@ func main() { | ||||||
| 	mux.Handle("/js/", fileServer) | 	mux.Handle("/js/", fileServer) | ||||||
| 	mux.Handle("/locales/", handlers.NewJSLocalesHandler(bundle)) | 	mux.Handle("/locales/", handlers.NewJSLocalesHandler(bundle)) | ||||||
| 	mux.Handle("/ws/", handlers.NewWebSocketHandler(signingRequestRegistry)) | 	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{ | 	server := http.Server{ | ||||||
| 		Addr:              ":8000", | 		Addr:              ":8000", | ||||||
| 		Handler:           csrf.Protect(csrfKey, csrf.FieldName("csrfToken"), csrf.RequestHeader("X-CSRF-Token"))(mux), | 		Handler:           csrf.Protect(csrfKey, csrf.FieldName("csrfToken"), csrf.RequestHeader("X-CSRF-Token"))(mux), | ||||||
|  |  | ||||||
|  | @ -150,7 +150,7 @@ | ||||||
|                         progressBar.setAttribute("aria-valuenow", "3"); |                         progressBar.setAttribute("aria-valuenow", "3"); | ||||||
|                         progressBar.classList.add('progress-bar-striped', 'progress-bar-animated'); |                         progressBar.classList.add('progress-bar-striped', 'progress-bar-animated'); | ||||||
|                         progressBar.innerHTML = i18n.t('keygen.generated', {seconds: seconds}) + ', ' + i18n.t('certificate.waiting'); |                         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 => { |                             .then(data => { | ||||||
|                                 const request_id = data["request_id"] |                                 const request_id = data["request_id"] | ||||||
|                                 const webSocket = new WebSocket( |                                 const webSocket = new WebSocket( | ||||||
|  |  | ||||||
		Reference in a new issue