[Git][NTPsec/ntpsec][master] 2 commits: Response MAC verification is working in pyntpdig.

Eric S. Raymond gitlab at mg.gitlab.com
Thu Nov 10 01:24:19 UTC 2016


Eric S. Raymond pushed to branch master at NTPsec / ntpsec


Commits:
abe8d3f4 by Eric S. Raymond at 2016-11-09T11:15:11-05:00
Response MAC verification is working in pyntpdig.

- - - - -
e1046e8d by Eric S. Raymond at 2016-11-09T20:24:05-05:00
In packet.py, clarity about the differences between modes 0-5 and 6

- - - - -


1 changed file:

- pylib/packet.py


Changes:

=====================================
pylib/packet.py
=====================================
--- a/pylib/packet.py
+++ b/pylib/packet.py
@@ -1,14 +1,100 @@
-#
-# packet.py - definitions and classes for Python querying of NTP
-#
-# Freely translated from the old C ntpq code by ESR, with comments
-# preserved.  The idea was to cleanly separate ntpq-that-was into a
-# thin front-end layer handling mainly command interpretation and a
-# back-end that presents the take from ntpd as objects that can be
-# re-used by other front ends. Other reusable pieces live in util.py.
-#
-# This code should be Python2-vs-Python-3 agnostic.  Keep it that way!
-#
+"""
+packet.py - definitions and classes for Python querying of NTP
+
+Freely translated from the old C ntpq code by ESR, with comments
+preserved.  The idea was to cleanly separate ntpq-that-was into a
+thin front-end layer handling mainly command interpretation and a
+back-end that presents the take from ntpd as objects that can be
+re-used by other front ends. Other reusable pieces live in util.py.
+
+This code should be Python2-vs-Python-3 agnostic.  Keep it that way!
+
+Here are some pictures to help make sense of this code. First, from RFC 5905,
+the general structure of an NTP packet (Figure 8):
+
+       0                   1                   2                   3
+       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |LI | VN  |Mode |    Stratum     |     Poll      |  Precision   |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                         Root Delay                            |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                         Root Dispersion                       |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                          Reference ID                         |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                                                               |
+      +                     Reference Timestamp (64)                  +
+      |                                                               |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                                                               |
+      +                      Origin Timestamp (64)                    +
+      |                                                               |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                                                               |
+      +                      Receive Timestamp (64)                   +
+      |                                                               |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                                                               |
+      +                      Transmit Timestamp (64)                  +
+      |                                                               |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                                                               |
+      .                                                               .
+      .                    Extension Field 1 (variable)               .
+      .                                                               .
+      |                                                               |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                                                               |
+      .                                                               .
+      .                    Extension Field 2 (variable)               .
+      .                                                               .
+      |                                                               |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                          Key Identifier                       |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                                                               |
+      |                            dgst (128)                         |
+      |                                                               |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+The fixed header is 48 bytes long.  The simplest possible case of an
+NTP packet is the minimal SNTP request, a mode 3 packet with the
+Stratum and all following fields zeroed out to byte 47.
+
+Here's what a Mode 6 packet looks like
+
+       0                   1                   2                   3
+       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |LI | VN  | 6   |R|E|M|  Opcode  |          Sequence            |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |               Status           |       Association ID         |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |               Offset           |            Count             |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                                                               |
+      .                                                               .
+      .                        Payload (variable)                     .
+      .                                                               .
+      |                                                               |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                          Key Identifier                       |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+      |                                                               |
+      |                            dgst (128)                         |
+      |                                                               |
+      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+
+In this case the fixed header is 24 bytes long.
+
+R = Response bit
+E = Error bit
+M = More bit.
+
+A Mode 6 packet cannot have extension fields.
+
+"""
 # SPDX-License-Identifier: BSD-2-clause
 from __future__ import print_function, division
 import sys, socket, select, struct, curses.ascii, collections
@@ -143,12 +229,9 @@ class Packet:
         self.session = session  # Where to get session context
         self.li_vn_mode = 0     # leap, version, mode (uint8_t)
         self.r_e_m_op = 0       # response, error, more, opcode (uint8_t)
-        # Subclasses have four uint16_t fields here
-        self.count = 0          # octet count of extension data
+        # Subclasses have variable fields here
         self.extension = b''     # extension data
         self.li_vn_mode = Packet.PKT_LI_VN_MODE(0, version, mode)
-    format = "!BBHHHHH"
-    HEADER_LEN = 12
 
     # These decorators will allow us to assign the extension Python 3 strings
     @property
@@ -158,29 +241,9 @@ class Packet:
     def extension(self, x):
         self.__extension = polybytes(x)
 
-    def flatten(self, payload1, payload2, payload3, payload4):
-        "Flatten the packet into an octet sequence."
-        body = struct.pack(Packet.format,
-                             self.li_vn_mode,
-                             self.r_e_m_op,
-                             payload1, payload2, payload3, payload4,
-                             self.count)
-        return body + self.extension
-
-    def analyze(self, rawdata):
-        (self.li_vn_mode,
-         self.r_e_m_op,
-         payload1, payload2, payload3, payload4,
-         self.count) = struct.unpack(Packet.format, rawdata[:Packet.HEADER_LEN])
-        self.data = rawdata[Packet.HEADER_LEN:]
-        return (payload1, payload2, payload3, payload4)
-
     def mode(self):
         return self.li_vn_mode & 0x7
 
-    def end(self):
-        return self.count + self.offset
-
     def version(self):
         return (self.li_vn_mode >> 3) & 0x7
 
@@ -194,8 +257,11 @@ class Mode6Packet(Packet):
         self.status = 0         # status word for association (uint16_t)
         self.associd = associd  # association ID (uint16_t)
         self.offset = 0         # offset of this batch of data (uint16_t)
+        self.count = 0          # octet count of extension data
         self.extension = qdata  # Data for this packet
         self.count = len(qdata)	# length of data
+    format = "!BBHHHHH"
+    HEADER_LEN = 12
 
     def is_response(self):
         return self.r_e_m_op & 0x80
@@ -212,26 +278,40 @@ class Mode6Packet(Packet):
     def errcode(self):
         return (self.status >> 8) & 0xff
 
+    def end(self):
+        return self.count + self.offset
+
     def stats(self, idx):
         "Return statistics on a fragment."
         return "%5d %5d\t%3d octets\n" % (self.offset, self.end(), self.count)
 
+    def analyze(self, rawdata):
+        (self.li_vn_mode,
+         self.r_e_m_op,
+         self.sequence,
+         self.status,
+         self.associd,
+         self.offset,
+         self.count) = struct.unpack(Mode6Packet.format,
+                                     rawdata[:Mode6Packet.HEADER_LEN])
+        self.data = rawdata[Mode6Packet.HEADER_LEN:]
+        return (self.sequence, self.status, self.associd, self.offset)
+
     def flatten(self):
-        return Packet.flatten(self,
-                              self.sequence,
-                              self.status,
-                              self.associd,
-                              self.offset)
+        "Flatten the packet into an octet sequence."
+        body = struct.pack(Mode6Packet.format,
+                           self.li_vn_mode,
+                           self.r_e_m_op,
+                           self.sequence,
+                           self.status,
+                           self.associd,
+                           self.offset,
+                           self.count)
+        return body + self.extension
 
     def send(self):
         self.session.sendpkt(self.flatten())
 
-    def analyze(self, data):
-        (self.sequence,
-         self.status,
-         self.associd,
-         self.offset) = Packet.analyze(self, data)
-
 class Peer:
     "The information we have about an NTP peer."
     def __init__(self, session, associd, status):
@@ -504,7 +584,7 @@ class Mode6Session:
         # If we have data, pad it out to a 32-bit boundary.
         # Do not include these in the payload count.
         if pkt.extension:
-            while ((Packet.HEADER_LEN + len(pkt.extension)) & 3):
+            while ((Mode6Packet.HEADER_LEN + len(pkt.extension)) & 3):
                 pkt.extension += b"\x00"
 
         # If it isn't authenticated we can just send it.  Otherwise
@@ -518,7 +598,7 @@ class Mode6Session:
 	# Pad out packet to a multiple of 8 octets to be sure
 	# receiver can handle it. Note: these pad bytes should
         # *not* be counted in the header count field.
-        while ((Packet.HEADER_LEN + len(pkt.extension)) & 7):
+        while ((Mode6Packet.HEADER_LEN + len(pkt.extension)) & 7):
             pkt.extension += b"\x00"
 
         # Do the MAC compuation.
@@ -634,17 +714,17 @@ class Mode6Session:
                 warn("Response packet not padded, size = %d\n" % len(rawdata))
                 continue
 
-            shouldbesize = (Packet.HEADER_LEN + rpkt.count + 3) & ~3
+            shouldbesize = (Mode6Packet.HEADER_LEN + rpkt.count + 3) & ~3
             if len(rawdata) < shouldbesize:
                 warn("Response packet claims %u octets payload, above %d received\n" % \
-                    (count, len(rawdata) - Packet.HEADER_LEN))
+                    (count, len(rawdata) - Mode6Packet.HEADER_LEN))
                 raise Mode6Exception(SERR_INCOMPLETE)
 
             if self.debug > 1:
                 warn("Got packet, size = %d\n"% len(rawdata))
-            if rpkt.count > (len(rawdata) - Packet.HEADER_LEN):
+            if rpkt.count > (len(rawdata) - Mode6Packet.HEADER_LEN):
                     warn("Received count of %u octets, data in packet is %ld\n"\
-                                   % (count, len(rawdata) - Packet.HEADER_LEN))
+                                   % (count, len(rawdata) - Mode6Packet.HEADER_LEN))
                     continue
 
             # Someday, perhaps, check authentication here
@@ -1043,7 +1123,6 @@ DEFAULT_KEYFILE = "/usr/local/etc/ntp.keys"
 
 class Authenticator:
     "MAC authentication manager for NTP packets."
-    HASHLEN = 20	# True for both MD5 and SHA1 hashes
     def __init__(self, keyfile=None):
         # We allow I/O and permission errors upward deliberately
         self.passwords = {}
@@ -1095,11 +1174,12 @@ class Authenticator:
         return len(packet) > LEN_PKT_NOMAC
     def verify_mac(self, packet):
         "Does the MAC on this packet verify according to credentials we have?"
-        payload = packet[-Authenticator.HASHLEN-4:]
-        keyid = packet[-Authenticator.HASHLEN-4:-Authenticator.HASHLEN]
-        mac = packet[:-Authenticator.HASHLEN]
+        # FIXME: Someday, figure out how to handle SHA1?
+        HASHLEN = 16	# Length of MD5 hash.
+        payload = packet[:-HASHLEN-4]
+        keyid = packet[-HASHLEN-4:-HASHLEN]
+        mac = packet[-HASHLEN:]
         (keyid,) = struct.unpack("!I", keyid)
-        print("I see: %d" % keyid)
         if keyid not in self.passwords:
             return False
         (keytype, passwd) = self.passwords[keyid]



View it on GitLab: https://gitlab.com/NTPsec/ntpsec/compare/2cf7f5b8576f4b10d83672ac86797694afa60b45...e1046e8d59f08ce7dba7ebcd9c06a5f0af557b28
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.ntpsec.org/pipermail/vc/attachments/20161110/0bff34e9/attachment.html>


More information about the vc mailing list