| // Copyright 2018 The BoringSSL Authors | |
| // | |
| // Licensed under the Apache License, Version 2.0 (the "License"); | |
| // you may not use this file except in compliance with the License. | |
| // You may obtain a copy of the License at | |
| // | |
| // https://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, software | |
| // distributed under the License is distributed on an "AS IS" BASIS, | |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| // See the License for the specific language governing permissions and | |
| // limitations under the License. | |
| // convert_wycheproof converts Wycheproof test vectors into a format more easily | |
| // consumed by BoringSSL. | |
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "io" | |
| "os" | |
| "sort" | |
| "strings" | |
| ) | |
| type wycheproofTest struct { | |
| Algorithm string `json:"algorithm"` | |
| GeneratorVersion string `json:"generatorVersion"` | |
| NumberOfTests int `json:"numberOfTests"` | |
| Header []string `json:"header"` | |
| // encoding/json does not support collecting unused keys, so we leave | |
| // everything past this point as generic. | |
| TestGroups []map[string]any `json:"testGroups"` | |
| } | |
| func sortedKeys(m map[string]any) []string { | |
| keys := make([]string, 0, len(m)) | |
| for k, _ := range m { | |
| keys = append(keys, k) | |
| } | |
| sort.Strings(keys) | |
| return keys | |
| } | |
| func printAttribute(w io.Writer, key string, valueAny any, isInstruction bool) error { | |
| switch value := valueAny.(type) { | |
| case float64: | |
| if float64(int(value)) != value { | |
| panic(key + "was not an integer.") | |
| } | |
| if isInstruction { | |
| if _, err := fmt.Fprintf(w, "[%s = %d]\n", key, int(value)); err != nil { | |
| return err | |
| } | |
| } else { | |
| if _, err := fmt.Fprintf(w, "%s = %d\n", key, int(value)); err != nil { | |
| return err | |
| } | |
| } | |
| case string: | |
| if strings.Contains(value, "\n") { | |
| panic(key + " contained a newline.") | |
| } | |
| if isInstruction { | |
| if _, err := fmt.Fprintf(w, "[%s = %s]\n", key, value); err != nil { | |
| return err | |
| } | |
| } else { | |
| if _, err := fmt.Fprintf(w, "%s = %s\n", key, value); err != nil { | |
| return err | |
| } | |
| } | |
| case map[string]any: | |
| for _, k := range sortedKeys(value) { | |
| if err := printAttribute(w, key+"."+k, value[k], isInstruction); err != nil { | |
| return err | |
| } | |
| } | |
| default: | |
| panic(fmt.Sprintf("Unknown type for %q: %T", key, valueAny)) | |
| } | |
| return nil | |
| } | |
| func printComment(w io.Writer, in string) error { | |
| const width = 80 - 2 | |
| lines := strings.Split(in, "\n") | |
| for _, line := range lines { | |
| for { | |
| if len(line) <= width { | |
| if _, err := fmt.Fprintf(w, "# %s\n", line); err != nil { | |
| return err | |
| } | |
| break | |
| } | |
| // Find the last space we can break at. | |
| n := strings.LastIndexByte(line[:width+1], ' ') | |
| if n < 0 { | |
| // The next word is too long. Wrap as soon as that word ends. | |
| n = strings.IndexByte(line[width+1:], ' ') | |
| if n < 0 { | |
| // This was the last word. | |
| if _, err := fmt.Fprintf(w, "# %s\n", line); err != nil { | |
| return nil | |
| } | |
| break | |
| } | |
| n += width + 1 | |
| } | |
| if _, err := fmt.Fprintf(w, "# %s\n", line[:n]); err != nil { | |
| return err | |
| } | |
| line = line[n+1:] // Ignore the space. | |
| } | |
| } | |
| return nil | |
| } | |
| func convertWycheproof(f io.Writer, jsonPath string) error { | |
| jsonData, err := os.ReadFile(jsonPath) | |
| if err != nil { | |
| return err | |
| } | |
| var w wycheproofTest | |
| if err := json.Unmarshal(jsonData, &w); err != nil { | |
| return err | |
| } | |
| if _, err := fmt.Fprintf(f, `# Imported from Wycheproof's %s. | |
| # This file is generated by convert_wycheproof.go. Do not edit by hand. | |
| # | |
| # Algorithm: %s | |
| # Generator version: %s | |
| `, jsonPath, w.Algorithm, w.GeneratorVersion); err != nil { | |
| return err | |
| } | |
| for _, group := range w.TestGroups { | |
| for _, k := range sortedKeys(group) { | |
| // Wycheproof files include keys in multiple formats. Skip PEM and | |
| // JWK formats. We process DER more easily. PEM has newlines and | |
| // JWK is a JSON object. | |
| if k == "type" || k == "tests" || k == "source" || strings.HasSuffix(k, "Pem") || strings.HasSuffix(k, "Jwk") || k == "jwk" { | |
| continue | |
| } | |
| if err := printAttribute(f, k, group[k], true); err != nil { | |
| return err | |
| } | |
| } | |
| fmt.Fprintf(f, "\n") | |
| tests := group["tests"].([]any) | |
| for _, testAny := range tests { | |
| test := testAny.(map[string]any) | |
| if _, err := fmt.Fprintf(f, "# tcId = %d\n", int(test["tcId"].(float64))); err != nil { | |
| return err | |
| } | |
| if comment, ok := test["comment"]; ok && comment.(string) != "" { | |
| if err := printComment(f, comment.(string)); err != nil { | |
| return err | |
| } | |
| } | |
| for _, k := range sortedKeys(test) { | |
| if k == "comment" || k == "flags" || k == "tcId" { | |
| continue | |
| } | |
| if err := printAttribute(f, k, test[k], false); err != nil { | |
| return err | |
| } | |
| } | |
| if flagsAny, ok := test["flags"]; ok { | |
| var flags []string | |
| for _, flagAny := range flagsAny.([]any) { | |
| flag := flagAny.(string) | |
| flags = append(flags, flag) | |
| } | |
| if len(flags) != 0 { | |
| if err := printAttribute(f, "flags", strings.Join(flags, ","), false); err != nil { | |
| return err | |
| } | |
| } | |
| } | |
| if _, err := fmt.Fprintf(f, "\n"); err != nil { | |
| return err | |
| } | |
| } | |
| } | |
| return nil | |
| } | |
| var defaultInputs = []string{ | |
| "aes_cbc_pkcs5_test.json", | |
| "aes_cmac_test.json", | |
| "aes_eax_test.json", | |
| "aes_gcm_siv_test.json", | |
| "aes_gcm_test.json", | |
| "chacha20_poly1305_test.json", | |
| "dsa_2048_224_sha224_test.json", | |
| "dsa_2048_224_sha256_test.json", | |
| "dsa_2048_256_sha256_test.json", | |
| "dsa_3072_256_sha256_test.json", | |
| "dsa_2048_224_sha224_p1363_test.json", | |
| "dsa_2048_224_sha256_p1363_test.json", | |
| "dsa_2048_256_sha256_p1363_test.json", | |
| "dsa_3072_256_sha256_p1363_test.json", | |
| "ecdh_secp224r1_test.json", | |
| "ecdh_secp256r1_test.json", | |
| "ecdh_secp384r1_test.json", | |
| "ecdh_secp521r1_test.json", | |
| "ecdsa_secp224r1_sha224_p1363_test.json", | |
| "ecdsa_secp224r1_sha224_test.json", | |
| "ecdsa_secp224r1_sha256_p1363_test.json", | |
| "ecdsa_secp224r1_sha256_test.json", | |
| "ecdsa_secp224r1_sha512_p1363_test.json", | |
| "ecdsa_secp224r1_sha512_test.json", | |
| "ecdsa_secp256r1_sha256_p1363_test.json", | |
| "ecdsa_secp256r1_sha256_test.json", | |
| "ecdsa_secp256r1_sha512_p1363_test.json", | |
| "ecdsa_secp256r1_sha512_test.json", | |
| "ecdsa_secp384r1_sha384_p1363_test.json", | |
| "ecdsa_secp384r1_sha384_test.json", | |
| "ecdsa_secp384r1_sha512_p1363_test.json", | |
| "ecdsa_secp384r1_sha512_test.json", | |
| "ecdsa_secp521r1_sha512_p1363_test.json", | |
| "ecdsa_secp521r1_sha512_test.json", | |
| "ed25519_test.json", | |
| "hkdf_sha1_test.json", | |
| "hkdf_sha256_test.json", | |
| "hkdf_sha384_test.json", | |
| "hkdf_sha512_test.json", | |
| "hmac_sha1_test.json", | |
| "hmac_sha224_test.json", | |
| "hmac_sha256_test.json", | |
| "hmac_sha384_test.json", | |
| "hmac_sha512_test.json", | |
| "aes_wrap_test.json", | |
| "aes_kwp_test.json", | |
| "primality_test.json", | |
| "rsa_oaep_2048_sha1_mgf1sha1_test.json", | |
| "rsa_oaep_2048_sha224_mgf1sha1_test.json", | |
| "rsa_oaep_2048_sha224_mgf1sha224_test.json", | |
| "rsa_oaep_2048_sha256_mgf1sha1_test.json", | |
| "rsa_oaep_2048_sha256_mgf1sha256_test.json", | |
| "rsa_oaep_2048_sha384_mgf1sha1_test.json", | |
| "rsa_oaep_2048_sha384_mgf1sha384_test.json", | |
| "rsa_oaep_2048_sha512_mgf1sha1_test.json", | |
| "rsa_oaep_2048_sha512_mgf1sha512_test.json", | |
| "rsa_oaep_3072_sha256_mgf1sha1_test.json", | |
| "rsa_oaep_3072_sha256_mgf1sha256_test.json", | |
| "rsa_oaep_3072_sha512_mgf1sha1_test.json", | |
| "rsa_oaep_3072_sha512_mgf1sha512_test.json", | |
| "rsa_oaep_4096_sha256_mgf1sha1_test.json", | |
| "rsa_oaep_4096_sha256_mgf1sha256_test.json", | |
| "rsa_oaep_4096_sha512_mgf1sha1_test.json", | |
| "rsa_oaep_4096_sha512_mgf1sha512_test.json", | |
| "rsa_oaep_misc_test.json", | |
| "rsa_pkcs1_2048_test.json", | |
| "rsa_pkcs1_3072_test.json", | |
| "rsa_pkcs1_4096_test.json", | |
| "rsa_pss_2048_sha1_mgf1_20_test.json", | |
| "rsa_pss_2048_sha256_mgf1_0_test.json", | |
| "rsa_pss_2048_sha256_mgf1_32_test.json", | |
| "rsa_pss_3072_sha256_mgf1_32_test.json", | |
| "rsa_pss_4096_sha256_mgf1_32_test.json", | |
| "rsa_pss_4096_sha512_mgf1_32_test.json", | |
| "rsa_pss_misc_test.json", | |
| "rsa_pkcs1_1024_sig_gen_test.json", | |
| "rsa_pkcs1_1536_sig_gen_test.json", | |
| "rsa_pkcs1_2048_sig_gen_test.json", | |
| "rsa_pkcs1_3072_sig_gen_test.json", | |
| "rsa_pkcs1_4096_sig_gen_test.json", | |
| "rsa_signature_2048_sha224_test.json", | |
| "rsa_signature_2048_sha256_test.json", | |
| "rsa_signature_2048_sha384_test.json", | |
| "rsa_signature_2048_sha512_test.json", | |
| "rsa_signature_3072_sha256_test.json", | |
| "rsa_signature_3072_sha384_test.json", | |
| "rsa_signature_3072_sha512_test.json", | |
| "rsa_signature_4096_sha256_test.json", | |
| "rsa_signature_4096_sha384_test.json", | |
| "rsa_signature_4096_sha512_test.json", | |
| "rsa_signature_8192_sha256_test.json", | |
| "rsa_signature_8192_sha384_test.json", | |
| "rsa_signature_8192_sha512_test.json", | |
| "x25519_test.json", | |
| "xchacha20_poly1305_test.json", | |
| "mldsa_44_sign_noseed_test.json", | |
| "mldsa_44_sign_seed_test.json", | |
| "mldsa_44_verify_test.json", | |
| "mldsa_65_sign_noseed_test.json", | |
| "mldsa_65_sign_seed_test.json", | |
| "mldsa_65_verify_test.json", | |
| "mldsa_87_sign_noseed_test.json", | |
| "mldsa_87_sign_seed_test.json", | |
| "mldsa_87_verify_test.json", | |
| // TODO(crbug.com/42290453): Enable ML-KEM-768 and ML-KEM-1024 tests | |
| // "mlkem_768_test.json", | |
| // "mlkem_1024_test.json", | |
| } | |
| func main() { | |
| switch len(os.Args) { | |
| case 1: | |
| for _, jsonPath := range defaultInputs { | |
| if !strings.HasSuffix(jsonPath, ".json") { | |
| panic(jsonPath) | |
| } | |
| txtPath := jsonPath[:len(jsonPath)-len(".json")] + ".txt" | |
| out, err := os.Create(txtPath) | |
| if err != nil { | |
| fmt.Fprintf(os.Stderr, "Error opening output %s: %s\n", txtPath, err) | |
| os.Exit(1) | |
| } | |
| defer out.Close() | |
| if err := convertWycheproof(out, jsonPath); err != nil { | |
| fmt.Fprintf(os.Stderr, "Error converting %s: %s\n", jsonPath, err) | |
| os.Exit(1) | |
| } | |
| } | |
| case 2: | |
| if err := convertWycheproof(os.Stdout, os.Args[1]); err != nil { | |
| fmt.Fprintf(os.Stderr, "Error converting %s: %s\n", os.Args[1], err) | |
| os.Exit(1) | |
| } | |
| default: | |
| fmt.Fprintf(os.Stderr, "Usage: %s [input JSON]\n", os.Args[0]) | |
| os.Exit(1) | |
| } | |
| } |