@@ -18,6 +18,7 @@ package est
1818import (
1919 "bytes"
2020 "context"
21+ "crypto/rand"
2122 "crypto/tls"
2223 "crypto/x509"
2324 "errors"
@@ -26,10 +27,17 @@ import (
2627 "io/ioutil"
2728 "mime"
2829 "mime/multipart"
30+ "net"
2931 "net/http"
3032 "strconv"
3133 "strings"
3234 "time"
35+
36+ "github.com/smallstep/scep/x509util"
37+ )
38+
39+ const (
40+ tcpProtocol string = "tcp"
3341)
3442
3543// Client is an EST client implementing the Enrollment over Secure Transport
@@ -70,6 +78,11 @@ type Client struct {
7078 // Trusted Platform Module (TPM) or other hardware device.
7179 PrivateKey interface {}
7280
81+ // SigningKey is an optional private key to use for signing CSRs during initial enrollment.
82+ //
83+ // If not set, the challenge password field will not be included in the CSR.
84+ SigningKey interface {}
85+
7386 // AdditionalHeaders are additional HTTP headers to include with the
7487 // request to the EST server.
7588 AdditionalHeaders map [string ]string
@@ -111,7 +124,12 @@ func (c *Client) CACerts(ctx context.Context) ([]*x509.Certificate, error) {
111124 return nil , err
112125 }
113126
114- resp , err := c .makeHTTPClient ().Do (req )
127+ httpc , _ , err := c .makeHTTPClient ()
128+ if err != nil {
129+ return nil , err
130+ }
131+
132+ resp , err := httpc .Do (req )
115133 if err != nil {
116134 return nil , fmt .Errorf ("failed to execute HTTP request: %w" , err )
117135 }
@@ -135,7 +153,12 @@ func (c *Client) CSRAttrs(ctx context.Context) (CSRAttrs, error) {
135153 return CSRAttrs {}, err
136154 }
137155
138- resp , err := c .makeHTTPClient ().Do (req )
156+ httpc , _ , err := c .makeHTTPClient ()
157+ if err != nil {
158+ return CSRAttrs {}, err
159+ }
160+
161+ resp , err := httpc .Do (req )
139162 if err != nil {
140163 return CSRAttrs {}, fmt .Errorf ("failed to execute HTTP request: %w" , err )
141164 }
@@ -177,19 +200,31 @@ func (c *Client) Reenroll(ctx context.Context, r *x509.CertificateRequest) (*x50
177200
178201// Enroll requests a new certificate.
179202func (c * Client ) enrollCommon (ctx context.Context , r * x509.CertificateRequest , renew bool ) (* x509.Certificate , error ) {
180- reqBody := ioutil .NopCloser (bytes .NewBuffer (base64Encode (r .Raw )))
181-
182203 var endpoint = enrollEndpoint
183204 if renew {
184205 endpoint = reenrollEndpoint
185206 }
186207
208+ httpc , tlsUnique64 , err := c .makeHTTPClient ()
209+ if err != nil {
210+ return nil , err
211+ }
212+
213+ crBs := r .Raw
214+ if tlsUnique64 != "" && c .SigningKey != nil {
215+ crBs , err = c .addChallengePassword (r .Raw , tlsUnique64 )
216+ if err != nil {
217+ return nil , err
218+ }
219+ }
220+
221+ reqBody := ioutil .NopCloser (bytes .NewBuffer (base64Encode (crBs )))
187222 req , err := c .newRequest (ctx , http .MethodPost , endpoint , mimeTypePKCS10 , encodingTypeBase64 , mimeTypePKCS7 , reqBody )
188223 if err != nil {
189224 return nil , err
190225 }
191226
192- resp , err := c . makeHTTPClient () .Do (req )
227+ resp , err := httpc .Do (req )
193228 if err != nil {
194229 return nil , fmt .Errorf ("failed to execute HTTP request: %w" , err )
195230 }
@@ -216,7 +251,12 @@ func (c *Client) ServerKeyGen(ctx context.Context, r *x509.CertificateRequest) (
216251 return nil , nil , err
217252 }
218253
219- resp , err := c .makeHTTPClient ().Do (req )
254+ httpc , _ , err := c .makeHTTPClient ()
255+ if err != nil {
256+ return nil , nil , err
257+ }
258+
259+ resp , err := httpc .Do (req )
220260 if err != nil {
221261 return nil , nil , fmt .Errorf ("failed to execute HTTP request: %w" , err )
222262 }
@@ -342,7 +382,12 @@ func (c *Client) TPMEnroll(
342382 return nil , nil , nil , err
343383 }
344384
345- resp , err := c .makeHTTPClient ().Do (req )
385+ httpc , _ , err := c .makeHTTPClient ()
386+ if err != nil {
387+ return nil , nil , nil , err
388+ }
389+
390+ resp , err := httpc .Do (req )
346391 if err != nil {
347392 return nil , nil , nil , fmt .Errorf ("failed to execute HTTP request: %w" , err )
348393 }
@@ -447,6 +492,26 @@ func (c *Client) newRequest(
447492 return req , err
448493}
449494
495+ // addChallengePassword returns a new CSR based on the input csr.
496+ // The challenge password corresponds to the tls-unique value base64 encoded (only in TLS 1.2)
497+ func (c * Client ) addChallengePassword (csr []byte , challengePassword string ) ([]byte , error ) {
498+ if challengePassword == "" {
499+ return csr , nil
500+ }
501+
502+ stdCsr , err := x509 .ParseCertificateRequest (csr )
503+ if err != nil {
504+ return nil , err
505+ }
506+
507+ cr := x509util.CertificateRequest {
508+ CertificateRequest : * stdCsr ,
509+ ChallengePassword : challengePassword ,
510+ }
511+ crBs , err := x509util .CreateCertificateRequest (rand .Reader , & cr , c .SigningKey )
512+ return crBs , err
513+ }
514+
450515// checkResponseError returns nil if the HTTP response status code is 200 OK,
451516// otherwise it returns an error object implementing est.Error. In order to
452517// parse the Retry-After header and return a value, note that 202 Accepted
@@ -533,8 +598,12 @@ func (c *Client) uri(endpoint string) string {
533598}
534599
535600// makeHTTPClient makes and configures an HTTP client for connecting to an
536- // EST server.
537- func (c * Client ) makeHTTPClient () * http.Client {
601+ // EST server. It also returns the corresponding TLS-unique value base64 encoded which could be required by EST server.
602+ //
603+ // RFC 7030 - section 3.5 recommends including it in the CSR challenge password field.
604+ //
605+ // The value could be nil with respect to TLS version. More details at https://pkg.go.dev/crypto/tls#ConnectionState.TLSUnique
606+ func (c * Client ) makeHTTPClient () (* http.Client , string , error ) {
538607 var rootCAs * x509.CertPool
539608 if c .ExplicitAnchor != nil {
540609 rootCAs = c .ExplicitAnchor
@@ -550,14 +619,32 @@ func (c *Client) makeHTTPClient() *http.Client {
550619 }
551620 }
552621
553- return & http.Client {
622+ tlsClientConfig := & tls.Config {
623+ RootCAs : rootCAs ,
624+ Certificates : tlsCerts ,
625+ InsecureSkipVerify : c .InsecureSkipVerify ,
626+ }
627+
628+ conn , err := tls .Dial (tcpProtocol , c .Host , tlsClientConfig )
629+ if err != nil {
630+ return nil , "" , err
631+ }
632+
633+ var tlsUnique64 string
634+ if tlsu := conn .ConnectionState ().TLSUnique ; tlsu != nil {
635+ tlsUnique64 = string (base64Encode (tlsu ))
636+ } else {
637+ tlsUnique64 = ""
638+ }
639+
640+ httpc := & http.Client {
554641 Transport : & http.Transport {
555- TLSClientConfig : & tls.Config {
556- RootCAs : rootCAs ,
557- Certificates : tlsCerts ,
558- InsecureSkipVerify : c .InsecureSkipVerify ,
642+ DialTLSContext : func (ctx context.Context , network , addr string ) (net.Conn , error ) {
643+ return conn , nil
559644 },
560645 DisableKeepAlives : c .DisableKeepAlives ,
561646 },
562647 }
648+
649+ return httpc , tlsUnique64 , nil
563650}
0 commit comments