Files
opencloud/vendor/github.com/open-policy-agent/opa/bundle/hash.go
2023-04-19 20:24:34 +02:00

142 lines
3.2 KiB
Go

// 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"))
}