kvm/vendor/github.com/beevik/ntp/auth.go

241 lines
6.0 KiB
Go

// Copyright © 2015-2023 Brett Vickers.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package ntp
import (
"bytes"
"crypto/aes"
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"crypto/sha512"
"crypto/subtle"
"encoding/binary"
"encoding/hex"
)
// AuthType specifies the cryptographic hash algorithm used to generate a
// symmetric key authentication digest (or CMAC) for an NTP message. Please
// note that MD5 and SHA1 are no longer considered secure; they appear here
// solely for compatibility with existing NTP server implementations.
type AuthType int
const (
AuthNone AuthType = iota // no authentication
AuthMD5 // MD5 digest
AuthSHA1 // SHA-1 digest
AuthSHA256 // SHA-2 digest (256 bits)
AuthSHA512 // SHA-2 digest (512 bits)
AuthAES128 // AES-128-CMAC
AuthAES256 // AES-256-CMAC
)
// AuthOptions contains fields used to configure symmetric key authentication
// for an NTP query.
type AuthOptions struct {
// Type determines the cryptographic hash algorithm used to compute the
// authentication digest or CMAC.
Type AuthType
// The cryptographic key used by the client to perform authentication. The
// key may be hex-encoded or ascii-encoded. To use a hex-encoded key,
// prefix it by "HEX:". To use an ascii-encoded key, prefix it by
// "ASCII:". For example, "HEX:6931564b4a5a5045766c55356b30656c7666316c"
// or "ASCII:cvuZyN4C8HX8hNcAWDWp".
Key string
// The identifier used by the NTP server to identify which key to use
// for authentication purposes.
KeyID uint16
}
var algorithms = []struct {
MinKeySize int
MaxKeySize int
DigestSize int
CalcDigest func(payload, key []byte) []byte
}{
{0, 0, 0, nil}, // AuthNone
{4, 32, 16, calcDigest_MD5}, // AuthMD5
{4, 32, 20, calcDigest_SHA1}, // AuthSHA1
{4, 32, 20, calcDigest_SHA256}, // AuthSHA256
{4, 32, 20, calcDigest_SHA512}, // AuthSHA512
{16, 16, 16, calcCMAC_AES}, // AuthAES128
{32, 32, 16, calcCMAC_AES}, // AuthAES256
}
func calcDigest_MD5(payload, key []byte) []byte {
digest := md5.Sum(append(key, payload...))
return digest[:]
}
func calcDigest_SHA1(payload, key []byte) []byte {
digest := sha1.Sum(append(key, payload...))
return digest[:]
}
func calcDigest_SHA256(payload, key []byte) []byte {
digest := sha256.Sum256(append(key, payload...))
return digest[:20]
}
func calcDigest_SHA512(payload, key []byte) []byte {
digest := sha512.Sum512(append(key, payload...))
return digest[:20]
}
func calcCMAC_AES(payload, key []byte) []byte {
// calculate the CMAC according to the algorithm defined in RFC 4493. See
// https://tools.ietf.org/html/rfc4493 for details.
c, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
// Generate subkeys.
const rb = 0x87
k1 := make([]byte, 16)
k2 := make([]byte, 16)
c.Encrypt(k1, k1)
double(k1, k1, rb)
double(k2, k1, rb)
// Process all but the last block.
cmac := make([]byte, 16)
for ; len(payload) > 16; payload = payload[16:] {
xor(cmac, payload[:16])
c.Encrypt(cmac, cmac)
}
// Process the last block, padding as necessary.
if len(payload) == 16 {
xor(cmac, payload)
xor(cmac, k1)
} else {
xor(cmac, pad(payload))
xor(cmac, k2)
}
c.Encrypt(cmac, cmac)
return cmac
}
func pad(block []byte) []byte {
pad := make([]byte, 16-len(block))
pad[0] = 0x80
return append(block, pad...)
}
func double(dst, src []byte, xor int) {
_ = src[15] // compiler hint: bounds check
s0 := binary.BigEndian.Uint64(src[0:8])
s1 := binary.BigEndian.Uint64(src[8:16])
carry := int(s0 >> 63)
d0 := (s0 << 1) | (s1 >> 63)
d1 := (s1 << 1) ^ uint64(subtle.ConstantTimeSelect(carry, xor, 0))
_ = dst[15] // compiler hint: bounds check
binary.BigEndian.PutUint64(dst[0:8], d0)
binary.BigEndian.PutUint64(dst[8:16], d1)
}
func xor(dst, src []byte) {
_ = src[15] // compiler hint: bounds check
s0 := binary.BigEndian.Uint64(src[0:8])
s1 := binary.BigEndian.Uint64(src[8:16])
_ = dst[15] // compiler hint: bounds check
d0 := s0 ^ binary.BigEndian.Uint64(dst[0:8])
d1 := s1 ^ binary.BigEndian.Uint64(dst[8:16])
binary.BigEndian.PutUint64(dst[0:8], d0)
binary.BigEndian.PutUint64(dst[8:16], d1)
}
func decodeAuthKey(opt AuthOptions) (key []byte, err error) {
if opt.Type == AuthNone {
return nil, nil
}
var keyIn string
var isHex bool
switch {
case len(opt.Key) >= 4 && opt.Key[:4] == "HEX:":
isHex, keyIn = true, opt.Key[4:]
case len(opt.Key) >= 6 && opt.Key[:6] == "ASCII:":
isHex, keyIn = false, opt.Key[6:]
case len(opt.Key) > 20:
isHex, keyIn = true, opt.Key
default:
isHex, keyIn = false, opt.Key
}
if isHex {
key, err = hex.DecodeString(keyIn)
if err != nil {
return nil, ErrInvalidAuthKey
}
} else {
key = []byte(keyIn)
}
a := algorithms[opt.Type]
if len(key) < a.MinKeySize {
return nil, ErrInvalidAuthKey
}
if len(key) > a.MaxKeySize {
key = key[:a.MaxKeySize]
}
return key, nil
}
func appendMAC(buf *bytes.Buffer, opt AuthOptions, key []byte) {
if opt.Type == AuthNone {
return
}
a := algorithms[opt.Type]
payload := buf.Bytes()
digest := a.CalcDigest(payload, key)
binary.Write(buf, binary.BigEndian, uint32(opt.KeyID))
binary.Write(buf, binary.BigEndian, digest)
}
func verifyMAC(buf []byte, opt AuthOptions, key []byte) error {
if opt.Type == AuthNone {
return nil
}
// Validate that there are enough bytes at the end of the message to
// contain a MAC.
const headerSize = 48
a := algorithms[opt.Type]
macLen := 4 + a.DigestSize
remain := len(buf) - headerSize
if remain < macLen || (remain%4) != 0 {
return ErrAuthFailed
}
// The key ID returned by the server must be the same as the key ID sent
// to the server.
payloadLen := len(buf) - macLen
mac := buf[payloadLen:]
keyID := binary.BigEndian.Uint32(mac[:4])
if keyID != uint32(opt.KeyID) {
return ErrAuthFailed
}
// Calculate and compare digests.
payload := buf[:payloadLen]
digest := a.CalcDigest(payload, key)
if subtle.ConstantTimeCompare(digest, mac[4:]) != 1 {
return ErrAuthFailed
}
return nil
}