github.com/haraldrudell/parl@v0.4.176/phttp/https_test.go (about) 1 /* 2 © 2021–present Harald Rudell <harald.rudell@gmail.com> (https://haraldrudell.github.io/haraldrudell/) 3 ISC License 4 */ 5 6 package phttp 7 8 import ( 9 "crypto" 10 "crypto/x509" 11 "fmt" 12 "io/fs" 13 "net" 14 "net/http" 15 "net/netip" 16 "os" 17 "path/filepath" 18 "strconv" 19 "testing" 20 21 "github.com/haraldrudell/parl" 22 "github.com/haraldrudell/parl/parlca" 23 "github.com/haraldrudell/parl/perrors" 24 "github.com/haraldrudell/parl/pnet" 25 ) 26 27 func TestNewHttps(t *testing.T) { 28 var ip = "127.0.0.1" 29 var nearSocket = netip.MustParseAddrPort(ip + ":0") 30 var network = pnet.NetworkTCP 31 var addrExp = ip + ":" + strconv.Itoa(int(HttpsPort)) 32 33 var certDER parl.CertificateDer 34 var signer crypto.Signer 35 var https = NewHttps(nearSocket, network, certDER, signer) 36 if https.Network != pnet.NetworkTCP { 37 t.Errorf("New bad network %s exp %s", https.Network, network) 38 } 39 if https.Server.Addr != addrExp { 40 t.Errorf("bad Addr %q exp %q", https.Server.Addr, addrExp) 41 } 42 } 43 44 func TestHttpsListen(t *testing.T) { 45 var IPv4loopback = net.IPv4(127, 0, 0, 1) 46 var ipS = IPv4loopback.String() 47 var dir = t.TempDir() 48 var nearSocket = netip.MustParseAddrPort(ipS + ":0") 49 var network = pnet.NetworkDefault 50 // https:// 51 var protocol = "https://" 52 // "/" matches everything 53 var URIPattern = "/" 54 // ca common name, will default to {host}ca 55 var canonicalName = "" 56 // /usr/local/opt/openssl/bin/openssl x509 -in ca.der -inform der -noout -text 57 var caCertFilename = filepath.Join(dir, "ca.der") 58 var ownerRW = fs.FileMode(0o600) 59 // /usr/local/opt/openssl/bin/openssl pkey -in key.der -inform der -text -noout 60 var keyFilename = filepath.Join(dir, "key.der") 61 // /usr/local/opt/openssl/bin/openssl x509 -in cert.der -inform der -noout -text 62 var certFilename = filepath.Join(dir, "cert.der") 63 // var commonName = "" // server certificate, will default to {host} 64 // var subject = "subject" 65 66 var err error 67 var caCert parl.CertificateAuthority 68 var caX509 *x509.Certificate 69 var serverKey parl.PrivateKey 70 // private key for running the server 71 var serverSigner parl.PrivateKey 72 // public key for creating server certificate 73 var serverPublic crypto.PublicKey 74 var keyDER parl.PrivateKeyDer 75 var template x509.Certificate 76 var certDER parl.CertificateDer 77 var handler *sHandler 78 var goResult = parl.NewGoResult() 79 var near, respS string 80 var resp *http.Response 81 var statusCode int 82 83 t.Log("Creating self-signed certificate authority") 84 if caCert, err = parlca.NewSelfSigned(canonicalName, x509.RSA); err != nil { 85 t.Fatalf("parlca.NewSelfSigned %s %s", x509.RSA, perrors.Short(err)) 86 } 87 writeFile(caCertFilename, caCert.DER(), ownerRW, t.Logf) 88 caX509, err = caCert.Check() 89 if err != nil { 90 t.Fatalf("caCert,Check: %s", perrors.Short(err)) 91 } 92 93 t.Log("Creating server private key") 94 serverKey, err = parlca.NewEd25519() 95 if err != nil { 96 t.Fatal(perrors.Errorf("server parlca.NewEd25519: '%w'", err)) 97 } 98 serverSigner = serverKey // private key for running the server 99 serverPublic = serverSigner.Public() // public key for creating server certificate 100 if keyDER, err = serverKey.DER(); err != nil { 101 t.Fatal(err) 102 } 103 writeFile(keyFilename, keyDER, ownerRW, t.Logf) 104 105 t.Log("Creating server certificate") 106 template = x509.Certificate{ 107 IPAddresses: []net.IP{IPv4loopback, net.IPv6loopback}, 108 } 109 parlca.EnsureServer(&template) 110 if certDER, err = caCert.Sign(&template, serverPublic); err != nil { 111 t.Errorf("signing server certificate: %+v", err) 112 return 113 } 114 writeFile(certFilename, certDER, ownerRW, t.Logf) 115 116 // Listen() TLS() 117 var https *Https = NewHttps(nearSocket, network, certDER, serverSigner) 118 119 handler = newShandler() 120 https.HandleFunc(URIPattern, handler.Handle) 121 defer https.Shutdown() 122 123 t.Log("invoking Listen") 124 go errChListener(https.Listen(), goResult) 125 126 t.Log("waiting for ListenAwaitable") 127 <-https.ListenAwaitable.Ch() 128 if !https.Near.IsValid() { 129 t.Fatalf("FATAL: https.Near invalid") 130 } 131 near = https.Near.String() 132 t.Logf("Near: %s", near) 133 134 t.Log("issuing http.GET") 135 resp, err = pnet.Get(protocol+near, pnet.NewTLSConfig(caX509), nil) 136 // macOS does accept self-signed certificate 137 //resp, err = http.Get(protocol + near) 138 if resp != nil { 139 statusCode = resp.StatusCode 140 respS = fmt.Sprintf("status code: %d", statusCode) 141 } else { 142 respS = "resp nil" 143 } 144 145 t.Logf("%s err: %s", respS, perrors.Short(err)) 146 if err != nil { 147 t.Errorf("http.Get err %s", perrors.Short(err)) 148 } 149 // status code should be 204 150 if resp != nil { 151 if resp.StatusCode != http.StatusNoContent { 152 t.Errorf("http.Get status code %d exp %d", resp.StatusCode, http.StatusNoContent) 153 } 154 if e := resp.Body.Close(); e != nil { 155 panic(e) 156 } 157 } 158 159 // handle count should be 1 160 if c := int(handler.Rqs.Load()); c != 1 { 161 t.Errorf("bad handle count: %d exp 1", c) 162 } 163 164 t.Logf("Shutting down server") 165 https.Shutdown() 166 167 // wait for error reader to exit 168 goResult.ReceiveError(nil) 169 170 if !https.ErrCh.IsClosed() { 171 t.Error("ErrCh not closed") 172 } 173 if !https.EndListenAwaitable.IsClosed() { 174 t.Error("EndListenAwaitable not closed") 175 } 176 } 177 178 // writeFile writes byts to file panic on error 179 func writeFile(filename string, byts []byte, mode fs.FileMode, logf parl.PrintfFunc) { 180 logf("Writing: %s", filename) 181 if err := os.WriteFile(filename, byts, mode); err != nil { 182 panic(perrors.Errorf("os.WriteFile: '%w'", err)) 183 } 184 }