summaryrefslogtreecommitdiffstats
path: root/security/nss/gtests/common/wycheproof/genTestVectors.py
blob: 1e2eb47ecda248530171f980877f91e51b260a7e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.

import json
import os
import subprocess

script_dir = os.path.dirname(os.path.abspath(__file__))

# Imports a JSON testvector file.
def import_testvector(file):
    """Import a JSON testvector file and return an array of the contained objects."""
    with open(file) as f:
        vectors = json.loads(f.read())
    return vectors

# Convert a test data string to a hex array.
def string_to_hex_array(string):
    """Convert a string of hex chars to a string representing a C-format array of hex bytes."""
    b = bytearray.fromhex(string)
    result = '{' + ', '.join("{:#04x}".format(x) for x in b) + '}'
    return result

# Writes one AES-GCM testvector into C-header format. (Not clang-format conform)
class AESGCM():
    """Class that provides the generator function for a single AES-GCM test case."""

    def format_testcase(self, vector):
        """Format an AES-GCM testcase object. Return a string in C-header format."""
        result = '{{ {},\n'.format(vector['tcId'])
        for key in ['key', 'msg', 'aad', 'iv']:
            result += ' \"{}\",\n'.format(vector[key])
        result += ' \"\",\n'
        result += ' \"{}\",\n'.format(vector['tag'])
        result += ' \"{}\",\n'.format(vector['ct'] + vector['tag'])
        result += ' {},\n'.format(str(vector['result'] == 'invalid').lower())
        result += ' {}}},\n\n'.format(str('ZeroLengthIv' in vector['flags']).lower())

        return result

# Writes one ChaChaPoly testvector into C-header format. (Not clang-format conform)
class ChaChaPoly():
    """Class that provides the generator function for a single ChaCha test case."""

    def format_testcase(self, testcase):
        """Format an ChaCha testcase object. Return a string in C-header format."""
        result = '\n// Comment: {}'.format(testcase['comment'])
        result += '\n{{{},\n'.format(testcase['tcId']-1)
        for key in ['msg', 'aad', 'key', 'iv']:
            result += '{},\n'.format(string_to_hex_array(testcase[key]))
        ct = testcase['ct'] + testcase['tag']
        result += '{},\n'.format(string_to_hex_array(ct))
        result += '{},\n'.format(str(testcase['result'] == 'invalid').lower())
        result += '{}}},\n'.format(str(testcase['comment'] == 'invalid nonce size').lower())

        return result

# Writes one Curve25519 testvector into C-header format. (Not clang-format conform)
class Curve25519():
    """Class that provides the generator function for a single curve25519 test case."""

    # Static pkcs8 and skpi wrappers for the raw keys from Wycheproof.
    # The public key section of the pkcs8 wrapper is filled up with 0's, which is
    # not correct, but acceptable for the tests at this moment because
    # validity of the public key is not checked.
    # It's still necessary because of
    # https://searchfox.org/nss/rev/7bc70a3317b800aac07bad83e74b6c79a9ec5bff/lib/pk11wrap/pk11pk12.c#171
    pkcs8WrapperStart = "3067020100301406072a8648ce3d020106092b06010401da470f01044c304a0201010420"
    pkcs8WrapperEnd = "a1230321000000000000000000000000000000000000000000000000000000000000000000"
    spkiWrapper = "3039301406072a8648ce3d020106092b06010401da470f01032100"

    def format_testcase(self, testcase):
        result = '\n// Comment: {}'.format(testcase['comment'])
        result += '\n{{{},\n'.format(string_to_hex_array(self.pkcs8WrapperStart + testcase['private'] + self.pkcs8WrapperEnd))
        result += '{},\n'.format(string_to_hex_array(self.spkiWrapper + testcase['public']))
        result += '{},\n'.format(string_to_hex_array(testcase['shared']))

        # Flag 'acceptable' cases with secret == 0 as invalid for NSS.
        # Flag 'acceptable' cases with forbidden public key values as invalid for NSS.
        # Flag 'acceptable' cases with small public key (0 or 1) as invalid for NSS.
        valid = testcase['result'] in ['valid', 'acceptable'] \
                and not testcase['shared'] == "0000000000000000000000000000000000000000000000000000000000000000" \
                and not testcase["public"] == "daffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" \
                and not testcase["public"] == "dbffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" \
                and not 'Small public key' in testcase['flags']
        result += '{}}},\n'.format(str(valid).lower())

        return result

def generate_vectors_file(params):
    """
    Generate and store a .h-file with test vectors for one test.

    params -- Dictionary with parameters for test vector generation for the desired test.
    """

    cases = import_testvector(os.path.join(script_dir, params['source_dir'] + params['source_file']))

    with open(os.path.join(script_dir, params['base'])) as base:
        header = base.read()

    header = header[:params['crop_size_start']]
    header += '\n\n// Testvectors from project wycheproof\n'
    header += '// <https://github.com/google/wycheproof>\n'
    vectors_file = header + params['array_init']

    for group in cases['testGroups']:
        for test in group['tests']:
            vectors_file += params['formatter'].format_testcase(test)

    vectors_file = vectors_file[:params['crop_size_end']] + '};\n\n'
    vectors_file += params['finish']

    with open(os.path.join(script_dir, params['target']), 'w') as target:
        target.write(vectors_file)

# Parameters that describe the generation of a testvector file for each supoorted testself.
# source -- relaive path the wycheproof JSON source file with testvectorsself.
# base -- relative path to the pre-fabricated .h-file with general defintions and non-wycheproof vectors.
# target -- relative path to where the finished .h-file is written.
# crop_size_start -- number of characters removed from the end of the base file at start.
# array_init -- string to initialize the c-header style array of testvectors.
# formatter -- the test case formatter class to be used for this test.
# crop_size_end -- number of characters removed from the end of the last generated test vector to close the array definiton.
# finish -- string to re-insert at the end and finish the file. (identical to chars cropped at the start)
# comment -- additional comments to add to the file just before defintion of the test vector array.
aes_gcm_params = {
    'source_dir': 'source_vectors/',
    'source_file': 'aes_gcm_test.json',
    'base': '../testvectors_base/gcm-vectors_base.h',
    'target': '../testvectors/gcm-vectors.h',
    'crop_size_start': -27,
    'array_init': 'const gcm_kat_value kGcmWycheproofVectors[] = {\n',
    'formatter' : AESGCM(),
    'crop_size_end': -3,
    'finish': '#endif  // gcm_vectors_h__\n',
    'comment' : ''
}

chacha_poly_params = {
    'source_dir': 'source_vectors/',
    'source_file': 'chacha20_poly1305_test.json',
    'base': '../testvectors_base/chachapoly-vectors_base.h',
    'target': '../testvectors/chachapoly-vectors.h',
    'crop_size_start': -35,
    'array_init': 'const chacha_testvector kChaCha20WycheproofVectors[] = {\n',
    'formatter' : ChaChaPoly(),
    'crop_size_end': -2,
    'finish': '#endif  // chachapoly_vectors_h__\n',
    'comment' : ''
}

curve25519_params = {
    'source_dir': 'source_vectors/',
    'source_file': 'x25519_test.json',
    'base': '../testvectors_base/curve25519-vectors_base.h',
    'target': '../testvectors/curve25519-vectors.h',
    'crop_size_start': -34,
    'array_init': 'const curve25519_testvector kCurve25519WycheproofVectors[] = {\n',
    'formatter' : Curve25519(),
    'crop_size_end': -2,
    'finish': '#endif  // curve25519_vectors_h__\n',
    'comment' : '// The public key section of the pkcs8 wrapped private key is\n\
    // filled up with 0\'s, which is not correct, but acceptable for the\n\
    // tests at this moment because validity of the public key is not checked.\n'
}

def update_tests(tests):

    remote = "https://raw.githubusercontent.com/google/wycheproof/master/testvectors/"
    for test in tests:
        subprocess.check_call(['wget', remote+test['source_file'], '-O',
                               'gtests/common/wycheproof/source_vectors/' +test['source_file'],
                               '--no-check-certificate'])

def generate_test_vectors():
    """Generate C-header files for all supported tests."""
    all_tests = [aes_gcm_params, chacha_poly_params, curve25519_params]
    update_tests(all_tests)
    for test in all_tests:
        generate_vectors_file(test)

def main():
    generate_test_vectors()

if __name__ == '__main__':
    main()