golanggohlsrtmpwebrtcmedia-serverobs-studiortcprtmp-proxyrtmp-serverrtprtsprtsp-proxyrtsp-relayrtsp-serversrtstreamingwebrtc-proxy
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
298 lines
4.5 KiB
298 lines
4.5 KiB
// Package handshake contains the RTMP handshake mechanism. |
|
package handshake |
|
|
|
import ( |
|
"bytes" |
|
"fmt" |
|
"io" |
|
) |
|
|
|
const ( |
|
encryptedVersion = 3<<24 | 5<<16 | 1<<8 | 1 |
|
) |
|
|
|
func doClientEncrypted(rw io.ReadWriter) ([]byte, []byte, error) { |
|
var c0 C0S0 |
|
|
|
c0.Version = 6 |
|
|
|
err := c0.Write(rw) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
localPrivateKey, localPublicKey, err := dhGenerateKeyPair() |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
var c1 C1S1 |
|
|
|
c1Digest, err := c1.fill(false, localPublicKey) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
err = c1.Write(rw) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
var s0 C0S0 |
|
|
|
err = s0.Read(rw) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
if s0.Version != 6 { |
|
return nil, nil, fmt.Errorf("server replied with unexpected version %d", s0.Version) |
|
} |
|
|
|
var s1 C1S1 |
|
|
|
err = s1.Read(rw) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
s1Digest, remotePublicKey, err := s1.validate(true) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
var s2 C2S2 |
|
|
|
err = s2.Read(rw) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
err = s2.validate(true, c1Digest) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
var c2 C2S2 |
|
|
|
err = c2.fill(false, s1Digest) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
err = c2.Write(rw) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
sharedSecret := dhComputeSharedSecret(localPrivateKey, remotePublicKey) |
|
keyIn := hmacSha256(sharedSecret, localPublicKey)[:16] |
|
keyOut := hmacSha256(sharedSecret, remotePublicKey)[:16] |
|
return keyIn, keyOut, nil |
|
} |
|
|
|
func doClientPlain(rw io.ReadWriter, strict bool) error { |
|
var c0 C0S0 |
|
|
|
c0.Version = 3 |
|
|
|
err := c0.Write(rw) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
var c1 C1S1 |
|
|
|
err = c1.fillPlain() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
err = c1.Write(rw) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
var s0 C0S0 |
|
|
|
err = s0.Read(rw) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
if s0.Version != 3 { |
|
return fmt.Errorf("server replied with unexpected version %d", s0.Version) |
|
} |
|
|
|
var s1 C1S1 |
|
|
|
err = s1.Read(rw) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
var s2 C2S2 |
|
|
|
err = s2.Read(rw) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
if strict && !bytes.Equal(s2.Data, c1.Data) { |
|
return fmt.Errorf("data in S2 does not correspond") |
|
} |
|
|
|
var c2 C2S2 |
|
|
|
c2.Data = s1.Data |
|
|
|
return c2.Write(rw) |
|
} |
|
|
|
// DoClient performs a client-side handshake. |
|
func DoClient(rw io.ReadWriter, encrypted bool, strict bool) ([]byte, []byte, error) { |
|
if encrypted { |
|
return doClientEncrypted(rw) |
|
} |
|
return nil, nil, doClientPlain(rw, strict) |
|
} |
|
|
|
func doServerEncrypted(rw io.ReadWriter) ([]byte, []byte, error) { |
|
var c1 C1S1 |
|
|
|
err := c1.Read(rw) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
c1Digest, remotePublicKey, err := c1.validate(false) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
localPrivateKey, localPublicKey, err := dhGenerateKeyPair() |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
var s0 C0S0 |
|
|
|
s0.Version = 6 |
|
|
|
err = s0.Write(rw) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
var s1 C1S1 |
|
|
|
s1.Version = encryptedVersion |
|
|
|
s1Digest, err := s1.fill(true, localPublicKey) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
err = s1.Write(rw) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
var s2 C2S2 |
|
|
|
s2.Time2 = encryptedVersion |
|
|
|
err = s2.fill(true, c1Digest) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
err = s2.Write(rw) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
var c2 C2S2 |
|
|
|
err = c2.Read(rw) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
err = c2.validate(false, s1Digest) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
sharedSecret := dhComputeSharedSecret(localPrivateKey, remotePublicKey) |
|
keyIn := hmacSha256(sharedSecret, localPublicKey)[:16] |
|
keyOut := hmacSha256(sharedSecret, remotePublicKey)[:16] |
|
return keyIn, keyOut, nil |
|
} |
|
|
|
func doServerPlain(rw io.ReadWriter, strict bool) error { |
|
var c1 C1S1 |
|
|
|
err := c1.Read(rw) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
var s0 C0S0 |
|
|
|
s0.Version = 3 |
|
|
|
err = s0.Write(rw) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
var s1 C1S1 |
|
|
|
err = s1.fillPlain() |
|
if err != nil { |
|
return err |
|
} |
|
|
|
err = s1.Write(rw) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
var s2 C2S2 |
|
|
|
s2.Data = c1.Data |
|
|
|
err = s2.Write(rw) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
var c2 C2S2 |
|
|
|
err = c2.Read(rw) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
if strict && !bytes.Equal(c2.Data, s1.Data) { |
|
return fmt.Errorf("data in C2 does not correspond") |
|
} |
|
|
|
return nil |
|
} |
|
|
|
// DoServer performs a server-side handshake. |
|
func DoServer(rw io.ReadWriter, strict bool) ([]byte, []byte, error) { |
|
var c0 C0S0 |
|
|
|
err := c0.Read(rw) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
if c0.Version == 6 { |
|
return doServerEncrypted(rw) |
|
} |
|
return nil, nil, doServerPlain(rw, strict) |
|
}
|
|
|