1<?php
2/**
3 * PrivateBin
4 *
5 * a zero-knowledge paste bin
6 *
7 * @link      https://github.com/PrivateBin/PrivateBin
8 * @copyright 2012 Sébastien SAUVAGE (sebsauvage.net)
9 * @license   https://www.opensource.org/licenses/zlib-license.php The zlib/libpng License
10 * @version   1.3.5
11 */
12
13namespace PrivateBin;
14
15/**
16 * FormatV2
17 *
18 * Provides validation function for version 2 format of pastes & comments.
19 */
20class FormatV2
21{
22    /**
23     * version 2 format validator
24     *
25     * Checks if the given array is a proper version 2 formatted, encrypted message.
26     *
27     * @access public
28     * @static
29     * @param  array $message
30     * @param  bool  $isComment
31     * @return bool
32     */
33    public static function isValid($message, $isComment = false)
34    {
35        $required_keys = array('adata', 'v', 'ct');
36        if ($isComment) {
37            $required_keys[] = 'pasteid';
38            $required_keys[] = 'parentid';
39        } else {
40            $required_keys[] = 'meta';
41        }
42
43        // Make sure no additionnal keys were added.
44        if (count(array_keys($message)) != count($required_keys)) {
45            return false;
46        }
47
48        // Make sure required fields are present.
49        foreach ($required_keys as $k) {
50            if (!array_key_exists($k, $message)) {
51                return false;
52            }
53        }
54
55        $cipherParams = $isComment ? $message['adata'] : $message['adata'][0];
56
57        // Make sure some fields are base64 data:
58        // - initialization vector
59        if (!base64_decode($cipherParams[0], true)) {
60            return false;
61        }
62        // - salt
63        if (!base64_decode($cipherParams[1], true)) {
64            return false;
65        }
66        // - cipher text
67        if (!($ct = base64_decode($message['ct'], true))) {
68            return false;
69        }
70
71        // Make sure some fields have a reasonable size:
72        // - initialization vector
73        if (strlen($cipherParams[0]) > 24) {
74            return false;
75        }
76        // - salt
77        if (strlen($cipherParams[1]) > 14) {
78            return false;
79        }
80
81        // Make sure some fields contain no unsupported values:
82        // - version
83        if (!(is_int($message['v']) || is_float($message['v'])) || (float) $message['v'] < 2) {
84            return false;
85        }
86        // - iterations, refuse less then 10000 iterations (minimum NIST recommendation)
87        if (!is_int($cipherParams[2]) || $cipherParams[2] <= 10000) {
88            return false;
89        }
90        // - key size
91        if (!in_array($cipherParams[3], array(128, 192, 256), true)) {
92            return false;
93        }
94        // - tag size
95        if (!in_array($cipherParams[4], array(64, 96, 128), true)) {
96            return false;
97        }
98        // - algorithm, must be AES
99        if ($cipherParams[5] !== 'aes') {
100            return false;
101        }
102        // - mode
103        if (!in_array($cipherParams[6], array('ctr', 'cbc', 'gcm'), true)) {
104            return false;
105        }
106        // - compression
107        if (!in_array($cipherParams[7], array('zlib', 'none'), true)) {
108            return false;
109        }
110
111        // Reject data if entropy is too low
112        if (strlen($ct) > strlen(gzdeflate($ct))) {
113            return false;
114        }
115
116        // require only the key 'expire' in the metadata of pastes
117        if (!$isComment && (
118            count($message['meta']) === 0 ||
119            !array_key_exists('expire', $message['meta']) ||
120            count($message['meta']) > 1
121        )) {
122            return false;
123        }
124
125        return true;
126    }
127}
128