-
Notifications
You must be signed in to change notification settings - Fork 17.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
crypto/hmac: move implementation to crypto/internal/fips
For #69536 Change-Id: I38508a8de4ac321554a2c12ac70bcf9e25fad1aa Reviewed-on: https://go-review.googlesource.com/c/go/+/616636 LUCI-TryBot-Result: Go LUCI <[email protected]> Auto-Submit: Filippo Valsorda <[email protected]> Reviewed-by: Roland Shoemaker <[email protected]> Reviewed-by: Michael Pratt <[email protected]>
- Loading branch information
1 parent
8eeac50
commit fdf6605
Showing
4 changed files
with
206 additions
and
130 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
// Copyright 2024 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package fips | ||
|
||
import "io" | ||
|
||
// Hash is the common interface implemented by all hash functions. It is a copy | ||
// of [hash.Hash] from the standard library, to avoid depending on security | ||
// definitions from outside of the module. | ||
type Hash interface { | ||
// Write (via the embedded io.Writer interface) adds more data to the | ||
// running hash. It never returns an error. | ||
io.Writer | ||
|
||
// Sum appends the current hash to b and returns the resulting slice. | ||
// It does not change the underlying hash state. | ||
Sum(b []byte) []byte | ||
|
||
// Reset resets the Hash to its initial state. | ||
Reset() | ||
|
||
// Size returns the number of bytes Sum will return. | ||
Size() int | ||
|
||
// BlockSize returns the hash's underlying block size. | ||
// The Write method must be able to accept any amount | ||
// of data, but it may operate more efficiently if all writes | ||
// are a multiple of the block size. | ||
BlockSize() int | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
// Copyright 2009 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
// Package hmac implements HMAC according to [FIPS 198-1]. | ||
// | ||
// [FIPS 198-1]: https://doi.org/10.6028/NIST.FIPS.198-1 | ||
package hmac | ||
|
||
import ( | ||
"crypto/internal/fips" | ||
"crypto/internal/fips/sha256" | ||
"crypto/internal/fips/sha512" | ||
) | ||
|
||
// key is zero padded to the block size of the hash function | ||
// ipad = 0x36 byte repeated for key length | ||
// opad = 0x5c byte repeated for key length | ||
// hmac = H([key ^ opad] H([key ^ ipad] text)) | ||
|
||
// marshalable is the combination of encoding.BinaryMarshaler and | ||
// encoding.BinaryUnmarshaler. Their method definitions are repeated here to | ||
// avoid a dependency on the encoding package. | ||
type marshalable interface { | ||
MarshalBinary() ([]byte, error) | ||
UnmarshalBinary([]byte) error | ||
} | ||
|
||
type HMAC struct { | ||
opad, ipad []byte | ||
outer, inner fips.Hash | ||
|
||
// If marshaled is true, then opad and ipad do not contain a padded | ||
// copy of the key, but rather the marshaled state of outer/inner after | ||
// opad/ipad has been fed into it. | ||
marshaled bool | ||
} | ||
|
||
func (h *HMAC) Sum(in []byte) []byte { | ||
origLen := len(in) | ||
in = h.inner.Sum(in) | ||
|
||
if h.marshaled { | ||
if err := h.outer.(marshalable).UnmarshalBinary(h.opad); err != nil { | ||
panic(err) | ||
} | ||
} else { | ||
h.outer.Reset() | ||
h.outer.Write(h.opad) | ||
} | ||
h.outer.Write(in[origLen:]) | ||
return h.outer.Sum(in[:origLen]) | ||
} | ||
|
||
func (h *HMAC) Write(p []byte) (n int, err error) { | ||
return h.inner.Write(p) | ||
} | ||
|
||
func (h *HMAC) Size() int { return h.outer.Size() } | ||
func (h *HMAC) BlockSize() int { return h.inner.BlockSize() } | ||
|
||
func (h *HMAC) Reset() { | ||
if h.marshaled { | ||
if err := h.inner.(marshalable).UnmarshalBinary(h.ipad); err != nil { | ||
panic(err) | ||
} | ||
return | ||
} | ||
|
||
h.inner.Reset() | ||
h.inner.Write(h.ipad) | ||
|
||
// If the underlying hash is marshalable, we can save some time by saving a | ||
// copy of the hash state now, and restoring it on future calls to Reset and | ||
// Sum instead of writing ipad/opad every time. | ||
// | ||
// We do this on Reset to avoid slowing down the common single-use case. | ||
// | ||
// This is allowed by FIPS 198-1, Section 6: "Conceptually, the intermediate | ||
// results of the compression function on the B-byte blocks (K0 ⊕ ipad) and | ||
// (K0 ⊕ opad) can be precomputed once, at the time of generation of the key | ||
// K, or before its first use. These intermediate results can be stored and | ||
// then used to initialize H each time that a message needs to be | ||
// authenticated using the same key. [...] These stored intermediate values | ||
// shall be treated and protected in the same manner as secret keys." | ||
marshalableInner, innerOK := h.inner.(marshalable) | ||
if !innerOK { | ||
return | ||
} | ||
marshalableOuter, outerOK := h.outer.(marshalable) | ||
if !outerOK { | ||
return | ||
} | ||
|
||
imarshal, err := marshalableInner.MarshalBinary() | ||
if err != nil { | ||
return | ||
} | ||
|
||
h.outer.Reset() | ||
h.outer.Write(h.opad) | ||
omarshal, err := marshalableOuter.MarshalBinary() | ||
if err != nil { | ||
return | ||
} | ||
|
||
// Marshaling succeeded; save the marshaled state for later | ||
h.ipad = imarshal | ||
h.opad = omarshal | ||
h.marshaled = true | ||
} | ||
|
||
// New returns a new HMAC hash using the given [fips.Hash] type and key. | ||
func New[H fips.Hash](h func() H, key []byte) *HMAC { | ||
hm := new(HMAC) | ||
hm.outer = h() | ||
hm.inner = h() | ||
unique := true | ||
func() { | ||
defer func() { | ||
// The comparison might panic if the underlying types are not comparable. | ||
_ = recover() | ||
}() | ||
if hm.outer == hm.inner { | ||
unique = false | ||
} | ||
}() | ||
if !unique { | ||
panic("crypto/hmac: hash generation function does not produce unique values") | ||
} | ||
setServiceIndicator(hm.outer, key) | ||
blocksize := hm.inner.BlockSize() | ||
hm.ipad = make([]byte, blocksize) | ||
hm.opad = make([]byte, blocksize) | ||
if len(key) > blocksize { | ||
// If key is too big, hash it. | ||
hm.outer.Write(key) | ||
key = hm.outer.Sum(nil) | ||
} | ||
copy(hm.ipad, key) | ||
copy(hm.opad, key) | ||
for i := range hm.ipad { | ||
hm.ipad[i] ^= 0x36 | ||
} | ||
for i := range hm.opad { | ||
hm.opad[i] ^= 0x5c | ||
} | ||
hm.inner.Write(hm.ipad) | ||
|
||
return hm | ||
} | ||
|
||
func setServiceIndicator(h fips.Hash, key []byte) { | ||
// Per FIPS 140-3 IG C.M, key lengths below 112 bits are only allowed for | ||
// legacy use (i.e. verification only) and we don't support that. | ||
if len(key) < 112/8 { | ||
return | ||
} | ||
|
||
switch h.(type) { | ||
case *sha256.Digest, *sha512.Digest: | ||
// TODO(fips): SHA-3 | ||
default: | ||
return | ||
} | ||
|
||
// TODO(fips): set service indicator. | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters