Fast and extensible multi-platform HTTP/1-2-3 web server with automatic HTTPS
Mirrored from https://github.com/Slazee/caddy.git
Synced Nov 11, 01:00 UTC
| 1 | package caddytls |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "crypto" |
| 6 | "crypto/ecdsa" |
| 7 | "crypto/elliptic" |
| 8 | "crypto/rand" |
| 9 | "crypto/tls" |
| 10 | "crypto/x509" |
| 11 | "crypto/x509/pkix" |
| 12 | "encoding/json" |
| 13 | "errors" |
| 14 | "math/big" |
| 15 | "net" |
| 16 | "reflect" |
| 17 | "testing" |
| 18 | "time" |
| 19 | |
| 20 | "github.com/caddyserver/caddy/v2" |
| 21 | ) |
| 22 | |
| 23 | type testClientCertificateVerifier struct { |
| 24 | called bool |
| 25 | rawCerts [][]byte |
| 26 | verifiedChains [][]*x509.Certificate |
| 27 | err error |
| 28 | } |
| 29 | |
| 30 | func (v *testClientCertificateVerifier) VerifyClientCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error { |
| 31 | v.called = true |
| 32 | v.rawCerts = rawCerts |
| 33 | v.verifiedChains = verifiedChains |
| 34 | return v.err |
| 35 | } |
| 36 | |
| 37 | func TestClientAuthenticationVerifyConnectionPassesRawCertsToVerifiers(t *testing.T) { |
| 38 | verifier := &testClientCertificateVerifier{} |
| 39 | clientauth := &ClientAuthentication{ |
| 40 | verifiers: []ClientCertificateVerifier{verifier}, |
| 41 | } |
| 42 | |
| 43 | peerCert := &x509.Certificate{Raw: []byte("peer-cert-raw")} |
| 44 | verifiedChains := [][]*x509.Certificate{{peerCert}} |
| 45 | connState := tls.ConnectionState{ |
| 46 | PeerCertificates: []*x509.Certificate{peerCert}, |
| 47 | VerifiedChains: verifiedChains, |
| 48 | } |
| 49 | |
| 50 | if err := clientauth.verifyConnection(connState); err != nil { |
| 51 | t.Fatalf("verifyConnection failed: %v", err) |
| 52 | } |
| 53 | |
| 54 | if !reflect.DeepEqual(verifier.rawCerts, [][]byte{[]byte("peer-cert-raw")}) { |
| 55 | t.Fatalf("unexpected raw certs: got %#v", verifier.rawCerts) |
| 56 | } |
| 57 | if !reflect.DeepEqual(verifier.verifiedChains, verifiedChains) { |
| 58 | t.Fatalf("unexpected verified chains: got %#v", verifier.verifiedChains) |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | func TestClientAuthenticationVerifyConnectionReturnsVerifierError(t *testing.T) { |
| 63 | wantErr := errors.New("verify failed") |
| 64 | verifier := &testClientCertificateVerifier{err: wantErr} |
| 65 | clientauth := &ClientAuthentication{ |
| 66 | verifiers: []ClientCertificateVerifier{verifier}, |
| 67 | } |
| 68 | |
| 69 | peerCert := &x509.Certificate{Raw: []byte("peer-cert-raw")} |
| 70 | err := clientauth.verifyConnection(tls.ConnectionState{ |
| 71 | PeerCertificates: []*x509.Certificate{peerCert}, |
| 72 | }) |
| 73 | if !errors.Is(err, wantErr) { |
| 74 | t.Fatalf("expected error %v, got %v", wantErr, err) |
| 75 | } |
| 76 | } |
| 77 | |
| 78 | func TestClientAuthenticationVerifyConnectionSkipsVerifiersWithoutPeerCert(t *testing.T) { |
| 79 | verifier := &testClientCertificateVerifier{err: errors.New("verify failed")} |
| 80 | clientauth := &ClientAuthentication{ |
| 81 | verifiers: []ClientCertificateVerifier{verifier}, |
| 82 | } |
| 83 | |
| 84 | if err := clientauth.verifyConnection(tls.ConnectionState{}); err != nil { |
| 85 | t.Fatalf("verifyConnection failed: %v", err) |
| 86 | } |
| 87 | if verifier.called { |
| 88 | t.Fatal("verifier was called without a peer certificate") |
| 89 | } |
| 90 | } |
| 91 | |
| 92 | func TestClientAuthenticationModeWithLeafVerifier(t *testing.T) { |
| 93 | ca := issueTestCert(t, nil, 0) |
| 94 | serverCert := issueTestCert(t, &ca, x509.ExtKeyUsageServerAuth) |
| 95 | trusted := issueTestCert(t, &ca, x509.ExtKeyUsageClientAuth) |
| 96 | untrusted := issueTestCert(t, &ca, x509.ExtKeyUsageClientAuth) |
| 97 | |
| 98 | caPool := x509.NewCertPool() |
| 99 | caPool.AddCert(ca.Leaf) |
| 100 | |
| 101 | for _, tc := range []struct { |
| 102 | mode string |
| 103 | cert *tls.Certificate |
| 104 | wantError bool |
| 105 | }{ |
| 106 | {mode: "request", cert: nil, wantError: false}, |
| 107 | {mode: "request", cert: &trusted, wantError: false}, |
| 108 | {mode: "request", cert: &untrusted, wantError: true}, |
| 109 | {mode: "require", cert: nil, wantError: true}, |
| 110 | {mode: "require", cert: &trusted, wantError: false}, |
| 111 | {mode: "require", cert: &untrusted, wantError: true}, |
| 112 | {mode: "verify_if_given", cert: nil, wantError: false}, |
| 113 | {mode: "verify_if_given", cert: &trusted, wantError: false}, |
| 114 | {mode: "verify_if_given", cert: &untrusted, wantError: true}, |
| 115 | {mode: "require_and_verify", cert: nil, wantError: true}, |
| 116 | {mode: "require_and_verify", cert: &trusted, wantError: false}, |
| 117 | {mode: "require_and_verify", cert: &untrusted, wantError: true}, |
| 118 | } { |
| 119 | for _, version := range []uint16{tls.VersionTLS12, tls.VersionTLS13} { |
| 120 | clientauth := &ClientAuthentication{ |
| 121 | Mode: tc.mode, |
| 122 | ca: InlineCAPool{pool: caPool}, |
| 123 | verifiers: []ClientCertificateVerifier{LeafCertClientAuth{trustedLeafCerts: []*x509.Certificate{trusted.Leaf}}}, |
| 124 | } |
| 125 | serverConfig := &tls.Config{Certificates: []tls.Certificate{serverCert}} |
| 126 | if err := clientauth.ConfigureTLSConfig(serverConfig); err != nil { |
| 127 | t.Fatalf("configuring TLS: %v", err) |
| 128 | } |
| 129 | clientConfig := &tls.Config{ |
| 130 | InsecureSkipVerify: true, |
| 131 | MinVersion: version, |
| 132 | MaxVersion: version, |
| 133 | } |
| 134 | if tc.cert != nil { |
| 135 | clientConfig.Certificates = []tls.Certificate{*tc.cert} |
| 136 | } |
| 137 | |
| 138 | err := serverHandshake(t, serverConfig, clientConfig) |
| 139 | certName := "no certificate" |
| 140 | if tc.cert != nil { |
| 141 | certName = tc.cert.Leaf.Subject.CommonName |
| 142 | } |
| 143 | if tc.wantError && err == nil { |
| 144 | t.Errorf("mode %s, %s, %s: expected handshake error but got none", tc.mode, tls.VersionName(version), certName) |
| 145 | } |
| 146 | if !tc.wantError && err != nil { |
| 147 | t.Errorf("mode %s, %s, %s: unexpected handshake error: %v", tc.mode, tls.VersionName(version), certName, err) |
| 148 | } |
| 149 | } |
| 150 | } |
| 151 | } |
| 152 | |
| 153 | func TestClientAuthenticationVerifyModesNeedTrustPool(t *testing.T) { |
| 154 | ctx, cancel := caddy.NewContext(caddy.Context{Context: context.Background()}) |
| 155 | defer cancel() |
| 156 | |
| 157 | // verifiers are loaded by the connection policy, so set both fields like a provisioned config |
| 158 | verifiersRaw := []json.RawMessage{json.RawMessage(`{"verifier":"leaf"}`)} |
| 159 | verifiers := []ClientCertificateVerifier{LeafCertClientAuth{trustedLeafCerts: []*x509.Certificate{issueTestCert(t, nil, 0).Leaf}}} |
| 160 | customPool := json.RawMessage(`{"provider":"inline","trusted_ca_certs":["` + test_der_1 + `"]}`) |
| 161 | systemPool := json.RawMessage(`{"provider":"system"}`) |
| 162 | |
| 163 | for i, tc := range []struct { |
| 164 | clientauth ClientAuthentication |
| 165 | want tls.ClientAuthType |
| 166 | wantErr bool |
| 167 | }{ |
| 168 | {clientauth: ClientAuthentication{Mode: "verify_if_given"}, wantErr: true}, |
| 169 | {clientauth: ClientAuthentication{Mode: "require_and_verify"}, wantErr: true}, |
| 170 | {clientauth: ClientAuthentication{Mode: "require_and_verify", VerifiersRaw: verifiersRaw, verifiers: verifiers}, wantErr: true}, |
| 171 | {clientauth: ClientAuthentication{Mode: "verify_if_given", CARaw: systemPool}, want: tls.VerifyClientCertIfGiven}, |
| 172 | {clientauth: ClientAuthentication{Mode: "require_and_verify", CARaw: systemPool}, want: tls.RequireAndVerifyClientCert}, |
| 173 | {clientauth: ClientAuthentication{Mode: "verify_if_given", CARaw: customPool}, want: tls.VerifyClientCertIfGiven}, |
| 174 | {clientauth: ClientAuthentication{Mode: "require_and_verify", CARaw: customPool}, want: tls.RequireAndVerifyClientCert}, |
| 175 | {clientauth: ClientAuthentication{CARaw: customPool}, want: tls.RequireAndVerifyClientCert}, |
| 176 | {clientauth: ClientAuthentication{Mode: "request"}, want: tls.RequestClientCert}, |
| 177 | {clientauth: ClientAuthentication{Mode: "require"}, want: tls.RequireAnyClientCert}, |
| 178 | {clientauth: ClientAuthentication{VerifiersRaw: verifiersRaw, verifiers: verifiers}, want: tls.RequireAnyClientCert}, |
| 179 | {clientauth: ClientAuthentication{TrustedLeafCerts: []string{test_der_1}}, want: tls.RequireAnyClientCert}, |
| 180 | } { |
| 181 | if err := tc.clientauth.provision(ctx); err != nil { |
| 182 | t.Fatalf("Test %d: provisioning: %v", i, err) |
| 183 | } |
| 184 | cfg := new(tls.Config) |
| 185 | err := tc.clientauth.ConfigureTLSConfig(cfg) |
| 186 | if tc.wantErr { |
| 187 | if err == nil { |
| 188 | t.Errorf("Test %d: expected error but got none", i) |
| 189 | } |
| 190 | continue |
| 191 | } |
| 192 | if err != nil { |
| 193 | t.Errorf("Test %d: unexpected error: %v", i, err) |
| 194 | continue |
| 195 | } |
| 196 | if cfg.ClientAuth != tc.want { |
| 197 | t.Errorf("Test %d: expected %v, got %v", i, tc.want, cfg.ClientAuth) |
| 198 | } |
| 199 | if tc.clientauth.CARaw != nil && cfg.ClientCAs == nil { |
| 200 | t.Errorf("Test %d: expected ClientCAs to be set", i) |
| 201 | } |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | func TestClientAuthenticationVerifierOnlyHandshake(t *testing.T) { |
| 206 | ca := issueTestCert(t, nil, 0) |
| 207 | serverCert := issueTestCert(t, &ca, x509.ExtKeyUsageServerAuth) |
| 208 | trusted := issueTestCert(t, &ca, x509.ExtKeyUsageClientAuth) |
| 209 | untrusted := issueTestCert(t, &ca, x509.ExtKeyUsageClientAuth) |
| 210 | |
| 211 | for _, tc := range []struct { |
| 212 | cert *tls.Certificate |
| 213 | wantError bool |
| 214 | }{ |
| 215 | {cert: nil, wantError: true}, |
| 216 | {cert: &trusted, wantError: false}, |
| 217 | {cert: &untrusted, wantError: true}, |
| 218 | } { |
| 219 | // no mode and no trust pool, so only the verifier checks the cert |
| 220 | clientauth := &ClientAuthentication{ |
| 221 | VerifiersRaw: []json.RawMessage{json.RawMessage(`{"verifier":"leaf"}`)}, |
| 222 | verifiers: []ClientCertificateVerifier{LeafCertClientAuth{trustedLeafCerts: []*x509.Certificate{trusted.Leaf}}}, |
| 223 | } |
| 224 | serverConfig := &tls.Config{Certificates: []tls.Certificate{serverCert}} |
| 225 | if err := clientauth.ConfigureTLSConfig(serverConfig); err != nil { |
| 226 | t.Fatalf("configuring TLS: %v", err) |
| 227 | } |
| 228 | clientConfig := &tls.Config{InsecureSkipVerify: true} |
| 229 | if tc.cert != nil { |
| 230 | clientConfig.Certificates = []tls.Certificate{*tc.cert} |
| 231 | } |
| 232 | |
| 233 | err := serverHandshake(t, serverConfig, clientConfig) |
| 234 | if tc.wantError && err == nil { |
| 235 | t.Errorf("cert %v: expected handshake error but got none", tc.cert != nil) |
| 236 | } |
| 237 | if !tc.wantError && err != nil { |
| 238 | t.Errorf("cert %v: unexpected handshake error: %v", tc.cert != nil, err) |
| 239 | } |
| 240 | } |
| 241 | } |
| 242 | |
| 243 | // serverHandshake runs one TLS handshake over loopback TCP and returns |
| 244 | // the server's handshake error. |
| 245 | func serverHandshake(t *testing.T, serverConfig, clientConfig *tls.Config) error { |
| 246 | t.Helper() |
| 247 | ln, err := net.Listen("tcp", "127.0.0.1:0") |
| 248 | if err != nil { |
| 249 | t.Fatal(err) |
| 250 | } |
| 251 | defer ln.Close() |
| 252 | |
| 253 | errCh := make(chan error, 1) |
| 254 | go func() { |
| 255 | conn, err := ln.Accept() |
| 256 | if err != nil { |
| 257 | errCh <- err |
| 258 | return |
| 259 | } |
| 260 | tlsConn := tls.Server(conn, serverConfig) |
| 261 | errCh <- tlsConn.Handshake() |
| 262 | tlsConn.Close() |
| 263 | }() |
| 264 | |
| 265 | conn, err := tls.Dial("tcp", ln.Addr().String(), clientConfig) |
| 266 | if err == nil { |
| 267 | // wait for the server to finish before closing |
| 268 | _, _ = conn.Read(make([]byte, 1)) |
| 269 | conn.Close() |
| 270 | } |
| 271 | return <-errCh |
| 272 | } |
| 273 | |
| 274 | // issueTestCert returns a certificate signed by parent, or a self-signed |
| 275 | // CA certificate if parent is nil. |
| 276 | func issueTestCert(t *testing.T, parent *tls.Certificate, extKeyUsage x509.ExtKeyUsage) tls.Certificate { |
| 277 | t.Helper() |
| 278 | key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) |
| 279 | if err != nil { |
| 280 | t.Fatal(err) |
| 281 | } |
| 282 | serial, err := rand.Int(rand.Reader, big.NewInt(1<<62)) |
| 283 | if err != nil { |
| 284 | t.Fatal(err) |
| 285 | } |
| 286 | template := &x509.Certificate{ |
| 287 | SerialNumber: serial, |
| 288 | Subject: pkix.Name{CommonName: "test " + serial.String()}, |
| 289 | DNSNames: []string{"localhost"}, |
| 290 | NotBefore: time.Now().Add(-time.Hour), |
| 291 | NotAfter: time.Now().Add(time.Hour), |
| 292 | KeyUsage: x509.KeyUsageDigitalSignature, |
| 293 | } |
| 294 | signer, signerKey := template, crypto.Signer(key) |
| 295 | if parent == nil { |
| 296 | template.IsCA = true |
| 297 | template.BasicConstraintsValid = true |
| 298 | template.KeyUsage |= x509.KeyUsageCertSign |
| 299 | } else { |
| 300 | template.ExtKeyUsage = []x509.ExtKeyUsage{extKeyUsage} |
| 301 | signer, signerKey = parent.Leaf, parent.PrivateKey.(crypto.Signer) |
| 302 | } |
| 303 | der, err := x509.CreateCertificate(rand.Reader, template, signer, &key.PublicKey, signerKey) |
| 304 | if err != nil { |
| 305 | t.Fatal(err) |
| 306 | } |
| 307 | leaf, err := x509.ParseCertificate(der) |
| 308 | if err != nil { |
| 309 | t.Fatal(err) |
| 310 | } |
| 311 | return tls.Certificate{Certificate: [][]byte{der}, PrivateKey: key, Leaf: leaf} |
| 312 | } |