1package pq 2 3// This file contains SSL tests 4 5import ( 6 _ "crypto/sha256" 7 "crypto/x509" 8 "database/sql" 9 "os" 10 "path/filepath" 11 "testing" 12) 13 14func maybeSkipSSLTests(t *testing.T) { 15 // Require some special variables for testing certificates 16 if os.Getenv("PQSSLCERTTEST_PATH") == "" { 17 t.Skip("PQSSLCERTTEST_PATH not set, skipping SSL tests") 18 } 19 20 value := os.Getenv("PQGOSSLTESTS") 21 if value == "" || value == "0" { 22 t.Skip("PQGOSSLTESTS not enabled, skipping SSL tests") 23 } else if value != "1" { 24 t.Fatalf("unexpected value %q for PQGOSSLTESTS", value) 25 } 26} 27 28func openSSLConn(t *testing.T, conninfo string) (*sql.DB, error) { 29 db, err := openTestConnConninfo(conninfo) 30 if err != nil { 31 // should never fail 32 t.Fatal(err) 33 } 34 // Do something with the connection to see whether it's working or not. 35 tx, err := db.Begin() 36 if err == nil { 37 return db, tx.Rollback() 38 } 39 _ = db.Close() 40 return nil, err 41} 42 43func checkSSLSetup(t *testing.T, conninfo string) { 44 _, err := openSSLConn(t, conninfo) 45 if pge, ok := err.(*Error); ok { 46 if pge.Code.Name() != "invalid_authorization_specification" { 47 t.Fatalf("unexpected error code '%s'", pge.Code.Name()) 48 } 49 } else { 50 t.Fatalf("expected %T, got %v", (*Error)(nil), err) 51 } 52} 53 54// Connect over SSL and run a simple query to test the basics 55func TestSSLConnection(t *testing.T) { 56 maybeSkipSSLTests(t) 57 // Environment sanity check: should fail without SSL 58 checkSSLSetup(t, "sslmode=disable user=pqgossltest") 59 60 db, err := openSSLConn(t, "sslmode=require user=pqgossltest") 61 if err != nil { 62 t.Fatal(err) 63 } 64 rows, err := db.Query("SELECT 1") 65 if err != nil { 66 t.Fatal(err) 67 } 68 rows.Close() 69} 70 71// Test sslmode=verify-full 72func TestSSLVerifyFull(t *testing.T) { 73 maybeSkipSSLTests(t) 74 // Environment sanity check: should fail without SSL 75 checkSSLSetup(t, "sslmode=disable user=pqgossltest") 76 77 // Not OK according to the system CA 78 _, err := openSSLConn(t, "host=postgres sslmode=verify-full user=pqgossltest") 79 if err == nil { 80 t.Fatal("expected error") 81 } 82 _, ok := err.(x509.UnknownAuthorityError) 83 if !ok { 84 t.Fatalf("expected x509.UnknownAuthorityError, got %#+v", err) 85 } 86 87 rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt") 88 rootCert := "sslrootcert=" + rootCertPath + " " 89 // No match on Common Name 90 _, err = openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=verify-full user=pqgossltest") 91 if err == nil { 92 t.Fatal("expected error") 93 } 94 _, ok = err.(x509.HostnameError) 95 if !ok { 96 t.Fatalf("expected x509.HostnameError, got %#+v", err) 97 } 98 // OK 99 _, err = openSSLConn(t, rootCert+"host=postgres sslmode=verify-full user=pqgossltest") 100 if err != nil { 101 t.Fatal(err) 102 } 103} 104 105// Test sslmode=require sslrootcert=rootCertPath 106func TestSSLRequireWithRootCert(t *testing.T) { 107 maybeSkipSSLTests(t) 108 // Environment sanity check: should fail without SSL 109 checkSSLSetup(t, "sslmode=disable user=pqgossltest") 110 111 bogusRootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "bogus_root.crt") 112 bogusRootCert := "sslrootcert=" + bogusRootCertPath + " " 113 114 // Not OK according to the bogus CA 115 _, err := openSSLConn(t, bogusRootCert+"host=postgres sslmode=require user=pqgossltest") 116 if err == nil { 117 t.Fatal("expected error") 118 } 119 _, ok := err.(x509.UnknownAuthorityError) 120 if !ok { 121 t.Fatalf("expected x509.UnknownAuthorityError, got %s, %#+v", err, err) 122 } 123 124 nonExistentCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "non_existent.crt") 125 nonExistentCert := "sslrootcert=" + nonExistentCertPath + " " 126 127 // No match on Common Name, but that's OK because we're not validating anything. 128 _, err = openSSLConn(t, nonExistentCert+"host=127.0.0.1 sslmode=require user=pqgossltest") 129 if err != nil { 130 t.Fatal(err) 131 } 132 133 rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt") 134 rootCert := "sslrootcert=" + rootCertPath + " " 135 136 // No match on Common Name, but that's OK because we're not validating the CN. 137 _, err = openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=require user=pqgossltest") 138 if err != nil { 139 t.Fatal(err) 140 } 141 // Everything OK 142 _, err = openSSLConn(t, rootCert+"host=postgres sslmode=require user=pqgossltest") 143 if err != nil { 144 t.Fatal(err) 145 } 146} 147 148// Test sslmode=verify-ca 149func TestSSLVerifyCA(t *testing.T) { 150 maybeSkipSSLTests(t) 151 // Environment sanity check: should fail without SSL 152 checkSSLSetup(t, "sslmode=disable user=pqgossltest") 153 154 // Not OK according to the system CA 155 { 156 _, err := openSSLConn(t, "host=postgres sslmode=verify-ca user=pqgossltest") 157 if _, ok := err.(x509.UnknownAuthorityError); !ok { 158 t.Fatalf("expected %T, got %#+v", x509.UnknownAuthorityError{}, err) 159 } 160 } 161 162 // Still not OK according to the system CA; empty sslrootcert is treated as unspecified. 163 { 164 _, err := openSSLConn(t, "host=postgres sslmode=verify-ca user=pqgossltest sslrootcert=''") 165 if _, ok := err.(x509.UnknownAuthorityError); !ok { 166 t.Fatalf("expected %T, got %#+v", x509.UnknownAuthorityError{}, err) 167 } 168 } 169 170 rootCertPath := filepath.Join(os.Getenv("PQSSLCERTTEST_PATH"), "root.crt") 171 rootCert := "sslrootcert=" + rootCertPath + " " 172 // No match on Common Name, but that's OK 173 if _, err := openSSLConn(t, rootCert+"host=127.0.0.1 sslmode=verify-ca user=pqgossltest"); err != nil { 174 t.Fatal(err) 175 } 176 // Everything OK 177 if _, err := openSSLConn(t, rootCert+"host=postgres sslmode=verify-ca user=pqgossltest"); err != nil { 178 t.Fatal(err) 179 } 180} 181 182// Authenticate over SSL using client certificates 183func TestSSLClientCertificates(t *testing.T) { 184 maybeSkipSSLTests(t) 185 // Environment sanity check: should fail without SSL 186 checkSSLSetup(t, "sslmode=disable user=pqgossltest") 187 188 const baseinfo = "sslmode=require user=pqgosslcert" 189 190 // Certificate not specified, should fail 191 { 192 _, err := openSSLConn(t, baseinfo) 193 if pge, ok := err.(*Error); ok { 194 if pge.Code.Name() != "invalid_authorization_specification" { 195 t.Fatalf("unexpected error code '%s'", pge.Code.Name()) 196 } 197 } else { 198 t.Fatalf("expected %T, got %v", (*Error)(nil), err) 199 } 200 } 201 202 // Empty certificate specified, should fail 203 { 204 _, err := openSSLConn(t, baseinfo+" sslcert=''") 205 if pge, ok := err.(*Error); ok { 206 if pge.Code.Name() != "invalid_authorization_specification" { 207 t.Fatalf("unexpected error code '%s'", pge.Code.Name()) 208 } 209 } else { 210 t.Fatalf("expected %T, got %v", (*Error)(nil), err) 211 } 212 } 213 214 // Non-existent certificate specified, should fail 215 { 216 _, err := openSSLConn(t, baseinfo+" sslcert=/tmp/filedoesnotexist") 217 if pge, ok := err.(*Error); ok { 218 if pge.Code.Name() != "invalid_authorization_specification" { 219 t.Fatalf("unexpected error code '%s'", pge.Code.Name()) 220 } 221 } else { 222 t.Fatalf("expected %T, got %v", (*Error)(nil), err) 223 } 224 } 225 226 certpath, ok := os.LookupEnv("PQSSLCERTTEST_PATH") 227 if !ok { 228 t.Fatalf("PQSSLCERTTEST_PATH not present in environment") 229 } 230 231 sslcert := filepath.Join(certpath, "postgresql.crt") 232 233 // Cert present, key not specified, should fail 234 { 235 _, err := openSSLConn(t, baseinfo+" sslcert="+sslcert) 236 if _, ok := err.(*os.PathError); !ok { 237 t.Fatalf("expected %T, got %#+v", (*os.PathError)(nil), err) 238 } 239 } 240 241 // Cert present, empty key specified, should fail 242 { 243 _, err := openSSLConn(t, baseinfo+" sslcert="+sslcert+" sslkey=''") 244 if _, ok := err.(*os.PathError); !ok { 245 t.Fatalf("expected %T, got %#+v", (*os.PathError)(nil), err) 246 } 247 } 248 249 // Cert present, non-existent key, should fail 250 { 251 _, err := openSSLConn(t, baseinfo+" sslcert="+sslcert+" sslkey=/tmp/filedoesnotexist") 252 if _, ok := err.(*os.PathError); !ok { 253 t.Fatalf("expected %T, got %#+v", (*os.PathError)(nil), err) 254 } 255 } 256 257 // Key has wrong permissions (passing the cert as the key), should fail 258 if _, err := openSSLConn(t, baseinfo+" sslcert="+sslcert+" sslkey="+sslcert); err != ErrSSLKeyHasWorldPermissions { 259 t.Fatalf("expected %s, got %#+v", ErrSSLKeyHasWorldPermissions, err) 260 } 261 262 sslkey := filepath.Join(certpath, "postgresql.key") 263 264 // Should work 265 if db, err := openSSLConn(t, baseinfo+" sslcert="+sslcert+" sslkey="+sslkey); err != nil { 266 t.Fatal(err) 267 } else { 268 rows, err := db.Query("SELECT 1") 269 if err != nil { 270 t.Fatal(err) 271 } 272 if err := rows.Close(); err != nil { 273 t.Fatal(err) 274 } 275 if err := db.Close(); err != nil { 276 t.Fatal(err) 277 } 278 } 279} 280