[Server-devel] [PATCH 2/2] xs-activation: Add support for rtcreset messages

Daniel Drake dsd at laptop.org
Thu Dec 29 15:55:51 EST 2011


Upon receiving a rtcreset request in xs-activation-tcpserver, the
oat class and the activation signer will generate a RTC timestamp reset
signature and send it to the client. When the client reboots, the server's
current date/time (recorded in rtcreset.sig) will be programmed into the
rtc-timestamp field.

This allows for automated recovery of corrupt or invalid rtc-timestamp
fields: http://wiki.laptop.org/go/RTC_Anti-rollback

It depends on a new olpc-bios-crypto RPM including the make-reset command.

Common code in oat and xs-activation-signer was factored out to prevent
further duplication.
---
 oat.py                     |   91 ++++++++++++++++++++++++++------------------
 xs-activation-signer.py    |   81 +++++++++++++++++++++++++++++++++------
 xs-activation-tcpserver.py |   28 ++++++++++++-
 3 files changed, 148 insertions(+), 52 deletions(-)

diff --git a/oat.py b/oat.py
index 4c5cbaa..bb22a23 100644
--- a/oat.py
+++ b/oat.py
@@ -77,6 +77,33 @@ class oat:
             mdbc.execute(sql, {'timenow': int(time.time()), 'sn': sn})
             mdbh.commit()
 
+    def get_signed_output(self, fname):
+        """
+        Watch for output file from signing helper and return its contents, or
+        None if a timeout occurred. The output file is automatically deleted.
+        """
+
+        destpath = '/var/lib/xs-activation/done/' + fname
+        output = None
+
+        # wait for up to X seconds
+        timestart = time.time()
+        while (time.time() - timestart) < 3:
+            if not os.path.exists(destpath):
+                time.sleep(0.20)
+                continue
+            try:
+                fh = open(destpath)
+                output = fh.read()
+                fh.close()
+                os.unlink(destpath)
+                break
+            except:
+                self.log_error("Error handling response from signer script")
+                break
+
+        return output
+
     def get_rescue_leases(self):
         """Note: will write (potentially lots) of cjson formatted stuff to to STDOUT"""
         # get SNs of stolen machines
@@ -118,25 +145,12 @@ class oat:
         os.close(fh)
         os.rename(tmpfpath, '/var/lib/xs-activation/req/' + fname)
 
-        destpath = '/var/lib/xs-activation/done/' + fname
-        # wait for up to X seconds
-        timestart = time.time()
-        while (time.time() - timestart) < 3:
-            if not os.path.exists(destpath):
-                time.sleep(0.20)
-                continue
-            try:
-                fh = open(destpath)
-                for line in fh:
-                    sys.stdout.write(line)
-                fh.close()
-                os.unlink(destpath)
-                break
-            except:
-                self.log_error("Error handling response from signer script")
-                return False
-
-        return True
+        output = self.get_signed_output(fname)
+        if output:
+            sys.stdout.write(output)
+            return True
+        else:
+            return False
 
 
     def in_list(self, haystack, needle):
@@ -241,7 +255,13 @@ class oat:
 
         return response
 
-
+    def get_rtcreset(self, sn, currentrtc, nonce):
+        # attempt to build a sig02 delegated rtcreset
+        kpath        = self.get_key_path()
+        ldpath = self.get_lease_delegation_path(sn)
+        if kpath and ldpath:
+            newrtc = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
+            return self.generate_delegated_rtcreset(sn, currentrtc, nonce, newrtc)
 
     def mdb_available(self):
         """Check if it's available - so that we can serve
@@ -297,29 +317,26 @@ class oat:
         cmd = ['/bin/touch', reqpath ] 
         subprocess.check_call(cmd)
 
-        destpath = '/var/lib/xs-activation/done/' + fname
-        # wait for up to X seconds
-        timestart = time.time()
-        lease = None
-        while (time.time() - timestart) < 3:
-            if not os.path.exists(destpath):
-                time.sleep(0.20)
-                continue
-            try:
-                fh = open(destpath)
-                lease = fh.read()
-                fh.close()
-                os.unlink(destpath)
-                break
-            except:
-                self.log_error("Error handling response from signer script")
-                break
+        lease = self.get_signed_output(fname)
         if lease == None:
             self.log_error("Timed out waiting for signed response")
             raise RuntimeError("Timed out waiting for signed response")
 
         return lease;
 
+    def generate_delegated_rtcreset(self, sn, currentrtc, nonce, newrtc):
+        fname = "rtc01delegated_%s_%s_%s_%s_%s" % (sn, currentrtc, nonce, newrtc, hexlify(os.urandom(8)))
+        reqpath = '/var/lib/xs-activation/req/' + fname
+        cmd = ['/bin/touch', reqpath ]
+        subprocess.check_call(cmd)
+
+        rtcreset = self.get_signed_output(fname)
+        if rtcreset == None:
+            self.log_error("Timed out waiting for signed response")
+            raise RuntimeError("Timed out waiting for signed response")
+
+        return rtcreset
+
     def sign_sig02(self, sn, absexpiry, str):
         """Make a delegated OAT signature on an arbitrary bit of content"""
 
diff --git a/xs-activation-signer.py b/xs-activation-signer.py
index 9700b8e..46ccec2 100755
--- a/xs-activation-signer.py
+++ b/xs-activation-signer.py
@@ -148,23 +148,15 @@ def serve_delegated_lease(dirpath, fname, fpath, params):
     destpath = '/var/lib/xs-activation/done/' + fname
     save_atomically(destpath, lease)
 
-def generate_delegated_lease(sn, expiry):
-
-    myoat = oat.oat()
-
-    ldpath = myoat.get_lease_delegation_path(sn)
-    if not ldpath:
-        log_error("No lease deleation for " + sn)
-        exit(1)
-
-    ld = open(ldpath)
+def parse_lease_delegation(path, sn):
+    ld = open(path)
     ldstr  = ld.read()
     ld.close()
 
     # check
     if not ('del01:' == ldstr[0:6]
             and sn   == ldstr[7:18]):
-        log_error("Strange delegation header in " + ldpath)
+        log_error("Strange delegation header in " + path)
         exit(1)
     ldstr = ldstr[19:]
 
@@ -177,7 +169,21 @@ def generate_delegated_lease(sn, expiry):
     # delegation file that the script expects
     ldstr = ldstr[len(uuid)+1:]
 
-    # write it to a tmpfile
+    return uuid, ldstr
+
+def generate_delegated_lease(sn, expiry):
+
+    myoat = oat.oat()
+
+    ldpath = myoat.get_lease_delegation_path(sn)
+    if not ldpath:
+        log_error("No lease deleation for " + sn)
+        exit(1)
+
+    # extract UUID and delegation
+    uuid, ldstr = parse_lease_delegation(ldpath, sn)
+
+    # write delegation to a tmpfile
     (dfh, dfpath) = tempfile.mkstemp()        
     os.write(dfh, ldstr)
     os.close(dfh)
@@ -219,6 +225,55 @@ def generate_multiple_delegated_leases(dirpath, fname, fpath, params):
     destpath = '/var/lib/xs-activation/done/' + fname
     save_atomically(destpath, cjson.write([1,leases]))
 
+def serve_delegated_rtcreset(dirpath, fname, fpath, params):
+    # remove empty/unused file
+    os.unlink(fpath)
+
+    randid = params.pop()
+    newrtc = params.pop();
+    nonce = params.pop();
+    currentrtc = params.pop();
+
+    sn = params.pop();
+    if not validate_sn(sn):
+        raise RuntimeError('Invalid SN')
+
+    if len(currentrtc) != 16 or currentrtc[15] != 'Z' or currentrtc[8] != 'T':
+        log_error("Unrecognised rtcreset timestamp")
+        exit(1)
+
+    if not nonce.isdigit():
+        log_error("Unrecognised rtcreset nonce")
+        exit(1)
+
+    # find uuid and delegation
+    myoat = oat.oat()
+
+    ldpath = myoat.get_lease_delegation_path(sn)
+    if not ldpath:
+        log_error("No lease delegation for " + sn)
+        exit(1)
+
+    # extract UUID and delegation
+    uuid, ldstr = parse_lease_delegation(ldpath, sn)
+
+    # write delegation to a tmpfile
+    (dfh, dfpath) = tempfile.mkstemp()
+    os.write(dfh, ldstr)
+    os.close(dfh)
+
+    # prep params
+    cmd = ['/usr/bin/obc-make-rtcreset',
+           '--chain', dfpath,
+           '--signingkey', kpath,
+           sn, uuid, currentrtc, nonce, newrtc]
+    rtcreset = subprocess.Popen(cmd,
+                                stdout=subprocess.PIPE).communicate()[0]
+    os.unlink(dfpath)
+
+    destpath = '/var/lib/xs-activation/done/' + fname
+    save_atomically(destpath, rtcreset)
+
 def cleanup(fpath):
     try:
         os.unlink(fpath)
@@ -268,6 +323,8 @@ try:
         serve_delegated_lease(dirpath, fname, fpath, params)
     elif cmd == 'multiact01':
         generate_multiple_delegated_leases(dirpath, fname, fpath, params)
+    elif cmd == 'rtc01delegated':
+        serve_delegated_rtcreset(dirpath, fname, fpath, params)
     else:
         log_error('unknown command ' + cmd)
         exit(1)
diff --git a/xs-activation-tcpserver.py b/xs-activation-tcpserver.py
index 942eaf1..5556cb5 100755
--- a/xs-activation-tcpserver.py
+++ b/xs-activation-tcpserver.py
@@ -31,9 +31,10 @@ def log_error(str):
 
 # handle input - we are expecting
 #  - a 11 char-long serial number
-#  - the string of no more than 10 characters, followed by
-#    a ':' and a space, followed by additional data
-#    for example - 'time01: serialnum'
+#  - the string "time01: " followed by a 11-character serial number, a space,
+#    and a 22 character nonce
+#  - the string "rtcreset " followed by 3 space separated parameters: serial
+#    number, current rtc-timestamp value, current rtc-count nonce
 # there's no newline expected
 
 def check_stolen(sn):
@@ -92,6 +93,25 @@ def send_time(req):
     else:
         sys.stderr.write("Could not serve time request for %s - probable cause: no lease available\n" % sn)
 
+def send_rtcreset(req):
+    # remove rtcreset prefix
+    req = req[9:]
+    params = req.strip().split(' ')
+    if len(params) != 3:
+        log_error("Insufficient pararameters for rtcreset request")
+        exit(1)
+
+    sn = params[0]
+    currentrtc = params[1]
+    nonce = params[2]
+
+    msg = myoat.get_rtcreset(sn, currentrtc, nonce)
+    if msg:
+        sys.stdout.write(msg)
+        exit()
+    else:
+        sys.stderr.write("Could not serve rtcreset request for %s - probable cause: no delegation available\n" % sn)
+
 ######## Main ###########
 
 # wait for data to arrive
@@ -104,6 +124,8 @@ if re.match('^[A-Z0-9]{11}$', req):
     send_lease(req)
 elif req.startswith('time01: '):
     send_time(req)
+elif req.startswith('rtcreset '):
+    send_rtcreset(req)
 else:
     # unknown command? log up to 48 chars to minimize chance of DoS flood
     log_error("unknown request: " + req[:48])
-- 
1.7.7.4



More information about the Server-devel mailing list