Browse code

Test x509 intermediates correctly

Jordan Liggitt authored on 2016/10/11 14:47:19
Showing 12 changed files
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 25
new file mode 100644
... ...
@@ -0,0 +1,3 @@
0
+{
1
+    "CN": "My Client"
2
+}
0 3
\ 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 19
new file mode 100644
... ...
@@ -0,0 +1,6 @@
0
+{
1
+    "CN": "Intermediate-CA",
2
+    "ca": {
3
+        "expiry": "876000h"
4
+    }
5
+}
0 6
\ 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-----
0 11
new file mode 100644
... ...
@@ -0,0 +1,6 @@
0
+{
1
+    "CN": "Root-CA",
2
+    "ca": {
3
+        "expiry": "876000h"
4
+    }
5
+}
0 6
\ No newline at end of file
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)