Skip to content

Commit 5a34e35

Browse files
authored
feat: Make SDK FIPS-compliant by using internal SHA1 module (#2179)
The Go standard library has removed support for using SHA1 when in FIPS mode in golang/go@54693a8. However the [NIST FIPS spec](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-131Ar1.pdf) allows for using SHA1 in certain cases: > SHA-1 for non-digital signature applications: > For all other hash function applications, the use of SHA-1 is **acceptable**. The other > applications include HMAC, Key Derivation Functions (KDFs), Random Bit Generation, and > hash-only applications (e.g., hashing passwords and using SHA-1 to compute a checksum, > such as the approved integrity technique specified in Section 4.6.1 of [FIPS 140]). The above removal from the standard lib is too restrictive, as we should be able to use this algo in our SDK for checksums. I've borrowed the implementation of packages `sha1` and `byteorder` and added them as internal modules.
1 parent e1dbd6c commit 5a34e35

File tree

9 files changed

+688
-6
lines changed

9 files changed

+688
-6
lines changed

internal/sha1/byteorder.go

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package sha1
2+
3+
func BEUint32(b []byte) uint32 {
4+
_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
5+
return uint32(b[3]) | uint32(b[2])<<8 | uint32(b[1])<<16 | uint32(b[0])<<24
6+
}
7+
8+
func BEPutUint32(b []byte, v uint32) {
9+
_ = b[3] // early bounds check to guarantee safety of writes below
10+
b[0] = byte(v >> 24)
11+
b[1] = byte(v >> 16)
12+
b[2] = byte(v >> 8)
13+
b[3] = byte(v)
14+
}
15+
16+
func BEAppendUint32(b []byte, v uint32) []byte {
17+
return append(b,
18+
byte(v>>24),
19+
byte(v>>16),
20+
byte(v>>8),
21+
byte(v),
22+
)
23+
}
24+
25+
func BEUint64(b []byte) uint64 {
26+
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
27+
return uint64(b[7]) | uint64(b[6])<<8 | uint64(b[5])<<16 | uint64(b[4])<<24 |
28+
uint64(b[3])<<32 | uint64(b[2])<<40 | uint64(b[1])<<48 | uint64(b[0])<<56
29+
}
30+
31+
func BEPutUint64(b []byte, v uint64) {
32+
_ = b[7] // early bounds check to guarantee safety of writes below
33+
b[0] = byte(v >> 56)
34+
b[1] = byte(v >> 48)
35+
b[2] = byte(v >> 40)
36+
b[3] = byte(v >> 32)
37+
b[4] = byte(v >> 24)
38+
b[5] = byte(v >> 16)
39+
b[6] = byte(v >> 8)
40+
b[7] = byte(v)
41+
}
42+
43+
func BEAppendUint64(b []byte, v uint64) []byte {
44+
return append(b,
45+
byte(v>>56),
46+
byte(v>>48),
47+
byte(v>>40),
48+
byte(v>>32),
49+
byte(v>>24),
50+
byte(v>>16),
51+
byte(v>>8),
52+
byte(v),
53+
)
54+
}

internal/sha1/sha1.go

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
//nolint:revive,errcheck,staticcheck
2+
package sha1
3+
4+
import (
5+
"errors"
6+
"hash"
7+
)
8+
9+
// The size of a SHA-1 checksum in bytes.
10+
const Size = 20
11+
12+
// The blocksize of SHA-1 in bytes.
13+
const BlockSize = 64
14+
15+
const (
16+
chunk = 64
17+
init0 = 0x67452301
18+
init1 = 0xEFCDAB89
19+
init2 = 0x98BADCFE
20+
init3 = 0x10325476
21+
init4 = 0xC3D2E1F0
22+
)
23+
24+
// digest represents the partial evaluation of a checksum.
25+
type digest struct {
26+
h [5]uint32
27+
x [chunk]byte
28+
nx int
29+
len uint64
30+
}
31+
32+
const (
33+
magic = "sha\x01"
34+
marshaledSize = len(magic) + 5*4 + chunk + 8
35+
)
36+
37+
func (d *digest) MarshalBinary() ([]byte, error) {
38+
return d.AppendBinary(make([]byte, 0, marshaledSize))
39+
}
40+
41+
func (d *digest) AppendBinary(b []byte) ([]byte, error) {
42+
b = append(b, magic...)
43+
b = BEAppendUint32(b, d.h[0])
44+
b = BEAppendUint32(b, d.h[1])
45+
b = BEAppendUint32(b, d.h[2])
46+
b = BEAppendUint32(b, d.h[3])
47+
b = BEAppendUint32(b, d.h[4])
48+
b = append(b, d.x[:d.nx]...)
49+
b = append(b, make([]byte, len(d.x)-d.nx)...)
50+
b = BEAppendUint64(b, d.len)
51+
return b, nil
52+
}
53+
54+
func (d *digest) UnmarshalBinary(b []byte) error {
55+
if len(b) < len(magic) || string(b[:len(magic)]) != magic {
56+
return errors.New("crypto/sha1: invalid hash state identifier")
57+
}
58+
if len(b) != marshaledSize {
59+
return errors.New("crypto/sha1: invalid hash state size")
60+
}
61+
b = b[len(magic):]
62+
b, d.h[0] = consumeUint32(b)
63+
b, d.h[1] = consumeUint32(b)
64+
b, d.h[2] = consumeUint32(b)
65+
b, d.h[3] = consumeUint32(b)
66+
b, d.h[4] = consumeUint32(b)
67+
b = b[copy(d.x[:], b):]
68+
b, d.len = consumeUint64(b)
69+
d.nx = int(d.len % chunk)
70+
return nil
71+
}
72+
73+
func consumeUint64(b []byte) ([]byte, uint64) {
74+
return b[8:], BEUint64(b)
75+
}
76+
77+
func consumeUint32(b []byte) ([]byte, uint32) {
78+
return b[4:], BEUint32(b)
79+
}
80+
81+
func (d *digest) Reset() {
82+
d.h[0] = init0
83+
d.h[1] = init1
84+
d.h[2] = init2
85+
d.h[3] = init3
86+
d.h[4] = init4
87+
d.nx = 0
88+
d.len = 0
89+
}
90+
91+
// New returns a new [hash.Hash] computing the SHA1 checksum. The Hash
92+
// also implements [encoding.BinaryMarshaler], [encoding.BinaryAppender] and
93+
// [encoding.BinaryUnmarshaler] to marshal and unmarshal the internal
94+
// state of the hash.
95+
func New() hash.Hash {
96+
d := new(digest)
97+
d.Reset()
98+
return d
99+
}
100+
101+
func (d *digest) Size() int { return Size }
102+
103+
func (d *digest) BlockSize() int { return BlockSize }
104+
105+
func (d *digest) Write(p []byte) (nn int, err error) {
106+
nn = len(p)
107+
d.len += uint64(nn)
108+
if d.nx > 0 {
109+
n := copy(d.x[d.nx:], p)
110+
d.nx += n
111+
if d.nx == chunk {
112+
block(d, d.x[:])
113+
d.nx = 0
114+
}
115+
p = p[n:]
116+
}
117+
if len(p) >= chunk {
118+
n := len(p) &^ (chunk - 1)
119+
block(d, p[:n])
120+
p = p[n:]
121+
}
122+
if len(p) > 0 {
123+
d.nx = copy(d.x[:], p)
124+
}
125+
return
126+
}
127+
128+
func (d *digest) Sum(in []byte) []byte {
129+
// Make a copy of d so that caller can keep writing and summing.
130+
d0 := *d
131+
hash := d0.checkSum()
132+
return append(in, hash[:]...)
133+
}
134+
135+
func (d *digest) checkSum() [Size]byte {
136+
len := d.len
137+
// Padding. Add a 1 bit and 0 bits until 56 bytes mod 64.
138+
var tmp [64 + 8]byte // padding + length buffer
139+
tmp[0] = 0x80
140+
var t uint64
141+
if len%64 < 56 {
142+
t = 56 - len%64
143+
} else {
144+
t = 64 + 56 - len%64
145+
}
146+
147+
// Length in bits.
148+
len <<= 3
149+
padlen := tmp[:t+8]
150+
BEPutUint64(padlen[t:], len)
151+
d.Write(padlen)
152+
153+
if d.nx != 0 {
154+
panic("d.nx != 0")
155+
}
156+
157+
var digest [Size]byte
158+
159+
BEPutUint32(digest[0:], d.h[0])
160+
BEPutUint32(digest[4:], d.h[1])
161+
BEPutUint32(digest[8:], d.h[2])
162+
BEPutUint32(digest[12:], d.h[3])
163+
BEPutUint32(digest[16:], d.h[4])
164+
165+
return digest
166+
}
167+
168+
// ConstantTimeSum computes the same result of [Sum] but in constant time
169+
func (d *digest) ConstantTimeSum(in []byte) []byte {
170+
d0 := *d
171+
hash := d0.constSum()
172+
return append(in, hash[:]...)
173+
}
174+
175+
func (d *digest) constSum() [Size]byte {
176+
var length [8]byte
177+
l := d.len << 3
178+
for i := uint(0); i < 8; i++ {
179+
length[i] = byte(l >> (56 - 8*i))
180+
}
181+
182+
nx := byte(d.nx)
183+
t := nx - 56 // if nx < 56 then the MSB of t is one
184+
mask1b := byte(int8(t) >> 7) // mask1b is 0xFF iff one block is enough
185+
186+
separator := byte(0x80) // gets reset to 0x00 once used
187+
for i := byte(0); i < chunk; i++ {
188+
mask := byte(int8(i-nx) >> 7) // 0x00 after the end of data
189+
190+
// if we reached the end of the data, replace with 0x80 or 0x00
191+
d.x[i] = (^mask & separator) | (mask & d.x[i])
192+
193+
// zero the separator once used
194+
separator &= mask
195+
196+
if i >= 56 {
197+
// we might have to write the length here if all fit in one block
198+
d.x[i] |= mask1b & length[i-56]
199+
}
200+
}
201+
202+
// compress, and only keep the digest if all fit in one block
203+
block(d, d.x[:])
204+
205+
var digest [Size]byte
206+
for i, s := range d.h {
207+
digest[i*4] = mask1b & byte(s>>24)
208+
digest[i*4+1] = mask1b & byte(s>>16)
209+
digest[i*4+2] = mask1b & byte(s>>8)
210+
digest[i*4+3] = mask1b & byte(s)
211+
}
212+
213+
for i := byte(0); i < chunk; i++ {
214+
// second block, it's always past the end of data, might start with 0x80
215+
if i < 56 {
216+
d.x[i] = separator
217+
separator = 0
218+
} else {
219+
d.x[i] = length[i-56]
220+
}
221+
}
222+
223+
// compress, and only keep the digest if we actually needed the second block
224+
block(d, d.x[:])
225+
226+
for i, s := range d.h {
227+
digest[i*4] |= ^mask1b & byte(s>>24)
228+
digest[i*4+1] |= ^mask1b & byte(s>>16)
229+
digest[i*4+2] |= ^mask1b & byte(s>>8)
230+
digest[i*4+3] |= ^mask1b & byte(s)
231+
}
232+
233+
return digest
234+
}
235+
236+
// Sum returns the SHA-1 checksum of the data.
237+
func Sum(data []byte) [Size]byte {
238+
var d digest
239+
d.Reset()
240+
d.Write(data)
241+
return d.checkSum()
242+
}

0 commit comments

Comments
 (0)