mirror of
https://github.com/hanzoai/node-jsonwebtoken.git
synced 2026-09-24 17:00:48 +00:00
initial
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
@@ -0,0 +1,4 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- 0.8
|
||||
- 0.10
|
||||
@@ -0,0 +1,121 @@
|
||||
# node-jws [](http://travis-ci.org/auth0/node-jsonwebtoken)
|
||||
|
||||
|
||||
An implementation of [JSON Web Tokens](http://self-issued.info/docs/draft-ietf-oauth-json-web-token.html).
|
||||
|
||||
This was developed against `draft-ietf-oauth-json-web-token-08`. It makes use of [node-jws](https://github.com/brianloveswords/node-jws)
|
||||
|
||||
# Install
|
||||
|
||||
```bash
|
||||
$ npm install jsonwebtoken
|
||||
```
|
||||
|
||||
# Usage
|
||||
|
||||
## jwt.sign(payload, secretOrPrivateKey, options)
|
||||
|
||||
(Synchronous) Returns the JsonWebToken as string
|
||||
|
||||
`payload` could be an literal, buffer or string
|
||||
|
||||
`secretOrPrivateKey` is a string or buffer containing either the secret for HMAC algorithms, or the PEM
|
||||
encoded private key for RSA and ECDSA.
|
||||
|
||||
`options`:
|
||||
|
||||
* `algorithm` (default: `HS256`)
|
||||
* `expiresInMinutes`
|
||||
* `audience`
|
||||
* `subject`
|
||||
* `issuer`
|
||||
|
||||
If `payload` is not a buffer or a string, it will be coerced into a string
|
||||
using `JSON.stringify`.
|
||||
|
||||
If any `expiresInMinutes`, `audience`, `subject`, `issuer` are not provided, there is no default. The jwt generated won't include those properties in the payload.
|
||||
|
||||
Example
|
||||
|
||||
```js
|
||||
// sign with default (HMAC SHA256)
|
||||
var token = jwt.sign({ foo: 'bar' }, 'shhhhh');
|
||||
|
||||
// sign with RSA SHA256
|
||||
var cert = fs.readFileSync('private.key'); // get private key
|
||||
var token = jwt.sign({ foo: 'bar' }, cert, { algorithm: 'RS256'});
|
||||
```
|
||||
|
||||
## jwt.verify(token, secretOrPublicKey, options, callback)
|
||||
|
||||
(Synchronous with callback) Returns the payload decoded if the signature (and optionally expiration, audience, issuer) are valid. If not, it will return the error.
|
||||
|
||||
`token` is the JsonWebToken string
|
||||
|
||||
`secretOrPublicKey` is a string or buffer containing either the secret for HMAC algorithms, or the PEM
|
||||
encoded public key for RSA and ECDSA.
|
||||
|
||||
`options`
|
||||
|
||||
* `audience`: if you want to check audience (`aud`), provide a value here
|
||||
* `issuer`: if you want to check issuer (`iss`), provide a value here
|
||||
|
||||
```js
|
||||
// verify a token symmetric
|
||||
jwt.verify(token, 'shhhhh', function(err, decoded) {
|
||||
console.log(decoded.foo) // bar
|
||||
});
|
||||
|
||||
// invalid token
|
||||
jwt.verify(token, 'wrong-secret', function(err, decoded) {
|
||||
// err
|
||||
// decoded undefined
|
||||
});
|
||||
|
||||
// verify a token asymmetric
|
||||
var cert = fs.readFileSync('public.pem'); // get public key
|
||||
jwt.verify(token, cert, function(err, decoded) {
|
||||
console.log(decoded.foo) // bar
|
||||
});
|
||||
|
||||
// verify audience
|
||||
var cert = fs.readFileSync('public.pem'); // get public key
|
||||
jwt.verify(token, cert, { audience: 'urn:foo' }, function(err, decoded) {
|
||||
// if audience mismatch, err == invalid audience
|
||||
});
|
||||
|
||||
// verify issuer
|
||||
var cert = fs.readFileSync('public.pem'); // get public key
|
||||
jwt.verify(token, cert, { audience: 'urn:foo', issuer: 'urn:issuer' }, function(err, decoded) {
|
||||
// if issuer mismatch, err == invalid issuer
|
||||
});
|
||||
|
||||
```
|
||||
|
||||
## Algorithms supported
|
||||
|
||||
Array of supported algorithms. The following algorithms are currently supported.
|
||||
|
||||
alg Parameter Value | Digital Signature or MAC Algorithm
|
||||
----------------|----------------------------
|
||||
HS256 | HMAC using SHA-256 hash algorithm
|
||||
HS384 | HMAC using SHA-384 hash algorithm
|
||||
HS512 | HMAC using SHA-512 hash algorithm
|
||||
RS256 | RSASSA using SHA-256 hash algorithm
|
||||
RS384 | RSASSA using SHA-384 hash algorithm
|
||||
RS512 | RSASSA using SHA-512 hash algorithm
|
||||
ES256 | ECDSA using P-256 curve and SHA-256 hash algorithm
|
||||
ES384 | ECDSA using P-384 curve and SHA-384 hash algorithm
|
||||
ES512 | ECDSA using P-521 curve and SHA-512 hash algorithm
|
||||
none | No digital signature or MAC value included
|
||||
|
||||
|
||||
|
||||
# TODO
|
||||
|
||||
* X.509 certificate chain is not checked
|
||||
|
||||
# License
|
||||
|
||||
MIT
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
var jws = require('jws');
|
||||
var moment = require('moment');
|
||||
|
||||
module.exports.sign = function(payload, secretOrPrivateKey, options) {
|
||||
options = options || {};
|
||||
|
||||
var header = {typ: 'JWT', alg: options.algorithm || 'HS256'};
|
||||
if (options.expiresInMinutes)
|
||||
payload.exp = moment().add('minutes', options.expiresInMinutes).utc().unix();
|
||||
|
||||
if (options.audience)
|
||||
payload.aud = options.audience;
|
||||
|
||||
if (options.issuer)
|
||||
payload.iss = options.issuer;
|
||||
|
||||
if (options.subject)
|
||||
payload.sub = options.subject;
|
||||
|
||||
payload.iat = moment().utc().unix();
|
||||
|
||||
var signed = jws.sign({header: header, payload: payload, secret: secretOrPrivateKey});
|
||||
|
||||
return signed;
|
||||
};
|
||||
|
||||
module.exports.verify = function(jwtString, secretOrPublicKey, options, callback) {
|
||||
if ((typeof options === 'function') && !callback) callback = options;
|
||||
if (!options) options = {};
|
||||
|
||||
var valid;
|
||||
try {
|
||||
valid = jws.verify(jwtString, secretOrPublicKey);
|
||||
}
|
||||
catch (e) {
|
||||
return callback(e);
|
||||
}
|
||||
|
||||
if (!valid)
|
||||
return callback(new Error('invalid signature'));
|
||||
|
||||
var jwt = jws.decode(jwtString);
|
||||
|
||||
if (jwt.payload.exp) {
|
||||
if (moment().utc().unix() >= jwt.payload.exp)
|
||||
return callback(new Error('jwt expired'));
|
||||
}
|
||||
|
||||
if (jwt.payload.aud && options.audience) {
|
||||
if (jwt.payload.aud !== options.audience)
|
||||
return callback(new Error('jwt audience invalid. expected: ' + jwt.payload.aud));
|
||||
}
|
||||
|
||||
if (jwt.payload.iss && options.issuer) {
|
||||
if (jwt.payload.iss !== options.issuer)
|
||||
return callback(new Error('jwt issuer invalid. expected: ' + jwt.payload.iss));
|
||||
}
|
||||
|
||||
callback(null, jwt.payload);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "node-jsonwebtoken",
|
||||
"version": "0.1.0",
|
||||
"description": "JSON Web Token implementation (symmetric and asymmetric)",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "mocha"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/auth0/node-jsonwebtoken"
|
||||
},
|
||||
"keywords": [
|
||||
"jwt"
|
||||
],
|
||||
"author": "auth0",
|
||||
"license": "MIT",
|
||||
"bugs": {
|
||||
"url": "https://github.com/auth0/node-jsonwebtoken/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"jws": "~0.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDJjCCAg6gAwIBAgIJAMyz3mSPlaW4MA0GCSqGSIb3DQEBBQUAMBYxFDASBgNV
|
||||
BAMUCyouYXV0aDAuY29tMB4XDTEzMDQxODE3MDE1MFoXDTI2MTIyNjE3MDE1MFow
|
||||
FjEUMBIGA1UEAxQLKi5hdXRoMC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQDZq1Ua0/BGm+TaBFoftKWeYMWrQG9Fx3g7ikErxljmyOvlwqkiat3q
|
||||
ixX+Dxw9TFb5gbBjNJ+L3nt4YefJgLsYvsHqkOUxWsB+HM/ulJRVnVrZm1tI3Nbg
|
||||
xO1BQ7DrGfBpq2KCxtQCaQFRlQJw1+qS5LwrdIvihB7Kc142VElCFFHJ6+09eMUy
|
||||
jy00Z5pfQr4Am6W6eEOS9ObDbNs4XgKOcWe5khWXj3UStou+VgbAg40XcYht2IbY
|
||||
gMfKF+VUZOy3+e+aRTqPOBU3MAeb0tvCCPUQJbNAUHgSKVhAvNf8mRwttVsOLT70
|
||||
anjjeCOd7RKS8fVKBwc2KtgNkghYdPY9AgMBAAGjdzB1MB0GA1UdDgQWBBSi4+X0
|
||||
+MvCKDdd375mDhx/ZBbJ4DBGBgNVHSMEPzA9gBSi4+X0+MvCKDdd375mDhx/ZBbJ
|
||||
4KEapBgwFjEUMBIGA1UEAxQLKi5hdXRoMC5jb22CCQDMs95kj5WluDAMBgNVHRME
|
||||
BTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQBi0qPe0DzlPSufq+Gdk2Fwf1pGEtjA
|
||||
D34IxxJ9SX6r1DS/NIP7IOLUnNU8cP8BQWl7i413v29jJsNV457pjdmqf8J7OE9O
|
||||
eF5Yz1x91gY/27561Iga/TQeIVOlFQAgx66eLfUFFoAig3hz2srZo5TzYBixMJsS
|
||||
fYMXHPiU7KoLUqYXvpSXIllstQCu51KCC6t9H7wZ92lTES1v76hFY4edQ30sftPo
|
||||
kjAYWGEhMjPo/r4THcdSMqKXoRtCGEun4pTXid7MJcTgdGDrAJddLWi6SxKecEVB
|
||||
MhMu4XfUCdxCwqQPjHeJ+zE49A1CUdBB2FN3BNLbmTTwEBgmuwyGRzhj
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,35 @@
|
||||
var jwt = require('../index');
|
||||
|
||||
var expect = require('chai').expect;
|
||||
var assert = require('chai').assert;
|
||||
|
||||
describe('HS256', function() {
|
||||
|
||||
describe('when signing a token', function() {
|
||||
var secret = 'shhhhhh';
|
||||
|
||||
var token = jwt.sign({ foo: 'bar' }, secret, { algorithm: 'HS256' });
|
||||
|
||||
it('should be syntactically valid', function() {
|
||||
expect(token).to.be.a('string');
|
||||
expect(token.split('.')).to.have.length(3);
|
||||
});
|
||||
|
||||
it('should validate with secret', function(done) {
|
||||
jwt.verify(token, secret, function(err, decoded) {
|
||||
assert.ok(decoded.foo);
|
||||
assert.equal('bar', decoded.foo);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw with invalid secret', function(done) {
|
||||
jwt.verify(token, 'invalid secret', function(err, decoded) {
|
||||
assert.isUndefined(decoded);
|
||||
assert.isNotNull(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,115 @@
|
||||
var jwt = require('../index');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var expect = require('chai').expect;
|
||||
var assert = require('chai').assert;
|
||||
|
||||
describe('RS256', function() {
|
||||
var pub = fs.readFileSync(path.join(__dirname, 'pub.pem'));
|
||||
var priv = fs.readFileSync(path.join(__dirname, 'priv.pem'));
|
||||
var invalid_pub = fs.readFileSync(path.join(__dirname, 'invalid_pub.pem'));
|
||||
|
||||
describe('when signing a token', function() {
|
||||
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256' });
|
||||
|
||||
it('should be syntactically valid', function() {
|
||||
expect(token).to.be.a('string');
|
||||
expect(token.split('.')).to.have.length(3);
|
||||
});
|
||||
|
||||
it('should validate with public key', function(done) {
|
||||
jwt.verify(token, pub, function(err, decoded) {
|
||||
assert.ok(decoded.foo);
|
||||
assert.equal('bar', decoded.foo);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw with invalid public key', function(done) {
|
||||
jwt.verify(token, invalid_pub, function(err, decoded) {
|
||||
assert.isUndefined(decoded);
|
||||
assert.isNotNull(err);
|
||||
done();
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('when signing a token with expiration', function() {
|
||||
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', expiresInMinutes: 10 });
|
||||
|
||||
it('should be valid expiration', function(done) {
|
||||
jwt.verify(token, pub, function(err, decoded) {
|
||||
assert.isNotNull(decoded);
|
||||
assert.isNull(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should be invalid', function(done) {
|
||||
// expired token
|
||||
token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', expiresInMinutes: -10 });
|
||||
|
||||
jwt.verify(token, pub, function(err, decoded) {
|
||||
assert.isUndefined(decoded);
|
||||
assert.isNotNull(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when signing a token with audience', function() {
|
||||
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', audience: 'urn:foo' });
|
||||
|
||||
it('should check audience', function(done) {
|
||||
jwt.verify(token, pub, function(err, decoded) {
|
||||
assert.isNotNull(decoded);
|
||||
assert.isNull(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw when invalid audience', function(done) {
|
||||
jwt.verify(token, pub, { audience: 'urn:wrong' }, function(err, decoded) {
|
||||
assert.isUndefined(decoded);
|
||||
assert.isNotNull(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
describe('when signing a token with issuer', function() {
|
||||
var token = jwt.sign({ foo: 'bar' }, priv, { algorithm: 'RS256', issuer: 'urn:foo' });
|
||||
|
||||
it('should check issuer', function() {
|
||||
jwt.verify(token, pub, { issuer: 'urn:foo' }, function(err, decoded) {
|
||||
assert.isNotNull(decoded);
|
||||
assert.isNull(err);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw when invalid issuer', function() {
|
||||
jwt.verify(token, pub, { issuer: 'urn:wrong' }, function(err, decoded) {
|
||||
assert.isUndefined(decoded);
|
||||
assert.isNotNull(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('when verifying a malformed token', function() {
|
||||
it('should throw', function(done) {
|
||||
jwt.verify('fruit.fruit.fruit', pub, function(err, decoded) {
|
||||
assert.isUndefined(decoded);
|
||||
assert.isNotNull(err);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAvtH4wKLYlIXZlfYQFJtXZVC3fD8XMarzwvb/fHUyJ6NvNStN
|
||||
+H7GHp3/QhZbSaRyqK5hu5xXtFLgnI0QG8oE1NlXbczjH45LeHWhPIdc2uHSpzXi
|
||||
c78kOugMY1vng4J10PF6+T2FNaiv0iXeIQq9xbwwPYpflViQyJnzGCIZ7VGan6Gb
|
||||
RKzyTKcB58yx24pJq+CviLXEY52TIW1l5imcjGvLtlCp1za9qBZa4XGoVqHi1kRX
|
||||
kdDSHty6lZWj3KxoRvTbiaBCH+75U7rifS6fR9lqjWE57bCGoz7+BBu9YmPKtI1K
|
||||
kyHFqWpxaJc/AKf9xgg+UumeqVcirUmAsHJrMwIDAQABAoIBAQCYKw05YSNhXVPk
|
||||
eHLeW/pXuwR3OkCexPrakOmwMC0s2vIF7mChN0d6hvhVlUp68X7V8SnS2JxAGo8v
|
||||
iHY+Et3DdwZ3cxnzwh+BEhzgDfoIOmkoGppZPyX/K6klWtbGUrTtSISOWXbvEXQU
|
||||
G0qGAvDOzIGTsdMDX7slnU70Ac23JybPY5qBSiE+ky8U4dm2fUHMroWub4QP5vA/
|
||||
nqyWqX2FB/MEAbcujaknDQrFCtbmtUYlBbJCKGd9V3cGEqp6H7oH+ah2ofMc91gJ
|
||||
mCHk3YyWZB/bcVXH3CA+s1ywvCOVDBZ3Nw7Pt9zIcv6Rl9UKIy+Nx0QjXxR90Hla
|
||||
Tr0GHIShAoGBAPsD7uXm+0ksnGyKRYgvlVad8Z8FUFT6bf4B+vboDbx40FO8O/5V
|
||||
PraBPC5z8YRSBOQ/WfccPQzakkA28F2pXlRpXu5JcErVWnyyUiKpX5sw6iPenQR2
|
||||
JO9hY/GFbKiwUhVHpvWMcXFqFLSQu2A86jPnFFEfG48ZT4IhTzINKJVZAoGBAMKc
|
||||
B3YGfVfY9qiRFXzYRdSRLg5c8p/HzuWwXc9vfJ4kQTDkPXe/+nqD67rzeT54uVec
|
||||
jKoIrsCu4BfEaoyvOT+1KmUfdEpBgYZuuEC4CZf7dgKbXOpPVvZDMyJ/e7HyqTpw
|
||||
mvIYJLPm2fNAcAsnbrNX5mhLwwzEIltbplUUeRdrAoGBAKhZgPYsLkhrZRXevreR
|
||||
wkTvdUfD1pbHxtFfHqROCjhnhsFCM7JmFcNtdaFqHYczQxiZ7IqxI7jlNsVek2Md
|
||||
3qgaa5LBKlDmOuP67N9WXUrGSaJ5ATIm0qrB1Lf9VlzktIiVH8L7yHHaRby8fQ8U
|
||||
i7b3ukaV6HPW895A3M6iyJ8xAoGAInp4S+3MaTL0SFsj/nFmtcle6oaHKc3BlyoP
|
||||
BMBQyMfNkPbu+PdXTjtvGTknouzKkX4X4cwWAec5ppxS8EffEa1sLGxNMxa19vZI
|
||||
yJaShI21k7Ko3I5f7tNrDNKfPKCsYMEwgnHKluDwfktNTnyW/Uk2dgXuMaXSHHN5
|
||||
XZt59K8CgYArGVOWK7LUmf3dkTIs3tXBm4/IMtUZmWmcP9C8Xe/Dg/IdQhK5CIx4
|
||||
VXl8rgZNeX/5/4nJ8Q3LrdLau1Iz620trNRGU6sGMs3x4WQbSq93RRbFzfG1oK74
|
||||
IOo5yIBxImQOSk5jz31gF9RJb15SDBIxonuWv8qAERyUfvrmEwR0kg==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,22 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDtTCCAp2gAwIBAgIJAMKR/NsyfcazMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
|
||||
BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
|
||||
aWRnaXRzIFB0eSBMdGQwHhcNMTIxMTEyMjM0MzQxWhcNMTYxMjIxMjM0MzQxWjBF
|
||||
MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
|
||||
ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
|
||||
CgKCAQEAvtH4wKLYlIXZlfYQFJtXZVC3fD8XMarzwvb/fHUyJ6NvNStN+H7GHp3/
|
||||
QhZbSaRyqK5hu5xXtFLgnI0QG8oE1NlXbczjH45LeHWhPIdc2uHSpzXic78kOugM
|
||||
Y1vng4J10PF6+T2FNaiv0iXeIQq9xbwwPYpflViQyJnzGCIZ7VGan6GbRKzyTKcB
|
||||
58yx24pJq+CviLXEY52TIW1l5imcjGvLtlCp1za9qBZa4XGoVqHi1kRXkdDSHty6
|
||||
lZWj3KxoRvTbiaBCH+75U7rifS6fR9lqjWE57bCGoz7+BBu9YmPKtI1KkyHFqWpx
|
||||
aJc/AKf9xgg+UumeqVcirUmAsHJrMwIDAQABo4GnMIGkMB0GA1UdDgQWBBTs83nk
|
||||
LtoXFlmBUts3EIxcVvkvcjB1BgNVHSMEbjBsgBTs83nkLtoXFlmBUts3EIxcVvkv
|
||||
cqFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNV
|
||||
BAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAMKR/NsyfcazMAwGA1UdEwQF
|
||||
MAMBAf8wDQYJKoZIhvcNAQEFBQADggEBABw7w/5k4d5dVDgd/OOOmXdaaCIKvt7d
|
||||
3ntlv1SSvAoKT8d8lt97Dm5RrmefBI13I2yivZg5bfTge4+vAV6VdLFdWeFp1b/F
|
||||
OZkYUv6A8o5HW0OWQYVX26zIqBcG2Qrm3reiSl5BLvpj1WSpCsYvs5kaO4vFpMak
|
||||
/ICgdZD+rxwxf8Vb/6fntKywWSLgwKH3mJ+Z0kRlpq1g1oieiOm1/gpZ35s0Yuor
|
||||
XZba9ptfLCYSggg/qc3d3d0tbHplKYkwFm7f5ORGHDSD5SJm+gI7RPE+4bO8q79R
|
||||
PAfbG1UGuJ0b/oigagciHhJp851SQRYf3JuNSc17BnK2L5IEtzjqr+Q=
|
||||
-----END CERTIFICATE-----
|
||||
Reference in New Issue
Block a user