Passed
Pull Request — master (#262)
by Pascal
02:31
created

EncryptsHLSSegments::rotateEncryptionKey()   A

Complexity

Conditions 5
Paths 16

Size

Total Lines 35
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 18
nc 16
nop 0
dl 0
loc 35
rs 9.3554
c 1
b 0
f 0
1
<?php
2
3
namespace ProtoneMedia\LaravelFFMpeg\Exporters;
4
5
use Closure;
6
use Illuminate\Support\Str;
7
use ProtoneMedia\LaravelFFMpeg\FFMpeg\StdListener;
8
use ProtoneMedia\LaravelFFMpeg\Filesystem\Disk;
9
10
trait EncryptsHLSSegments
11
{
12
    /**
13
     * The encryption key.
14
     *
15
     * @var string
16
     */
17
    private $encryptionKey;
18
19
    private $newEncryptionKeyCallback = null;
20
21
    private $encryptionKeyDisk      = null;
22
    private $encryptionKeyName      = null;
23
    private $encryptionIV           = null;
24
    private $rotatingEncryptiongKey = false;
25
    private $segmentsOpenend        = 0;
26
27
    public function onEncryptionKey(Closure $callback): self
28
    {
29
        $this->newEncryptionKeyCallback = $callback;
30
31
        return $this;
32
    }
33
34
    /**
35
     * Creates a new encryption key.
36
     *
37
     * @return string
38
     */
39
    public static function generateEncryptionKey(): string
40
    {
41
        return random_bytes(16);
42
    }
43
44
    /**
45
     * Sets the encryption key with the given value or generates a new one.
46
     *
47
     * @param string $key
48
     * @return self
49
     */
50
    public function withEncryptionKey($key = null): self
51
    {
52
        $this->encryptionKey = $key ?: static::generateEncryptionKey();
53
54
        return $this;
55
    }
56
57
    public function withRotatingEncryptionKey(): self
58
    {
59
        $this->withEncryptionKey();
60
61
        $this->rotatingEncryptiongKey = true;
62
63
        return $this;
64
    }
65
66
    private function rotateEncryptionKey()
67
    {
68
        $this->withEncryptionKey();
69
70
        if (!$this->encryptionKeyDisk) {
71
            $this->encryptionKeyDisk = Disk::makeTemporaryDisk();
72
        }
73
74
        if (!$this->encryptionKeyName) {
75
            $this->encryptionKeyName = Str::random(8);
76
        }
77
78
        if (!$this->encryptionIV) {
79
            $this->encryptionIV = bin2hex(static::generateEncryptionKey());
80
        }
81
82
        $keyInfoPath = $this->encryptionKeyDisk
83
            ->makeMedia("{$this->encryptionKeyName}.keyinfo")
84
            ->getLocalPath();
85
86
        $name = $this->encryptionKeyName . "_" . Str::random(8);
87
88
        $keyPath = $this->encryptionKeyDisk->makeMedia("{$name}.key")->getLocalPath();
89
90
        file_put_contents($keyPath, $this->encryptionKey);
91
92
        file_put_contents($keyInfoPath, implode(PHP_EOL, [
93
            $keyPath, $keyPath, $this->encryptionIV,
94
        ]));
95
96
        if ($this->newEncryptionKeyCallback) {
97
            call_user_func($this->newEncryptionKeyCallback, "{$name}.key", $this->encryptionKey);
98
        }
99
100
        return $keyInfoPath;
101
    }
102
103
    private function getEncrypedHLSParameters(): array
104
    {
105
        if (!$this->encryptionKey) {
106
            return [];
107
        }
108
109
        $hlsParameters = [
110
            '-hls_key_info_file',
111
            $this->rotateEncryptionKey(),
112
        ];
113
114
        if ($this->rotatingEncryptiongKey) {
115
            $hlsParameters[] = '-hls_flags';
116
            $hlsParameters[] = 'periodic_rekey';
117
        }
118
119
        return $hlsParameters;
120
    }
121
122
    private function addRotatingKeyListener()
123
    {
124
        if (!$this->rotatingEncryptiongKey) {
125
            return;
126
        }
127
128
        $this->addListener(new StdListener)->onEvent('listen', function ($line) {
0 ignored issues
show
Bug introduced by
It seems like addListener() must be provided by classes using this trait. How about adding it as abstract method to this trait? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

128
        $this->/** @scrutinizer ignore-call */ 
129
               addListener(new StdListener)->onEvent('listen', function ($line) {
Loading history...
129
            if (!(Str::contains($line, "Opening 'crypto:/") && Str::contains($line, ".ts' for writing"))) {
130
                return;
131
            }
132
133
            $this->segmentsOpenend++;
134
            $this->rotateEncryptionKey();
135
        });
136
    }
137
}
138