-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathsecure_demo.py
executable file
·159 lines (135 loc) · 5.3 KB
/
secure_demo.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#!/usr/bin/env python
#
# Copyright(C) 2012 Simon Howard
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.
#
#
# This is a library used by the master for the signed demos system. It
# uses GPG to create signed messages that are returned by the master
# back to the clients.
#
from __future__ import division, generators, unicode_literals, print_function
from io import BytesIO
import os
import sys
import time
NONCE_SIZE = 16 # bytes
try:
import gpgme
available = True
except ImportError:
available = False
def now_string():
"""Generate a string representing the current time.
The time is roughly ISO8601 UTC format, but also includes
milliseconds for additional accuracy.
"""
now = time.time()
ms = int(now * 1000) % 1000
datetime_base = time.strftime('%Y-%m-%dT%H:%M:%S', time.gmtime(now))
return "%s.%03iZ" % (datetime_base, ms)
def bin_to_hex(data):
"""Convert a string of binary data into a hex representation."""
return "".join(map(lambda x: "%02x" % ord(x), data))
class SecureSigner(object):
def __init__(self, key):
"""Initialize a new SecureSigner. Must be passed a key identifier
string specifying the GPG key to use. """
self.context = gpgme.Context()
self.key = self.context.get_key(key)
self.context.signers = [ self.key ]
def _generate_start_message(self, nonce):
"""Generate the plaintext used for a start message."""
return "\n".join([
"Message-Type: Start",
"Start-Time: %s" % now_string(),
"Nonce: %s" % bin_to_hex(nonce),
])
def _sign_plaintext_message(self, message):
"""Sign a plaintext message."""
signature = BytesIO()
self.context.sign(BytesIO(message), signature, gpgme.SIG_MODE_CLEAR)
return signature.getvalue()
def sign_start_message(self):
"""Generate a new signed start message with a random nonce value."""
nonce = os.urandom(NONCE_SIZE)
message = self._generate_start_message(nonce)
return (nonce, self._sign_plaintext_message(message))
def _verify_signature(self, result):
"""Check the results of a verify operation."""
if len(result) != 1:
return False
# Check the signature is valid:
signature = result[0]
if (signature.summary & gpgme.SIGSUM_VALID) == 0:
return False
# Check the signature matches the right key:
for subkey in self.key.subkeys:
if subkey.fpr == signature.fpr:
break
else:
return False
return True
def _verify_start_message(self, signed_message):
"""Check that a signed message is correctly signed, returning
the plaintext if it is valid, or None if it is invalid."""
# Parse the plain text signed message:
try:
plaintext = BytesIO()
result = self.context.verify(BytesIO(signed_message),
None, plaintext)
if self._verify_signature(result):
return plaintext.getvalue()
except gpgme.GpgmeError:
pass
# Failure of some kind occurred: message failed to parse, or
# did not pass verification, etc.
return None
def sign_end_message(self, start_message, demo_hash):
"""Verify a start message and sign an end message that verifies
a complete demo."""
plaintext = self._verify_start_message(start_message)
if plaintext is None:
return None
# Split plain-text of start message into lines, and verify
# message type:
plaintext = plaintext.rstrip("\n")
plaintext_lines = plaintext.split("\n")
if plaintext_lines[0] != "Message-Type: Start":
return None
# Construct the end message:
message_lines = [ "Message-Type: Signature" ]
message_lines += plaintext_lines[1:]
message_lines += [
"End-Time: %s" % now_string(),
"Demo-Checksum: %s" % bin_to_hex(demo_hash),
]
message = "\n".join(message_lines)
return self._sign_plaintext_message(message)
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: %s <start|end> <key>" % sys.argv[0])
sys.exit(1)
signer = SecureSigner(sys.argv[2])
if sys.argv[1] == "start":
nonce, start_message = signer.sign_start_message()
print("Nonce: %s" % bin_to_hex(nonce))
print(start_message)
elif sys.argv[1] == "end":
start_message = sys.stdin.read()
fake_checksum = "3vism1idm4ibmaJ3nF1f"
print(signer.sign_end_message(start_message, fake_checksum))