86 lines
2.7 KiB
Go
86 lines
2.7 KiB
Go
package gateway
|
|
|
|
import (
|
|
"crypto/aes"
|
|
"crypto/cipher"
|
|
"encoding/binary"
|
|
"errors"
|
|
)
|
|
|
|
const (
|
|
encryptedRTSPHeaderSize = 24
|
|
encryptedRTSPMaxPayload = 64 << 10
|
|
)
|
|
|
|
var errEncryptedRTSPFrame = errors.New("invalid encrypted RTSP frame")
|
|
|
|
// encryptedRTSPCodec keeps client and host nonce spaces disjoint. It accepts
|
|
// only strictly increasing host sequence numbers, so a replay cannot be fed
|
|
// into the RTSP parser after it has already authenticated once.
|
|
type encryptedRTSPCodec struct {
|
|
aead cipher.AEAD
|
|
nextClient uint32
|
|
lastHost uint32
|
|
hostReceived bool
|
|
}
|
|
|
|
func newEncryptedRTSPCodec(key []byte) (*encryptedRTSPCodec, error) {
|
|
block, err := aes.NewCipher(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
aead, err := cipher.NewGCM(block)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &encryptedRTSPCodec{aead: aead, nextClient: 1}, nil
|
|
}
|
|
|
|
func (codec *encryptedRTSPCodec) SealClient(plaintext []byte) ([]byte, error) {
|
|
if codec == nil || codec.aead == nil || len(plaintext) == 0 || len(plaintext) > encryptedRTSPMaxPayload || codec.nextClient == 0 {
|
|
return nil, errEncryptedRTSPFrame
|
|
}
|
|
sequence := codec.nextClient
|
|
codec.nextClient++
|
|
nonce := encryptedRTSPNonce(sequence, 'C', 'R')
|
|
sealed := codec.aead.Seal(nil, nonce[:], plaintext, nil)
|
|
ciphertext, tag := sealed[:len(plaintext)], sealed[len(plaintext):]
|
|
frame := make([]byte, encryptedRTSPHeaderSize+len(ciphertext))
|
|
binary.BigEndian.PutUint32(frame[:4], uint32(len(ciphertext))|0x80000000)
|
|
binary.BigEndian.PutUint32(frame[4:8], sequence)
|
|
copy(frame[8:24], tag)
|
|
copy(frame[24:], ciphertext)
|
|
return frame, nil
|
|
}
|
|
|
|
func (codec *encryptedRTSPCodec) OpenHost(frame []byte) ([]byte, error) {
|
|
if codec == nil || codec.aead == nil || len(frame) < encryptedRTSPHeaderSize {
|
|
return nil, errEncryptedRTSPFrame
|
|
}
|
|
length := binary.BigEndian.Uint32(frame[:4])
|
|
if length&0x80000000 == 0 || int(length&0x7fffffff) > encryptedRTSPMaxPayload || len(frame) != encryptedRTSPHeaderSize+int(length&0x7fffffff) {
|
|
return nil, errEncryptedRTSPFrame
|
|
}
|
|
sequence := binary.BigEndian.Uint32(frame[4:8])
|
|
if sequence == 0 || (codec.hostReceived && sequence <= codec.lastHost) {
|
|
return nil, errEncryptedRTSPFrame
|
|
}
|
|
nonce := encryptedRTSPNonce(sequence, 'H', 'R')
|
|
sealed := make([]byte, int(length&0x7fffffff)+codec.aead.Overhead())
|
|
copy(sealed, frame[24:])
|
|
copy(sealed[length&0x7fffffff:], frame[8:24])
|
|
plaintext, err := codec.aead.Open(nil, nonce[:], sealed, nil)
|
|
if err != nil {
|
|
return nil, errEncryptedRTSPFrame
|
|
}
|
|
codec.lastHost, codec.hostReceived = sequence, true
|
|
return plaintext, nil
|
|
}
|
|
|
|
func encryptedRTSPNonce(sequence uint32, origin, protocol byte) [12]byte {
|
|
var nonce [12]byte
|
|
binary.BigEndian.PutUint32(nonce[:4], sequence)
|
|
nonce[10], nonce[11] = origin, protocol
|
|
return nonce
|
|
}
|