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 ( | ||||
| 	"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) { | ||||
|  |  | |||
|  | @ -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 { | ||||
|  |  | |||
							
								
								
									
										24
									
								
								main.go
									
										
									
									
									
								
							
							
						
						
									
										24
									
								
								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), | ||||
|  |  | |||
|  | @ -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( | ||||
|  |  | |||
		Reference in a new issue