| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240 |
- // 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
- }
|