Support longer pkcs1-oaep secrets

We have run into a case where we need to store a secret longer
than 3760 bits.  We may eventually support a hybrid encryption
scheme, but for now, let's also support the alt.zuul.secrets protocol
where we split the secret into 3760 bit chunks and recombine it.

The encrypt_secret utility is updated to output a copy-pastable
YAML data structure to simplify dealing with long secrets.

Change-Id: Ied372572e5aa29fddfb7043bf07df4cd3e39566c
diff --git a/tools/encrypt_secret.py b/tools/encrypt_secret.py
index 72429e9..df4f449 100755
--- a/tools/encrypt_secret.py
+++ b/tools/encrypt_secret.py
@@ -14,10 +14,13 @@
 
 import argparse
 import base64
+import math
 import os
+import re
 import subprocess
 import sys
 import tempfile
+import textwrap
 
 # we to import Request and urlopen differently for python 2 and 3
 try:
@@ -68,28 +71,70 @@
     else:
         plaintext = sys.stdin.read()
 
+    plaintext = plaintext.encode("utf-8")
+
     pubkey_file = tempfile.NamedTemporaryFile(delete=False)
     try:
         pubkey_file.write(pubkey.read())
         pubkey_file.close()
 
-        p = subprocess.Popen(['openssl', 'rsautl', '-encrypt',
-                              '-oaep', '-pubin', '-inkey',
+        p = subprocess.Popen(['openssl', 'rsa', '-text',
+                              '-pubin', '-in',
                               pubkey_file.name],
-                             stdin=subprocess.PIPE,
                              stdout=subprocess.PIPE)
-        (stdout, stderr) = p.communicate(plaintext.encode("utf-8"))
+        (stdout, stderr) = p.communicate()
         if p.returncode != 0:
             raise Exception("Return code %s from openssl" % p.returncode)
-        ciphertext = base64.b64encode(stdout)
+        output = stdout.decode('utf-8')
+        m = re.match(r'^Public-Key: \((\d+) bit\)$', output, re.MULTILINE)
+        nbits = int(m.group(1))
+        nbytes = int(nbits / 8)
+        max_bytes = nbytes - 42  # PKCS1-OAEP overhead
+        chunks = int(math.ceil(float(len(plaintext)) / max_bytes))
+
+        ciphertext_chunks = []
+
+        print("Public key length: {} bits ({} bytes)".format(nbits, nbytes))
+        print("Max plaintext length per chunk: {} bytes".format(max_bytes))
+        print("Input plaintext length: {} bytes".format(len(plaintext)))
+        print("Number of chunks: {}".format(chunks))
+
+        for count in range(chunks):
+            chunk = plaintext[int(count * max_bytes):
+                              int((count + 1) * max_bytes)]
+            p = subprocess.Popen(['openssl', 'rsautl', '-encrypt',
+                                  '-oaep', '-pubin', '-inkey',
+                                  pubkey_file.name],
+                                 stdin=subprocess.PIPE,
+                                 stdout=subprocess.PIPE)
+            (stdout, stderr) = p.communicate(chunk)
+            if p.returncode != 0:
+                raise Exception("Return code %s from openssl" % p.returncode)
+            ciphertext_chunks.append(base64.b64encode(stdout).decode('utf-8'))
+
     finally:
         os.unlink(pubkey_file.name)
 
+    output = textwrap.dedent(
+        '''
+        - secret:
+            name: <name>
+            data:
+              <fieldname>: !encrypted/pkcs1-oaep
+        ''')
+
+    twrap = textwrap.TextWrapper(width=79,
+                                 initial_indent=' ' * 8,
+                                 subsequent_indent=' ' * 10)
+    for chunk in ciphertext_chunks:
+        chunk = twrap.fill('- ' + chunk)
+        output += chunk + '\n'
+
     if args.outfile:
-        with open(args.outfile, "wb") as f:
-            f.write(ciphertext)
+        with open(args.outfile, "w") as f:
+            f.write(output)
     else:
-        print(ciphertext.decode("utf-8"))
+        print(output)
 
 
 if __name__ == '__main__':