Adding AES encryption tests in TestFlows.

This commit is contained in:
Vitaliy Zakaznikov 2020-08-04 20:06:20 +02:00 committed by Vasily Nemkov
parent b147ffcd43
commit 266b231a3c
41 changed files with 14176 additions and 0 deletions

View File

@ -0,0 +1,6 @@
<yandex>
<timezone>Europe/Moscow</timezone>
<listen_host replace="replace">0.0.0.0</listen_host>
<path>/var/lib/clickhouse/</path>
<tmp_path>/var/lib/clickhouse/tmp/</tmp_path>
</yandex>

View File

@ -0,0 +1,17 @@
<yandex>
<shutdown_wait_unfinished>3</shutdown_wait_unfinished>
<logger>
<level>trace</level>
<log>/var/log/clickhouse-server/log.log</log>
<errorlog>/var/log/clickhouse-server/log.err.log</errorlog>
<size>1000M</size>
<count>10</count>
<stderr>/var/log/clickhouse-server/stderr.log</stderr>
<stdout>/var/log/clickhouse-server/stdout.log</stdout>
</logger>
<part_log>
<database>system</database>
<table>part_log</table>
<flush_interval_milliseconds>500</flush_interval_milliseconds>
</part_log>
</yandex>

View File

@ -0,0 +1,5 @@
<?xml version="1.0"?>
<yandex>
<https_port>8443</https_port>
<tcp_port_secure>9440</tcp_port_secure>
</yandex>

View File

@ -0,0 +1,107 @@
<?xml version="1.0"?>
<yandex>
<remote_servers>
<replicated_cluster>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>clickhouse1</host>
<port>9000</port>
</replica>
<replica>
<host>clickhouse2</host>
<port>9000</port>
</replica>
<replica>
<host>clickhouse3</host>
<port>9000</port>
</replica>
</shard>
</replicated_cluster>
<!--
<replicated_cluster_readonly>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>clickhouse1</host>
<port>9000</port>
<user>readonly</user>
</replica>
<replica>
<host>clickhouse2</host>
<port>9000</port>
<user>readonly</user>
</replica>
<replica>
<host>clickhouse3</host>
<port>9000</port>
<user>readonly</user>
</replica>
</shard>
</replicated_cluster_readonly>
-->
<replicated_cluster_secure>
<shard>
<internal_replication>true</internal_replication>
<replica>
<host>clickhouse1</host>
<port>9440</port>
<secure>1</secure>
</replica>
<replica>
<host>clickhouse2</host>
<port>9440</port>
<secure>1</secure>
</replica>
<replica>
<host>clickhouse3</host>
<port>9440</port>
<secure>1</secure>
</replica>
</shard>
</replicated_cluster_secure>
<sharded_cluster>
<shard>
<replica>
<host>clickhouse1</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>clickhouse2</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>clickhouse3</host>
<port>9000</port>
</replica>
</shard>
</sharded_cluster>
<sharded_cluster_secure>
<shard>
<replica>
<host>clickhouse1</host>
<port>9440</port>
<secure>1</secure>
</replica>
</shard>
<shard>
<replica>
<host>clickhouse2</host>
<port>9440</port>
<secure>1</secure>
</replica>
</shard>
<shard>
<replica>
<host>clickhouse3</host>
<port>9440</port>
<secure>1</secure>
</replica>
</shard>
</sharded_cluster_secure>
</remote_servers>
</yandex>

View File

@ -0,0 +1,17 @@
<yandex>
<openSSL>
<server>
<certificateFile>/etc/clickhouse-server/ssl/server.crt</certificateFile>
<privateKeyFile>/etc/clickhouse-server/ssl/server.key</privateKeyFile>
<verificationMode>none</verificationMode>
<cacheSessions>true</cacheSessions>
</server>
<client>
<cacheSessions>true</cacheSessions>
<verificationMode>none</verificationMode>
<invalidCertificateHandler>
<name>AcceptCertificateHandler</name>
</invalidCertificateHandler>
</client>
</openSSL>
</yandex>

View File

@ -0,0 +1,20 @@
<yandex>
<storage_configuration>
<disks>
<default>
<keep_free_space_bytes>1024</keep_free_space_bytes>
</default>
</disks>
<policies>
<default>
<volumes>
<default>
<disk>default</disk>
</default>
</volumes>
</default>
</policies>
</storage_configuration>
</yandex>

View File

@ -0,0 +1,10 @@
<?xml version="1.0"?>
<yandex>
<zookeeper>
<node index="1">
<host>zookeeper</host>
<port>2181</port>
</node>
<session_timeout_ms>15000</session_timeout_ms>
</zookeeper>
</yandex>

View File

@ -0,0 +1,436 @@
<?xml version="1.0"?>
<!--
NOTE: User and query level settings are set up in "users.xml" file.
-->
<yandex>
<logger>
<!-- Possible levels: https://github.com/pocoproject/poco/blob/develop/Foundation/include/Poco/Logger.h#L105 -->
<level>trace</level>
<log>/var/log/clickhouse-server/clickhouse-server.log</log>
<errorlog>/var/log/clickhouse-server/clickhouse-server.err.log</errorlog>
<size>1000M</size>
<count>10</count>
<!-- <console>1</console> --> <!-- Default behavior is autodetection (log to console if not daemon mode and is tty) -->
</logger>
<!--display_name>production</display_name--> <!-- It is the name that will be shown in the client -->
<http_port>8123</http_port>
<tcp_port>9000</tcp_port>
<!-- For HTTPS and SSL over native protocol. -->
<!--
<https_port>8443</https_port>
<tcp_port_secure>9440</tcp_port_secure>
-->
<!-- Used with https_port and tcp_port_secure. Full ssl options list: https://github.com/ClickHouse-Extras/poco/blob/master/NetSSL_OpenSSL/include/Poco/Net/SSLManager.h#L71 -->
<openSSL>
<server> <!-- Used for https server AND secure tcp port -->
<!-- openssl req -subj "/CN=localhost" -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout /etc/clickhouse-server/server.key -out /etc/clickhouse-server/server.crt -->
<certificateFile>/etc/clickhouse-server/server.crt</certificateFile>
<privateKeyFile>/etc/clickhouse-server/server.key</privateKeyFile>
<!-- openssl dhparam -out /etc/clickhouse-server/dhparam.pem 4096 -->
<dhParamsFile>/etc/clickhouse-server/dhparam.pem</dhParamsFile>
<verificationMode>none</verificationMode>
<loadDefaultCAFile>true</loadDefaultCAFile>
<cacheSessions>true</cacheSessions>
<disableProtocols>sslv2,sslv3</disableProtocols>
<preferServerCiphers>true</preferServerCiphers>
</server>
<client> <!-- Used for connecting to https dictionary source -->
<loadDefaultCAFile>true</loadDefaultCAFile>
<cacheSessions>true</cacheSessions>
<disableProtocols>sslv2,sslv3</disableProtocols>
<preferServerCiphers>true</preferServerCiphers>
<!-- Use for self-signed: <verificationMode>none</verificationMode> -->
<invalidCertificateHandler>
<!-- Use for self-signed: <name>AcceptCertificateHandler</name> -->
<name>RejectCertificateHandler</name>
</invalidCertificateHandler>
</client>
</openSSL>
<!-- Default root page on http[s] server. For example load UI from https://tabix.io/ when opening http://localhost:8123 -->
<!--
<http_server_default_response><![CDATA[<html ng-app="SMI2"><head><base href="http://ui.tabix.io/"></head><body><div ui-view="" class="content-ui"></div><script src="http://loader.tabix.io/master.js"></script></body></html>]]></http_server_default_response>
-->
<!-- Port for communication between replicas. Used for data exchange. -->
<interserver_http_port>9009</interserver_http_port>
<!-- Hostname that is used by other replicas to request this server.
If not specified, than it is determined analoguous to 'hostname -f' command.
This setting could be used to switch replication to another network interface.
-->
<!--
<interserver_http_host>example.yandex.ru</interserver_http_host>
-->
<!-- Listen specified host. use :: (wildcard IPv6 address), if you want to accept connections both with IPv4 and IPv6 from everywhere. -->
<!-- <listen_host>::</listen_host> -->
<!-- Same for hosts with disabled ipv6: -->
<!-- <listen_host>0.0.0.0</listen_host> -->
<!-- Default values - try listen localhost on ipv4 and ipv6: -->
<!--
<listen_host>::1</listen_host>
<listen_host>127.0.0.1</listen_host>
-->
<!-- Don't exit if ipv6 or ipv4 unavailable, but listen_host with this protocol specified -->
<!-- <listen_try>0</listen_try> -->
<!-- Allow listen on same address:port -->
<!-- <listen_reuse_port>0</listen_reuse_port> -->
<!-- <listen_backlog>64</listen_backlog> -->
<max_connections>4096</max_connections>
<keep_alive_timeout>3</keep_alive_timeout>
<!-- Maximum number of concurrent queries. -->
<max_concurrent_queries>100</max_concurrent_queries>
<!-- Set limit on number of open files (default: maximum). This setting makes sense on Mac OS X because getrlimit() fails to retrieve
correct maximum value. -->
<!-- <max_open_files>262144</max_open_files> -->
<!-- Size of cache of uncompressed blocks of data, used in tables of MergeTree family.
In bytes. Cache is single for server. Memory is allocated only on demand.
Cache is used when 'use_uncompressed_cache' user setting turned on (off by default).
Uncompressed cache is advantageous only for very short queries and in rare cases.
-->
<uncompressed_cache_size>8589934592</uncompressed_cache_size>
<!-- Approximate size of mark cache, used in tables of MergeTree family.
In bytes. Cache is single for server. Memory is allocated only on demand.
You should not lower this value.
-->
<mark_cache_size>5368709120</mark_cache_size>
<!-- Path to data directory, with trailing slash. -->
<path>/var/lib/clickhouse/</path>
<!-- Path to temporary data for processing hard queries. -->
<tmp_path>/var/lib/clickhouse/tmp/</tmp_path>
<!-- Directory with user provided files that are accessible by 'file' table function. -->
<user_files_path>/var/lib/clickhouse/user_files/</user_files_path>
<!-- Path to folder where users and roles created by SQL commands are stored. -->
<access_control_path>/var/lib/clickhouse/access/</access_control_path>
<!-- Path to configuration file with users, access rights, profiles of settings, quotas. -->
<users_config>users.xml</users_config>
<!-- Default profile of settings. -->
<default_profile>default</default_profile>
<!-- System profile of settings. This settings are used by internal processes (Buffer storage, Distibuted DDL worker and so on). -->
<!-- <system_profile>default</system_profile> -->
<!-- Default database. -->
<default_database>default</default_database>
<!-- Server time zone could be set here.
Time zone is used when converting between String and DateTime types,
when printing DateTime in text formats and parsing DateTime from text,
it is used in date and time related functions, if specific time zone was not passed as an argument.
Time zone is specified as identifier from IANA time zone database, like UTC or Africa/Abidjan.
If not specified, system time zone at server startup is used.
Please note, that server could display time zone alias instead of specified name.
Example: W-SU is an alias for Europe/Moscow and Zulu is an alias for UTC.
-->
<!-- <timezone>Europe/Moscow</timezone> -->
<!-- You can specify umask here (see "man umask"). Server will apply it on startup.
Number is always parsed as octal. Default umask is 027 (other users cannot read logs, data files, etc; group can only read).
-->
<!-- <umask>022</umask> -->
<!-- Perform mlockall after startup to lower first queries latency
and to prevent clickhouse executable from being paged out under high IO load.
Enabling this option is recommended but will lead to increased startup time for up to a few seconds.
-->
<mlock_executable>false</mlock_executable>
<!-- Configuration of clusters that could be used in Distributed tables.
https://clickhouse.yandex/docs/en/table_engines/distributed/
-->
<remote_servers incl="remote" >
<!-- Test only shard config for testing distributed storage -->
<test_shard_localhost>
<shard>
<replica>
<host>localhost</host>
<port>9000</port>
</replica>
</shard>
</test_shard_localhost>
<test_cluster_two_shards_localhost>
<shard>
<replica>
<host>localhost</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>localhost</host>
<port>9000</port>
</replica>
</shard>
</test_cluster_two_shards_localhost>
<test_shard_localhost_secure>
<shard>
<replica>
<host>localhost</host>
<port>9440</port>
<secure>1</secure>
</replica>
</shard>
</test_shard_localhost_secure>
<test_unavailable_shard>
<shard>
<replica>
<host>localhost</host>
<port>9000</port>
</replica>
</shard>
<shard>
<replica>
<host>localhost</host>
<port>1</port>
</replica>
</shard>
</test_unavailable_shard>
</remote_servers>
<!-- If element has 'incl' attribute, then for it's value will be used corresponding substitution from another file.
By default, path to file with substitutions is /etc/metrika.xml. It could be changed in config in 'include_from' element.
Values for substitutions are specified in /yandex/name_of_substitution elements in that file.
-->
<!-- ZooKeeper is used to store metadata about replicas, when using Replicated tables.
Optional. If you don't use replicated tables, you could omit that.
See https://clickhouse.yandex/docs/en/table_engines/replication/
-->
<zookeeper incl="zookeeper" optional="true" />
<!-- Substitutions for parameters of replicated tables.
Optional. If you don't use replicated tables, you could omit that.
See https://clickhouse.yandex/docs/en/table_engines/replication/#creating-replicated-tables
-->
<macros incl="macros" optional="true" />
<!-- Reloading interval for embedded dictionaries, in seconds. Default: 3600. -->
<builtin_dictionaries_reload_interval>3600</builtin_dictionaries_reload_interval>
<!-- Maximum session timeout, in seconds. Default: 3600. -->
<max_session_timeout>3600</max_session_timeout>
<!-- Default session timeout, in seconds. Default: 60. -->
<default_session_timeout>60</default_session_timeout>
<!-- Sending data to Graphite for monitoring. Several sections can be defined. -->
<!--
interval - send every X second
root_path - prefix for keys
hostname_in_path - append hostname to root_path (default = true)
metrics - send data from table system.metrics
events - send data from table system.events
asynchronous_metrics - send data from table system.asynchronous_metrics
-->
<!--
<graphite>
<host>localhost</host>
<port>42000</port>
<timeout>0.1</timeout>
<interval>60</interval>
<root_path>one_min</root_path>
<hostname_in_path>true</hostname_in_path>
<metrics>true</metrics>
<events>true</events>
<asynchronous_metrics>true</asynchronous_metrics>
</graphite>
<graphite>
<host>localhost</host>
<port>42000</port>
<timeout>0.1</timeout>
<interval>1</interval>
<root_path>one_sec</root_path>
<metrics>true</metrics>
<events>true</events>
<asynchronous_metrics>false</asynchronous_metrics>
</graphite>
-->
<!-- Query log. Used only for queries with setting log_queries = 1. -->
<query_log>
<!-- What table to insert data. If table is not exist, it will be created.
When query log structure is changed after system update,
then old table will be renamed and new table will be created automatically.
-->
<database>system</database>
<table>query_log</table>
<!--
PARTITION BY expr https://clickhouse.yandex/docs/en/table_engines/custom_partitioning_key/
Example:
event_date
toMonday(event_date)
toYYYYMM(event_date)
toStartOfHour(event_time)
-->
<partition_by>toYYYYMM(event_date)</partition_by>
<!-- Interval of flushing data. -->
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
</query_log>
<!-- Trace log. Stores stack traces collected by query profilers.
See query_profiler_real_time_period_ns and query_profiler_cpu_time_period_ns settings. -->
<trace_log>
<database>system</database>
<table>trace_log</table>
<partition_by>toYYYYMM(event_date)</partition_by>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
</trace_log>
<!-- Query thread log. Has information about all threads participated in query execution.
Used only for queries with setting log_query_threads = 1. -->
<query_thread_log>
<database>system</database>
<table>query_thread_log</table>
<partition_by>toYYYYMM(event_date)</partition_by>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
</query_thread_log>
<!-- Uncomment if use part log.
Part log contains information about all actions with parts in MergeTree tables (creation, deletion, merges, downloads).
<part_log>
<database>system</database>
<table>part_log</table>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
</part_log>
-->
<!-- Uncomment to write text log into table.
Text log contains all information from usual server log but stores it in structured and efficient way.
<text_log>
<database>system</database>
<table>text_log</table>
<flush_interval_milliseconds>7500</flush_interval_milliseconds>
</text_log>
-->
<!-- Parameters for embedded dictionaries, used in Yandex.Metrica.
See https://clickhouse.yandex/docs/en/dicts/internal_dicts/
-->
<!-- Path to file with region hierarchy. -->
<!-- <path_to_regions_hierarchy_file>/opt/geo/regions_hierarchy.txt</path_to_regions_hierarchy_file> -->
<!-- Path to directory with files containing names of regions -->
<!-- <path_to_regions_names_files>/opt/geo/</path_to_regions_names_files> -->
<!-- Configuration of external dictionaries. See:
https://clickhouse.yandex/docs/en/dicts/external_dicts/
-->
<dictionaries_config>*_dictionary.xml</dictionaries_config>
<!-- Uncomment if you want data to be compressed 30-100% better.
Don't do that if you just started using ClickHouse.
-->
<compression incl="compression">
<!--
<!- - Set of variants. Checked in order. Last matching case wins. If nothing matches, lz4 will be used. - ->
<case>
<!- - Conditions. All must be satisfied. Some conditions may be omitted. - ->
<min_part_size>10000000000</min_part_size> <!- - Min part size in bytes. - ->
<min_part_size_ratio>0.01</min_part_size_ratio> <!- - Min size of part relative to whole table size. - ->
<!- - What compression method to use. - ->
<method>zstd</method>
</case>
-->
</compression>
<!-- Allow to execute distributed DDL queries (CREATE, DROP, ALTER, RENAME) on cluster.
Works only if ZooKeeper is enabled. Comment it if such functionality isn't required. -->
<distributed_ddl>
<!-- Path in ZooKeeper to queue with DDL queries -->
<path>/clickhouse/task_queue/ddl</path>
<!-- Settings from this profile will be used to execute DDL queries -->
<!-- <profile>default</profile> -->
</distributed_ddl>
<!-- Settings to fine tune MergeTree tables. See documentation in source code, in MergeTreeSettings.h -->
<!--
<merge_tree>
<max_suspicious_broken_parts>5</max_suspicious_broken_parts>
</merge_tree>
-->
<!-- Protection from accidental DROP.
If size of a MergeTree table is greater than max_table_size_to_drop (in bytes) than table could not be dropped with any DROP query.
If you want do delete one table and don't want to restart clickhouse-server, you could create special file <clickhouse-path>/flags/force_drop_table and make DROP once.
By default max_table_size_to_drop is 50GB; max_table_size_to_drop=0 allows to DROP any tables.
The same for max_partition_size_to_drop.
Uncomment to disable protection.
-->
<!-- <max_table_size_to_drop>0</max_table_size_to_drop> -->
<!-- <max_partition_size_to_drop>0</max_partition_size_to_drop> -->
<!-- Example of parameters for GraphiteMergeTree table engine -->
<graphite_rollup_example>
<pattern>
<regexp>click_cost</regexp>
<function>any</function>
<retention>
<age>0</age>
<precision>3600</precision>
</retention>
<retention>
<age>86400</age>
<precision>60</precision>
</retention>
</pattern>
<default>
<function>max</function>
<retention>
<age>0</age>
<precision>60</precision>
</retention>
<retention>
<age>3600</age>
<precision>300</precision>
</retention>
<retention>
<age>86400</age>
<precision>3600</precision>
</retention>
</default>
</graphite_rollup_example>
<!-- Directory in <clickhouse-path> containing schema files for various input formats.
The directory will be created if it doesn't exist.
-->
<format_schema_path>/var/lib/clickhouse/format_schemas/</format_schema_path>
<!-- Uncomment to disable ClickHouse internal DNS caching. -->
<!-- <disable_internal_dns_cache>1</disable_internal_dns_cache> -->
</yandex>

View File

@ -0,0 +1,8 @@
-----BEGIN DH PARAMETERS-----
MIIBCAKCAQEAua92DDli13gJ+//ZXyGaggjIuidqB0crXfhUlsrBk9BV1hH3i7fR
XGP9rUdk2ubnB3k2ejBStL5oBrkHm9SzUFSQHqfDjLZjKoUpOEmuDc4cHvX1XTR5
Pr1vf5cd0yEncJWG5W4zyUB8k++SUdL2qaeslSs+f491HBLDYn/h8zCgRbBvxhxb
9qeho1xcbnWeqkN6Kc9bgGozA16P9NLuuLttNnOblkH+lMBf42BSne/TWt3AlGZf
slKmmZcySUhF8aKfJnLKbkBCFqOtFRh8zBA9a7g+BT/lSANATCDPaAk1YVih2EKb
dpc3briTDbRsiqg2JKMI7+VdULY9bh3EawIBAg==
-----END DH PARAMETERS-----

View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIC/TCCAeWgAwIBAgIJANjx1QSR77HBMA0GCSqGSIb3DQEBCwUAMBQxEjAQBgNV
BAMMCWxvY2FsaG9zdDAgFw0xODA3MzAxODE2MDhaGA8yMjkyMDUxNDE4MTYwOFow
FDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB
CgKCAQEAs9uSo6lJG8o8pw0fbVGVu0tPOljSWcVSXH9uiJBwlZLQnhN4SFSFohfI
4K8U1tBDTnxPLUo/V1K9yzoLiRDGMkwVj6+4+hE2udS2ePTQv5oaMeJ9wrs+5c9T
4pOtlq3pLAdm04ZMB1nbrEysceVudHRkQbGHzHp6VG29Fw7Ga6YpqyHQihRmEkTU
7UCYNA+Vk7aDPdMS/khweyTpXYZimaK9f0ECU3/VOeG3fH6Sp2X6FN4tUj/aFXEj
sRmU5G2TlYiSIUMF2JPdhSihfk1hJVALrHPTU38SOL+GyyBRWdNcrIwVwbpvsvPg
pryMSNxnpr0AK0dFhjwnupIv5hJIOQIDAQABo1AwTjAdBgNVHQ4EFgQUjPLb3uYC
kcamyZHK4/EV8jAP0wQwHwYDVR0jBBgwFoAUjPLb3uYCkcamyZHK4/EV8jAP0wQw
DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAM/ocuDvfPus/KpMVD51j
4IdlU8R0vmnYLQ+ygzOAo7+hUWP5j0yvq4ILWNmQX6HNvUggCgFv9bjwDFhb/5Vr
85ieWfTd9+LTjrOzTw4avdGwpX9G+6jJJSSq15tw5ElOIFb/qNA9O4dBiu8vn03C
L/zRSXrARhSqTW5w/tZkUcSTT+M5h28+Lgn9ysx4Ff5vi44LJ1NnrbJbEAIYsAAD
+UA+4MBFKx1r6hHINULev8+lCfkpwIaeS8RL+op4fr6kQPxnULw8wT8gkuc8I4+L
P9gg/xDHB44T3ADGZ5Ib6O0DJaNiToO6rnoaaxs0KkotbvDWvRoxEytSbXKoYjYp
0g==
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCz25KjqUkbyjyn
DR9tUZW7S086WNJZxVJcf26IkHCVktCeE3hIVIWiF8jgrxTW0ENOfE8tSj9XUr3L
OguJEMYyTBWPr7j6ETa51LZ49NC/mhox4n3Cuz7lz1Pik62WreksB2bThkwHWdus
TKxx5W50dGRBsYfMenpUbb0XDsZrpimrIdCKFGYSRNTtQJg0D5WTtoM90xL+SHB7
JOldhmKZor1/QQJTf9U54bd8fpKnZfoU3i1SP9oVcSOxGZTkbZOViJIhQwXYk92F
KKF+TWElUAusc9NTfxI4v4bLIFFZ01ysjBXBum+y8+CmvIxI3GemvQArR0WGPCe6
ki/mEkg5AgMBAAECggEATrbIBIxwDJOD2/BoUqWkDCY3dGevF8697vFuZKIiQ7PP
TX9j4vPq0DfsmDjHvAPFkTHiTQXzlroFik3LAp+uvhCCVzImmHq0IrwvZ9xtB43f
7Pkc5P6h1l3Ybo8HJ6zRIY3TuLtLxuPSuiOMTQSGRL0zq3SQ5DKuGwkz+kVjHXUN
MR2TECFwMHKQ5VLrC+7PMpsJYyOMlDAWhRfUalxC55xOXTpaN8TxNnwQ8K2ISVY5
212Jz/a4hn4LdwxSz3Tiu95PN072K87HLWx3EdT6vW4Ge5P/A3y+smIuNAlanMnu
plHBRtpATLiTxZt/n6npyrfQVbYjSH7KWhB8hBHtaQKBgQDh9Cq1c/KtqDtE0Ccr
/r9tZNTUwBE6VP+3OJeKdEdtsfuxjOCkS1oAjgBJiSDOiWPh1DdoDeVZjPKq6pIu
Mq12OE3Doa8znfCXGbkSzEKOb2unKZMJxzrz99kXt40W5DtrqKPNb24CNqTiY8Aa
CjtcX+3weat82VRXvph6U8ltMwKBgQDLxjiQQzNoY7qvg7CwJCjf9qq8jmLK766g
1FHXopqS+dTxDLM8eJSRrpmxGWJvNeNc1uPhsKsKgotqAMdBUQTf7rSTbt4MyoH5
bUcRLtr+0QTK9hDWMOOvleqNXha68vATkohWYfCueNsC60qD44o8RZAS6UNy3ENq
cM1cxqe84wKBgQDKkHutWnooJtajlTxY27O/nZKT/HA1bDgniMuKaz4R4Gr1PIez
on3YW3V0d0P7BP6PWRIm7bY79vkiMtLEKdiKUGWeyZdo3eHvhDb/3DCawtau8L2K
GZsHVp2//mS1Lfz7Qh8/L/NedqCQ+L4iWiPnZ3THjjwn3CoZ05ucpvrAMwKBgB54
nay039MUVq44Owub3KDg+dcIU62U+cAC/9oG7qZbxYPmKkc4oL7IJSNecGHA5SbU
2268RFdl/gLz6tfRjbEOuOHzCjFPdvAdbysanpTMHLNc6FefJ+zxtgk9sJh0C4Jh
vxFrw9nTKKzfEl12gQ1SOaEaUIO0fEBGbe8ZpauRAoGAMAlGV+2/K4ebvAJKOVTa
dKAzQ+TD2SJmeR1HZmKDYddNqwtZlzg3v4ZhCk4eaUmGeC1Bdh8MDuB3QQvXz4Dr
vOIP4UVaOr+uM+7TgAgVnP4/K6IeJGzUDhX93pmpWhODfdu/oojEKVcpCojmEmS1
KCBtmIrQLqzMpnBpLNuSY+Q=
-----END PRIVATE KEY-----

View File

@ -0,0 +1,133 @@
<?xml version="1.0"?>
<yandex>
<!-- Profiles of settings. -->
<profiles>
<!-- Default settings. -->
<default>
<!-- Maximum memory usage for processing single query, in bytes. -->
<max_memory_usage>10000000000</max_memory_usage>
<!-- Use cache of uncompressed blocks of data. Meaningful only for processing many of very short queries. -->
<use_uncompressed_cache>0</use_uncompressed_cache>
<!-- How to choose between replicas during distributed query processing.
random - choose random replica from set of replicas with minimum number of errors
nearest_hostname - from set of replicas with minimum number of errors, choose replica
with minimum number of different symbols between replica's hostname and local hostname
(Hamming distance).
in_order - first live replica is chosen in specified order.
first_or_random - if first replica one has higher number of errors, pick a random one from replicas with minimum number of errors.
-->
<load_balancing>random</load_balancing>
</default>
<!-- Profile that allows only read queries. -->
<readonly>
<readonly>1</readonly>
</readonly>
</profiles>
<!-- Users and ACL. -->
<users>
<!-- If user name was not specified, 'default' user is used. -->
<default>
<!-- Password could be specified in plaintext or in SHA256 (in hex format).
If you want to specify password in plaintext (not recommended), place it in 'password' element.
Example: <password>qwerty</password>.
Password could be empty.
If you want to specify SHA256, place it in 'password_sha256_hex' element.
Example: <password_sha256_hex>65e84be33532fb784c48129675f9eff3a682b27168c0ea744b2cf58ee02337c5</password_sha256_hex>
Restrictions of SHA256: impossibility to connect to ClickHouse using MySQL JS client (as of July 2019).
If you want to specify double SHA1, place it in 'password_double_sha1_hex' element.
Example: <password_double_sha1_hex>e395796d6546b1b65db9d665cd43f0e858dd4303</password_double_sha1_hex>
How to generate decent password:
Execute: PASSWORD=$(base64 < /dev/urandom | head -c8); echo "$PASSWORD"; echo -n "$PASSWORD" | sha256sum | tr -d '-'
In first line will be password and in second - corresponding SHA256.
How to generate double SHA1:
Execute: PASSWORD=$(base64 < /dev/urandom | head -c8); echo "$PASSWORD"; echo -n "$PASSWORD" | openssl dgst -sha1 -binary | openssl dgst -sha1
In first line will be password and in second - corresponding double SHA1.
-->
<password></password>
<!-- List of networks with open access.
To open access from everywhere, specify:
<ip>::/0</ip>
To open access only from localhost, specify:
<ip>::1</ip>
<ip>127.0.0.1</ip>
Each element of list has one of the following forms:
<ip> IP-address or network mask. Examples: 213.180.204.3 or 10.0.0.1/8 or 10.0.0.1/255.255.255.0
2a02:6b8::3 or 2a02:6b8::3/64 or 2a02:6b8::3/ffff:ffff:ffff:ffff::.
<host> Hostname. Example: server01.yandex.ru.
To check access, DNS query is performed, and all received addresses compared to peer address.
<host_regexp> Regular expression for host names. Example, ^server\d\d-\d\d-\d\.yandex\.ru$
To check access, DNS PTR query is performed for peer address and then regexp is applied.
Then, for result of PTR query, another DNS query is performed and all received addresses compared to peer address.
Strongly recommended that regexp is ends with $
All results of DNS requests are cached till server restart.
-->
<networks incl="networks" replace="replace">
<ip>::/0</ip>
</networks>
<!-- Settings profile for user. -->
<profile>default</profile>
<!-- Quota for user. -->
<quota>default</quota>
<!-- Allow access management -->
<access_management>1</access_management>
<!-- Example of row level security policy. -->
<!-- <databases>
<test>
<filtered_table1>
<filter>a = 1</filter>
</filtered_table1>
<filtered_table2>
<filter>a + b &lt; 1 or c - d &gt; 5</filter>
</filtered_table2>
</test>
</databases> -->
</default>
<!-- Example of user with readonly access. -->
<!-- <readonly>
<password></password>
<networks incl="networks" replace="replace">
<ip>::1</ip>
<ip>127.0.0.1</ip>
</networks>
<profile>readonly</profile>
<quota>default</quota>
</readonly> -->
</users>
<!-- Quotas. -->
<quotas>
<!-- Name of quota. -->
<default>
<!-- Limits for time interval. You could specify many intervals with different limits. -->
<interval>
<!-- Length of interval. -->
<duration>3600</duration>
<!-- No limits. Just calculate resource usage for time interval. -->
<queries>0</queries>
<errors>0</errors>
<result_rows>0</result_rows>
<read_rows>0</read_rows>
<execution_time>0</execution_time>
</interval>
</default>
</quotas>
</yandex>

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<yandex>
<macros>
<replica>clickhouse1</replica>
<shard>01</shard>
<shard2>01</shard2>
</macros>
</yandex>

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<yandex>
<macros>
<replica>clickhouse2</replica>
<shard>01</shard>
<shard2>02</shard2>
</macros>
</yandex>

View File

@ -0,0 +1,8 @@
<?xml version="1.0"?>
<yandex>
<macros>
<replica>clickhouse3</replica>
<shard>01</shard>
<shard2>03</shard2>
</macros>
</yandex>

View File

@ -0,0 +1,28 @@
version: '2.3'
services:
clickhouse:
image: yandex/clickhouse-integration-test
expose:
- "9000"
- "9009"
- "8123"
volumes:
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse/config.d:/etc/clickhouse-server/config.d"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse/users.d/:/etc/clickhouse-server/users.d"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse/ssl:/etc/clickhouse-server/ssl"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse/config.xml:/etc/clickhouse-server/config.xml"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse/users.xml:/etc/clickhouse-server/users.xml"
- "${CLICKHOUSE_TESTS_SERVER_BIN_PATH:-/usr/bin/clickhouse}:/usr/bin/clickhouse"
- "${CLICKHOUSE_TESTS_ODBC_BRIDGE_BIN_PATH:-/usr/bin/clickhouse-odbc-bridge}:/usr/bin/clickhouse-odbc-bridge"
entrypoint: bash -c "clickhouse server --config-file=/etc/clickhouse-server/config.xml --log-file=/var/log/clickhouse-server/clickhouse-server.log --errorlog-file=/var/log/clickhouse-server/clickhouse-server.err.log"
healthcheck:
test: clickhouse client --query='select 1'
interval: 3s
timeout: 2s
retries: 40
start_period: 2s
cap_add:
- SYS_PTRACE
security_opt:
- label:disable

View File

@ -0,0 +1,73 @@
version: '2.3'
services:
zookeeper:
extends:
file: zookeeper-service.yml
service: zookeeper
mysql1:
extends:
file: mysql-service.yml
service: mysql
hostname: mysql1
volumes:
- "${CLICKHOUSE_TESTS_DIR}/_instances/mysql1/database:/var/lib/mysql"
clickhouse1:
extends:
file: clickhouse-service.yml
service: clickhouse
hostname: clickhouse1
volumes:
- "${CLICKHOUSE_TESTS_DIR}/_instances/clickhouse1/database/:/var/lib/clickhouse/"
- "${CLICKHOUSE_TESTS_DIR}/_instances/clickhouse1/logs/:/var/log/clickhouse-server/"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse1/config.d:/etc/clickhouse-server/config.d"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse1/users.d:/etc/clickhouse-server/users.d"
depends_on:
zookeeper:
condition: service_healthy
clickhouse2:
extends:
file: clickhouse-service.yml
service: clickhouse
hostname: clickhouse2
volumes:
- "${CLICKHOUSE_TESTS_DIR}/_instances/clickhouse2/database/:/var/lib/clickhouse/"
- "${CLICKHOUSE_TESTS_DIR}/_instances/clickhouse2/logs/:/var/log/clickhouse-server/"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse2/config.d:/etc/clickhouse-server/config.d"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse2/users.d:/etc/clickhouse-server/users.d"
depends_on:
zookeeper:
condition: service_healthy
clickhouse3:
extends:
file: clickhouse-service.yml
service: clickhouse
hostname: clickhouse3
volumes:
- "${CLICKHOUSE_TESTS_DIR}/_instances/clickhouse3/database/:/var/lib/clickhouse/"
- "${CLICKHOUSE_TESTS_DIR}/_instances/clickhouse3/logs/:/var/log/clickhouse-server/"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse3/config.d:/etc/clickhouse-server/config.d"
- "${CLICKHOUSE_TESTS_DIR}/configs/clickhouse3/users.d:/etc/clickhouse-server/users.d"
depends_on:
zookeeper:
condition: service_healthy
# dummy service which does nothing, but allows to postpone
# 'docker-compose up -d' till all dependecies will go healthy
all_services_ready:
image: hello-world
depends_on:
mysql1:
condition: service_healthy
clickhouse1:
condition: service_healthy
clickhouse2:
condition: service_healthy
clickhouse3:
condition: service_healthy
zookeeper:
condition: service_healthy

View File

@ -0,0 +1,19 @@
version: '2.3'
services:
mysql:
image: mysql:5.7.30
restart: always
environment:
MYSQL_DATABASE: 'db'
MYSQL_USER: 'user'
MYSQL_PASSWORD: 'password'
MYSQL_ROOT_PASSWORD: 'password'
expose:
- '3306'
healthcheck:
test: mysql -D db -u user --password=password -e "select 1;"
interval: 3s
timeout: 2s
retries: 40
start_period: 2s

View File

@ -0,0 +1,18 @@
version: '2.3'
services:
zookeeper:
image: zookeeper:3.4.12
expose:
- "2181"
environment:
ZOO_TICK_TIME: 500
ZOO_MY_ID: 1
healthcheck:
test: echo stat | nc localhost 2181
interval: 3s
timeout: 2s
retries: 5
start_period: 2s
security_opt:
- label:disable

View File

@ -0,0 +1,73 @@
#!/usr/bin/env python3
import sys
from testflows.core import *
append_path(sys.path, "..")
from helpers.cluster import Cluster
from helpers.argparser import argparser
from aes_encryption.requirements import *
xfails = {
# encrypt
"encrypt/invalid key or iv length for mode/mode=\"'aes-???-gcm'\", key_len=??, iv_len=12, aad=True/iv is too short":
[(Fail, "known issue")],
"encrypt/invalid key or iv length for mode/mode=\"'aes-???-gcm'\", key_len=??, iv_len=12, aad=True/iv is too long":
[(Fail, "known issue")],
# encrypt_mysql
"encrypt_mysql/key or iv length for mode/mode=\"'aes-???-ecb'\", key_len=??, iv_len=None":
[(Fail, "https://altinity.atlassian.net/browse/CH-190")],
"encrypt_mysql/invalid parameters/iv not valid for mode":
[(Fail, "https://altinity.atlassian.net/browse/CH-190")],
"encrypt_mysql/invalid parameters/no parameters":
[(Fail, "https://altinity.atlassian.net/browse/CH-191")],
# decrypt_mysql
"decrypt_mysql/key or iv length for mode/mode=\"'aes-???-ecb'\", key_len=??, iv_len=None:":
[(Fail, "https://altinity.atlassian.net/browse/CH-190")],
# compatibility
"compatibility/insert/encrypt using materialized view/:":
[(Fail, "https://altinity.atlassian.net/browse/CH-193")],
"compatibility/insert/decrypt using materialized view/:":
[(Error, "https://altinity.atlassian.net/browse/CH-193")],
"compatibility/insert/aes encrypt mysql using materialized view/:":
[(Fail, "https://altinity.atlassian.net/browse/CH-193")],
"compatibility/insert/aes decrypt mysql using materialized view/:":
[(Error, "https://altinity.atlassian.net/browse/CH-193")],
"compatibility/select/decrypt unique":
[(Fail, "https://altinity.atlassian.net/browse/CH-193")],
"compatibility/mysql/:engine/decrypt/mysql_datatype='TEXT'/:":
[(Fail, "https://altinity.atlassian.net/browse/CH-194")],
"compatibility/mysql/:engine/decrypt/mysql_datatype='VARCHAR(100)'/:":
[(Fail, "https://altinity.atlassian.net/browse/CH-194")],
"compatibility/mysql/:engine/encrypt/mysql_datatype='TEXT'/:":
[(Fail, "https://altinity.atlassian.net/browse/CH-194")],
"compatibility/mysql/:engine/encrypt/mysql_datatype='VARCHAR(100)'/:":
[(Fail, "https://altinity.atlassian.net/browse/CH-194")]
}
@TestFeature
@Name("aes encryption")
@ArgumentParser(argparser)
@Requirements(
RQ_SRS008_AES_Functions("1.0"),
RQ_SRS008_AES_Functions_DifferentModes("1.0")
)
@XFails(xfails)
def regression(self, local, clickhouse_binary_path):
"""ClickHouse AES encryption functions regression module.
"""
nodes = {
"clickhouse": ("clickhouse1", "clickhouse2", "clickhouse3"),
}
with Cluster(local, clickhouse_binary_path, nodes=nodes) as cluster:
self.context.cluster = cluster
Feature(run=load("aes_encryption.tests.encrypt", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.decrypt", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.encrypt_mysql", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.decrypt_mysql", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.compatibility.feature", "feature"), flags=TE)
if main():
regression()

View File

@ -0,0 +1 @@
from .requirements import *

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,162 @@
# -*- coding: utf-8 -*-
modes = [
# mode, key_len, iv_len, aad
("'aes-128-ecb'", 16, None, None),
("'aes-192-ecb'", 24, None, None),
("'aes-256-ecb'", 32, None, None),
# cbc
("'aes-128-cbc'", 16, None, None),
("'aes-192-cbc'", 24, None, None),
("'aes-256-cbc'", 32, None, None),
("'aes-128-cbc'", 16, 16, None),
("'aes-192-cbc'", 24, 16, None),
("'aes-256-cbc'", 32, 16, None),
# cfb1
("'aes-128-cfb1'", 16, None, None),
("'aes-192-cfb1'", 24, None, None),
("'aes-256-cfb1'", 32, None, None),
("'aes-128-cfb1'", 16, 16, None),
("'aes-192-cfb1'", 24, 16, None),
("'aes-256-cfb1'", 32, 16, None),
# cfb8
("'aes-128-cfb8'", 16, None, None),
("'aes-192-cfb8'", 24, None, None),
("'aes-256-cfb8'", 32, None, None),
("'aes-128-cfb8'", 16, 16, None),
("'aes-192-cfb8'", 24, 16, None),
("'aes-256-cfb8'", 32, 16, None),
# cfb128
("'aes-128-cfb128'", 16, None, None),
("'aes-192-cfb128'", 24, None, None),
("'aes-256-cfb128'", 32, None, None),
("'aes-128-cfb128'", 16, 16, None),
("'aes-192-cfb128'", 24, 16, None),
("'aes-256-cfb128'", 32, 16, None),
# ofb
("'aes-128-ofb'", 16, None, None),
("'aes-192-ofb'", 24, None, None),
("'aes-256-ofb'", 32, None, None),
("'aes-128-ofb'", 16, 16, None),
("'aes-192-ofb'", 24, 16, None),
("'aes-256-ofb'", 32, 16, None),
# gcm
("'aes-128-gcm'", 16, 12, None),
("'aes-192-gcm'", 24, 12, None),
("'aes-256-gcm'", 32, 12, None),
("'aes-128-gcm'", 16, 12, True),
("'aes-192-gcm'", 24, 12, True),
("'aes-256-gcm'", 32, 12, True),
# ctr
("'aes-128-ctr'", 16, None, None),
("'aes-192-ctr'", 24, None, None),
("'aes-256-ctr'", 32, None, None),
("'aes-128-ctr'", 16, 16, None),
("'aes-192-ctr'", 24, 16, None),
("'aes-256-ctr'", 32, 16, None),
]
mysql_modes = [
# mode, key_len, iv_len
("'aes-128-ecb'", 16, None),
("'aes-128-ecb'", 24, None),
("'aes-192-ecb'", 24, None),
("'aes-192-ecb'", 32, None),
("'aes-256-ecb'", 32, None),
("'aes-256-ecb'", 64, None),
# cbc
("'aes-128-cbc'", 16, None),
("'aes-192-cbc'", 24, None),
("'aes-256-cbc'", 32, None),
("'aes-128-cbc'", 16, 16),
("'aes-128-cbc'", 24, 24),
("'aes-192-cbc'", 24, 16),
("'aes-192-cbc'", 32, 32),
("'aes-256-cbc'", 32, 16),
("'aes-256-cbc'", 64, 64),
# cfb1
("'aes-128-cfb1'", 16, None),
("'aes-192-cfb1'", 24, None),
("'aes-256-cfb1'", 32, None),
("'aes-128-cfb1'", 16, 16),
("'aes-128-cfb1'", 24, 24),
("'aes-192-cfb1'", 24, 16),
("'aes-192-cfb1'", 32, 32),
("'aes-256-cfb1'", 32, 16),
("'aes-256-cfb1'", 64, 64),
# cfb8
("'aes-128-cfb8'", 16, None),
("'aes-192-cfb8'", 24, None),
("'aes-256-cfb8'", 32, None),
("'aes-128-cfb8'", 16, 16),
("'aes-128-cfb8'", 24, 24),
("'aes-192-cfb8'", 24, 16),
("'aes-192-cfb8'", 32, 32),
("'aes-256-cfb8'", 32, 16),
("'aes-256-cfb8'", 64, 64),
# cfb128
("'aes-128-cfb128'", 16, None),
("'aes-192-cfb128'", 24, None),
("'aes-256-cfb128'", 32, None),
("'aes-128-cfb128'", 16, 16),
("'aes-128-cfb128'", 24, 24),
("'aes-192-cfb128'", 24, 16),
("'aes-192-cfb128'", 32, 32),
("'aes-256-cfb128'", 32, 16),
("'aes-256-cfb128'", 64, 64),
# ofb
("'aes-128-ofb'", 16, None),
("'aes-192-ofb'", 24, None),
("'aes-256-ofb'", 32, None),
("'aes-128-ofb'", 16, 16),
("'aes-128-ofb'", 24, 24),
("'aes-192-ofb'", 24, 16),
("'aes-192-ofb'", 32, 32),
("'aes-256-ofb'", 32, 16),
("'aes-256-ofb'", 64, 64),
]
plaintexts = [
("bytes", "unhex('0')"),
("emptystring", "''"),
("utf8string", "'Gãńdåłf_Thê_Gręât'"),
("utf8fixedstring", "toFixedString('Gãńdåłf_Thê_Gręât', 24)"),
("String", "'1'"),
("FixedString", "toFixedString('1', 1)"),
("UInt8", "toUInt8('1')"),
("UInt16", "toUInt16('1')"),
("UInt32", "toUInt32('1')"),
("UInt64", "toUInt64('1')"),
("Int8", "toInt8('1')"),
("Int16", "toInt16('1')"),
("Int32", "toInt32('1')"),
("Int64", "toInt64('1')"),
("Float32", "toFloat32('1')"),
("Float64", "toFloat64('1')"),
("Decimal32", "toDecimal32(2, 4)"),
("Decimal64", "toDecimal64(2, 4)"),
("Decimal128", "toDecimal128(2, 4)"),
("UUID", "toUUID('61f0c404-5cb3-11e7-907b-a6006ad3dba0')"),
("Date", "toDate('2020-01-01')"),
("DateTime", "toDateTime('2020-01-01 20:01:02')"),
("DateTime64", "toDateTime64('2020-01-01 20:01:02.123', 3)"),
("LowCardinality", "toLowCardinality('1')"),
("Array", "[1,2]"),
#("Tuple", "(1,'a')") - not supported
#("Nullable, "Nullable(X)") - not supported
("NULL", "toDateOrNull('foo')"),
("IPv4", "toIPv4('171.225.130.45')"),
("IPv6", "toIPv6('2001:0db8:0000:85a3:0000:0000:ac1f:8001')"),
("Enum8", r"CAST('a', 'Enum8(\'a\' = 1, \'b\' = 2)')"),
("Enum16", r"CAST('a', 'Enum16(\'a\' = 1, \'b\' = 2)')"),
]
_hex = hex
def hex(s):
"""Convert string to hex.
"""
if isinstance(s, str):
return "".join(['%X' % ord(c) for c in s])
if isinstance(s, bytes):
return "".join(['%X' % c for c in s])
return _hex(s)

View File

@ -0,0 +1,17 @@
from testflows.core import *
from aes_encryption.requirements import *
@TestFeature
@Name("compatibility")
@Requirements(
RQ_SRS008_AES_Functions_DataFromMultipleSources("1.0")
)
def feature(self, node="clickhouse1"):
"""Check encryption functions usage compatibility.
"""
self.context.node = self.context.cluster.node(node)
Feature(run=load("aes_encryption.tests.compatibility.insert", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.compatibility.select", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.compatibility.mysql.feature", "feature"), flags=TE)

View File

@ -0,0 +1,414 @@
import os
import textwrap
from contextlib import contextmanager
from importlib.machinery import SourceFileLoader
from testflows.core import *
from testflows.core.name import basename
from testflows.asserts.helpers import varname
from testflows.asserts import values, error, snapshot
from aes_encryption.tests.common import modes, mysql_modes
@contextmanager
def table(name):
node = current().context.node
try:
with Given("table"):
sql = f"""
CREATE TABLE {name}
(
date Nullable(Date),
name Nullable(String),
secret Nullable(String)
)
ENGINE = Memory()
"""
with By("dropping table if exists"):
node.query(f"DROP TABLE IF EXISTS {name}")
with And("creating a table"):
node.query(textwrap.dedent(sql))
yield
finally:
with Finally("I drop the table", flags=TE):
node.query(f"DROP TABLE IF EXISTS {name}")
@contextmanager
def mv_transform(table, transform):
node = current().context.node
try:
with Given("tables for input transformation"):
with By("creating Null input table"):
sql = f"""
CREATE TABLE {table}_input
(
date Nullable(Date),
name Nullable(String),
secret Nullable(String),
mode String,
key String,
iv String,
aad String
)
ENGINE=Null()
"""
node.query(textwrap.dedent(sql))
with And("creating materialized view table"):
sql = f"""
CREATE MATERIALIZED VIEW {table}_input_mv TO {table} AS
SELECT date, name, {transform}
FROM {table}_input
"""
node.query(textwrap.dedent(sql))
yield
finally:
with Finally("I drop tables for input transformation", flags=TE):
with By("dropping materialized view table", flags=TE):
node.query(f"DROP TABLE IF EXISTS {table}_input_mv")
with And("dropping Null input table", flags=TE):
node.query(f"DROP TABLE IF EXISTS {table}_input")
@TestScenario
def encrypt_using_materialized_view(self):
"""Check that we can use `encrypt` function when inserting
data into a table using a materialized view for input
data transformation.
"""
node = self.context.node
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} iv={iv_len} aad={aad_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_aad = None if not aad_len else f"'{aad}'"
example_transform = f"encrypt(mode, secret, key{', iv' if example_iv else ''}{', aad' if example_aad else ''})"
with table("user_data"):
with mv_transform("user_data", example_transform):
with When("I insert encrypted data"):
node.query(f"""
INSERT INTO user_data_input
(date, name, secret, mode, key)
VALUES
('2020-01-01', 'user0', 'user0_secret', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}),
('2020-01-02', 'user1', 'user1_secret', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}),
('2020-01-03', 'user2', 'user2_secret', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""})
""")
with And("I read inserted data back"):
node.query("SELECT date, name, hex(secret) FROM user_data ORDER BY date")
with Then("output must match the snapshot"):
with values() as that:
assert that(snapshot(r.output.strip(), "insert", name=f"encrypt_mv_example_{varname(basename(self.name))}")), error()
@TestScenario
def aes_encrypt_mysql_using_materialized_view(self):
"""Check that we can use `aes_encrypt_mysql` function when inserting
data into a table using a materialized view for input
data transformation.
"""
node = self.context.node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
aad = "some random aad"
for mode, key_len, iv_len in mysql_modes:
with Example(f"""mode={mode.strip("'")} key={key_len} iv={iv_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_transform = f"aes_encrypt_mysql(mode, secret, key{', iv' if example_iv else ''})"
with table("user_data"):
with mv_transform("user_data", example_transform):
with When("I insert encrypted data"):
node.query(f"""
INSERT INTO user_data_input
(date, name, secret, mode, key)
VALUES
('2020-01-01', 'user0', 'user0_secret', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}),
('2020-01-02', 'user1', 'user1_secret', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}),
('2020-01-03', 'user2', 'user2_secret', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""})
""")
with And("I read inserted data back"):
node.query("SELECT date, name, hex(secret) FROM user_data ORDER BY date")
with Then("output must match the snapshot"):
with values() as that:
assert that(snapshot(r.output.strip(), "insert", name=f"aes_encrypt_mysql_mv_example_{varname(basename(self.name))}")), error()
@TestScenario
def encrypt_using_input_table_function(self):
"""Check that we can use `encrypt` function when inserting
data into a table using insert select and `input()` table
function.
"""
node = self.context.node
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} iv={iv_len} aad={aad_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_aad = None if not aad_len else f"'{aad}'"
example_transform = f"encrypt({mode}, secret, {example_key}{(', ' + example_iv) if example_iv else ''}{(', ' + example_aad) if example_aad else ''})"
with table("user_data"):
with When("I insert encrypted data"):
node.query(f"""
INSERT INTO
user_data
SELECT
date, name, {example_transform}
FROM
input('date Date, name String, secret String')
FORMAT Values ('2020-01-01', 'user0', 'user0_secret'), ('2020-01-02', 'user1', 'user1_secret'), ('2020-01-03', 'user2', 'user2_secret')
""")
with And("I read inserted data back"):
r = node.query("SELECT date, name, hex(secret) FROM user_data ORDER BY date")
with Then("output must match the snapshot"):
with values() as that:
assert that(snapshot(r.output.strip(), "insert", name=f"encrypt_input_example_{varname(basename(example.name))}")), error()
@TestScenario
def aes_encrypt_mysql_using_input_table_function(self):
"""Check that we can use `aes_encrypt_mysql` function when inserting
data into a table using insert select and `input()` table
function.
"""
node = self.context.node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
aad = "some random aad"
for mode, key_len, iv_len in mysql_modes:
with Example(f"""mode={mode.strip("'")} key={key_len} iv={iv_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_transform = f"aes_encrypt_mysql({mode}, secret, {example_key}{(', ' + example_iv) if example_iv else ''})"
with table("user_data"):
with When("I insert encrypted data"):
node.query(f"""
INSERT INTO
user_data
SELECT
date, name, {example_transform}
FROM
input('date Date, name String, secret String')
FORMAT Values ('2020-01-01', 'user0', 'user0_secret'), ('2020-01-02', 'user1', 'user1_secret'), ('2020-01-03', 'user2', 'user2_secret')
""")
with And("I read inserted data back"):
r = node.query("SELECT date, name, hex(secret) FROM user_data ORDER BY date")
with Then("output must match the snapshot"):
with values() as that:
assert that(snapshot(r.output.strip(), "insert", name=f"aes_encrypt_mysql_input_example_{varname(basename(example.name))}")), error()
@TestScenario
def decrypt_using_materialized_view(self):
"""Check that we can use `decrypt` function when inserting
data into a table using a materialized view for input
data transformation.
"""
node = self.context.node
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "insert.py.insert.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} iv={iv_len} aad={aad_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_aad = None if not aad_len else f"'{aad}'"
example_transform = f"decrypt(mode, secret, key{', iv' if example_iv else ''}{', aad' if example_aad else ''})"
with Given("I have ciphertexts"):
example_name = basename(example.name)
ciphertexts = getattr(snapshot_module, varname(f"encrypt_mv_example_{example_name}"))
example_ciphertexts = ["'{}'".format(l.split("\t")[-1].strup("'")) for l in ciphertexts.split("\n")]
with table("user_data"):
with mv_transform("user_data", example_transform):
with When("I insert encrypted data"):
node.query(f"""
INSERT INTO user_data_input
(date, name, secret, mode, key)
VALUES
('2020-01-01', 'user0', 'unhex({example_ciphertexts[0]})', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}),
('2020-01-02', 'user1', 'unhex({example_ciphertexts[1]})', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}),
('2020-01-03', 'user2', 'unhex({example_ciphertexts[2]})', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""})
""")
with And("I read inserted data back"):
r = node.query("SELECT date, name, secret FROM user_data ORDER BY date")
with Then("output must match the expected"):
expected = r"""'2020-01-01\tuser0\tuser0_secret\n2020-01-02\tuser1\tuser1_secret\n2020-01-03\tuser2\tuser2_secret'"""
assert r.output == expected, error()
@TestScenario
def aes_decrypt_mysql_using_materialized_view(self):
"""Check that we can use `aes_decrypt_mysql` function when inserting
data into a table using a materialized view for input
data transformation.
"""
node = self.context.node
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "insert.py.insert.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} key={key_len} iv={iv_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_aad = None if not aad_len else f"'{aad}'"
example_transform = f"aes_decrypt_mysql(mode, secret, key{', iv' if example_iv else ''})"
with Given("I have ciphertexts"):
example_name = basename(example.name)
ciphertexts = getattr(snapshot_module, varname(f"aes_encrypt_mysql_mv_example_{example_name}"))
example_ciphertexts = ["'{}'".format(l.split("\t")[-1].strup("'")) for l in ciphertexts.split("\n")]
with table("user_data"):
with mv_transform("user_data", example_transform):
with When("I insert encrypted data"):
node.query(f"""
INSERT INTO user_data_input
(date, name, secret, mode, key)
VALUES
('2020-01-01', 'user0', 'unhex({example_ciphertexts[0]})', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}),
('2020-01-02', 'user1', 'unhex({example_ciphertexts[1]})', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""}),
('2020-01-03', 'user2', 'unhex({example_ciphertexts[2]})', {example_mode}, {example_key}{(", " + example_iv) if example_iv else ""})
""")
with And("I read inserted data back"):
r = node.query("SELECT date, name, secret FROM user_data ORDER BY date")
with Then("output must match the expected"):
expected = r"""'2020-01-01\tuser0\tuser0_secret\n2020-01-02\tuser1\tuser1_secret\n2020-01-03\tuser2\tuser2_secret'"""
assert r.output == expected, error()
@TestScenario
def decrypt_using_input_table_function(self):
"""Check that we can use `decrypt` function when inserting
data into a table using insert select and `input()` table
function.
"""
node = self.context.node
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "insert.py.insert.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} iv={iv_len} aad={aad_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_aad = None if not aad_len else f"'{aad}'"
example_transform = f"decrypt({mode}, unhex(secret), {example_key}{(', ' + example_iv) if example_iv else ''}{(', ' + example_aad) if example_aad else ''})"
with Given("I have ciphertexts"):
example_name = basename(example.name)
ciphertexts = getattr(snapshot_module, varname(f"encrypt_input_example_{example_name}"))
example_ciphertexts = [l.split("\\t")[-1].strip("'") for l in ciphertexts.split("\\n")]
with table("user_data"):
with When("I insert decrypted data"):
node.query(textwrap.dedent(f"""
INSERT INTO
user_data
SELECT
date, name, {example_transform}
FROM
input('date Date, name String, secret String')
FORMAT Values ('2020-01-01', 'user0', '{example_ciphertexts[0]}'), ('2020-01-02', 'user1', '{example_ciphertexts[1]}'), ('2020-01-03', 'user2', '{example_ciphertexts[2]}')
"""))
with And("I read inserted data back"):
r = node.query("SELECT date, name, secret FROM user_data ORDER BY date")
expected = """2020-01-01\tuser0\tuser0_secret\n2020-01-02\tuser1\tuser1_secret\n2020-01-03\tuser2\tuser2_secret"""
with Then("output must match the expected", description=expected):
assert r.output == expected, error()
@TestScenario
def aes_decrypt_mysql_using_input_table_function(self):
"""Check that we can use `aes_decrypt_mysql` function when inserting
data into a table using insert select and `input()` table
function.
"""
node = self.context.node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
aad = "some random aad"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "insert.py.insert.snapshot")).load_module()
for mode, key_len, iv_len in mysql_modes:
with Example(f"""mode={mode.strip("'")} key={key_len} iv={iv_len}""") as example:
example_key = f"'{key[:key_len]}'"
example_mode = mode
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_transform = f"aes_decrypt_mysql({mode}, unhex(secret), {example_key}{(', ' + example_iv) if example_iv else ''})"
with Given("I have ciphertexts"):
example_name = basename(example.name)
ciphertexts = getattr(snapshot_module, varname(f"aes_encrypt_mysql_input_example_{example_name}"))
example_ciphertexts = [l.split("\\t")[-1].strip("'") for l in ciphertexts.split("\\n")]
with table("user_data"):
with When("I insert decrypted data"):
node.query(textwrap.dedent(f"""
INSERT INTO
user_data
SELECT
date, name, {example_transform}
FROM
input('date Date, name String, secret String')
FORMAT Values ('2020-01-01', 'user0', '{example_ciphertexts[0]}'), ('2020-01-02', 'user1', '{example_ciphertexts[1]}'), ('2020-01-03', 'user2', '{example_ciphertexts[2]}')
"""))
with And("I read inserted data back"):
r = node.query("SELECT date, name, secret FROM user_data ORDER BY date")
expected = """2020-01-01\tuser0\tuser0_secret\n2020-01-02\tuser1\tuser1_secret\n2020-01-03\tuser2\tuser2_secret"""
with Then("output must match the expected", description=expected):
assert r.output == expected, error()
@TestFeature
@Name("insert")
def feature(self, node="clickhouse1"):
"""Check encryption functions when used during data insertion into a table.
"""
self.context.node = self.context.cluster.node(node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,196 @@
import textwrap
from contextlib import contextmanager
from testflows.core import *
from testflows.asserts import error
from aes_encryption.requirements import *
from aes_encryption.tests.common import mysql_modes, hex
@contextmanager
def table(name, node, mysql_node, secret_type):
"""Create a table that can be accessed using MySQL database engine.
"""
try:
with Given("table in MySQL"):
sql = f"""
CREATE TABLE {name}(
id INT NOT NULL AUTO_INCREMENT,
date DATE,
name VARCHAR(100),
secret {secret_type},
PRIMARY KEY ( id )
);
"""
with When("I drop the table if exists"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
with And("I create a table"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I create a database using MySQL database engine"):
sql = f"""
CREATE DATABASE mysql_db
ENGINE = MySQL('{mysql_node.name}:3306', 'db', 'user', 'password')
"""
with When("I drop database if exists"):
node.query(f"DROP DATABASE IF EXISTS mysql_db")
with And("I create database"):
node.query(textwrap.dedent(sql))
yield
finally:
with And("I drop the database that is using MySQL database engine", flags=TE):
node.query(f"DROP DATABASE IF EXISTS mysql_db")
with And("I drop a table in MySQL", flags=TE):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
@TestOutline(Scenario)
@Examples("mysql_datatype", [
("VARBINARY(100)",),
#("VARCHAR(100)",),
("BLOB", ),
#("TEXT",)
])
def decrypt(self, mysql_datatype):
"""Check that when using a table provided by MySQL database engine that
contains a column encrypted in MySQL stored using specified data type
I can decrypt data in the column using the `decrypt` and `aes_decrypt_mysql`
functions in the select query.
"""
node = self.context.node
mysql_node = self.context.mysql_node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for func in ["decrypt", "aes_decrypt_mysql"]:
for mode, key_len, iv_len in mysql_modes:
exact_key_size = int(mode.split("-")[1])//8
if "ecb" not in mode and not iv_len:
continue
if func == "decrypt":
if iv_len and iv_len != 16:
continue
if key_len != exact_key_size:
continue
with Example(f"""{func} mode={mode.strip("'")} key={key_len} iv={iv_len}"""):
with table("user_data", node, mysql_node, mysql_datatype):
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
with When("I insert encrypted data in MySQL"):
sql = f"""
SET block_encryption_mode = {example_mode};
INSERT INTO user_data VALUES (NULL, '2020-01-01', 'user0', AES_ENCRYPT('secret', {example_key}{(", " + example_iv) if example_iv else ", ''"}));
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read encrypted data in MySQL to make sure it is valid"):
sql = f"""
SET block_encryption_mode = {example_mode};
SELECT id, date, name, AES_DECRYPT(secret, {example_key}{(", " + example_iv) if example_iv else ", ''"}) AS secret FROM user_data;
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read raw encrypted data in MySQL"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"SELECT id, date, name, hex(secret) as secret FROM user_data;\"", exitcode=0)
with And("I read raw data using MySQL database engine"):
output = node.query("SELECT id, date, name, hex(secret) AS secret FROM mysql_db.user_data")
with And("I read decrypted data using MySQL database engine"):
output = node.query(f"""SELECT hex({func}({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""})) FROM mysql_db.user_data""").output.strip()
with Then("output should match the original plain text"):
assert output == hex("secret"), error()
@TestOutline(Scenario)
@Examples("mysql_datatype", [
("VARBINARY(100)",),
#("VARCHAR(100)",),
("BLOB", ),
#("TEXT",)
])
def encrypt(self, mysql_datatype):
"""Check that when using a table provided by MySQL database engine that
we can encrypt data during insert using the `aes_encrypt_mysql` function
and decrypt it in MySQL.
"""
node = self.context.node
mysql_node = self.context.mysql_node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for func in ["encrypt", "aes_encrypt_mysql"]:
for mode, key_len, iv_len in mysql_modes:
exact_key_size = int(mode.split("-")[1])//8
if "ecb" not in mode and not iv_len:
continue
if func == "encrypt":
if iv_len and iv_len != 16:
continue
if key_len != exact_key_size:
continue
with Example(f"""{func} mode={mode.strip("'")} key={key_len} iv={iv_len}"""):
with table("user_data", node, mysql_node, mysql_datatype):
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_transform = f"{func}({mode}, secret, {example_key}{(', ' + example_iv) if example_iv else ''})"
with When("I insert encrypted data into a table provided by MySQL database engine"):
node.query(textwrap.dedent(f"""
INSERT INTO
mysql_db.user_data
SELECT
id, date, name, {example_transform}
FROM
input('id Int32, date Date, name String, secret String')
FORMAT Values (1, '2020-01-01', 'user0', 'secret')
"""))
with And("I read decrypted data using MySQL database engine"):
output = node.query(f"""SELECT hex(aes_decrypt_mysql({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""})) FROM mysql_db.user_data""").output.strip()
with Then("decrypted data from MySQL database engine should should match the original plain text"):
assert output == hex("secret"), error()
with And("I read raw data using MySQL database engine to get expected raw data"):
expected_raw_data = node.query("SELECT hex(secret) AS secret FROM mysql_db.user_data").output.strip()
with And("I read raw encrypted data in MySQL"):
output = mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"SELECT hex(secret) as secret FROM user_data;\"", exitcode=0).output.strip()
with Then("check that raw encryted data in MySQL matches the expected"):
assert expected_raw_data in output, error()
with And("I decrypt data in MySQL to make sure it is valid"):
sql = f"""
SET block_encryption_mode = {example_mode};
SELECT id, date, name, hex(AES_DECRYPT(secret, {example_key}{(", " + example_iv) if example_iv else ", ''"})) AS secret FROM user_data;
"""
output = mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0).output.strip()
with Then("decryted data in MySQL should match the original plain text"):
assert hex("secret") in output, error()
@TestFeature
@Name("database engine")
@Requirements(
RQ_SRS008_AES_Functions_Compatability_Engine_Database_MySQL("1.0")
)
def feature(self, node="clickhouse1", mysql_node="mysql1"):
"""Check usage of encryption functions with [MySQL database engine].
[MySQL database engine]: https://clickhouse.tech/docs/en/engines/database-engines/mysql/
"""
self.context.node = self.context.cluster.node(node)
self.context.mysql_node = self.context.cluster.node(mysql_node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,251 @@
import textwrap
from contextlib import contextmanager
from testflows.core import *
from testflows.asserts import error
from aes_encryption.requirements import *
from aes_encryption.tests.common import mysql_modes, hex
@contextmanager
def dictionary(name, node, mysql_node, secret_type):
"""Create a table in MySQL and use it a source for a dictionary.
"""
try:
with Given("table in MySQL"):
sql = f"""
CREATE TABLE {name}(
id INT NOT NULL AUTO_INCREMENT,
date DATE,
name VARCHAR(100),
secret {secret_type},
PRIMARY KEY ( id )
);
"""
with When("I drop the table if exists"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
with And("I create a table"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("dictionary that uses MySQL table as the external source"):
with When("I drop the dictionary if exists"):
node.query(f"DROP DICTIONARY IF EXISTS dict_{name}")
with And("I create the dictionary"):
sql = f"""
CREATE DICTIONARY dict_{name}
(
id Int32,
date Date,
name String,
secret String
)
PRIMARY KEY id
SOURCE(MYSQL(
USER 'user'
PASSWORD 'password'
DB 'db'
TABLE '{name}'
REPLICA(PRIORITY 1 HOST '{mysql_node.name}' PORT 3306)
))
LAYOUT(HASHED())
LIFETIME(0)
"""
node.query(textwrap.dedent(sql))
yield f"dict_{name}"
finally:
with Finally("I drop the dictionary", flags=TE):
node.query(f"DROP DICTIONARY IF EXISTS dict_{name}")
with And("I drop a table in MySQL", flags=TE):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
@contextmanager
def parameters_dictionary(name, node, mysql_node):
"""Create a table in MySQL and use it a source for a dictionary
that stores parameters for the encryption functions.
"""
try:
with Given("table in MySQL"):
sql = f"""
CREATE TABLE {name}(
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(100),
`mode` VARCHAR(100),
`key` BLOB,
`iv` BLOB,
`text` BLOB,
PRIMARY KEY ( id )
);
"""
with When("I drop the table if exists"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
with And("I create a table"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("dictionary that uses MySQL table as the external source"):
with When("I drop the dictionary if exists"):
node.query(f"DROP DICTIONARY IF EXISTS dict_{name}")
with And("I create the dictionary"):
sql = f"""
CREATE DICTIONARY dict_{name}
(
id Int32,
name String,
mode String,
key String,
iv String,
text String
)
PRIMARY KEY id
SOURCE(MYSQL(
USER 'user'
PASSWORD 'password'
DB 'db'
TABLE '{name}'
REPLICA(PRIORITY 1 HOST '{mysql_node.name}' PORT 3306)
))
LAYOUT(HASHED())
LIFETIME(0)
"""
node.query(textwrap.dedent(sql))
yield f"dict_{name}"
finally:
with Finally("I drop the dictionary", flags=TE):
node.query(f"DROP DICTIONARY IF EXISTS dict_{name}")
with And("I drop a table in MySQL", flags=TE):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
@TestScenario
def parameter_values(self):
"""Check that we can use a dictionary that uses MySQL table as a source
can be used as a parameters store for the `encrypt`, `decrypt`, and
`aes_encrypt_mysql`, `aes_decrypt_mysql` functions.
"""
node = self.context.node
mysql_node = self.context.mysql_node
mode = "'aes-128-cbc'"
key = f"'{'1' * 16}'"
iv = f"'{'2' * 16}'"
plaintext = "'secret'"
for encrypt, decrypt in [
("encrypt", "decrypt"),
("aes_encrypt_mysql", "aes_decrypt_mysql")
]:
with Example(f"{encrypt} and {decrypt}", description=f"Check using dictionary for parameters of {encrypt} and {decrypt} functions."):
with parameters_dictionary("parameters_data", node, mysql_node) as dict_name:
with When("I insert parameters values in MySQL"):
sql = f"""
INSERT INTO parameters_data VALUES (1, 'user0', {mode}, {key}, {iv}, {plaintext});
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I use dictionary values as parameters"):
sql = f"""
SELECT {decrypt}(
dictGet('default.{dict_name}', 'mode', toUInt64(1)),
{encrypt}(
dictGet('default.{dict_name}', 'mode', toUInt64(1)),
dictGet('default.{dict_name}', 'text', toUInt64(1)),
dictGet('default.{dict_name}', 'key', toUInt64(1)),
dictGet('default.{dict_name}', 'iv', toUInt64(1))
),
dictGet('default.{dict_name}', 'key', toUInt64(1)),
dictGet('default.{dict_name}', 'iv', toUInt64(1))
)
"""
output = node.query(textwrap.dedent(sql)).output.strip()
with Then("output should match the plain text"):
assert f"'{output}'" == plaintext, error()
@TestOutline(Scenario)
@Examples("mysql_datatype", [
("VARBINARY(100)",),
#("VARCHAR(100)",),
("BLOB", ),
#("TEXT",)
])
def decrypt(self, mysql_datatype):
"""Check that when using a dictionary that uses MySQL table as a source and
contains a data encrypted in MySQL and stored using specified data type
that we can decrypt data from the dictionary using
the `aes_decrypt_mysql` or `decrypt` functions in the select query.
"""
node = self.context.node
mysql_node = self.context.mysql_node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for func in ["decrypt", "aes_decrypt_mysql"]:
for mode, key_len, iv_len in mysql_modes:
exact_key_size = int(mode.split("-")[1])//8
if "ecb" not in mode and not iv_len:
continue
if func == "decrypt":
if iv_len and iv_len != 16:
continue
if key_len != exact_key_size:
continue
with Example(f"""{func} mode={mode.strip("'")} key={key_len} iv={iv_len}"""):
with dictionary("user_data", node, mysql_node, mysql_datatype) as dict_name:
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
with When("I insert encrypted data in MySQL"):
sql = f"""
SET block_encryption_mode = {example_mode};
INSERT INTO user_data VALUES (NULL, '2020-01-01', 'user0', AES_ENCRYPT('secret', {example_key}{(", " + example_iv) if example_iv else ", ''"}));
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read encrypted data in MySQL to make sure it is valid"):
sql = f"""
SET block_encryption_mode = {example_mode};
SELECT id, date, name, AES_DECRYPT(secret, {example_key}{(", " + example_iv) if example_iv else ", ''"}) AS secret FROM user_data;
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read raw encrypted data in MySQL"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"SELECT id, date, name, hex(secret) as secret FROM user_data;\"", exitcode=0)
with And("I read raw data using MySQL dictionary"):
output = node.query(f"SELECT hex(dictGet('default.{dict_name}', 'secret', toUInt64(1))) AS secret")
with And("I read decrypted data using MySQL dictionary"):
output = node.query(textwrap.dedent(f"""
SELECT hex(
{func}(
{example_mode},
dictGet('default.{dict_name}', 'secret', toUInt64(1)),
{example_key}{(", " + example_iv) if example_iv else ""}
)
)
""")).output.strip()
with Then("output should match the original plain text"):
assert output == hex("secret"), error()
@TestFeature
@Name("dictionary")
@Requirements(
RQ_SRS008_AES_Functions_Compatability_Dictionaries("1.0")
)
def feature(self, node="clickhouse1", mysql_node="mysql1"):
"""Check usage of encryption functions with [MySQL dictionary].
[MySQL dictionary]: https://clickhouse.tech/docs/en/sql-reference/dictionaries/external-dictionaries/external-dicts-dict-sources/#dicts-external_dicts_dict_sources-mysql
"""
self.context.node = self.context.cluster.node(node)
self.context.mysql_node = self.context.cluster.node(mysql_node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,18 @@
from testflows.core import *
from aes_encryption.requirements import *
@TestFeature
@Name("mysql")
@Requirements(
RQ_SRS008_AES_Functions_Compatability_MySQL("1.0")
)
def feature(self, node="clickhouse1"):
"""Check encryption functions usage compatibility with MySQL.
"""
self.context.node = self.context.cluster.node(node)
Feature(run=load("aes_encryption.tests.compatibility.mysql.table_engine", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.compatibility.mysql.database_engine", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.compatibility.mysql.table_function", "feature"), flags=TE)
Feature(run=load("aes_encryption.tests.compatibility.mysql.dictionary", "feature"), flags=TE)

View File

@ -0,0 +1,202 @@
import textwrap
from contextlib import contextmanager
from testflows.core import *
from testflows.asserts import error
from aes_encryption.requirements import *
from aes_encryption.tests.common import mysql_modes, hex
@contextmanager
def table(name, node, mysql_node, secret_type):
"""Create a table that can be accessed using MySQL table engine.
"""
try:
with Given("table in MySQL"):
sql = f"""
CREATE TABLE {name}(
id INT NOT NULL AUTO_INCREMENT,
date DATE,
name VARCHAR(100),
secret {secret_type},
PRIMARY KEY ( id )
);
"""
with When("I drop the table if exists"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
with And("I create a table"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I create a table using MySQL table engine"):
sql = f"""
CREATE TABLE mysql_{name}
(
id Nullable(Int32),
date Nullable(Date),
name Nullable(String),
secret Nullable(String)
)
ENGINE = MySQL('{mysql_node.name}:3306', 'db', '{name}', 'user', 'password')
"""
with When("I drop table if exists"):
node.query(f"DROP TABLE IF EXISTS mysql_{name}")
with And("I create table"):
node.query(textwrap.dedent(sql))
yield
finally:
with And("I drop a table using MySQL table engine", flags=TE):
node.query(f"DROP TABLE IF EXISTS mysql_{name}")
with And("I drop a table in MySQL", flags=TE):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
@TestOutline(Scenario)
@Examples("mysql_datatype", [
("VARBINARY(100)",),
#("VARCHAR(100)",),
("BLOB", ),
#("TEXT",)
])
def decrypt(self, mysql_datatype):
"""Check that when using a table with MySQL table engine that
contains a column encrypted in MySQL stored using specified data type
I can decrypt data in the column using the `decrypt` and `aes_decrypt_mysql`
functions in the select query.
"""
node = self.context.node
mysql_node = self.context.mysql_node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for func in ["decrypt", "aes_decrypt_mysql"]:
for mode, key_len, iv_len in mysql_modes:
exact_key_size = int(mode.split("-")[1])//8
if "ecb" not in mode and not iv_len:
continue
if func == "decrypt":
if iv_len and iv_len != 16:
continue
if key_len != exact_key_size:
continue
with Example(f"""{func} mode={mode.strip("'")} key={key_len} iv={iv_len}"""):
with table("user_data", node, mysql_node, mysql_datatype):
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
with When("I insert encrypted data in MySQL"):
sql = f"""
SET block_encryption_mode = {example_mode};
INSERT INTO user_data VALUES (NULL, '2020-01-01', 'user0', AES_ENCRYPT('secret', {example_key}{(", " + example_iv) if example_iv else ", ''"}));
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read encrypted data in MySQL to make sure it is valid"):
sql = f"""
SET block_encryption_mode = {example_mode};
SELECT id, date, name, AES_DECRYPT(secret, {example_key}{(", " + example_iv) if example_iv else ", ''"}) AS secret FROM user_data;
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read raw encrypted data in MySQL"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"SELECT id, date, name, hex(secret) as secret FROM user_data;\"", exitcode=0)
with And("I read raw data using MySQL table engine"):
output = node.query("SELECT id, date, name, hex(secret) AS secret FROM mysql_user_data")
with And("I read decrypted data via MySQL table engine"):
output = node.query(f"""SELECT hex({func}({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""})) FROM mysql_user_data""").output.strip()
with Then("the output should match the original plain text"):
assert output == hex("secret"), error()
@TestOutline(Scenario)
@Examples("mysql_datatype", [
("VARBINARY(100)",),
#("VARCHAR(100)",),
("BLOB", ),
#("TEXT",)
])
def encrypt(self, mysql_datatype):
"""Check that when using a table with MySQL table engine that
we can encrypt data during insert using the `encrypt` and `aes_encrypt_mysql`
functions and decrypt it in MySQL.
"""
node = self.context.node
mysql_node = self.context.mysql_node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for func in ["encrypt", "aes_encrypt_mysql"]:
for mode, key_len, iv_len in mysql_modes:
exact_key_size = int(mode.split("-")[1])//8
if "ecb" not in mode and not iv_len:
continue
if func == "encrypt":
if iv_len and iv_len != 16:
continue
if key_len != exact_key_size:
continue
with Example(f"""mode={mode.strip("'")} key={key_len} iv={iv_len}"""):
with table("user_data", node, mysql_node, mysql_datatype):
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_transform = f"{func}({mode}, secret, {example_key}{(', ' + example_iv) if example_iv else ''})"
with When("I insert encrypted data into MySQL table engine"):
node.query(textwrap.dedent(f"""
INSERT INTO
mysql_user_data
SELECT
id, date, name, {example_transform}
FROM
input('id Nullable(Int32), date Date, name String, secret String')
FORMAT Values (null, '2020-01-01', 'user0', 'secret')
"""))
with And("I read decrypted data via MySQL table engine"):
output = node.query(f"""SELECT hex(aes_decrypt_mysql({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""})) FROM mysql_user_data""").output.strip()
with Then("decrypted data from MySQL table engine should should match the original plain text"):
assert output == hex("secret"), error()
with And("I read raw data using MySQL table engine to get expected raw data"):
expected_raw_data = node.query("SELECT hex(secret) AS secret FROM mysql_user_data").output.strip()
with And("I read raw encrypted data in MySQL"):
output = mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"SELECT hex(secret) as secret FROM user_data;\"", exitcode=0).output.strip()
with Then("check that raw encryted data in MySQL matches the expected"):
assert expected_raw_data in output, error()
with And("I decrypt data in MySQL to make sure it is valid"):
sql = f"""
SET block_encryption_mode = {example_mode};
SELECT id, date, name, hex(AES_DECRYPT(secret, {example_key}{(", " + example_iv) if example_iv else ", ''"})) AS secret FROM user_data;
"""
output = mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0).output.strip()
with Then("decryted data in MySQL should match the original plain text"):
assert hex("secret") in output, error()
@TestFeature
@Name("table engine")
@Requirements(
RQ_SRS008_AES_Functions_Compatability_Engine_Table_MySQL("1.0")
)
def feature(self, node="clickhouse1", mysql_node="mysql1"):
"""Check usage of encryption functions with [MySQL table engine].
[MySQL table engine]: https://clickhouse.tech/docs/en/engines/table-engines/integrations/mysql/
"""
self.context.node = self.context.cluster.node(node)
self.context.mysql_node = self.context.cluster.node(mysql_node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,183 @@
import textwrap
from contextlib import contextmanager
from testflows.core import *
from testflows.asserts import error
from aes_encryption.requirements import *
from aes_encryption.tests.common import mysql_modes, hex
@contextmanager
def table(name, node, mysql_node, secret_type):
"""Create a table that can be accessed using MySQL table function.
"""
try:
with Given("table in MySQL"):
sql = f"""
CREATE TABLE {name}(
id INT NOT NULL AUTO_INCREMENT,
date DATE,
name VARCHAR(100),
secret {secret_type},
PRIMARY KEY ( id )
);
"""
with When("I drop the table if exists"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
with And("I create a table"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
yield f"mysql('{mysql_node.name}:3306', 'db', 'user_data', 'user', 'password')"
finally:
with And("I drop a table in MySQL", flags=TE):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"DROP TABLE IF EXISTS {name};\"", exitcode=0)
@TestOutline(Scenario)
@Examples("mysql_datatype", [
("VARBINARY(100)",),
#("VARCHAR(100)",),
("BLOB", ),
#("TEXT",)
])
def decrypt(self, mysql_datatype):
"""Check that when using a table accessed through MySQL table function that
contains a column encrypted in MySQL stored using specified data type
I can decrypt data in the column using the `decrypt` and `aes_decrypt_mysql`
functions in the select query.
"""
node = self.context.node
mysql_node = self.context.mysql_node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for func in ["decrypt", "aes_decrypt_mysql"]:
for mode, key_len, iv_len in mysql_modes:
exact_key_size = int(mode.split("-")[1])//8
if "ecb" not in mode and not iv_len:
continue
if func == "decrypt":
if iv_len and iv_len != 16:
continue
if key_len != exact_key_size:
continue
with Example(f"""{func} mode={mode.strip("'")} key={key_len} iv={iv_len}"""):
with table("user_data", node, mysql_node, mysql_datatype) as table_function:
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
with When("I insert encrypted data in MySQL"):
sql = f"""
SET block_encryption_mode = {example_mode};
INSERT INTO user_data VALUES (NULL, '2020-01-01', 'user0', AES_ENCRYPT('secret', {example_key}{(", " + example_iv) if example_iv else ", ''"}));
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read encrypted data in MySQL to make sure it is valid"):
sql = f"""
SET block_encryption_mode = {example_mode};
SELECT id, date, name, AES_DECRYPT(secret, {example_key}{(", " + example_iv) if example_iv else ", ''"}) AS secret FROM user_data;
"""
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0)
with And("I read raw encrypted data in MySQL"):
mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"SELECT id, date, name, hex(secret) as secret FROM user_data;\"", exitcode=0)
with And("I read raw data using MySQL table function"):
output = node.query(f"SELECT id, date, name, hex(secret) AS secret FROM {table_function}")
with And("I read decrypted data using MySQL table function"):
output = node.query(f"""SELECT hex({func}({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""})) FROM {table_function}""").output.strip()
with Then("output should match the original plain text"):
assert output == hex("secret"), error()
@TestOutline(Scenario)
@Examples("mysql_datatype", [
("VARBINARY(100)",),
#("VARCHAR(100)",),
("BLOB", ),
#("TEXT",)
])
def encrypt(self, mysql_datatype):
"""Check that when using a table accessed through MySQL table function that
we can encrypt data during insert using the `aes_encrypt_mysql` function
and decrypt it in MySQL.
"""
node = self.context.node
mysql_node = self.context.mysql_node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for func in ["encrypt", "aes_encrypt_mysql"]:
for mode, key_len, iv_len in mysql_modes:
exact_key_size = int(mode.split("-")[1])//8
if "ecb" not in mode and not iv_len:
continue
if func == "encrypt":
if iv_len and iv_len != 16:
continue
if key_len != exact_key_size:
continue
with Example(f"""{func} mode={mode.strip("'")} key={key_len} iv={iv_len}"""):
with table("user_data", node, mysql_node, mysql_datatype) as table_function:
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_transform = f"{func}({mode}, secret, {example_key}{(', ' + example_iv) if example_iv else ''})"
with When("I insert encrypted data into a table provided by MySQL database engine"):
node.query(textwrap.dedent(f"""
INSERT INTO TABLE FUNCTION
{table_function}
SELECT
id, date, name, {example_transform}
FROM
input('id Int32, date Date, name String, secret String')
FORMAT Values (1, '2020-01-01', 'user0', 'secret')
"""))
with And("I read decrypted data using MySQL database engine"):
output = node.query(f"""SELECT hex(aes_decrypt_mysql({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""})) FROM {table_function}""").output.strip()
with Then("decrypted data from MySQL database engine should should match the original plain text"):
assert output == hex("secret"), error()
with And("I read raw data using MySQL database engine to get expected raw data"):
expected_raw_data = node.query(f"SELECT hex(secret) AS secret FROM {table_function}").output.strip()
with And("I read raw encrypted data in MySQL"):
output = mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user -e \"SELECT hex(secret) as secret FROM user_data;\"", exitcode=0).output.strip()
with Then("check that raw encryted data in MySQL matches the expected"):
assert expected_raw_data in output, error()
with And("I decrypt data in MySQL to make sure it is valid"):
sql = f"""
SET block_encryption_mode = {example_mode};
SELECT id, date, name, hex(AES_DECRYPT(secret, {example_key}{(", " + example_iv) if example_iv else ", ''"})) AS secret FROM user_data;
"""
output = mysql_node.command(f"MYSQL_PWD=password mysql -D db -u user <<'EOF'{textwrap.dedent(sql)}\nEOF", exitcode=0).output.strip()
with Then("decryted data in MySQL should match the original plain text"):
assert hex("secret") in output, error()
@TestFeature
@Name("table function")
@Requirements(
RQ_SRS008_AES_Functions_Compatability_TableFunction_MySQL("1.0")
)
def feature(self, node="clickhouse1", mysql_node="mysql1"):
"""Check usage of encryption functions with [MySQL table function].
[MySQL table function]: https://clickhouse.tech/docs/en/sql-reference/table-functions/mysql/
"""
self.context.node = self.context.cluster.node(node)
self.context.mysql_node = self.context.cluster.node(mysql_node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,186 @@
import textwrap
from contextlib import contextmanager
from testflows.core import *
from testflows.asserts.helpers import varname
from testflows.asserts import values, error, snapshot
from aes_encryption.tests.common import modes, mysql_modes
@contextmanager
def table(name, sql):
node = current().context.node
try:
with Given("table"):
with By("dropping table if exists"):
node.query(f"DROP TABLE IF EXISTS {name}")
with And("creating a table"):
node.query(textwrap.dedent(sql.format(name=name)))
yield
finally:
with Finally("I drop the table", flags=TE):
node.query(f"DROP TABLE IF EXISTS {name}")
@TestScenario
def decrypt(self):
"""Check decrypting column when reading data from a table.
"""
node = self.context.node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
aad = "some random aad"
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} key={key_len} iv={iv_len} aad={aad_len}""") as example:
with table("user_table", """
CREATE TABLE {name}
(
date Nullable(Date),
name Nullable(String),
secret Nullable(String)
)
ENGINE = Memory()
"""):
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_aad = None if not aad_len else f"'{aad}'"
with When("I insert encrypted data"):
encrypted_secret = node.query(f"""SELECT hex(encrypt({example_mode}, 'secret', {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}))""").output.strip()
node.query(textwrap.dedent(f"""
INSERT INTO user_table
(date, name, secret)
VALUES
('2020-01-01', 'user0', unhex('{encrypted_secret}'))
"""))
with And("I decrypt data during query"):
output = node.query(f"""SELECT name, decrypt({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}) AS secret FROM user_table FORMAT JSONEachRow""").output.strip()
with Then("I should get back the original plain text"):
assert output == '{"name":"user0","secret":"secret"}', error()
@TestScenario
def decrypt_multiple(self, count=1000):
"""Check decrypting column when reading multiple entries
encrypted with the same parameters for the same user
from a table.
"""
node = self.context.node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
aad = "some random aad"
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} key={key_len} iv={iv_len} aad={aad_len}""") as example:
with table("user_table", """
CREATE TABLE {name}
(
date Nullable(Date),
name Nullable(String),
secret Nullable(String)
)
ENGINE = Memory()
"""):
example_mode = mode
example_key = f"'{key[:key_len]}'"
example_iv = None if not iv_len else f"'{iv[:iv_len]}'"
example_aad = None if not aad_len else f"'{aad}'"
with When("I insert encrypted data"):
encrypted_secret = node.query(f"""SELECT hex(encrypt({example_mode}, 'secret', {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}))""").output.strip()
values = [f"('2020-01-01', 'user0', unhex('{encrypted_secret}'))"] * count
node.query(
"INSERT INTO user_table\n"
" (date, name, secret)\n"
f"VALUES {', '.join(values)}")
with And("I decrypt data", description="using a subquery and get the number of entries that match the plaintext"):
output = node.query(f"""SELECT count() AS count FROM (SELECT name, decrypt({example_mode}, secret, {example_key}{(", " + example_iv) if example_iv else ""}{(", " + example_aad) if example_aad else ""}) AS secret FROM user_table) WHERE secret = 'secret' FORMAT JSONEachRow""").output.strip()
with Then("I should get back the expected result", description=f"{count}"):
assert output == f'{{"count":"{count}"}}', error()
@TestScenario
def decrypt_unique(self):
"""Check decrypting column when reading multiple entries
encrypted with the different parameters for each user
from a table.
"""
node = self.context.node
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
aad = "some random aad"
with table("user_table", """
CREATE TABLE {name}
(
id UInt64,
date Nullable(Date),
name Nullable(String),
secret Nullable(String)
)
ENGINE = Memory()
"""):
user_modes = []
user_keys = []
with When("I get encrypted data"):
user_id = 0
values = []
for mode, key_len, iv_len, aad_len in modes:
if "gcm" in mode:
continue
user_modes.append(mode)
user_keys.append(f"'{key[:key_len]}'")
with When(f"I get encrypted data for user {user_id}"):
encrypted_secret = node.query(
f"""SELECT hex(encrypt({user_modes[-1]}, 'secret', {user_keys[-1]}))"""
).output.strip()
values.append(f"({user_id}, '2020-01-01', 'user{user_id}', unhex('{encrypted_secret}'))")
user_id += 1
with And("I insert encrypted data for all users"):
node.query(
"INSERT INTO user_table\n"
" (id, date, name, secret)\n"
f"VALUES {', '.join(values)}")
with And("I read decrypted data for all users"):
output = node.query(textwrap.dedent(f"""
SELECT
count() AS count
FROM
(
SELECT
[{",".join(user_modes)}] AS modes,
[{",".join(user_keys)}] AS keys,
name,
decrypt(modes[id], secret, keys[id]) AS secret
FROM user_table
)
WHERE
secret = 'secret'
FORMAT JSONEachRow
""")).output.strip()
with Then("I should get back the expected result", description=f"{count}"):
assert output == f'{{"count":"{count}"}}', error()
@TestFeature
@Name("select")
def feature(self, node="clickhouse1"):
"""Check encryption functions when used during table querying.
"""
self.context.node = self.context.cluster.node(node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,192 @@
aes_encrypt_mysql_input_example_mode_aes_128_ecb_key_16_iv_None = r"""'2020-01-01\tuser0\t8C9598C3C8D8AC241DDF0D1B22020104\n2020-01-02\tuser1\tC5ECE31A240069D8F169B9F8CF687779\n2020-01-03\tuser2\t9FCFA4B05DD49D2B24BA61091F963CE3'"""
aes_encrypt_mysql_input_example_mode_aes_128_ecb_key_24_iv_None = r"""'2020-01-01\tuser0\tB418FF12BCBF9E42FA7C19D6EE26BF0B\n2020-01-02\tuser1\t3147A3FEE47DF418D1D75CBC1BC14DE6\n2020-01-03\tuser2\tAECEFD40C6632A0FC033D040E44CCBCC'"""
aes_encrypt_mysql_input_example_mode_aes_192_ecb_key_24_iv_None = r"""'2020-01-01\tuser0\t897F14C4E497962D986A7E7EA57AA043\n2020-01-02\tuser1\tED84AF2B3447113DA451E4577F649E36\n2020-01-03\tuser2\t4976F9D5AE195E61694A9ADCDD8A076F'"""
aes_encrypt_mysql_input_example_mode_aes_192_ecb_key_32_iv_None = r"""'2020-01-01\tuser0\t044E715357AF77234FD95359666CAFF3\n2020-01-02\tuser1\tB633EF852CE85B4C97827401FD9B606B\n2020-01-03\tuser2\t2AFF7052C748E4BC3BDA8460AFD5A21D'"""
aes_encrypt_mysql_input_example_mode_aes_256_ecb_key_32_iv_None = r"""'2020-01-01\tuser0\tBABD6C071FDEE1C9A33877006FBB0BE6\n2020-01-02\tuser1\t7753E81D1DB9DC91FC8148E88B3E9526\n2020-01-03\tuser2\tD77D1A8DF82C2273BF0D19A14526531F'"""
aes_encrypt_mysql_input_example_mode_aes_256_ecb_key_64_iv_None = r"""'2020-01-01\tuser0\tBFFEC9DF7285A3EC799C941E1450839C\n2020-01-02\tuser1\t3EA0ECBD06326D227A7B9519B1A2955D\n2020-01-03\tuser2\t1478C57DD49523ABDB83A0917F0EDA60'"""
aes_encrypt_mysql_input_example_mode_aes_128_cbc_key_16_iv_None = r"""'2020-01-01\tuser0\t8C9598C3C8D8AC241DDF0D1B22020104\n2020-01-02\tuser1\tC5ECE31A240069D8F169B9F8CF687779\n2020-01-03\tuser2\t9FCFA4B05DD49D2B24BA61091F963CE3'"""
aes_encrypt_mysql_input_example_mode_aes_192_cbc_key_24_iv_None = r"""'2020-01-01\tuser0\t897F14C4E497962D986A7E7EA57AA043\n2020-01-02\tuser1\tED84AF2B3447113DA451E4577F649E36\n2020-01-03\tuser2\t4976F9D5AE195E61694A9ADCDD8A076F'"""
aes_encrypt_mysql_input_example_mode_aes_256_cbc_key_32_iv_None = r"""'2020-01-01\tuser0\tBABD6C071FDEE1C9A33877006FBB0BE6\n2020-01-02\tuser1\t7753E81D1DB9DC91FC8148E88B3E9526\n2020-01-03\tuser2\tD77D1A8DF82C2273BF0D19A14526531F'"""
aes_encrypt_mysql_input_example_mode_aes_128_cbc_key_16_iv_16 = r"""'2020-01-01\tuser0\tFC93C1D5E5E3B054C1F3A5692AAC0A61\n2020-01-02\tuser1\tD6DBC76ABCB14B7C6D93F1A5FCA66B9C\n2020-01-03\tuser2\tD4F4158A650D01EB505CC72EFE455486'"""
aes_encrypt_mysql_input_example_mode_aes_128_cbc_key_24_iv_24 = r"""'2020-01-01\tuser0\t26CEE6B6EBDDE1BF887FDEB75F28FB52\n2020-01-02\tuser1\tF9EC1A75BEEFF70B4DEB39AAD075CEFF\n2020-01-03\tuser2\t3FF84AB3BD40FAEEF70F06BCF6AF9C42'"""
aes_encrypt_mysql_input_example_mode_aes_192_cbc_key_24_iv_16 = r"""'2020-01-01\tuser0\t0E3BAF7F4E0BFCFFAE2589B67F71E277\n2020-01-02\tuser1\t2581CCEE9ABE5770480901D65B3D9222\n2020-01-03\tuser2\tED9F3BD8DB12FDF9F2462FFA572361E7'"""
aes_encrypt_mysql_input_example_mode_aes_192_cbc_key_32_iv_32 = r"""'2020-01-01\tuser0\t07371B5DE2E378EE08A3A8B6B9FEAD13\n2020-01-02\tuser1\t3C0BF5D187421ECFFD3E00474A154452\n2020-01-03\tuser2\t05B253FA783D78D864AF7C4D5E6A492D'"""
aes_encrypt_mysql_input_example_mode_aes_256_cbc_key_32_iv_16 = r"""'2020-01-01\tuser0\t72AC7BA6F283EA94A3C33C4D3E51C7D3\n2020-01-02\tuser1\tDACBBE79062F1C721A01CEEE3E85524F\n2020-01-03\tuser2\tFF5A09D19E5EB2ADD94581308588E44A'"""
aes_encrypt_mysql_input_example_mode_aes_256_cbc_key_64_iv_64 = r"""'2020-01-01\tuser0\t573924F0BB4AA1780D45DB6451F123D6\n2020-01-02\tuser1\t007A54AA7ADE8EF844D28936486D75BC\n2020-01-03\tuser2\tAA7249B514398FE1EE827C44402BCE57'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb1_key_16_iv_None = r"""'2020-01-01\tuser0\t750BE8662F57A095EC0E610C\n2020-01-02\tuser1\t750BE8662E444A6284C0FC72\n2020-01-03\tuser2\t750BE8662C000B61CDCF1C94'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb1_key_24_iv_None = r"""'2020-01-01\tuser0\t5DCC67A043EB776D8B7F5B70\n2020-01-02\tuser1\t5DCC67A042B46DFCC10EFD66\n2020-01-03\tuser2\t5DCC67A040243A8C1346D2DD'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb1_key_32_iv_None = r"""'2020-01-01\tuser0\tFAAC1A7D2CE844F8DEB4C44E\n2020-01-02\tuser1\tFAAC1A7D2DF85A43828C0FF8\n2020-01-03\tuser2\tFAAC1A7D2FC7582CCEFCF330'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb1_key_16_iv_16 = r"""'2020-01-01\tuser0\t7670A865D13B1B65AD46F8ED\n2020-01-02\tuser1\t7670A865D046007A1E218286\n2020-01-03\tuser2\t7670A865D2E5B091492ECCFB'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb1_key_24_iv_24 = r"""'2020-01-01\tuser0\t51EADDE82195C31118D0C171\n2020-01-02\tuser1\t51EADDE82009A46518270271\n2020-01-03\tuser2\t51EADDE8235CB38F95766481'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb1_key_24_iv_16 = r"""'2020-01-01\tuser0\t7F38C051539074C0A635C937\n2020-01-02\tuser1\t7F38C051520A30DFACBE9564\n2020-01-03\tuser2\t7F38C051500DA29FF0E7B799'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb1_key_32_iv_32 = r"""'2020-01-01\tuser0\t2067186DB91666DE730D0708\n2020-01-02\tuser1\t2067186DB83E2E8B0019F839\n2020-01-03\tuser2\t2067186DBB540332BFC84955'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb1_key_32_iv_16 = r"""'2020-01-01\tuser0\t0A216A58A5C0A33215E8E722\n2020-01-02\tuser1\t0A216A58A4E94067ABF030B6\n2020-01-03\tuser2\t0A216A58A6822CAB0318C632'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb1_key_64_iv_64 = r"""'2020-01-01\tuser0\t81BD636E1BF4CA02399943E3\n2020-01-02\tuser1\t81BD636E1A93D5D6DD9DCD8D\n2020-01-03\tuser2\t81BD636E18F15168D19C8117'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb8_key_16_iv_None = r"""'2020-01-01\tuser0\t650D96B9698D20DB12E2E437\n2020-01-02\tuser1\t650D96B968F00D16ABF2852E\n2020-01-03\tuser2\t650D96B96B8141F425E60D6B'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb8_key_24_iv_None = r"""'2020-01-01\tuser0\t72C4724B2F528724A12041C0\n2020-01-02\tuser1\t72C4724B2EF3C6A6FF9E09A9\n2020-01-03\tuser2\t72C4724B2D6EAB1D47709E15'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb8_key_32_iv_None = r"""'2020-01-01\tuser0\tC5FD6C94961765ED204F2BCA\n2020-01-02\tuser1\tC5FD6C9497AB1C1AF1DE671C\n2020-01-03\tuser2\tC5FD6C949491F4A3EA5069B3'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb8_key_16_iv_16 = r"""'2020-01-01\tuser0\t471D217E9CA3593FFEC955C8\n2020-01-02\tuser1\t471D217E9D7F484D85F81F19\n2020-01-03\tuser2\t471D217E9EBBFD2EA9841008'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb8_key_24_iv_24 = r"""'2020-01-01\tuser0\t2EE6147B830481BE36CBE350\n2020-01-02\tuser1\t2EE6147B82DE8F3197AF17A6\n2020-01-03\tuser2\t2EE6147B81FF826E798A0355'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb8_key_24_iv_16 = r"""'2020-01-01\tuser0\t1D98EFFAEB9907457BD3FCB2\n2020-01-02\tuser1\t1D98EFFAEA2D930825C6AE22\n2020-01-03\tuser2\t1D98EFFAE92C1D018438B98B'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb8_key_32_iv_32 = r"""'2020-01-01\tuser0\t4410165F7DCFDDBB1B15573F\n2020-01-02\tuser1\t4410165F7CFE6A0D2FD5CA9C\n2020-01-03\tuser2\t4410165F7FE8E0C081B3FB7B'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb8_key_32_iv_16 = r"""'2020-01-01\tuser0\t1C07B443BB7D7D60E9999C1D\n2020-01-02\tuser1\t1C07B443BA9674A3F68FF3FE\n2020-01-03\tuser2\t1C07B443B95F4B68161A616F'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb8_key_64_iv_64 = r"""'2020-01-01\tuser0\tA6D2368A5F177157D73FBD9D\n2020-01-02\tuser1\tA6D2368A5E695ADF99475359\n2020-01-03\tuser2\tA6D2368A5DB96AFD43311124'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb128_key_16_iv_None = r"""'2020-01-01\tuser0\t65ACA4C7C6338E0F7EB60812\n2020-01-02\tuser1\t65ACA4C7C7338E0F7EB60812\n2020-01-03\tuser2\t65ACA4C7C4338E0F7EB60812'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb128_key_24_iv_None = r"""'2020-01-01\tuser0\t72C47CEF0D63D2FB4FBC3CE4\n2020-01-02\tuser1\t72C47CEF0C63D2FB4FBC3CE4\n2020-01-03\tuser2\t72C47CEF0F63D2FB4FBC3CE4'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb128_key_32_iv_None = r"""'2020-01-01\tuser0\tC5FDAAECF7B42C68180AA151\n2020-01-02\tuser1\tC5FDAAECF6B42C68180AA151\n2020-01-03\tuser2\tC5FDAAECF5B42C68180AA151'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb128_key_16_iv_16 = r"""'2020-01-01\tuser0\t47FBCCF6ED598C3D8A4B05C5\n2020-01-02\tuser1\t47FBCCF6EC598C3D8A4B05C5\n2020-01-03\tuser2\t47FBCCF6EF598C3D8A4B05C5'"""
aes_encrypt_mysql_input_example_mode_aes_128_cfb128_key_24_iv_24 = r"""'2020-01-01\tuser0\t2E046787D9EFFED25D69C908\n2020-01-02\tuser1\t2E046787D8EFFED25D69C908\n2020-01-03\tuser2\t2E046787DBEFFED25D69C908'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb128_key_24_iv_16 = r"""'2020-01-01\tuser0\t1DB482E0874F04D4E734607A\n2020-01-02\tuser1\t1DB482E0864F04D4E734607A\n2020-01-03\tuser2\t1DB482E0854F04D4E734607A'"""
aes_encrypt_mysql_input_example_mode_aes_192_cfb128_key_32_iv_32 = r"""'2020-01-01\tuser0\t44D3EB069FF443A121590842\n2020-01-02\tuser1\t44D3EB069EF443A121590842\n2020-01-03\tuser2\t44D3EB069DF443A121590842'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb128_key_32_iv_16 = r"""'2020-01-01\tuser0\t1C2BED650C8137ED139226D3\n2020-01-02\tuser1\t1C2BED650D8137ED139226D3\n2020-01-03\tuser2\t1C2BED650E8137ED139226D3'"""
aes_encrypt_mysql_input_example_mode_aes_256_cfb128_key_64_iv_64 = r"""'2020-01-01\tuser0\tA69DAA2E8B265618D25D5FE4\n2020-01-02\tuser1\tA69DAA2E8A265618D25D5FE4\n2020-01-03\tuser2\tA69DAA2E89265618D25D5FE4'"""
aes_encrypt_mysql_input_example_mode_aes_128_ofb_key_16_iv_None = r"""'2020-01-01\tuser0\t65ACA4C7C6338E0F7EB60812\n2020-01-02\tuser1\t65ACA4C7C7338E0F7EB60812\n2020-01-03\tuser2\t65ACA4C7C4338E0F7EB60812'"""
aes_encrypt_mysql_input_example_mode_aes_192_ofb_key_24_iv_None = r"""'2020-01-01\tuser0\t72C47CEF0D63D2FB4FBC3CE4\n2020-01-02\tuser1\t72C47CEF0C63D2FB4FBC3CE4\n2020-01-03\tuser2\t72C47CEF0F63D2FB4FBC3CE4'"""
aes_encrypt_mysql_input_example_mode_aes_256_ofb_key_32_iv_None = r"""'2020-01-01\tuser0\tC5FDAAECF7B42C68180AA151\n2020-01-02\tuser1\tC5FDAAECF6B42C68180AA151\n2020-01-03\tuser2\tC5FDAAECF5B42C68180AA151'"""
aes_encrypt_mysql_input_example_mode_aes_128_ofb_key_16_iv_16 = r"""'2020-01-01\tuser0\t47FBCCF6ED598C3D8A4B05C5\n2020-01-02\tuser1\t47FBCCF6EC598C3D8A4B05C5\n2020-01-03\tuser2\t47FBCCF6EF598C3D8A4B05C5'"""
aes_encrypt_mysql_input_example_mode_aes_128_ofb_key_24_iv_24 = r"""'2020-01-01\tuser0\t2E046787D9EFFED25D69C908\n2020-01-02\tuser1\t2E046787D8EFFED25D69C908\n2020-01-03\tuser2\t2E046787DBEFFED25D69C908'"""
aes_encrypt_mysql_input_example_mode_aes_192_ofb_key_24_iv_16 = r"""'2020-01-01\tuser0\t1DB482E0874F04D4E734607A\n2020-01-02\tuser1\t1DB482E0864F04D4E734607A\n2020-01-03\tuser2\t1DB482E0854F04D4E734607A'"""
aes_encrypt_mysql_input_example_mode_aes_192_ofb_key_32_iv_32 = r"""'2020-01-01\tuser0\t44D3EB069FF443A121590842\n2020-01-02\tuser1\t44D3EB069EF443A121590842\n2020-01-03\tuser2\t44D3EB069DF443A121590842'"""
aes_encrypt_mysql_input_example_mode_aes_256_ofb_key_32_iv_16 = r"""'2020-01-01\tuser0\t1C2BED650C8137ED139226D3\n2020-01-02\tuser1\t1C2BED650D8137ED139226D3\n2020-01-03\tuser2\t1C2BED650E8137ED139226D3'"""
aes_encrypt_mysql_input_example_mode_aes_256_ofb_key_64_iv_64 = r"""'2020-01-01\tuser0\tA69DAA2E8B265618D25D5FE4\n2020-01-02\tuser1\tA69DAA2E8A265618D25D5FE4\n2020-01-03\tuser2\tA69DAA2E89265618D25D5FE4'"""
encrypt_input_example_mode_aes_128_ecb_iv_None_aad_None = r"""'2020-01-01\tuser0\t8C9598C3C8D8AC241DDF0D1B22020104\n2020-01-02\tuser1\tC5ECE31A240069D8F169B9F8CF687779\n2020-01-03\tuser2\t9FCFA4B05DD49D2B24BA61091F963CE3'"""
encrypt_input_example_mode_aes_192_ecb_iv_None_aad_None = r"""'2020-01-01\tuser0\t897F14C4E497962D986A7E7EA57AA043\n2020-01-02\tuser1\tED84AF2B3447113DA451E4577F649E36\n2020-01-03\tuser2\t4976F9D5AE195E61694A9ADCDD8A076F'"""
encrypt_input_example_mode_aes_256_ecb_iv_None_aad_None = r"""'2020-01-01\tuser0\tBABD6C071FDEE1C9A33877006FBB0BE6\n2020-01-02\tuser1\t7753E81D1DB9DC91FC8148E88B3E9526\n2020-01-03\tuser2\tD77D1A8DF82C2273BF0D19A14526531F'"""
encrypt_input_example_mode_aes_128_cbc_iv_None_aad_None = r"""'2020-01-01\tuser0\t8C9598C3C8D8AC241DDF0D1B22020104\n2020-01-02\tuser1\tC5ECE31A240069D8F169B9F8CF687779\n2020-01-03\tuser2\t9FCFA4B05DD49D2B24BA61091F963CE3'"""
encrypt_input_example_mode_aes_192_cbc_iv_None_aad_None = r"""'2020-01-01\tuser0\t897F14C4E497962D986A7E7EA57AA043\n2020-01-02\tuser1\tED84AF2B3447113DA451E4577F649E36\n2020-01-03\tuser2\t4976F9D5AE195E61694A9ADCDD8A076F'"""
encrypt_input_example_mode_aes_256_cbc_iv_None_aad_None = r"""'2020-01-01\tuser0\tBABD6C071FDEE1C9A33877006FBB0BE6\n2020-01-02\tuser1\t7753E81D1DB9DC91FC8148E88B3E9526\n2020-01-03\tuser2\tD77D1A8DF82C2273BF0D19A14526531F'"""
encrypt_input_example_mode_aes_128_cbc_iv_16_aad_None = r"""'2020-01-01\tuser0\tFC93C1D5E5E3B054C1F3A5692AAC0A61\n2020-01-02\tuser1\tD6DBC76ABCB14B7C6D93F1A5FCA66B9C\n2020-01-03\tuser2\tD4F4158A650D01EB505CC72EFE455486'"""
encrypt_input_example_mode_aes_192_cbc_iv_16_aad_None = r"""'2020-01-01\tuser0\t0E3BAF7F4E0BFCFFAE2589B67F71E277\n2020-01-02\tuser1\t2581CCEE9ABE5770480901D65B3D9222\n2020-01-03\tuser2\tED9F3BD8DB12FDF9F2462FFA572361E7'"""
encrypt_input_example_mode_aes_256_cbc_iv_16_aad_None = r"""'2020-01-01\tuser0\t72AC7BA6F283EA94A3C33C4D3E51C7D3\n2020-01-02\tuser1\tDACBBE79062F1C721A01CEEE3E85524F\n2020-01-03\tuser2\tFF5A09D19E5EB2ADD94581308588E44A'"""
encrypt_input_example_mode_aes_128_cfb1_iv_None_aad_None = r"""'2020-01-01\tuser0\t750BE8662F57A095EC0E610C\n2020-01-02\tuser1\t750BE8662E444A6284C0FC72\n2020-01-03\tuser2\t750BE8662C000B61CDCF1C94'"""
encrypt_input_example_mode_aes_192_cfb1_iv_None_aad_None = r"""'2020-01-01\tuser0\t5DCC67A043EB776D8B7F5B70\n2020-01-02\tuser1\t5DCC67A042B46DFCC10EFD66\n2020-01-03\tuser2\t5DCC67A040243A8C1346D2DD'"""
encrypt_input_example_mode_aes_256_cfb1_iv_None_aad_None = r"""'2020-01-01\tuser0\tFAAC1A7D2CE844F8DEB4C44E\n2020-01-02\tuser1\tFAAC1A7D2DF85A43828C0FF8\n2020-01-03\tuser2\tFAAC1A7D2FC7582CCEFCF330'"""
encrypt_input_example_mode_aes_128_cfb1_iv_16_aad_None = r"""'2020-01-01\tuser0\t7670A865D13B1B65AD46F8ED\n2020-01-02\tuser1\t7670A865D046007A1E218286\n2020-01-03\tuser2\t7670A865D2E5B091492ECCFB'"""
encrypt_input_example_mode_aes_192_cfb1_iv_16_aad_None = r"""'2020-01-01\tuser0\t7F38C051539074C0A635C937\n2020-01-02\tuser1\t7F38C051520A30DFACBE9564\n2020-01-03\tuser2\t7F38C051500DA29FF0E7B799'"""
encrypt_input_example_mode_aes_256_cfb1_iv_16_aad_None = r"""'2020-01-01\tuser0\t0A216A58A5C0A33215E8E722\n2020-01-02\tuser1\t0A216A58A4E94067ABF030B6\n2020-01-03\tuser2\t0A216A58A6822CAB0318C632'"""
encrypt_input_example_mode_aes_128_cfb8_iv_None_aad_None = r"""'2020-01-01\tuser0\t650D96B9698D20DB12E2E437\n2020-01-02\tuser1\t650D96B968F00D16ABF2852E\n2020-01-03\tuser2\t650D96B96B8141F425E60D6B'"""
encrypt_input_example_mode_aes_192_cfb8_iv_None_aad_None = r"""'2020-01-01\tuser0\t72C4724B2F528724A12041C0\n2020-01-02\tuser1\t72C4724B2EF3C6A6FF9E09A9\n2020-01-03\tuser2\t72C4724B2D6EAB1D47709E15'"""
encrypt_input_example_mode_aes_256_cfb8_iv_None_aad_None = r"""'2020-01-01\tuser0\tC5FD6C94961765ED204F2BCA\n2020-01-02\tuser1\tC5FD6C9497AB1C1AF1DE671C\n2020-01-03\tuser2\tC5FD6C949491F4A3EA5069B3'"""
encrypt_input_example_mode_aes_128_cfb8_iv_16_aad_None = r"""'2020-01-01\tuser0\t471D217E9CA3593FFEC955C8\n2020-01-02\tuser1\t471D217E9D7F484D85F81F19\n2020-01-03\tuser2\t471D217E9EBBFD2EA9841008'"""
encrypt_input_example_mode_aes_192_cfb8_iv_16_aad_None = r"""'2020-01-01\tuser0\t1D98EFFAEB9907457BD3FCB2\n2020-01-02\tuser1\t1D98EFFAEA2D930825C6AE22\n2020-01-03\tuser2\t1D98EFFAE92C1D018438B98B'"""
encrypt_input_example_mode_aes_256_cfb8_iv_16_aad_None = r"""'2020-01-01\tuser0\t1C07B443BB7D7D60E9999C1D\n2020-01-02\tuser1\t1C07B443BA9674A3F68FF3FE\n2020-01-03\tuser2\t1C07B443B95F4B68161A616F'"""
encrypt_input_example_mode_aes_128_cfb128_iv_None_aad_None = r"""'2020-01-01\tuser0\t65ACA4C7C6338E0F7EB60812\n2020-01-02\tuser1\t65ACA4C7C7338E0F7EB60812\n2020-01-03\tuser2\t65ACA4C7C4338E0F7EB60812'"""
encrypt_input_example_mode_aes_192_cfb128_iv_None_aad_None = r"""'2020-01-01\tuser0\t72C47CEF0D63D2FB4FBC3CE4\n2020-01-02\tuser1\t72C47CEF0C63D2FB4FBC3CE4\n2020-01-03\tuser2\t72C47CEF0F63D2FB4FBC3CE4'"""
encrypt_input_example_mode_aes_256_cfb128_iv_None_aad_None = r"""'2020-01-01\tuser0\tC5FDAAECF7B42C68180AA151\n2020-01-02\tuser1\tC5FDAAECF6B42C68180AA151\n2020-01-03\tuser2\tC5FDAAECF5B42C68180AA151'"""
encrypt_input_example_mode_aes_128_cfb128_iv_16_aad_None = r"""'2020-01-01\tuser0\t47FBCCF6ED598C3D8A4B05C5\n2020-01-02\tuser1\t47FBCCF6EC598C3D8A4B05C5\n2020-01-03\tuser2\t47FBCCF6EF598C3D8A4B05C5'"""
encrypt_input_example_mode_aes_192_cfb128_iv_16_aad_None = r"""'2020-01-01\tuser0\t1DB482E0874F04D4E734607A\n2020-01-02\tuser1\t1DB482E0864F04D4E734607A\n2020-01-03\tuser2\t1DB482E0854F04D4E734607A'"""
encrypt_input_example_mode_aes_256_cfb128_iv_16_aad_None = r"""'2020-01-01\tuser0\t1C2BED650C8137ED139226D3\n2020-01-02\tuser1\t1C2BED650D8137ED139226D3\n2020-01-03\tuser2\t1C2BED650E8137ED139226D3'"""
encrypt_input_example_mode_aes_128_ofb_iv_None_aad_None = r"""'2020-01-01\tuser0\t65ACA4C7C6338E0F7EB60812\n2020-01-02\tuser1\t65ACA4C7C7338E0F7EB60812\n2020-01-03\tuser2\t65ACA4C7C4338E0F7EB60812'"""
encrypt_input_example_mode_aes_192_ofb_iv_None_aad_None = r"""'2020-01-01\tuser0\t72C47CEF0D63D2FB4FBC3CE4\n2020-01-02\tuser1\t72C47CEF0C63D2FB4FBC3CE4\n2020-01-03\tuser2\t72C47CEF0F63D2FB4FBC3CE4'"""
encrypt_input_example_mode_aes_256_ofb_iv_None_aad_None = r"""'2020-01-01\tuser0\tC5FDAAECF7B42C68180AA151\n2020-01-02\tuser1\tC5FDAAECF6B42C68180AA151\n2020-01-03\tuser2\tC5FDAAECF5B42C68180AA151'"""
encrypt_input_example_mode_aes_128_ofb_iv_16_aad_None = r"""'2020-01-01\tuser0\t47FBCCF6ED598C3D8A4B05C5\n2020-01-02\tuser1\t47FBCCF6EC598C3D8A4B05C5\n2020-01-03\tuser2\t47FBCCF6EF598C3D8A4B05C5'"""
encrypt_input_example_mode_aes_192_ofb_iv_16_aad_None = r"""'2020-01-01\tuser0\t1DB482E0874F04D4E734607A\n2020-01-02\tuser1\t1DB482E0864F04D4E734607A\n2020-01-03\tuser2\t1DB482E0854F04D4E734607A'"""
encrypt_input_example_mode_aes_256_ofb_iv_16_aad_None = r"""'2020-01-01\tuser0\t1C2BED650C8137ED139226D3\n2020-01-02\tuser1\t1C2BED650D8137ED139226D3\n2020-01-03\tuser2\t1C2BED650E8137ED139226D3'"""
encrypt_input_example_mode_aes_128_gcm_iv_12_aad_None = r"""'2020-01-01\tuser0\t98E5A430C4A01C4429B0F37A4B3CDBC2BDB491651A36D7F904E231E0\n2020-01-02\tuser1\t98E5A430C5A01C4429B0F37A6E108322C2863C1ABF9BC7098CD369DB\n2020-01-03\tuser2\t98E5A430C6A01C4429B0F37A01646A0243D1CB9A516CF61814808196'"""
encrypt_input_example_mode_aes_192_gcm_iv_12_aad_None = r"""'2020-01-01\tuser0\t3F89C3B657596C86202B59F4350807B364DA1E94682EAB679617575D\n2020-01-02\tuser1\t3F89C3B656596C86202B59F4FA03602ED37788B312FDE2AFDBB7F097\n2020-01-03\tuser2\t3F89C3B655596C86202B59F4691EC8880B8132DA9D8838F70D5618C8'"""
encrypt_input_example_mode_aes_256_gcm_iv_12_aad_None = r"""'2020-01-01\tuser0\t23B80948CCDB54DC6D0B62F215132A07B30BA6F15593B4F946726B11\n2020-01-02\tuser1\t23B80948CDDB54DC6D0B62F2A01C1BAE07B8D6B26F60116040CDDB55\n2020-01-03\tuser2\t23B80948CEDB54DC6D0B62F2BD0D4954DA6D46772074FFCB4B0D0B98'"""
encrypt_input_example_mode_aes_128_gcm_iv_12_aad_True = r"""'2020-01-01\tuser0\t98E5A430C4A01C4429B0F37AF9758E0EA4B44A50A7F964C8E51A913C\n2020-01-02\tuser1\t98E5A430C5A01C4429B0F37ADC59D6EEDB86E72F025474386D2BC907\n2020-01-03\tuser2\t98E5A430C6A01C4429B0F37AB32D3FCE5AD110AFECA34529F578214A'"""
encrypt_input_example_mode_aes_192_gcm_iv_12_aad_True = r"""'2020-01-01\tuser0\t3F89C3B657596C86202B59F4B6C662DFF6347EF3B46C170A2F80E946\n2020-01-02\tuser1\t3F89C3B656596C86202B59F479CD05424199E8D4CEBF5EC262204E8C\n2020-01-03\tuser2\t3F89C3B655596C86202B59F4EAD0ADE4996F52BD41CA849AB4C1A6D3'"""
encrypt_input_example_mode_aes_256_gcm_iv_12_aad_True = r"""'2020-01-01\tuser0\t23B80948CCDB54DC6D0B62F28787710BBF3F9A594C387B9F7CA2372B\n2020-01-02\tuser1\t23B80948CDDB54DC6D0B62F2328840A20B8CEA1A76CBDE067A1D876F\n2020-01-03\tuser2\t23B80948CEDB54DC6D0B62F22F991258D6597ADF39DF30AD71DD57A2'"""
encrypt_input_example_mode_aes_128_ctr_iv_None_aad_None = r"""'2020-01-01\tuser0\t65ACA4C7C6338E0F7EB60812\n2020-01-02\tuser1\t65ACA4C7C7338E0F7EB60812\n2020-01-03\tuser2\t65ACA4C7C4338E0F7EB60812'"""
encrypt_input_example_mode_aes_192_ctr_iv_None_aad_None = r"""'2020-01-01\tuser0\t72C47CEF0D63D2FB4FBC3CE4\n2020-01-02\tuser1\t72C47CEF0C63D2FB4FBC3CE4\n2020-01-03\tuser2\t72C47CEF0F63D2FB4FBC3CE4'"""
encrypt_input_example_mode_aes_256_ctr_iv_None_aad_None = r"""'2020-01-01\tuser0\tC5FDAAECF7B42C68180AA151\n2020-01-02\tuser1\tC5FDAAECF6B42C68180AA151\n2020-01-03\tuser2\tC5FDAAECF5B42C68180AA151'"""
encrypt_input_example_mode_aes_128_ctr_iv_16_aad_None = r"""'2020-01-01\tuser0\t47FBCCF6ED598C3D8A4B05C5\n2020-01-02\tuser1\t47FBCCF6EC598C3D8A4B05C5\n2020-01-03\tuser2\t47FBCCF6EF598C3D8A4B05C5'"""
encrypt_input_example_mode_aes_192_ctr_iv_16_aad_None = r"""'2020-01-01\tuser0\t1DB482E0874F04D4E734607A\n2020-01-02\tuser1\t1DB482E0864F04D4E734607A\n2020-01-03\tuser2\t1DB482E0854F04D4E734607A'"""
encrypt_input_example_mode_aes_256_ctr_iv_16_aad_None = r"""'2020-01-01\tuser0\t1C2BED650C8137ED139226D3\n2020-01-02\tuser1\t1C2BED650D8137ED139226D3\n2020-01-03\tuser2\t1C2BED650E8137ED139226D3'"""

View File

@ -0,0 +1,634 @@
# -*- coding: utf-8 -*-
import os
from importlib.machinery import SourceFileLoader
from testflows.core import *
from testflows.core.name import basename
from testflows.asserts.helpers import varname
from testflows.asserts import error
from aes_encryption.requirements.requirements import *
from aes_encryption.tests.common import *
@TestOutline
def decrypt(self, ciphertext=None, key=None, mode=None, iv=None, aad=None, exitcode=0, message=None, step=When, cast=None, endcast=None, compare=None, no_checks=False):
"""Execute `decrypt` function with the specified parameters.
"""
params = []
if mode is not None:
params.append(mode)
if ciphertext is not None:
params.append(ciphertext)
if key is not None:
params.append(key)
if iv is not None:
params.append(iv)
if aad is not None:
params.append(aad)
sql = f"decrypt(" + ", ".join(params) + ")"
if cast:
sql = f"{cast}({sql}){endcast or ''}"
if compare:
sql = f"{compare} = {sql}"
sql = f"SELECT {sql}"
return current().context.node.query(sql, step=step, exitcode=exitcode, message=message, no_checks=no_checks)
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Parameters_CipherText("1.0"),
)
def invalid_ciphertext(self):
"""Check that `decrypt` function does not crash when invalid
`ciphertext` is passed as the first parameter.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
invalid_ciphertexts = plaintexts
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} iv={iv_len} aad={aad_len}"""):
d_iv = None if not iv_len else f"'{iv[:iv_len]}'"
d_aad = None if not aad_len else f"'{aad}'"
for datatype, ciphertext in invalid_ciphertexts:
if datatype in ["NULL"]:
continue
with When(f"invalid ciphertext={ciphertext}"):
if "cfb" in mode or "ofb" in mode or "ctr" in mode:
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", mode=mode, iv=d_iv, aad=d_aad, cast="hex")
else:
with When("I execute decrypt function"):
r = decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", mode=mode, iv=d_iv, aad=d_aad, no_checks=True, step=By)
with Then("exitcode is not zero"):
assert r.exitcode in [198, 36]
with And("exception is present in the output"):
assert "DB::Exception:" in r.output
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_InvalidParameters("1.0")
)
def invalid_parameters(self):
"""Check that `decrypt` function returns an error when
we call it with invalid parameters.
"""
ciphertext = "unhex('AA1826B5F66A903C888D5DCDA9FB63D1D9CCA10EC55F59D6C00D37')"
with Example("no parameters"):
decrypt(exitcode=42, message="DB::Exception: Incorrect number of arguments for function decrypt provided 0, expected 3 to 5")
with Example("missing key and mode"):
decrypt(ciphertext=ciphertext, exitcode=42,
message="DB::Exception: Incorrect number of arguments for function decrypt provided 1")
with Example("missing mode"):
decrypt(ciphertext=ciphertext, key="'123'", exitcode=42,
message="DB::Exception: Incorrect number of arguments for function decrypt provided 2")
with Example("bad key type - UInt8"):
decrypt(ciphertext=ciphertext, key="123", mode="'aes-128-ecb'", exitcode=43,
message="DB::Exception: Received from localhost:9000. DB::Exception: Illegal type of argument #3")
with Example("bad mode type - forgot quotes"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="aes-128-ecb", exitcode=47,
message="DB::Exception: Missing columns: 'ecb' 'aes' while processing query")
with Example("bad mode type - UInt8"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="128", exitcode=43,
message="DB::Exception: Illegal type of argument #1 'mode'")
with Example("bad iv type - UInt8"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-cbc'", iv='128', exitcode=43,
message="DB::Exception: Illegal type of argument")
with Example("bad aad type - UInt8"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-gcm'", iv="'012345678912'", aad="123", exitcode=43,
message="DB::Exception: Illegal type of argument")
with Example("iv not valid for mode", requirements=[RQ_SRS008_AES_Decrypt_Function_InitializationVector_NotValidForMode("1.0")]):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-ecb'", iv="'012345678912'", exitcode=36,
message="DB::Exception: aes-128-ecb does not support IV")
with Example("iv not valid for mode - size 0", requirements=[RQ_SRS008_AES_Decrypt_Function_InitializationVector_NotValidForMode("1.0")]):
decrypt(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key="'0123456789123456'", mode="'aes-128-ecb'", iv="''", exitcode=36,
message="DB::Exception: aes-128-ecb does not support IV")
with Example("aad not valid for mode", requirements=[RQ_SRS008_AES_Decrypt_Function_AdditionalAuthenticationData_NotValidForMode("1.0")]):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-cbc'", iv="'0123456789123456'", aad="'aad'", exitcode=36,
message="DB::Exception: AAD can be only set for GCM-mode")
with Example("invalid mode value", requirements=[RQ_SRS008_AES_Decrypt_Function_Parameters_Mode_Value_Invalid("1.0")]):
with When("typo in the block algorithm"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-eeb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128-eeb")
with When("typo in the key size"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-127-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-127-ecb")
with When("typo in the aes prefix"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aee-128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aee-128-ecb")
with When("missing last dash"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128ecb")
with When("missing first dash"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes128-ecb")
with When("all capitals"):
decrypt(ciphertext=ciphertext, key="'0123456789123456'", mode="'AES-128-ECB'", exitcode=36,
message="DB::Exception: Invalid mode: AES-128-ECB")
@TestOutline(Scenario)
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Key_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Decrypt_Function_InitializationVector_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Decrypt_Function_AdditionalAuthenticationData_NotValidForMode("1.0")
)
@Examples("mode key_len iv_len aad", [
# ECB
("'aes-128-ecb'", 16, None, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ecb'", 24, None, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ecb'", 32, None, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_ECB_KeyAndInitializationVector_Length("1.0"))),
# CBC
("'aes-128-cbc'", 16, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cbc'", 24, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cbc'", 32, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_CBC_KeyAndInitializationVector_Length("1.0"))),
# CFB1
("'aes-128-cfb1'", 16, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb1'", 24, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb1'", 32, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_CFB1_KeyAndInitializationVector_Length("1.0"))),
# CFB8
("'aes-128-cfb8'", 16, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb8'", 24, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb8'", 32, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_CFB8_KeyAndInitializationVector_Length("1.0"))),
# CFB128
("'aes-128-cfb128'", 16, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb128'", 24, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb128'", 32, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_CFB128_KeyAndInitializationVector_Length("1.0"))),
# OFB
("'aes-128-ofb'", 16, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ofb'", 24, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ofb'", 32, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_OFB_KeyAndInitializationVector_Length("1.0"))),
# CTR
("'aes-128-ctr'", 16, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_CTR_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ctr'", 24, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_CTR_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ctr'", 32, 16, None,
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_CTR_KeyAndInitializationVector_Length("1.0"))),
], "%-16s %-10s %-10s %-10s")
def invalid_key_or_iv_length_for_mode_non_gcm(self, mode, key_len, iv_len, aad):
"""Check that an error is returned when key or iv length does not match
the expected value for the mode.
"""
ciphertext = "unhex('AA1826B5F66A903C888D5DCDA9FB63D1D9CCA10EC55F59D6C00D37')"
key = "0123456789" * 4
iv = "0123456789" * 4
with When("key is too short"):
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
with When("key is too long"):
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len+1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
if iv_len is not None:
with When("iv is too short"):
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
with When("iv is too long"):
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len+1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
if aad is None:
with When("aad is specified but not needed"):
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len+1] if iv_len is not None else ''}'", aad="'AAD'", mode=mode, exitcode=36, message="DB::Exception: AAD can be only set for GCM-mode")
else:
with When("iv is specified but not needed"):
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv}'", mode=mode, exitcode=36, message="DB::Exception: {} does not support IV".format(mode.strip("'")))
@TestOutline(Scenario)
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Key_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Decrypt_Function_InitializationVector_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Decrypt_Function_AdditionalAuthenticationData_NotValidForMode("1.0")
)
@Examples("mode key_len iv_len aad", [
# GCM
("'aes-128-gcm'", 16, 8, "'hello there aad'",
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_GCM_KeyAndInitializationVector_Length("1.0"))),
("'aes-128-gcm'", 16, None, "'hello there aad'",
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_128_GCM_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-gcm'", 24, 8, "''",
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_GCM_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-gcm'", 24, None, "''",
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_192_GCM_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-gcm'", 32, 8, "'a'",
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_GCM_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-gcm'", 32, None, "'a'",
Requirements(RQ_SRS008_AES_Decrypt_Function_AES_256_GCM_KeyAndInitializationVector_Length("1.0"))),
], "%-16s %-10s %-10s %-10s")
def invalid_key_or_iv_length_for_gcm(self, mode, key_len, iv_len, aad):
"""Check that an error is returned when key or iv length does not match
the expected value for the GCM mode.
"""
ciphertext = "'hello there'"
plaintext = "hello there"
key = "0123456789" * 4
iv = "0123456789" * 4
with When("key is too short"):
ciphertext = "unhex('AA1826B5F66A903C888D5DCDA9FB63D1D9CCA10EC55F59D6C00D37')"
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len-1]}'", iv=f"'{iv[:iv_len]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
with When("key is too long"):
ciphertext = "unhex('24AEBFEA049D6F4CF85AAB8CADEDF39CCCAA1C3C2AFF99E194789D')"
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len+1]}'", iv=f"'{iv[:iv_len]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
if iv_len is not None:
with When(f"iv is too short"):
ciphertext = "unhex('24AEBFEA049D6F4CF85AAB8CADEDF39CCCAA1C3C2AFF99E194789D')"
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len-1]}'", mode=mode, exitcode=198, message="DB::Exception:")
else:
with When("iv is not specified"):
ciphertext = "unhex('1CD4EC93A4B0C687926E8F8C2AA3B4CE1943D006DAE3A774CB1AE5')"
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", mode=mode, exitcode=198, message="DB::Exception: Failed to set custom IV length to 0")
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Parameters_AdditionalAuthenticatedData("1.0"),
RQ_SRS008_AES_Decrypt_Function_AdditionalAuthenticationData_Length("1.0")
)
def aad_parameter_types_and_length(self):
"""Check that `decrypt` function accepts `aad` parameter as the fifth argument
of either `String` or `FixedString` types and that the length is not limited.
"""
plaintext = "hello there"
iv = "'012345678912'"
mode = "'aes-128-gcm'"
key = "'0123456789123456'"
with When("aad is specified using String type"):
ciphertext = "unhex('19A1183335B374C626B24208AAEC97F148732CE05621AC87B21526')"
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, aad="'aad'", message=plaintext)
with When("aad is specified using String with UTF8 characters"):
ciphertext = "unhex('19A1183335B374C626B242C68D9618A8C2664D7B6A3FE978104B39')"
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, aad="'Gãńdåłf_Thê_Gręât'", message=plaintext)
with When("aad is specified using FixedString type"):
ciphertext = "unhex('19A1183335B374C626B24208AAEC97F148732CE05621AC87B21526')"
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, aad="toFixedString('aad', 3)", message=plaintext)
with When("aad is specified using FixedString with UTF8 characters"):
ciphertext = "unhex('19A1183335B374C626B242C68D9618A8C2664D7B6A3FE978104B39')"
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, aad="toFixedString('Gãńdåłf_Thê_Gręât', 24)", message=plaintext)
with When("aad is 0 bytes"):
ciphertext = "unhex('19A1183335B374C626B242DF92BB3F57F5D82BEDF41FD5D49F8BC9')"
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, aad="''", message=plaintext)
with When("aad is 1 byte"):
ciphertext = "unhex('19A1183335B374C626B242D1BCFC63B09CFE9EAD20285044A01035')"
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, aad="'1'", message=plaintext)
with When("aad is 256 bytes"):
ciphertext = "unhex('19A1183335B374C626B242355AD3DD2C5D7E36AEECBB847BF9E8A7')"
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, aad=f"'{'1' * 256}'", message=plaintext)
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Parameters_InitializationVector("1.0")
)
def iv_parameter_types(self):
"""Check that `decrypt` function accepts `iv` parameter as the fourth argument
of either `String` or `FixedString` types.
"""
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("iv is specified using String type"):
decrypt(ciphertext="unhex('F024F9372FA0D8B974894D29FFB8A7F7')", key=key, mode=mode, iv=iv, message="hello there")
with When("iv is specified using String with UTF8 characters"):
decrypt(ciphertext="unhex('7A4EC0FF3796F46BED281F4778ACE1DC')", key=key, mode=mode, iv="'Gãńdåłf_Thê'", message="hello there")
with When("iv is specified using FixedString type"):
decrypt(ciphertext="unhex('F024F9372FA0D8B974894D29FFB8A7F7')", key=key, mode=mode, iv=f"toFixedString({iv}, 16)", message="hello there")
with When("iv is specified using FixedString with UTF8 characters"):
decrypt(ciphertext="unhex('7A4EC0FF3796F46BED281F4778ACE1DC')", key=key, mode=mode, iv=f"toFixedString('Gãńdåłf_Thê', 16)", message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Parameters_Key("1.0")
)
def key_parameter_types(self):
"""Check that `decrypt` function accepts `key` parameter as the second argument
of either `String` or `FixedString` types.
"""
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("key is specified using String type"):
decrypt(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=key, mode=mode, message="hello there")
with When("key is specified using String with UTF8 characters"):
decrypt(ciphertext="unhex('180086AA42AD57B71C706EEC372D0C3D')", key="'Gãńdåłf_Thê'", mode=mode, message="hello there")
with When("key is specified using FixedString type"):
decrypt(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=f"toFixedString({key}, 16)", mode=mode, message="hello there")
with When("key is specified using FixedString with UTF8 characters"):
decrypt(ciphertext="unhex('180086AA42AD57B71C706EEC372D0C3D')", key=f"toFixedString('Gãńdåłf_Thê', 16)", mode=mode, message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Parameters_Mode("1.0"),
)
def mode_parameter_types(self):
"""Check that `decrypt` function accepts `mode` parameter as the third argument
of either `String` or `FixedString` types.
"""
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("mode is specified using String type"):
decrypt(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=key, mode=mode, message="hello there")
with When("mode is specified using FixedString type"):
decrypt(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=key, mode=f"toFixedString({mode}, 12)", message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Parameters_ReturnValue("1.0")
)
def return_value(self):
"""Check that `decrypt` functions returns String data type.
"""
ciphertext = "unhex('F024F9372FA0D8B974894D29FFB8A7F7')"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("I get type of the return value"):
sql = "SELECT toTypeName(decrypt(" + mode + "," + ciphertext + "," + key + "," + iv + "))"
r = self.context.node.query(sql)
with Then("type should be String"):
assert r.output.strip() == "String", error()
with When("I get the return value"):
decrypt(ciphertext=ciphertext, key=key, mode=mode, iv=iv, message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Syntax("1.0"),
)
def syntax(self):
"""Check that `decrypt` function supports syntax
```sql
decrypt(ciphertext, key, mode, [iv, aad])
```
"""
ciphertext = "19A1183335B374C626B242A6F6E8712E2B64DCDC6A468B2F654614"
sql = f"SELECT decrypt('aes-128-gcm', unhex('{ciphertext}'), '0123456789123456', '012345678912', 'AAD')"
self.context.node.query(sql, step=When, message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_Decrypt_Function_Parameters_CipherText("1.0"),
RQ_SRS008_AES_Decrypt_Function_Parameters_Mode("1.0"),
RQ_SRS008_AES_Decrypt_Function_Parameters_Mode_ValuesFormat("1.0"),
)
def decryption(self):
"""Check that `decrypt` functions accepts `ciphertext` as the second parameter
and `mode` as the first parameter and we can convert the decrypted value into the original
value with the original data type.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt.py.encrypt.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
for datatype, plaintext in plaintexts:
requirement = globals().get(f"""RQ_SRS008_AES_Decrypt_Function_Parameters_Mode_Value_{mode.strip("'").replace("-","_").upper()}""")("1.0")
with Example(f"""mode={mode.strip("'")} datatype={datatype.strip("'")} iv={iv_len} aad={aad_len}""",
requirements=[requirement]) as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
cast = None
endcast = None
ciphertext = f"unhex({ciphertext})"
compare = plaintext
if datatype == "IPv4":
cast = "toIPv4(IPv4NumToString(reinterpretAsUInt32"
endcast = "))"
elif datatype in ["DateTime64", "UUID", "IPv6", "LowCardinality", "Enum8", "Enum16", "Decimal32", "Decimal64", "Decimal128", "Array"]:
xfail(reason="no conversion")
elif datatype == "NULL":
ciphertext = "NULL"
cast = "isNull"
compare = None
elif datatype in ["Float32", "Float64", "Date", "DateTime"] or "Int" in datatype:
cast = f"reinterpretAs{datatype}"
decrypt(ciphertext=ciphertext, key=f"'{key[:key_len]}'", mode=mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"),
aad=(None if not aad_len else f"'{aad}'"),
cast=cast, endcast=endcast, compare=compare, message="1")
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_Mismatched_Key("1.0")
)
def mismatched_key(self):
"""Check that `decrypt` function returns garbage or an error when key parameter does not match.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
datatype = "String"
plaintext = "'1'"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt.py.encrypt.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} datatype={datatype.strip("'")} iv={iv_len} aad={aad_len}""") as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
with When("I decrypt using a mismatched key"):
r = decrypt(ciphertext=f"unhex({ciphertext})", key=f"'a{key[:key_len-1]}'", mode=mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"),
aad=(None if not aad_len else f"'{aad}'"), no_checks=True, cast="hex")
with Then("exitcode shoud be 0 or 198"):
assert r.exitcode in [0, 198], error()
with And("output should be garbage or an error"):
output = r.output.strip()
assert "Exception: Failed to decrypt" in output or output != "31", error()
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_Mismatched_IV("1.0")
)
def mismatched_iv(self):
"""Check that `decrypt` function returns garbage or an error when iv parameter does not match.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
datatype = "String"
plaintext = "'1'"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt.py.encrypt.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
if not iv_len:
continue
with Example(f"""mode={mode.strip("'")} datatype={datatype.strip("'")} iv={iv_len} aad={aad_len}""") as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
with When("I decrypt using a mismatched iv"):
r = decrypt(ciphertext=f"unhex({ciphertext})", key=f"'{key[:key_len]}'", mode=mode,
iv=f"'a{iv[:iv_len-1]}'",
aad=(None if not aad_len else f"'{aad}'"), no_checks=True, cast="hex")
with Then("exitcode shoud be 0 or 198"):
assert r.exitcode in [0, 198], error()
with And("output should be garbage or an error"):
output = r.output.strip()
assert "Exception: Failed to decrypt" in output or output != "31", error()
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_Mismatched_AAD("1.0")
)
def mismatched_aad(self):
"""Check that `decrypt` function returns garbage or an error when aad parameter does not match.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
datatype = "String"
plaintext = "'1'"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt.py.encrypt.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
if not aad_len:
continue
with Example(f"""mode={mode.strip("'")} datatype={datatype.strip("'")} iv={iv_len} aad={aad_len}""") as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
with When("I decrypt using a mismatched aad"):
r = decrypt(ciphertext=f"unhex({ciphertext})", key=f"'{key[:key_len]}'", mode=mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"),
aad=(None if not aad_len else f"'a{aad}'"), no_checks=True, cast="hex")
with Then("exitcode shoud be 0 or 198"):
assert r.exitcode in [0, 198], error()
with And("output should be garbage or an error"):
output = r.output.strip()
assert "Exception: Failed to decrypt" in output or output != "31", error()
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_Mismatched_Mode("1.0")
)
def mismatched_mode(self):
"""Check that `decrypt` function returns garbage or an error when mode parameter does not match.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
plaintext = hex('Gãńdåłf_Thê_Gręât'.encode("utf-8"))
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt.py.encrypt.snapshot")).load_module()
for mode, key_len, iv_len, aad_len in modes:
with Example(f"""mode={mode.strip("'")} datatype=utf8string iv={iv_len} aad={aad_len}""") as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
for mismatched_mode, _, _, _ in modes:
if mismatched_mode == mode:
continue
with When(f"I decrypt using mismatched mode {mismatched_mode}"):
r = decrypt(ciphertext=f"unhex({ciphertext})", key=f"'{key[:key_len]}'", mode=mismatched_mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"),
aad=(None if not aad_len else f"'{aad}'"), no_checks=True, cast="hex")
with Then("exitcode shoud be 0 or 36 or 198"):
assert r.exitcode in [0, 36, 198], error()
with And("output should be garbage or an error"):
output = r.output.strip()
condition = "Exception: Failed to decrypt" in output \
or 'Exception: Invalid key size' in output \
or output != plaintext
assert condition, error()
@TestFeature
@Name("decrypt")
@Requirements(
RQ_SRS008_AES_Decrypt_Function("1.0")
)
def feature(self, node="clickhouse1"):
"""Check the behavior of the `decrypt` function.
"""
self.context.node = self.context.cluster.node(node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,521 @@
# -*- coding: utf-8 -*-
import os
from importlib.machinery import SourceFileLoader
from testflows.core import *
from testflows.core.name import basename
from testflows.asserts.helpers import varname
from testflows.asserts import error
from aes_encryption.requirements.requirements import *
from aes_encryption.tests.common import *
@TestOutline
def aes_decrypt_mysql(self, ciphertext=None, key=None, mode=None, iv=None, aad=None, exitcode=0, message=None,
step=When, cast=None, endcast=None, compare=None, no_checks=False):
"""Execute `aes_decrypt_mysql` function with the specified parameters.
"""
params = []
if mode is not None:
params.append(mode)
if ciphertext is not None:
params.append(ciphertext)
if key is not None:
params.append(key)
if iv is not None:
params.append(iv)
if aad is not None:
params.append(aad)
sql = f"aes_decrypt_mysql(" + ", ".join(params) + ")"
if cast:
sql = f"{cast}({sql}){endcast or ''}"
if compare:
sql = f"{compare} = {sql}"
sql = f"SELECT {sql}"
return current().context.node.query(sql, step=step, exitcode=exitcode, message=message, no_checks=no_checks)
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_CipherText("1.0"),
)
def invalid_ciphertext(self):
"""Check that `aes_decrypt_mysql` function does not crash when invalid
`ciphertext` is passed as the first parameter.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
invalid_ciphertexts = plaintexts
for mode, key_len, iv_len in mysql_modes:
with Example(f"""mode={mode.strip("'")} iv={iv_len}"""):
d_iv = None if not iv_len else f"'{iv[:iv_len]}'"
for datatype, ciphertext in invalid_ciphertexts:
if datatype in ["NULL"]:
continue
with When(f"invalid ciphertext={ciphertext}"):
if "cfb" in mode or "ofb" in mode or "ctr" in mode:
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", mode=mode, iv=d_iv, cast="hex")
else:
with When("I execute aes_decrypt_mysql function"):
r = aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", mode=mode, iv=d_iv, no_checks=True, step=By)
with Then("exitcode is not zero"):
assert r.exitcode in [198, 36]
with And("exception is present in the output"):
assert "DB::Exception:" in r.output
@TestOutline(Scenario)
@Examples("mode", [
("'aes-128-gcm'", Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_AES_128_GCM_Error("1.0"))),
("'aes-192-gcm'", Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_AES_192_GCM_Error("1.0"))),
("'aes-256-gcm'", Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_AES_256_GCM_Error("1.0"))),
("'aes-128-ctr'", Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_AES_128_CTR_Error("1.0"))),
("'aes-192-ctr'", Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_AES_192_CTR_Error("1.0"))),
("'aes-256-ctr'", Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_AES_256_CTR_Error("1.0"))),
])
def unsupported_modes(self, mode):
"""Check that `aes_decrypt_mysql` function returns an error when unsupported modes are specified.
"""
ciphertext = "unhex('AA1826B5F66A903C888D5DCDA9FB63D1D9CCA10EC55F59D6C00D37')"
aes_decrypt_mysql(ciphertext=ciphertext, mode=mode, key=f"'{'1'* 32}'", exitcode=36, message="DB::Exception: Unsupported cipher mode")
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_InvalidParameters("1.0")
)
def invalid_parameters(self):
"""Check that `aes_decrypt_mysql` function returns an error when
we call it with invalid parameters.
"""
ciphertext = "unhex('AA1826B5F66A903C888D5DCDA9FB63D1D9CCA10EC55F59D6C00D37')"
with Example("no parameters"):
aes_decrypt_mysql(exitcode=42, message="DB::Exception: Incorrect number of arguments for function aes_decrypt_mysql provided 0, expected 3 to 4")
with Example("missing key and mode"):
aes_decrypt_mysql(ciphertext=ciphertext, exitcode=42,
message="DB::Exception: Incorrect number of arguments for function aes_decrypt_mysql provided 1")
with Example("missing mode"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'123'", exitcode=42,
message="DB::Exception: Incorrect number of arguments for function aes_decrypt_mysql provided 2")
with Example("bad key type - UInt8"):
aes_decrypt_mysql(ciphertext=ciphertext, key="123", mode="'aes-128-ecb'", exitcode=43,
message="DB::Exception: Received from localhost:9000. DB::Exception: Illegal type of argument #3")
with Example("bad mode type - forgot quotes"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="aes-128-ecb", exitcode=47,
message="DB::Exception: Missing columns: 'ecb' 'aes' while processing query")
with Example("bad mode type - UInt8"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="128", exitcode=43,
message="DB::Exception: Illegal type of argument #1 'mode'")
with Example("bad iv type - UInt8"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-cbc'", iv='128', exitcode=43,
message="DB::Exception: Illegal type of argument")
with Example("iv not valid for mode", requirements=[RQ_SRS008_AES_MySQL_Decrypt_Function_InitializationVector_NotValidForMode("1.0")]):
aes_decrypt_mysql(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key="'0123456789123456'", mode="'aes-128-ecb'", iv="'012345678912'", exitcode=0,
message=None)
with Example("iv not valid for mode - size 0", requirements=[RQ_SRS008_AES_MySQL_Decrypt_Function_InitializationVector_NotValidForMode("1.0")]):
aes_decrypt_mysql(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key="'0123456789123456'", mode="'aes-128-ecb'", iv="''", exitcode=0,
message=None)
with Example("aad passed by mistake"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-cbc'", iv="'0123456789123456'", aad="'aad'", exitcode=42,
message="DB::Exception: Incorrect number of arguments for function aes_decrypt_mysql provided 5")
with Example("aad passed by mistake type - UInt8"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-gcm'", iv="'012345678912'", aad="123", exitcode=42,
message="DB::Exception: Incorrect number of arguments for function aes_decrypt_mysql provided 5")
with Example("invalid mode value", requirements=[RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_Invalid("1.0")]):
with When("typo in the block algorithm"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128-eeb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128-eeb")
with When("typo in the key size"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-127-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-127-ecb")
with When("typo in the aes prefix"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aee-128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aee-128-ecb")
with When("missing last dash"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes-128ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128ecb")
with When("missing first dash"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'aes128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes128-ecb")
with When("all capitals"):
aes_decrypt_mysql(ciphertext=ciphertext, key="'0123456789123456'", mode="'AES-128-ECB'", exitcode=36,
message="DB::Exception: Invalid mode: AES-128-ECB")
@TestOutline(Scenario)
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Key_Length_TooShortError("1.0"),
RQ_SRS008_AES_MySQL_Decrypt_Function_Key_Length_TooLong("1.0"),
RQ_SRS008_AES_MySQL_Decrypt_Function_InitializationVector_Length_TooShortError("1.0"),
RQ_SRS008_AES_MySQL_Decrypt_Function_InitializationVector_Length_TooLong("1.0"),
RQ_SRS008_AES_MySQL_Decrypt_Function_InitializationVector_NotValidForMode("1.0")
)
@Examples("mode key_len iv_len", [
# ECB
("'aes-128-ecb'", 16, None,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_128_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ecb'", 24, None,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_192_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ecb'", 32, None,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_256_ECB_KeyAndInitializationVector_Length("1.0"))),
# CBC
("'aes-128-cbc'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_128_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cbc'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_192_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cbc'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_256_CBC_KeyAndInitializationVector_Length("1.0"))),
# CFB1
("'aes-128-cfb1'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_128_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb1'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_192_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb1'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_256_CFB1_KeyAndInitializationVector_Length("1.0"))),
# CFB8
("'aes-128-cfb8'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_128_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb8'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_192_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb8'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_256_CFB8_KeyAndInitializationVector_Length("1.0"))),
# CFB128
("'aes-128-cfb128'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_128_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb128'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_192_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb128'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_256_CFB128_KeyAndInitializationVector_Length("1.0"))),
# OFB
("'aes-128-ofb'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_128_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ofb'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_192_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ofb'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Decrypt_Function_AES_256_OFB_KeyAndInitializationVector_Length("1.0"))),
], "%-16s %-10s %-10s")
def key_or_iv_length_for_mode(self, mode, key_len, iv_len):
"""Check that key or iv length for mode.
"""
ciphertext = "unhex('31F4C847CAB873AB34584368E3E85E3A')"
if mode == "'aes-128-ecb'":
ciphertext = "unhex('31F4C847CAB873AB34584368E3E85E3B')"
elif mode == "'aes-192-ecb'":
ciphertext = "unhex('073868ECDECA94133A61A0FFA282E877')"
elif mode == "'aes-256-ecb'":
ciphertext = "unhex('1729E5354D6EC44D89900ABDB09DC297')"
key = "0123456789" * 4
iv = "0123456789" * 4
with When("key is too short"):
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
with When("key is too long"):
if "ecb" in mode or "cbc" in mode:
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len+1]}'", mode=mode, exitcode=198, message="DB::Exception: Failed to decrypt")
else:
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len+1]}'", mode=mode, cast="hex")
if iv_len is not None:
with When("iv is too short"):
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
with When("iv is too long"):
if "ecb" in mode or "cbc" in mode:
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len+1]}'", mode=mode, exitcode=198, message="DB::Exception: Failed to decrypt")
else:
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len+1]}'", mode=mode, cast="hex")
else:
with When("iv is specified but not needed"):
if "ecb" in mode or "cbc" in mode:
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv}'", mode=mode, exitcode=198, message="DB::Exception: Failed to decrypt")
else:
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", iv=f"'{iv}'", mode=mode)
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_InitializationVector("1.0")
)
def iv_parameter_types(self):
"""Check that `aes_decrypt_mysql` function accepts `iv` parameter as the fourth argument
of either `String` or `FixedString` types.
"""
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("iv is specified using String type"):
aes_decrypt_mysql(ciphertext="unhex('F024F9372FA0D8B974894D29FFB8A7F7')", key=key, mode=mode, iv=iv, message="hello there")
with When("iv is specified using String with UTF8 characters"):
aes_decrypt_mysql(ciphertext="unhex('7A4EC0FF3796F46BED281F4778ACE1DC')", key=key, mode=mode, iv="'Gãńdåłf_Thê'", message="hello there")
with When("iv is specified using FixedString type"):
aes_decrypt_mysql(ciphertext="unhex('F024F9372FA0D8B974894D29FFB8A7F7')", key=key, mode=mode, iv=f"toFixedString({iv}, 16)", message="hello there")
with When("iv is specified using FixedString with UTF8 characters"):
aes_decrypt_mysql(ciphertext="unhex('7A4EC0FF3796F46BED281F4778ACE1DC')", key=key, mode=mode, iv=f"toFixedString('Gãńdåłf_Thê', 16)", message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Key("1.0")
)
def key_parameter_types(self):
"""Check that `aes_decrypt` function accepts `key` parameter as the second argument
of either `String` or `FixedString` types.
"""
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("key is specified using String type"):
aes_decrypt_mysql(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=key, mode=mode, message="hello there")
with When("key is specified using String with UTF8 characters"):
aes_decrypt_mysql(ciphertext="unhex('180086AA42AD57B71C706EEC372D0C3D')", key="'Gãńdåłf_Thê'", mode=mode, message="hello there")
with When("key is specified using FixedString type"):
aes_decrypt_mysql(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=f"toFixedString({key}, 16)", mode=mode, message="hello there")
with When("key is specified using FixedString with UTF8 characters"):
aes_decrypt_mysql(ciphertext="unhex('180086AA42AD57B71C706EEC372D0C3D')", key=f"toFixedString('Gãńdåłf_Thê', 16)", mode=mode, message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode("1.0"),
)
def mode_parameter_types(self):
"""Check that `aes_decrypt_mysql` function accepts `mode` parameter as the third argument
of either `String` or `FixedString` types.
"""
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("mode is specified using String type"):
aes_decrypt_mysql(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=key, mode=mode, message="hello there")
with When("mode is specified using FixedString type"):
aes_decrypt_mysql(ciphertext="unhex('49C9ADB81BA9B58C485E7ADB90E70576')", key=key, mode=f"toFixedString({mode}, 12)", message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_ReturnValue("1.0")
)
def return_value(self):
"""Check that `aes_decrypt_mysql` functions returns String data type.
"""
ciphertext = "unhex('F024F9372FA0D8B974894D29FFB8A7F7')"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("I get type of the return value"):
sql = "SELECT toTypeName(aes_decrypt_mysql(" + mode + "," + ciphertext + "," + key + "," + iv + "))"
r = self.context.node.query(sql)
with Then("type should be String"):
assert r.output.strip() == "String", error()
with When("I get the return value"):
aes_decrypt_mysql(ciphertext=ciphertext, key=key, mode=mode, iv=iv, message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Syntax("1.0"),
)
def syntax(self):
"""Check that `aes_decrypt_mysql` function supports syntax
```sql
aes_decrypt_mysql(ciphertext, key, mode, [iv])
```
"""
ciphertext = "70FE78410D6EE237C2DE4A"
sql = f"SELECT aes_decrypt_mysql('aes-128-ofb', unhex('{ciphertext}'), '0123456789123456', '0123456789123456')"
self.context.node.query(sql, step=When, message="hello there")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_CipherText("1.0"),
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode("1.0"),
RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_ValuesFormat("1.0"),
)
def decryption(self):
"""Check that `aes_decrypt_mysql` functions accepts `mode` as the first parameter
and `ciphertext` as the second parameter and we can convert the decrypted value into the original
value with the original data type.
"""
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt_mysql.py.encrypt_mysql.snapshot")).load_module()
for mode, key_len, iv_len in mysql_modes:
for datatype, plaintext in plaintexts:
requirement = globals().get(f"""RQ_SRS008_AES_MySQL_Decrypt_Function_Parameters_Mode_Value_{mode.strip("'").replace("-","_").upper()}""")("1.0")
with Example(f"""mode={mode.strip("'")} datatype={datatype.strip("'")} key={key_len} iv={iv_len}""",
requirements=[requirement]) as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
cast = None
endcast = None
ciphertext = f"unhex({ciphertext})"
compare = plaintext
if datatype == "IPv4":
cast = "toIPv4(IPv4NumToString(reinterpretAsUInt32"
endcast = "))"
elif datatype in ["DateTime64", "UUID", "IPv6", "LowCardinality", "Enum8", "Enum16", "Decimal32", "Decimal64", "Decimal128", "Array"]:
xfail(reason="no conversion")
elif datatype == "NULL":
ciphertext = "NULL"
cast = "isNull"
compare = None
elif datatype in ["Float32", "Float64", "Date", "DateTime"] or "Int" in datatype:
cast = f"reinterpretAs{datatype}"
aes_decrypt_mysql(ciphertext=ciphertext, key=f"'{key[:key_len]}'", mode=mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"),
cast=cast, endcast=endcast, compare=compare, message="1")
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_Mismatched_Key("1.0")
)
def mismatched_key(self):
"""Check that `aes_decrypt_mysql` function returns garbage or an error when key parameter does not match.
"""
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt_mysql.py.encrypt_mysql.snapshot")).load_module()
for mode, key_len, iv_len in mysql_modes:
with Example(f"""mode={mode.strip("'")} datatype=String key={key_len} iv={iv_len}""") as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
with When("I decrypt using a mismatched key"):
r = aes_decrypt_mysql(ciphertext=f"unhex({ciphertext})", key=f"'a{key[:key_len-1]}'", mode=mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"),
cast="hex", no_checks=True)
with Then("exitcode shoud be 0 or 198"):
assert r.exitcode in [0, 198], error()
with And("output should be garbage or an error"):
output = r.output.strip()
assert "Exception: Failed to decrypt" in output or output != "31", error()
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_Mismatched_IV("1.0")
)
def mismatched_iv(self):
"""Check that `aes_decrypt_mysql` function returns garbage or an error when iv parameter does not match.
"""
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt_mysql.py.encrypt_mysql.snapshot")).load_module()
for mode, key_len, iv_len in mysql_modes:
if not iv_len:
continue
with Example(f"""mode={mode.strip("'")} datatype=String key={key_len} iv={iv_len}""") as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
with When("I decrypt using a mismatched key"):
r = aes_decrypt_mysql(ciphertext=f"unhex({ciphertext})", key=f"'{key[:key_len]}'", mode=mode,
iv=f"'a{iv[:iv_len-1]}'",
cast="hex", no_checks=True)
with Then("exitcode shoud be 0 or 198"):
assert r.exitcode in [0, 198], error()
with And("output should be garbage or an error"):
output = r.output.strip()
assert "Exception: Failed to decrypt" in output or output != "31", error()
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_Mismatched_Mode("1.0")
)
def mismatched_mode(self):
"""Check that `aes_decrypt_mysql` function returns garbage or an error when mode parameter does not match.
"""
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
plaintext = hex('Gãńdåłf_Thê_Gręât'.encode("utf-8"))
with Given("I load encrypt snapshots"):
snapshot_module = SourceFileLoader("snapshot", os.path.join(current_dir(), "snapshots", "encrypt_mysql.py.encrypt_mysql.snapshot")).load_module()
for mode, key_len, iv_len in mysql_modes:
if not iv_len:
continue
with Example(f"""mode={mode.strip("'")} datatype=utf8string key={key_len} iv={iv_len}""") as example:
with Given("I have ciphertext"):
example_name = basename(example.name)
ciphertext = getattr(snapshot_module, varname(f"example_{example_name}"))
for mismatched_mode, _, _ in mysql_modes:
if mismatched_mode == mode:
continue
with When(f"I decrypt using a mismatched mode {mismatched_mode}"):
r = aes_decrypt_mysql(ciphertext=f"unhex({ciphertext})", key=f"'{key[:key_len]}'", mode=mismatched_mode,
iv=f"'{iv[:iv_len]}'",
cast="hex", no_checks=True)
with Then("exitcode shoud be 0 or 36 or 198"):
assert r.exitcode in [0, 36, 198], error()
with And("output should be garbage or an error"):
output = r.output.strip()
assert "Exception: Failed to decrypt" in output or output != plaintext, error()
@TestFeature
@Name("decrypt_mysql")
@Requirements(
RQ_SRS008_AES_MySQL_Decrypt_Function("1.0")
)
def feature(self, node="clickhouse1"):
"""Check the behavior of the `aes_decrypt_mysql` function.
"""
self.context.node = self.context.cluster.node(node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,408 @@
# -*- coding: utf-8 -*-
from testflows.core import *
from testflows.core.name import basename
from testflows.asserts import values, error, snapshot
from aes_encryption.requirements.requirements import *
from aes_encryption.tests.common import *
@TestOutline
def encrypt(self, plaintext=None, key=None, mode=None, iv=None, aad=None, exitcode=0, message=None, step=When):
"""Execute `encrypt` function with the specified parameters.
"""
params = []
if mode is not None:
params.append(mode)
if plaintext is not None:
params.append(plaintext)
if key is not None:
params.append(key)
if iv is not None:
params.append(iv)
if aad is not None:
params.append(aad)
sql = "SELECT hex(encrypt(" + ", ".join(params) + "))"
return current().context.node.query(sql, step=step, exitcode=exitcode, message=message)
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_InvalidParameters("1.0")
)
def invalid_parameters(self):
"""Check that `encrypt` function returns an error when
we call it with invalid parameters.
"""
with Example("no parameters"):
encrypt(exitcode=42, message="DB::Exception: Incorrect number of arguments for function encrypt provided 0, expected 3 to 5")
with Example("missing key and mode"):
encrypt(plaintext="'hello there'", exitcode=42, message="DB::Exception: Incorrect number of arguments for function encrypt provided 1")
with Example("missing mode"):
encrypt(plaintext="'hello there'", key="'123'", exitcode=42, message="DB::Exception: Incorrect number of arguments for function encrypt provided 2")
with Example("bad key type - UInt8"):
encrypt(plaintext="'hello there'", key="123", mode="'aes-128-ecb'", exitcode=43,
message="DB::Exception: Received from localhost:9000. DB::Exception: Illegal type of argument #3")
with Example("bad mode type - forgot quotes"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="aes-128-ecb", exitcode=47,
message="DB::Exception: Missing columns: 'ecb' 'aes' while processing query")
with Example("bad mode type - UInt8"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="128", exitcode=43,
message="DB::Exception: Illegal type of argument #1 'mode'")
with Example("bad iv type - UInt8"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-cbc'", iv='128', exitcode=43,
message="DB::Exception: Illegal type of argument")
with Example("bad aad type - UInt8"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-gcm'", iv="'012345678912'", aad="123", exitcode=43,
message="DB::Exception: Illegal type of argument")
with Example("iv not valid for mode", requirements=[RQ_SRS008_AES_Encrypt_Function_InitializationVector_NotValidForMode("1.0")]):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-ecb'", iv="'012345678912'", exitcode=36,
message="DB::Exception: aes-128-ecb does not support IV")
with Example("iv not valid for mode - size 0", requirements=[RQ_SRS008_AES_Encrypt_Function_InitializationVector_NotValidForMode("1.0")]):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-ecb'", iv="''", exitcode=36,
message="DB::Exception: aes-128-ecb does not support IV")
with Example("aad not valid for mode", requirements=[RQ_SRS008_AES_Encrypt_Function_AdditionalAuthenticationData_NotValidForMode("1.0")]):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-cbc'", iv="'0123456789123456'", aad="'aad'", exitcode=36,
message="DB::Exception: AAD can be only set for GCM-mode")
with Example("invalid mode value", requirements=[RQ_SRS008_AES_Encrypt_Function_Parameters_Mode_Value_Invalid("1.0")]):
with When("typo in the block algorithm"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-eeb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128-eeb")
with When("typo in the key size"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-127-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-127-ecb")
with When("typo in the aes prefix"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aee-128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aee-128-ecb")
with When("missing last dash"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128ecb")
with When("missing first dash"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'aes128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes128-ecb")
with When("all capitals"):
encrypt(plaintext="'hello there'", key="'0123456789123456'", mode="'AES-128-ECB'", exitcode=36,
message="DB::Exception: Invalid mode: AES-128-ECB")
@TestOutline(Scenario)
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Key_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Encrypt_Function_InitializationVector_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Encrypt_Function_AdditionalAuthenticationData_NotValidForMode("1.0")
)
@Examples("mode key_len iv_len aad", [
# ECB
("'aes-128-ecb'", 16, None, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ecb'", 24, None, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ecb'", 32, None, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_ECB_KeyAndInitializationVector_Length("1.0"))),
# CBC
("'aes-128-cbc'", 16, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cbc'", 24, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cbc'", 32, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_CBC_KeyAndInitializationVector_Length("1.0"))),
# CFB1
("'aes-128-cfb1'", 16, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb1'", 24, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb1'", 32, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_CFB1_KeyAndInitializationVector_Length("1.0"))),
# CFB8
("'aes-128-cfb8'", 16, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb8'", 24, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb8'", 32, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_CFB8_KeyAndInitializationVector_Length("1.0"))),
# CFB128
("'aes-128-cfb128'", 16, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb128'", 24, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb128'", 32, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_CFB128_KeyAndInitializationVector_Length("1.0"))),
# OFB
("'aes-128-ofb'", 16, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ofb'", 24, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ofb'", 32, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_OFB_KeyAndInitializationVector_Length("1.0"))),
# CTR
("'aes-128-ctr'", 16, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_CTR_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ctr'", 24, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_CTR_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ctr'", 32, 16, None,
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_CTR_KeyAndInitializationVector_Length("1.0"))),
], "%-16s %-10s %-10s %-10s")
def invalid_key_or_iv_length_for_mode_non_gcm(self, mode, key_len, iv_len, aad):
"""Check that an error is returned when key or iv length does not match
the expected value for the mode.
"""
plaintext = "'hello there'"
key = "0123456789" * 4
iv = "0123456789" * 4
with When("key is too short"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
with When("key is too long"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len+1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
if iv_len is not None:
with When("iv is too short"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
with When("iv is too long"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len+1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
if aad is None:
with When("aad is specified but not needed"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len+1] if iv_len is not None else ''}'", aad="'AAD'", mode=mode, exitcode=36, message="DB::Exception: AAD can be only set for GCM-mode")
else:
with When("iv is specified but not needed"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv}'", mode=mode, exitcode=36, message="DB::Exception: {} does not support IV".format(mode.strip("'")))
@TestOutline(Scenario)
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Key_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Encrypt_Function_InitializationVector_Length_InvalidLengthError("1.0"),
RQ_SRS008_AES_Encrypt_Function_AdditionalAuthenticationData_NotValidForMode("1.0")
)
@Examples("mode key_len iv_len aad", [
# GCM
("'aes-128-gcm'", 16, 8, "'hello there aad'",
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_128_GCM_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-gcm'", 24, 8, "''",
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_192_GCM_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-gcm'", 32, 8, "'a'",
Requirements(RQ_SRS008_AES_Encrypt_Function_AES_256_GCM_KeyAndInitializationVector_Length("1.0"))),
], "%-16s %-10s %-10s %-10s")
def invalid_key_or_iv_length_for_gcm(self, mode, key_len, iv_len, aad):
"""Check that an error is returned when key or iv length does not match
the expected value for the GCM mode.
"""
plaintext = "'hello there'"
key = "0123456789" * 4
iv = "0123456789" * 4
with When("key is too short"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len-1]}'", iv=f"'{iv[:iv_len]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
with When("key is too long"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len+1]}'", iv=f"'{iv[:iv_len]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
if iv_len is not None:
with When(f"iv is too short"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len-1]}'", mode=mode, exitcode=198, message="DB::Exception:")
else:
with When("iv is not specified"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
if aad is not None:
with When(f"aad is {aad}"):
encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len]}'", aad=f"{aad}", mode=mode)
@TestScenario
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Parameters_AdditionalAuthenticatedData("1.0"),
RQ_SRS008_AES_Encrypt_Function_AdditionalAuthenticationData_Length("1.0")
)
def aad_parameter_types_and_length(self):
"""Check that `encrypt` function accepts `aad` parameter as the fifth argument
of either `String` or `FixedString` types and that the length is not limited.
"""
plaintext = "'hello there'"
iv = "'012345678912'"
mode = "'aes-128-gcm'"
key = "'0123456789123456'"
with When("aad is specified using String type"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, aad="'aad'", message="19A1183335B374C626B24208AAEC97F148732CE05621AC87B21526")
with When("aad is specified using String with UTF8 characters"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, aad="'Gãńdåłf_Thê_Gręât'", message="19A1183335B374C626B242C68D9618A8C2664D7B6A3FE978104B39")
with When("aad is specified using FixedString type"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, aad="toFixedString('aad', 3)", message="19A1183335B374C626B24208AAEC97F148732CE05621AC87B21526")
with When("aad is specified using FixedString with UTF8 characters"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, aad="toFixedString('Gãńdåłf_Thê_Gręât', 24)", message="19A1183335B374C626B242C68D9618A8C2664D7B6A3FE978104B39")
with When("aad is 0 bytes"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, aad="''", message="19A1183335B374C626B242DF92BB3F57F5D82BEDF41FD5D49F8BC9")
with When("aad is 1 byte"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, aad="'1'", message="19A1183335B374C626B242D1BCFC63B09CFE9EAD20285044A01035")
with When("aad is 256 bytes"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, aad=f"'{'1' * 256}'", message="19A1183335B374C626B242355AD3DD2C5D7E36AEECBB847BF9E8A7")
@TestScenario
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Parameters_InitializationVector("1.0")
)
def iv_parameter_types(self):
"""Check that `encrypt` function accepts `iv` parameter as the fourth argument
of either `String` or `FixedString` types.
"""
plaintext = "'hello there'"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("iv is specified using String type"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, message="F024F9372FA0D8B974894D29FFB8A7F7")
with When("iv is specified using String with UTF8 characters"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv="'Gãńdåłf_Thê'", message="7A4EC0FF3796F46BED281F4778ACE1DC")
with When("iv is specified using FixedString type"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=f"toFixedString({iv}, 16)", message="F024F9372FA0D8B974894D29FFB8A7F7")
with When("iv is specified using FixedString with UTF8 characters"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv="toFixedString('Gãńdåłf_Thê', 16)", message="7A4EC0FF3796F46BED281F4778ACE1DC")
@TestScenario
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Parameters_Key("1.0")
)
def key_parameter_types(self):
"""Check that `encrypt` function accepts `key` parameter as the second argument
of either `String` or `FixedString` types.
"""
plaintext = "'hello there'"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("key is specified using String type"):
encrypt(plaintext=plaintext, key=key, mode=mode, message="49C9ADB81BA9B58C485E7ADB90E70576")
with When("key is specified using String with UTF8 characters"):
encrypt(plaintext=plaintext, key="'Gãńdåłf_Thê'", mode=mode, message="180086AA42AD57B71C706EEC372D0C3D")
with When("key is specified using FixedString type"):
encrypt(plaintext=plaintext, key=f"toFixedString({key}, 16)", mode=mode, message="49C9ADB81BA9B58C485E7ADB90E70576")
with When("key is specified using FixedString with UTF8 characters"):
encrypt(plaintext=plaintext, key="toFixedString('Gãńdåłf_Thê', 16)", mode=mode, message="180086AA42AD57B71C706EEC372D0C3D")
@TestScenario
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Parameters_Mode("1.0"),
)
def mode_parameter_types(self):
"""Check that `encrypt` function accepts `mode` parameter as the third argument
of either `String` or `FixedString` types.
"""
plaintext = "'hello there'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("mode is specified using String type"):
encrypt(plaintext=plaintext, key=key, mode=mode, message="49C9ADB81BA9B58C485E7ADB90E70576")
with When("mode is specified using FixedString type"):
encrypt(plaintext=plaintext, key=key, mode=f"toFixedString({mode}, 12)", message="49C9ADB81BA9B58C485E7ADB90E70576")
@TestScenario
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Parameters_PlainText("1.0"),
RQ_SRS008_AES_Encrypt_Function_Parameters_Mode("1.0"),
RQ_SRS008_AES_Encrypt_Function_Parameters_Mode_ValuesFormat("1.0"),
)
def encryption(self):
"""Check that `encrypt` functions accepts `plaintext` as the second parameter
with any data type and `mode` as the first parameter.
"""
key = f"{'1' * 36}"
iv = f"{'2' * 16}"
aad = "some random aad"
for mode, key_len, iv_len, aad_len in modes:
for datatype, plaintext in plaintexts:
requirement = globals().get(f"""RQ_SRS008_AES_Encrypt_Function_Parameters_Mode_Value_{mode.strip("'").replace("-","_").upper()}""")("1.0")
with Example(f"""mode={mode.strip("'")} datatype={datatype.strip("'")} iv={iv_len} aad={aad_len}""",
requirements=[requirement]) as example:
r = encrypt(plaintext=plaintext, key=f"'{key[:key_len]}'", mode=mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"), aad=(None if not aad_len else f"'{aad}'"))
with Then("I check output against snapshot"):
with values() as that:
example_name = basename(example.name)
assert that(snapshot(r.output.strip(), "encrypt", name=f"example_{example_name.replace(' ', '_')}")), error()
@TestScenario
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Parameters_ReturnValue("1.0")
)
def return_value(self):
"""Check that `encrypt` functions returns String data type.
"""
plaintext = "'hello there'"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("I get type of the return value"):
sql = "SELECT toTypeName(encrypt(" + mode + "," + plaintext + "," + key + "," + iv + "))"
r = self.context.node.query(sql)
with Then("type should be String"):
assert r.output.strip() == "String", error()
with When("I get return ciphertext as hex"):
encrypt(plaintext=plaintext, key=key, mode=mode, iv=iv, message="F024F9372FA0D8B974894D29FFB8A7F7")
@TestScenario
@Requirements(
RQ_SRS008_AES_Encrypt_Function_Syntax("1.0"),
)
def syntax(self):
"""Check that `encrypt` function supports syntax
```sql
encrypt(plaintext, key, mode, [iv, aad])
```
"""
sql = "SELECT hex(encrypt('aes-128-gcm', 'hello there', '0123456789123456', '012345678912', 'AAD'))"
self.context.node.query(sql, step=When, message="19A1183335B374C626B242A6F6E8712E2B64DCDC6A468B2F654614")
@TestFeature
@Name("encrypt")
@Requirements(
RQ_SRS008_AES_Encrypt_Function("1.0")
)
def feature(self, node="clickhouse1"):
"""Check the behavior of the `encrypt` function.
"""
self.context.node = self.context.cluster.node(node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

View File

@ -0,0 +1,326 @@
from testflows.core import *
from testflows.core.name import basename
from testflows.asserts import values, error, snapshot
from aes_encryption.requirements.requirements import *
from aes_encryption.tests.common import *
@TestOutline
def aes_encrypt_mysql(self, plaintext=None, key=None, mode=None, iv=None, exitcode=0, message=None, step=When):
"""Execute `aes_encrypt_mysql` function with the specified parameters.
"""
params = []
if mode is not None:
params.append(mode)
if plaintext is not None:
params.append(plaintext)
if key is not None:
params.append(key)
if iv is not None:
params.append(iv)
sql = "SELECT hex(aes_encrypt_mysql(" + ", ".join(params) + "))"
return current().context.node.query(sql, step=step, exitcode=exitcode, message=message)
@TestOutline(Scenario)
@Examples("mode", [
("'aes-128-gcm'", Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_AES_128_GCM_Error("1.0"))),
("'aes-192-gcm'", Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_AES_192_GCM_Error("1.0"))),
("'aes-256-gcm'", Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_AES_256_GCM_Error("1.0"))),
("'aes-128-ctr'", Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_AES_128_CTR_Error("1.0"))),
("'aes-192-ctr'", Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_AES_192_CTR_Error("1.0"))),
("'aes-256-ctr'", Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_AES_256_CTR_Error("1.0"))),
])
def unsupported_modes(self, mode):
"""Check that `aes_encrypt_mysql` function returns an error when unsupported modes are specified.
"""
aes_encrypt_mysql(plaintext="'hello there'", mode=mode, key=f"'{'1'* 32}'", exitcode=36, message="DB::Exception: Unsupported cipher mode")
@TestScenario
@Requirements(
RQ_SRS008_AES_Functions_InvalidParameters("1.0")
)
def invalid_parameters(self):
"""Check that `aes_encrypt_mysql` function returns an error when
we call it with invalid parameters.
"""
with Example("no parameters"):
aes_encrypt_mysql(exitcode=42, message="DB::Exception: Incorrect number of arguments for function aes_encrypt provided 0, expected 3 to 4")
with Example("missing key and mode"):
aes_encrypt_mysql(plaintext="'hello there'", exitcode=42, message="DB::Exception: Incorrect number of arguments for function aes_encrypt_mysql provided 1")
with Example("missing mode"):
aes_encrypt_mysql(plaintext="'hello there'", key="'123'", exitcode=42, message="DB::Exception: Incorrect number of arguments for function aes_encrypt_mysql provided 2")
with Example("bad key type - UInt8"):
aes_encrypt_mysql(plaintext="'hello there'", key="123", mode="'aes-128-ecb'", exitcode=43,
message="DB::Exception: Received from localhost:9000. DB::Exception: Illegal type of argument #3")
with Example("bad mode type - forgot quotes"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="aes-128-ecb", exitcode=47,
message="DB::Exception: Missing columns: 'ecb' 'aes' while processing query")
with Example("bad mode type - UInt8"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="128", exitcode=43,
message="DB::Exception: Illegal type of argument #1 'mode'")
with Example("bad iv type - UInt8"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-cbc'", iv='128', exitcode=43,
message="DB::Exception: Illegal type of argument")
with Example("iv not valid for mode", requirements=[RQ_SRS008_AES_MySQL_Encrypt_Function_InitializationVector_NotValidForMode("1.0")]):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-ecb'", iv="'012345678912'", exitcode=36,
message="DB::Exception: aes-128-ecb does not support IV")
with Example("iv not valid for mode - size 0", requirements=[RQ_SRS008_AES_MySQL_Encrypt_Function_InitializationVector_NotValidForMode("1.0")]):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-ecb'", iv="''", exitcode=0,
message=None)
with Example("invalid mode value", requirements=[RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_Invalid("1.0")]):
with When("typo in the block algorithm"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128-eeb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128-eeb")
with When("typo in the key size"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-127-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-127-ecb")
with When("typo in the aes prefix"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aee-128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aee-128-ecb")
with When("missing last dash"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aes-128ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes-128ecb")
with When("missing first dash"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'aes128-ecb'", exitcode=36,
message="DB::Exception: Invalid mode: aes128-ecb")
with When("all capitals"):
aes_encrypt_mysql(plaintext="'hello there'", key="'0123456789123456'", mode="'AES-128-ECB'", exitcode=36,
message="DB::Exception: Invalid mode: AES-128-ECB")
@TestOutline(Scenario)
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function_Key_Length_TooShortError("1.0"),
RQ_SRS008_AES_MySQL_Encrypt_Function_Key_Length_TooLong("1.0"),
RQ_SRS008_AES_MySQL_Encrypt_Function_InitializationVector_Length_TooShortError("1.0"),
RQ_SRS008_AES_MySQL_Encrypt_Function_InitializationVector_Length_TooLong("1.0"),
RQ_SRS008_AES_MySQL_Encrypt_Function_InitializationVector_NotValidForMode("1.0")
)
@Examples("mode key_len iv_len", [
# ECB
("'aes-128-ecb'", 16, None,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_128_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ecb'", 24, None,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_192_ECB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ecb'", 32, None,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_256_ECB_KeyAndInitializationVector_Length("1.0"))),
# CBC
("'aes-128-cbc'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_128_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cbc'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_192_CBC_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cbc'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_256_CBC_KeyAndInitializationVector_Length("1.0"))),
# CFB1
("'aes-128-cfb1'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_128_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb1'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_192_CFB1_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb1'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_256_CFB1_KeyAndInitializationVector_Length("1.0"))),
# CFB8
("'aes-128-cfb8'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_128_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb8'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_192_CFB8_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb8'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_256_CFB8_KeyAndInitializationVector_Length("1.0"))),
# CFB128
("'aes-128-cfb128'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_128_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-cfb128'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_192_CFB128_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-cfb128'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_256_CFB128_KeyAndInitializationVector_Length("1.0"))),
# OFB
("'aes-128-ofb'", 16, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_128_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-192-ofb'", 24, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_192_OFB_KeyAndInitializationVector_Length("1.0"))),
("'aes-256-ofb'", 32, 16,
Requirements(RQ_SRS008_AES_MySQL_Encrypt_Function_AES_256_OFB_KeyAndInitializationVector_Length("1.0"))),
], "%-16s %-10s %-10s")
def key_or_iv_length_for_mode(self, mode, key_len, iv_len):
"""Check that key or iv length for mode.
"""
plaintext = "'hello there'"
key = "0123456789" * 4
iv = "0123456789" * 4
with When("key is too short"):
aes_encrypt_mysql(plaintext=plaintext, key=f"'{key[:key_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid key size")
with When("key is too long"):
aes_encrypt_mysql(plaintext=plaintext, key=f"'{key[:key_len+1]}'", mode=mode)
if iv_len is not None:
with When("iv is too short"):
aes_encrypt_mysql(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len-1]}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
with When("iv is too long"):
aes_encrypt_mysql(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv[:iv_len+1]}'", mode=mode)
else:
with When("iv is specified but not needed"):
aes_encrypt_mysql(plaintext=plaintext, key=f"'{key[:key_len]}'", iv=f"'{iv}'", mode=mode, exitcode=36, message="DB::Exception: Invalid IV size")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_InitializationVector("1.0")
)
def iv_parameter_types(self):
"""Check that `aes_encrypt_mysql` function accepts `iv` parameter as the fourth argument
of either `String` or `FixedString` types.
"""
plaintext = "'hello there'"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("iv is specified using String type"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=mode, iv=iv, message="F024F9372FA0D8B974894D29FFB8A7F7")
with When("iv is specified using String with UTF8 characters"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=mode, iv="'Gãńdåłf_Thê'", message="7A4EC0FF3796F46BED281F4778ACE1DC")
with When("iv is specified using FixedString type"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=mode, iv=f"toFixedString({iv}, 16)", message="F024F9372FA0D8B974894D29FFB8A7F7")
with When("iv is specified using FixedString with UTF8 characters"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=mode, iv="toFixedString('Gãńdåłf_Thê', 16)", message="7A4EC0FF3796F46BED281F4778ACE1DC")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Key("1.0")
)
def key_parameter_types(self):
"""Check that `aes_encrypt_mysql` function accepts `key` parameter as the second argument
of either `String` or `FixedString` types.
"""
plaintext = "'hello there'"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("key is specified using String type"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=mode, message="49C9ADB81BA9B58C485E7ADB90E70576")
with When("key is specified using String with UTF8 characters"):
aes_encrypt_mysql(plaintext=plaintext, key="'Gãńdåłf_Thê'", mode=mode, message="180086AA42AD57B71C706EEC372D0C3D")
with When("key is specified using FixedString type"):
aes_encrypt_mysql(plaintext=plaintext, key=f"toFixedString({key}, 16)", mode=mode, message="49C9ADB81BA9B58C485E7ADB90E70576")
with When("key is specified using FixedString with UTF8 characters"):
aes_encrypt_mysql(plaintext=plaintext, key="toFixedString('Gãńdåłf_Thê', 16)", mode=mode, message="180086AA42AD57B71C706EEC372D0C3D")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode("1.0"),
)
def mode_parameter_types(self):
"""Check that `aes_encrypt_mysql` function accepts `mode` parameter as the third argument
of either `String` or `FixedString` types.
"""
plaintext = "'hello there'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("mode is specified using String type"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=mode, message="49C9ADB81BA9B58C485E7ADB90E70576")
with When("mode is specified using FixedString type"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=f"toFixedString({mode}, 12)", message="49C9ADB81BA9B58C485E7ADB90E70576")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_ReturnValue("1.0")
)
def return_value(self):
"""Check that `aes_encrypt_mysql` functions returns String data type.
"""
plaintext = "'hello there'"
iv = "'0123456789123456'"
mode = "'aes-128-cbc'"
key = "'0123456789123456'"
with When("I get type of the return value"):
sql = "SELECT toTypeName(aes_encrypt_mysql("+ mode + "," + plaintext + "," + key + "," + iv + "))"
r = self.context.node.query(sql)
with Then("type should be String"):
assert r.output.strip() == "String", error()
with When("I get return ciphertext as hex"):
aes_encrypt_mysql(plaintext=plaintext, key=key, mode=mode, iv=iv, message="F024F9372FA0D8B974894D29FFB8A7F7")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function_Syntax("1.0"),
)
def syntax(self):
"""Check that `aes_encrypt_mysql` function supports syntax
```sql
aes_encrypt_mysql(plaintext, key, mode, [iv])
```
"""
sql = "SELECT hex(aes_encrypt_mysql('aes-128-ofb', 'hello there', '0123456789123456', '0123456789123456'))"
self.context.node.query(sql, step=When, message="70FE78410D6EE237C2DE4A")
@TestScenario
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_PlainText("1.0"),
RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode("1.0"),
RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_ValuesFormat("1.0"),
)
def encryption(self):
"""Check that `aes_encrypt_mysql` functions accepts `plaintext` as the second parameter
with any data type and `mode` as the first parameter.
"""
key = f"{'1' * 64}"
iv = f"{'2' * 64}"
for mode, key_len, iv_len in mysql_modes:
for datatype, plaintext in plaintexts:
requirement = globals().get(f"""RQ_SRS008_AES_MySQL_Encrypt_Function_Parameters_Mode_Value_{mode.strip("'").replace("-","_").upper()}""")("1.0")
with Example(f"""mode={mode.strip("'")} datatype={datatype.strip("'")} key={key_len} iv={iv_len}""",
requirements=[requirement]) as example:
r = aes_encrypt_mysql(plaintext=plaintext, key=f"'{key[:key_len]}'", mode=mode,
iv=(None if not iv_len else f"'{iv[:iv_len]}'"))
with Then("I check output against snapshot"):
with values() as that:
example_name = basename(example.name)
assert that(snapshot(r.output.strip(), "encrypt_mysql", name=f"example_{example_name.replace(' ', '_')}")), error()
@TestFeature
@Name("encrypt_mysql")
@Requirements(
RQ_SRS008_AES_MySQL_Encrypt_Function("1.0")
)
def feature(self, node="clickhouse1"):
"""Check the behavior of the `aes_encrypt_mysql` function.
"""
self.context.node = self.context.cluster.node(node)
for scenario in loads(current_module(), Scenario):
Scenario(run=scenario, flags=TE)

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@ def regression(self, local, clickhouse_binary_path):
Feature(test=load("example.regression", "regression"))(**args)
Feature(test=load("ldap.regression", "regression"))(**args)
Feature(test=load("rbac.regression", "regression"))(**args)
Feature(test=load("aes_encryption.regression", "regression"))(**args)
if main():
regression()