1// Copyright 2019 The BoringSSL Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     https://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package subprocess
16
17import (
18	"encoding/hex"
19	"encoding/json"
20	"fmt"
21	"strconv"
22)
23
24// The following structures reflect the JSON of ACVP HMAC tests. See
25// https://pages.nist.gov/ACVP/draft-fussell-acvp-mac.html#name-test-vectors
26
27type hmacTestVectorSet struct {
28	Groups []hmacTestGroup `json:"testGroups"`
29}
30
31type hmacTestGroup struct {
32	ID      uint64 `json:"tgId"`
33	Type    string `json:"testType"`
34	MsgBits int    `json:"msgLen"`
35	KeyBits int    `json:"keyLen"` // maximum possible value is 524288
36	MACBits int    `json:"macLen"` // maximum possible value is 512
37	Tests   []struct {
38		ID     uint64 `json:"tcId"`
39		KeyHex string `json:"key"`
40		MsgHex string `json:"msg"`
41	} `json:"tests"`
42}
43
44type hmacTestGroupResponse struct {
45	ID    uint64             `json:"tgId"`
46	Tests []hmacTestResponse `json:"tests"`
47}
48
49type hmacTestResponse struct {
50	ID     uint64 `json:"tcId"`
51	MACHex string `json:"mac,omitempty"`
52}
53
54// hmacPrimitive implements an ACVP algorithm by making requests to the
55// subprocess to HMAC strings with the given key.
56type hmacPrimitive struct {
57	// algo is the ACVP name for this algorithm and also the command name
58	// given to the subprocess to HMAC with this hash function.
59	algo  string
60	mdLen int // mdLen is the number of bytes of output that the underlying hash produces.
61}
62
63// hmac uses the subprocess to compute HMAC and returns the result.
64func (h *hmacPrimitive) hmac(msg []byte, key []byte, outBits int, m Transactable) []byte {
65	if outBits%8 != 0 {
66		panic("fractional-byte output length requested: " + strconv.Itoa(outBits))
67	}
68	outBytes := outBits / 8
69	result, err := m.Transact(h.algo, 1, msg, key)
70	if err != nil {
71		panic("HMAC operation failed: " + err.Error())
72	}
73	if l := len(result[0]); l < outBytes {
74		panic(fmt.Sprintf("HMAC result too short: %d bytes but wanted %d", l, outBytes))
75	}
76	return result[0][:outBytes]
77}
78
79func (h *hmacPrimitive) Process(vectorSet []byte, m Transactable) (any, error) {
80	var parsed hmacTestVectorSet
81	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
82		return nil, err
83	}
84
85	var ret []hmacTestGroupResponse
86	// See
87	// https://pages.nist.gov/ACVP/draft-fussell-acvp-mac.html#name-test-vectors
88	// for details about the tests.
89	for _, group := range parsed.Groups {
90		group := group
91		response := hmacTestGroupResponse{
92			ID: group.ID,
93		}
94		if group.MACBits > h.mdLen*8 {
95			return nil, fmt.Errorf("test group %d specifies MAC length should be %d, but maximum possible length is %d", group.ID, group.MACBits, h.mdLen*8)
96		}
97		if group.MACBits%8 != 0 {
98			return nil, fmt.Errorf("fractional-byte HMAC output length requested: %d", group.MACBits)
99		}
100		outBytes := group.MACBits / 8
101
102		for _, test := range group.Tests {
103			test := test
104
105			if len(test.MsgHex)*4 != group.MsgBits {
106				return nil, fmt.Errorf("test case %d/%d contains hex message of length %d but specifies a bit length of %d", group.ID, test.ID, len(test.MsgHex), group.MsgBits)
107			}
108			msg, err := hex.DecodeString(test.MsgHex)
109			if err != nil {
110				return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err)
111			}
112
113			if len(test.KeyHex)*4 != group.KeyBits {
114				return nil, fmt.Errorf("test case %d/%d contains hex key of length %d but specifies a bit length of %d", group.ID, test.ID, len(test.KeyHex), group.KeyBits)
115			}
116			key, err := hex.DecodeString(test.KeyHex)
117			if err != nil {
118				return nil, fmt.Errorf("failed to decode key in test case %d/%d: %s", group.ID, test.ID, err)
119			}
120
121			m.TransactAsync(h.algo, 1, [][]byte{msg, key}, func(result [][]byte) error {
122				if l := len(result[0]); l != outBytes {
123					return fmt.Errorf("incorrect HMAC length: %d bytes but wanted %d", l, outBytes)
124				}
125
126				// https://pages.nist.gov/ACVP/draft-fussell-acvp-mac.html#name-test-vectors
127				response.Tests = append(response.Tests, hmacTestResponse{
128					ID:     test.ID,
129					MACHex: hex.EncodeToString(result[0]),
130				})
131				return nil
132			})
133		}
134
135		m.Barrier(func() {
136			ret = append(ret, response)
137		})
138	}
139
140	if err := m.Flush(); err != nil {
141		return nil, err
142	}
143
144	return ret, nil
145}
146