Path: blob/main/sys/contrib/openzfs/scripts/convert_wycheproof.pl
48261 views
#!/usr/bin/env perl12# SPDX-License-Identifier: MIT3#4# Copyright (c) 2025, Rob Norris <[email protected]>5#6# Permission is hereby granted, free of charge, to any person obtaining a copy7# of this software and associated documentation files (the "Software"), to8# deal in the Software without restriction, including without limitation the9# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or10# sell copies of the Software, and to permit persons to whom the Software is11# furnished to do so, subject to the following conditions:12#13# The above copyright notice and this permission notice shall be included in14# all copies or substantial portions of the Software.15#16# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR17# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,18# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE19# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER20# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING21# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS22# IN THE SOFTWARE.2324#25# This programs converts AEAD test vectors from Project Wycheproof into a26# format that can be consumed more easily by tests/zfs-tests/cmd/crypto_test.27# See tests/zfs-tests/tests/functional/crypto/README for more info.28#2930use 5.010;31use warnings;32use strict;33use JSON qw(decode_json);3435sub usage {36say "usage: $0 <infile> [<outfile>]";37exit 1;38}3940my ($infile, $outfile) = @ARGV;4142usage() if !defined $infile;4344open my $infh, '<', $infile or die "E: $infile: $!\n";45my $json = do { local $/; <$infh> };46close $infh;4748my $data = decode_json $json;4950select STDERR;5152# 0.8 had a slightly different format. 0.9* is current, stabilising for 1.053my $version = $data->{generatorVersion} // "[unknown]";54if ("$version" !~ m/^0\.9[^0-9]/) {55warn56"W: this converter was written for Wycheproof 0.9 test vectors\n".57" input file has version: $version\n".58" bravely continuing, but expect crashes or garbled output\n";59}6061# we only support AEAD tests62my $schema = $data->{schema} // "[unknown]";63if ("$schema" ne 'aead_test_schema.json') {64warn65"W: this converter is expecting AEAD test vectors\n".66" input file has schema: $schema\n".67" bravely continuing, but expect crashes or garbled output\n";68}6970# sanity check; algorithm is provided71my $algorithm = $data->{algorithm};72if (!defined $algorithm) {73die "E: $infile: required field 'algorithm' not found\n";74}7576# sanity check; test count is present and correct77my $ntests = 0;78$ntests += $_ for map { scalar @{$_->{tests}} } @{$data->{testGroups}};79if (!exists $data->{numberOfTests}) {80warn "W: input file has no test count, using mine: $ntests\n";81} elsif ($data->{numberOfTests} != $ntests) {82warn83"W: input file has incorrect test count: $data->{numberOfTests}\n".84" using my own count: $ntests\n";85}8687say " version: $version";88say " schema: $schema";89say "algorithm: $algorithm";90say " ntests: $ntests";9192my $skipped = 0;9394my @tests;9596# tests are grouped into "test groups". groups have the same type and IV, key97# and tag sizes. we can infer this info from the tests themselves, but it's98# useful for sanity checks99#100# "testGroups" : [101# {102# "ivSize" : 96,103# "keySize" : 128,104# "tagSize" : 128,105# "type" : "AeadTest",106# "tests" : [ ... ]107#108for my $group (@{$data->{testGroups}}) {109# skip non-AEAD test groups110my $type = $group->{type} // "[unknown]";111if ($type ne 'AeadTest') {112warn "W: group has unexpected type '$type', skipping it\n";113$skipped += @{$data->{tests}};114next;115}116117my ($iv_size, $key_size, $tag_size) =118@$group{qw(ivSize keySize tagSize)};119120# a typical test:121#122# {123# "tcId" : 48,124# "comment" : "Flipped bit 63 in tag",125# "flags" : [126# "ModifiedTag"127# ],128# "key" : "000102030405060708090a0b0c0d0e0f",129# "iv" : "505152535455565758595a5b",130# "aad" : "",131# "msg" : "202122232425262728292a2b2c2d2e2f",132# "ct" : "eb156d081ed6b6b55f4612f021d87b39",133# "tag" : "d8847dbc326a066988c77ad3863e6083",134# "result" : "invalid"135# },136#137# we include everything in the output. the id is useful output so the138# user can go back to the original test. comment and flags are useful139# for output in a failing test140#141for my $test (@{$group->{tests}}) {142my ($id, $comment, $iv, $key, $msg, $ct, $aad, $tag, $result) =143@$test{qw(tcId comment iv key msg ct aad tag result)};144145# sanity check; iv and key must have the length declared by the146# group params.147unless (148length_check($id, 'iv', $iv, $iv_size) &&149length_check($id, 'key', $key, $key_size)) {150$skipped++;151next;152}153154# sanity check; tag must have the length declared by the group155# param, but only for valid tests (invalid tests should be156# rejected, and so can't produce a tag anyway)157unless (158$result eq 'invalid' ||159length_check($id, 'tag', $tag, $tag_size)) {160$skipped++;161next;162}163164# flatten and sort the flags into a single string165my $flags;166if ($test->{flags}) {167$flags = join(' ', sort @{$test->{flags}});168}169170# the completed test record. we'll emit this later once we're171# finished with the input; the output file is not open yet.172push @tests, [173[ id => $id ],174[ comment => $comment ],175(defined $flags ? [ flags => $flags ] : ()),176[ iv => $iv ],177[ key => $key ],178[ msg => $msg ],179[ ct => $ct ],180[ aad => $aad ],181[ tag => $tag ],182[ result => $result ],183];184}185}186187if ($skipped) {188$ntests -= $skipped;189warn "W: skipped $skipped tests; new test count: $ntests\n";190}191if ($ntests == 0) {192die "E: no tests extracted, sorry!\n";193}194195my $outfh;196if ($outfile) {197open $outfh, '>', $outfile or die "E: $outfile: $!\n";198} else {199$outfh = *STDOUT;200}201202# the "header" record has the algorithm and count of tests203say $outfh "algorithm: $algorithm";204say $outfh "tests: $ntests";205206#207for my $test (@tests) {208# blank line is a record separator209say $outfh "";210211# output the test data in a simple record of 'key: value' lines212#213# id: 48214# comment: Flipped bit 63 in tag215# flags: ModifiedTag216# iv: 505152535455565758595a5b217# key: 000102030405060708090a0b0c0d0e0f218# msg: 202122232425262728292a2b2c2d2e2f219# ct: eb156d081ed6b6b55f4612f021d87b39220# aad:221# tag: d8847dbc326a066988c77ad3863e6083222# result: invalid223for my $row (@$test) {224my ($k, $v) = @$row;225say $outfh "$k: $v";226}227}228229close $outfh;230231# check that the length of hex string matches the wanted number of bits232sub length_check {233my ($id, $name, $hexstr, $wantbits) = @_;234my $got = length($hexstr)/2;235my $want = $wantbits/8;236return 1 if $got == $want;237my $gotbits = $got*8;238say239"W: $id: '$name' has incorrect len, skipping test:\n".240" got $got bytes ($gotbits bits)\n".241" want $want bytes ($wantbits bits)\n";242return;243}244245246