1 | 1 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,11 @@ |
0 |
+-----BEGIN CERTIFICATE----- |
|
1 |
+MIIBpTCCAUugAwIBAgIUPV4LAC5KK8YWY1FegyTuhkGUr3EwCgYIKoZIzj0EAwIw |
|
2 |
+GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMB4XDTkwMTIzMTIzNTkwMFoXDTkw |
|
3 |
+MTIzMTIzNTkwMFowFDESMBAGA1UEAxMJTXkgQ2xpZW50MFkwEwYHKoZIzj0CAQYI |
|
4 |
+KoZIzj0DAQcDQgAEyYUnseNUN87rfHgekrfZu5sj4wlt5LYr3JYZZkfSbsb+BW3/ |
|
5 |
+RzX02ifjp+8w7mI4qUGg6y6J7oXHGFT3uj9kj6N1MHMwDgYDVR0PAQH/BAQDAgWg |
|
6 |
+MBMGA1UdJQQMMAoGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHQYDVR0OBBYEFKsX |
|
7 |
+EnXwDg8j2LIEM1QzmFrE6537MB8GA1UdIwQYMBaAFF+p0JcY31pz+mjNZnjv0Gum |
|
8 |
+92vZMAoGCCqGSM49BAMCA0gAMEUCIG4FBcb57oqOCoaFiJ+Yx6S0zkaash7bTv3V |
|
9 |
+CIy9JvFdAiEAy8bf2S9EkvZyURZ6ycgEMnekll57Ebze6rjlPx8+B1Y= |
|
10 |
+-----END CERTIFICATE----- |
0 | 11 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,11 @@ |
0 |
+-----BEGIN CERTIFICATE----- |
|
1 |
+MIIBqDCCAU2gAwIBAgIUfbqeieihh/oERbfvRm38XvS/xHAwCgYIKoZIzj0EAwIw |
|
2 |
+GjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMCAXDTE2MTAxMTA1MDYwMFoYDzIx |
|
3 |
+MTYwOTE3MDUwNjAwWjAUMRIwEAYDVQQDEwlNeSBDbGllbnQwWTATBgcqhkjOPQIB |
|
4 |
+BggqhkjOPQMBBwNCAARv6N4R/sjMR65iMFGNLN1GC/vd7WhDW6J4X/iAjkRLLnNb |
|
5 |
+KbRG/AtOUZ+7upJ3BWIRKYbOabbQGQe2BbKFiap4o3UwczAOBgNVHQ8BAf8EBAMC |
|
6 |
+BaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNVHQ4EFgQU |
|
7 |
+K/pZOWpNcYai6eHFpmJEeFpeQlEwHwYDVR0jBBgwFoAUX6nQlxjfWnP6aM1meO/Q |
|
8 |
+a6b3a9kwCgYIKoZIzj0EAwIDSQAwRgIhAIWTKw/sjJITqeuNzJDAKU4xo1zL+xJ5 |
|
9 |
+MnVCuBwfwDXCAiEAw/1TA+CjPq9JC5ek1ifR0FybTURjeQqYkKpve1dveps= |
|
10 |
+-----END CERTIFICATE----- |
0 | 11 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,24 @@ |
0 |
+{ |
|
1 |
+ "signing": { |
|
2 |
+ "profiles": { |
|
3 |
+ "valid": { |
|
4 |
+ "expiry": "876000h", |
|
5 |
+ "usages": [ |
|
6 |
+ "signing", |
|
7 |
+ "key encipherment", |
|
8 |
+ "client auth" |
|
9 |
+ ] |
|
10 |
+ }, |
|
11 |
+ "expired": { |
|
12 |
+ "expiry": "1h", |
|
13 |
+ "not_before": "1990-12-31T23:59:00Z", |
|
14 |
+ "not_after": "1990-12-31T23:59:00Z", |
|
15 |
+ "usages": [ |
|
16 |
+ "signing", |
|
17 |
+ "key encipherment", |
|
18 |
+ "client auth" |
|
19 |
+ ] |
|
20 |
+ } |
|
21 |
+ } |
|
22 |
+ } |
|
23 |
+} |
|
0 | 24 |
\ No newline at end of file |
1 | 4 |
new file mode 100755 |
... | ... |
@@ -0,0 +1,10 @@ |
0 |
+#!/usr/bin/env bash |
|
1 |
+ |
|
2 |
+cfssl gencert -initca root.csr.json | cfssljson -bare root |
|
3 |
+ |
|
4 |
+cfssl gencert -initca intermediate.csr.json | cfssljson -bare intermediate |
|
5 |
+cfssl sign -ca root.pem -ca-key root-key.pem -config intermediate.config.json intermediate.csr | cfssljson -bare intermediate |
|
6 |
+ |
|
7 |
+cfssl gencert -ca intermediate.pem -ca-key intermediate-key.pem -config client.config.json --profile=valid client.csr.json | cfssljson -bare client-valid |
|
8 |
+cfssl gencert -ca intermediate.pem -ca-key intermediate-key.pem -config client.config.json --profile=expired client.csr.json | cfssljson -bare client-expired |
|
9 |
+ |
0 | 10 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,18 @@ |
0 |
+{ |
|
1 |
+ "signing": { |
|
2 |
+ "default": { |
|
3 |
+ "usages": [ |
|
4 |
+ "digital signature", |
|
5 |
+ "cert sign", |
|
6 |
+ "crl sign", |
|
7 |
+ "signing", |
|
8 |
+ "key encipherment", |
|
9 |
+ "client auth" |
|
10 |
+ ], |
|
11 |
+ "expiry": "876000h", |
|
12 |
+ "ca_constraint": { |
|
13 |
+ "is_ca": true |
|
14 |
+ } |
|
15 |
+ } |
|
16 |
+ } |
|
17 |
+} |
|
0 | 18 |
\ No newline at end of file |
1 | 7 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,11 @@ |
0 |
+-----BEGIN CERTIFICATE----- |
|
1 |
+MIIBqDCCAU6gAwIBAgIUfqZtjoFgczZ+oQZbEC/BDSS2J6wwCgYIKoZIzj0EAwIw |
|
2 |
+EjEQMA4GA1UEAxMHUm9vdC1DQTAgFw0xNjEwMTEwNTA2MDBaGA8yMTE2MDkxNzA1 |
|
3 |
+MDYwMFowGjEYMBYGA1UEAxMPSW50ZXJtZWRpYXRlLUNBMFkwEwYHKoZIzj0CAQYI |
|
4 |
+KoZIzj0DAQcDQgAEyWHEMMCctJg8Xa5YWLqaCPbk3MjB+uvXac42JM9pj4k9jedD |
|
5 |
+kpUJRkWIPzgJI8Zk/3cSzluUTixP6JBSDKtwwaN4MHYwDgYDVR0PAQH/BAQDAgGm |
|
6 |
+MBMGA1UdJQQMMAoGCCsGAQUFBwMCMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYE |
|
7 |
+FF+p0JcY31pz+mjNZnjv0Gum92vZMB8GA1UdIwQYMBaAFB7P6+i4/pfNjqZgJv/b |
|
8 |
+dgA7Fe4tMAoGCCqGSM49BAMCA0gAMEUCIQCTT1YWQZaAqfQ2oBxzOkJE2BqLFxhz |
|
9 |
+3smQlrZ5gCHddwIgcvT7puhYOzAgcvMn9+SZ1JOyZ7edODjshCVCRnuHK2c= |
|
10 |
+-----END CERTIFICATE----- |
1 | 7 |
new file mode 100644 |
... | ... |
@@ -0,0 +1,11 @@ |
0 |
+-----BEGIN CERTIFICATE----- |
|
1 |
+MIIBizCCATGgAwIBAgIUH4plk9qwD61FVXgiOTngFU5FeSkwCgYIKoZIzj0EAwIw |
|
2 |
+EjEQMA4GA1UEAxMHUm9vdC1DQTAgFw0xNjEwMTEwNTA2MDBaGA8yMTE2MDkxNzA1 |
|
3 |
+MDYwMFowEjEQMA4GA1UEAxMHUm9vdC1DQTBZMBMGByqGSM49AgEGCCqGSM49AwEH |
|
4 |
+A0IABI2CsrAnMGT8P2VGU2MLo5pv86Z74kcV9hgkLJUkSaeNyc1s89w7X5V2wvwu |
|
5 |
+iWEJRGm5RoZJausmyZLZEoKEVXejYzBhMA4GA1UdDwEB/wQEAwIBBjAPBgNVHRMB |
|
6 |
+Af8EBTADAQH/MB0GA1UdDgQWBBQez+vouP6XzY6mYCb/23YAOxXuLTAfBgNVHSME |
|
7 |
+GDAWgBQez+vouP6XzY6mYCb/23YAOxXuLTAKBggqhkjOPQQDAgNIADBFAiBGclts |
|
8 |
+vJRM+QMVoV/1L9b+hvhgLIp/OupUFsSOReefIwIhALY06hBklyh8eFwuBtyX2VcE |
|
9 |
+8xlVn4/5idUvc3Xv2h9s |
|
10 |
+-----END CERTIFICATE----- |
... | ... |
@@ -40,28 +40,34 @@ func New(opts x509.VerifyOptions, user UserConversion) *Authenticator { |
40 | 40 |
|
41 | 41 |
// AuthenticateRequest authenticates the request using presented client certificates |
42 | 42 |
func (a *Authenticator) AuthenticateRequest(req *http.Request) (user.Info, bool, error) { |
43 |
- if req.TLS == nil { |
|
43 |
+ if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 { |
|
44 | 44 |
return nil, false, nil |
45 | 45 |
} |
46 | 46 |
|
47 |
+ // Use intermediates, if provided |
|
48 |
+ optsCopy := a.opts |
|
49 |
+ if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 { |
|
50 |
+ optsCopy.Intermediates = x509.NewCertPool() |
|
51 |
+ for _, intermediate := range req.TLS.PeerCertificates[1:] { |
|
52 |
+ optsCopy.Intermediates.AddCert(intermediate) |
|
53 |
+ } |
|
54 |
+ } |
|
55 |
+ |
|
56 |
+ chains, err := req.TLS.PeerCertificates[0].Verify(optsCopy) |
|
57 |
+ if err != nil { |
|
58 |
+ return nil, false, err |
|
59 |
+ } |
|
60 |
+ |
|
47 | 61 |
var errlist []error |
48 |
- for _, cert := range req.TLS.PeerCertificates { |
|
49 |
- chains, err := cert.Verify(a.opts) |
|
62 |
+ for _, chain := range chains { |
|
63 |
+ user, ok, err := a.user.User(chain) |
|
50 | 64 |
if err != nil { |
51 | 65 |
errlist = append(errlist, err) |
52 | 66 |
continue |
53 | 67 |
} |
54 | 68 |
|
55 |
- for _, chain := range chains { |
|
56 |
- user, ok, err := a.user.User(chain) |
|
57 |
- if err != nil { |
|
58 |
- errlist = append(errlist, err) |
|
59 |
- continue |
|
60 |
- } |
|
61 |
- |
|
62 |
- if ok { |
|
63 |
- return user, ok, err |
|
64 |
- } |
|
69 |
+ if ok { |
|
70 |
+ return user, ok, err |
|
65 | 71 |
} |
66 | 72 |
} |
67 | 73 |
return nil, false, kerrors.NewAggregate(errlist) |
... | ... |
@@ -81,25 +87,28 @@ func NewVerifier(opts x509.VerifyOptions, auth authenticator.Request, allowedCom |
81 | 81 |
return &Verifier{opts, auth, allowedCommonNames} |
82 | 82 |
} |
83 | 83 |
|
84 |
-// AuthenticateRequest verifies the presented client certificates, then delegates to the wrapped auth |
|
84 |
+// AuthenticateRequest verifies the presented client certificate, then delegates to the wrapped auth |
|
85 | 85 |
func (a *Verifier) AuthenticateRequest(req *http.Request) (user.Info, bool, error) { |
86 |
- if req.TLS == nil { |
|
86 |
+ if req.TLS == nil || len(req.TLS.PeerCertificates) == 0 { |
|
87 | 87 |
return nil, false, nil |
88 | 88 |
} |
89 | 89 |
|
90 |
- var errlist []error |
|
91 |
- for _, cert := range req.TLS.PeerCertificates { |
|
92 |
- if _, err := cert.Verify(a.opts); err != nil { |
|
93 |
- errlist = append(errlist, err) |
|
94 |
- continue |
|
95 |
- } |
|
96 |
- if err := a.verifySubject(cert.Subject); err != nil { |
|
97 |
- errlist = append(errlist, err) |
|
98 |
- continue |
|
90 |
+ // Use intermediates, if provided |
|
91 |
+ optsCopy := a.opts |
|
92 |
+ if optsCopy.Intermediates == nil && len(req.TLS.PeerCertificates) > 1 { |
|
93 |
+ optsCopy.Intermediates = x509.NewCertPool() |
|
94 |
+ for _, intermediate := range req.TLS.PeerCertificates[1:] { |
|
95 |
+ optsCopy.Intermediates.AddCert(intermediate) |
|
99 | 96 |
} |
100 |
- return a.auth.AuthenticateRequest(req) |
|
101 | 97 |
} |
102 |
- return nil, false, kerrors.NewAggregate(errlist) |
|
98 |
+ |
|
99 |
+ if _, err := req.TLS.PeerCertificates[0].Verify(optsCopy); err != nil { |
|
100 |
+ return nil, false, err |
|
101 |
+ } |
|
102 |
+ if err := a.verifySubject(req.TLS.PeerCertificates[0].Subject); err != nil { |
|
103 |
+ return nil, false, err |
|
104 |
+ } |
|
105 |
+ return a.auth.AuthenticateRequest(req) |
|
103 | 106 |
} |
104 | 107 |
|
105 | 108 |
func (a *Verifier) verifySubject(subject pkix.Name) error { |
... | ... |
@@ -5,6 +5,7 @@ import ( |
5 | 5 |
"crypto/x509" |
6 | 6 |
"encoding/pem" |
7 | 7 |
"errors" |
8 |
+ "io/ioutil" |
|
8 | 9 |
"net/http" |
9 | 10 |
"testing" |
10 | 11 |
"time" |
... | ... |
@@ -357,6 +358,10 @@ mFlG6tStAWz3TmydciZNdiEbeqHw5uaIYWj1zC5AdvFXBFue0ojIrJ5JtbTWccH9 |
357 | 357 |
) |
358 | 358 |
|
359 | 359 |
func TestX509(t *testing.T) { |
360 |
+ multilevelOpts := DefaultVerifyOptions() |
|
361 |
+ multilevelOpts.Roots = x509.NewCertPool() |
|
362 |
+ multilevelOpts.Roots.AddCert(getCertsFromFile(t, "root")[0]) |
|
363 |
+ |
|
360 | 364 |
testCases := map[string]struct { |
361 | 365 |
Insecure bool |
362 | 366 |
Certs []*x509.Certificate |
... | ... |
@@ -495,6 +500,24 @@ func TestX509(t *testing.T) { |
495 | 495 |
ExpectOK: false, |
496 | 496 |
ExpectErr: true, |
497 | 497 |
}, |
498 |
+ |
|
499 |
+ "multi-level, valid": { |
|
500 |
+ Opts: multilevelOpts, |
|
501 |
+ Certs: getCertsFromFile(t, "client-valid", "intermediate"), |
|
502 |
+ User: CommonNameUserConversion, |
|
503 |
+ |
|
504 |
+ ExpectUserName: "My Client", |
|
505 |
+ ExpectOK: true, |
|
506 |
+ ExpectErr: false, |
|
507 |
+ }, |
|
508 |
+ "multi-level, expired": { |
|
509 |
+ Opts: multilevelOpts, |
|
510 |
+ Certs: getCertsFromFile(t, "client-expired", "intermediate"), |
|
511 |
+ User: CommonNameUserConversion, |
|
512 |
+ |
|
513 |
+ ExpectOK: false, |
|
514 |
+ ExpectErr: true, |
|
515 |
+ }, |
|
498 | 516 |
} |
499 | 517 |
|
500 | 518 |
for k, testCase := range testCases { |
... | ... |
@@ -531,6 +554,10 @@ func TestX509(t *testing.T) { |
531 | 531 |
} |
532 | 532 |
|
533 | 533 |
func TestX509Verifier(t *testing.T) { |
534 |
+ multilevelOpts := DefaultVerifyOptions() |
|
535 |
+ multilevelOpts.Roots = x509.NewCertPool() |
|
536 |
+ multilevelOpts.Roots.AddCert(getCertsFromFile(t, "root")[0]) |
|
537 |
+ |
|
534 | 538 |
testCases := map[string]struct { |
535 | 539 |
Insecure bool |
536 | 540 |
Certs []*x509.Certificate |
... | ... |
@@ -619,6 +646,21 @@ func TestX509Verifier(t *testing.T) { |
619 | 619 |
ExpectOK: false, |
620 | 620 |
ExpectErr: true, |
621 | 621 |
}, |
622 |
+ |
|
623 |
+ "multi-level, valid": { |
|
624 |
+ Opts: multilevelOpts, |
|
625 |
+ Certs: getCertsFromFile(t, "client-valid", "intermediate"), |
|
626 |
+ |
|
627 |
+ ExpectOK: true, |
|
628 |
+ ExpectErr: false, |
|
629 |
+ }, |
|
630 |
+ "multi-level, expired": { |
|
631 |
+ Opts: multilevelOpts, |
|
632 |
+ Certs: getCertsFromFile(t, "client-expired", "intermediate"), |
|
633 |
+ |
|
634 |
+ ExpectOK: false, |
|
635 |
+ ExpectErr: true, |
|
636 |
+ }, |
|
622 | 637 |
} |
623 | 638 |
|
624 | 639 |
for k, testCase := range testCases { |
... | ... |
@@ -681,6 +723,19 @@ func getRootCertPool(t *testing.T) *x509.CertPool { |
681 | 681 |
return pool |
682 | 682 |
} |
683 | 683 |
|
684 |
+func getCertsFromFile(t *testing.T, names ...string) []*x509.Certificate { |
|
685 |
+ certs := []*x509.Certificate{} |
|
686 |
+ for _, name := range names { |
|
687 |
+ filename := "testdata/" + name + ".pem" |
|
688 |
+ data, err := ioutil.ReadFile(filename) |
|
689 |
+ if err != nil { |
|
690 |
+ t.Fatalf("error reading %s: %v", filename, err) |
|
691 |
+ } |
|
692 |
+ certs = append(certs, getCert(t, string(data))) |
|
693 |
+ } |
|
694 |
+ return certs |
|
695 |
+} |
|
696 |
+ |
|
684 | 697 |
func getCert(t *testing.T, pemData string) *x509.Certificate { |
685 | 698 |
pemBlock, _ := pem.Decode([]byte(pemData)) |
686 | 699 |
cert, err := x509.ParseCertificate(pemBlock.Bytes) |