Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F33095824
crypto-test.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Size
21 KB
Referenced Files
None
Subscribers
None
crypto-test.cpp
View Options
/*
* This file is part of libkazv.
* SPDX-FileCopyrightText: 2020-2024 tusooa <tusooa@kazv.moe>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#include
<libkazv-config.hpp>
#include
<catch2/catch_all.hpp>
#include
<sstream>
#include
<boost/archive/text_iarchive.hpp>
#include
<boost/archive/text_oarchive.hpp>
#include
<crypto/crypto.hpp>
#include
<aes-256-ctr.hpp>
#include
<base64.hpp>
#include
<sha256.hpp>
#include
"crypto/crypto-test-resource.hpp"
using
namespace
Kazv
;
using
namespace
Kazv
::
CryptoConstants
;
using
IAr
=
boost
::
archive
::
text_iarchive
;
using
OAr
=
boost
::
archive
::
text_oarchive
;
static
const
auto
resource
=
cryptoDumpResource
();
json
makeEncryptedJson
(
json
ciphertext
,
std
::
string
senderKey
)
{
return
json
{{
"content"
,
{
{
"algorithm"
,
olmAlgo
},
{
"ciphertext"
,
std
::
move
(
ciphertext
)},
{
"sender_key"
,
std
::
move
(
senderKey
)},
}}};
}
template
<
class
T
>
static
void
serializeDup
(
const
T
&
in
,
T
&
out
)
{
std
::
stringstream
stream
;
{
auto
ar
=
OAr
(
stream
);
ar
<<
in
;
}
{
auto
ar
=
IAr
(
stream
);
ar
>>
out
;
}
}
static
bool
doesDecryptTo
(
Crypto
&
crypto
,
const
nlohmann
::
json
&
encryptedEvent
,
const
nlohmann
::
json
&
plainText
)
{
auto
res
=
crypto
.
decrypt
(
encryptedEvent
);
if
(
!
res
)
{
return
false
;
}
auto
decrypted
=
json
::
parse
(
res
.
value
());
return
decrypted
==
plainText
;
}
TEST_CASE
(
"Crypto constructors"
,
"[crypto]"
)
{
Crypto
crypto
;
REQUIRE
(
!
crypto
.
valid
());
Crypto
crypto2
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
REQUIRE
(
crypto2
.
valid
());
}
TEST_CASE
(
"Crypto conversion from libolm to vodozemac"
,
"[crypto]"
)
{
Crypto
a
;
a
.
loadJson
(
resource
[
"a"
]);
Crypto
b
;
b
.
loadJson
(
resource
[
"b"
]);
// encrypt with existing sessions
auto
aIdKey
=
a
.
curve25519IdentityKey
();
auto
origJson
=
json
{{
"test"
,
"mew"
}};
{
auto
encryptedMsg
=
b
.
encryptOlmWithRandom
(
genRandomData
(
Crypto
::
encryptOlmMaxRandomSize
()),
origJson
,
aIdKey
);
auto
decryptedOpt
=
a
.
decrypt
(
makeEncryptedJson
(
encryptedMsg
,
b
.
curve25519IdentityKey
()));
REQUIRE
(
decryptedOpt
);
}
// encrypt/decrypt with new sessions
auto
k
=
a
.
unpublishedOneTimeKeys
();
a
.
markOneTimeKeysAsPublished
();
auto
oneTimeKey
=
std
::
string
{};
for
(
auto
[
id
,
key
]
:
k
[
curve25519
].
items
())
{
oneTimeKey
=
key
;
}
{
Crypto
c
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
c
.
createOutboundSessionWithRandom
(
genRandomData
(
Crypto
::
createOutboundSessionRandomSize
()),
aIdKey
,
oneTimeKey
);
auto
encryptedMsg
=
c
.
encryptOlmWithRandom
(
genRandomData
(
Crypto
::
encryptOlmMaxRandomSize
()),
origJson
,
aIdKey
);
auto
decryptedOpt
=
a
.
decrypt
(
makeEncryptedJson
(
encryptedMsg
,
c
.
curve25519IdentityKey
()));
REQUIRE
(
decryptedOpt
.
reason
()
==
""
);
REQUIRE
(
decryptedOpt
);
REQUIRE
(
decryptedOpt
.
value
()
==
origJson
.
dump
());
}
}
TEST_CASE
(
"Crypto should be copyable"
,
"[crypto]"
)
{
Crypto
crypto
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
crypto
.
genOneTimeKeysWithRandom
(
genRandomData
(
Crypto
::
genOneTimeKeysRandomSize
(
1
)),
1
);
auto
oneTimeKeys
=
crypto
.
unpublishedOneTimeKeys
();
Crypto
cryptoClone
(
crypto
);
REQUIRE
(
crypto
.
ed25519IdentityKey
()
==
cryptoClone
.
ed25519IdentityKey
());
REQUIRE
(
crypto
.
curve25519IdentityKey
()
==
cryptoClone
.
curve25519IdentityKey
());
auto
oneTimeKeys2
=
cryptoClone
.
unpublishedOneTimeKeys
();
REQUIRE
(
oneTimeKeys
==
oneTimeKeys2
);
REQUIRE
(
crypto
.
numUnpublishedOneTimeKeys
()
==
cryptoClone
.
numUnpublishedOneTimeKeys
());
}
TEST_CASE
(
"Crypto should be serializable"
,
"[crypto]"
)
{
Crypto
crypto
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
crypto
.
genOneTimeKeysWithRandom
(
genRandomData
(
Crypto
::
genOneTimeKeysRandomSize
(
1
)),
1
);
auto
oneTimeKeys
=
crypto
.
unpublishedOneTimeKeys
();
Crypto
cryptoClone
;
serializeDup
(
crypto
,
cryptoClone
);
REQUIRE
(
crypto
.
ed25519IdentityKey
()
==
cryptoClone
.
ed25519IdentityKey
());
REQUIRE
(
crypto
.
curve25519IdentityKey
()
==
cryptoClone
.
curve25519IdentityKey
());
auto
oneTimeKeys2
=
cryptoClone
.
unpublishedOneTimeKeys
();
REQUIRE
(
oneTimeKeys
==
oneTimeKeys2
);
REQUIRE
(
crypto
.
numUnpublishedOneTimeKeys
()
==
cryptoClone
.
numUnpublishedOneTimeKeys
());
}
TEST_CASE
(
"Invalid Crypto should be serializable"
,
"[crypto]"
)
{
Crypto
crypto
;
Crypto
cryptoClone
;
serializeDup
(
crypto
,
cryptoClone
);
REQUIRE
(
!
cryptoClone
.
valid
());
}
TEST_CASE
(
"Serialize Crypto with an OutboundGroupSession"
,
"[crypto]"
)
{
Crypto
crypto
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
std
::
string
roomId
=
"!example:example.org"
;
auto
desc
=
MegOlmSessionRotateDesc
{
500000
/* ms */
,
100
/* messages */
};
crypto
.
rotateMegOlmSessionWithRandom
(
genRandomData
(
Crypto
::
rotateMegOlmSessionRandomSize
()),
currentTimeMs
(),
roomId
);
Crypto
cryptoClone
;
serializeDup
(
crypto
,
cryptoClone
);
REQUIRE
(
!
cryptoClone
.
rotateMegOlmSessionWithRandomIfNeeded
(
genRandomData
(
Crypto
::
rotateMegOlmSessionRandomSize
()),
currentTimeMs
(),
roomId
,
desc
).
has_value
());
}
TEST_CASE
(
"Generating and publishing keys should work"
,
"[crypto]"
)
{
Crypto
crypto
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
crypto
.
genOneTimeKeysWithRandom
(
genRandomData
(
Crypto
::
genOneTimeKeysRandomSize
(
1
)),
1
);
REQUIRE
(
crypto
.
numUnpublishedOneTimeKeys
()
==
1
);
crypto
.
genOneTimeKeysWithRandom
(
genRandomData
(
Crypto
::
genOneTimeKeysRandomSize
(
1
)),
1
);
REQUIRE
(
crypto
.
numUnpublishedOneTimeKeys
()
==
2
);
crypto
.
markOneTimeKeysAsPublished
();
REQUIRE
(
crypto
.
numUnpublishedOneTimeKeys
()
==
0
);
}
TEST_CASE
(
"Should reuse existing inbound session to encrypt"
,
"[crypto]"
)
{
Crypto
a
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
Crypto
b
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
a
.
genOneTimeKeysWithRandom
(
genRandomData
(
Crypto
::
genOneTimeKeysRandomSize
(
1
)),
1
);
// Get A publish the key and send to B
auto
k
=
a
.
unpublishedOneTimeKeys
();
a
.
markOneTimeKeysAsPublished
();
auto
oneTimeKey
=
std
::
string
{};
for
(
auto
[
id
,
key
]
:
k
[
curve25519
].
items
())
{
oneTimeKey
=
key
;
}
auto
aIdKey
=
a
.
curve25519IdentityKey
();
b
.
createOutboundSessionWithRandom
(
genRandomData
(
Crypto
::
createOutboundSessionRandomSize
()),
aIdKey
,
oneTimeKey
);
auto
origJson
=
json
{{
"test"
,
"mew"
}};
auto
encryptedMsg
=
b
.
encryptOlmWithRandom
(
genRandomData
(
Crypto
::
encryptOlmMaxRandomSize
()),
origJson
,
aIdKey
);
auto
encJson
=
json
{
{
"content"
,
{
{
"algorithm"
,
olmAlgo
},
{
"ciphertext"
,
encryptedMsg
},
{
"sender_key"
,
b
.
curve25519IdentityKey
()}
}
}
};
auto
decryptedOpt
=
a
.
decrypt
(
encJson
);
REQUIRE
(
decryptedOpt
);
auto
decryptedJson
=
json
::
parse
(
decryptedOpt
.
value
());
REQUIRE
(
decryptedJson
==
origJson
);
using
StrMap
=
immer
::
map
<
std
::
string
,
std
::
string
>
;
auto
devMap
=
immer
::
map
<
std
::
string
,
StrMap
>
()
.
set
(
"b"
,
StrMap
().
set
(
"dev"
,
b
.
curve25519IdentityKey
()));
Crypto
aClone
{
a
};
auto
devices
=
a
.
devicesMissingOutboundSessionKey
(
devMap
);
auto
devicesAClone
=
aClone
.
devicesMissingOutboundSessionKey
(
devMap
);
// No device should be missing an olm session, as A has received an
// inbound olm session before.
auto
expected
=
immer
::
map
<
std
::
string
,
immer
::
flex_vector
<
std
::
string
>>
();
REQUIRE
(
devices
==
expected
);
REQUIRE
(
devicesAClone
==
expected
);
}
TEST_CASE
(
"Encrypt and decrypt AES-256-CTR"
,
"[crypto][aes256ctr]"
)
{
auto
r
=
genRandom
(
AES256CTRDesc
::
randomSize
);
auto
desc
=
AES256CTRDesc
::
fromRandom
(
r
);
auto
desc2
=
desc
;
std
::
string
original
=
"test for aes-256-ctr"
;
auto
encrypted
=
desc
.
processInPlace
(
original
);
auto
decrypted
=
desc2
.
processInPlace
(
encrypted
);
REQUIRE
(
original
==
decrypted
);
}
TEST_CASE
(
"Encrypt and decrypt AES-256-CTR with any sequence type"
,
"[crypto][aes256ctr]"
)
{
auto
r
=
genRandom
(
AES256CTRDesc
::
randomSize
);
auto
desc
=
AES256CTRDesc
::
fromRandom
(
r
);
auto
desc2
=
desc
;
std
::
string
oStr
=
"test for aes-256-ctr"
;
std
::
vector
<
unsigned
char
>
original
(
oStr
.
begin
(),
oStr
.
end
());
auto
encrypted
=
desc
.
processInPlace
(
original
);
auto
decrypted
=
desc2
.
processInPlace
(
encrypted
);
REQUIRE
(
original
==
decrypted
);
}
TEST_CASE
(
"Encrypt and decrypt AES-256-CTR in a non-destructive way"
,
"[crypto][aes256ctr]"
)
{
auto
r
=
genRandom
(
AES256CTRDesc
::
randomSize
);
auto
desc
=
AES256CTRDesc
::
fromRandom
(
r
);
std
::
string
original
=
"test for aes-256-ctr"
;
auto
[
next
,
encrypted
]
=
desc
.
process
(
original
);
auto
[
next2
,
encrypted2
]
=
desc
.
process
(
original
);
REQUIRE
(
encrypted
==
encrypted2
);
auto
[
next3
,
decrypted
]
=
desc
.
process
(
encrypted
);
REQUIRE
(
original
==
decrypted
);
}
TEST_CASE
(
"Encrypt and decrypt AES-256-CTR in batches"
,
"[crypto][aes256ctr]"
)
{
auto
r
=
genRandom
(
AES256CTRDesc
::
randomSize
);
auto
desc
=
AES256CTRDesc
::
fromRandom
(
r
);
std
::
string
original
=
"test for aes-256-ctr"
;
std
::
string
orig2
=
"another test string..."
;
auto
[
next
,
encrypted
]
=
desc
.
process
(
original
);
auto
[
next2
,
encrypted2
]
=
next
.
process
(
orig2
);
auto
[
next3
,
decrypted
]
=
desc
.
process
(
encrypted
+
encrypted2
);
REQUIRE
(
original
+
orig2
==
decrypted
);
}
TEST_CASE
(
"AES-256-CTR should be movable"
,
"[crypto][aes256ctr]"
)
{
auto
r
=
genRandom
(
AES256CTRDesc
::
randomSize
);
auto
desc
=
AES256CTRDesc
::
fromRandom
(
r
);
auto
desc2
=
std
::
move
(
desc
);
REQUIRE
(
desc2
.
valid
());
REQUIRE
(
!
desc
.
valid
());
std
::
string
original
=
"test for aes-256-ctr"
;
std
::
string
encrypted
;
// Can be moved from itself
desc2
=
std
::
move
(
desc2
);
REQUIRE
(
desc2
.
valid
());
std
::
tie
(
desc2
,
encrypted
)
=
std
::
move
(
desc2
).
process
(
original
);
REQUIRE
(
desc2
.
valid
());
}
TEST_CASE
(
"AES-256-CTR should be copyable"
,
"[crypto][aes256ctr]"
)
{
auto
r
=
genRandom
(
AES256CTRDesc
::
randomSize
);
auto
desc
=
AES256CTRDesc
::
fromRandom
(
r
);
auto
desc2
=
desc
;
REQUIRE
(
desc2
.
valid
());
REQUIRE
(
desc
.
valid
());
desc
=
AES256CTRDesc
::
fromRandom
(
RandomData
{});
std
::
string
original
=
"test for aes-256-ctr"
;
std
::
string
encrypted
;
REQUIRE
(
desc2
.
valid
());
std
::
tie
(
desc2
,
encrypted
)
=
desc2
.
process
(
original
);
REQUIRE
(
desc2
.
valid
());
}
TEST_CASE
(
"Construct AES-256-CTR from known key and iv"
,
"[crypto][aes256ctr]"
)
{
auto
r
=
genRandom
(
AES256CTRDesc
::
randomSize
);
auto
desc
=
AES256CTRDesc
::
fromRandom
(
r
);
auto
desc2
=
AES256CTRDesc
(
desc
.
key
(),
desc
.
iv
());
REQUIRE
(
desc2
.
valid
());
REQUIRE
(
desc
.
key
()
==
desc2
.
key
());
REQUIRE
(
desc
.
iv
()
==
desc2
.
iv
());
}
TEST_CASE
(
"AES-256-CTR validity check"
,
"[crypto][aes256ctr]"
)
{
SECTION
(
"Not enough random, should reject"
)
{
ByteArray
random
=
genRandom
(
AES256CTRDesc
::
randomSize
-
1
);
auto
desc
=
AES256CTRDesc
::
fromRandom
(
random
);
REQUIRE
(
!
desc
.
valid
());
}
SECTION
(
"More than enough random, should accept"
)
{
ByteArray
random
=
genRandom
(
AES256CTRDesc
::
randomSize
+
1
);
auto
desc
=
AES256CTRDesc
::
fromRandom
(
random
);
REQUIRE
(
desc
.
valid
());
}
}
TEST_CASE
(
"AES256CTRDesc::fromRandom() should leave the lower 8 bytes as 0 for the counter"
,
"[crypto][aes256ctr]"
)
{
auto
r
=
genRandom
(
AES256CTRDesc
::
randomSize
);
auto
desc
=
AES256CTRDesc
::
fromRandom
(
r
);
auto
iv
=
decodeBase64
(
desc
.
iv
());
REQUIRE
(
iv
.
size
()
==
AES256CTRDesc
::
ivSize
);
REQUIRE
(
std
::
all_of
(
iv
.
begin
()
+
AES256CTRDesc
::
ivSizeInit
,
iv
.
end
(),
[](
auto
ch
)
{
return
ch
==
0
;
}));
auto
desc2
=
desc
;
std
::
string
original
=
"test for aes-256-ctr"
;
auto
encrypted
=
desc
.
processInPlace
(
original
);
auto
decrypted
=
desc2
.
processInPlace
(
encrypted
);
REQUIRE
(
original
==
decrypted
);
}
TEST_CASE
(
"Base64 encoder and decoder"
,
"[crypto][base64]"
)
{
std
::
string
orig
=
"The Quick Brown Fox Jumps Over the Lazy Dog"
;
// no padding
std
::
string
expected
=
"VGhlIFF1aWNrIEJyb3duIEZveCBKdW1wcyBPdmVyIHRoZSBMYXp5IERvZw"
;
std
::
string
encoded
=
encodeBase64
(
orig
);
REQUIRE
(
encoded
==
expected
);
std
::
string
decoded
=
decodeBase64
(
encoded
);
REQUIRE
(
decoded
==
orig
);
}
TEST_CASE
(
"Urlsafe base64 encoder and decoder"
,
"[crypto][base64]"
)
{
std
::
string
orig
=
"The Quick Brown Fox Jumps Over the Lazy Dog"
;
// no padding
std
::
string
expected
=
"VGhlIFF1aWNrIEJyb3duIEZveCBKdW1wcyBPdmVyIHRoZSBMYXp5IERvZw"
;
std
::
string
encoded
=
encodeBase64
(
orig
,
Base64Opts
::
urlSafe
);
REQUIRE
(
encoded
==
expected
);
std
::
string
decoded
=
decodeBase64
(
encoded
,
Base64Opts
::
urlSafe
);
REQUIRE
(
decoded
==
orig
);
}
TEST_CASE
(
"Base64 encoder and decoder, example from Matrix specs"
,
"[crypto][base64]"
)
{
std
::
string
orig
=
"JGLn/yafz74HB2AbPLYJWIVGnKAtqECOBf11yyXac2Y"
;
std
::
string
decoded
=
decodeBase64
(
orig
);
std
::
string
encoded
=
encodeBase64
(
decoded
);
REQUIRE
(
encoded
==
orig
);
}
TEST_CASE
(
"Urlsafe base64 encoder and decoder, example from Matrix specs"
,
"[crypto][base64]"
)
{
std
::
string
orig
=
"JGLn_yafz74HB2AbPLYJWIVGnKAtqECOBf11yyXac2Y"
;
std
::
string
decoded
=
decodeBase64
(
orig
,
Base64Opts
::
urlSafe
);
std
::
string
encoded
=
encodeBase64
(
decoded
,
Base64Opts
::
urlSafe
);
REQUIRE
(
encoded
==
orig
);
}
TEST_CASE
(
"SHA256 hashing support"
,
"[crypto][sha256]"
)
{
auto
hash
=
SHA256Desc
{};
auto
message1
=
std
::
string
(
"12345678910"
);
hash
.
processInPlace
(
message1
);
auto
res
=
hash
.
get
();
auto
expected
=
std
::
string
(
"Y2QCZISah8kDVhKdmeoWXjeqX6vB/qRpBt8afKUNtJI"
);
REQUIRE
(
res
==
expected
);
}
TEST_CASE
(
"SHA256 hashing streaming"
,
"[crypto][sha256]"
)
{
auto
hash
=
SHA256Desc
{};
auto
message1
=
std
::
string
(
"12345678910"
);
auto
message2
=
std
::
string
(
"abcdefghijklmn"
);
hash
.
processInPlace
(
message1
);
hash
.
processInPlace
(
message2
);
auto
res
=
hash
.
get
();
auto
hash2
=
SHA256Desc
{};
hash2
.
processInPlace
(
message1
+
message2
);
auto
expected
=
hash2
.
get
();
REQUIRE
(
res
==
expected
);
}
TEST_CASE
(
"SHA256Desc should be copyable"
,
"[crypto][sha256]"
)
{
auto
hash
=
SHA256Desc
{};
auto
message1
=
std
::
string
(
"12345678910"
);
auto
message2
=
std
::
string
(
"abcdefghijklmn"
);
hash
.
processInPlace
(
message1
);
auto
hash2
=
hash
;
hash
.
processInPlace
(
message2
);
hash2
.
processInPlace
(
message2
);
auto
res
=
hash
.
get
();
auto
res2
=
hash2
.
get
();
REQUIRE
(
res
==
res2
);
}
TEST_CASE
(
"SHA256Desc should be self-copyable and -movable"
,
"[crypto][sha256]"
)
{
auto
hash
=
SHA256Desc
{};
auto
message1
=
std
::
string
(
"12345678910"
);
hash
=
hash
.
process
(
message1
);
auto
message2
=
std
::
string
(
"abcdefghijklmn"
);
hash
=
std
::
move
(
hash
).
process
(
message2
);
auto
hash2
=
SHA256Desc
{};
hash2
.
processInPlace
(
message1
+
message2
);
auto
res
=
hash
.
get
();
auto
res2
=
hash2
.
get
();
REQUIRE
(
res
==
res2
);
}
TEST_CASE
(
"SHA256 should accept any range type"
,
"[crypto][sha256]"
)
{
std
::
string
msg
=
"12345678910"
;
std
::
vector
<
char
>
arr
(
msg
.
begin
(),
msg
.
end
());
auto
hash
=
SHA256Desc
{};
auto
res1
=
hash
.
process
(
arr
).
get
();
auto
res2
=
std
::
move
(
hash
).
process
(
arr
).
get
();
// after moving, hash is no longer valid, reset it here
hash
=
SHA256Desc
{};
hash
.
processInPlace
(
arr
);
auto
res3
=
hash
.
get
();
hash
=
SHA256Desc
{};
auto
reference
=
hash
.
process
(
msg
).
get
();
REQUIRE
(
res1
==
res2
);
REQUIRE
(
res1
==
res3
);
REQUIRE
(
res1
==
reference
);
}
TEST_CASE
(
"Crypto::createInboundGroupSession should not allow session key replacement attacks"
,
"[crypto][group-session]"
)
{
std
::
string
roomId
=
"!someroom:example.com"
;
Crypto
a
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
// creating a outbound group session will add it to inbound group sessions
auto
initialKey
=
a
.
rotateMegOlmSessionWithRandom
(
genRandomData
(
Crypto
::
rotateMegOlmSessionRandomSize
()),
0
,
roomId
);
// encrypt to get the session id
auto
plainText
=
R
"
(
{
"content": {},
"type": "m.room.message",
"room_id": "!someroom:example.com"
}
)
"
_json
;
auto
encryptedContent
=
a
.
encryptMegOlm
(
plainText
);
auto
encryptedEvent
=
json
{
{
"event_id"
,
"$some-event-id1"
},
{
"origin_server_ts"
,
1719196953000
},
{
"content"
,
encryptedContent
},
{
"type"
,
"m.room.encrypted"
},
{
"room_id"
,
roomId
},
};
// message index is currently at 1
auto
currentKey
=
a
.
outboundGroupSessionCurrentKey
(
roomId
);
auto
sessionId
=
encryptedContent
[
"session_id"
].
template
get
<
std
::
string
>
();
auto
plainText2
=
R
"
(
{
"content": {"a": "b"},
"type": "m.room.message",
"room_id": "!someroom:example.com"
}
)
"
_json
;
auto
encryptedContent2
=
a
.
encryptMegOlm
(
plainText2
);
Crypto
b
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
Crypto
malice
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
auto
encryptedEvent2
=
json
{
{
"event_id"
,
"$some-event-id"
},
{
"origin_server_ts"
,
1719196953000
},
{
"content"
,
encryptedContent2
},
{
"type"
,
"m.room.encrypted"
},
{
"room_id"
,
roomId
},
};
auto
keyOfSession
=
KeyOfGroupSession
{
roomId
,
sessionId
};
auto
created
=
b
.
createInboundGroupSession
(
keyOfSession
,
currentKey
,
a
.
ed25519IdentityKey
());
REQUIRE
(
created
);
REQUIRE
(
doesDecryptTo
(
b
,
encryptedEvent2
,
plainText2
));
SECTION
(
"it should reject if the identity key is not the same"
)
{
auto
updated
=
b
.
createInboundGroupSession
(
keyOfSession
,
initialKey
,
malice
.
ed25519IdentityKey
());
REQUIRE
(
!
updated
);
}
SECTION
(
"it should reject if the given session key does not belong to the same session"
)
{
auto
anotherSessionKey
=
a
.
rotateMegOlmSessionWithRandom
(
genRandomData
(
Crypto
::
rotateMegOlmSessionRandomSize
()),
0
,
roomId
);
auto
updated
=
b
.
createInboundGroupSession
(
keyOfSession
,
anotherSessionKey
,
a
.
ed25519IdentityKey
());
REQUIRE
(
!
updated
);
}
SECTION
(
"it should prevent replay attack if merging with itself"
)
{
auto
updated
=
b
.
createInboundGroupSession
(
keyOfSession
,
currentKey
,
a
.
ed25519IdentityKey
());
REQUIRE
(
updated
);
REQUIRE
(
doesDecryptTo
(
b
,
encryptedEvent2
,
plainText2
));
auto
replay
=
encryptedEvent2
;
replay
[
"event_id"
]
=
"$some-other-id"
;
REQUIRE
(
!
b
.
decrypt
(
replay
).
has_value
());
}
SECTION
(
"it should prevent replay attack if session is updated"
)
{
auto
updated
=
b
.
createInboundGroupSession
(
keyOfSession
,
initialKey
,
a
.
ed25519IdentityKey
());
REQUIRE
(
updated
);
REQUIRE
(
doesDecryptTo
(
b
,
encryptedEvent2
,
plainText2
));
REQUIRE
(
doesDecryptTo
(
b
,
encryptedEvent
,
plainText
));
auto
replay
=
encryptedEvent2
;
replay
[
"event_id"
]
=
"$some-other-id"
;
REQUIRE
(
!
b
.
decrypt
(
replay
).
has_value
());
}
}
TEST_CASE
(
"Crypto::hasInboundGroupSession"
,
"[crypto][group-session]"
)
{
Crypto
crypto
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
// creating a outbound group session will add it to inbound group sessions
crypto
.
rotateMegOlmSessionWithRandom
(
genRandomData
(
Crypto
::
rotateMegOlmSessionRandomSize
()),
0
,
"!someroom:example.com"
);
// encrypt to get the session id
auto
encryptedContent
=
crypto
.
encryptMegOlm
(
R
"
(
{
"content": {},
"type": "m.room.message",
"room_id": "!someroom:example.com"
}
)
"
_json
);
auto
sessionId
=
encryptedContent
[
"session_id"
].
template
get
<
std
::
string
>
();
REQUIRE
(
crypto
.
hasInboundGroupSession
(
KeyOfGroupSession
{
"!someroom:example.com"
,
sessionId
,
}));
REQUIRE
(
!
crypto
.
hasInboundGroupSession
(
KeyOfGroupSession
{
"!someroom:example.com"
,
sessionId
+
"something something"
,
}));
}
TEST_CASE
(
"Crypto::decrypt(MegOlmEvent)"
,
"[crypto][group-session]"
)
{
Crypto
crypto
(
RandomTag
{},
genRandomData
(
Crypto
::
constructRandomSize
()));
crypto
.
rotateMegOlmSessionWithRandom
(
genRandomData
(
Crypto
::
rotateMegOlmSessionRandomSize
()),
0
,
"!someroom:example.com"
);
auto
plainText
=
R
"
(
{
"content": {"body": "something"},
"type": "m.room.message",
"room_id": "!someroom:example.com"
}
)
"
_json
;
// encrypt to get the session id
auto
encryptedContent
=
crypto
.
encryptMegOlm
(
plainText
);
auto
encryptedEvent
=
json
{
{
"event_id"
,
"$some-event-id"
},
{
"origin_server_ts"
,
1719196953000
},
{
"content"
,
encryptedContent
},
{
"type"
,
"m.room.encrypted"
},
{
"room_id"
,
"!someroom:example.com"
},
};
REQUIRE
(
doesDecryptTo
(
crypto
,
encryptedEvent
,
plainText
));
}
TEST_CASE
(
"KeyOfGroupSession serialization"
,
"[crypto][group-session]"
)
{
{
auto
k
=
KeyOfGroupSession
{
"!someroom:example.com"
,
"some-session-id"
};
json
j
=
k
;
REQUIRE
(
j
==
json
{
{
"roomId"
,
"!someroom:example.com"
},
{
"sessionId"
,
"some-session-id"
},
});
}
{
json
j
{
{
"roomId"
,
"!someroom:example.com"
},
{
"senderKey"
,
"some-key"
},
// legacy version
{
"sessionId"
,
"some-session-id"
},
};
auto
k
=
j
.
template
get
<
KeyOfGroupSession
>
();
REQUIRE
(
k
==
KeyOfGroupSession
{
"!someroom:example.com"
,
"some-session-id"
});
}
}
File Metadata
Details
Attached
Mime Type
text/x-c++
Expires
Tue, Jan 20, 9:47 AM (1 d, 13 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
972892
Default Alt Text
crypto-test.cpp (21 KB)
Attached To
Mode
rL libkazv
Attached
Detach File
Event Timeline
Log In to Comment