TLSSig::setAppid()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
ccs 0
cts 4
cp 0
crap 2
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
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);
0 ignored issues
show
Documentation Bug introduced by
It seems like openssl_pkey_get_private($private_key) of type resource is incompatible with the declared type boolean of property $private_key.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
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);
0 ignored issues
show
Documentation Bug introduced by
It seems like openssl_pkey_get_public($public_key) of type resource is incompatible with the declared type boolean of property $public_key.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
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 View Code Duplication
    private function base64Encode($string)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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 View Code Duplication
    private function base64Decode($base64)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
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')) {
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');
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, 单位秒
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 TLSSig::genSignContent() has too many arguments starting with $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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
168
        $signature = $this->sign($content, $err);
0 ignored issues
show
Unused Code introduced by
The call to TLSSig::sign() has too many arguments starting with $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.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
169
        $json['TLS.sig'] = base64_encode($signature);
170
        if ($json['TLS.sig'] === false) {
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