mirror of https://github.com/jetkvm/kvm.git
241 lines
6.0 KiB
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
|
|
}
|