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.
189 lines
4.3 KiB
189 lines
4.3 KiB
package handshake |
|
|
|
import ( |
|
"bytes" |
|
"crypto/hmac" |
|
"crypto/rand" |
|
"crypto/sha256" |
|
"fmt" |
|
"io" |
|
) |
|
|
|
const ( |
|
c1s1Size = 1536 |
|
digestPointerPos1 = 0 |
|
digestPointerPos2 = 772 - 8 |
|
digestChunkPos1 = digestPointerPos1 + 4 |
|
digestChunkPos2 = digestPointerPos2 + 4 |
|
digestChunkLength = 728 |
|
digestLength = 32 |
|
publicKeyPointerPos1 = 1532 - 8 |
|
publicKeyPointerPos2 = 768 - 8 |
|
publicKeyChunkPos1 = publicKeyPointerPos1 - 760 |
|
publicKeyChunkPos2 = publicKeyPointerPos2 - 760 |
|
publicKeyChunkLength = 632 |
|
) |
|
|
|
var ( |
|
clientKeyC1 = []byte("Genuine Adobe Flash Player 001") |
|
serverKeyS1 = []byte("Genuine Adobe Flash Media Server 001") |
|
) |
|
|
|
func hmacSha256(key []byte, buf []byte) []byte { |
|
h := hmac.New(sha256.New, key) |
|
h.Write(buf) |
|
return h.Sum(nil) |
|
} |
|
|
|
// C1S1 is a C1 or S1 packet. |
|
type C1S1 struct { |
|
Time uint32 |
|
Version uint32 |
|
Data []byte |
|
} |
|
|
|
// Read reads a C1S1. |
|
func (c *C1S1) Read(r io.Reader) error { |
|
buf := make([]byte, c1s1Size) |
|
_, err := io.ReadFull(r, buf) |
|
if err != nil { |
|
return err |
|
} |
|
|
|
c.Time = uint32(buf[0])<<24 | uint32(buf[1])<<16 | uint32(buf[2])<<8 | uint32(buf[3]) |
|
c.Version = uint32(buf[4])<<24 | uint32(buf[5])<<16 | uint32(buf[6])<<8 | uint32(buf[7]) |
|
c.Data = buf[8:] |
|
|
|
return nil |
|
} |
|
|
|
func (c C1S1) readPointer(p int) int { |
|
return int(c.Data[p]) + int(c.Data[p+1]) + int(c.Data[p+2]) + int(c.Data[p+3]) |
|
} |
|
|
|
func (c C1S1) publicKeyPos1() int { |
|
return publicKeyChunkPos1 + (c.readPointer(publicKeyPointerPos1) % publicKeyChunkLength) |
|
} |
|
|
|
func (c C1S1) publicKeyPos2() int { |
|
return publicKeyChunkPos2 + (c.readPointer(publicKeyPointerPos2) % publicKeyChunkLength) |
|
} |
|
|
|
func (c C1S1) digestPos1() int { |
|
return digestChunkPos1 + (c.readPointer(digestPointerPos1) % digestChunkLength) |
|
} |
|
|
|
func (c C1S1) digestPos2() int { |
|
return digestChunkPos2 + (c.readPointer(digestPointerPos2) % digestChunkLength) |
|
} |
|
|
|
func (c C1S1) computeDigest(digestPos int, isS1 bool) []byte { |
|
// hash entire message except digest |
|
msg := make([]byte, c1s1Size-digestLength) |
|
msg[0] = byte(c.Time >> 24) |
|
msg[1] = byte(c.Time >> 16) |
|
msg[2] = byte(c.Time >> 8) |
|
msg[3] = byte(c.Time) |
|
msg[4] = byte(c.Version >> 24) |
|
msg[5] = byte(c.Version >> 16) |
|
msg[6] = byte(c.Version >> 8) |
|
msg[7] = byte(c.Version) |
|
copy(msg[8:], c.Data[:digestPos]) |
|
copy(msg[8+digestPos:], c.Data[digestPos+digestLength:]) |
|
|
|
if isS1 { |
|
return hmacSha256(serverKeyS1, msg) |
|
} |
|
return hmacSha256(clientKeyC1, msg) |
|
} |
|
|
|
func (c C1S1) validateDigest(isS1 bool) ([]byte, []byte, error) { |
|
digestPos := c.digestPos1() |
|
d1 := c.Data[digestPos : digestPos+digestLength] |
|
d2 := c.computeDigest(digestPos, isS1) |
|
|
|
if bytes.Equal(d1, d2) { |
|
publicKeyPos := c.publicKeyPos1() |
|
publicKey := c.Data[publicKeyPos : publicKeyPos+dhKeyLength] |
|
return d1, publicKey, nil |
|
} |
|
|
|
digestPos = c.digestPos2() |
|
d1 = c.Data[digestPos : digestPos+digestLength] |
|
d2 = c.computeDigest(digestPos, isS1) |
|
|
|
if bytes.Equal(d1, d2) { |
|
publicKeyPos := c.publicKeyPos2() |
|
publicKey := c.Data[publicKeyPos : publicKeyPos+dhKeyLength] |
|
return d1, publicKey, nil |
|
} |
|
|
|
return nil, nil, fmt.Errorf("unable to validate C1/S1 digest") |
|
} |
|
|
|
func (c C1S1) validate(isS1 bool) ([]byte, []byte, error) { |
|
digest, publicKey, err := c.validateDigest(isS1) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
err = dhValidatePublicKey(publicKey) |
|
if err != nil { |
|
return nil, nil, err |
|
} |
|
|
|
return digest, publicKey, nil |
|
} |
|
|
|
func (c *C1S1) fillPlain() error { |
|
c.Data = make([]byte, c1s1Size-8) |
|
_, err := rand.Read(c.Data) |
|
return err |
|
} |
|
|
|
func (c *C1S1) fill(isS1 bool, publicKey []byte) ([]byte, error) { |
|
err := c.fillPlain() |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var r [1]byte |
|
_, err = rand.Read(r[:]) |
|
if err != nil { |
|
return nil, err |
|
} |
|
|
|
var digestPos int |
|
var publicKeyPos int |
|
|
|
if r[0] == 0 { |
|
digestPos = c.digestPos1() |
|
publicKeyPos = c.publicKeyPos1() |
|
} else { |
|
digestPos = c.digestPos2() |
|
publicKeyPos = c.publicKeyPos2() |
|
} |
|
|
|
copy(c.Data[publicKeyPos:], publicKey) |
|
digest := c.computeDigest(digestPos, isS1) |
|
copy(c.Data[digestPos:], digest) |
|
return digest, nil |
|
} |
|
|
|
// Write writes a C1S1. |
|
func (c C1S1) Write(w io.Writer) error { |
|
buf := make([]byte, c1s1Size) |
|
|
|
buf[0] = byte(c.Time >> 24) |
|
buf[1] = byte(c.Time >> 16) |
|
buf[2] = byte(c.Time >> 8) |
|
buf[3] = byte(c.Time) |
|
buf[4] = byte(c.Version >> 24) |
|
buf[5] = byte(c.Version >> 16) |
|
buf[6] = byte(c.Version >> 8) |
|
buf[7] = byte(c.Version) |
|
copy(buf[8:], c.Data) |
|
|
|
_, err := w.Write(buf) |
|
return err |
|
}
|
|
|