зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1191936
- Implement SPKI/PKCS#8/JWK import/export for RSA-PSS r=rbarnes
This commit is contained in:
Родитель
38e4db6e5e
Коммит
49dd294551
|
@ -193,6 +193,19 @@ KeyAlgorithmProxy::JwkAlg() const
|
|||
}
|
||||
}
|
||||
|
||||
if (mName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
nsString hashName = mRsa.mHash.mName;
|
||||
if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA1)) {
|
||||
return NS_LITERAL_STRING(JWK_ALG_PS1);
|
||||
} else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA256)) {
|
||||
return NS_LITERAL_STRING(JWK_ALG_PS256);
|
||||
} else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA384)) {
|
||||
return NS_LITERAL_STRING(JWK_ALG_PS384);
|
||||
} else if (hashName.EqualsLiteral(WEBCRYPTO_ALG_SHA512)) {
|
||||
return NS_LITERAL_STRING(JWK_ALG_PS512);
|
||||
}
|
||||
}
|
||||
|
||||
return nsString();
|
||||
}
|
||||
|
||||
|
|
|
@ -88,6 +88,10 @@
|
|||
#define JWK_ALG_RSA_OAEP_256 "RSA-OAEP-256"
|
||||
#define JWK_ALG_RSA_OAEP_384 "RSA-OAEP-384"
|
||||
#define JWK_ALG_RSA_OAEP_512 "RSA-OAEP-512"
|
||||
#define JWK_ALG_PS1 "PS1" // RSA-PSS
|
||||
#define JWK_ALG_PS256 "PS256"
|
||||
#define JWK_ALG_PS384 "PS384"
|
||||
#define JWK_ALG_PS512 "PS512"
|
||||
#define JWK_ALG_ECDSA_P_256 "ES256"
|
||||
#define JWK_ALG_ECDSA_P_384 "ES384"
|
||||
#define JWK_ALG_ECDSA_P_521 "ES521"
|
||||
|
@ -206,6 +210,8 @@ MapAlgorithmNameToMechanism(const nsString& aName)
|
|||
mechanism = CKM_RSA_PKCS;
|
||||
} else if (aName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
|
||||
mechanism = CKM_RSA_PKCS_OAEP;
|
||||
} else if (aName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
mechanism = CKM_RSA_PKCS_PSS;
|
||||
} else if (aName.EqualsLiteral(WEBCRYPTO_ALG_ECDH)) {
|
||||
mechanism = CKM_ECDH1_DERIVE;
|
||||
} else if (aName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
|
||||
|
|
|
@ -1668,7 +1668,8 @@ public:
|
|||
|
||||
// If this is RSA with a hash, cache the hash name
|
||||
if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
RootedDictionary<RsaHashedImportParams> params(aCx);
|
||||
mEarlyRv = Coerce(aCx, params, aAlgorithm);
|
||||
if (NS_FAILED(mEarlyRv)) {
|
||||
|
@ -1770,7 +1771,8 @@ private:
|
|||
mKey->HasUsageOtherThan(CryptoKey::DECRYPT | CryptoKey::UNWRAPKEY))) {
|
||||
return NS_ERROR_DOM_DATA_ERR;
|
||||
}
|
||||
} else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1)) {
|
||||
} else if (mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
|
||||
mAlgName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
if ((mKey->GetKeyType() == CryptoKey::PUBLIC &&
|
||||
mKey->HasUsageOtherThan(CryptoKey::VERIFY)) ||
|
||||
(mKey->GetKeyType() == CryptoKey::PRIVATE &&
|
||||
|
@ -3336,7 +3338,8 @@ WebCryptoTask::CreateImportKeyTask(JSContext* aCx,
|
|||
return new ImportSymmetricKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
|
||||
aExtractable, aKeyUsages);
|
||||
} else if (algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP)) {
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
return new ImportRsaKeyTask(aCx, aFormat, aKeyData, aAlgorithm,
|
||||
aExtractable, aKeyUsages);
|
||||
} else if (algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
|
||||
|
@ -3382,6 +3385,7 @@ WebCryptoTask::CreateExportKeyTask(const nsAString& aFormat,
|
|||
algName.EqualsLiteral(WEBCRYPTO_ALG_HMAC) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_OAEP) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_RSA_PSS) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_ECDSA) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_ECDH) ||
|
||||
algName.EqualsLiteral(WEBCRYPTO_ALG_DH)) {
|
||||
|
@ -3604,7 +3608,8 @@ WebCryptoTask::CreateUnwrapKeyTask(JSContext* aCx,
|
|||
aUnwrappedKeyAlgorithm,
|
||||
aExtractable, aKeyUsages);
|
||||
} else if (keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSASSA_PKCS1) ||
|
||||
keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP)) {
|
||||
keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_OAEP) ||
|
||||
keyAlgName.EqualsASCII(WEBCRYPTO_ALG_RSA_PSS)) {
|
||||
importTask = new ImportRsaKeyTask(aCx, aFormat,
|
||||
aUnwrappedKeyAlgorithm,
|
||||
aExtractable, aKeyUsages);
|
||||
|
|
|
@ -418,6 +418,86 @@ tv = {
|
|||
),
|
||||
},
|
||||
|
||||
// RSA-PSS test vectors, pss-vect.txt, Example 1: A 1024-bit RSA Key Pair
|
||||
// <ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip>
|
||||
rsapss: {
|
||||
pkcs8: util.hex2abv(
|
||||
"30820275020100300d06092a864886f70d01010105000482025f3082025b0201" +
|
||||
"0002818100a56e4a0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1" +
|
||||
"e61f7ad991d8c51056ffedb162b4c0f283a12a88a394dff526ab7291cbb307ce" +
|
||||
"abfce0b1dfd5cd9508096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e" +
|
||||
"6ff89d19f105acc2d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb22" +
|
||||
"49bd9a2137020301000102818033a5042a90b27d4f5451ca9bbbd0b44771a101" +
|
||||
"af884340aef9885f2a4bbe92e894a724ac3c568c8f97853ad07c0266c8c6a3ca" +
|
||||
"0929f1e8f11231884429fc4d9ae55fee896a10ce707c3ed7e734e44727a39574" +
|
||||
"501a532683109c2abacaba283c31b4bd2f53c3ee37e352cee34f9e503bd80c06" +
|
||||
"22ad79c6dcee883547c6a3b325024100e7e8942720a877517273a356053ea2a1" +
|
||||
"bc0c94aa72d55c6e86296b2dfc967948c0a72cbccca7eacb35706e09a1df55a1" +
|
||||
"535bd9b3cc34160b3b6dcd3eda8e6443024100b69dca1cf7d4d7ec81e75b90fc" +
|
||||
"ca874abcde123fd2700180aa90479b6e48de8d67ed24f9f19d85ba275874f542" +
|
||||
"cd20dc723e6963364a1f9425452b269a6799fd024028fa13938655be1f8a159c" +
|
||||
"baca5a72ea190c30089e19cd274a556f36c4f6e19f554b34c077790427bbdd8d" +
|
||||
"d3ede2448328f385d81b30e8e43b2fffa02786197902401a8b38f398fa712049" +
|
||||
"898d7fb79ee0a77668791299cdfa09efc0e507acb21ed74301ef5bfd48be455e" +
|
||||
"aeb6e1678255827580a8e4e8e14151d1510a82a3f2e729024027156aba4126d2" +
|
||||
"4a81f3a528cbfb27f56886f840a9f6e86e17a44b94fe9319584b8e22fdde1e5a" +
|
||||
"2e3bd8aa5ba8d8584194eb2190acf832b847f13a3d24a79f4d"
|
||||
),
|
||||
spki: util.hex2abv(
|
||||
"30819f300d06092a864886f70d010101050003818d0030818902818100a56e4a" +
|
||||
"0e701017589a5187dc7ea841d156f2ec0e36ad52a44dfeb1e61f7ad991d8c510" +
|
||||
"56ffedb162b4c0f283a12a88a394dff526ab7291cbb307ceabfce0b1dfd5cd95" +
|
||||
"08096d5b2b8b6df5d671ef6377c0921cb23c270a70e2598e6ff89d19f105acc2" +
|
||||
"d3f0cb35f29280e1386b6f64c4ef22e1e1f20d0ce8cffb2249bd9a2137020301" +
|
||||
"0001"
|
||||
),
|
||||
data: util.hex2abv(
|
||||
"cdc87da223d786df3b45e0bbbc721326d1ee2af806cc315475cc6f0d9c66e1b6" +
|
||||
"2371d45ce2392e1ac92844c310102f156a0d8d52c1f4c40ba3aa65095786cb76" +
|
||||
"9757a6563ba958fed0bcc984e8b517a3d5f515b23b8a41e74aa867693f90dfb0" +
|
||||
"61a6e86dfaaee64472c00e5f20945729cbebe77f06ce78e08f4098fba41f9d61" +
|
||||
"93c0317e8b60d4b6084acb42d29e3808a3bc372d85e331170fcbf7cc72d0b71c" +
|
||||
"296648b3a4d10f416295d0807aa625cab2744fd9ea8fd223c42537029828bd16" +
|
||||
"be02546f130fd2e33b936d2676e08aed1b73318b750a0167d0"
|
||||
),
|
||||
sig: util.hex2abv(
|
||||
"9074308fb598e9701b2294388e52f971faac2b60a5145af185df5287b5ed2887" +
|
||||
"e57ce7fd44dc8634e407c8e0e4360bc226f3ec227f9d9e54638e8d31f5051215" +
|
||||
"df6ebb9c2f9579aa77598a38f914b5b9c1bd83c4e2f9f382a0d0aa3542ffee65" +
|
||||
"984a601bc69eb28deb27dca12c82c2d4c3f66cd500f1ff2b994d8a4e30cbb33c"
|
||||
),
|
||||
salt: util.hex2abv(
|
||||
"dee959c7e06411361420ff80185ed57f3e6776af"
|
||||
),
|
||||
jwk_priv: {
|
||||
kty: "RSA",
|
||||
n: "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhK" +
|
||||
"oijlN_1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm" +
|
||||
"_4nRnxBazC0_DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc",
|
||||
e: "AQAB",
|
||||
d: "M6UEKpCyfU9UUcqbu9C0R3GhAa-IQ0Cu-YhfKku-kuiUpySsPFaMj5eFOtB8A" +
|
||||
"mbIxqPKCSnx6PESMYhEKfxNmuVf7olqEM5wfD7X5zTkRyejlXRQGlMmgxCcKr" +
|
||||
"rKuig8MbS9L1PD7jfjUs7jT55QO9gMBiKtecbc7og1R8ajsyU",
|
||||
p: "5-iUJyCod1Fyc6NWBT6iobwMlKpy1VxuhilrLfyWeUjApyy8zKfqyzVwbgmh3" +
|
||||
"1WhU1vZs8w0Fgs7bc0-2o5kQw",
|
||||
q: "tp3KHPfU1-yB51uQ_MqHSrzeEj_ScAGAqpBHm25I3o1n7ST58Z2FuidYdPVCz" +
|
||||
"SDccj5pYzZKH5QlRSsmmmeZ_Q",
|
||||
dp: "KPoTk4ZVvh-KFZy6ylpy6hkMMAieGc0nSlVvNsT24Z9VSzTAd3kEJ7vdjdPt" +
|
||||
"4kSDKPOF2Bsw6OQ7L_-gJ4YZeQ",
|
||||
dq: "Gos485j6cSBJiY1_t57gp3ZoeRKZzfoJ78DlB6yyHtdDAe9b_Ui-RV6utuFn" +
|
||||
"glWCdYCo5OjhQVHRUQqCo_LnKQ",
|
||||
qi: "JxVqukEm0kqB86Uoy_sn9WiG-ECp9uhuF6RLlP6TGVhLjiL93h5aLjvYqluo" +
|
||||
"2FhBlOshkKz4MrhH8To9JKefTQ",
|
||||
},
|
||||
jwk_pub: {
|
||||
kty: "RSA",
|
||||
n: "pW5KDnAQF1iaUYfcfqhB0Vby7A42rVKkTf6x5h962ZHYxRBW_-2xYrTA8oOhK" +
|
||||
"oijlN_1JqtykcuzB86r_OCx39XNlQgJbVsri2311nHvY3fAkhyyPCcKcOJZjm" +
|
||||
"_4nRnxBazC0_DLNfKSgOE4a29kxO8i4eHyDQzoz_siSb2aITc",
|
||||
e: "AQAB",
|
||||
},
|
||||
},
|
||||
|
||||
key_wrap_known_answer: {
|
||||
key: util.hex2abv("0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a0a"),
|
||||
wrapping_key: util.hex2abv("0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"),
|
||||
|
|
|
@ -73,6 +73,204 @@ TestArray.addTest(
|
|||
.then(complete(that, x => x), error(that))
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS verify known signature (SHA-1, 1024-bit)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
|
||||
function doVerify(x) {
|
||||
var alg = {name: "RSA-PSS", saltLength: tv.rsapss.salt.byteLength};
|
||||
return crypto.subtle.verify(alg, x, tv.rsapss.sig, tv.rsapss.data);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("spki", tv.rsapss.spki, alg, false, ["verify"])
|
||||
.then(doVerify, error(that))
|
||||
.then(complete(that, x => x), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS verify known signature (SHA-1, 1024-bit, JWK)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
|
||||
function doVerify(x) {
|
||||
var alg = {name: "RSA-PSS", saltLength: tv.rsapss.salt.byteLength};
|
||||
return crypto.subtle.verify(alg, x, tv.rsapss.sig, tv.rsapss.data);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("jwk", tv.rsapss.jwk_pub, alg, false, ["verify"])
|
||||
.then(doVerify, error(that))
|
||||
.then(complete(that, x => x), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS import SPKI/PKCS#8 keys and sign/verify (SHA-1, 1024-bit)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
|
||||
var privKey, pubKey;
|
||||
function setKeys([pub, priv]) { pubKey = pub; privKey = priv; }
|
||||
function doSign() {
|
||||
var alg = {name: "RSA-PSS", saltLength: tv.rsapss.salt.byteLength};
|
||||
return crypto.subtle.sign(alg, privKey, tv.rsapss.data);
|
||||
}
|
||||
function doVerify(x) {
|
||||
var alg = {name: "RSA-PSS", saltLength: tv.rsapss.salt.byteLength};
|
||||
return crypto.subtle.verify(alg, pubKey, x, tv.rsapss.data);
|
||||
}
|
||||
|
||||
var spki =
|
||||
crypto.subtle.importKey("spki", tv.rsapss.spki, alg, false, ["verify"]);
|
||||
var pkcs8 =
|
||||
crypto.subtle.importKey("pkcs8", tv.rsapss.pkcs8, alg, false, ["sign"]);
|
||||
|
||||
Promise.all([spki, pkcs8])
|
||||
.then(setKeys, error(that))
|
||||
.then(doSign, error(that))
|
||||
.then(doVerify, error(that))
|
||||
.then(complete(that, x => x), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS import JWK keys and sign/verify (SHA-1, 1024-bit)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
|
||||
var privKey, pubKey;
|
||||
function setKeys([pub, priv]) { pubKey = pub; privKey = priv; }
|
||||
function doSign() {
|
||||
var alg = {name: "RSA-PSS", saltLength: tv.rsapss.salt.byteLength};
|
||||
return crypto.subtle.sign(alg, privKey, tv.rsapss.data);
|
||||
}
|
||||
function doVerify(x) {
|
||||
var alg = {name: "RSA-PSS", saltLength: tv.rsapss.salt.byteLength};
|
||||
return crypto.subtle.verify(alg, pubKey, x, tv.rsapss.data);
|
||||
}
|
||||
|
||||
var spki =
|
||||
crypto.subtle.importKey("jwk", tv.rsapss.jwk_pub, alg, false, ["verify"]);
|
||||
var pkcs8 =
|
||||
crypto.subtle.importKey("jwk", tv.rsapss.jwk_priv, alg, false, ["sign"]);
|
||||
|
||||
Promise.all([spki, pkcs8])
|
||||
.then(setKeys, error(that))
|
||||
.then(doSign, error(that))
|
||||
.then(doVerify, error(that))
|
||||
.then(complete(that, x => x), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS SPKI import/export (SHA-1, 1024-bit)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
|
||||
function doExport(x) {
|
||||
return crypto.subtle.exportKey("spki", x);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("spki", tv.rsapss.spki, alg, true, ["verify"])
|
||||
.then(doExport, error(that))
|
||||
.then(memcmp_complete(that, tv.rsapss.spki), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS PKCS#8 import/export (SHA-1, 1024-bit)",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
|
||||
function doExport(x) {
|
||||
return crypto.subtle.exportKey("pkcs8", x);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("pkcs8", tv.rsapss.pkcs8, alg, true, ["sign"])
|
||||
.then(doExport, error(that))
|
||||
.then(memcmp_complete(that, tv.rsapss.pkcs8), error(that));
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS JWK export a public key",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
var jwk = tv.rsapss.jwk_pub;
|
||||
|
||||
function doExport(x) {
|
||||
return crypto.subtle.exportKey("jwk", x);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("jwk", jwk, alg, true, ["verify"])
|
||||
.then(doExport)
|
||||
.then(
|
||||
complete(that, function(x) {
|
||||
return hasBaseJwkFields(x) &&
|
||||
hasFields(x, ["n", "e"]) &&
|
||||
x.kty == "RSA" &&
|
||||
x.alg == "PS1" &&
|
||||
x.ext &&
|
||||
shallowArrayEquals(x.key_ops, ["verify"]) &&
|
||||
x.n == jwk.n &&
|
||||
x.e == jwk.e;
|
||||
}),
|
||||
error(that)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
TestArray.addTest(
|
||||
"RSA-PSS JWK export a private key",
|
||||
function () {
|
||||
var that = this;
|
||||
var alg = {name: "RSA-PSS", hash: "SHA-1"};
|
||||
var jwk = tv.rsapss.jwk_priv;
|
||||
|
||||
function doExport(x) {
|
||||
return crypto.subtle.exportKey("jwk", x);
|
||||
}
|
||||
|
||||
crypto.subtle.importKey("jwk", jwk, alg, true, ["sign"])
|
||||
.then(doExport)
|
||||
.then(
|
||||
complete(that, function(x) {
|
||||
return hasBaseJwkFields(x) &&
|
||||
hasFields(x, ["n", "e", "d", "p", "q", "dp", "dq", "qi"]) &&
|
||||
x.kty == "RSA" &&
|
||||
x.alg == "PS1" &&
|
||||
x.ext &&
|
||||
shallowArrayEquals(x.key_ops, ["sign"]) &&
|
||||
x.n == jwk.n &&
|
||||
x.e == jwk.e &&
|
||||
x.d == jwk.d &&
|
||||
x.p == jwk.p &&
|
||||
x.q == jwk.q &&
|
||||
x.dp == jwk.dp &&
|
||||
x.dq == jwk.dq &&
|
||||
x.qi == jwk.qi;
|
||||
}),
|
||||
error(that)
|
||||
);
|
||||
}
|
||||
);
|
||||
/*]]>*/</script>
|
||||
</head>
|
||||
|
||||
|
|
Загрузка…
Ссылка в новой задаче