You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@warble.apache.org by hu...@apache.org on 2018/06/26 13:44:49 UTC

[incubator-warble-server] branch master updated (d92e8a5 -> 57f2a89)

This is an automated email from the ASF dual-hosted git repository.

humbedooh pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-warble-server.git.


    from d92e8a5  fix img sizes
     new 0359738  note that we'll be relying on the cryptography lib, 2.0 or newer
     new 10436a4  lint
     new 7637b08  note the algo used
     new 2301ee2  stricter openapi validation
     new 6d381b7  add crypto lib for server
     new 8835792  add version variable to node registry
     new a81aceb  start work on a registry lib for nodes/agents
     new 30dc7e9  add an API endpoint for registering nodes
     new 57f2a89  regen openapi yaml

The 9 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 api/pages/node/register.py                         |  93 +++++++++
 api/plugins/crypto.py                              | 219 +++++++++++++++++++++
 api/plugins/openapi.py                             |   4 +-
 api/plugins/registry.py                            | 117 +++++++++++
 api/yaml/openapi.yaml                              |  54 +++++
 .../openapi/components/schemas/APIKeyResult.yaml   |  16 ++
 .../components/schemas/NodeCredentials.yaml        |  20 ++
 docs/source/design-general.rst                     |  14 +-
 docs/source/setup.rst                              |   1 +
 setup/dbs.yaml                                     |   1 +
 10 files changed, 530 insertions(+), 9 deletions(-)
 create mode 100644 api/pages/node/register.py
 create mode 100644 api/plugins/crypto.py
 create mode 100644 api/plugins/registry.py
 create mode 100644 api/yaml/openapi/components/schemas/APIKeyResult.yaml
 create mode 100644 api/yaml/openapi/components/schemas/NodeCredentials.yaml


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@warble.apache.org
For additional commands, e-mail: commits-help@warble.apache.org


[incubator-warble-server] 08/09: add an API endpoint for registering nodes

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

humbedooh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-warble-server.git

commit 30dc7e9dead1adceb445488b26afbda8940653a2
Author: Daniel Gruno <hu...@apache.org>
AuthorDate: Tue Jun 26 08:43:35 2018 -0500

    add an API endpoint for registering nodes
---
 api/pages/node/register.py                         | 93 ++++++++++++++++++++++
 .../openapi/components/schemas/APIKeyResult.yaml   | 16 ++++
 .../components/schemas/NodeCredentials.yaml        | 20 +++++
 3 files changed, 129 insertions(+)

diff --git a/api/pages/node/register.py b/api/pages/node/register.py
new file mode 100644
index 0000000..f9dd958
--- /dev/null
+++ b/api/pages/node/register.py
@@ -0,0 +1,93 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+########################################################################
+# OPENAPI-URI: /api/node/register
+########################################################################
+# post:
+#   requestBody:
+#     content:
+#       application/json:
+#         schema:
+#           $ref: '#/components/schemas/NodeCredentials'
+#     description: Node credentials
+#     required: true
+#   responses:
+#     '200':
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/APIKeyResult'
+#       description: Node successfully registered with server
+#     default:
+#       content:
+#         application/json:
+#           schema:
+#             $ref: '#/components/schemas/Error'
+#       description: unexpected error
+#   summary: Registers a new node with the Warble server
+# 
+########################################################################
+
+
+
+
+
+"""
+This is the node registration handler for Apache Warble
+"""
+
+import json
+import re
+import time
+import plugins.crypto
+import plugins.registry
+import base64
+
+def run(API, environ, indata, session):
+    
+    method = environ['REQUEST_METHOD']
+    
+    # Registering a new node?
+    if method == "POST":
+        hostname = indata['hostname']
+        pubkey_pem = indata['pubkey']
+        nodeversion = indata['version'] # TODO: Check for incompatibilities!
+        
+        # Try loading the PEM
+        try:
+            pubkey = plugins.crypto.loads(pubkey_pem)
+        except:
+            raise API.exception(400, "Bad PEM payload passed from client!")
+        
+        # Okay, we have what we need for now. Register potential node and gen an API key
+        node = plugins.registry.node(session)
+        node.hostname = hostname
+        node.pem = pubkey_pem
+        node.version = nodeversion
+        node.ip = environ.get('REMOTE_ADDR', '0.0.0.0')
+        
+        # Encrypt API key with the pub key we just got. base64 encode the result
+        apikey_crypt = str(base64.b64encode(plugins.crypto.encrypt(pubkey, node.apikey)), 'ascii')
+        node.save()
+        
+        yield json.dumps({"encrypted": True, "key": apikey_crypt}, indent = 2)
+        return
+
+    
+    # Finally, if we hit a method we don't know, balk!
+    yield API.exception(400, "I don't know this request method!!")
+    
diff --git a/api/yaml/openapi/components/schemas/APIKeyResult.yaml b/api/yaml/openapi/components/schemas/APIKeyResult.yaml
new file mode 100644
index 0000000..ef65532
--- /dev/null
+++ b/api/yaml/openapi/components/schemas/APIKeyResult.yaml
@@ -0,0 +1,16 @@
+########################################################################
+# APIKeyResult                                                         #
+########################################################################
+properties:
+  encrypted:
+    description: Whether the API Key is encrypted (in case of nodes/agents requesting it)
+    example: false
+    type: boolean
+  key:
+    description: The (encrypted?) API Key assigned by the server
+    type: string
+    example: abcdef-1234
+required:
+- encrypted
+- key
+
diff --git a/api/yaml/openapi/components/schemas/NodeCredentials.yaml b/api/yaml/openapi/components/schemas/NodeCredentials.yaml
new file mode 100644
index 0000000..d4e47fe
--- /dev/null
+++ b/api/yaml/openapi/components/schemas/NodeCredentials.yaml
@@ -0,0 +1,20 @@
+########################################################################
+# NodeCredentials                                                      #
+########################################################################
+properties:
+  hostname:
+    description: The node's own perceived hostname
+    example: foo1.warble.xyz
+    type: string
+  pubkey:
+    description: The node's self-generated public RSA key, PEM-encoded
+    type: string
+  version:
+    description: The version of Warble the node is running
+    type: string
+    example: 0.1.0
+required:
+  - hostname
+  - pubkey
+  - version
+


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@warble.apache.org
For additional commands, e-mail: commits-help@warble.apache.org


[incubator-warble-server] 07/09: start work on a registry lib for nodes/agents

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

humbedooh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-warble-server.git

commit a81aceb20cb2a0c338fd1d10d13f36ef24e634e3
Author: Daniel Gruno <hu...@apache.org>
AuthorDate: Tue Jun 26 08:41:05 2018 -0500

    start work on a registry lib for nodes/agents
---
 api/plugins/registry.py | 117 ++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 117 insertions(+)

diff --git a/api/plugins/registry.py b/api/plugins/registry.py
new file mode 100644
index 0000000..42ae0a3
--- /dev/null
+++ b/api/plugins/registry.py
@@ -0,0 +1,117 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+########################################################################
+
+"""
+This is the client registry class for Apache Warble
+"""
+
+import uuid
+import re
+import time
+import plugins.crypto
+
+""" Warble node class """
+class node(object):
+    
+    def __init__(self, session, nodeid = None):
+        """ Loads a node from the registry or inits a new one """
+        print("registered node")
+        self._data = {}
+        self.session = session
+        self.conn = session.DB.sqlite.open('nodes.db')
+        
+        # node variables
+        self.hostname = ""
+        self.pem = ""
+        self.id = None
+        self.description = None
+        self.location = None
+        self.ipv6 = False
+        self.verified = False
+        self.enabled = False
+        self.ip = "0.0.0.0"
+        self.lastping = int(time.time())
+        
+        if nodeid:
+            doc = None
+            nc = self.conn.cursor()
+            # Load by API Key?
+            if re.match(r"^[a-f0-9]+-[a-f0-9-]+$", nodeid):
+                self.apikey = nodeid
+                nc.execute("SELECT * FROM `registry` WHERE `apikey` = ? LIMIT 1", (self.apikey,))
+                doc = nc.fetchone()
+            # Load by Node ID?
+            elif re.match(r"^[0-9]+$", str(nodeid)):
+                self.id = int(nodeid)
+                nc.execute("SELECT * FROM `registry` WHERE `id` = ? LIMIT 1", (self.id,))
+                doc = nc.fetchone()
+                
+            if doc:
+                self.apikey = doc['apikey']
+                self.hostname = doc['hostname']
+                self.pem = doc['pubkey']
+                self.id = doc['id']
+                self.description = doc['description']
+                self.location = doc['location']
+                self.ipv6 = False # TODO!
+                self.verified = (doc['verified'] == 1)
+                self.enabled = (doc['enabled'] == 1)
+                self.ip = doc['ip']
+                self.lastping = doc['lastping']
+                self.version = doc['version']
+                self.key = plugins.crypto.loads(self.pem)
+                self.fingerprint = plugins.crypto.fingerprint(self.pem)
+        
+        # new node from scratch?
+        else:
+            self.apikey = str(uuid.uuid4())
+        
+    def save(self):
+        """ Saves or updates a node in the registry """
+        nc = self.conn.cursor()
+        # Save a new node?
+        if not self.id:
+            self.fingerprint = plugins.crypto.fingerprint(self.pem)
+            print("Saving node with cert %s" % self.fingerprint)
+            nc.execute("INSERT INTO `registry` (`hostname`, `apikey`, `pubkey`, `verified`, `enabled`, `ip`, `lastping`, `version`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
+                    (self.hostname, self.apikey, self.pem, 0, 0, self.ip, int(time.time()), self.version, )
+                )
+        # Save an existing node?
+        else:
+            nc.execute("UPDATE `registry` SET `hostname` = ?, `apikey` = ?, `pubkey` = ?, `verified` = ?, `enabled` = ?, `ip` = ?, `lastping` = ?, `version` = ? WHERE `id` = ? LIMIT 1",
+                    (self.hostname, self.apikey, self.pem, 1 if self.verified else 0, 1 if self.enabled else 0, self.ip, self.lastping, self.version, self.id,)
+                )
+        self.conn.commit()
+    
+    def remove(self):
+        """ Removes a node from the registry """
+        nc = self.conn.cursor()
+        if self.id:
+            nc.execute("DELETE FROM `registry` WHERE `id` = ? LIMIT 1", (self.id, ))
+            self.conn.commit()
+        
+    def __del__(self):
+        # shut off sqlite connection
+        if self.conn:
+            self.conn.close()
+
+    def __enter__(self):
+        pass
+    def __exit__(self, exception_type, exception_value, traceback):
+        del self
+    
\ No newline at end of file


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@warble.apache.org
For additional commands, e-mail: commits-help@warble.apache.org


[incubator-warble-server] 02/09: lint

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

humbedooh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-warble-server.git

commit 10436a433ddaa6580a34a5b1e91cd710ffdee4c8
Author: Daniel Gruno <hu...@apache.org>
AuthorDate: Mon Jun 25 19:38:32 2018 -0500

    lint
---
 docs/source/design-general.rst | 1 -
 1 file changed, 1 deletion(-)

diff --git a/docs/source/design-general.rst b/docs/source/design-general.rst
index ca78881..d7201ac 100644
--- a/docs/source/design-general.rst
+++ b/docs/source/design-general.rst
@@ -53,7 +53,6 @@ with the Warble Server using a three-stage protocol:
    in encrypted form, using the previously sent public key. Thus, only
    a verified client can get test targets, and only the client should be
    able to decrypt the payload and get clear-text target data.
-
  
 3. Once a client has completed a test (or a batch of tests), the result
    is sent to the server and signed using the private key. Thus, the


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@warble.apache.org
For additional commands, e-mail: commits-help@warble.apache.org


[incubator-warble-server] 01/09: note that we'll be relying on the cryptography lib, 2.0 or newer

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

humbedooh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-warble-server.git

commit 0359738b76d317a9f7d089bea635fd9878234919
Author: Daniel Gruno <hu...@apache.org>
AuthorDate: Mon Jun 25 16:55:26 2018 -0500

    note that we'll be relying on the cryptography lib, 2.0 or newer
---
 docs/source/setup.rst | 1 +
 1 file changed, 1 insertion(+)

diff --git a/docs/source/setup.rst b/docs/source/setup.rst
index 449fdc1..ca6b63d 100644
--- a/docs/source/setup.rst
+++ b/docs/source/setup.rst
@@ -96,6 +96,7 @@ following components installed and set up:
 - - certifi
 - - sqlite3
 - - bcrypt
+- - cryptography >= 2.0.0
 - Gunicorn for Python 3.x (often called gunicorn3) or mod_wsgi
 
 ###########################################


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@warble.apache.org
For additional commands, e-mail: commits-help@warble.apache.org


[incubator-warble-server] 03/09: note the algo used

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

humbedooh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-warble-server.git

commit 7637b08755cae1d57618c6522d2c16c571967756
Author: Daniel Gruno <hu...@apache.org>
AuthorDate: Mon Jun 25 19:42:51 2018 -0500

    note the algo used
---
 docs/source/design-general.rst | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/docs/source/design-general.rst b/docs/source/design-general.rst
index d7201ac..b70c3f1 100644
--- a/docs/source/design-general.rst
+++ b/docs/source/design-general.rst
@@ -41,12 +41,13 @@ Client and Server communication
 Agents and Nodes (referred to in this segment as `clients`) communicate
 with the Warble Server using a three-stage protocol:
 
-1. First time a client is started, it generates an async key pair for
-   encryption and subsequent verification/signing. The private key is
-   stored on-disk on the client host, and the public key is sent to the
-   node registry on the master, along with a request to add the client to
-   the node registry as a verified client. The Server registers a unique
-   API key for each client, and binds the public key to this API key.
+1. First time a client is started, it generates an async RSA key pair
+   (default key size is 4096 bits) for encryption and subsequent
+   verification/signing. The private key is stored on-disk on the client
+   host, and the public key is sent to the node registry on the master,
+   along with a request to add the client to the node registry as a
+   verified client. The Server registers a unique API key for each
+   client, and binds the public key to this API key.
 
 2. Once verified, a client can request test targets and parameters from
    the node registry at the Server. This data is sent back to the client


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@warble.apache.org
For additional commands, e-mail: commits-help@warble.apache.org


[incubator-warble-server] 09/09: regen openapi yaml

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

humbedooh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-warble-server.git

commit 57f2a894c2cac215810165c93e74e1ead78026f0
Author: Daniel Gruno <hu...@apache.org>
AuthorDate: Tue Jun 26 08:44:03 2018 -0500

    regen openapi yaml
---
 api/yaml/openapi.yaml | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 54 insertions(+)

diff --git a/api/yaml/openapi.yaml b/api/yaml/openapi.yaml
index b3de7b6..7d96aff 100644
--- a/api/yaml/openapi.yaml
+++ b/api/yaml/openapi.yaml
@@ -11,6 +11,20 @@ info:
     url: 'http://www.apache.org/licenses/LICENSE-2.0.html'
 components:
   schemas:
+    APIKeyResult:
+      properties:
+        encrypted:
+          description: Whether the API Key is encrypted (in case of nodes/agents requesting
+            it)
+          example: false
+          type: boolean
+        key:
+          description: The (encrypted?) API Key assigned by the server
+          example: abcdef-1234
+          type: string
+      required:
+      - encrypted
+      - key
     ActionCompleted:
       properties:
         message:
@@ -42,6 +56,23 @@ components:
       required:
       - code
       - reason
+    NodeCredentials:
+      properties:
+        hostname:
+          description: The node's own perceived hostname
+          example: foo1.warble.xyz
+          type: string
+        pubkey:
+          description: The node's self-generated public RSA key, PEM-encoded
+          type: string
+        version:
+          description: The version of Warble the node is running
+          example: 0.1.0
+          type: string
+      required:
+      - hostname
+      - pubkey
+      - version
     Timeseries:
       properties:
         interval:
@@ -307,6 +338,29 @@ paths:
                 $ref: '#/components/schemas/Error'
           description: unexpected error
       summary: Create a new account
+  /api/node/register:
+    post:
+      requestBody:
+        content:
+          application/json:
+            schema:
+              $ref: '#/components/schemas/NodeCredentials'
+        description: Node credentials
+        required: true
+      responses:
+        '200':
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/APIKeyResult'
+          description: Node successfully registered with server
+        default:
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/Error'
+          description: unexpected error
+      summary: Registers a new node with the Warble server
   /api/session:
     delete:
       requestBody:


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@warble.apache.org
For additional commands, e-mail: commits-help@warble.apache.org


[incubator-warble-server] 04/09: stricter openapi validation

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

humbedooh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-warble-server.git

commit 2301ee240dcfb9b7d11013085f608edbaaeb2957
Author: Daniel Gruno <hu...@apache.org>
AuthorDate: Mon Jun 25 20:10:42 2018 -0500

    stricter openapi validation
---
 api/plugins/openapi.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/api/plugins/openapi.py b/api/plugins/openapi.py
index 71a7117..dbc5208 100644
--- a/api/plugins/openapi.py
+++ b/api/plugins/openapi.py
@@ -88,6 +88,7 @@ class OpenAPI():
             where = "item matching schema %s" % schema
 
         # Check that all required fields are present
+        
         if 'required' in pdef:
             for field in pdef['required']:
                 if not field in formdata:
@@ -142,8 +143,7 @@ class OpenAPI():
                     self.validateParameters(mdefs['parameters'], formdata)
                 elif formdata and 'requestBody' not in mdefs:
                     raise OpenAPIException("OpenAPI mismatch: JSON data is now allowed for this request type")
-                elif formdata and 'requestBody' in mdefs and 'content' in mdefs['requestBody']:
-
+                elif 'requestBody' in mdefs and 'content' in mdefs['requestBody']:
                     # SHORTCUT: We only care about JSON input for Warble! Disregard other types
                     if not 'application/json' in mdefs['requestBody']['content']:
                         raise OpenAPIException ("OpenAPI mismatch: API endpoint accepts input, but no application/json definitions found in api.yaml!")


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@warble.apache.org
For additional commands, e-mail: commits-help@warble.apache.org


[incubator-warble-server] 06/09: add version variable to node registry

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

humbedooh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-warble-server.git

commit 88357923b605904d537513ce5db1b3a29cbd0662
Author: Daniel Gruno <hu...@apache.org>
AuthorDate: Tue Jun 26 08:40:48 2018 -0500

    add version variable to node registry
---
 setup/dbs.yaml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/setup/dbs.yaml b/setup/dbs.yaml
index 539d269..95cf664 100644
--- a/setup/dbs.yaml
+++ b/setup/dbs.yaml
@@ -31,4 +31,5 @@ registry:
     location:     text      # Physical location of node (addr or DC)
     ip:           text      # Known public IP of node
     lastping:     integer   # Last time node was alive
+    version:      text      # Node client software version
 


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@warble.apache.org
For additional commands, e-mail: commits-help@warble.apache.org


[incubator-warble-server] 05/09: add crypto lib for server

Posted by hu...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

humbedooh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-warble-server.git

commit 6d381b7dc5423b2161b9a6d2cc826891602a8773
Author: Daniel Gruno <hu...@apache.org>
AuthorDate: Tue Jun 26 08:40:38 2018 -0500

    add crypto lib for server
---
 api/plugins/crypto.py | 219 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 219 insertions(+)

diff --git a/api/plugins/crypto.py b/api/plugins/crypto.py
new file mode 100644
index 0000000..4e20cab
--- /dev/null
+++ b/api/plugins/crypto.py
@@ -0,0 +1,219 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+ #the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+""" This is the library for basic cryptographic features in
+    Apache Warble (incubating) client/server comms. It includes wrappers for
+    encrypting, decrypting, signing and verifying using RSA async key
+    pairs.
+    
+    NB: Ideally we'd use SHA256 for hashing, but as that still isn't
+    widely supported, we're resorting to SHA1 for now.
+"""
+
+import cryptography.hazmat.backends
+import cryptography.hazmat.primitives
+import cryptography.hazmat.primitives.serialization
+import cryptography.hazmat.primitives.asymmetric.rsa
+import cryptography.hazmat.primitives.asymmetric.utils
+import cryptography.hazmat.primitives.asymmetric.padding
+import cryptography.hazmat.primitives.hashes
+import hashlib
+
+def keypair(bits = 4096):
+    """ Generate a private+public key pair for encryption/signing """
+    private_key = cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key(
+        public_exponent=65537,
+        key_size=bits, # Minimum hould be 4096, puhlease.
+        backend=cryptography.hazmat.backends.default_backend()
+    )
+    return private_key
+
+def loadprivate(filepath):
+    """ Loads a private key from a file path """
+    with open(filepath, "rb") as key_file:
+        private_key = cryptography.hazmat.primitives.serialization.load_pem_private_key(
+            key_file.read(),
+            password=None,
+            backend=cryptography.hazmat.backends.default_backend()
+        )
+        return private_key
+
+def loadpublic(filepath):
+    """ Loads a public key from a file path """
+    with open(filepath, "rb") as key_file:
+        public_key = cryptography.hazmat.primitives.serialization.load_pem_public_key(
+            key_file.read(),
+            backend=cryptography.hazmat.backends.default_backend()
+        )
+        return public_key
+
+def loads(text):
+    """ Loads a public key from a string """
+    public_key = cryptography.hazmat.primitives.serialization.load_pem_public_key(
+        bytes(text, 'ascii', errors = 'strict'),
+        backend=cryptography.hazmat.backends.default_backend()
+    )
+    return public_key
+
+def pem(key):
+    """ Turn a key (public or private) into PEM format """
+    # Private key?
+    if hasattr(key, 'decrypt'):
+        return key.private_bytes(
+            encoding=cryptography.hazmat.primitives.serialization.Encoding.PEM,
+            format=cryptography.hazmat.primitives.serialization.PrivateFormat.PKCS8,
+            encryption_algorithm=cryptography.hazmat.primitives.serialization.NoEncryption()
+         )
+    # Public key?
+    else:
+        return key.public_bytes(
+            encoding=cryptography.hazmat.primitives.serialization.Encoding.PEM,
+            format=cryptography.hazmat.primitives.serialization.PublicFormat.SubjectPublicKeyInfo
+         )
+
+def fingerprint(key):
+    """ Derives a digest fingerprint from a key """
+    if isinstance(key, cryptography.hazmat.backends.openssl.rsa._RSAPublicKey):
+        _pem = pem(key)
+    elif type(key) is str:
+        _pem = bytes(key, 'ascii', errors = 'replace')
+    else:
+        _pem = key
+    sha = hashlib.sha224(_pem).hexdigest()
+    return sha
+    
+def decrypt(key, text):
+    """ Decrypt a message encrypted with the public key, by using the private key on-disk """
+    retval = b""
+    i = 0
+    txtl = len(text)
+    ks = int(key.key_size / 8) # bits -> bytes, room for padding
+    # Process the data in chunks the size of the key, as per the encryption
+    # model used below.
+    while i < txtl:
+        chunk = text[i:i+ks]
+        i += ks
+        ciphertext = key.decrypt(
+            chunk,
+            cryptography.hazmat.primitives.asymmetric.padding.OAEP(
+                mgf=cryptography.hazmat.primitives.asymmetric.padding.MGF1(
+                    algorithm=cryptography.hazmat.primitives.hashes.SHA1()
+                    ),
+                algorithm=cryptography.hazmat.primitives.hashes.SHA1(),
+                label=None
+            )
+        )
+        retval += ciphertext
+    return retval
+
+def encrypt(key, text):
+    """ Encrypt a message using the public key, for decryption with the private key """
+    retval = b""
+    i = 0
+    txtl = len(text)
+    ks = int(key.key_size / 8) - 64 # bits -> bytes, room for padding
+    # Process data in chunks no larger than the key, leave some room for padding.
+    while i < txtl:
+        chunk = text[i:i+ks-1]
+        i += ks
+        ciphertext = key.encrypt(
+            chunk.encode('utf-8'),
+            cryptography.hazmat.primitives.asymmetric.padding.OAEP(
+                mgf=cryptography.hazmat.primitives.asymmetric.padding.MGF1(
+                    algorithm=cryptography.hazmat.primitives.hashes.SHA1()
+                    ),
+                algorithm=cryptography.hazmat.primitives.hashes.SHA1(),
+                label=None
+            )
+        )
+        retval += ciphertext
+    return retval
+
+
+def sign(key, text):
+    """ Signs a string with the private key """
+    hashver = cryptography.hazmat.primitives.hashes.SHA1()
+    hasher = cryptography.hazmat.primitives.hashes.Hash(hashver, cryptography.hazmat.backends.default_backend())
+    retval = b""
+    i = 0
+    txtl = len(text)
+    ks = int(key.key_size / 8)
+    while i < txtl:
+        chunk = text[i:i+ks-1]
+        i += ks
+        hasher.update(chunk.encode('utf-8'))
+    digest = hasher.finalize()
+    sig = key.sign(
+        digest,
+        cryptography.hazmat.primitives.asymmetric.padding.PSS(
+            mgf=cryptography.hazmat.primitives.asymmetric.padding.MGF1(cryptography.hazmat.primitives.hashes.SHA1()),
+            salt_length=cryptography.hazmat.primitives.asymmetric.padding.PSS.MAX_LENGTH
+        ),
+        cryptography.hazmat.primitives.asymmetric.utils.Prehashed(hashver)
+    )
+    return sig
+
+def verify(key, sig, text):
+    """ Verifies a signature of a text using the public key """
+    hashver = cryptography.hazmat.primitives.hashes.SHA1()
+    hasher = cryptography.hazmat.primitives.hashes.Hash(hashver, cryptography.hazmat.backends.default_backend())
+    retval = b""
+    i = 0
+    txtl = len(text)
+    ks = int(key.key_size / 8)
+    while i < txtl:
+        chunk = text[i:i+ks-1]
+        i += ks
+        hasher.update(chunk.encode('utf-8'))
+    digest = hasher.finalize()
+    try:
+        key.verify(
+            sig,
+            digest,
+            cryptography.hazmat.primitives.asymmetric.padding.PSS(
+                mgf=cryptography.hazmat.primitives.asymmetric.padding.MGF1(cryptography.hazmat.primitives.hashes.SHA1()),
+                salt_length=cryptography.hazmat.primitives.asymmetric.padding.PSS.MAX_LENGTH
+            ),
+            cryptography.hazmat.primitives.asymmetric.utils.Prehashed(hashver)
+        )
+        return True
+    except cryptography.exceptions.InvalidSignature as err:
+        return False
+
+def test():
+    """ Tests for the crypto lib """
+    
+    # Generate a key pair, agree on a string to test with
+    privkey = keypair()
+    pubkey = privkey.public_key()
+    mystring = "Bob was here, his burgers were great."
+    
+    # Test encrypting
+    etxt = encrypt(pubkey, mystring)
+    
+    # Test decrypting
+    dtxt = decrypt(privkey, etxt)
+    assert(mystring == str(dtxt, 'utf-8'))
+    
+    # Test signing
+    xx = sign(privkey, mystring)
+    
+    # Test verification
+    assert( verify(pubkey, xx, mystring))
+    
+    print("Crypto lib works as intended!")
+


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@warble.apache.org
For additional commands, e-mail: commits-help@warble.apache.org