-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
hash.go
141 lines (121 loc) · 3.22 KB
/
hash.go
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
// Copyright 2020 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
package bundle
import (
"bytes"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"encoding/json"
"fmt"
"hash"
"io"
"sort"
"strings"
)
// HashingAlgorithm represents a subset of hashing algorithms implemented in Go
type HashingAlgorithm string
// Supported values for HashingAlgorithm
const (
MD5 HashingAlgorithm = "MD5"
SHA1 HashingAlgorithm = "SHA-1"
SHA224 HashingAlgorithm = "SHA-224"
SHA256 HashingAlgorithm = "SHA-256"
SHA384 HashingAlgorithm = "SHA-384"
SHA512 HashingAlgorithm = "SHA-512"
SHA512224 HashingAlgorithm = "SHA-512-224"
SHA512256 HashingAlgorithm = "SHA-512-256"
)
// String returns the string representation of a HashingAlgorithm
func (alg HashingAlgorithm) String() string {
return string(alg)
}
// SignatureHasher computes a signature digest for a file with (structured or unstructured) data and policy
type SignatureHasher interface {
HashFile(v interface{}) ([]byte, error)
}
type hasher struct {
h func() hash.Hash // hash function factory
}
// NewSignatureHasher returns a signature hasher suitable for a particular hashing algorithm
func NewSignatureHasher(alg HashingAlgorithm) (SignatureHasher, error) {
h := &hasher{}
switch alg {
case MD5:
h.h = md5.New
case SHA1:
h.h = sha1.New
case SHA224:
h.h = sha256.New224
case SHA256:
h.h = sha256.New
case SHA384:
h.h = sha512.New384
case SHA512:
h.h = sha512.New
case SHA512224:
h.h = sha512.New512_224
case SHA512256:
h.h = sha512.New512_256
default:
return nil, fmt.Errorf("unsupported hashing algorithm: %s", alg)
}
return h, nil
}
// HashFile hashes the file content, JSON or binary, both in golang native format.
func (h *hasher) HashFile(v interface{}) ([]byte, error) {
hf := h.h()
walk(v, hf)
return hf.Sum(nil), nil
}
// walk hashes the file content, JSON or binary, both in golang native format.
//
// Computation for unstructured documents is a hash of the document.
//
// Computation for the types of structured JSON document is as follows:
//
// object: Hash {, then each key (in alphabetical order) and digest of the value, then comma (between items) and finally }.
//
// array: Hash [, then digest of the value, then comma (between items) and finally ].
func walk(v interface{}, h io.Writer) {
switch x := v.(type) {
case map[string]interface{}:
_, _ = h.Write([]byte("{"))
var keys []string
for k := range x {
keys = append(keys, k)
}
sort.Strings(keys)
for i, key := range keys {
if i > 0 {
_, _ = h.Write([]byte(","))
}
_, _ = h.Write(encodePrimitive(key))
_, _ = h.Write([]byte(":"))
walk(x[key], h)
}
_, _ = h.Write([]byte("}"))
case []interface{}:
_, _ = h.Write([]byte("["))
for i, e := range x {
if i > 0 {
_, _ = h.Write([]byte(","))
}
walk(e, h)
}
_, _ = h.Write([]byte("]"))
case []byte:
_, _ = h.Write(x)
default:
_, _ = h.Write(encodePrimitive(x))
}
}
func encodePrimitive(v interface{}) []byte {
var buf bytes.Buffer
encoder := json.NewEncoder(&buf)
encoder.SetEscapeHTML(false)
_ = encoder.Encode(v)
return []byte(strings.Trim(buf.String(), "\n"))
}