Skip to content

Commit e190ef1

Browse files
reaperhulklkubb
andauthored
Backport ssh cert fix (#9211)
* Fix encoding of SSH certs with critical options (#9208) * Add tests for issue #9207 * Fix encoding of SSH certs with critical options * Test unexpected additional values for crit opts/exts * temporarily allow invalid ssh cert encoding --------- Co-authored-by: jeanluc <2163936+lkubb@users.noreply.github.com>
1 parent bb204c8 commit e190ef1

File tree

6 files changed

+122
-34
lines changed

6 files changed

+122
-34
lines changed

CHANGELOG.rst

+11
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,17 @@
11
Changelog
22
=========
33

4+
.. _v41-0-2:
5+
6+
41.0.2 - 2023-07-10
7+
~~~~~~~~~~~~~~~~~~~
8+
9+
* Fixed bugs in creating and parsing SSH certificates where critical options
10+
with values were handled incorrectly. Certificates are now created correctly
11+
and parsing accepts correct values as well as the previously generated
12+
invalid forms with a warning. In the next release, support for parsing these
13+
invalid forms will be removed.
14+
415
.. _v41-0-1:
516

617
41.0.1 - 2023-06-01

docs/development/test-vectors.rst

+4
Original file line numberDiff line numberDiff line change
@@ -866,6 +866,10 @@ Custom OpenSSH Certificate Test Vectors
866866
critical option.
867867
* ``p256-p256-non-lexical-crit-opts.pub`` - A certificate with critical
868868
options in non-lexical order.
869+
* ``p256-ed25519-non-singular-crit-opt-val.pub`` - A certificate with
870+
a critical option that contains more than one value.
871+
* ``p256-ed25519-non-singular-ext-val.pub`` - A certificate with
872+
an extension that contains more than one value.
869873
* ``dsa-p256.pub`` - A certificate with a DSA public key signed by a P256
870874
CA.
871875
* ``p256-dsa.pub`` - A certificate with a P256 public key signed by a DSA

src/cryptography/hazmat/primitives/serialization/ssh.py

+26-2
Original file line numberDiff line numberDiff line change
@@ -1063,6 +1063,20 @@ def _parse_exts_opts(exts_opts: memoryview) -> typing.Dict[bytes, bytes]:
10631063
if last_name is not None and bname < last_name:
10641064
raise ValueError("Fields not lexically sorted")
10651065
value, exts_opts = _get_sshstr(exts_opts)
1066+
if len(value) > 0:
1067+
try:
1068+
value, extra = _get_sshstr(value)
1069+
except ValueError:
1070+
warnings.warn(
1071+
"This certificate has an incorrect encoding for critical "
1072+
"options or extensions. This will be an exception in "
1073+
"cryptography 42",
1074+
utils.DeprecatedIn41,
1075+
stacklevel=4,
1076+
)
1077+
else:
1078+
if len(extra) > 0:
1079+
raise ValueError("Unexpected extra data after value")
10661080
result[bname] = bytes(value)
10671081
last_name = bname
10681082
return result
@@ -1450,12 +1464,22 @@ def sign(self, private_key: SSHCertPrivateKeyTypes) -> SSHCertificate:
14501464
fcrit = _FragList()
14511465
for name, value in self._critical_options:
14521466
fcrit.put_sshstr(name)
1453-
fcrit.put_sshstr(value)
1467+
if len(value) > 0:
1468+
foptval = _FragList()
1469+
foptval.put_sshstr(value)
1470+
fcrit.put_sshstr(foptval.tobytes())
1471+
else:
1472+
fcrit.put_sshstr(value)
14541473
f.put_sshstr(fcrit.tobytes())
14551474
fext = _FragList()
14561475
for name, value in self._extensions:
14571476
fext.put_sshstr(name)
1458-
fext.put_sshstr(value)
1477+
if len(value) > 0:
1478+
fextval = _FragList()
1479+
fextval.put_sshstr(value)
1480+
fext.put_sshstr(fextval.tobytes())
1481+
else:
1482+
fext.put_sshstr(value)
14591483
f.put_sshstr(fext.tobytes())
14601484
f.put_sshstr(b"") # RESERVED FIELD
14611485
# encode CA public key

tests/hazmat/primitives/test_ssh.py

+79-32
Original file line numberDiff line numberDiff line change
@@ -1115,26 +1115,28 @@ def test_loads_ssh_cert(self, backend):
11151115
# secp256r1 public key, ed25519 signing key
11161116
cert = load_ssh_public_identity(
11171117
b"ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbm"
1118-
b"lzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgtdU+dl9vD4xPi8afxERYo"
1119-
b"s0c0d9/3m7XGY6fGeSkqn0AAAAIbmlzdHAyNTYAAABBBAsuVFNNj/mMyFm2xB99"
1120-
b"G4xiaUJE1lZNjcp+S2tXYW5KorcHpusSlSqOkUPZ2l0644dgiNPDKR/R+BtYENC"
1121-
b"8aq8AAAAAAAAAAAAAAAEAAAAUdGVzdEBjcnlwdG9ncmFwaHkuaW8AAAAaAAAACm"
1122-
b"NyeXB0b3VzZXIAAAAIdGVzdHVzZXIAAAAAY7KyZAAAAAB2frXAAAAAAAAAAIIAA"
1123-
b"AAVcGVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9y"
1124-
b"d2FyZGluZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGV"
1125-
b"ybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAADMAAAALc3"
1126-
b"NoLWVkMjU1MTkAAAAg3P0eyGf2crKGwSlnChbLzTVOFKwQELE1Ve+EZ6rXF18AA"
1127-
b"ABTAAAAC3NzaC1lZDI1NTE5AAAAQKoij8BsPj/XLb45+wHmRWKNqXeZYXyDIj8J"
1128-
b"IE6dIymjEqq0TP6ntu5t59hTmWlDO85GnMXAVGBjFbeikBMfAQc= reaperhulk"
1129-
b"@despoina.local"
1118+
b"lzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgLfsFv9Gbc6LZSiJFWdYQl"
1119+
b"IMNI50GExXW0fBpgGVf+Y4AAAAIbmlzdHAyNTYAAABBBIzVyRgVLR4F38bIOLBN"
1120+
b"8CNm8Nf+eBHCVkKDKb9WDyLLD61CEmzjK/ORwFuSE4N60eIGbFidBf0D0xh7G6o"
1121+
b"TNxsAAAAAAAAAAAAAAAEAAAAUdGVzdEBjcnlwdG9ncmFwaHkuaW8AAAAaAAAACm"
1122+
b"NyeXB0b3VzZXIAAAAIdGVzdHVzZXIAAAAAY7KyZAAAAAB2frXAAAAAWAAAAA1mb"
1123+
b"3JjZS1jb21tYW5kAAAALAAAAChlY2hvIGFhYWFhYWFhYWFhYWFhYWFhYWFhYWFh"
1124+
b"YWFhYWFhYWFhYWFhAAAAD3ZlcmlmeS1yZXF1aXJlZAAAAAAAAACCAAAAFXBlcm1"
1125+
b"pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbm"
1126+
b"cAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wd"
1127+
b"HkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1"
1128+
b"NTE5AAAAICH6csEOmGbOfT2B/S/FJg3uyPsaPSZUZk2SVYlfs0KLAAAAUwAAAAt"
1129+
b"zc2gtZWQyNTUxOQAAAEDz2u7X5/TFbN7Ms7DP4yArhz1oWWYKkdAk7FGFkHfjtY"
1130+
b"/YfNQ8Oky3dCZRi7PnSzScEEjos7723dhF8/y99WwH reaperhulk@despoina."
1131+
b"local"
11301132
)
11311133
assert isinstance(cert, SSHCertificate)
11321134
cert.verify_cert_signature()
11331135
signature_key = cert.signature_key()
11341136
assert isinstance(signature_key, ed25519.Ed25519PublicKey)
11351137
assert cert.nonce == (
1136-
b"\xb5\xd5>v_o\x0f\x8cO\x8b\xc6\x9f\xc4DX\xa2\xcd\x1c\xd1\xdf"
1137-
b"\x7f\xden\xd7\x19\x8e\x9f\x19\xe4\xa4\xaa}"
1138+
b'-\xfb\x05\xbf\xd1\x9bs\xa2\xd9J"EY\xd6\x10\x94\x83\r#\x9d'
1139+
b"\x06\x13\x15\xd6\xd1\xf0i\x80e_\xf9\x8e"
11381140
)
11391141
public_key = cert.public_key()
11401142
assert isinstance(public_key, ec.EllipticCurvePublicKey)
@@ -1145,7 +1147,10 @@ def test_loads_ssh_cert(self, backend):
11451147
assert cert.valid_principals == [b"cryptouser", b"testuser"]
11461148
assert cert.valid_before == 1988015552
11471149
assert cert.valid_after == 1672655460
1148-
assert cert.critical_options == {}
1150+
assert cert.critical_options == {
1151+
b"force-command": b"echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1152+
b"verify-required": b"",
1153+
}
11491154
assert cert.extensions == {
11501155
b"permit-X11-forwarding": b"",
11511156
b"permit-agent-forwarding": b"",
@@ -1154,6 +1159,31 @@ def test_loads_ssh_cert(self, backend):
11541159
b"permit-user-rc": b"",
11551160
}
11561161

1162+
def test_loads_deprecated_invalid_encoding_cert(self, backend):
1163+
with pytest.warns(utils.DeprecatedIn41):
1164+
cert = load_ssh_public_identity(
1165+
b"ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYT"
1166+
b"ItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgXE7sJ+xDVVNCO"
1167+
b"cEvpZS+SXIbc0nJdny/KqVbnwHslMIAAAAIbmlzdHAyNTYAAABBBI/qcLq8"
1168+
b"iiErpAhOWRqdMkpFSCNv7TVUcXCIfAl01JXbe2MvS4V7lFtiyrBjLSV7Iyw"
1169+
b"3TrulrWLibjPzZvLwmQcAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAA//"
1170+
b"////////8AAABUAAAADWZvcmNlLWNvbW1hbmQAAAAoZWNobyBhYWFhYWFhY"
1171+
b"WFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYQAAAA92ZXJpZnktcmVxdWly"
1172+
b"ZWQAAAAAAAAAEgAAAApwZXJtaXQtcHR5AAAAAAAAAAAAAABoAAAAE2VjZHN"
1173+
b"hLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBI/qcLq8iiErpAhOWR"
1174+
b"qdMkpFSCNv7TVUcXCIfAl01JXbe2MvS4V7lFtiyrBjLSV7Iyw3TrulrWLib"
1175+
b"jPzZvLwmQcAAABlAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAABKAAAAIQCi"
1176+
b"eCsIhGKrZdkE1+zY5EBucrLzxFpwnm/onIT/6rapvQAAACEAuVQ1yQjlPKr"
1177+
b"kfsGfjeG+2umZrOS5Ycx85BQhYf0RgsA="
1178+
)
1179+
assert isinstance(cert, SSHCertificate)
1180+
cert.verify_cert_signature()
1181+
assert cert.extensions == {b"permit-pty": b""}
1182+
assert cert.critical_options == {
1183+
b"force-command": b"echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
1184+
b"verify-required": b"",
1185+
}
1186+
11571187
@pytest.mark.parametrize(
11581188
"filename",
11591189
[
@@ -1267,6 +1297,8 @@ def test_invalid_cert_type(self):
12671297
"p256-p256-non-lexical-extensions.pub",
12681298
"p256-p256-duplicate-crit-opts.pub",
12691299
"p256-p256-non-lexical-crit-opts.pub",
1300+
"p256-ed25519-non-singular-crit-opt-val.pub",
1301+
"p256-ed25519-non-singular-ext-val.pub",
12701302
],
12711303
)
12721304
def test_invalid_encodings(self, filename):
@@ -1693,6 +1725,11 @@ def test_sign_and_byte_compare_rsa(self, monkeypatch):
16931725
.valid_after(1672531200)
16941726
.valid_before(1672617600)
16951727
.type(SSHCertificateType.USER)
1728+
.add_extension(b"permit-pty", b"")
1729+
.add_critical_option(
1730+
b"force-command", b"echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1731+
)
1732+
.add_critical_option(b"verify-required", b"")
16961733
)
16971734
cert = builder.sign(private_key)
16981735
sig_key = cert.signature_key()
@@ -1707,19 +1744,21 @@ def test_sign_and_byte_compare_rsa(self, monkeypatch):
17071744
b"4kyHpbLEIVloBjzetoqXK6u8Hjz/APuagONypNDCySDR6M7jM85HDcLoFFrbBb8"
17081745
b"pruHSTxQejMeEmJxYf8b7rNl58/IWPB1ymbNlvHL/4oSOlnrtHkjcxRWzpQ7U3g"
17091746
b"T9BThGyhCiI7EMyEHMgP3r7kTzEUwT6IavWDAAAAAAAAAAAAAAABAAAAAAAAAAA"
1710-
b"AAAAAY7DNAAAAAABjsh6AAAAAAAAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAw"
1711-
b"EAAQAAAQEAwXr8fndHTKpaqDA2FYo/+/e1IWhRuiIw5dar/MHGz+9Z6SPqEzC8W"
1712-
b"TtzgCq2CKbkozBlI6MRa6WqOWYUUXThO2xJ6beAYuRJ1y77EP1J6R+gi5bQUeeC"
1713-
b"6fWrxbWm95hIJ6245z2gDyKy79zbduq0btrZjtZWYnQ/3GwOM2pdDNuqfcKeU2N"
1714-
b"eJMh6WyxCFZaAY83raKlyurvB48/wD7moDjcqTQwskg0ejO4zPORw3C6BRa2wW/"
1715-
b"Ka7h0k8UHozHhJicWH/G+6zZefPyFjwdcpmzZbxy/+KEjpZ67R5I3MUVs6UO1N4"
1716-
b"E/QU4RsoQoiOxDMhBzID96+5E8xFME+iGr1gwAAARQAAAAMcnNhLXNoYTItNTEy"
1717-
b"AAABAKCRnfhn6MZs3jRgIDICUpUyWrDCbpStEbdzhmoxF8w2m8klR7owRH/rxOf"
1718-
b"nWhKMGnXnoERS+az3Zh9ckiQPujkuEToORKpzu6CEWlzHSzyK1o2X548KkW76HJ"
1719-
b"gqzwMas94HY7UOJUgKSFUI0S3jAgqXAKSa1DxvJBu5/n57aUqPq+BmAtoI8uNBo"
1720-
b"x4F1pNEop38+oD7rUt8bZ8K0VcrubJZz806K8UNiK0mOahaEIkvZXBfzPGvSNRj"
1721-
b"0OjDl1dLUZaP8C1o5lVRomEm7pLcgE9i+ZDq5iz+mvQrSBStlpQ5hPGuUOrZ/oY"
1722-
b"ZLZ1G30R5tWj212MHoNZjxFxM8+f2OT4="
1747+
b"AAAAAY7DNAAAAAABjsh6AAAAAWAAAAA1mb3JjZS1jb21tYW5kAAAALAAAAChlY2"
1748+
b"hvIGFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhAAAAD3Zlcmlme"
1749+
b"S1yZXF1aXJlZAAAAAAAAAASAAAACnBlcm1pdC1wdHkAAAAAAAAAAAAAARcAAAAH"
1750+
b"c3NoLXJzYQAAAAMBAAEAAAEBAMF6/H53R0yqWqgwNhWKP/v3tSFoUboiMOXWq/z"
1751+
b"Bxs/vWekj6hMwvFk7c4Aqtgim5KMwZSOjEWulqjlmFFF04TtsSem3gGLkSdcu+x"
1752+
b"D9SekfoIuW0FHngun1q8W1pveYSCetuOc9oA8isu/c23bqtG7a2Y7WVmJ0P9xsD"
1753+
b"jNqXQzbqn3CnlNjXiTIelssQhWWgGPN62ipcrq7wePP8A+5qA43Kk0MLJINHozu"
1754+
b"MzzkcNwugUWtsFvymu4dJPFB6Mx4SYnFh/xvus2Xnz8hY8HXKZs2W8cv/ihI6We"
1755+
b"u0eSNzFFbOlDtTeBP0FOEbKEKIjsQzIQcyA/evuRPMRTBPohq9YMAAAEUAAAADH"
1756+
b"JzYS1zaGEyLTUxMgAAAQCYbbNzhflDqZAxyBpdLIX0nLAdnTeFNBudMqgo3KGND"
1757+
b"WlU9N17hqBEmcvIOrtNi+JKuKZW89zZrbORHvdjv6NjGSKzJD/XA25YrX1KgMEO"
1758+
b"wt5pzMZX+100drwrjQo+vZqeIN3FJNmT3wssge73v+JsxQrdIAz7YM2OZrFr5HM"
1759+
b"qZEZ5tMvAf/s5YEMDttEU4zMtmjubQyDM5KyYnZdoDT4sKi2rB8gfaigc4IdI/K"
1760+
b"8oXL/3Y7rHuOtejl3lUK4v6DxeRl4aqGYWmhUJc++Rh0cbDgC2S6Cq7gAfG2tND"
1761+
b"zbwL217Q93R08bJn1hDWuiTiaHGauSy2gPUI+cnkvlEocHM"
17231762
)
17241763

17251764
@pytest.mark.supported(
@@ -1745,6 +1784,11 @@ def test_sign_and_byte_compare_ed25519(self, monkeypatch, backend):
17451784
.valid_after(1672531200)
17461785
.valid_before(1672617600)
17471786
.type(SSHCertificateType.USER)
1787+
.add_extension(b"permit-pty", b"")
1788+
.add_critical_option(
1789+
b"force-command", b"echo aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1790+
)
1791+
.add_critical_option(b"verify-required", b"")
17481792
)
17491793
cert = builder.sign(private_key)
17501794
sig_key = cert.signature_key()
@@ -1754,8 +1798,11 @@ def test_sign_and_byte_compare_ed25519(self, monkeypatch, backend):
17541798
b"ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdj"
17551799
b"AxQG9wZW5zc2guY29tAAAAIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
17561800
b"AAAAAAAINdamAGCsQq31Uv+08lkBzoO4XLz2qYjJa8CGmj3B1EaAAAAAAAAAAAA"
1757-
b"AAABAAAAAAAAAAAAAAAAY7DNAAAAAABjsh6AAAAAAAAAAAAAAAAAAAAAMwAAAAt"
1758-
b"zc2gtZWQyNTUxOQAAACDXWpgBgrEKt9VL/tPJZAc6DuFy89qmIyWvAhpo9wdRGg"
1759-
b"AAAFMAAAALc3NoLWVkMjU1MTkAAABAAlF6Lxabxs+8fkOr7KjKYei9konIG13cQ"
1760-
b"gJ2tWf3yFcg3OuV5s/AkRmKdwHlQfTUrhRdOmDnGxeLEB0mvkVFCw=="
1801+
b"AAABAAAAAAAAAAAAAAAAY7DNAAAAAABjsh6AAAAAWAAAAA1mb3JjZS1jb21tYW5"
1802+
b"kAAAALAAAAChlY2hvIGFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYW"
1803+
b"FhAAAAD3ZlcmlmeS1yZXF1aXJlZAAAAAAAAAASAAAACnBlcm1pdC1wdHkAAAAAA"
1804+
b"AAAAAAAADMAAAALc3NoLWVkMjU1MTkAAAAg11qYAYKxCrfVS/7TyWQHOg7hcvPa"
1805+
b"piMlrwIaaPcHURoAAABTAAAAC3NzaC1lZDI1NTE5AAAAQL2aUjeD60C2FrbgHcN"
1806+
b"t8yRa8IRbxvOyA9TZYDGG1dRE3DiR0fuudU20v6vqfTd1gx0S5QyEdECXLl9ZI3"
1807+
b"AwZgc="
17611808
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIbmlzdHAyNTYAAABBBCZWRs4GYIHGJpyXuqvfFGWN49dnJRkZJLDkFrHf6mNHhIMI3vtrLfCZwxPSfnCYWK6YofssZ1FYA6TkVJq8Xi8AAAAAAAAAAAAAAAEAAAAAAAAAAAAAAABjsM0AAAAAAGOyHoAAAABtAAAADWZvcmNlLWNvbW1hbmQAAABBAAAAKGVjaG8gYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWEAAAARaW52YWxpZF9taXNjX2RhdGEAAAAPdmVyaWZ5LXJlcXVpcmVkAAAAAAAAABIAAAAKcGVybWl0LXB0eQAAAAAAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACDdZgztgAFFC7T5PifrUy/kMu0Pnwq1au3vStKHe7FFMAAAAFMAAAALc3NoLWVkMjU1MTkAAABAt/0pBSDBFy1crBPHOBoKFoxRjKd1tKVdOrD3QVgbBfpaHfxi4vrgYe6JfQ54+vu5P+8yrMyACekT8H6hhvxHDw==
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzdHAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIbmlzdHAyNTYAAABBBCZWRs4GYIHGJpyXuqvfFGWN49dnJRkZJLDkFrHf6mNHhIMI3vtrLfCZwxPSfnCYWK6YofssZ1FYA6TkVJq8Xi8AAAAAAAAAAAAAAAEAAAAAAAAAAAAAAABjsM0AAAAAAGOyHoAAAAAXAAAAD3ZlcmlmeS1yZXF1aXJlZAAAAAAAAAAvAAAAFGNvbnRhaW5zLWV4dHJhLXZhbHVlAAAAEwAAAAVoZWxsbwAAAAYgd29ybGQAAAAAAAAAMwAAAAtzc2gtZWQyNTUxOQAAACDdZgztgAFFC7T5PifrUy/kMu0Pnwq1au3vStKHe7FFMAAAAFMAAAALc3NoLWVkMjU1MTkAAABAY80oIEvooz/k3x9a+yVkjSNRfi4y/q87wVYiT7keTpP4n9JV/Vlc0u7O2QYOHfb4DUkcrvbsksKVsiqoQu5qDg==

0 commit comments

Comments
 (0)