| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
1 parent 795db76 commit 314dacd
12 files changed
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -927,6 +927,14 @@ async function wrapKey(format, key, wrappingKey, algorithm) { | |||
| 927 | 927 | } catch { | |
| 928 | 928 | algorithm = normalizeAlgorithm(algorithm, 'encrypt'); | |
| 929 | 929 | } | |
| 930 | + | ||
| 931 | + if (algorithm.name !== wrappingKey[kAlgorithm].name) | ||
| 932 | + throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); | ||
| 933 | + | ||
| 934 | + if (!ArrayPrototypeIncludes(wrappingKey[kKeyUsages], 'wrapKey')) | ||
| 935 | + throw lazyDOMException( | ||
| 936 | + 'Unable to use this key to wrapKey', 'InvalidAccessError'); | ||
| 937 | + | ||
| 930 | 938 | let keyData = await FunctionPrototypeCall(exportKey, this, format, key); | |
| 931 | 939 | ||
| 932 | 940 | if (format === 'jwk') { | |
@@ -1005,6 +1013,13 @@ async function unwrapKey( | |||
| 1005 | 1013 | ||
| 1006 | 1014 | unwrappedKeyAlgo = normalizeAlgorithm(unwrappedKeyAlgo, 'importKey'); | |
| 1007 | 1015 | ||
| 1016 | + if (unwrapAlgo.name !== unwrappingKey[kAlgorithm].name) | ||
| 1017 | + throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); | ||
| 1018 | + | ||
| 1019 | + if (!ArrayPrototypeIncludes(unwrappingKey[kKeyUsages], 'unwrapKey')) | ||
| 1020 | + throw lazyDOMException( | ||
| 1021 | + 'Unable to use this key to unwrapKey', 'InvalidAccessError'); | ||
| 1022 | + | ||
| 1008 | 1023 | let keyData = await cipherOrWrap( | |
| 1009 | 1024 | kWebCryptoCipherDecrypt, | |
| 1010 | 1025 | unwrapAlgo, | |
@@ -1038,12 +1053,12 @@ async function signVerify(algorithm, key, data, signature) { | |||
| 1038 | 1053 | } | |
| 1039 | 1054 | algorithm = normalizeAlgorithm(algorithm, usage); | |
| 1040 | 1055 | ||
| 1041 | - if (!ArrayPrototypeIncludes(key[kKeyUsages], usage) || | ||
| 1042 | - algorithm.name !== key[kAlgorithm].name) { | ||
| 1056 | + if (algorithm.name !== key[kAlgorithm].name) | ||
| 1057 | + throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); | ||
| 1058 | + | ||
| 1059 | + if (!ArrayPrototypeIncludes(key[kKeyUsages], usage)) | ||
| 1043 | 1060 | throw lazyDOMException( | |
| 1044 | - `Unable to use this key to ${usage}`, | ||
| 1045 | - 'InvalidAccessError'); | ||
| 1046 | - } | ||
| 1061 | + `Unable to use this key to ${usage}`, 'InvalidAccessError'); | ||
| 1047 | 1062 | ||
| 1048 | 1063 | switch (algorithm.name) { | |
| 1049 | 1064 | case 'RSA-PSS': | |
@@ -1128,18 +1143,6 @@ async function verify(algorithm, key, signature, data) { | |||
| 1128 | 1143 | } | |
| 1129 | 1144 | ||
| 1130 | 1145 | async function cipherOrWrap(mode, algorithm, key, data, op) { | |
| 1131 | - // We use a Node.js style error here instead of a DOMException because | ||
| 1132 | - // the WebCrypto spec is not specific what kind of error is to be thrown | ||
| 1133 | - // in this case. Both Firefox and Chrome throw simple TypeErrors here. | ||
| 1134 | - // The key algorithm and cipher algorithm must match, and the | ||
| 1135 | - // key must have the proper usage. | ||
| 1136 | - if (key[kAlgorithm].name !== algorithm.name || | ||
| 1137 | - !ArrayPrototypeIncludes(key[kKeyUsages], op)) { | ||
| 1138 | - throw lazyDOMException( | ||
| 1139 | - 'The requested operation is not valid for the provided key', | ||
| 1140 | - 'InvalidAccessError'); | ||
| 1141 | - } | ||
| 1142 | - | ||
| 1143 | 1146 | // While WebCrypto allows for larger input buffer sizes, we limit | |
| 1144 | 1147 | // those to sizes that can fit within uint32_t because of limitations | |
| 1145 | 1148 | // in the OpenSSL API. | |
@@ -1190,6 +1193,14 @@ async function encrypt(algorithm, key, data) { | |||
| 1190 | 1193 | }); | |
| 1191 | 1194 | ||
| 1192 | 1195 | algorithm = normalizeAlgorithm(algorithm, 'encrypt'); | |
| 1196 | + | ||
| 1197 | + if (algorithm.name !== key[kAlgorithm].name) | ||
| 1198 | + throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); | ||
| 1199 | + | ||
| 1200 | + if (!ArrayPrototypeIncludes(key[kKeyUsages], 'encrypt')) | ||
| 1201 | + throw lazyDOMException( | ||
| 1202 | + 'Unable to use this key to encrypt', 'InvalidAccessError'); | ||
| 1203 | + | ||
| 1193 | 1204 | return await cipherOrWrap( | |
| 1194 | 1205 | kWebCryptoCipherEncrypt, | |
| 1195 | 1206 | algorithm, | |
@@ -1219,6 +1230,14 @@ async function decrypt(algorithm, key, data) { | |||
| 1219 | 1230 | }); | |
| 1220 | 1231 | ||
| 1221 | 1232 | algorithm = normalizeAlgorithm(algorithm, 'decrypt'); | |
| 1233 | + | ||
| 1234 | + if (algorithm.name !== key[kAlgorithm].name) | ||
| 1235 | + throw lazyDOMException('Key algorithm mismatch', 'InvalidAccessError'); | ||
| 1236 | + | ||
| 1237 | + if (!ArrayPrototypeIncludes(key[kKeyUsages], 'decrypt')) | ||
| 1238 | + throw lazyDOMException( | ||
| 1239 | + 'Unable to use this key to decrypt', 'InvalidAccessError'); | ||
| 1240 | + | ||
| 1222 | 1241 | return await cipherOrWrap( | |
| 1223 | 1242 | kWebCryptoCipherDecrypt, | |
| 1224 | 1243 | algorithm, | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -49,7 +49,7 @@ async function testEncryptNoEncrypt({ keyBuffer, algorithm, plaintext }) { | |||
| 49 | 49 | ['decrypt']); | |
| 50 | 50 | ||
| 51 | 51 | return assert.rejects(subtle.encrypt(algorithm, key, plaintext), { | |
| 52 | - message: /The requested operation is not valid for the provided key/ | ||
| 52 | + message: /Unable to use this key to encrypt/ | ||
| 53 | 53 | }); | |
| 54 | 54 | } | |
| 55 | 55 | ||
@@ -65,7 +65,7 @@ async function testEncryptNoDecrypt({ keyBuffer, algorithm, plaintext }) { | |||
| 65 | 65 | const output = await subtle.encrypt(algorithm, key, plaintext); | |
| 66 | 66 | ||
| 67 | 67 | return assert.rejects(subtle.decrypt(algorithm, key, output), { | |
| 68 | - message: /The requested operation is not valid for the provided key/ | ||
| 68 | + message: /Unable to use this key to decrypt/ | ||
| 69 | 69 | }); | |
| 70 | 70 | } | |
| 71 | 71 | ||
@@ -80,7 +80,23 @@ async function testEncryptWrongAlg({ keyBuffer, algorithm, plaintext }, alg) { | |||
| 80 | 80 | ['encrypt']); | |
| 81 | 81 | ||
| 82 | 82 | return assert.rejects(subtle.encrypt(algorithm, key, plaintext), { | |
| 83 | - message: /The requested operation is not valid for the provided key/ | ||
| 83 | + message: /Key algorithm mismatch/ | ||
| 84 | + }); | ||
| 85 | + } | ||
| 86 | + | ||
| 87 | + async function testDecryptWrongAlg({ keyBuffer, algorithm, result }, alg) { | ||
| 88 | + if (result === undefined) return; | ||
| 89 | + assert.notStrictEqual(algorithm.name, alg); | ||
| 90 | + const keyFormat = alg === 'AES-OCB' ? 'raw-secret' : 'raw'; | ||
| 91 | + const key = await subtle.importKey( | ||
| 92 | + keyFormat, | ||
| 93 | + keyBuffer, | ||
| 94 | + { name: alg }, | ||
| 95 | + false, | ||
| 96 | + ['decrypt']); | ||
| 97 | + | ||
| 98 | + return assert.rejects(subtle.decrypt(algorithm, key, result), { | ||
| 99 | + message: /Key algorithm mismatch/ | ||
| 84 | 100 | }); | |
| 85 | 101 | } | |
| 86 | 102 | ||
@@ -112,6 +128,7 @@ async function testDecrypt({ keyBuffer, algorithm, result }) { | |||
| 112 | 128 | variations.push(testEncryptNoEncrypt(vector)); | |
| 113 | 129 | variations.push(testEncryptNoDecrypt(vector)); | |
| 114 | 130 | variations.push(testEncryptWrongAlg(vector, 'AES-CTR')); | |
| 131 | + variations.push(testDecryptWrongAlg(vector, 'AES-CTR')); | ||
| 115 | 132 | }); | |
| 116 | 133 | ||
| 117 | 134 | failing.forEach((vector) => { | |
@@ -149,6 +166,7 @@ async function testDecrypt({ keyBuffer, algorithm, result }) { | |||
| 149 | 166 | variations.push(testEncryptNoEncrypt(vector)); | |
| 150 | 167 | variations.push(testEncryptNoDecrypt(vector)); | |
| 151 | 168 | variations.push(testEncryptWrongAlg(vector, 'AES-CBC')); | |
| 169 | + variations.push(testDecryptWrongAlg(vector, 'AES-CBC')); | ||
| 152 | 170 | }); | |
| 153 | 171 | ||
| 154 | 172 | // TODO(@jasnell): These fail for different reasons. Need to | |
@@ -188,6 +206,7 @@ async function testDecrypt({ keyBuffer, algorithm, result }) { | |||
| 188 | 206 | variations.push(testEncryptNoEncrypt(vector)); | |
| 189 | 207 | variations.push(testEncryptNoDecrypt(vector)); | |
| 190 | 208 | variations.push(testEncryptWrongAlg(vector, 'AES-CBC')); | |
| 209 | + variations.push(testDecryptWrongAlg(vector, 'AES-CBC')); | ||
| 191 | 210 | }); | |
| 192 | 211 | ||
| 193 | 212 | failing.forEach((vector) => { | |
@@ -225,6 +244,7 @@ if (hasOpenSSL(3)) { | |||
| 225 | 244 | variations.push(testEncryptNoEncrypt(vector)); | |
| 226 | 245 | variations.push(testEncryptNoDecrypt(vector)); | |
| 227 | 246 | variations.push(testEncryptWrongAlg(vector, 'AES-GCM')); | |
| 247 | + variations.push(testDecryptWrongAlg(vector, 'AES-GCM')); | ||
| 228 | 248 | }); | |
| 229 | 249 | ||
| 230 | 250 | failing.forEach((vector) => { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -48,7 +48,7 @@ async function testEncryptNoEncrypt({ keyBuffer, algorithm, plaintext }) { | |||
| 48 | 48 | ['decrypt']); | |
| 49 | 49 | ||
| 50 | 50 | return assert.rejects(subtle.encrypt(algorithm, key, plaintext), { | |
| 51 | - message: /The requested operation is not valid for the provided key/ | ||
| 51 | + message: /Unable to use this key to encrypt/ | ||
| 52 | 52 | }); | |
| 53 | 53 | } | |
| 54 | 54 | ||
@@ -63,7 +63,7 @@ async function testEncryptNoDecrypt({ keyBuffer, algorithm, plaintext }) { | |||
| 63 | 63 | const output = await subtle.encrypt(algorithm, key, plaintext); | |
| 64 | 64 | ||
| 65 | 65 | return assert.rejects(subtle.decrypt(algorithm, key, output), { | |
| 66 | - message: /The requested operation is not valid for the provided key/ | ||
| 66 | + message: /Unable to use this key to decrypt/ | ||
| 67 | 67 | }); | |
| 68 | 68 | } | |
| 69 | 69 | ||
@@ -77,7 +77,22 @@ async function testEncryptWrongAlg({ keyBuffer, algorithm, plaintext }, alg) { | |||
| 77 | 77 | ['encrypt']); | |
| 78 | 78 | ||
| 79 | 79 | return assert.rejects(subtle.encrypt(algorithm, key, plaintext), { | |
| 80 | - message: /The requested operation is not valid for the provided key/ | ||
| 80 | + message: /Key algorithm mismatch/ | ||
| 81 | + }); | ||
| 82 | + } | ||
| 83 | + | ||
| 84 | + async function testDecryptWrongAlg({ keyBuffer, algorithm, result }, alg) { | ||
| 85 | + if (result === undefined) return; | ||
| 86 | + assert.notStrictEqual(algorithm.name, alg); | ||
| 87 | + const key = await subtle.importKey( | ||
| 88 | + 'raw-secret', | ||
| 89 | + keyBuffer, | ||
| 90 | + { name: alg }, | ||
| 91 | + false, | ||
| 92 | + ['decrypt']); | ||
| 93 | + | ||
| 94 | + return assert.rejects(subtle.decrypt(algorithm, key, result), { | ||
| 95 | + message: /Key algorithm mismatch/ | ||
| 81 | 96 | }); | |
| 82 | 97 | } | |
| 83 | 98 | ||
@@ -107,6 +122,7 @@ async function testDecrypt({ keyBuffer, algorithm, result }) { | |||
| 107 | 122 | variations.push(testEncryptNoEncrypt(vector)); | |
| 108 | 123 | variations.push(testEncryptNoDecrypt(vector)); | |
| 109 | 124 | variations.push(testEncryptWrongAlg(vector, 'AES-GCM')); | |
| 125 | + variations.push(testDecryptWrongAlg(vector, 'AES-GCM')); | ||
| 110 | 126 | }); | |
| 111 | 127 | ||
| 112 | 128 | failing.forEach((vector) => { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -147,7 +147,7 @@ async function testEncryptionWrongKey({ algorithm, | |||
| 147 | 147 | ['decrypt']); | |
| 148 | 148 | return assert.rejects( | |
| 149 | 149 | subtle.encrypt(algorithm, privateKey, plaintext), { | |
| 150 | - message: /The requested operation is not valid/ | ||
| 150 | + message: /Unable to use this key to encrypt/ | ||
| 151 | 151 | }); | |
| 152 | 152 | } | |
| 153 | 153 | ||
@@ -167,7 +167,7 @@ async function testEncryptionBadUsage({ algorithm, | |||
| 167 | 167 | ['decrypt']); | |
| 168 | 168 | return assert.rejects( | |
| 169 | 169 | subtle.encrypt(algorithm, publicKey, plaintext), { | |
| 170 | - message: /The requested operation is not valid/ | ||
| 170 | + message: /Unable to use this key to encrypt/ | ||
| 171 | 171 | }); | |
| 172 | 172 | } | |
| 173 | 173 | ||
@@ -191,7 +191,7 @@ async function testDecryptionWrongKey({ ciphertext, | |||
| 191 | 191 | ||
| 192 | 192 | return assert.rejects( | |
| 193 | 193 | subtle.decrypt(algorithm, publicKey, ciphertext), { | |
| 194 | - message: /The requested operation is not valid/ | ||
| 194 | + message: /Unable to use this key to decrypt/ | ||
| 195 | 195 | }); | |
| 196 | 196 | } | |
| 197 | 197 | ||
@@ -215,7 +215,7 @@ async function testDecryptionBadUsage({ ciphertext, | |||
| 215 | 215 | ||
| 216 | 216 | return assert.rejects( | |
| 217 | 217 | subtle.decrypt(algorithm, publicKey, ciphertext), { | |
| 218 | - message: /The requested operation is not valid/ | ||
| 218 | + message: /Unable to use this key to decrypt/ | ||
| 219 | 219 | }); | |
| 220 | 220 | } | |
| 221 | 221 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -43,14 +43,14 @@ const { subtle } = globalThis.crypto; | |||
| 43 | 43 | name: 'RSA-OAEP', | |
| 44 | 44 | }, privateKey, buf), { | |
| 45 | 45 | name: 'InvalidAccessError', | |
| 46 | - message: 'The requested operation is not valid for the provided key' | ||
| 46 | + message: 'Unable to use this key to encrypt' | ||
| 47 | 47 | }); | |
| 48 | 48 | ||
| 49 | 49 | await assert.rejects(() => subtle.decrypt({ | |
| 50 | 50 | name: 'RSA-OAEP', | |
| 51 | 51 | }, publicKey, ciphertext), { | |
| 52 | 52 | name: 'InvalidAccessError', | |
| 53 | - message: 'The requested operation is not valid for the provided key' | ||
| 53 | + message: 'Unable to use this key to decrypt' | ||
| 54 | 54 | }); | |
| 55 | 55 | } | |
| 56 | 56 | ||
@@ -88,14 +88,14 @@ if (!process.features.openssl_is_boringssl) { | |||
| 88 | 88 | name: 'RSA-OAEP', | |
| 89 | 89 | }, privateKey, buf), { | |
| 90 | 90 | name: 'InvalidAccessError', | |
| 91 | - message: 'The requested operation is not valid for the provided key' | ||
| 91 | + message: 'Unable to use this key to encrypt' | ||
| 92 | 92 | }); | |
| 93 | 93 | ||
| 94 | 94 | await assert.rejects(() => subtle.decrypt({ | |
| 95 | 95 | name: 'RSA-OAEP', | |
| 96 | 96 | }, publicKey, ciphertext), { | |
| 97 | 97 | name: 'InvalidAccessError', | |
| 98 | - message: 'The requested operation is not valid for the provided key' | ||
| 98 | + message: 'Unable to use this key to decrypt' | ||
| 99 | 99 | }); | |
| 100 | 100 | } | |
| 101 | 101 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -88,17 +88,17 @@ async function testVerify({ name, | |||
| 88 | 88 | // Test failure when using the wrong algorithms | |
| 89 | 89 | await assert.rejects( | |
| 90 | 90 | subtle.verify({ name, hash }, hmacKey, signature, plaintext), { | |
| 91 | - message: /Unable to use this key to verify/ | ||
| 91 | + message: /Key algorithm mismatch/ | ||
| 92 | 92 | }); | |
| 93 | 93 | ||
| 94 | 94 | await assert.rejects( | |
| 95 | 95 | subtle.verify({ name, hash }, rsaKeys.publicKey, signature, plaintext), { | |
| 96 | - message: /Unable to use this key to verify/ | ||
| 96 | + message: /Key algorithm mismatch/ | ||
| 97 | 97 | }); | |
| 98 | 98 | ||
| 99 | 99 | await assert.rejects( | |
| 100 | 100 | subtle.verify({ name, hash }, okpKeys.publicKey, signature, plaintext), { | |
| 101 | - message: /Unable to use this key to verify/ | ||
| 101 | + message: /Key algorithm mismatch/ | ||
| 102 | 102 | }); | |
| 103 | 103 | ||
| 104 | 104 | // Test failure when signature is altered | |
@@ -210,17 +210,17 @@ async function testSign({ name, | |||
| 210 | 210 | // Test failure when using the wrong algorithms | |
| 211 | 211 | await assert.rejects( | |
| 212 | 212 | subtle.sign({ name, hash }, hmacKey, plaintext), { | |
| 213 | - message: /Unable to use this key to sign/ | ||
| 213 | + message: /Key algorithm mismatch/ | ||
| 214 | 214 | }); | |
| 215 | 215 | ||
| 216 | 216 | await assert.rejects( | |
| 217 | 217 | subtle.sign({ name, hash }, rsaKeys.privateKey, plaintext), { | |
| 218 | - message: /Unable to use this key to sign/ | ||
| 218 | + message: /Key algorithm mismatch/ | ||
| 219 | 219 | }); | |
| 220 | 220 | ||
| 221 | 221 | await assert.rejects( | |
| 222 | 222 | subtle.sign({ name, hash }, okpKeys.privateKey, plaintext), { | |
| 223 | - message: /Unable to use this key to sign/ | ||
| 223 | + message: /Key algorithm mismatch/ | ||
| 224 | 224 | }); | |
| 225 | 225 | } | |
| 226 | 226 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -101,17 +101,17 @@ async function testVerify({ name, | |||
| 101 | 101 | // Test failure when using the wrong algorithms | |
| 102 | 102 | await assert.rejects( | |
| 103 | 103 | subtle.verify({ name, context }, hmacKey, signature, data), { | |
| 104 | - message: /Unable to use this key to verify/ | ||
| 104 | + message: /Key algorithm mismatch/ | ||
| 105 | 105 | }); | |
| 106 | 106 | ||
| 107 | 107 | await assert.rejects( | |
| 108 | 108 | subtle.verify({ name, context }, rsaKeys.publicKey, signature, data), { | |
| 109 | - message: /Unable to use this key to verify/ | ||
| 109 | + message: /Key algorithm mismatch/ | ||
| 110 | 110 | }); | |
| 111 | 111 | ||
| 112 | 112 | await assert.rejects( | |
| 113 | 113 | subtle.verify({ name, context }, ecKeys.publicKey, signature, data), { | |
| 114 | - message: /Unable to use this key to verify/ | ||
| 114 | + message: /Key algorithm mismatch/ | ||
| 115 | 115 | }); | |
| 116 | 116 | ||
| 117 | 117 | if (name === 'Ed448' && supportsContext) { | |
@@ -227,17 +227,17 @@ async function testSign({ name, | |||
| 227 | 227 | // Test failure when using the wrong algorithms | |
| 228 | 228 | await assert.rejects( | |
| 229 | 229 | subtle.sign({ name, context }, hmacKey, data), { | |
| 230 | - message: /Unable to use this key to sign/ | ||
| 230 | + message: /Key algorithm mismatch/ | ||
| 231 | 231 | }); | |
| 232 | 232 | ||
| 233 | 233 | await assert.rejects( | |
| 234 | 234 | subtle.sign({ name, context }, rsaKeys.privateKey, data), { | |
| 235 | - message: /Unable to use this key to sign/ | ||
| 235 | + message: /Key algorithm mismatch/ | ||
| 236 | 236 | }); | |
| 237 | 237 | ||
| 238 | 238 | await assert.rejects( | |
| 239 | 239 | subtle.sign({ name, context }, ecKeys.privateKey, data), { | |
| 240 | - message: /Unable to use this key to sign/ | ||
| 240 | + message: /Key algorithm mismatch/ | ||
| 241 | 241 | }); | |
| 242 | 242 | ||
| 243 | 243 | if (name === 'Ed448' && supportsContext) { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -62,7 +62,7 @@ async function testVerify({ hash, | |||
| 62 | 62 | // Test failure when using the wrong algorithms | |
| 63 | 63 | await assert.rejects( | |
| 64 | 64 | subtle.verify({ name, hash }, rsaKeys.publicKey, signature, plaintext), { | |
| 65 | - message: /Unable to use this key to verify/ | ||
| 65 | + message: /Key algorithm mismatch/ | ||
| 66 | 66 | }); | |
| 67 | 67 | ||
| 68 | 68 | // Test failure when signature is altered | |
@@ -165,7 +165,7 @@ async function testSign({ hash, | |||
| 165 | 165 | // Test failure when using the wrong algorithms | |
| 166 | 166 | await assert.rejects( | |
| 167 | 167 | subtle.sign({ name, hash }, rsaKeys.privateKey, plaintext), { | |
| 168 | - message: /Unable to use this key to sign/ | ||
| 168 | + message: /Key algorithm mismatch/ | ||
| 169 | 169 | }); | |
| 170 | 170 | } | |
| 171 | 171 | ||
| Back | FazBrowse Home | New Git URL |
0 commit comments