• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1package subprocess
2
3import (
4	"encoding/hex"
5	"encoding/json"
6	"fmt"
7)
8
9// The following structures reflect the JSON of ACVP hash tests. See
10// https://usnistgov.github.io/ACVP/artifacts/draft-celi-acvp-sha-00.html#test_vectors
11
12type hashTestVectorSet struct {
13	Groups []hashTestGroup `json:"testGroups"`
14}
15
16type hashTestGroup struct {
17	ID    uint64 `json:"tgId"`
18	Type  string `json:"testType"`
19	Tests []struct {
20		ID        uint64 `json:"tcId"`
21		BitLength uint64 `json:"len"`
22		MsgHex    string `json:"msg"`
23	} `json:"tests"`
24}
25
26type hashTestGroupResponse struct {
27	ID    uint64             `json:"tgId"`
28	Tests []hashTestResponse `json:"tests"`
29}
30
31type hashTestResponse struct {
32	ID         uint64          `json:"tcId"`
33	DigestHex  string          `json:"md,omitempty"`
34	MCTResults []hashMCTResult `json:"resultsArray,omitempty"`
35}
36
37type hashMCTResult struct {
38	DigestHex string `json:"md"`
39}
40
41// hashPrimitive implements an ACVP algorithm by making requests to the
42// subprocess to hash strings.
43type hashPrimitive struct {
44	// algo is the ACVP name for this algorithm and also the command name
45	// given to the subprocess to hash with this hash function.
46	algo string
47	// size is the number of bytes of digest that the hash produces.
48	size int
49	m    *Subprocess
50}
51
52// hash uses the subprocess to hash msg and returns the digest.
53func (h *hashPrimitive) hash(msg []byte) []byte {
54	result, err := h.m.transact(h.algo, 1, msg)
55	if err != nil {
56		panic("hash operation failed: " + err.Error())
57	}
58	return result[0]
59}
60
61func (h *hashPrimitive) Process(vectorSet []byte) (interface{}, error) {
62	var parsed hashTestVectorSet
63	if err := json.Unmarshal(vectorSet, &parsed); err != nil {
64		return nil, err
65	}
66
67	var ret []hashTestGroupResponse
68	// See
69	// https://usnistgov.github.io/ACVP/artifacts/draft-celi-acvp-sha-00.html#rfc.section.3
70	// for details about the tests.
71	for _, group := range parsed.Groups {
72		response := hashTestGroupResponse{
73			ID: group.ID,
74		}
75
76		for _, test := range group.Tests {
77			if uint64(len(test.MsgHex))*4 != test.BitLength {
78				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), test.BitLength)
79			}
80			msg, err := hex.DecodeString(test.MsgHex)
81			if err != nil {
82				return nil, fmt.Errorf("failed to decode hex in test case %d/%d: %s", group.ID, test.ID, err)
83			}
84
85			// http://usnistgov.github.io/ACVP/artifacts/draft-celi-acvp-sha-00.html#rfc.section.3
86			switch group.Type {
87			case "AFT":
88				response.Tests = append(response.Tests, hashTestResponse{
89					ID:        test.ID,
90					DigestHex: hex.EncodeToString(h.hash(msg)),
91				})
92
93			case "MCT":
94				if len(msg) != h.size {
95					return nil, fmt.Errorf("MCT test case %d/%d contains message of length %d but the digest length is %d", group.ID, test.ID, len(msg), h.size)
96				}
97
98				testResponse := hashTestResponse{ID: test.ID}
99
100				buf := make([]byte, 3*h.size)
101				var digest []byte
102				for i := 0; i < 100; i++ {
103					copy(buf, msg)
104					copy(buf[h.size:], msg)
105					copy(buf[2*h.size:], msg)
106					for j := 0; j < 1000; j++ {
107						digest = h.hash(buf)
108						copy(buf, buf[h.size:])
109						copy(buf[2*h.size:], digest)
110					}
111
112					testResponse.MCTResults = append(testResponse.MCTResults, hashMCTResult{hex.EncodeToString(digest)})
113					msg = digest
114				}
115
116				response.Tests = append(response.Tests, testResponse)
117
118			default:
119				return nil, fmt.Errorf("test group %d has unknown type %q", group.ID, group.Type)
120			}
121		}
122
123		ret = append(ret, response)
124	}
125
126	return ret, nil
127}
128