github.com/pion/dtls/v2@v2.2.12/e2e/e2e_openssl_test.go (about) 1 // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly> 2 // SPDX-License-Identifier: MIT 3 4 //go:build openssl && !js 5 // +build openssl,!js 6 7 package e2e 8 9 import ( 10 "crypto/x509" 11 "encoding/pem" 12 "errors" 13 "fmt" 14 "io/ioutil" 15 "net" 16 "os" 17 "os/exec" 18 "regexp" 19 "strings" 20 "testing" 21 "time" 22 23 "github.com/pion/dtls/v2" 24 ) 25 26 func serverOpenSSL(c *comm) { 27 go func() { 28 c.serverMutex.Lock() 29 defer c.serverMutex.Unlock() 30 31 cfg := c.serverConfig 32 33 // create openssl arguments 34 args := []string{ 35 "s_server", 36 "-dtls1_2", 37 "-quiet", 38 "-verify_quiet", 39 "-verify_return_error", 40 fmt.Sprintf("-accept=%d", c.serverPort), 41 } 42 ciphers := ciphersOpenSSL(cfg) 43 if ciphers != "" { 44 args = append(args, fmt.Sprintf("-cipher=%s", ciphers)) 45 } 46 47 // psk arguments 48 if cfg.PSK != nil { 49 psk, err := cfg.PSK(nil) 50 if err != nil { 51 c.errChan <- err 52 return 53 } 54 args = append(args, fmt.Sprintf("-psk=%X", psk)) 55 if len(cfg.PSKIdentityHint) > 0 { 56 args = append(args, fmt.Sprintf("-psk_hint=%s", cfg.PSKIdentityHint)) 57 } 58 } 59 60 // certs arguments 61 if len(cfg.Certificates) > 0 { 62 // create temporary cert files 63 certPEM, keyPEM, err := writeTempPEM(cfg) 64 if err != nil { 65 c.errChan <- err 66 return 67 } 68 args = append(args, 69 fmt.Sprintf("-cert=%s", certPEM), 70 fmt.Sprintf("-key=%s", keyPEM)) 71 defer func() { 72 _ = os.Remove(certPEM) 73 _ = os.Remove(keyPEM) 74 }() 75 } else { 76 args = append(args, "-nocert") 77 } 78 79 // launch command 80 // #nosec G204 81 cmd := exec.CommandContext(c.ctx, "openssl", args...) 82 var inner net.Conn 83 inner, c.serverConn = net.Pipe() 84 cmd.Stdin = inner 85 cmd.Stdout = inner 86 cmd.Stderr = os.Stderr 87 if err := cmd.Start(); err != nil { 88 c.errChan <- err 89 _ = inner.Close() 90 return 91 } 92 93 // Ensure that server has started 94 time.Sleep(500 * time.Millisecond) 95 96 c.serverReady <- struct{}{} 97 simpleReadWrite(c.errChan, c.serverChan, c.serverConn, c.messageRecvCount) 98 }() 99 } 100 101 func clientOpenSSL(c *comm) { 102 select { 103 case <-c.serverReady: 104 // OK 105 case <-time.After(time.Second): 106 c.errChan <- errors.New("waiting on serverReady err: timeout") 107 } 108 109 c.clientMutex.Lock() 110 defer c.clientMutex.Unlock() 111 112 cfg := c.clientConfig 113 114 // create openssl arguments 115 args := []string{ 116 "s_client", 117 "-dtls1_2", 118 "-quiet", 119 "-verify_quiet", 120 "-servername=localhost", 121 fmt.Sprintf("-connect=127.0.0.1:%d", c.serverPort), 122 } 123 ciphers := ciphersOpenSSL(cfg) 124 if ciphers != "" { 125 args = append(args, fmt.Sprintf("-cipher=%s", ciphers)) 126 } 127 128 // psk arguments 129 if cfg.PSK != nil { 130 psk, err := cfg.PSK(nil) 131 if err != nil { 132 c.errChan <- err 133 return 134 } 135 args = append(args, fmt.Sprintf("-psk=%X", psk)) 136 } 137 138 // certificate arguments 139 if len(cfg.Certificates) > 0 { 140 // create temporary cert files 141 certPEM, keyPEM, err := writeTempPEM(cfg) 142 if err != nil { 143 c.errChan <- err 144 return 145 } 146 args = append(args, fmt.Sprintf("-CAfile=%s", certPEM), fmt.Sprintf("-cert=%s", certPEM), fmt.Sprintf("-key=%s", keyPEM)) 147 defer func() { 148 _ = os.Remove(certPEM) 149 _ = os.Remove(keyPEM) 150 }() 151 } 152 if !cfg.InsecureSkipVerify { 153 args = append(args, "-verify_return_error") 154 } 155 156 // launch command 157 // #nosec G204 158 cmd := exec.CommandContext(c.ctx, "openssl", args...) 159 var inner net.Conn 160 inner, c.clientConn = net.Pipe() 161 cmd.Stdin = inner 162 cmd.Stdout = inner 163 cmd.Stderr = os.Stderr 164 if err := cmd.Start(); err != nil { 165 c.errChan <- err 166 _ = inner.Close() 167 return 168 } 169 170 simpleReadWrite(c.errChan, c.clientChan, c.clientConn, c.messageRecvCount) 171 } 172 173 func ciphersOpenSSL(cfg *dtls.Config) string { 174 // See https://tls.mbed.org/supported-ssl-ciphersuites 175 translate := map[dtls.CipherSuiteID]string{ 176 dtls.TLS_ECDHE_ECDSA_WITH_AES_128_CCM: "ECDHE-ECDSA-AES128-CCM", 177 dtls.TLS_ECDHE_ECDSA_WITH_AES_128_CCM_8: "ECDHE-ECDSA-AES128-CCM8", 178 179 dtls.TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: "ECDHE-ECDSA-AES128-GCM-SHA256", 180 dtls.TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384: "ECDHE-ECDSA-AES256-GCM-SHA384", 181 182 dtls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: "ECDHE-RSA-AES128-GCM-SHA256", 183 dtls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384: "ECDHE-RSA-AES256-GCM-SHA384", 184 185 dtls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: "ECDHE-ECDSA-AES256-SHA", 186 dtls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: "ECDHE-RSA-AES256-SHA", 187 188 dtls.TLS_PSK_WITH_AES_128_CCM: "PSK-AES128-CCM", 189 dtls.TLS_PSK_WITH_AES_128_CCM_8: "PSK-AES128-CCM8", 190 dtls.TLS_PSK_WITH_AES_256_CCM_8: "PSK-AES256-CCM8", 191 192 dtls.TLS_PSK_WITH_AES_128_GCM_SHA256: "PSK-AES128-GCM-SHA256", 193 194 dtls.TLS_ECDHE_PSK_WITH_AES_128_CBC_SHA256: "ECDHE-PSK-AES128-CBC-SHA256", 195 } 196 197 var ciphers []string 198 for _, c := range cfg.CipherSuites { 199 if text, ok := translate[c]; ok { 200 ciphers = append(ciphers, text) 201 } 202 } 203 return strings.Join(ciphers, ";") 204 } 205 206 func writeTempPEM(cfg *dtls.Config) (string, string, error) { 207 certOut, err := ioutil.TempFile("", "cert.pem") 208 if err != nil { 209 return "", "", fmt.Errorf("failed to create temporary file: %w", err) 210 } 211 keyOut, err := ioutil.TempFile("", "key.pem") 212 if err != nil { 213 return "", "", fmt.Errorf("failed to create temporary file: %w", err) 214 } 215 216 cert := cfg.Certificates[0] 217 derBytes := cert.Certificate[0] 218 if err = pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}); err != nil { 219 return "", "", fmt.Errorf("failed to write data to cert.pem: %w", err) 220 } 221 if err = certOut.Close(); err != nil { 222 return "", "", fmt.Errorf("error closing cert.pem: %w", err) 223 } 224 225 priv := cert.PrivateKey 226 var privBytes []byte 227 privBytes, err = x509.MarshalPKCS8PrivateKey(priv) 228 if err != nil { 229 return "", "", fmt.Errorf("unable to marshal private key: %w", err) 230 } 231 if err = pem.Encode(keyOut, &pem.Block{Type: "PRIVATE KEY", Bytes: privBytes}); err != nil { 232 return "", "", fmt.Errorf("failed to write data to key.pem: %w", err) 233 } 234 if err = keyOut.Close(); err != nil { 235 return "", "", fmt.Errorf("error closing key.pem: %w", err) 236 } 237 return certOut.Name(), keyOut.Name(), nil 238 } 239 240 func minimumOpenSSLVersion(t *testing.T) bool { 241 t.Helper() 242 243 cmd := exec.Command("openssl", "version") 244 allOut, err := cmd.CombinedOutput() 245 if err != nil { 246 t.Log("Cannot determine OpenSSL version: ", err) 247 return false 248 } 249 verMatch := regexp.MustCompile(`(?i)^OpenSSL\s(?P<version>(\d+\.)?(\d+\.)?(\*|\d+)(\w)?).+$`) 250 match := verMatch.FindStringSubmatch(strings.TrimSpace(string(allOut))) 251 params := map[string]string{} 252 for i, name := range verMatch.SubexpNames() { 253 if i > 0 && i <= len(match) { 254 params[name] = match[i] 255 } 256 } 257 var ver string 258 if val, ok := params["version"]; !ok { 259 t.Log("Could not extract OpenSSL version") 260 return false 261 } else { 262 ver = val 263 } 264 265 cmp := strings.Compare(ver, "3.0.0") 266 if cmp == -1 { 267 return false 268 } 269 return true 270 } 271 272 func TestPionOpenSSLE2ESimple(t *testing.T) { 273 t.Run("OpenSSLServer", func(t *testing.T) { 274 testPionE2ESimple(t, serverOpenSSL, clientPion) 275 }) 276 t.Run("OpenSSLClient", func(t *testing.T) { 277 testPionE2ESimple(t, serverPion, clientOpenSSL) 278 }) 279 } 280 281 func TestPionOpenSSLE2ESimplePSK(t *testing.T) { 282 t.Run("OpenSSLServer", func(t *testing.T) { 283 testPionE2ESimplePSK(t, serverOpenSSL, clientPion) 284 }) 285 t.Run("OpenSSLClient", func(t *testing.T) { 286 testPionE2ESimplePSK(t, serverPion, clientOpenSSL) 287 }) 288 } 289 290 func TestPionOpenSSLE2EMTUs(t *testing.T) { 291 t.Run("OpenSSLServer", func(t *testing.T) { 292 testPionE2EMTUs(t, serverOpenSSL, clientPion) 293 }) 294 t.Run("OpenSSLClient", func(t *testing.T) { 295 testPionE2EMTUs(t, serverPion, clientOpenSSL) 296 }) 297 } 298 299 func TestPionOpenSSLE2ESimpleED25519(t *testing.T) { 300 t.Run("OpenSSLServer", func(t *testing.T) { 301 if !minimumOpenSSLVersion(t) { 302 t.Skip("Cannot use OpenSSL < 3.0 as a DTLS server with ED25519 keys") 303 } 304 testPionE2ESimpleED25519(t, serverOpenSSL, clientPion) 305 }) 306 t.Run("OpenSSLClient", func(t *testing.T) { 307 testPionE2ESimpleED25519(t, serverPion, clientOpenSSL) 308 }) 309 } 310 311 func TestPionOpenSSLE2ESimpleED25519ClientCert(t *testing.T) { 312 t.Run("OpenSSLServer", func(t *testing.T) { 313 if !minimumOpenSSLVersion(t) { 314 t.Skip("Cannot use OpenSSL < 3.0 as a DTLS server with ED25519 keys") 315 } 316 testPionE2ESimpleED25519ClientCert(t, serverOpenSSL, clientPion) 317 }) 318 t.Run("OpenSSLClient", func(t *testing.T) { 319 testPionE2ESimpleED25519ClientCert(t, serverPion, clientOpenSSL) 320 }) 321 } 322 323 func TestPionOpenSSLE2ESimpleECDSAClientCert(t *testing.T) { 324 t.Run("OpenSSLServer", func(t *testing.T) { 325 testPionE2ESimpleECDSAClientCert(t, serverOpenSSL, clientPion) 326 }) 327 t.Run("OpenSSLClient", func(t *testing.T) { 328 testPionE2ESimpleECDSAClientCert(t, serverPion, clientOpenSSL) 329 }) 330 }