Completed
Push — master ( b8ee44...86e435 )
by Jim
02:27
created

TLSSig::verifySig()   B

Complexity

Conditions 8
Paths 17

Size

Total Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 35
rs 8.1155
c 0
b 0
f 0
cc 8
nc 17
nop 5
1
<?php
2
/**
3
 * Created by PhpStorm.
4
 * User: lenovo
5
 * Date: 6/12/2018
6
 * Time: 10:31 AM
7
 */
8
9
namespace TimSDK\Core;
10
11
use TimSDK\Core\Exceptions\UserSigException;
12
use TimSDK\Core\Exceptions\OpensslException;
13
14
/**
15
 * Class TLSSigApi
16
 * @package TimSDK\Service
17
 */
18
class TLSSig
19
{
20
    private $private_key = false;
21
    private $public_key = false;
22
    private $appid;
23
24
    /**
25
     * 设置Appid
26
     * @param type $appid
0 ignored issues
show
Bug introduced by
The type TimSDK\Core\type was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
27
     */
28
    public function setAppid($appid)
29
    {
30
        $this->appid = $appid;
31
    }
32
33
    /**
34
     * 设置私钥 如果要生成usersig则需要私钥
35
     * @param string $private_key 私钥文件内容
36
     * @return bool 是否成功
37
     */
38
    public function setPrivateKey($private_key)
39
    {
40
        $this->private_key = openssl_pkey_get_private($private_key);
41
        if ($this->private_key === false) {
42
            throw new OpensslException(openssl_error_string());
43
        }
44
        return true;
45
    }
46
47
    /**
48
     * 设置公钥 如果要验证usersig则需要公钥
49
     * @param string $public_key 公钥文件内容
50
     * @return bool 是否成功
51
     */
52
    public function setPublicKey($public_key)
53
    {
54
        $this->public_key = openssl_pkey_get_public($public_key);
55
        if ($this->public_key === false) {
56
            throw new OpensslException(openssl_error_string());
57
        }
58
        return true;
59
    }
60
61
    /**
62
     * 用于url的base64encode
63
     * '+' => '*', '/' => '-', '=' => '_'
64
     * @param string $string 需要编码的数据
65
     * @return string 编码后的base64串,失败返回false
66
     */
67
    private function base64Encode($string)
68
    {
69
        static $replace = array('+' => '*', '/' => '-', '=' => '_');
70
        $base64 = base64_encode($string);
71
        if ($base64 === false) {
72
            throw new OpensslException('base64_encode error');
73
        }
74
        return str_replace(array_keys($replace), array_values($replace), $base64);
75
    }
76
77
    /**
78
     * 用于url的base64decode
79
     * '+' => '*', '/' => '-', '=' => '_'
80
     * @param string $base64 需要解码的base64串
81
     * @return string 解码后的数据,失败返回false
82
     */
83
    private function base64Decode($base64)
84
    {
85
        static $replace = array('+' => '*', '/' => '-', '=' => '_');
86
        $string = str_replace(array_values($replace), array_keys($replace), $base64);
87
        $result = base64_decode($string);
88
        if ($result == false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $result of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
89
            throw new OpensslException('base64_decode error');
90
        }
91
        return $result;
92
    }
93
94
    /**
95
     * 根据json内容生成需要签名的buf串
96
     * @param array $json 票据json对象
97
     * @return string 按标准格式生成的用于签名的字符串
98
     * 失败时返回false
99
     */
100
    private function genSignContent(array $json)
101
    {
102
        static $members = array(
103
            'TLS.appid_at_3rd',
104
            'TLS.account_type',
105
            'TLS.identifier',
106
            'TLS.sdk_appid',
107
            'TLS.time',
108
            'TLS.expire_after'
109
        );
110
        $content = '';
111
        foreach ($members as $member) {
112
            if (!isset($json[$member])) {
113
                throw new OpensslException('json need ' . $member);
114
            }
115
            $content .= "{$member}:{$json[$member]}\n";
116
        }
117
        return $content;
118
    }
119
120
    /**
121
     * ECDSA-SHA256签名
122
     * @param string $data 需要签名的数据
123
     * @return string 返回签名 失败时返回false
124
     */
125
    private function sign($data)
126
    {
127
        $signature = '';
128
        if (!openssl_sign($data, $signature, $this->private_key, 'sha256')) {
0 ignored issues
show
Bug introduced by
'sha256' of type string is incompatible with the type integer expected by parameter $signature_alg of openssl_sign(). ( Ignorable by Annotation )

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

128
        if (!openssl_sign($data, $signature, $this->private_key, /** @scrutinizer ignore-type */ 'sha256')) {
Loading history...
129
            throw new OpensslException(openssl_error_string());
130
        }
131
        return $signature;
132
    }
133
134
    /**
135
     * 验证ECDSA-SHA256签名
136
     * @param string $data 需要验证的数据原文
137
     * @param string $sig 需要验证的签名
138
     * @return int 1验证成功 0验证失败
139
     */
140
    private function verify($data, $sig)
141
    {
142
        $ret = openssl_verify($data, $sig, $this->public_key, 'sha256');
0 ignored issues
show
Bug introduced by
'sha256' of type string is incompatible with the type integer expected by parameter $signature_alg of openssl_verify(). ( Ignorable by Annotation )

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

142
        $ret = openssl_verify($data, $sig, $this->public_key, /** @scrutinizer ignore-type */ 'sha256');
Loading history...
143
        if ($ret == -1) {
144
            throw new OpensslException(openssl_error_string());
145
        }
146
        return $ret;
147
    }
148
149
    /**
150
     * 生成usersig
151
     * @param string $identifier 用户名
152
     * @param uint $expire usersig有效期 默认为180天, 180 * 24 * 3600, 单位秒
0 ignored issues
show
Bug introduced by
The type TimSDK\Core\uint was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
153
     * @return string 生成的UserSig 失败时为false
154
     */
155
    public function genSig($identifier, $expire = 15552000)
156
    {
157
        $json = array(
158
            'TLS.account_type' => '0',
159
            'TLS.identifier' => (string) $identifier,
160
            'TLS.appid_at_3rd' => '0',
161
            'TLS.sdk_appid' => (string) $this->appid,
162
            'TLS.expire_after' => (string) $expire,
163
            'TLS.version' => '201512300000',
164
            'TLS.time' => (string) time()
165
        );
166
        $err = '';
167
        $content = $this->genSignContent($json, $err);
0 ignored issues
show
Unused Code introduced by
The call to TimSDK\Core\TLSSig::genSignContent() has too many arguments starting with $err. ( Ignorable by Annotation )

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

167
        /** @scrutinizer ignore-call */ 
168
        $content = $this->genSignContent($json, $err);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
168
        $signature = $this->sign($content, $err);
0 ignored issues
show
Unused Code introduced by
The call to TimSDK\Core\TLSSig::sign() has too many arguments starting with $err. ( Ignorable by Annotation )

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

168
        /** @scrutinizer ignore-call */ 
169
        $signature = $this->sign($content, $err);

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
169
        $json['TLS.sig'] = base64_encode($signature);
170
        if ($json['TLS.sig'] === false) {
0 ignored issues
show
introduced by
The condition $json['TLS.sig'] === false is always false.
Loading history...
171
            throw new UserSigException('base64_encode error');
172
        }
173
        $json_text = json_encode($json);
174
        if ($json_text === false) {
175
            throw new UserSigException('json_encode error');
176
        }
177
        $compressed = gzcompress($json_text);
178
        if ($compressed === false) {
179
            throw new UserSigException('gzcompress error');
180
        }
181
        return $this->base64Encode($compressed);
182
    }
183
184
    /**
185
     * 验证usersig
186
     * @param type $sig usersig
187
     * @param type $identifier 需要验证用户名
188
     * @param type $init_time usersig中的生成时间
189
     * @param type $expire_time usersig中的有效期 如:3600秒
190
     * @param type $error_msg 失败时的错误信息
191
     * @return boolean 验证是否成功
192
     */
193
    public function verifySig($sig, $identifier, &$init_time, &$expire_time, &$error_msg)
194
    {
195
        try {
196
            $error_msg = '';
197
            $decoded_sig = $this->base64Decode($sig);
198
            $uncompressed_sig = gzuncompress($decoded_sig);
199
            if ($uncompressed_sig === false) {
200
                throw new UserSigException('gzuncompress error');
201
            }
202
            $json = json_decode($uncompressed_sig);
203
            if ($json == false) {
204
                throw new UserSigException('json_decode error');
205
            }
206
            $json = (array) $json;
207
            if ($json['TLS.identifier'] !== $identifier) {
208
                throw new UserSigException("identifier error sigid:{$json['TLS.identifier']} id:{$identifier}");
209
            }
210
            if ($json['TLS.sdk_appid'] != $this->appid) {
211
                throw new UserSigException("appid error sigappid:{$json['TLS.appid']} thisappid:{$this->appid}");
212
            }
213
            $content = $this->genSignContent($json);
214
            $signature = base64_decode($json['TLS.sig']);
215
            if ($signature == false) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $signature of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
216
                throw new UserSigException('sig json_decode error');
217
            }
218
            $succ = $this->verify($content, $signature);
219
            if (!$succ) {
220
                throw new UserSigException('verify failed');
221
            }
222
            $init_time = $json['TLS.time'];
223
            $expire_time = $json['TLS.expire_after'];
224
            return true;
225
        } catch (UserSigException $ex) {
226
            $error_msg = $ex->getMessage();
227
            return false;
228
        }
229
    }
230
}
231