From b8163a1102ffdff2d047208000795f871fe4b066 Mon Sep 17 00:00:00 2001 From: Trevor Date: Tue, 8 May 2012 19:00:56 +0300 Subject: [PATCH 01/94] Split sig verification out of parsing, added TACK_Extension.verifySignatures() --- TACKpy/tack_structures.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/TACKpy/tack_structures.py b/TACKpy/tack_structures.py index 42b8238..17691c4 100644 --- a/TACKpy/tack_structures.py +++ b/TACKpy/tack_structures.py @@ -86,9 +86,9 @@ def parse(self, b, verifyFunc=ecdsa256Verify): self.expiration = p.getInt(4) self.target_hash = p.getBytes(32) self.signature = p.getBytes(64) - - if not self.verifySignature(verifyFunc): - raise SyntaxError("Signature verification failure") + + if self.generation < self.min_generation: + raise SyntaxError("Generation less than min_generation") if p.index != len(b): raise SyntaxError("Excess bytes in TACK") @@ -145,7 +145,8 @@ def getTACKID(self): return makeTACKID(self.public_key) def verifySignature(self, verifyFunc): - return verifyFunc(self.public_key, bytearray("tack_break_sig", "ascii"), + return verifyFunc(self.public_key, + bytearray("tack_break_sig", "ascii"), self.signature) def parsePem(self, s, verifyFunc=ecdsa256Verify): @@ -167,8 +168,6 @@ def parse(self, b, verifyFunc=ecdsa256Verify): p = Parser(b) self.public_key = p.getBytes(64) self.signature = p.getBytes(64) - if not self.verifySignature(verifyFunc): - raise SyntaxError("Signature verification failure") if p.index != len(b): raise SyntaxError("Excess bytes in TACK_Break_Sig") @@ -220,6 +219,15 @@ def create(self, tack, break_sigs, pin_activation): def isEmpty(self): return (not self.tack and not self.break_sigs) + + def verifySignatures(self): + if self.tack: + if not self.tack.verifySignature(): + return False + for break_sig in self.break_sigs: + if not break_sig.verifySignature(): + return False + return True def parse(self, b, verifyFunc=ecdsa256Verify): p = Parser(b) From bb86702c138e4d6264d45c454be5456f64f945d0 Mon Sep 17 00:00:00 2001 From: Trevor Date: Tue, 8 May 2012 19:24:54 +0300 Subject: [PATCH 02/94] Fix SSL_Cert.matches() --- TACKpy/ssl_cert.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/TACKpy/ssl_cert.py b/TACKpy/ssl_cert.py index 2ace3a2..f672517 100644 --- a/TACKpy/ssl_cert.py +++ b/TACKpy/ssl_cert.py @@ -54,9 +54,7 @@ def open(self, filename): self.parse(sslBytes) # SyntaxError def matches(self, tack): - if tack.version == TACK_Version.v1: - return self.key_sha256 == tack.sig.target_sha256 - return False + return self.key_sha256 == tack.target_hash def parsePem(self, s): b = dePem(s, "CERTIFICATE") From 88c2b886aa0be1d0726d3ec3a36ce30422d16ace Mon Sep 17 00:00:00 2001 From: Trevor Date: Tue, 8 May 2012 20:42:51 +0300 Subject: [PATCH 03/94] Update for integration with latest TLS Lite. --- TACKpy/tack_structures.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/TACKpy/tack_structures.py b/TACKpy/tack_structures.py index 17691c4..2f719da 100644 --- a/TACKpy/tack_structures.py +++ b/TACKpy/tack_structures.py @@ -58,7 +58,7 @@ def getTACKID(self): def getToBeSigned(self): return bytearray("tack_sig", "ascii") + self.write()[:-64] - def verifySignature(self, verifyFunc): + def verifySignature(self, verifyFunc=ecdsa256Verify): bytesToVerify = self.getToBeSigned() return verifyFunc(self.public_key, bytesToVerify, self.signature) @@ -144,7 +144,7 @@ def create(self, public_key, signFunc): def getTACKID(self): return makeTACKID(self.public_key) - def verifySignature(self, verifyFunc): + def verifySignature(self, verifyFunc=ecdsa256Verify): return verifyFunc(self.public_key, bytearray("tack_break_sig", "ascii"), self.signature) @@ -220,24 +220,25 @@ def create(self, tack, break_sigs, pin_activation): def isEmpty(self): return (not self.tack and not self.break_sigs) - def verifySignatures(self): + def verifySignatures(self, verifyFunc=ecdsa256Verify): if self.tack: - if not self.tack.verifySignature(): + if not self.tack.verifySignature(verifyFunc): return False for break_sig in self.break_sigs: - if not break_sig.verifySignature(): + if not break_sig.verifySignature(verifyFunc): return False return True def parse(self, b, verifyFunc=ecdsa256Verify): p = Parser(b) tackLen = p.getInt(1) - if tackLen != TACK.length: - raise SyntaxError("TACK wrong size") - else: - b2 = p.getBytes(tackLen) - self.tack = TACK() - self.tack.parse(b2, verifyFunc) + if tackLen: + if tackLen != TACK.length: + raise SyntaxError("TACK wrong size") + else: + b2 = p.getBytes(tackLen) + self.tack = TACK() + self.tack.parse(b2, verifyFunc) sigsLen = p.getInt(2) if sigsLen >1024: From e0e91908eacdc058f4bc3435771ff796bb8bc272 Mon Sep 17 00:00:00 2001 From: Trevor Date: Tue, 8 May 2012 22:18:39 +0300 Subject: [PATCH 04/94] Cleanup unnecessary static func writeTextTACKStructures(). --- TACKpy/api.py | 3 +-- TACKpy/tack_structures.py | 32 +++++++++++++------------------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/TACKpy/api.py b/TACKpy/api.py index 1946de0..6592a7f 100644 --- a/TACKpy/api.py +++ b/TACKpy/api.py @@ -3,8 +3,7 @@ from .version import __version__ -from .tack_structures import TACK, TACK_Break_Sig, TACK_Extension, \ - writeTextTACKStructures +from .tack_structures import TACK, TACK_Break_Sig, TACK_Extension from .keyfile import TACK_KeyFile, TACK_KeyFileViewer from .ssl_cert import SSL_Cert from .m2crypto import m2cryptoLoaded diff --git a/TACKpy/tack_structures.py b/TACKpy/tack_structures.py index 2f719da..a2b938c 100644 --- a/TACKpy/tack_structures.py +++ b/TACKpy/tack_structures.py @@ -281,25 +281,19 @@ def write(self): assert(w.index == len(w.bytes)) # did we fill entire bytearray? return w.bytes - def writeText(self): - return writeTextTACKStructures(self.tack, self.break_sigs, - self.pin_activation) - - -def writeTextTACKStructures(tack, breakSigs, pin_activation, - tackidOnly=False): - s = "" - if tack: - if not tackidOnly: - s += tack.writeText() - else: - s += tack.getTACKID()+"\n" - if breakSigs: - for breakSig in breakSigs: - s += breakSig.writeText() - s += "pin_activation = %s\n" % \ - TACK_Activation.strings[pin_activation] - return s + def writeText(self, tackidOnly=False): + s = "" + if self.tack: + if not tackidOnly: + s += self.tack.writeText() + else: + s += self.tack.getTACKID()+"\n" + if self.break_sigs: + for break_sig in self.break_sigs: + s += break_sig.writeText() + s += "pin_activation = %s\n" % \ + TACK_Activation.strings[self.pin_activation] + return s def testTACKStructures(): print("Testing TACK STRUCTURES") From 3478f77129f22643db279dcd1d3589632cbda4c9 Mon Sep 17 00:00:00 2001 From: Trevor Date: Tue, 8 May 2012 23:05:49 +0300 Subject: [PATCH 05/94] pin_activation converts input to boolean, more convenient for tlslite. --- TACKpy/tack_structures.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/TACKpy/tack_structures.py b/TACKpy/tack_structures.py index a2b938c..27c4039 100644 --- a/TACKpy/tack_structures.py +++ b/TACKpy/tack_structures.py @@ -215,7 +215,10 @@ def __init__(self): def create(self, tack, break_sigs, pin_activation): self.tack = tack self.break_sigs = break_sigs - self.pin_activation = pin_activation + if not pin_activation: + self.pin_activation = TACK_Activation.disabled + else: + self.pin_activation = TACK_Activation.enabled def isEmpty(self): return (not self.tack and not self.break_sigs) From 31424abf36aff270480a933d105aaeb09520953b Mon Sep 17 00:00:00 2001 From: Trevor Date: Wed, 9 May 2012 21:12:45 +0300 Subject: [PATCH 06/94] Move length check from parsePEM to parse. --- TACKpy/tack_structures.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/TACKpy/tack_structures.py b/TACKpy/tack_structures.py index 27c4039..8c5c869 100644 --- a/TACKpy/tack_structures.py +++ b/TACKpy/tack_structures.py @@ -69,8 +69,6 @@ def parsePem(self, s, verifyFunc=ecdsa256Verify): validation failure. """ b = dePem(s, "TACK") - if len(b) != TACK.length: - raise SyntaxError("TACK is the wrong size") self.parse(b, verifyFunc) def parse(self, b, verifyFunc=ecdsa256Verify): @@ -78,7 +76,9 @@ def parse(self, b, verifyFunc=ecdsa256Verify): Raise a SyntaxError if input is malformed, including signature validation failure. - """ + """ + if len(b) != TACK.length: + raise SyntaxError("TACK is the wrong size") p = Parser(b) self.public_key = p.getBytes(64) self.min_generation = p.getInt(1) @@ -155,8 +155,6 @@ def parsePem(self, s, verifyFunc=ecdsa256Verify): Raise a SyntaxError if input is malformed. """ b = dePem(s, "TACK BREAK SIG") - if len(b) != TACK_Break_Sig.length: - raise SyntaxError("Break Sig is the wrong size") self.parse(b, verifyFunc) def parse(self, b, verifyFunc=ecdsa256Verify): @@ -165,6 +163,8 @@ def parse(self, b, verifyFunc=ecdsa256Verify): Raise a SyntaxError if input is malformed, including signature validation failure. """ + if len(b) != TACK_Break_Sig.length: + raise SyntaxError("Break Sig is the wrong size") p = Parser(b) self.public_key = p.getBytes(64) self.signature = p.getBytes(64) From 252959ec0a00ed89809dee668b13885febe80b17 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Wed, 9 May 2012 18:37:33 -0700 Subject: [PATCH 07/94] Refactored the TACKpy structure. 1) Broke up the main tack.py script logic, eliminating the monolithic getopt() parsing and separating each TACK command into a separate class with a common base class. 2) Refactored the TACK structure objects, making them immutable objects with a set of common factory methods and serialization methods. 3) Eliminated all the custom crypto code in favor of always using M2Crypto functions. 4) Moved the tests to a consolidated unittest framework. 5) Otherwise substiantial cleanup and code reduction. --- .gitignore | 1 + MANIFEST.in | 7 - Makefile | 64 --- TACKpy/__init__.py | 5 - TACKpy/aes_wrappers.py | 161 ------- TACKpy/api.py | 14 - TACKpy/constants.py | 10 - TACKpy/cryptomath.py | 89 ---- TACKpy/ecdsa.py | 527 ---------------------- TACKpy/ecdsa_wrappers.py | 233 ---------- TACKpy/ellipticcurve.py | 280 ------------ TACKpy/header.py | 14 - TACKpy/keyfile.py | 177 -------- TACKpy/m2crypto.py | 18 - TACKpy/misc.py | 18 - TACKpy/numbertheory.py | 622 -------------------------- TACKpy/pem.py | 100 ----- TACKpy/rijndael.py | 407 ----------------- TACKpy/ssl_cert.py | 221 --------- TACKpy/struct_parser.py | 65 --- TACKpy/tack_structures.py | 360 --------------- TACKpy/tackid.py | 73 --- TACKpy/test.py | 36 -- TACKpy/time_funcs.py | 151 ------- TODO | 2 - make_selfcontained.py | 43 -- reference/ecdsa.py | 556 ----------------------- reference/ellipticcurve.py | 270 ----------- reference/numbertheory.py | 609 ------------------------- reference/rijndael.py | 393 ---------------- scripts/TACK.py | 500 --------------------- setup.py | 18 +- tack.py | 29 ++ tack/InvalidPasswordException.py | 4 + tack/__init__.py | 0 tack/commands/BreakCommand.py | 33 ++ tack/commands/CertificateCommand.py | 106 +++++ tack/commands/Command.py | 93 ++++ tack/commands/GenerateKeyCommand.py | 46 ++ tack/commands/HelpCommand.py | 52 +++ tack/commands/SignCommand.py | 155 +++++++ tack/commands/ViewCommand.py | 80 ++++ tack/commands/__init__.py | 0 {TACKpy => tack}/compat.py | 32 -- tack/crypto/AES.py | 59 +++ TACKpy/asn1.py => tack/crypto/ASN1.py | 52 +-- tack/crypto/Digest.py | 14 + tack/crypto/ECDSA.py | 171 +++++++ tack/crypto/PBKDF2.py | 18 + tack/crypto/__init__.py | 0 tack/structures/Tack.py | 98 ++++ tack/structures/TackActivation.py | 6 + tack/structures/TackBreakSig.py | 73 +++ tack/structures/TackExtension.py | 101 +++++ tack/structures/TackId.py | 18 + tack/structures/TackKeyFile.py | 125 ++++++ tack/structures/TackVersion.py | 3 + tack/structures/__init__.py | 0 tack/tls/TlsCertificate.py | 184 ++++++++ tack/tls/TlsStructure.py | 29 ++ tack/tls/TlsStructureWriter.py | 30 ++ tack/tls/__init__.py | 0 tack/util/PEMDecoder.py | 79 ++++ tack/util/PEMEncoder.py | 29 ++ tack/util/Time.py | 122 +++++ tack/util/Util.py | 31 ++ tack/util/__init__.py | 0 {TACKpy => tack}/version.py | 0 testdata/serverX509Cert.der | Bin 643 -> 0 bytes testdata/serverX509Cert.pem | 16 - tests/CertificateTest.py | 50 +++ tests/CompatTest.py | 39 ++ tests/CryptoTest.py | 56 +++ tests/StructuresTest.py | 97 ++++ tests/TimeTest.py | 41 ++ tests/__init__.py | 0 76 files changed, 2097 insertions(+), 6118 deletions(-) delete mode 100644 MANIFEST.in delete mode 100644 Makefile delete mode 100644 TACKpy/__init__.py delete mode 100644 TACKpy/aes_wrappers.py delete mode 100644 TACKpy/api.py delete mode 100644 TACKpy/constants.py delete mode 100644 TACKpy/cryptomath.py delete mode 100644 TACKpy/ecdsa.py delete mode 100644 TACKpy/ecdsa_wrappers.py delete mode 100644 TACKpy/ellipticcurve.py delete mode 100644 TACKpy/header.py delete mode 100644 TACKpy/keyfile.py delete mode 100644 TACKpy/m2crypto.py delete mode 100644 TACKpy/misc.py delete mode 100644 TACKpy/numbertheory.py delete mode 100644 TACKpy/pem.py delete mode 100644 TACKpy/rijndael.py delete mode 100644 TACKpy/ssl_cert.py delete mode 100644 TACKpy/struct_parser.py delete mode 100644 TACKpy/tack_structures.py delete mode 100644 TACKpy/tackid.py delete mode 100644 TACKpy/test.py delete mode 100644 TACKpy/time_funcs.py delete mode 100644 TODO delete mode 100755 make_selfcontained.py delete mode 100644 reference/ecdsa.py delete mode 100644 reference/ellipticcurve.py delete mode 100644 reference/numbertheory.py delete mode 100755 reference/rijndael.py delete mode 100755 scripts/TACK.py create mode 100755 tack.py create mode 100644 tack/InvalidPasswordException.py create mode 100644 tack/__init__.py create mode 100644 tack/commands/BreakCommand.py create mode 100644 tack/commands/CertificateCommand.py create mode 100644 tack/commands/Command.py create mode 100644 tack/commands/GenerateKeyCommand.py create mode 100644 tack/commands/HelpCommand.py create mode 100644 tack/commands/SignCommand.py create mode 100644 tack/commands/ViewCommand.py create mode 100644 tack/commands/__init__.py rename {TACKpy => tack}/compat.py (68%) create mode 100644 tack/crypto/AES.py rename TACKpy/asn1.py => tack/crypto/ASN1.py (65%) create mode 100644 tack/crypto/Digest.py create mode 100644 tack/crypto/ECDSA.py create mode 100644 tack/crypto/PBKDF2.py create mode 100644 tack/crypto/__init__.py create mode 100644 tack/structures/Tack.py create mode 100644 tack/structures/TackActivation.py create mode 100644 tack/structures/TackBreakSig.py create mode 100644 tack/structures/TackExtension.py create mode 100644 tack/structures/TackId.py create mode 100644 tack/structures/TackKeyFile.py create mode 100644 tack/structures/TackVersion.py create mode 100644 tack/structures/__init__.py create mode 100644 tack/tls/TlsCertificate.py create mode 100644 tack/tls/TlsStructure.py create mode 100644 tack/tls/TlsStructureWriter.py create mode 100644 tack/tls/__init__.py create mode 100644 tack/util/PEMDecoder.py create mode 100644 tack/util/PEMEncoder.py create mode 100644 tack/util/Time.py create mode 100644 tack/util/Util.py create mode 100644 tack/util/__init__.py rename {TACKpy => tack}/version.py (100%) delete mode 100644 testdata/serverX509Cert.der delete mode 100644 testdata/serverX509Cert.pem create mode 100644 tests/CertificateTest.py create mode 100644 tests/CompatTest.py create mode 100644 tests/CryptoTest.py create mode 100644 tests/StructuresTest.py create mode 100644 tests/TimeTest.py create mode 100644 tests/__init__.py diff --git a/.gitignore b/.gitignore index 0d20b64..99ec428 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.pyc +.idea diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 6eb107d..0000000 --- a/MANIFEST.in +++ /dev/null @@ -1,7 +0,0 @@ -recursive-include testdata * -include LICENSE -include README -include Makefile -include MANIFEST.in -include make_selfcontained.py -include selfcontained/TACK.py diff --git a/Makefile b/Makefile deleted file mode 100644 index 134f979..0000000 --- a/Makefile +++ /dev/null @@ -1,64 +0,0 @@ - -.PHONY : default -default: - @echo To install TACKpy run \"./setup.py install\" or \"make install\" - -SCDIR := selfcontained - -# Variables for testing -TESTDIR = testoutput -EXEC = TACK.py -CERT1 = ./testdata/serverX509Cert.pem -CERT2 = ./testdata/serverX509Cert.der - -.PHONY: install -install: - ./setup.py install - -.PHONY : clean -clean: - rm -f src/*.pyc - rm -rf $(SCDIR) - rm -rf $(TESTDIR) - rm -rf build - rm -rf dist - -.PHONY: selfcontained -selfcontained: - rm -rf $(SCDIR) - mkdir $(SCDIR) - ./make_selfcontained.py > $(SCDIR)/TACK.py - chmod +x $(SCDIR)/TACK.py - -dist: selfcontained - ./setup.py sdist - -.PHONY: test -test: - rm -rf $(TESTDIR) - mkdir $(TESTDIR) - $(EXEC) test - # NOTE: USE 'asdf' for passwords... - $(EXEC) genkey > $(TESTDIR)/TACK_Key1.pem - $(EXEC) genkey -p asdf > $(TESTDIR)/TACK_Key2.pem - $(EXEC) genkey -p asdf -o $(TESTDIR)/TACK_Key3.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -c $(CERT1) > $(TESTDIR)/TACK1.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT2) -o $(TESTDIR)/TACK2.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) -m2 -o $(TESTDIR)/TACK3.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) -o $(TESTDIR)/TACK4.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -e 2030-06-06Z -c $(CERT1) -o $(TESTDIR)/TACK5.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -g2 -m2 -c $(CERT1) -o $(TESTDIR)/TACK6.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -m250 -g251 -c $(CERT1) -o $(TESTDIR)/T6 -e 2013-01-02Z -n 3@1d - $(EXEC) break -k $(TESTDIR)/TACK_Key1.pem -p asdf > $(TESTDIR)/TACK_Break_Sig1.pem - $(EXEC) b -k $(TESTDIR)/TACK_Key2.pem -p asdf -o $(TESTDIR)/TACK_Break_Sig2.pem - cat $(TESTDIR)/TACK_Break_Sig1.pem $(TESTDIR)/TACK_Break_Sig2.pem > $(TESTDIR)/TACK_Break_Sigs.pem - $(EXEC) tackcert -i $(TESTDIR)/TACK3.pem > $(TESTDIR)/TACK_Cert3.pem - $(EXEC) tackcert -i $(TESTDIR)/TACK4.pem -b $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_Cert4.pem - $(EXEC) tackcert -i $(TESTDIR)/TACK_Cert3.pem > $(TESTDIR)/TACK3_FromCert.pem - $(EXEC) view $(TESTDIR)/TACK_Key1.pem > $(TESTDIR)/TACK_View_Key1.txt - $(EXEC) view $(TESTDIR)/TACK1.pem > $(TESTDIR)/TACK_View1.txt - $(EXEC) v $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_View_Break_Sigs.txt - $(EXEC) v $(CERT1) > $(TESTDIR)/TACK_View_Cert1.txt - $(EXEC) v $(CERT2) > $(TESTDIR)/TACK_View_Cert2.txt - $(EXEC) v $(TESTDIR)/TACK_Cert3.pem > $(TESTDIR)/TACK_View_TACK_Cert3.txt - @echo OK diff --git a/TACKpy/__init__.py b/TACKpy/__init__.py deleted file mode 100644 index f130d35..0000000 --- a/TACKpy/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from TACKpy.api import * -from TACKpy.api import __version__ # Unsure why this is needed, but it is diff --git a/TACKpy/aes_wrappers.py b/TACKpy/aes_wrappers.py deleted file mode 100644 index 5297db9..0000000 --- a/TACKpy/aes_wrappers.py +++ /dev/null @@ -1,161 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from .cryptomath import * -from .compat import * -from .m2crypto import * -from .rijndael import rijndael - -################ AES WRAPPERS ### - -class AES: - def __init__(self, key, IV, implementation): - if len(key) not in (16, 24, 32): - raise AssertionError() - if len(IV) != 16: - raise AssertionError() - self.isBlockCipher = True - self.block_size = 16 - self.implementation = implementation - if len(key)==16: - self.name = "aes128" - elif len(key)==24: - self.name = "aes192" - elif len(key)==32: - self.name = "aes256" - else: - raise AssertionError() - - #CBC-Mode encryption, returns ciphertext - #WARNING: *MAY* modify the input as well - def encrypt(self, plaintext): - assert(len(plaintext) % 16 == 0) - - #CBC-Mode decryption, returns plaintext - #WARNING: *MAY* modify the input as well - def decrypt(self, ciphertext): - assert(len(ciphertext) % 16 == 0) - -def createAES(key, IV): - if m2cryptoLoaded: - return OpenSSL_AES(key, IV) - else: - return Python_AES(key, IV) - -"""OpenSSL/M2Crypto AES implementation.""" -if m2cryptoLoaded: - - class OpenSSL_AES(AES): - - def __init__(self, key, IV): - AES.__init__(self, key, IV, "openssl") - self.key = key - self.IV = IV - - def _createContext(self, encrypt): - context = m2.cipher_ctx_new() - if len(self.key)==16: - cipherType = m2.aes_128_cbc() - if len(self.key)==24: - cipherType = m2.aes_192_cbc() - if len(self.key)==32: - cipherType = m2.aes_256_cbc() - m2.cipher_init(context, cipherType, self.key, self.IV, encrypt) - return context - - def encrypt(self, plaintext): - AES.encrypt(self, plaintext) - context = self._createContext(1) - ciphertext = m2.cipher_update(context, plaintext) - m2.cipher_ctx_free(context) - self.IV = ciphertext[-self.block_size:] - return bytearray(ciphertext) - - def decrypt(self, ciphertext): - AES.decrypt(self, ciphertext) - context = self._createContext(0) - #I think M2Crypto has a bug - it fails to decrypt and return the last block passed in. - #To work around this, we append sixteen zeros to the string, below: - plaintext = m2.cipher_update(context, ciphertext+('\0'*16)) - - #If this bug is ever fixed, then plaintext will end up having a garbage - #plaintext block on the end. That's okay - the below code will discard it. - plaintext = plaintext[:len(ciphertext)] - m2.cipher_ctx_free(context) - self.IV = ciphertext[-self.block_size:] - return bytearray(plaintext) - -"""Pure-Python AES implementation.""" - -class Python_AES(AES): - def __init__(self, key, IV): - AES.__init__(self, key, IV, "python") - self.rijndael = rijndael(key, 16) - self.IV = IV - - def encrypt(self, plaintextBytes): - AES.encrypt(self, plaintextBytes) - ciphertextBytes = plaintextBytes[:] - chainBytes = self.IV - - #CBC Mode: For each block... - for x in range(len(ciphertextBytes)//16): - - #XOR with the chaining block - blockBytes = ciphertextBytes[x*16 : (x*16)+16] - for y in range(16): - blockBytes[y] ^= chainBytes[y] - - #Encrypt it - encryptedBytes = self.rijndael.encrypt(blockBytes) - - #Overwrite the input with the output - for y in range(16): - ciphertextBytes[(x*16)+y] = encryptedBytes[y] - - #Set the next chaining block - chainBytes = encryptedBytes - - self.IV = chainBytes - return ciphertextBytes - - def decrypt(self, ciphertextBytes): - AES.decrypt(self, ciphertextBytes) - plaintextBytes = ciphertextBytes[:] - chainBytes = self.IV - - #CBC Mode: For each block... - for x in range(len(plaintextBytes)//16): - - #Decrypt it - blockBytes = plaintextBytes[x*16 : (x*16)+16] - decryptedBytes = self.rijndael.decrypt(blockBytes) - - #XOR with the chaining block and overwrite the input with output - for y in range(16): - decryptedBytes[y] ^= chainBytes[y] - plaintextBytes[(x*16)+y] = decryptedBytes[y] - - #Set the next chaining block - chainBytes = blockBytes - - self.IV = chainBytes - return plaintextBytes - -def testAES(): - print("Testing AES WRAPPERS") - key = a2b_hex("c286696d887c9aa0611bbb3e2025a45a") - IV = a2b_hex("562e17996d093d28ddb3ba695a2e6f58") - plaintext = a2b_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") - ciphertext = a2b_hex("d296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1") - assert(isinstance(Python_AES(key, IV).encrypt(plaintext), bytearray)) - assert(isinstance(Python_AES(key, IV).decrypt(ciphertext), bytearray)) - assert(Python_AES(key, IV).encrypt(plaintext) == ciphertext) - assert(Python_AES(key, IV).decrypt(ciphertext) == plaintext) - if m2cryptoLoaded: - assert(isinstance(OpenSSL_AES(key, IV).encrypt(plaintext), bytearray)) - assert(isinstance(OpenSSL_AES(key, IV).decrypt(ciphertext), bytearray)) - assert(OpenSSL_AES(key, IV).encrypt(plaintext) == ciphertext) - assert(OpenSSL_AES(key, IV).decrypt(ciphertext) == plaintext) - assert(createAES(key, IV).encrypt(plaintext) == ciphertext) - return 1 \ No newline at end of file diff --git a/TACKpy/api.py b/TACKpy/api.py deleted file mode 100644 index 1946de0..0000000 --- a/TACKpy/api.py +++ /dev/null @@ -1,14 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from .version import __version__ - -from .tack_structures import TACK, TACK_Break_Sig, TACK_Extension, \ - writeTextTACKStructures -from .keyfile import TACK_KeyFile, TACK_KeyFileViewer -from .ssl_cert import SSL_Cert -from .m2crypto import m2cryptoLoaded -from .pem import pemSniff -from .time_funcs import posixTimeToStr, parseDurationArg, parseTimeArg -from .constants import TACK_Activation -from .test import selfTest diff --git a/TACKpy/constants.py b/TACKpy/constants.py deleted file mode 100644 index 6013e3a..0000000 --- a/TACKpy/constants.py +++ /dev/null @@ -1,10 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -################ CONSTANTS ### - -class TACK_Activation: - disabled = 0 - enabled = 1 - all = (disabled, enabled) - strings = ["disabled", "enabled"] \ No newline at end of file diff --git a/TACKpy/cryptomath.py b/TACKpy/cryptomath.py deleted file mode 100644 index 4395611..0000000 --- a/TACKpy/cryptomath.py +++ /dev/null @@ -1,89 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from .compat import * - -################ CRYPTOMATH ### - -import math, hashlib, hmac - -def bytesToNumber(bytes): - "Convert a sequence of bytes (eg bytearray) into integer." - total = 0 - multiplier = 1 - for count in range(len(bytes)-1, -1, -1): - byte = bytes[count] - total += multiplier * byte - multiplier *= 256 - return total - -def numberToBytes(n, howManyBytes=None): - """Convert an integer into a bytearray, zero-pad to howManyBytes. - - The returned bytearray may be smaller than howManyBytes, but will - not be larger. The returned bytearray will contain a big-endian - encoding of the input integer (n). - """ - if not howManyBytes: - howManyBytes = numBytes(n) - bytes = bytearray(howManyBytes) - for count in range(howManyBytes-1, -1, -1): - bytes[count] = int(n % 256) - n >>= 8 - return bytes - -def stringToNumber(s): - "Convert a string - interpreted as a sequence of bytes - into integer." - return bytesToNumber(bytearray(s)) - -def numBits(n): - "Return the number of bits needed to represent the integer n." - if n==0: - return 0 - s = "%x" % n - return ((len(s)-1)*4) + \ - {'0':0, '1':1, '2':2, '3':2, - '4':3, '5':3, '6':3, '7':3, - '8':4, '9':4, 'a':4, 'b':4, - 'c':4, 'd':4, 'e':4, 'f':4, - }[s[0]] - -def numBytes(n): - "Return the number of bytes needed to represent the integer n." - if n==0: - return 0 - bits = numBits(n) - return int(math.ceil(bits / 8.0)) - -def SHA256(b): - "Return a 32-byte bytearray which is the SHA256 of input bytearray." - return bytearray(hashlib.sha256(compat26Str(b)).digest()) - -def HMAC_SHA256(k, b): - """Return a 32-byte bytearray which is HMAC-SHA256 of key and input.""" - return bytearray(hmac.new(bytes(k), bytes(b), hashlib.sha256).digest()) - -def constTimeCompare(a, b): - """Compare two sequences of integer (eg bytearrays) without a timing leak. - - This function is secure when comparing against secret values, such as - passwords, MACs, etc., where a more naive, early-exit comparison loop - would leak information that could be used to extract the secret. - """ - if len(a) != len(b): - return False - result = 0 - for x in range(len(a)): - result |= a[x]^b[x] - if result: - return False - return True - - import zlib - -def testOsUrandom(): - import zlib, os - print("Testing OS_URANDOM") - length = len(zlib.compress(os.urandom(1000))) - assert(length > 900) - return 1 diff --git a/TACKpy/ecdsa.py b/TACKpy/ecdsa.py deleted file mode 100644 index d753ff7..0000000 --- a/TACKpy/ecdsa.py +++ /dev/null @@ -1,527 +0,0 @@ -# Authors: -# Peter Pearson - main author -# Trevor Perrin - minor changes for Python compatibility -# -# See the LICENSE file for legal information regarding use of this file. -# Also see Peter Pearson's statement below - -from .numbertheory import * -from .ellipticcurve import * -from .compat import * - -################ ECDSA ### -#! /usr/bin/env python -""" -Implementation of Elliptic-Curve Digital Signatures. - -Classes and methods for elliptic-curve signatures: -private keys, public keys, signatures, -NIST prime-modulus curves with modulus lengths of -192, 224, 256, 384, and 521 bits. - -Example: - - # (In real-life applications, you would probably want to - # protect against defects in SystemRandom.) - from random import SystemRandom - randrange = SystemRandom().randrange - - # Generate a public/private key pair using the NIST Curve P-192: - - g = generator_192 - n = g.order() - secret = randrange( 1, n ) - pubkey = Public_key( g, g * secret ) - privkey = Private_key( pubkey, secret ) - - # Signing a hash value: - - hash = randrange( 1, n ) - signature = privkey.sign( hash, randrange( 1, n ) ) - - # Verifying a signature for a hash value: - - if pubkey.verifies( hash, signature ): - print("Demo verification succeeded.") - else: - print("*** Demo verification failed.") - - # Verification fails if the hash value is modified: - - if pubkey.verifies( hash-1, signature ): - print("**** Demo verification failed to reject tampered hash.") - else: - print("Demo verification correctly rejected tampered hash.") - -Version of 2009.05.16. - -Revision history: - 2005.12.31 - Initial version. - 2008.11.25 - Substantial revisions introducing new classes. - 2009.05.16 - Warn against using random.randrange in real applications. - 2009.05.17 - Use random.SystemRandom by default. - -Written in 2005 by Peter Pearson and placed in the public domain. -""" - -class Signature( object ): - """ECDSA signature. - """ - def __init__( self, r, s ): - self.r = r - self.s = s - - - -class Public_key( object ): - """Public key for ECDSA. - """ - - def __init__( self, generator, point ): - """generator is the Point that generates the group, - point is the Point that defines the public key. - """ - - self.curve = generator.curve() - self.generator = generator - self.point = point - n = generator.order() - if not n: - raise RuntimeError("Generator point must have order.") - if not n * point == INFINITY: - raise RuntimeError("Generator point order is bad.") - if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y(): - raise RuntimeError("Generator point has x or y out of range.") - - - def verifies( self, hash, signature ): - """Verify that signature is a valid signature of hash. - Return True if the signature is valid. - """ - - # From X9.62 J.3.1. - - G = self.generator - n = G.order() - r = signature.r - s = signature.s - if r < 1 or r > n-1: return False - if s < 1 or s > n-1: return False - c = inverse_mod( s, n ) - u1 = ( hash * c ) % n - u2 = ( r * c ) % n - xy = u1 * G + u2 * self.point - v = xy.x() % n - return v == r - - - -class Private_key( object ): - """Private key for ECDSA. - """ - - def __init__( self, public_key, secret_multiplier ): - """public_key is of class Public_key; - secret_multiplier is a large integer. - """ - - self.public_key = public_key - self.secret_multiplier = secret_multiplier - - def sign( self, hash, random_k ): - """Return a signature for the provided hash, using the provided - random nonce. It is absolutely vital that random_k be an unpredictable - number in the range [1, self.public_key.point.order()-1]. If - an attacker can guess random_k, he can compute our private key from a - single signature. Also, if an attacker knows a few high-order - bits (or a few low-order bits) of random_k, he can compute our private - key from many signatures. The generation of nonces with adequate - cryptographic strength is very difficult and far beyond the scope - of this comment. - - May raise RuntimeError, in which case retrying with a new - random value k is in order. - """ - - G = self.public_key.generator - n = G.order() - k = random_k % n - p1 = k * G - r = p1.x() - if r == 0: raise RuntimeError("amazingly unlucky random number r") - s = ( inverse_mod( k, n ) * \ - ( hash + ( self.secret_multiplier * r ) % n ) ) % n - if s == 0: raise RuntimeError("amazingly unlucky random number s") - return Signature( r, s ) - - - -def int_to_string( x ): - """Convert integer x into a string of bytes, as per X9.62.""" - assert x >= 0 - if x == 0: return bytearray([0]) - result = bytearray() - while x > 0: - q, r = divmod( x, 256 ) - result = bytearray([r]) + result - x = q - return result - - -def string_to_int( s ): - """Convert a string of bytes into an integer, as per X9.62.""" - result = 0 - for c in s: result = 256 * result + c - return result - - -def digest_integer( m ): - """Convert an integer into a string of bytes, compute - its SHA-1 hash, and convert the result to an integer.""" - # - # I don't expect this function to be used much. I wrote - # it in order to be able to duplicate the examples - # in ECDSAVS. - # - #import sha - from hashlib import sha1 - return string_to_int( bytearray(sha1( compat26Str(int_to_string( m )) ).digest())) - - -def point_is_valid( generator, x, y ): - """Is (x,y) a valid public key based on the specified generator?""" - - # These are the tests specified in X9.62. - - n = generator.order() - curve = generator.curve() - if x < 0 or n <= x or y < 0 or n <= y: - return False - if not curve.contains_point( x, y ): - return False - if not n*Point( curve, x, y ) == \ - INFINITY: - return False - return True - -# TREV: Curve P-192 is included for the unit testing... eventually -# it would be better to convert the unit tests to P-256 -# NIST Curve P-192: -_p = 6277101735386680763835789423207666416083908700390324961279 -_r = 6277101735386680763835789423176059013767194773182842284081 -# s = 0x3045ae6fc8422f64ed579528d38120eae12196d5L -# c = 0x3099d2bbbfcb2538542dcd5fb078b6ef5f3d6fe2c745de65L -_b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1 -_Gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012 -_Gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811 - -curve_192 = CurveFp( _p, -3, _b ) -generator_192 = Point( curve_192, _Gx, _Gy, _r ) - -# NIST Curve P-256: -_p = 115792089210356248762697446949407573530086143415290314195533631308867097853951 -_r = 115792089210356248762697446949407573529996955224135760342422259061068512044369 -# s = 0xc49d360886e704936a6678e1139d26b7819f7e90L -# c = 0x7efba1662985be9403cb055c75d4f7e0ce8d84a9c5114abcaf3177680104fa0dL -_b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b -_Gx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296 -_Gy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5 - -curve_256 = CurveFp( _p, -3, _b ) -generator_256 = Point( curve_256, _Gx, _Gy, _r ) - - -def testECDSA(): - print("Testing ECDSA") - import random - - def test_point_validity( generator, x, y, expected ): - """generator defines the curve; is (x,y) a point on - this curve? "expected" is True if the right answer is Yes.""" - if point_is_valid( generator, x, y ) == expected: - #print("Point validity tested as expected.") - pass - else: - print("*** Point validity test gave wrong result.") - assert() - - def test_signature_validity( Msg, Qx, Qy, R, S, expected ): - """Msg = message, Qx and Qy represent the base point on - elliptic curve c192, R and S are the signature, and - "expected" is True iff the signature is expected to be valid.""" - pubk = Public_key( generator_192, - Point( curve_192, Qx, Qy ) ) - got = pubk.verifies( digest_integer( Msg ), Signature( R, S ) ) - if got == expected: - #print("Signature tested as expected: got %s, expected %s." % \ - # ( got, expected )) - pass - else: - print("*** Signature test failed: got %s, expected %s." % \ - ( got, expected )) - assert() - - #print("NIST Curve P-192:") - p192 = generator_192 - - # From X9.62: - - d = 651056770906015076056810763456358567190100156695615665659 - Q = d * p192 - if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5: - #print("*** p192 * d came out wrong.") - assert() - - k = 6140507067065001063065065565667405560006161556565665656654 - R = k * p192 - if R.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ - or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: - #print("*** k * p192 came out wrong.") - assert() - - u1 = 2563697409189434185194736134579731015366492496392189760599 - u2 = 6266643813348617967186477710235785849136406323338782220568 - temp = u1 * p192 + u2 * Q - if temp.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ - or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: - print("*** u1 * p192 + u2 * Q came out wrong.") - assert() - - e = 968236873715988614170569073515315707566766479517 - pubk = Public_key( generator_192, generator_192 * d ) - privk = Private_key( pubk, d ) - sig = privk.sign( e, k ) - r, s = sig.r, sig.s - if r != 3342403536405981729393488334694600415596881826869351677613 \ - or s != 5735822328888155254683894997897571951568553642892029982342: - print("*** r or s came out wrong.") - assert() - - valid = pubk.verifies( e, sig ) - if valid: pass#print("Signature verified OK.") - else: print("*** Signature failed verification."); assert() - - valid = pubk.verifies( e-1, sig ) - if not valid: pass#print("Forgery was correctly rejected.") - else: print("*** Forgery was erroneously accepted."); assert() - - #print("Testing point validity, as per ECDSAVS.pdf B.2.2:") - - test_point_validity( \ - p192, \ - 0xcd6d0f029a023e9aaca429615b8f577abee685d8257cc83a, \ - 0x00019c410987680e9fb6c0b6ecc01d9a2647c8bae27721bacdfc, \ - False ) - - test_point_validity( - p192, \ - 0x00017f2fce203639e9eaf9fb50b81fc32776b30e3b02af16c73b, \ - 0x95da95c5e72dd48e229d4748d4eee658a9a54111b23b2adb, \ - False ) - - test_point_validity( - p192, \ - 0x4f77f8bc7fccbadd5760f4938746d5f253ee2168c1cf2792, \ - 0x000147156ff824d131629739817edb197717c41aab5c2a70f0f6, \ - False ) - - test_point_validity( - p192, \ - 0xc58d61f88d905293bcd4cd0080bcb1b7f811f2ffa41979f6, \ - 0x8804dc7a7c4c7f8b5d437f5156f3312ca7d6de8a0e11867f, \ - True ) - """ - test_point_validity( - p192, \ - 0xcdf56c1aa3d8afc53c521adf3ffb96734a6a630a4a5b5a70, \ - 0x97c1c44a5fb229007b5ec5d25f7413d170068ffd023caa4e, \ - True ) - - test_point_validity( - p192, \ - 0x89009c0dc361c81e99280c8e91df578df88cdf4b0cdedced, \ - 0x27be44a529b7513e727251f128b34262a0fd4d8ec82377b9, \ - True ) - - test_point_validity( - p192, \ - 0x6a223d00bd22c52833409a163e057e5b5da1def2a197dd15, \ - 0x7b482604199367f1f303f9ef627f922f97023e90eae08abf, \ - True ) - - test_point_validity( - p192, \ - 0x6dccbde75c0948c98dab32ea0bc59fe125cf0fb1a3798eda, \ - 0x0001171a3e0fa60cf3096f4e116b556198de430e1fbd330c8835, \ - False ) - - test_point_validity( - p192, \ - 0xd266b39e1f491fc4acbbbc7d098430931cfa66d55015af12, \ - 0x193782eb909e391a3148b7764e6b234aa94e48d30a16dbb2, \ - False ) - - test_point_validity( - p192, \ - 0x9d6ddbcd439baa0c6b80a654091680e462a7d1d3f1ffeb43, \ - 0x6ad8efc4d133ccf167c44eb4691c80abffb9f82b932b8caa, \ - False ) - - test_point_validity( - p192, \ - 0x146479d944e6bda87e5b35818aa666a4c998a71f4e95edbc, \ - 0xa86d6fe62bc8fbd88139693f842635f687f132255858e7f6, \ - False ) - - test_point_validity( - p192, \ - 0xe594d4a598046f3598243f50fd2c7bd7d380edb055802253, \ - 0x509014c0c4d6b536e3ca750ec09066af39b4c8616a53a923, \ - False ) - """ - - #print("Trying signature-verification tests from ECDSAVS.pdf B.2.4:") - #print("P-192:") - Msg = 0x84ce72aa8699df436059f052ac51b6398d2511e49631bcb7e71f89c499b9ee425dfbc13a5f6d408471b054f2655617cbbaf7937b7c80cd8865cf02c8487d30d2b0fbd8b2c4e102e16d828374bbc47b93852f212d5043c3ea720f086178ff798cc4f63f787b9c2e419efa033e7644ea7936f54462dc21a6c4580725f7f0e7d158 - Qx = 0xd9dbfb332aa8e5ff091e8ce535857c37c73f6250ffb2e7ac - Qy = 0x282102e364feded3ad15ddf968f88d8321aa268dd483ebc4 - R = 0x64dca58a20787c488d11d6dd96313f1b766f2d8efe122916 - S = 0x1ecba28141e84ab4ecad92f56720e2cc83eb3d22dec72479 - test_signature_validity( Msg, Qx, Qy, R, S, True ) - - Msg = 0x94bb5bacd5f8ea765810024db87f4224ad71362a3c28284b2b9f39fab86db12e8beb94aae899768229be8fdb6c4f12f28912bb604703a79ccff769c1607f5a91450f30ba0460d359d9126cbd6296be6d9c4bb96c0ee74cbb44197c207f6db326ab6f5a659113a9034e54be7b041ced9dcf6458d7fb9cbfb2744d999f7dfd63f4 - Qx = 0x3e53ef8d3112af3285c0e74842090712cd324832d4277ae7 - Qy = 0xcc75f8952d30aec2cbb719fc6aa9934590b5d0ff5a83adb7 - R = 0x8285261607283ba18f335026130bab31840dcfd9c3e555af - S = 0x356d89e1b04541afc9704a45e9c535ce4a50929e33d7e06c - test_signature_validity( Msg, Qx, Qy, R, S, True ) - - Msg = 0xf6227a8eeb34afed1621dcc89a91d72ea212cb2f476839d9b4243c66877911b37b4ad6f4448792a7bbba76c63bdd63414b6facab7dc71c3396a73bd7ee14cdd41a659c61c99b779cecf07bc51ab391aa3252386242b9853ea7da67fd768d303f1b9b513d401565b6f1eb722dfdb96b519fe4f9bd5de67ae131e64b40e78c42dd - Qx = 0x16335dbe95f8e8254a4e04575d736befb258b8657f773cb7 - Qy = 0x421b13379c59bc9dce38a1099ca79bbd06d647c7f6242336 - R = 0x4141bd5d64ea36c5b0bd21ef28c02da216ed9d04522b1e91 - S = 0x159a6aa852bcc579e821b7bb0994c0861fb08280c38daa09 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x16b5f93afd0d02246f662761ed8e0dd9504681ed02a253006eb36736b563097ba39f81c8e1bce7a16c1339e345efabbc6baa3efb0612948ae51103382a8ee8bc448e3ef71e9f6f7a9676694831d7f5dd0db5446f179bcb737d4a526367a447bfe2c857521c7f40b6d7d7e01a180d92431fb0bbd29c04a0c420a57b3ed26ccd8a - Qx = 0xfd14cdf1607f5efb7b1793037b15bdf4baa6f7c16341ab0b - Qy = 0x83fa0795cc6c4795b9016dac928fd6bac32f3229a96312c4 - R = 0x8dfdb832951e0167c5d762a473c0416c5c15bc1195667dc1 - S = 0x1720288a2dc13fa1ec78f763f8fe2ff7354a7e6fdde44520 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - """ - Msg = 0x08a2024b61b79d260e3bb43ef15659aec89e5b560199bc82cf7c65c77d39192e03b9a895d766655105edd9188242b91fbde4167f7862d4ddd61e5d4ab55196683d4f13ceb90d87aea6e07eb50a874e33086c4a7cb0273a8e1c4408f4b846bceae1ebaac1b2b2ea851a9b09de322efe34cebe601653efd6ddc876ce8c2f2072fb - Qx = 0x674f941dc1a1f8b763c9334d726172d527b90ca324db8828 - Qy = 0x65adfa32e8b236cb33a3e84cf59bfb9417ae7e8ede57a7ff - R = 0x9508b9fdd7daf0d8126f9e2bc5a35e4c6d800b5b804d7796 - S = 0x36f2bf6b21b987c77b53bb801b3435a577e3d493744bfab0 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x1843aba74b0789d4ac6b0b8923848023a644a7b70afa23b1191829bbe4397ce15b629bf21a8838298653ed0c19222b95fa4f7390d1b4c844d96e645537e0aae98afb5c0ac3bd0e4c37f8daaff25556c64e98c319c52687c904c4de7240a1cc55cd9756b7edaef184e6e23b385726e9ffcba8001b8f574987c1a3fedaaa83ca6d - Qx = 0x10ecca1aad7220b56a62008b35170bfd5e35885c4014a19f - Qy = 0x04eb61984c6c12ade3bc47f3c629ece7aa0a033b9948d686 - R = 0x82bfa4e82c0dfe9274169b86694e76ce993fd83b5c60f325 - S = 0xa97685676c59a65dbde002fe9d613431fb183e8006d05633 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x5a478f4084ddd1a7fea038aa9732a822106385797d02311aeef4d0264f824f698df7a48cfb6b578cf3da416bc0799425bb491be5b5ecc37995b85b03420a98f2c4dc5c31a69a379e9e322fbe706bbcaf0f77175e05cbb4fa162e0da82010a278461e3e974d137bc746d1880d6eb02aa95216014b37480d84b87f717bb13f76e1 - Qx = 0x6636653cb5b894ca65c448277b29da3ad101c4c2300f7c04 - Qy = 0xfdf1cbb3fc3fd6a4f890b59e554544175fa77dbdbeb656c1 - R = 0xeac2ddecddfb79931a9c3d49c08de0645c783a24cb365e1c - S = 0x3549fee3cfa7e5f93bc47d92d8ba100e881a2a93c22f8d50 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0xc598774259a058fa65212ac57eaa4f52240e629ef4c310722088292d1d4af6c39b49ce06ba77e4247b20637174d0bd67c9723feb57b5ead232b47ea452d5d7a089f17c00b8b6767e434a5e16c231ba0efa718a340bf41d67ea2d295812ff1b9277daacb8bc27b50ea5e6443bcf95ef4e9f5468fe78485236313d53d1c68f6ba2 - Qx = 0xa82bd718d01d354001148cd5f69b9ebf38ff6f21898f8aaa - Qy = 0xe67ceede07fc2ebfafd62462a51e4b6c6b3d5b537b7caf3e - R = 0x4d292486c620c3de20856e57d3bb72fcde4a73ad26376955 - S = 0xa85289591a6081d5728825520e62ff1c64f94235c04c7f95 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0xca98ed9db081a07b7557f24ced6c7b9891269a95d2026747add9e9eb80638a961cf9c71a1b9f2c29744180bd4c3d3db60f2243c5c0b7cc8a8d40a3f9a7fc910250f2187136ee6413ffc67f1a25e1c4c204fa9635312252ac0e0481d89b6d53808f0c496ba87631803f6c572c1f61fa049737fdacce4adff757afed4f05beb658 - Qx = 0x7d3b016b57758b160c4fca73d48df07ae3b6b30225126c2f - Qy = 0x4af3790d9775742bde46f8da876711be1b65244b2b39e7ec - R = 0x95f778f5f656511a5ab49a5d69ddd0929563c29cbc3a9e62 - S = 0x75c87fc358c251b4c83d2dd979faad496b539f9f2ee7a289 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x31dd9a54c8338bea06b87eca813d555ad1850fac9742ef0bbe40dad400e10288acc9c11ea7dac79eb16378ebea9490e09536099f1b993e2653cd50240014c90a9c987f64545abc6a536b9bd2435eb5e911fdfde2f13be96ea36ad38df4ae9ea387b29cced599af777338af2794820c9cce43b51d2112380a35802ab7e396c97a - Qx = 0x9362f28c4ef96453d8a2f849f21e881cd7566887da8beb4a - Qy = 0xe64d26d8d74c48a024ae85d982ee74cd16046f4ee5333905 - R = 0xf3923476a296c88287e8de914b0b324ad5a963319a4fe73b - S = 0xf0baeed7624ed00d15244d8ba2aede085517dbdec8ac65f5 - test_signature_validity( Msg, Qx, Qy, R, S, True ) - - Msg = 0xb2b94e4432267c92f9fdb9dc6040c95ffa477652761290d3c7de312283f6450d89cc4aabe748554dfb6056b2d8e99c7aeaad9cdddebdee9dbc099839562d9064e68e7bb5f3a6bba0749ca9a538181fc785553a4000785d73cc207922f63e8ce1112768cb1de7b673aed83a1e4a74592f1268d8e2a4e9e63d414b5d442bd0456d - Qx = 0xcc6fc032a846aaac25533eb033522824f94e670fa997ecef - Qy = 0xe25463ef77a029eccda8b294fd63dd694e38d223d30862f1 - R = 0x066b1d07f3a40e679b620eda7f550842a35c18b80c5ebe06 - S = 0xa0b0fb201e8f2df65e2c4508ef303bdc90d934016f16b2dc - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x4366fcadf10d30d086911de30143da6f579527036937007b337f7282460eae5678b15cccda853193ea5fc4bc0a6b9d7a31128f27e1214988592827520b214eed5052f7775b750b0c6b15f145453ba3fee24a085d65287e10509eb5d5f602c440341376b95c24e5c4727d4b859bfe1483d20538acdd92c7997fa9c614f0f839d7 - Qx = 0x955c908fe900a996f7e2089bee2f6376830f76a19135e753 - Qy = 0xba0c42a91d3847de4a592a46dc3fdaf45a7cc709b90de520 - R = 0x1f58ad77fc04c782815a1405b0925e72095d906cbf52a668 - S = 0xf2e93758b3af75edf784f05a6761c9b9a6043c66b845b599 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x543f8af57d750e33aa8565e0cae92bfa7a1ff78833093421c2942cadf9986670a5ff3244c02a8225e790fbf30ea84c74720abf99cfd10d02d34377c3d3b41269bea763384f372bb786b5846f58932defa68023136cd571863b304886e95e52e7877f445b9364b3f06f3c28da12707673fecb4b8071de06b6e0a3c87da160cef3 - Qx = 0x31f7fa05576d78a949b24812d4383107a9a45bb5fccdd835 - Qy = 0x8dc0eb65994a90f02b5e19bd18b32d61150746c09107e76b - R = 0xbe26d59e4e883dde7c286614a767b31e49ad88789d3a78ff - S = 0x8762ca831c1ce42df77893c9b03119428e7a9b819b619068 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0xd2e8454143ce281e609a9d748014dcebb9d0bc53adb02443a6aac2ffe6cb009f387c346ecb051791404f79e902ee333ad65e5c8cb38dc0d1d39a8dc90add5023572720e5b94b190d43dd0d7873397504c0c7aef2727e628eb6a74411f2e400c65670716cb4a815dc91cbbfeb7cfe8c929e93184c938af2c078584da045e8f8d1 - Qx = 0x66aa8edbbdb5cf8e28ceb51b5bda891cae2df84819fe25c0 - Qy = 0x0c6bc2f69030a7ce58d4a00e3b3349844784a13b8936f8da - R = 0xa4661e69b1734f4a71b788410a464b71e7ffe42334484f23 - S = 0x738421cf5e049159d69c57a915143e226cac8355e149afe9 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x6660717144040f3e2f95a4e25b08a7079c702a8b29babad5a19a87654bc5c5afa261512a11b998a4fb36b5d8fe8bd942792ff0324b108120de86d63f65855e5461184fc96a0a8ffd2ce6d5dfb0230cbbdd98f8543e361b3205f5da3d500fdc8bac6db377d75ebef3cb8f4d1ff738071ad0938917889250b41dd1d98896ca06fb - Qx = 0xbcfacf45139b6f5f690a4c35a5fffa498794136a2353fc77 - Qy = 0x6f4a6c906316a6afc6d98fe1f0399d056f128fe0270b0f22 - R = 0x9db679a3dafe48f7ccad122933acfe9da0970b71c94c21c1 - S = 0x984c2db99827576c0a41a5da41e07d8cc768bc82f18c9da9 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - """ - #print("Testing the example code:") - - # Building a public/private key pair from the NIST Curve P-192: - - g = generator_256 - n = g.order() - - # (random.SystemRandom is supposed to provide - # crypto-quality random numbers, but as Debian recently - # illustrated, a systems programmer can accidentally - # demolish this security, so in serious applications - # further precautions are appropriate.) - - randrange = random.SystemRandom().randrange - - secret = randrange( 1, n ) - pubkey = Public_key( g, g * secret ) - privkey = Private_key( pubkey, secret ) - - # Signing a hash value: - - hash = randrange( 1, n ) - signature = privkey.sign( hash, randrange( 1, n ) ) - - # Verifying a signature for a hash value: - - if pubkey.verifies( hash, signature ): - #print("Demo verification succeeded.") - pass - else: - print("*** Demo verification failed.") - assert() - - if pubkey.verifies( hash-1, signature ): - print("**** Demo verification failed to reject tampered hash.") - assert() - return 1 - diff --git a/TACKpy/ecdsa_wrappers.py b/TACKpy/ecdsa_wrappers.py deleted file mode 100644 index 932f1a2..0000000 --- a/TACKpy/ecdsa_wrappers.py +++ /dev/null @@ -1,233 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from .ecdsa import * -from .cryptomath import * -from .pem import * -from .asn1 import * -from .misc import * -from .m2crypto import * - -################ ECDSA_WRAPPERS ### -"""The following three "wrapper" functions are used for working with ECDSA: - ec256Generate - ecdsa256Sign - ecdsa256Verify - -These wrapper functions operate on bytearrays: - privateKey is a bytearray of length 32 - publicKey is a bytearray of length 64 - signature is a bytearray of length 64 - dataToSign/Verify is an arbitrary-length bytearray - -There are M2Crypto/OpenSSL versions of these functions, as well as -pure Python versions based on Peter Pearson's code (see the -NUMBERTHEORY, ELLIPTICCURVE, and ECDSA sections for pure Python). - -The M2Crypto/OpenSSL versions are loaded and used if present, otherwise -the pure Python versions are used. - -Because M2Crypto operates on ASN.1-encoded signatures, and traditional OpenSSL -PEM-encoded public and private keys, there is a fair bit of data munging to -convert to/from M2Crypto formats. -""" - -import os - -def ec256Generate(): - if m2cryptoLoaded: - return m2crypto_ec256Generate() - else: - return python_ec256Generate() - -def ecdsa256Sign(privateKey, publicKey, dataToSign): - if m2cryptoLoaded: - return m2crypto_ecdsa256Sign(privateKey, publicKey, dataToSign) - else: - return python_ecdsa256Sign(privateKey, publicKey, dataToSign) - -def ecdsa256Verify(publicKey, dataToVerify, signature): - if m2cryptoLoaded: - return m2crypto_ecdsa256Verify(publicKey, dataToVerify, signature) - else: - return python_ecdsa256Verify(publicKey, dataToVerify, signature) - -if m2cryptoLoaded: - - # Marshal/unmarshal PEM-wrapped ECPrivateKey and ASN.1 Signatures - - def _parseECPrivateKey(pemPrivKeyBytes): - """Parse a bytearray containing a PEM-encoded ECPrivatey. - - Return a pair of (32-byte bytearray, 64-byte bytearray) - containing the (privateKey, publicKey) - """ - b = dePem(pemPrivKeyBytes, "EC PRIVATE KEY") - p = ASN1Parser(b) - # The private key is stored as an ASN.1 integer which may - # need to have zero padding removed (if 33 bytes) or added - # (if < 32 bytes): - privateKey = p.getChild(1).value - privateKey = fromAsn1IntBytes(privateKey, 32) - # There is a 00 04 byte prior to the 64-byte public key - # I'm not sure why M2Crypto has the 00 byte there?, - # some ASN1 thing - the 04 byte signals "uncompressed" - # per SECG. Anyways, strip both those bytes off ([2:]) - publicKey = p.getChild(3).getTagged().value[2:] - assert(len(privateKey) == 32) - assert(len(publicKey) == 64) - return (privateKey, publicKey) - - def _writeECPrivateKey(privateKey, publicKey): - assert(len(privateKey) == 32) - assert(len(publicKey) == 64) - bytes1 = a2b_hex("02010104") - bytes2 = a2b_hex("a00a06082a8648ce3d030107a14403420004") - privateKey = toAsn1IntBytes(privateKey) - b = bytes1 + asn1Length(len(privateKey)) + privateKey + \ - bytes2 + publicKey - b = bytearray([0x30]) + asn1Length(len(b)) + b - pemPrivKeyBytes = pem(b, "EC PRIVATE KEY") - return pemPrivKeyBytes - - def _writeECPublicKey(publicKey): - assert(len(publicKey) == 64) - bytes1 = a2b_hex(\ - "3059301306072a8648ce3d020106082a8648ce3d03010703420004") - asn1KeyBytes = bytes1 + publicKey - pemPubKeyBytes = pem(asn1KeyBytes, "PUBLIC KEY") - return pemPubKeyBytes - - def _parseECSignature(asn1SigBytes): - p = ASN1Parser(bytearray(asn1SigBytes)) - r = bytesToNumber(p.getChild(0).value) - s = bytesToNumber(p.getChild(1).value) - return numberToBytes(r, 32) + numberToBytes(s, 32) - - def _writeECSignature(ecSigBytes): - assert(len(ecSigBytes) == 64) - asn1R = toAsn1IntBytes(ecSigBytes[:32]) - asn1S = toAsn1IntBytes(ecSigBytes[32:]) - # Add ASN1 Type=2(int), and Length fields - asn1R = bytearray([2]) + asn1Length(len(asn1R)) + asn1R - asn1S = bytearray([2]) + asn1Length(len(asn1S)) + asn1S - # Add ASN1 Type=0x30(Sequence) and Length fields - asn1ECSigBytes = bytearray([0x30]) + \ - asn1Length(len(asn1R+asn1S)) + asn1R + asn1S - return asn1ECSigBytes - - def m2crypto_ec256Generate(): - # Generate M2Crypto.EC.EC object - m2EC = EC.gen_params(EC.NID_X9_62_prime256v1) - m2EC.gen_key() - # Get the ASN.1 ECPrivateKey for the object - m2Bio = BIO.MemoryBuffer() - m2EC.save_key_bio(m2Bio, cipher=None) - pemPrivKeyBytes = m2Bio.getvalue() - # Parse the ASN.1 ECPrivateKey into byte arrays - # for the 32-byte priv key, and 64-byte pub key - (privateKey, publicKey) = _parseECPrivateKey(pemPrivKeyBytes) - return (privateKey, publicKey) - - def m2crypto_ecdsa256Sign(privateKey, publicKey, dataToSign): - # Write the passed-in private key byte array into PEM form - # Then create M2Crypto EC object from ASN.1 form - pemPrivKeyBytes = _writeECPrivateKey(privateKey, publicKey) - m2EC = EC.load_key_bio(BIO.MemoryBuffer(pemPrivKeyBytes)) - - # Produce ASN.1 signature - hash = SHA256(dataToSign) - asn1SigBytes = m2EC.sign_dsa_asn1(hash) - - # Convert stupid ASN.1 signature into 64-byte signature - # Double-check before returning - sigBytes = _parseECSignature(asn1SigBytes) - assert(ecdsa256Verify(publicKey, dataToSign, sigBytes)) - return sigBytes - - def m2crypto_ecdsa256Verify(publicKey, dataToVerify, signature): - # Write the passed-in public key byte array into PEM form - # Then create M2Crypto EC_pub - pemPubKeyBytes = _writeECPublicKey(publicKey) - m2ECpub = EC.load_pub_key_bio(BIO.MemoryBuffer(pemPubKeyBytes)) - - # Convert 64-byte signature into a stupid ASN.1 signature - asn1SigBytes = _writeECSignature(signature) - hash = SHA256(dataToVerify) - return m2ECpub.verify_dsa_asn1(hash, asn1SigBytes) - - -# Always load the python functions, even if m2crypto loaded: - -def python_ec256Generate(extraRandBytes=None): - # ECDSA key generation per FIPS 186-3 B.4.1 - # (except we use 32 extra random bytes instead of 8 before reduction) - # Random bytes taken from /dev/urandom as well as any extraRandBytes - # REVIEW THIS CAREFULLY! CHANGE AT YOUR PERIL! - randBytes0 = bytearray(os.urandom(64)) - if extraRandBytes: - randBytes0 += bytearray(extraRandBytes) - randBytes = HMAC_SHA256(randBytes0, bytearray([1])) - randBytes+= HMAC_SHA256(randBytes0, bytearray([2])) - c = bytesToNumber(randBytes) - n = generator_256.order() - d = (c % (n-1))+1 - privateKey = numberToBytes(d, 32) - publicKeyPoint = generator_256 * d - publicKey = numberToBytes(publicKeyPoint.x(), 32) + \ - numberToBytes(publicKeyPoint.y(), 32) - return (privateKey, publicKey) - -def python_ecdsa256Sign(privateKey, publicKey, dataToSign): - privateKeyNum = bytesToNumber(privateKey) - hash = SHA256(dataToSign) - g = generator_256 - n = g.order() - x = bytesToNumber(publicKey[:32]) - y = bytesToNumber(publicKey[32:]) - pubkey = Public_key(g, Point(g.curve(), x,y)) - privkey = Private_key(pubkey, privateKeyNum) - - # Generating random nonce k per FIPS 186-3 B.5.1: - # (except we use 32 extra bytes instead of 8 before reduction) - # Random bytes taken from /dev/urandom as well as HMAC(privkey,hash) - # REVIEW THIS CAREFULLY!!! CHANGE AT YOUR PERIL!!! - randBytes0 = bytearray(os.urandom(64)) - randBytes0+= HMAC_SHA256(privateKey, hash) - randBytes = HMAC_SHA256(randBytes0, bytearray([1])) - randBytes+= HMAC_SHA256(randBytes0, bytearray([2])) - c = bytesToNumber(randBytes) - k = (c % (n-1))+1 - hashNum = bytesToNumber(hash) - sig = privkey.sign(hashNum, k) - signature = numberToBytes(sig.r, 32) + numberToBytes(sig.s, 32) - # Double-check value before returning - assert(ecdsa256Verify(publicKey, dataToSign, signature)) - return signature - -def python_ecdsa256Verify(publicKey, dataToVerify, signature): - hashNum = bytesToNumber(SHA256(dataToVerify)) - g = generator_256 - x = bytesToNumber(publicKey[:32]) - y = bytesToNumber(publicKey[32:]) - pubkey = Public_key(g, Point(g.curve(), x,y)) - sig = Signature(bytesToNumber(signature[:32]), - bytesToNumber(signature[32:])) - return pubkey.verifies(hashNum, sig) - -def testECDSAWrappers(): - print("Testing ECDSA WRAPPERS") - privateKey, publicKey = python_ec256Generate() - data = bytearray([0,1,2,3]) - badData = bytearray([0,1,2,4]) - signature = python_ecdsa256Sign(privateKey, publicKey, data) - assert(python_ecdsa256Verify(publicKey, data, signature)) - assert(not python_ecdsa256Verify(publicKey, badData, signature)) - if m2cryptoLoaded: - # See if M2Crypto can verify Python sig - assert(m2crypto_ecdsa256Verify(publicKey, data, signature)) - privateKey, publicKey = m2crypto_ec256Generate() - signature = m2crypto_ecdsa256Sign(privateKey, publicKey, data) - assert(m2crypto_ecdsa256Verify(publicKey, data, signature)) - assert(not m2crypto_ecdsa256Verify(publicKey, badData, signature)) - return 1 diff --git a/TACKpy/ellipticcurve.py b/TACKpy/ellipticcurve.py deleted file mode 100644 index a8810a8..0000000 --- a/TACKpy/ellipticcurve.py +++ /dev/null @@ -1,280 +0,0 @@ -# Authors: -# Peter Pearson - main author -# Trevor Perrin - minor changes for Python compatibility -# -# See the LICENSE file for legal information regarding use of this file. -# Also see Peter Pearson's statement below - -from .numbertheory import * - -################ ELLIPTIC CURVE ### - -#! /usr/bin/env python -# -# Implementation of elliptic curves, for cryptographic applications. -# -# This module doesn't provide any way to choose a random elliptic -# curve, nor to verify that an elliptic curve was chosen randomly, -# because one can simply use NIST's standard curves. -# -# Notes from X9.62-1998 (draft): -# Nomenclature: -# - Q is a public key. -# The "Elliptic Curve Domain Parameters" include: -# - q is the "field size", which in our case equals p. -# - p is a big prime. -# - G is a point of prime order (5.1.1.1). -# - n is the order of G (5.1.1.1). -# Public-key validation (5.2.2): -# - Verify that Q is not the point at infinity. -# - Verify that X_Q and Y_Q are in [0,p-1]. -# - Verify that Q is on the curve. -# - Verify that nQ is the point at infinity. -# Signature generation (5.3): -# - Pick random k from [1,n-1]. -# Signature checking (5.4.2): -# - Verify that r and s are in [1,n-1]. -# -# Version of 2008.11.25. -# -# Revision history: -# 2005.12.31 - Initial version. -# 2008.11.25 - Change CurveFp.is_on to contains_point. -# -# Written in 2005 by Peter Pearson and placed in the public domain. - -class CurveFp( object ): - """Elliptic Curve over the field of integers modulo a prime.""" - def __init__( self, p, a, b ): - """The curve of points satisfying y^2 = x^3 + a*x + b (mod p).""" - self.__p = p - self.__a = a - self.__b = b - - def p( self ): - return self.__p - - def a( self ): - return self.__a - - def b( self ): - return self.__b - - def contains_point( self, x, y ): - """Is the point (x,y) on this curve?""" - return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0 - - - -class Point( object ): - """A point on an elliptic curve. Altering x and y is forbidding, - but they can be read by the x() and y() methods.""" - def __init__( self, curve, x, y, order = None ): - """curve, x, y, order; order (optional) is the order of this point.""" - self.__curve = curve - self.__x = x - self.__y = y - self.__order = order - # self.curve is allowed to be None only for INFINITY: - if self.__curve: assert self.__curve.contains_point( x, y ) - if order: assert self * order == INFINITY - - def __eq__( self, other ): - """Return 0 if the points are identical, 1 otherwise.""" - if self.__curve == other.__curve \ - and self.__x == other.__x \ - and self.__y == other.__y: - return 1 - else: - return 0 - - def __add__( self, other ): - """Add one point to another point.""" - - # X9.62 B.3: - - if other == INFINITY: return self - if self == INFINITY: return other - assert self.__curve == other.__curve - if self.__x == other.__x: - if ( self.__y + other.__y ) % self.__curve.p() == 0: - return INFINITY - else: - return self.double() - - p = self.__curve.p() - - l = ( ( other.__y - self.__y ) * \ - inverse_mod( other.__x - self.__x, p ) ) % p - - x3 = ( l * l - self.__x - other.__x ) % p - y3 = ( l * ( self.__x - x3 ) - self.__y ) % p - - return Point( self.__curve, x3, y3 ) - - def __mul__( self, other ): - """Multiply a point by an integer.""" - - def leftmost_bit( x ): - assert x > 0 - result = 1 - while result <= x: result = 2 * result - return result // 2 - - e = other - if self.__order: e = e % self.__order - if e == 0: return INFINITY - if self == INFINITY: return INFINITY - assert e > 0 - - # From X9.62 D.3.2: - - e3 = 3 * e - negative_self = Point( self.__curve, self.__x, -self.__y, self.__order ) - i = leftmost_bit( e3 ) // 2 - result = self - # print "Multiplying %s by %d (e3 = %d):" % ( self, other, e3 ) - while i > 1: - result = result.double() - if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self - if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self - # print ". . . i = %d, result = %s" % ( i, result ) - i = i // 2 - - return result - - def __rmul__( self, other ): - """Multiply a point by an integer.""" - - return self * other - - def __str__( self ): - if self == INFINITY: return "infinity" - return "(%d,%d)" % ( self.__x, self.__y ) - - def double( self ): - """Return a new point that is twice the old.""" - - # X9.62 B.3: - - p = self.__curve.p() - a = self.__curve.a() - - l = ( ( 3 * self.__x * self.__x + a ) * \ - inverse_mod( 2 * self.__y, p ) ) % p - - x3 = ( l * l - 2 * self.__x ) % p - y3 = ( l * ( self.__x - x3 ) - self.__y ) % p - - return Point( self.__curve, x3, y3 ) - - def x( self ): - return self.__x - - def y( self ): - return self.__y - - def curve( self ): - return self.__curve - - def order( self ): - return self.__order - - -# This one point is the Point At Infinity for all purposes: -INFINITY = Point( None, None, None ) - -def testEllipticCurve(): - - print("Testing ELLIPTIC CURVE") - - def test_add( c, x1, y1, x2, y2, x3, y3 ): - """We expect that on curve c, (x1,y1) + (x2, y2 ) = (x3, y3).""" - p1 = Point( c, x1, y1 ) - p2 = Point( c, x2, y2 ) - p3 = p1 + p2 - #print "%s + %s = %s" % ( p1, p2, p3 ), - if p3.x() != x3 or p3.y() != y3: - print(" Failure: should give (%d,%d)." % ( x3, y3 )) - assert() - - def test_double( c, x1, y1, x3, y3 ): - """We expect that on curve c, 2*(x1,y1) = (x3, y3).""" - p1 = Point( c, x1, y1 ) - p3 = p1.double() - #print "%s doubled = %s" % ( p1, p3 ), - if p3.x() != x3 or p3.y() != y3: - print(" Failure: should give (%d,%d)." % ( x3, y3 )) - assert() - - def test_multiply( c, x1, y1, m, x3, y3 ): - """We expect that on curve c, m*(x1,y1) = (x3,y3).""" - p1 = Point( c, x1, y1 ) - p3 = p1 * m - #print "%s * %d = %s" % ( p1, m, p3 ), - if p3.x() != x3 or p3.y() != y3: - print(" Failure: should give (%d,%d)." % ( x3, y3 )) - assert() - - # A few tests from X9.62 B.3: - - c = CurveFp( 23, 1, 1 ) - test_add( c, 3, 10, 9, 7, 17, 20 ) - test_double( c, 3, 10, 7, 12 ) - test_add( c, 3, 10, 3, 10, 7, 12 ) # (Should just invoke double.) - test_multiply( c, 3, 10, 2, 7, 12 ) - - # From X9.62 I.1 (p. 96): - - g = Point( c, 13, 7, 7 ) - - check = INFINITY - for i in range( 7 + 1 ): - p = ( i % 7 ) * g - #print("%s * %d = %s, expected %s . . ." % ( g, i, p, check )) - if p == check: - #print(" Good.") - pass - else: - print(p.x(), p.y()) - #print(check.x(), check.y()) - #print(" Bad. %s %s %s %s" % (p, check, type(p), type(check))) - assert() - check = check + g - - # NIST Curve P-192: - p = 6277101735386680763835789423207666416083908700390324961279 - r = 6277101735386680763835789423176059013767194773182842284081 - s = 0x3045ae6fc8422f64ed579528d38120eae12196d5 - c = 0x3099d2bbbfcb2538542dcd5fb078b6ef5f3d6fe2c745de65 - b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1 - Gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012 - Gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811 - - c192 = CurveFp( p, -3, b ) - p192 = Point( c192, Gx, Gy, r ) - - # Checking against some sample computations presented - # in X9.62: - - d = 651056770906015076056810763456358567190100156695615665659 - Q = d * p192 - if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5: - print("p192 * d came out wrong.") - assert() - - k = 6140507067065001063065065565667405560006161556565665656654 - R = k * p192 - if R.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ - or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: - print("k * p192 came out wrong.") - assert() - - u1 = 2563697409189434185194736134579731015366492496392189760599 - u2 = 6266643813348617967186477710235785849136406323338782220568 - temp = u1 * p192 + u2 * Q - if temp.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ - or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: - print("u1 * p192 + u2 * Q came out wrong.") - assert() - return 1 diff --git a/TACKpy/header.py b/TACKpy/header.py deleted file mode 100644 index 368a036..0000000 --- a/TACKpy/header.py +++ /dev/null @@ -1,14 +0,0 @@ -#! /usr/bin/env python - -# Self-contained version of TACK.py - -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. -# All code below is dedicated to the public domain by its authors. -# -# Includes public domain code from: -# Peter Pearson (Number theory, Elliptic curve, ECDSA) -# Bram Cohen (Rijndael) - - - diff --git a/TACKpy/keyfile.py b/TACKpy/keyfile.py deleted file mode 100644 index aacb3f4..0000000 --- a/TACKpy/keyfile.py +++ /dev/null @@ -1,177 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from .ecdsa_wrappers import * -from .rijndael import * -from .struct_parser import * -from .pem import * -from .aes_wrappers import * -from .tackid import * - -################ KEY FILE ### - -import os -""" -File format: - - version 1 byte = 0x01 - iter_count 4 bytes = uint32, bigendian - salt 16 bytes - EC privkey 32 bytes } aes256-cbc(IV=0) } hmac-sha256 - EC pubkey 64 bytes } hmac-sha256 - HMAC 32 bytes - - Total 149 - -The AES256-CBC and HMAC-SHA256 steps require independent -32-byte keys (encKey and authKey, respectively). - -These keys are derived from a 32-byte masterKey. The masterKey is -derived from a password via PBKDF2-HMAC-SHA26: - - masterKey = PBKDF2-HMAC-SHA256(password, salt, iter_count) - encKey = HMAC-SHA256(masterKey, 0x01) - authKey = HMAC-SHA256(masterKey, 0x02) -""" - -def xorbytes(s1, s2): - return bytearray([a^b for a,b in zip(s1,s2)]) - -# Uses PBKDF2-HMAC-SHA256 to produce a 32-byte key -def pbkdf2_hmac_sha256(password, salt, iter_count): - m = salt + bytearray([0,0,0,1]) - result = bytearray(32) - for c in range(iter_count): - m = HMAC_SHA256(bytearray(password, "ascii"), m) - result = xorbytes(m, result) - return result - -# Uses PBKDF2, then HMAC-SHA256 as PRF to derive independent 32-byte keys -def deriveKeyFileKeys(password, salt, iter_count): - assert(iter_count>0) - masterKey = pbkdf2_hmac_sha256(password, salt, iter_count) - encKey = HMAC_SHA256(masterKey, bytearray([1])) - authKey = HMAC_SHA256(masterKey, bytearray([2])) - return (encKey, authKey) - - -class TACK_KeyFileViewer: - def __init__(self): - self.version = 0 - self.iter_count = 0 - self.salt = bytearray(16) - self.ciphertext = bytearray(64) - self.public_key = bytearray(64) - self.mac = bytearray(32) - - def parse(self, s): - b = dePem(s, "TACK PRIVATE KEY") - p = Parser(b) - self.version = p.getInt(1) - if self.version != 1: - raise SyntaxError("Bad version in Secret File") - self.iter_count = p.getInt(4) - self.salt = p.getBytes(16) - self.ciphertext = p.getBytes(32) - self.public_key = p.getBytes(64) - self.mac = bytearray(p.getBytes(32)) - if p.index != len(b): - raise SyntaxError("Excess bytes in TACK Key File") - - def writeText(self): - return "TACK ID = %s\n" % makeTACKID(self.public_key) - -class TACK_KeyFile: - length = 149 # length of keyfile in bytes - - def __init__(self): - self.version = 0 - self.private_key = bytearray(32) - self.public_key = bytearray(64) - self.iter_count = 0 - - def create(self): - self.version = 1 - self.private_key, self.public_key = ec256Generate() - self.iter_count = 8192 - - def sign(self, bytesToSign): - signature = ecdsa256Sign(self.private_key, self.public_key, bytesToSign) - return signature - - def parsePem(self, s, password): - b = dePem(s, "TACK PRIVATE KEY") - p = Parser(b) - self.version = p.getInt(1) - if self.version != 1: - raise SyntaxError("Bad version in Secret File") - self.iter_count = p.getInt(4) - salt = p.getBytes(16) - ciphertext = p.getBytes(32) - self.public_key = p.getBytes(64) - mac = bytearray(p.getBytes(32)) - if p.index != len(b): - raise SyntaxError("Excess bytes in TACK Key File") - - encKey, authKey = deriveKeyFileKeys(password, salt, self.iter_count) - macData = ciphertext + self.public_key - calcMac = HMAC_SHA256(authKey, macData) - if not constTimeCompare(calcMac, mac): - return False - plaintext = createAES(encKey, bytearray(16)).decrypt(ciphertext) - self.private_key = plaintext - return True - - def writeText(self): - s = \ -"""TACK ID = %s\n""" % makeTACKID(self.public_key) - return s - - def writePem(self, password): - salt = bytearray(os.urandom(16)) - IV = bytearray(os.urandom(16)) - encKey, authKey = deriveKeyFileKeys(password, salt, self.iter_count) - plaintext = self.private_key - ciphertext = createAES(encKey, bytearray(16)).encrypt(plaintext) - macData = ciphertext + self.public_key - mac = HMAC_SHA256(authKey, macData) - w = Writer(TACK_KeyFile.length) - w.add(self.version, 1) - w.add(self.iter_count, 4) - w.add(salt, 16) - w.add(ciphertext, 32) - w.add(self.public_key, 64) - w.add(mac, 32) - assert(w.index == len(w.bytes)) # did we fill entire bytearray? - b = pem(w.bytes, "TACK PRIVATE KEY") - return b - -def testKeyFile(): - print("Testing KEY FILE") - s = """ - -----BEGIN TACK PRIVATE KEY----- - AQAAIAAjOxiOdpiMo5qWidXwBTqJHxW5X1zRDBOA4ldqqFuKOSh6JJdrbXk1WsMN - X/gyaVuHMBhC/g/rjtu/EnmIHoUuT9348iXeeROaLVRPdNqwr+5KEfjtTY7uXA6Q - mhRUn+XmDePKRucRHYkcQaFPnzglrQ120Dh6aXD4PbtJMWajJtzTMvtEo9pNZhoM - QTNZNoM= - -----END TACK PRIVATE KEY-----""" - publicKey = a2b_hex("87301842fe0feb8edbbf1279881e852e" - "4fddf8f225de79139a2d544f74dab0af" - "ee4a11f8ed4d8eee5c0e909a14549fe5" - "e60de3ca46e7111d891c41a14f9f3825") - privateKey = a2b_hex("fc815de8b1de13a436e9cd69742cbf2c" - "d4c1c9bb33e023401d9291cf2781b754") - kf = TACK_KeyFile() - assert(kf.parsePem(s, "asdf")) - assert(kf.public_key == publicKey) - assert(kf.private_key == privateKey) - kf2 = TACK_KeyFile() - assert(kf2.parsePem(kf.writePem("cdef"), "cdef")) - assert(kf.public_key == publicKey) - assert(kf.private_key == privateKey) - kf3 = TACK_KeyFile() - kf3.create() - kf4 = TACK_KeyFile() - assert(kf4.parsePem(kf3.writePem("123"), "123")) - assert(kf3.public_key == kf4.public_key) - return 1 diff --git a/TACKpy/m2crypto.py b/TACKpy/m2crypto.py deleted file mode 100644 index 53cfbc9..0000000 --- a/TACKpy/m2crypto.py +++ /dev/null @@ -1,18 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -################ M2CRYPTO ### - -try: - from M2Crypto import EC, BIO, m2 - m2cryptoLoaded = True - -except ImportError: - m2cryptoLoaded = False -except: - # Most likely ImportError, but I've seen AttributeError from what - # might've been an incorrectly installed M2Crypto (on AWS), so - # let's just swallow everything... - m2cryptoLoaded = False - import sys - sys.stderr.write("Error importing M2Crypto?! \n") diff --git a/TACKpy/misc.py b/TACKpy/misc.py deleted file mode 100644 index a12792e..0000000 --- a/TACKpy/misc.py +++ /dev/null @@ -1,18 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from .compat import * - -################ MISC ### - -# Helper function used by structures to print out their binary elements -def writeBytes(b): - """Write hex-encoded byte array with 16 bytes (32 chars) per line""" - s = b2a_hex(b) - retVal = "" - while s: - retVal += s[:32] - s = s[32:] - if len(s): - retVal += "\n " - return retVal \ No newline at end of file diff --git a/TACKpy/numbertheory.py b/TACKpy/numbertheory.py deleted file mode 100644 index 0f6f3dd..0000000 --- a/TACKpy/numbertheory.py +++ /dev/null @@ -1,622 +0,0 @@ -# Authors: -# Peter Pearson - main author -# Trevor Perrin - minor changes for Python compatibility -# -# See the LICENSE file for legal information regarding use of this file. -# Also see Peter Pearson's statement below - -################ NUMBER THEORY ### -#! /usr/bin/env python -# -# Provide some simple capabilities from number theory. -# -# Version of 2008.11.14. -# -# Written in 2005 and 2006 by Peter Pearson and placed in the public domain. -# Revision history: -# 2008.11.14: Use pow( base, exponent, modulus ) for modular_exp. -# Make gcd and lcm accept arbitrarly many arguments. - - - -import math -import types - -# TREV: needed for python3 -from functools import reduce - - -class Error( Exception ): - """Base class for exceptions in this module.""" - pass - -class SquareRootError( Error ): - pass - -class NegativeExponentError( Error ): - pass - - -def modular_exp( base, exponent, modulus ): - "Raise base to exponent, reducing by modulus" - if exponent < 0: - raise NegativeExponentError( "Negative exponents (%d) not allowed" \ - % exponent ) - return pow( base, exponent, modulus ) -# result = 1L -# x = exponent -# b = base + 0L -# while x > 0: -# if x % 2 > 0: result = (result * b) % modulus -# x = x / 2 -# b = ( b * b ) % modulus -# return result - - -def polynomial_reduce_mod( poly, polymod, p ): - """Reduce poly by polymod, integer arithmetic modulo p. - - Polynomials are represented as lists of coefficients - of increasing powers of x.""" - - # This module has been tested only by extensive use - # in calculating modular square roots. - - # Just to make this easy, require a monic polynomial: - assert polymod[-1] == 1 - - assert len( polymod ) > 1 - - while len( poly ) >= len( polymod ): - if poly[-1] != 0: - for i in range( 2, len( polymod ) + 1 ): - poly[-i] = ( poly[-i] - poly[-1] * polymod[-i] ) % p - poly = poly[0:-1] - - return poly - - - -def polynomial_multiply_mod( m1, m2, polymod, p ): - """Polynomial multiplication modulo a polynomial over ints mod p. - - Polynomials are represented as lists of coefficients - of increasing powers of x.""" - - # This is just a seat-of-the-pants implementation. - - # This module has been tested only by extensive use - # in calculating modular square roots. - - # Initialize the product to zero: - - prod = ( len( m1 ) + len( m2 ) - 1 ) * [0] - - # Add together all the cross-terms: - - for i in range( len( m1 ) ): - for j in range( len( m2 ) ): - prod[i+j] = ( prod[i+j] + m1[i] * m2[j] ) % p - - return polynomial_reduce_mod( prod, polymod, p ) - - - - -def polynomial_exp_mod( base, exponent, polymod, p ): - """Polynomial exponentiation modulo a polynomial over ints mod p. - - Polynomials are represented as lists of coefficients - of increasing powers of x.""" - - # Based on the Handbook of Applied Cryptography, algorithm 2.227. - - # This module has been tested only by extensive use - # in calculating modular square roots. - - assert exponent < p - - if exponent == 0: return [ 1 ] - - G = base - k = exponent - if k%2 == 1: s = G - else: s = [ 1 ] - - while k > 1: - k = k // 2 - G = polynomial_multiply_mod( G, G, polymod, p ) - if k%2 == 1: s = polynomial_multiply_mod( G, s, polymod, p ) - - return s - - - -def jacobi( a, n ): - """Jacobi symbol""" - - # Based on the Handbook of Applied Cryptography (HAC), algorithm 2.149. - - # This function has been tested by comparison with a small - # table printed in HAC, and by extensive use in calculating - # modular square roots. - - assert n >= 3 - assert n%2 == 1 - a = a % n - if a == 0: return 0 - if a == 1: return 1 - a1, e = a, 0 - while a1%2 == 0: - a1, e = a1//2, e+1 - if e%2 == 0 or n%8 == 1 or n%8 == 7: s = 1 - else: s = -1 - if a1 == 1: return s - if n%4 == 3 and a1%4 == 3: s = -s - return s * jacobi( n % a1, a1 ) - - - - -def square_root_mod_prime( a, p ): - """Modular square root of a, mod p, p prime.""" - - # Based on the Handbook of Applied Cryptography, algorithms 3.34 to 3.39. - - # This module has been tested for all values in [0,p-1] for - # every prime p from 3 to 1229. - - assert 0 <= a < p - assert 1 < p - - if a == 0: return 0 - if p == 2: return a - - jac = jacobi( a, p ) - if jac == -1: raise SquareRootError( "%d has no square root modulo %d" \ - % ( a, p ) ) - - if p % 4 == 3: return modular_exp( a, (p+1)//4, p ) - - if p % 8 == 5: - d = modular_exp( a, (p-1)//4, p ) - if d == 1: return modular_exp( a, (p+3)//8, p ) - if d == p-1: return ( 2 * a * modular_exp( 4*a, (p-5)//8, p ) ) % p - raise RuntimeError("Shouldn't get here.") - - for b in range( 2, p ): - if jacobi( b*b-4*a, p ) == -1: - f = ( a, -b, 1 ) - ff = polynomial_exp_mod( ( 0, 1 ), (p+1)//2, f, p ) - assert ff[1] == 0 - return ff[0] - raise RuntimeError("No b found.") - - - -def inverse_mod( a, m ): - """Inverse of a mod m.""" - - if a < 0 or m <= a: a = a % m - - # From Ferguson and Schneier, roughly: - - c, d = a, m - uc, vc, ud, vd = 1, 0, 0, 1 - while c != 0: - q, c, d = divmod( d, c ) + ( c, ) - uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc - - # At this point, d is the GCD, and ud*a+vd*m = d. - # If d == 1, this means that ud is a inverse. - - assert d == 1 - if ud > 0: return ud - else: return ud + m - - -def gcd2(a, b): - """Greatest common divisor using Euclid's algorithm.""" - while a: - a, b = b%a, a - return b - - -def gcd( *a ): - """Greatest common divisor. - - Usage: gcd( [ 2, 4, 6 ] ) - or: gcd( 2, 4, 6 ) - """ - if len( a ) > 1: return reduce( gcd2, a ) - if hasattr( a[0], "__iter__" ): return reduce( gcd2, a[0] ) - return a[0] - - -def lcm2(a,b): - """Least common multiple of two integers.""" - - return (a*b)//gcd(a,b) - - -def lcm( *a ): - """Least common multiple. - - Usage: lcm( [ 3, 4, 5 ] ) - or: lcm( 3, 4, 5 ) - """ - - if len( a ) > 1: return reduce( lcm2, a ) - if hasattr( a[0], "__iter__" ): return reduce( lcm2, a[0] ) - return a[0] - - - -def factorization( n ): - """Decompose n into a list of (prime,exponent) pairs.""" - - # TREV: doesn't work on python 3 - #assert isinstance( n, types.IntType ) or isinstance( n, types.LongType ) - - if n < 2: return [] - - result = [] - d = 2 - - # Test the small primes: - - for d in smallprimes: - if d > n: break - q, r = divmod( n, d ) - if r == 0: - count = 1 - while d <= n: - n = q - q, r = divmod( n, d ) - if r != 0: break - count = count + 1 - result.append( ( d, count ) ) - - # If n is still greater than the last of our small primes, - # it may require further work: - - if n > smallprimes[-1]: - if is_prime( n ): # If what's left is prime, it's easy: - result.append( ( n, 1 ) ) - else: # Ugh. Search stupidly for a divisor: - d = smallprimes[-1] - while 1: - d = d + 2 # Try the next divisor. - q, r = divmod( n, d ) - if q < d: break # n < d*d means we're done, n = 1 or prime. - if r == 0: # d divides n. How many times? - count = 1 - n = q - while d <= n: # As long as d might still divide n, - q, r = divmod( n, d ) # see if it does. - if r != 0: break - n = q # It does. Reduce n, increase count. - count = count + 1 - result.append( ( d, count ) ) - if n > 1: result.append( ( n, 1 ) ) - - return result - - - -def phi( n ): - """Return the Euler totient function of n.""" - - assert isinstance( n, types.IntType ) or isinstance( n, types.LongType ) - - if n < 3: return 1 - - result = 1 - ff = factorization( n ) - for f in ff: - e = f[1] - if e > 1: - result = result * f[0] ** (e-1) * ( f[0] - 1 ) - else: - result = result * ( f[0] - 1 ) - return result - - -def carmichael( n ): - """Return Carmichael function of n. - - Carmichael(n) is the smallest integer x such that - m**x = 1 mod n for all m relatively prime to n. - """ - - return carmichael_of_factorized( factorization( n ) ) - - -def carmichael_of_factorized( f_list ): - """Return the Carmichael function of a number that is - represented as a list of (prime,exponent) pairs. - """ - - if len( f_list ) < 1: return 1 - - result = carmichael_of_ppower( f_list[0] ) - for i in range( 1, len( f_list ) ): - result = lcm( result, carmichael_of_ppower( f_list[i] ) ) - - return result - -def carmichael_of_ppower( pp ): - """Carmichael function of the given power of the given prime. - """ - - p, a = pp - if p == 2 and a > 2: return 2**(a-2) - else: return (p-1) * p**(a-1) - - - -def order_mod( x, m ): - """Return the order of x in the multiplicative group mod m. - """ - - # Warning: this implementation is not very clever, and will - # take a long time if m is very large. - - if m <= 1: return 0 - - assert gcd( x, m ) == 1 - - z = x - result = 1 - while z != 1: - z = ( z * x ) % m - result = result + 1 - return result - - -def largest_factor_relatively_prime( a, b ): - """Return the largest factor of a relatively prime to b. - """ - - while 1: - d = gcd( a, b ) - if d <= 1: break - b = d - while 1: - q, r = divmod( a, d ) - if r > 0: - break - a = q - return a - - -def kinda_order_mod( x, m ): - """Return the order of x in the multiplicative group mod m', - where m' is the largest factor of m relatively prime to x. - """ - - return order_mod( x, largest_factor_relatively_prime( m, x ) ) - - -def is_prime( n ): - """Return True if x is prime, False otherwise. - - We use the Miller-Rabin test, as given in Menezes et al. p. 138. - This test is not exact: there are composite values n for which - it returns True. - - In testing the odd numbers from 10000001 to 19999999, - about 66 composites got past the first test, - 5 got past the second test, and none got past the third. - Since factors of 2, 3, 5, 7, and 11 were detected during - preliminary screening, the number of numbers tested by - Miller-Rabin was (19999999 - 10000001)*(2/3)*(4/5)*(6/7) - = 4.57 million. - """ - - # (This is used to study the risk of false positives:) - global miller_rabin_test_count - - miller_rabin_test_count = 0 - - if n <= smallprimes[-1]: - if n in smallprimes: return True - else: return False - - if gcd( n, 2*3*5*7*11 ) != 1: return False - - # Choose a number of iterations sufficient to reduce the - # probability of accepting a composite below 2**-80 - # (from Menezes et al. Table 4.4): - - t = 40 - n_bits = 1 + int( math.log( n, 2 ) ) - for k, tt in ( ( 100, 27 ), - ( 150, 18 ), - ( 200, 15 ), - ( 250, 12 ), - ( 300, 9 ), - ( 350, 8 ), - ( 400, 7 ), - ( 450, 6 ), - ( 550, 5 ), - ( 650, 4 ), - ( 850, 3 ), - ( 1300, 2 ), - ): - if n_bits < k: break - t = tt - - # Run the test t times: - - s = 0 - r = n - 1 - while ( r % 2 ) == 0: - s = s + 1 - r = r // 2 - for i in range( t ): - a = smallprimes[ i ] - y = modular_exp( a, r, n ) - if y != 1 and y != n-1: - j = 1 - while j <= s - 1 and y != n - 1: - y = modular_exp( y, 2, n ) - if y == 1: - miller_rabin_test_count = i + 1 - return False - j = j + 1 - if y != n-1: - miller_rabin_test_count = i + 1 - return False - return True - - -def next_prime( starting_value ): - "Return the smallest prime larger than the starting value." - - if starting_value < 2: return 2 - result = ( starting_value + 1 ) | 1 - while not is_prime( result ): result = result + 2 - return result - - -smallprimes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, - 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, - 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, - 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, - 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, - 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, - 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, - 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, - 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, - 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, - 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, - 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, - 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, - 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, - 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, - 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, - 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, - 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, - 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, - 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229] - - - -def testNumberTheory(): - print("Testing NUMBER THEORY") - miller_rabin_test_count = 0 - - # Making sure locally defined exceptions work: - # p = modular_exp( 2, -2, 3 ) - # p = square_root_mod_prime( 2, 3 ) - - #print "Testing gcd..." - assert gcd( 3*5*7, 3*5*11, 3*5*13 ) == 3*5 - assert gcd( [ 3*5*7, 3*5*11, 3*5*13 ] ) == 3*5 - assert gcd( 3 ) == 3 - - #print "Testing lcm..." - assert lcm( 3, 5*3, 7*3 ) == 3*5*7 - assert lcm( [ 3, 5*3, 7*3 ] ) == 3*5*7 - assert lcm( 3 ) == 3 - - #print "Testing next_prime..." - bigprimes = ( 999671, - 999683, - 999721, - 999727, - 999749, - 999763, - 999769, - 999773, - 999809, - 999853, - 999863, - 999883, - 999907, - 999917, - 999931, - 999953, - 999959, - 999961, - 999979, - 999983 ) - - for i in range( len( bigprimes ) - 1 ): - assert next_prime( bigprimes[i] ) == bigprimes[ i+1 ] - - error_tally = 0 - - # Test the square_root_mod_prime function: - - for p in smallprimes[:50]: - #print "Testing square_root_mod_prime for modulus p = %d." % p - squares = [] - - for root in range( 0, 1+p//2 ): - sq = ( root * root ) % p - squares.append( sq ) - calculated = square_root_mod_prime( sq, p ) - if ( calculated * calculated ) % p != sq: - error_tally = error_tally + 1 - print("Failed to find %d as sqrt( %d ) mod %d. Said %d." % \ - ( root, sq, p, calculated )) - - for nonsquare in range( 0, p ): - if nonsquare not in squares: - try: - calculated = square_root_mod_prime( nonsquare, p ) - except SquareRootError: - pass - else: - error_tally = error_tally + 1 - print("Failed to report no root for sqrt( %d ) mod %d." % \ - ( nonsquare, p )) - - # Test the jacobi function: - for m in range( 3, 100, 2 ): - #print "Testing jacobi for modulus m = %d." % m - if is_prime( m ): - squares = [] - for root in range( 1, m ): - if jacobi( root * root, m ) != 1: - error_tally = error_tally + 1 - print("jacobi( %d * %d, %d ) != 1" % ( root, root, m )) - squares.append( root * root % m ) - for i in range( 1, m ): - if not i in squares: - if jacobi( i, m ) != -1: - error_tally = error_tally + 1 - print("jacobi( %d, %d ) != -1" % ( i, m )) - else: # m is not prime. - f = factorization( m ) - for a in range( 1, m ): - c = 1 - for i in f: - c = c * jacobi( a, i[0] ) ** i[1] - if c != jacobi( a, m ): - error_tally = error_tally + 1 - print("%d != jacobi( %d, %d )" % ( c, a, m )) - - -# Test the inverse_mod function: - #print "Testing inverse_mod . . ." - import random - n_tests = 0 - for i in range( 100 ): - m = random.randint( 20, 10000 ) - for j in range( 100 ): - a = random.randint( 1, m-1 ) - if gcd( a, m ) == 1: - n_tests = n_tests + 1 - inv = inverse_mod( a, m ) - if inv <= 0 or inv >= m or ( a * inv ) % m != 1: - error_tally = error_tally + 1 - print("%d = inverse_mod( %d, %d ) is wrong." % ( inv, a, m )) - assert(False) - #assert n_tests > 1000 - #print n_tests, " tests of inverse_mod completed." - #print error_tally, "errors detected." - assert(error_tally == 0) - return 1 diff --git a/TACKpy/pem.py b/TACKpy/pem.py deleted file mode 100644 index a27f070..0000000 --- a/TACKpy/pem.py +++ /dev/null @@ -1,100 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from .compat import * -from .time_funcs import * -from .version import __version__ - -################ PEM ### - -import binascii - -def dePem(s, name): - """Decode a PEM string into a bytearray of its payload. - - The input must contain an appropriate PEM prefix and postfix - based on the input name string, e.g. for name="CERTIFICATE": - - -----BEGIN CERTIFICATE----- - MIIBXDCCAUSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRUQUNL -... - KoZIhvcNAQEFBQADAwA5kw== - -----END CERTIFICATE----- - - The first such PEM block in the input will be found, and its - payload will be base64 decoded and returned. - """ - prefix = "-----BEGIN %s-----" % name - postfix = "-----END %s-----" % name - start = s.find(prefix) - if start == -1: - raise SyntaxError("Missing PEM prefix") - end = s.find(postfix, start+len(prefix)) - if end == -1: - raise SyntaxError("Missing PEM postfix") - s = s[start+len("-----BEGIN %s-----" % name) : end] - retBytes = a2b_base64(s) # May raise SyntaxError - return retBytes - -def dePemList(s, name): - """Decode a sequence of PEM blocks into a list of bytearrays. - - The input must contain any number of PEM blocks, each with the appropriate - PEM prefix and postfix based on the input name string, e.g. for - name="TACK BREAK SIG". Arbitrary text can appear between and before and - after the PEM blocks. For example: - - " Created by TACK.py 0.9.3 Created at 2012-02-01T00:30:10Z -----BEGIN TACK - BREAK SIG----- - ATKhrz5C6JHJW8BF5fLVrnQss6JnWVyEaC0p89LNhKPswvcC9/s6+vWLd9snYTUv - YMEBdw69PUP8JB4AdqA3K6Ap0Fgd9SSTOECeAKOUAym8zcYaXUwpk0+WuPYa7Zmm - SkbOlK4ywqt+amhWbg9txSGUwFO5tWUHT3QrnRlE/e3PeNFXLx5Bckg= -----END TACK - BREAK SIG----- Created by TACK.py 0.9.3 Created at 2012-02-01T00:30:11Z - -----BEGIN TACK BREAK SIG----- - ATKhrz5C6JHJW8BF5fLVrnQss6JnWVyEaC0p89LNhKPswvcC9/s6+vWLd9snYTUv - YMEBdw69PUP8JB4AdqA3K6BVCWfcjN36lx6JwxmZQncS6sww7DecFO/qjSePCxwM - +kdDqX/9/183nmjx6bf0ewhPXkA0nVXsDYZaydN8rJU1GaMlnjcIYxY= -----END TACK - BREAK SIG----- " - - All such PEM blocks will be found, decoded, and return in an ordered list - of bytearrays, which may have zero elements if not PEM blocks are found. - """ - bList = [] - prefix = "-----BEGIN %s-----" % name - postfix = "-----END %s-----" % name - while 1: - start = s.find(prefix) - if start == -1: - return bList - end = s.find(postfix, start+len(prefix)) - if end == -1: - raise SyntaxError("Missing PEM postfix") - s2 = s[start+len(prefix) : end] - retBytes = a2b_base64(s2) # May raise SyntaxError - bList.append(retBytes) - s = s[end+len(postfix) : ] - -def pem(b, name): - """Encode a payload bytearray into a PEM string. - - The input will be base64 encoded, then wrapped in a PEM prefix/postfix - based on the name string, e.g. for name="CERTIFICATE": - - -----BEGIN CERTIFICATE----- - MIIBXDCCAUSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRUQUNL -... - KoZIhvcNAQEFBQADAwA5kw== - -----END CERTIFICATE----- - """ - s1 = b2a_base64(b)[:-1] # remove terminating \n - s2 = "" - while s1: - s2 += s1[:64] + "\n" - s1 = s1[64:] - s = ("-----BEGIN %s-----\n" % name) + s2 + \ - ("-----END %s-----\n" % name) - return s - -def pemSniff(inStr, name): - searchStr = "-----BEGIN %s-----" % name - return searchStr in inStr diff --git a/TACKpy/rijndael.py b/TACKpy/rijndael.py deleted file mode 100644 index cca8faa..0000000 --- a/TACKpy/rijndael.py +++ /dev/null @@ -1,407 +0,0 @@ -# Authors: -# Bram Cohen - main author -# Trevor Perrin - minor changes for Python compatibility -# -# See the LICENSE file for legal information regarding use of this file. -# Also see Bram Cohen's statement below - -################ RIJNDAEL ### -# Trevor edited below code for python 3, and ease of packaging - -""" -A pure python (slow) implementation of rijndael with a decent interface - -To include - - -from rijndael import rijndael - -To do a key setup - - -r = rijndael(key, block_size = 16) - -key must be a string of length 16, 24, or 32 -blocksize must be 16, 24, or 32. Default is 16 - -To use - - -ciphertext = r.encrypt(plaintext) -plaintext = r.decrypt(ciphertext) - -If any strings are of the wrong length a ValueError is thrown -""" - -# ported from the Java reference code by Bram Cohen, bram@gawth.com, April 2001 -# this code is public domain, unless someone makes -# an intellectual property claim against the reference -# code, in which case it can be made public domain by -# deleting all the comments and renaming all the variables - -import copy -import string - - - -#TREV 2011 - is this still needed? seems not -#----------------------- -#TREV - ADDED BECAUSE THERE'S WARNINGS ABOUT INT OVERFLOW BEHAVIOR CHANGING IN -#2.4..... -#import os -#if os.name != "java": -# import exceptions -# if hasattr(exceptions, "FutureWarning"): -# import warnings -# warnings.filterwarnings("ignore", category=FutureWarning, append=1) -#----------------------- - - - -shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]], - [[0, 0], [1, 5], [2, 4], [3, 3]], - [[0, 0], [1, 7], [3, 5], [4, 4]]] - -# [keysize][block_size] -num_rounds = {16: {16: 10, 24: 12, 32: 14}, -24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}} - -A = [[1, 1, 1, 1, 1, 0, 0, 0], - [0, 1, 1, 1, 1, 1, 0, 0], - [0, 0, 1, 1, 1, 1, 1, 0], - [0, 0, 0, 1, 1, 1, 1, 1], - [1, 0, 0, 0, 1, 1, 1, 1], - [1, 1, 0, 0, 0, 1, 1, 1], - [1, 1, 1, 0, 0, 0, 1, 1], - [1, 1, 1, 1, 0, 0, 0, 1]] - -# produce log and alog tables, needed for multiplying in the -# field GF(2^m) (generator = 3) -alog = [1] -for i in range(255): - j = (alog[-1] << 1) ^ alog[-1] - if j & 0x100 != 0: - j ^= 0x11B - alog.append(j) - -log = [0] * 256 -for i in range(1, 255): - log[alog[i]] = i - -# multiply two elements of GF(2^m) -def mul(a, b): - if a == 0 or b == 0: - return 0 - return alog[(log[a & 0xFF] + log[b & 0xFF]) % 255] - -# substitution box based on F^{-1}(x) -box = [[0] * 8 for i in range(256)] -box[1][7] = 1 -for i in range(2, 256): - j = alog[255 - log[i]] - for t in range(8): - box[i][t] = (j >> (7 - t)) & 0x01 - -B = [0, 1, 1, 0, 0, 0, 1, 1] - -# affine transform: box[i] <- B + A*box[i] -cox = [[0] * 8 for i in range(256)] -for i in range(256): - for t in range(8): - cox[i][t] = B[t] - for j in range(8): - cox[i][t] ^= A[t][j] * box[i][j] - -# S-boxes and inverse S-boxes -S = [0] * 256 -Si = [0] * 256 -for i in range(256): - S[i] = cox[i][0] << 7 - for t in range(1, 8): - S[i] ^= cox[i][t] << (7-t) - Si[S[i] & 0xFF] = i - -# T-boxes -G = [[2, 1, 1, 3], - [3, 2, 1, 1], - [1, 3, 2, 1], - [1, 1, 3, 2]] - -AA = [[0] * 8 for i in range(4)] - -for i in range(4): - for j in range(4): - AA[i][j] = G[i][j] - AA[i][i+4] = 1 - -for i in range(4): - pivot = AA[i][i] - if pivot == 0: - t = i + 1 - while AA[t][i] == 0 and t < 4: - t += 1 - assert t != 4, 'G matrix must be invertible' - for j in range(8): - AA[i][j], AA[t][j] = AA[t][j], AA[i][j] - pivot = AA[i][i] - for j in range(8): - if AA[i][j] != 0: - AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255] - for t in range(4): - if i != t: - for j in range(i+1, 8): - AA[t][j] ^= mul(AA[i][j], AA[t][i]) - AA[t][i] = 0 - -iG = [[0] * 4 for i in range(4)] - -for i in range(4): - for j in range(4): - iG[i][j] = AA[i][j + 4] - -def mul4(a, bs): - if a == 0: - return 0 - r = 0 - for b in bs: - r <<= 8 - if b != 0: - r = r | mul(a, b) - return r - -T1 = [] -T2 = [] -T3 = [] -T4 = [] -T5 = [] -T6 = [] -T7 = [] -T8 = [] -U1 = [] -U2 = [] -U3 = [] -U4 = [] - -for t in range(256): - s = S[t] - T1.append(mul4(s, G[0])) - T2.append(mul4(s, G[1])) - T3.append(mul4(s, G[2])) - T4.append(mul4(s, G[3])) - - s = Si[t] - T5.append(mul4(s, iG[0])) - T6.append(mul4(s, iG[1])) - T7.append(mul4(s, iG[2])) - T8.append(mul4(s, iG[3])) - - U1.append(mul4(t, iG[0])) - U2.append(mul4(t, iG[1])) - U3.append(mul4(t, iG[2])) - U4.append(mul4(t, iG[3])) - -# round constants -rcon = [1] -r = 1 -for t in range(1, 30): - r = mul(2, r) - rcon.append(r) - -del A -del AA -del pivot -del B -del G -del box -del log -del alog -del i -del j -del r -del s -del t -del mul -del mul4 -del cox -del iG - -class rijndael: - def __init__(self, key, block_size = 16): - if block_size != 16 and block_size != 24 and block_size != 32: - raise ValueError('Invalid block size: ' + str(block_size)) - if len(key) != 16 and len(key) != 24 and len(key) != 32: - raise ValueError('Invalid key size: ' + str(len(key))) - self.block_size = block_size - - ROUNDS = num_rounds[len(key)][block_size] - BC = block_size // 4 - # encryption round keys - Ke = [[0] * BC for i in range(ROUNDS + 1)] - # decryption round keys - Kd = [[0] * BC for i in range(ROUNDS + 1)] - ROUND_KEY_COUNT = (ROUNDS + 1) * BC - KC = len(key) // 4 - - # copy user material bytes into temporary ints - tk = [] - for i in range(0, KC): - tk.append((key[i * 4] << 24) | (key[i * 4 + 1] << 16) | - (key[i * 4 + 2]) << 8 | key[i * 4 + 3]) - - # copy values into round key arrays - t = 0 - j = 0 - while j < KC and t < ROUND_KEY_COUNT: - Ke[t // BC][t % BC] = tk[j] - Kd[ROUNDS - (t // BC)][t % BC] = tk[j] - j += 1 - t += 1 - tt = 0 - rconpointer = 0 - while t < ROUND_KEY_COUNT: - # extrapolate using phi (the round key evolution function) - tt = tk[KC - 1] - tk[0] ^= (S[(tt >> 16) & 0xFF] & 0xFF) << 24 ^ \ - (S[(tt >> 8) & 0xFF] & 0xFF) << 16 ^ \ - (S[ tt & 0xFF] & 0xFF) << 8 ^ \ - (S[(tt >> 24) & 0xFF] & 0xFF) ^ \ - (rcon[rconpointer] & 0xFF) << 24 - rconpointer += 1 - if KC != 8: - for i in range(1, KC): - tk[i] ^= tk[i-1] - else: - for i in range(1, KC // 2): - tk[i] ^= tk[i-1] - tt = tk[KC // 2 - 1] - tk[KC // 2] ^= (S[ tt & 0xFF] & 0xFF) ^ \ - (S[(tt >> 8) & 0xFF] & 0xFF) << 8 ^ \ - (S[(tt >> 16) & 0xFF] & 0xFF) << 16 ^ \ - (S[(tt >> 24) & 0xFF] & 0xFF) << 24 - for i in range(KC // 2 + 1, KC): - tk[i] ^= tk[i-1] - # copy values into round key arrays - j = 0 - while j < KC and t < ROUND_KEY_COUNT: - Ke[t // BC][t % BC] = tk[j] - Kd[ROUNDS - (t // BC)][t % BC] = tk[j] - j += 1 - t += 1 - # inverse MixColumn where needed - for r in range(1, ROUNDS): - for j in range(BC): - tt = Kd[r][j] - Kd[r][j] = U1[(tt >> 24) & 0xFF] ^ \ - U2[(tt >> 16) & 0xFF] ^ \ - U3[(tt >> 8) & 0xFF] ^ \ - U4[ tt & 0xFF] - self.Ke = Ke - self.Kd = Kd - - def encrypt(self, plaintext): - if len(plaintext) != self.block_size: - raise ValueError('wrong block length, expected ' + - str(self.block_size) + ' got ' + str(len(plaintext))) - Ke = self.Ke - - BC = self.block_size // 4 - ROUNDS = len(Ke) - 1 - if BC == 4: - SC = 0 - elif BC == 6: - SC = 1 - else: - SC = 2 - s1 = shifts[SC][1][0] - s2 = shifts[SC][2][0] - s3 = shifts[SC][3][0] - a = [0] * BC - # temporary work array - t = [] - # plaintext to ints + key - for i in range(BC): - t.append((plaintext[i * 4 ] << 24 | - plaintext[i * 4 + 1] << 16 | - plaintext[i * 4 + 2] << 8 | - plaintext[i * 4 + 3] ) ^ Ke[0][i]) - # apply round transforms - for r in range(1, ROUNDS): - for i in range(BC): - a[i] = (T1[(t[ i ] >> 24) & 0xFF] ^ - T2[(t[(i + s1) % BC] >> 16) & 0xFF] ^ - T3[(t[(i + s2) % BC] >> 8) & 0xFF] ^ - T4[ t[(i + s3) % BC] & 0xFF] ) ^ Ke[r][i] - t = copy.copy(a) - # last round is special - result = [] - for i in range(BC): - tt = Ke[ROUNDS][i] - result.append((S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) - result.append((S[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) - result.append((S[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) - result.append((S[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF) - return bytearray(result) - - def decrypt(self, ciphertext): - if len(ciphertext) != self.block_size: - raise ValueError('wrong block length, expected ' + - str(self.block_size) + ' got ' + str(len(ciphertext))) - Kd = self.Kd - - BC = self.block_size // 4 - ROUNDS = len(Kd) - 1 - if BC == 4: - SC = 0 - elif BC == 6: - SC = 1 - else: - SC = 2 - s1 = shifts[SC][1][1] - s2 = shifts[SC][2][1] - s3 = shifts[SC][3][1] - a = [0] * BC - # temporary work array - t = [0] * BC - # ciphertext to ints + key - for i in range(BC): - t[i] = (ciphertext[i * 4 ] << 24 | - ciphertext[i * 4 + 1] << 16 | - ciphertext[i * 4 + 2] << 8 | - ciphertext[i * 4 + 3] ) ^ Kd[0][i] - # apply round transforms - for r in range(1, ROUNDS): - for i in range(BC): - a[i] = (T5[(t[ i ] >> 24) & 0xFF] ^ - T6[(t[(i + s1) % BC] >> 16) & 0xFF] ^ - T7[(t[(i + s2) % BC] >> 8) & 0xFF] ^ - T8[ t[(i + s3) % BC] & 0xFF] ) ^ Kd[r][i] - t = copy.copy(a) - # last round is special - result = [] - for i in range(BC): - tt = Kd[ROUNDS][i] - result.append((Si[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) - result.append((Si[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) - result.append((Si[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) - result.append((Si[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF) - return bytearray(result) - -def encrypt(key, block): - return rijndael(key, len(block)).encrypt(block) - -def decrypt(key, block): - return rijndael(key, len(block)).decrypt(block) - -def testRijndael(): - print("Testing RIJNDAEL") - def t(kl, bl): - b = bytearray(b'b') * bl - r = rijndael(bytearray(b'a') * kl, bl) - assert r.decrypt(r.encrypt(b)) == b - t(16, 16) - t(16, 24) - t(16, 32) - t(24, 16) - t(24, 24) - t(24, 32) - t(32, 16) - t(32, 24) - t(32, 32) - return 1 diff --git a/TACKpy/ssl_cert.py b/TACKpy/ssl_cert.py deleted file mode 100644 index 2ace3a2..0000000 --- a/TACKpy/ssl_cert.py +++ /dev/null @@ -1,221 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from .misc import * -from .pem import * -from .asn1 import * -from .time_funcs import * -from .cryptomath import * -from .tack_structures import * -from .constants import * - -################ SSL CERT ### - -# NOTE!: lengths are hardcoded in write(), be aware if changing... -oid_TACK = bytearray(b"\x2B\x06\x01\x04\x01\x82\xB0\x34\x01") - -class SSL_Cert: - def __init__(self): - self.key_sha256 = bytearray(32) - self.cert_sha256 = bytearray(32) - self.notAfter = 0 - # Below values are populated for TACK certs - self.tackExt = None - # Below values hold cert contents excluding TACK stuff - self.preExtBytes = None - self.extBytes = None - self.postExtBytes = None - - def create(self, tackExt = None): - self.tackExt = tackExt - self.preExtBytes = a2b_hex( -"a003020102020100300d06092a864886f70d0101050500300f310d300b06035504031" -"3045441434b301e170d3031303730353138303534385a170d34343037303431383035" -"34385a300f310d300b060355040313045441434b301f300d06092a864886f70d01010" -"10500030e00300b0204010203050203010001") - # Below is BasicConstraints, saving space by omitting - #self.extBytes = binascii.a2b_hex(\ -#"300c0603551d13040530030101ff") - self.extBytes = bytearray() - self.postExtBytes = a2b_hex( -"300d06092a864886f70d01010505000303003993") - - def open(self, filename): - # May raise IOError or SyntaxError - try: - sslStr = open(filename, "rU").read() # IOError, UnicodeDecodeError - self.parsePem(sslStr) # SyntaxError - return - except (UnicodeDecodeError, SyntaxError): - # File had non-text chars in it (python3), *OR* - # File did not PEM-decode - pass - sslBytes = bytearray(open(filename, "rb").read()) # IOError - self.parse(sslBytes) # SyntaxError - - def matches(self, tack): - if tack.version == TACK_Version.v1: - return self.key_sha256 == tack.sig.target_sha256 - return False - - def parsePem(self, s): - b = dePem(s, "CERTIFICATE") - self.parse(b) - - def parse(self, b): - p = ASN1Parser(b) - - #Get the tbsCertificate - tbsCertificateP = p.getChild(0) - - #Is the optional version field present? - #This determines which index the key is at - if tbsCertificateP.value[0]==0xA0: - subjectPublicKeyInfoIndex = 6 - validityIndex = 4 - else: - subjectPublicKeyInfoIndex = 5 - validityIndex = 3 - #Get the subjectPublicKeyInfo - spkiP = tbsCertificateP.getChild(subjectPublicKeyInfoIndex) - - #Parse the notAfter time - validityP = tbsCertificateP.getChild(validityIndex) - notAfterP = validityP.getChild(1) - if notAfterP.type == 0x17: # UTCTime - self.notAfter = parseASN1UTCTime(notAfterP.value) - elif notAfterP.type == 0x18: # GeneralizedTime - self.notAfter = parseASN1GeneralizedTime(notAfterP.value) - else: - raise SyntaxError() - - # Get the hash values - self.cert_sha256 = SHA256(b) - self.key_sha256 = SHA256(spkiP.getTotalBytes()) - - # Check if this is a TACK certificate: - #Get the tbsCertificate - versionP = tbsCertificateP.getChild(0) - if versionP.type != 0xA0: # i.e. tag of [0], version - return # X.509 version field not present - versionPP = versionP.getTagged() - if versionPP.value != bytearray([0x02]): - return # X.509 version field does not equal v3 - - # Find extensions element - x = 0 - while 1: - certFieldP = tbsCertificateP.getChild(x) - if not certFieldP: - raise SyntaxError("X.509 extensions not present") - if certFieldP.type == 0xA3: # i.e. tag of [3], extensions - break - x += 1 - - self.preExtBytes = b[versionP.offset : certFieldP.offset] - self.extBytes = bytearray() - - # Iterate through extensions - x = 0 - certFieldPP = certFieldP.getTagged() - while 1: - extFieldP = certFieldPP.getChild(x) - if not extFieldP: - break - - # Check the extnID and parse out TACK if present - extnIDP = extFieldP.getChild(0) - if extnIDP.value == oid_TACK: - if self.tackExt: - raise SyntaxError("More than one TACK Extension") - - # OK! We found a TACK, parse it.. - self.tackExt = TACK_Extension() - self.tackExt.parse(extFieldP.getChild(1).value) - else: - # Collect all non-TACK extensions: - self.extBytes += b[extFieldP.offset : \ - extFieldP.offset + extFieldP.getTotalLength()] - x += 1 - - # Finish copying the tail of the certificate - self.postExtBytes = b[certFieldP.offset + certFieldP.getTotalLength():] - - def write(self): - b = bytearray(0) - if self.tackExt: - # type=SEQ,len=?,type=6,len=9(for OID), - # type=4,len=?,TACK - TACKBytes = self.tackExt.write() - b = bytearray([4]) + asn1Length(len(TACKBytes)) + TACKBytes - b = bytearray([6,9]) + oid_TACK + b - b = bytearray([0x30]) + asn1Length(len(b)) + b - - b = b + self.extBytes # add non-TACK extensions after TACK - # Add length fields for extensions and its enclosing tag - b = bytearray([0x30]) + asn1Length(len(b)) + b - b = bytearray([0xA3]) + asn1Length(len(b)) + b - # Add prefix of tbsCertificate, then its type/length fields - b = self.preExtBytes + b - b = bytearray([0x30]) + asn1Length(len(b)) + b - # Add postfix of Certificate (ie SignatureAlgorithm, SignatureValue) - # then its prefix'd type/length fields - b = b + self.postExtBytes - b = bytearray([0x30]) + asn1Length(len(b)) + b - return b - - def writePem(self): - b = self.write() - return pem(b, "CERTIFICATE") - def writeText(self): - s = \ -"""key_sha256 = 0x%s -notAfter = %s -""" % (\ - writeBytes(self.key_sha256), - posixTimeToStr(self.notAfter, True)) - if self.tackExt: - s += "\n" + self.tackExt.writeText() - return s - -def testSSLCert(): - print("Testing SSL CERT") - s = """ ------BEGIN CERTIFICATE----- -MIIFSzCCBDOgAwIBAgIHJ6JvWHUrOTANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE -BhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAY -BgNVBAoTEUdvRGFkZHkuY29tLCBJbmMuMTMwMQYDVQQLEypodHRwOi8vY2VydGlm -aWNhdGVzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkxMDAuBgNVBAMTJ0dvIERhZGR5 -IFNlY3VyZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTERMA8GA1UEBRMIMDc5Njky -ODcwHhcNMTEwNzA4MDAxOTU3WhcNMTIwNzA4MDAxOTU3WjBPMRQwEgYDVQQKFAsq -LnRyZXZwLm5ldDEhMB8GA1UECxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMRQw -EgYDVQQDFAsqLnRyZXZwLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBAMgawQKi4zY4TTz1RNL7klt/ibvjG+jGqBYlc6qjUiTQORD3fUrdAF83Alav -JiC3rrwfvarL8KpPn7zQQOOk+APwzFxn0sVphDvAN8E7xI/cC7es08EYA9/DDN7r -VTe/wvbs77CL5AniRSJyAP5puvSUHgixingTgYmnkIgC+3ZFqyfz2uenxvkPkoUT -QEBkm2uEcBOwBMXAih1fdsuhEiJ9qpmejpIEvxLIDoMnCWTPs897zhwr3epQkn5g -lKQ9H+FnEo5Jf8YBM4YhAzwG/8pyfc8NtOHafKUb5PhSIC7Vy7N2EBQ4y9kDOZc+ -r0Vguq4p+Nncc32JI/i1Cdj/lO0CAwEAAaOCAa4wggGqMA8GA1UdEwEB/wQFMAMB -AQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA4GA1UdDwEB/wQEAwIF -oDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmdvZGFkZHkuY29tL2dkczEt -NTIuY3JsME0GA1UdIARGMEQwQgYLYIZIAYb9bQEHFwEwMzAxBggrBgEFBQcCARYl -aHR0cHM6Ly9jZXJ0cy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5LzCBgAYIKwYBBQUH -AQEEdDByMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5nb2RhZGR5LmNvbS8wSgYI -KwYBBQUHMAKGPmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3Np -dG9yeS9nZF9pbnRlcm1lZGlhdGUuY3J0MB8GA1UdIwQYMBaAFP2sYTKTbEXW4u6F -X5q653aZaMznMCEGA1UdEQQaMBiCCyoudHJldnAubmV0ggl0cmV2cC5uZXQwHQYD -VR0OBBYEFCYv4a9+enZGS27wqAv+TPfJOOb7MA0GCSqGSIb3DQEBBQUAA4IBAQA+ -2OKO77vpwKtoKddDtamBokiVhHrfw0c7ALGysOXtss1CKV2WgH4FdNuh9pFkVZB2 -mKZ7keS7EMW11OzgBR3pRRk0AkNYtDsOJEXA2+1NLFgrtdujHrDX4WIoi9MGbqB5 -TfK08XufM7OP3yXDLtMxyUtyjprFhdxPE+9p/GJ0IVdZrMmzYTjyCOO8+okY9zAQ -RVUKuxd+eEaH3BpPAau4MP2n24gy6WEsJ2auB81ee9fDnx/tfKPqvyuc4r4/Z4aL -5CvQvlPHaG/TTXXNh3pZFl3d/J5/76ZfeQzQtZ+dCrE4a4601Q4hBBXEq5gQfaof -H4yTGzfDv+JLIICAIcCs ------END CERTIFICATE-----""" - sslc = SSL_Cert() - sslc.parsePem(s) - assert(sslc.key_sha256 == a2b_hex("ffd30bcb84dbbc211a510875694354c58863d84fb7fc5853dfe36f4be2eb2e50")) - assert(sslc.cert_sha256 == a2b_hex("1a50e3de3a153f33b314b67c1aacc2f59fc99c49b8449c33dcc3665663e2bff1")) - assert(posixTimeToStr(sslc.notAfter, True) == "2012-07-08T00:19:57Z") - assert(isinstance(sslc.writeText(), str)) - return 1 diff --git a/TACKpy/struct_parser.py b/TACKpy/struct_parser.py deleted file mode 100644 index b125f7d..0000000 --- a/TACKpy/struct_parser.py +++ /dev/null @@ -1,65 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from .compat import * - -################ STRUCT PARSER ### - -class Writer: - def __init__(self, totalLength): - self.index = 0 - self.bytes = bytearray(totalLength) - - def add(self, x, elementLength): - """Writes 'elementLength' bytes, input is either an integer - (written as big-endian) or a sequence of bytes""" - if isinstance(x, int): - assert(x >= 0 and x < 2**(8*elementLength)) - newIndex = self.index + elementLength-1 - while newIndex >= self.index: - self.bytes[newIndex] = x & 0xFF - x >>= 8 - newIndex -= 1 - else: - assert(len(x) == elementLength) - for i in range(elementLength): - self.bytes[self.index + i] = x[i] - self.index += elementLength - - def addVarSeq(self, seq, elementLength, lengthLength): - """Writes a sequence of elements prefixed by a - total-length field of lengthLength bytes""" - self.add(len(seq)*elementLength, lengthLength) - for e in seq: - self.add(e, elementLength) - -class Parser: - def __init__(self, bytes): - self.bytes = bytes - self.index = 0 - - def getInt(self, elementLength): - """Reads an integer of 'length' bytes""" - if self.index + elementLength > len(self.bytes): - raise SyntaxError() - x = 0 - for count in range(elementLength): - x <<= 8 - x |= self.bytes[self.index] - self.index += 1 - return x - - def getBytes(self, elementLength): - """Reads some number of bytes as determined by 'elementLength'""" - bytes = self.bytes[self.index : self.index + elementLength] - self.index += elementLength - return bytes - - def getVarSeqBytes(self, elementLength, lengthLength): - dataLength = self.getInt(lengthLength) - if dataLength % elementLength != 0: - raise SyntaxError() - return [self.getBytes(elementLength) for x in \ - range(dataLength//elementLength)] - - diff --git a/TACKpy/tack_structures.py b/TACKpy/tack_structures.py deleted file mode 100644 index 42b8238..0000000 --- a/TACKpy/tack_structures.py +++ /dev/null @@ -1,360 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from .constants import * -from .struct_parser import * -from .time_funcs import * -from .misc import * -from .pem import * -from .ecdsa_wrappers import * -from .tackid import * - -################ TACK STRUCTURES ### - -class TACK: - length = 166 - - def __init__(self): - """Create an uninitialized TACK. - - Use create() or parsePem() to populate.""" - self.public_key = bytearray(64) # EC public key using NIST-P256 - self.min_generation = 0 # 8-bit integer - self.generation = 0 # 8-bit integer - self.expiration = 0 # 32-bit unsigned integer encoding POSIX time - self.target_hash = bytearray(32) # 32-byte SHA256 result - self.signature = bytearray(64) # ECDSA signature using NIST-P256 - - def create(self, public_key, min_generation, generation, - expiration, target_hash, signFunc=ecdsa256Sign): - """Initialize a TACK. - """ - assert(len(public_key) == 64) - self.public_key = public_key - self.sign(min_generation, generation, expiration, target_hash, - signFunc) - - def sign(self, min_generation, generation, - expiration, target_hash, signFunc=ecdsa256Sign): - """Create a new TACK sig for the TACK. - - The existing TACK sig is replaced. The existing TACK_Key is - unmodified. - """ - assert(min_generation >= 0 and min_generation <= 255) - assert(generation >= 0 and generation <= 255 and - generation >= min_generation) - assert(expiration >=0 and expiration <= 2**32-1) - assert(len(target_hash) == 32) - self.min_generation = min_generation - self.generation = generation - self.expiration = expiration - self.target_hash = target_hash - self.signature = signFunc(self.getToBeSigned()) - - def getTACKID(self): - return makeTACKID(self.public_key) - - def getToBeSigned(self): - return bytearray("tack_sig", "ascii") + self.write()[:-64] - - def verifySignature(self, verifyFunc): - bytesToVerify = self.getToBeSigned() - return verifyFunc(self.public_key, bytesToVerify, self.signature) - - def parsePem(self, s, verifyFunc=ecdsa256Verify): - """Parse a string containing a PEM file for a TACK. - - Raise a SyntaxError if input is malformed, including signature - validation failure. - """ - b = dePem(s, "TACK") - if len(b) != TACK.length: - raise SyntaxError("TACK is the wrong size") - self.parse(b, verifyFunc) - - def parse(self, b, verifyFunc=ecdsa256Verify): - """Parse a bytearray containing a TACK. - - Raise a SyntaxError if input is malformed, including signature - validation failure. - """ - p = Parser(b) - self.public_key = p.getBytes(64) - self.min_generation = p.getInt(1) - self.generation = p.getInt(1) - self.expiration = p.getInt(4) - self.target_hash = p.getBytes(32) - self.signature = p.getBytes(64) - - if not self.verifySignature(verifyFunc): - raise SyntaxError("Signature verification failure") - if p.index != len(b): - raise SyntaxError("Excess bytes in TACK") - - def writePem(self): - """Return a string containing a PEM file for the TACK.""" - return pem(self.write(), "TACK") - - def write(self): - """Return a bytearray containing the TACK.""" - w = Writer(TACK.length) - w.add(self.public_key, 64) - w.add(self.min_generation, 1) - w.add(self.generation, 1) - w.add(self.expiration, 4) - w.add(self.target_hash, 32) - w.add(self.signature, 64) - return w.bytes - - def writeText(self): - """Return a readable string describing this TACK. - - Used by the "TACK view" command to display TACK objects.""" - s =\ -"""TACK ID = %s -min_generation = %d -generation = %d -expiration = %s -target_hash = %s\n""" % \ -(self.getTACKID(), -self.min_generation, -self.generation, -posixTimeToStr(self.expiration*60), -writeBytes(self.target_hash)) - return s - -class TACK_Break_Sig: - length = 128 - - def __init__(self): - """Create an uninitialized TACK_Break_Sig. - - Use create() or parsePem() to populate.""" - self.public_key = bytearray(64) - self.signature = bytearray(64) # ECDSA signature on TACK_Key - - def create(self, public_key, signFunc): - """Initialize a TACK_Break_Sig with a TACK_Key and 64-byte bytearray. - """ - assert(len(public_key) == 64) - self.public_key = public_key - self.signature = signFunc(bytearray("tack_break_sig", "ascii")) - - def getTACKID(self): - return makeTACKID(self.public_key) - - def verifySignature(self, verifyFunc): - return verifyFunc(self.public_key, bytearray("tack_break_sig", "ascii"), - self.signature) - - def parsePem(self, s, verifyFunc=ecdsa256Verify): - """Parse a string containing a PEM file for a TACK_Break_Sig. - - Raise a SyntaxError if input is malformed. - """ - b = dePem(s, "TACK BREAK SIG") - if len(b) != TACK_Break_Sig.length: - raise SyntaxError("Break Sig is the wrong size") - self.parse(b, verifyFunc) - - def parse(self, b, verifyFunc=ecdsa256Verify): - """Parse a bytearray containing a TACK_Break_Sig. - - Raise a SyntaxError if input is malformed, including signature - validation failure. - """ - p = Parser(b) - self.public_key = p.getBytes(64) - self.signature = p.getBytes(64) - if not self.verifySignature(verifyFunc): - raise SyntaxError("Signature verification failure") - if p.index != len(b): - raise SyntaxError("Excess bytes in TACK_Break_Sig") - - def write(self): - """Return a bytearray containing the TACK_Break_Sig.""" - w = Writer(TACK_Break_Sig.length) - w.add(self.public_key, 64) - w.add(self.signature, 64) - assert(w.index == len(w.bytes)) # did we fill entire bytearray? - return w.bytes - - def writePem(self): - """Return a string containing a PEM file for the TACK_Break_Sig.""" - return pem(self.write(), "TACK BREAK SIG") - - def writeText(self): - """Return a readable string describing this TACK_Break_Sig. - - Used by the "TACK view" command to display TACK objects.""" - s = "Breaks TACK ID = %s\n" % makeTACKID(self.public_key) - return s - - @staticmethod - def parsePemList(s, verifyFunc=ecdsa256Verify): - """Parse a string containing a sequence of PEM Break Sigs. - - Raise a SyntaxError if input is malformed. - """ - breakSigs = [] - bList = dePemList(s, "TACK BREAK SIG") - for b in bList: - breakSig = TACK_Break_Sig() - breakSig.parse(b, verifyFunc) - breakSigs.append(breakSig) - return breakSigs - - -class TACK_Extension: - - def __init__(self): - self.tack = None - self.break_sigs = [] - self.pin_activation = TACK_Activation.disabled - - def create(self, tack, break_sigs, pin_activation): - self.tack = tack - self.break_sigs = break_sigs - self.pin_activation = pin_activation - - def isEmpty(self): - return (not self.tack and not self.break_sigs) - - def parse(self, b, verifyFunc=ecdsa256Verify): - p = Parser(b) - tackLen = p.getInt(1) - if tackLen != TACK.length: - raise SyntaxError("TACK wrong size") - else: - b2 = p.getBytes(tackLen) - self.tack = TACK() - self.tack.parse(b2, verifyFunc) - - sigsLen = p.getInt(2) - if sigsLen >1024: - raise SyntaxError("break_sigs too large") - elif sigsLen % TACK_Break_Sig.length != 0: - raise SyntaxError("break sigs wrong size") - else: - self.break_sigs = [] - b2 = p.getBytes(sigsLen) - while b2: - breakSig = TACK_Break_Sig() - breakSig.parse(b2[:TACK_Break_Sig.length], verifyFunc) - self.break_sigs.append(breakSig) - b2 = b2[TACK_Break_Sig.length:] - self.pin_activation = p.getInt(1) - if self.pin_activation not in TACK_Activation.all: - raise SyntaxError("Bad pin_activation value") - if p.index != len(b): - raise SyntaxError("Excess bytes in TACK_Extension") - - def write(self): - length = 0 - if self.tack: - length += TACK.length - if self.break_sigs: - length += len(self.break_sigs) * TACK_Break_Sig.length - w = Writer(length+4) - if self.tack: - w.add(TACK.length, 1) - w.add(self.tack.write(), TACK.length) - else: - w.add(0, 1) - if self.break_sigs: - w.add(len(self.break_sigs) * TACK_Break_Sig.length, 2) - for break_sig in self.break_sigs: - w.add(break_sig.write(), TACK_Break_Sig.length) - else: - w.add(0, 2) - w.add(self.pin_activation, 1) - assert(w.index == len(w.bytes)) # did we fill entire bytearray? - return w.bytes - - def writeText(self): - return writeTextTACKStructures(self.tack, self.break_sigs, - self.pin_activation) - - -def writeTextTACKStructures(tack, breakSigs, pin_activation, - tackidOnly=False): - s = "" - if tack: - if not tackidOnly: - s += tack.writeText() - else: - s += tack.getTACKID()+"\n" - if breakSigs: - for breakSig in breakSigs: - s += breakSig.writeText() - s += "pin_activation = %s\n" % \ - TACK_Activation.strings[pin_activation] - return s - -def testTACKStructures(): - print("Testing TACK STRUCTURES") - s = """ ------BEGIN TACK----- -AR3jPQCkF1ud7U9hb44zpFuRLz6EKE22hwQRkQFEdxT/YUd5Qi7FYCtVk1+P1nh+ -/ZPihRFAXJH0WkN4L5bSS7wBAAACQnQFvz5Foz1gTLBokQZWIokmalgb+AkeB8Zp -58j2QptdIOYCTri1KsSYPuRK4LSannEvgQxfG58c84RxNu69VS6ihZVNlkx8/zGU -C6ag+gdToKLmPJDQIXb0cdO04CbTu2Hc ------END TACK-----""" - t = TACK() - t.parsePem(s) - assert(t.key.type == TACK_Key_Type.v1) - assert(t.key.public_key == a2b_hex( -"1de33d00a4175b9ded4f616f8e33a45b" -"912f3e84284db68704119101447714ff" -"614779422ec5602b55935f8fd6787efd" -"93e28511405c91f45a43782f96d24bbc")) - assert(t.sig.type == TACK_Sig_Type.v1) - assert(posixTimeToStr(t.sig.expiration*60) == "2042-01-29T01:09Z") - assert(t.sig.generation == 0) - assert(t.sig.target_hash == a2b_hex("bf3e45a33d604cb0689106562289266a" - "581bf8091e07c669e7c8f6429b5d20e6")) - assert(t.sig.signature == a2b_hex("024eb8b52ac4983ee44ae0b49a9e712f" - "810c5f1b9f1cf3847136eebd552ea285" - "954d964c7cff31940ba6a0fa0753a0a2" - "e63c90d02176f471d3b4e026d3bb61dc")) - s = """ ------BEGIN TACK BREAK SIG----- -AR3jPQCkF1ud7U9hb44zpFuRLz6EKE22hwQRkQFEdxT/YUd5Qi7FYCtVk1+P1nh+ -/ZPihRFAXJH0WkN4L5bSS7wLSfGPO7ezR9GJtpI2pFfaGFwYRy2jpbjg4T+O5SAu -FwFGRqeNl/uT+iTRYJH+GU0hHkA+v6Rm0oBt2COSvP5E ------END TACK BREAK SIG-----""" - tbs = TACK_Break_Sig() - tbs.parsePem(s) - assert(tbs.key.type == TACK_Key_Type.v1) - #print tbs.getTACKID() - assert(tbs.getTACKID() == "bcx9r.6iyw3.8tn4d.5g9jj.i18gy") - assert(tbs.signature == a2b_hex( -"0b49f18f3bb7b347d189b69236a457da" -"185c18472da3a5b8e0e13f8ee5202e17" -"014646a78d97fb93fa24d16091fe194d" -"211e403ebfa466d2806dd82392bcfe44")) - - s = """ -Created by TACK.py 0.9.6 -Created at 2012-03-15T20:42:21Z ------BEGIN TACK BREAK SIG----- -AR3jPQCkF1ud7U9hb44zpFuRLz6EKE22hwQRkQFEdxT/YUd5Qi7FYCtVk1+P1nh+ -/ZPihRFAXJH0WkN4L5bSS7wLSfGPO7ezR9GJtpI2pFfaGFwYRy2jpbjg4T+O5SAu -FwFGRqeNl/uT+iTRYJH+GU0hHkA+v6Rm0oBt2COSvP5E ------END TACK BREAK SIG----- -Created by TACK.py 0.9.6 -Created at 2012-03-15T20:42:22Z ------BEGIN TACK BREAK SIG----- -ATBZ2H0kKqfIK0s0lFLr5zogOTLHpkXawY9cGd5W19IkKofKjPLl6yHGAuxFIgF3 -K3SpVgeBp0R2gpBKuEnLxH1DfR2lg+gjycXKc0JXoAR3TeOG3Aig6Y9ziVHvikHD -5jlRD867NH5U3zE/xHka02Lhd3zRao0sEPsy6WCuPJze ------END TACK BREAK SIG----- -""" - tbsList = TACK_Break_Sig.parsePemList(s) - #print tbsList[0].getTACKID() - #print tbsList[1].getTACKID() - assert(tbsList[0].getTACKID() == "bcx9r.6iyw3.8tn4d.5g9jj.i18gy") - assert(tbsList[1].getTACKID() == "budhm.4q6xu.74geu.isna9.ke6Ln") - assert(len(tbsList) == 2) - return 1 \ No newline at end of file diff --git a/TACKpy/tackid.py b/TACKpy/tackid.py deleted file mode 100644 index d45a591..0000000 --- a/TACKpy/tackid.py +++ /dev/null @@ -1,73 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -""" -TACK ID alphabet base32 encoding - -""" - -from .compat import b2a_base32 -from .cryptomath import SHA256 - -################ FINGERPRINTS ### - -def makeTACKID(public_key): - return hashToTACKID(SHA256(public_key)) - -def hashToTACKID(b): - assert(len(b) == 32) - s = b2a_base32(b).lower()[:25] - return "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) - s2 = "b" - for c in s: - if c.isdigit(): - s2 += chr(ord(c)+2) - elif c == 'z': - s2 += '3' - elif c == 'l': - s2 += 'L' - elif c == 'o': - s2 += '1' - else: - s2 += c - return "%s.%s.%s.%s.%s" % (s2[:5],s2[5:10],s2[10:15],s2[15:20],s2[20:25]) - -# old version below... - -alphabet = "abcdefghijkLmn1pqrstuvwxy3456789" - -def base32EncodeIntList(listInts): - s = "" - for x in listInts: - assert(x < 32) - s += alphabet[x] - return s - -def hashToTACKIDold(b): - assert(len(b) == 15) - listInts = [1] # 5 bit header = V1 - nextInt = 0 - outMask = 0x10 - for x in b: - for inMask in [0x80,0x40,0x20,0x10,0x8,0x4,0x2,0x1]: - if x & inMask: - nextInt |= outMask - outMask >>= 1 - if outMask == 0: - listInts.append(nextInt) - nextInt = 0 - outMask = 0x10 - s = base32EncodeIntList(listInts) - s = "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) - return s - - -def testTACKID(): - import os - print("Testing TACK ID") - for x in range(1000): - b = bytearray(os.urandom(15)) - tackID1 = hashToTACKID(b) - tackID2 = hashToTACKIDold(b) - assert(tackID1 == tackID2) - return 1 diff --git a/TACKpy/test.py b/TACKpy/test.py deleted file mode 100644 index a3050ab..0000000 --- a/TACKpy/test.py +++ /dev/null @@ -1,36 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from .numbertheory import testNumberTheory -from .ellipticcurve import testEllipticCurve -from .ecdsa import testECDSA -from .ecdsa_wrappers import testECDSAWrappers -from .rijndael import testRijndael -from .aes_wrappers import testAES -from .cryptomath import testOsUrandom -from .compat import testCompat -from .asn1 import testASN1 -from .time_funcs import testTime -from .tack_structures import testTACKStructures -from .tackid import testTACKID -from .ssl_cert import testSSLCert -from .keyfile import testKeyFile - -################ TEST ### - -def selfTest(): - assert(testNumberTheory() == 1) - assert(testEllipticCurve() == 1) - assert(testECDSA() == 1) - assert(testECDSAWrappers() == 1) - assert(testRijndael() == 1) - assert(testAES() == 1) - assert(testOsUrandom() == 1) - assert(testASN1() == 1) - assert(testCompat() == 1) - assert(testTime() == 1) - #assert(testTACKStructures() == 1) - #assert(testTACKID() == 1) - assert(testSSLCert() == 1) - assert(testKeyFile() == 1) - return 1 diff --git a/TACKpy/time_funcs.py b/TACKpy/time_funcs.py deleted file mode 100644 index a1a2aca..0000000 --- a/TACKpy/time_funcs.py +++ /dev/null @@ -1,151 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from .compat import * - -################ TIME FUNCS ### - -import time, calendar, datetime, math - -# u in seconds -def posixTimeToStr(u, includeSeconds=False): - t = time.gmtime(u) - if includeSeconds: - s = time.strftime("%Y-%m-%dT%H:%M:%SZ", t) - else: - s = time.strftime("%Y-%m-%dT%H:%MZ", t) - return s - -# u in minutes -def durationToStr(u): - s = "" - if u >= (1440): # 1440 minutes per day - s += "%dd" % (u//1440) - u %= 1440 - if u >= (60): # 60 minutes per hour - s += "%dh" % (u//60) - u %= 60 - if u>0 or not s: - s += "%dm" % u - return s - -def parseTimeArg(arg): - # First, see if they specified time as a duration - try: - mins = parseDurationArg(arg) - return int(math.ceil(time.time() / 60.0)) + mins - except SyntaxError: - pass - - # Otherwise, allow them to specify as much or as little of - # ISO8601 as they want, but must end with "Z" - patterns = ["%Y-%m-%dT%H:%MZ", "%Y-%m-%dT%HZ", - "%Y-%m-%dZ", "%Y-%mZ", "%YZ"] - t = None - for p in patterns: - try: - t = time.strptime(arg, p) - break - except ValueError: - pass - if not t: - s = posixTimeToStr(time.time()) - raise SyntaxError(\ -'''Invalid time format, use e.g. "%s" (current time) -or some prefix, such as: "%sZ", "%sZ", or "%sZ", -*OR* some duration, such as "5m", "30d", "1d12h5m", etc."''' % - (s, s[:13], s[:10], s[:4])) - u = int(calendar.timegm(t)//60) - if u < 0: - raise SyntaxError("Time too early, epoch starts at 1970.") - return u - -def parseDurationArg(arg): - arg = arg.upper() - foundSomething = False - try: - mins = 0 - while 1: - i = arg.find("D") - if i != -1: - mins += 1440 * int(arg[:i]) - arg = arg[i+1:] - foundSomething = True - i = arg.find("H") - if i != -1: - mins += 60 * int(arg[:i]) - arg = arg[i+1:] - foundSomething = True - i = arg.find("M") - if i != -1: - mins += int(arg[:i]) - arg = arg[i+1:] - foundSomething = True - if arg: - raise SyntaxError() - if not foundSomething: - raise SyntaxError() - return mins - except: - raise SyntaxError() - -# Return UNIX time int -def parseASN1UTCTime(b): - try: - if b[-1] != ord("Z"): - raise SyntaxError() - if len(b) == len("YYMHDDHHMMSSZ"): - pass - elif len(b) == len("YYHHDDHHMMZ"): - b = b[:-1] + b"00Z" - else: - raise SyntaxError() - year = int(b[:2]) - if year < 50: - b = b"20" + b - else: - b = b"19" + b - except: - raise SyntaxError() - return parseASN1GeneralizedTime(b) - - -def parseASN1GeneralizedTime(b): - t = time.strptime(bytesToStrAscii(b), "%Y%m%d%H%M%SZ") - return int(calendar.timegm(t)) - -def testTime(): - print("Testing TIME FUNCS") - assert(posixTimeToStr(1234567890, True) == "2009-02-13T23:31:30Z") - assert(posixTimeToStr(1234567890) == "2009-02-13T23:31Z") - assert(durationToStr(0) == "0m") - assert(durationToStr(59) == "59m") - assert(durationToStr(60) == "1h") - assert(durationToStr(61) == "1h1m") - assert(durationToStr(1439) == "23h59m") - assert(durationToStr(1440) == "1d") - assert(durationToStr(1441) == "1d1m") - assert(durationToStr(1500) == "1d1h") - assert(durationToStr(1501) == "1d1h1m") - assert(durationToStr(1440*37+122) == "37d2h2m") - - assert(0 == parseDurationArg("0m")) - assert(59 == parseDurationArg("59m")) - assert(60 == parseDurationArg("1h")) - assert(61 == parseDurationArg("1h1m")) - assert(1439 == parseDurationArg("23h59m")) - assert(1440 == parseDurationArg("1d")) - assert(1441 == parseDurationArg("1d1m")) - assert(1500 == parseDurationArg("1d1h")) - assert(1501 == parseDurationArg("1d1h1m")) - assert(1440*37+122 == parseDurationArg("37d2h2m")) - - assert(parseTimeArg("2012-07-20T05:40Z")*60 == 1342762800) - assert(parseTimeArg("2012-07-20T05Z")*60 == 1342760400) - assert(parseTimeArg("2012-07-20Z")*60 == 1342742400) - assert(parseTimeArg("2012-07Z")*60 == 1341100800) - assert(parseTimeArg("2012Z")*60 == 1325376000) - # !!! Add tests for ASN1 times - - return 1 - diff --git a/TODO b/TODO deleted file mode 100644 index 139597f..0000000 --- a/TODO +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/make_selfcontained.py b/make_selfcontained.py deleted file mode 100755 index aa428f3..0000000 --- a/make_selfcontained.py +++ /dev/null @@ -1,43 +0,0 @@ -#! /usr/bin/env python - -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -fnames = [\ -"TACKpy/version.py", -"TACKpy/numbertheory.py", -"TACKpy/ellipticcurve.py", -"TACKpy/ecdsa.py", -"TACKpy/rijndael.py", -"TACKpy/misc.py", -"TACKpy/compat.py", -"TACKpy/cryptomath.py", -"TACKpy/time_funcs.py", -"TACKpy/asn1.py", -"TACKpy/pem.py", -"TACKpy/m2crypto.py", -"TACKpy/aes_wrappers.py", -"TACKpy/ecdsa_wrappers.py", -"TACKpy/struct_parser.py", -"TACKpy/constants.py", -"TACKpy/tack_structures.py", -"TACKpy/tackid.py", -"TACKpy/ssl_cert.py", -"TACKpy/keyfile.py", -"TACKpy/test.py", -"scripts/TACK.py"] - -# Stitch the source files together, ommitting anything before -# the ######... header (usually just import statements) necessary -# for when this is separate modules - -s = open("TACKpy/header.py").read() -for fname in fnames: - s2 = open(fname).read() - i = s2.find("################ ") - assert(i != -1) - s2 = s2[i:] - if s2[-1] != "\n": - s2 += "\n" - s += s2 -print s diff --git a/reference/ecdsa.py b/reference/ecdsa.py deleted file mode 100644 index 216e9d2..0000000 --- a/reference/ecdsa.py +++ /dev/null @@ -1,556 +0,0 @@ -#! /usr/bin/env python -""" -Implementation of Elliptic-Curve Digital Signatures. - -Classes and methods for elliptic-curve signatures: -private keys, public keys, signatures, -NIST prime-modulus curves with modulus lengths of -192, 224, 256, 384, and 521 bits. - -Example: - - # (In real-life applications, you would probably want to - # protect against defects in SystemRandom.) - from random import SystemRandom - randrange = SystemRandom().randrange - - # Generate a public/private key pair using the NIST Curve P-192: - - g = generator_192 - n = g.order() - secret = randrange( 1, n ) - pubkey = Public_key( g, g * secret ) - privkey = Private_key( pubkey, secret ) - - # Signing a hash value: - - hash = randrange( 1, n ) - signature = privkey.sign( hash, randrange( 1, n ) ) - - # Verifying a signature for a hash value: - - if pubkey.verifies( hash, signature ): - print "Demo verification succeeded." - else: - print "*** Demo verification failed." - - # Verification fails if the hash value is modified: - - if pubkey.verifies( hash-1, signature ): - print "**** Demo verification failed to reject tampered hash." - else: - print "Demo verification correctly rejected tampered hash." - -Version of 2009.05.16. - -Revision history: - 2005.12.31 - Initial version. - 2008.11.25 - Substantial revisions introducing new classes. - 2009.05.16 - Warn against using random.randrange in real applications. - 2009.05.17 - Use random.SystemRandom by default. - -Written in 2005 by Peter Pearson and placed in the public domain. -""" - - -import ellipticcurve -import numbertheory -import random - - - -class Signature( object ): - """ECDSA signature. - """ - def __init__( self, r, s ): - self.r = r - self.s = s - - - -class Public_key( object ): - """Public key for ECDSA. - """ - - def __init__( self, generator, point ): - """generator is the Point that generates the group, - point is the Point that defines the public key. - """ - - self.curve = generator.curve() - self.generator = generator - self.point = point - n = generator.order() - if not n: - raise RuntimeError, "Generator point must have order." - if not n * point == ellipticcurve.INFINITY: - raise RuntimeError, "Generator point order is bad." - if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y(): - raise RuntimeError, "Generator point has x or y out of range." - - - def verifies( self, hash, signature ): - """Verify that signature is a valid signature of hash. - Return True if the signature is valid. - """ - - # From X9.62 J.3.1. - - G = self.generator - n = G.order() - r = signature.r - s = signature.s - if r < 1 or r > n-1: return False - if s < 1 or s > n-1: return False - c = numbertheory.inverse_mod( s, n ) - u1 = ( hash * c ) % n - u2 = ( r * c ) % n - xy = u1 * G + u2 * self.point - v = xy.x() % n - return v == r - - - -class Private_key( object ): - """Private key for ECDSA. - """ - - def __init__( self, public_key, secret_multiplier ): - """public_key is of class Public_key; - secret_multiplier is a large integer. - """ - - self.public_key = public_key - self.secret_multiplier = secret_multiplier - - def sign( self, hash, random_k ): - """Return a signature for the provided hash, using the provided - random nonce. It is absolutely vital that random_k be an unpredictable - number in the range [1, self.public_key.point.order()-1]. If - an attacker can guess random_k, he can compute our private key from a - single signature. Also, if an attacker knows a few high-order - bits (or a few low-order bits) of random_k, he can compute our private - key from many signatures. The generation of nonces with adequate - cryptographic strength is very difficult and far beyond the scope - of this comment. - - May raise RuntimeError, in which case retrying with a new - random value k is in order. - """ - - G = self.public_key.generator - n = G.order() - k = random_k % n - p1 = k * G - r = p1.x() - if r == 0: raise RuntimeError, "amazingly unlucky random number r" - s = ( numbertheory.inverse_mod( k, n ) * \ - ( hash + ( self.secret_multiplier * r ) % n ) ) % n - if s == 0: raise RuntimeError, "amazingly unlucky random number s" - return Signature( r, s ) - - - -def int_to_string( x ): - """Convert integer x into a string of bytes, as per X9.62.""" - assert x >= 0 - if x == 0: return chr(0) - result = "" - while x > 0: - q, r = divmod( x, 256 ) - result = chr( r ) + result - x = q - return result - - -def string_to_int( s ): - """Convert a string of bytes into an integer, as per X9.62.""" - result = 0L - for c in s: result = 256 * result + ord( c ) - return result - - -def digest_integer( m ): - """Convert an integer into a string of bytes, compute - its SHA-1 hash, and convert the result to an integer.""" - # - # I don't expect this function to be used much. I wrote - # it in order to be able to duplicate the examples - # in ECDSAVS. - # - import sha - return string_to_int( sha.new( int_to_string( m ) ).digest() ) - - -def point_is_valid( generator, x, y ): - """Is (x,y) a valid public key based on the specified generator?""" - - # These are the tests specified in X9.62. - - n = generator.order() - curve = generator.curve() - if x < 0 or n <= x or y < 0 or n <= y: - return False - if not curve.contains_point( x, y ): - return False - if not n*ellipticcurve.Point( curve, x, y ) == \ - ellipticcurve.INFINITY: - return False - return True - - - -# NIST Curve P-192: -_p = 6277101735386680763835789423207666416083908700390324961279L -_r = 6277101735386680763835789423176059013767194773182842284081L -# s = 0x3045ae6fc8422f64ed579528d38120eae12196d5L -# c = 0x3099d2bbbfcb2538542dcd5fb078b6ef5f3d6fe2c745de65L -_b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1L -_Gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012L -_Gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811L - -curve_192 = ellipticcurve.CurveFp( _p, -3, _b ) -generator_192 = ellipticcurve.Point( curve_192, _Gx, _Gy, _r ) - - -# NIST Curve P-224: -_p = 26959946667150639794667015087019630673557916260026308143510066298881L -_r = 26959946667150639794667015087019625940457807714424391721682722368061L -# s = 0xbd71344799d5c7fcdc45b59fa3b9ab8f6a948bc5L -# c = 0x5b056c7e11dd68f40469ee7f3c7a7d74f7d121116506d031218291fbL -_b = 0xb4050a850c04b3abf54132565044b0b7d7bfd8ba270b39432355ffb4L -_Gx =0xb70e0cbd6bb4bf7f321390b94a03c1d356c21122343280d6115c1d21L -_Gy = 0xbd376388b5f723fb4c22dfe6cd4375a05a07476444d5819985007e34L - -curve_224 = ellipticcurve.CurveFp( _p, -3, _b ) -generator_224 = ellipticcurve.Point( curve_224, _Gx, _Gy, _r ) - -# NIST Curve P-256: -_p = 115792089210356248762697446949407573530086143415290314195533631308867097853951L -_r = 115792089210356248762697446949407573529996955224135760342422259061068512044369L -# s = 0xc49d360886e704936a6678e1139d26b7819f7e90L -# c = 0x7efba1662985be9403cb055c75d4f7e0ce8d84a9c5114abcaf3177680104fa0dL -_b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604bL -_Gx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296L -_Gy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5L - -curve_256 = ellipticcurve.CurveFp( _p, -3, _b ) -generator_256 = ellipticcurve.Point( curve_256, _Gx, _Gy, _r ) - -# NIST Curve P-384: -_p = 39402006196394479212279040100143613805079739270465446667948293404245721771496870329047266088258938001861606973112319L -_r = 39402006196394479212279040100143613805079739270465446667946905279627659399113263569398956308152294913554433653942643L -# s = 0xa335926aa319a27a1d00896a6773a4827acdac73L -# c = 0x79d1e655f868f02fff48dcdee14151ddb80643c1406d0ca10dfe6fc52009540a495e8042ea5f744f6e184667cc722483L -_b = 0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8edd3ec2aefL -_Gx = 0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e3872760ab7L -_Gy = 0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c90ea0e5fL - -curve_384 = ellipticcurve.CurveFp( _p, -3, _b ) -generator_384 = ellipticcurve.Point( curve_384, _Gx, _Gy, _r ) - -# NIST Curve P-521: -_p = 6864797660130609714981900799081393217269435300143305409394463459185543183397656052122559640661454554977296311391480858037121987999716643812574028291115057151L -_r = 6864797660130609714981900799081393217269435300143305409394463459185543183397655394245057746333217197532963996371363321113864768612440380340372808892707005449L -# s = 0xd09e8800291cb85396cc6717393284aaa0da64baL -# c = 0x0b48bfa5f420a34949539d2bdfc264eeeeb077688e44fbf0ad8f6d0edb37bd6b533281000518e19f1b9ffbe0fe9ed8a3c2200b8f875e523868c70c1e5bf55bad637L -_b = 0x051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00L -_Gx = 0xc6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1dc127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66L -_Gy = 0x11839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550b9013fad0761353c7086a272c24088be94769fd16650L - -curve_521 = ellipticcurve.CurveFp( _p, -3, _b ) -generator_521 = ellipticcurve.Point( curve_521, _Gx, _Gy, _r ) - - - -if __name__ == "__main__": - - def test_point_validity( generator, x, y, expected ): - """generator defines the curve; is (x,y) a point on - this curve? "expected" is True if the right answer is Yes.""" - if point_is_valid( generator, x, y ) == expected: - print "Point validity tested as expected." - else: - print "*** Point validity test gave wrong result." - - def test_signature_validity( Msg, Qx, Qy, R, S, expected ): - """Msg = message, Qx and Qy represent the base point on - elliptic curve c192, R and S are the signature, and - "expected" is True iff the signature is expected to be valid.""" - pubk = Public_key( generator_192, - ellipticcurve.Point( curve_192, Qx, Qy ) ) - got = pubk.verifies( digest_integer( Msg ), Signature( R, S ) ) - if got == expected: - print "Signature tested as expected: got %s, expected %s." % \ - ( got, expected ) - else: - print "*** Signature test failed: got %s, expected %s." % \ - ( got, expected ) - - print "NIST Curve P-192:" - - p192 = generator_192 - - # From X9.62: - - d = 651056770906015076056810763456358567190100156695615665659L - Q = d * p192 - if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5L: - print "*** p192 * d came out wrong." - else: - print "p192 * d came out right." - - k = 6140507067065001063065065565667405560006161556565665656654L - R = k * p192 - if R.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEADL \ - or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835L: - print "*** k * p192 came out wrong." - else: - print "k * p192 came out right." - - u1 = 2563697409189434185194736134579731015366492496392189760599L - u2 = 6266643813348617967186477710235785849136406323338782220568L - temp = u1 * p192 + u2 * Q - if temp.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEADL \ - or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835L: - print "*** u1 * p192 + u2 * Q came out wrong." - else: - print "u1 * p192 + u2 * Q came out right." - - e = 968236873715988614170569073515315707566766479517L - pubk = Public_key( generator_192, generator_192 * d ) - privk = Private_key( pubk, d ) - sig = privk.sign( e, k ) - r, s = sig.r, sig.s - if r != 3342403536405981729393488334694600415596881826869351677613L \ - or s != 5735822328888155254683894997897571951568553642892029982342L: - print "*** r or s came out wrong." - else: - print "r and s came out right." - - valid = pubk.verifies( e, sig ) - if valid: print "Signature verified OK." - else: print "*** Signature failed verification." - - valid = pubk.verifies( e-1, sig ) - if not valid: print "Forgery was correctly rejected." - else: print "*** Forgery was erroneously accepted." - - print "Testing point validity, as per ECDSAVS.pdf B.2.2:" - - test_point_validity( \ - p192, \ - 0xcd6d0f029a023e9aaca429615b8f577abee685d8257cc83aL, \ - 0x00019c410987680e9fb6c0b6ecc01d9a2647c8bae27721bacdfcL, \ - False ) - - test_point_validity( - p192, \ - 0x00017f2fce203639e9eaf9fb50b81fc32776b30e3b02af16c73bL, \ - 0x95da95c5e72dd48e229d4748d4eee658a9a54111b23b2adbL, \ - False ) - - test_point_validity( - p192, \ - 0x4f77f8bc7fccbadd5760f4938746d5f253ee2168c1cf2792L, \ - 0x000147156ff824d131629739817edb197717c41aab5c2a70f0f6L, \ - False ) - - test_point_validity( - p192, \ - 0xc58d61f88d905293bcd4cd0080bcb1b7f811f2ffa41979f6L, \ - 0x8804dc7a7c4c7f8b5d437f5156f3312ca7d6de8a0e11867fL, \ - True ) - - test_point_validity( - p192, \ - 0xcdf56c1aa3d8afc53c521adf3ffb96734a6a630a4a5b5a70L, \ - 0x97c1c44a5fb229007b5ec5d25f7413d170068ffd023caa4eL, \ - True ) - - test_point_validity( - p192, \ - 0x89009c0dc361c81e99280c8e91df578df88cdf4b0cdedcedL, \ - 0x27be44a529b7513e727251f128b34262a0fd4d8ec82377b9L, \ - True ) - - test_point_validity( - p192, \ - 0x6a223d00bd22c52833409a163e057e5b5da1def2a197dd15L, \ - 0x7b482604199367f1f303f9ef627f922f97023e90eae08abfL, \ - True ) - - test_point_validity( - p192, \ - 0x6dccbde75c0948c98dab32ea0bc59fe125cf0fb1a3798edaL, \ - 0x0001171a3e0fa60cf3096f4e116b556198de430e1fbd330c8835L, \ - False ) - - test_point_validity( - p192, \ - 0xd266b39e1f491fc4acbbbc7d098430931cfa66d55015af12L, \ - 0x193782eb909e391a3148b7764e6b234aa94e48d30a16dbb2L, \ - False ) - - test_point_validity( - p192, \ - 0x9d6ddbcd439baa0c6b80a654091680e462a7d1d3f1ffeb43L, \ - 0x6ad8efc4d133ccf167c44eb4691c80abffb9f82b932b8caaL, \ - False ) - - test_point_validity( - p192, \ - 0x146479d944e6bda87e5b35818aa666a4c998a71f4e95edbcL, \ - 0xa86d6fe62bc8fbd88139693f842635f687f132255858e7f6L, \ - False ) - - test_point_validity( - p192, \ - 0xe594d4a598046f3598243f50fd2c7bd7d380edb055802253L, \ - 0x509014c0c4d6b536e3ca750ec09066af39b4c8616a53a923L, \ - False ) - - print "Trying signature-verification tests from ECDSAVS.pdf B.2.4:" - print "P-192:" - Msg = 0x84ce72aa8699df436059f052ac51b6398d2511e49631bcb7e71f89c499b9ee425dfbc13a5f6d408471b054f2655617cbbaf7937b7c80cd8865cf02c8487d30d2b0fbd8b2c4e102e16d828374bbc47b93852f212d5043c3ea720f086178ff798cc4f63f787b9c2e419efa033e7644ea7936f54462dc21a6c4580725f7f0e7d158L - Qx = 0xd9dbfb332aa8e5ff091e8ce535857c37c73f6250ffb2e7acL - Qy = 0x282102e364feded3ad15ddf968f88d8321aa268dd483ebc4L - R = 0x64dca58a20787c488d11d6dd96313f1b766f2d8efe122916L - S = 0x1ecba28141e84ab4ecad92f56720e2cc83eb3d22dec72479L - test_signature_validity( Msg, Qx, Qy, R, S, True ) - - Msg = 0x94bb5bacd5f8ea765810024db87f4224ad71362a3c28284b2b9f39fab86db12e8beb94aae899768229be8fdb6c4f12f28912bb604703a79ccff769c1607f5a91450f30ba0460d359d9126cbd6296be6d9c4bb96c0ee74cbb44197c207f6db326ab6f5a659113a9034e54be7b041ced9dcf6458d7fb9cbfb2744d999f7dfd63f4L - Qx = 0x3e53ef8d3112af3285c0e74842090712cd324832d4277ae7L - Qy = 0xcc75f8952d30aec2cbb719fc6aa9934590b5d0ff5a83adb7L - R = 0x8285261607283ba18f335026130bab31840dcfd9c3e555afL - S = 0x356d89e1b04541afc9704a45e9c535ce4a50929e33d7e06cL - test_signature_validity( Msg, Qx, Qy, R, S, True ) - - Msg = 0xf6227a8eeb34afed1621dcc89a91d72ea212cb2f476839d9b4243c66877911b37b4ad6f4448792a7bbba76c63bdd63414b6facab7dc71c3396a73bd7ee14cdd41a659c61c99b779cecf07bc51ab391aa3252386242b9853ea7da67fd768d303f1b9b513d401565b6f1eb722dfdb96b519fe4f9bd5de67ae131e64b40e78c42ddL - Qx = 0x16335dbe95f8e8254a4e04575d736befb258b8657f773cb7L - Qy = 0x421b13379c59bc9dce38a1099ca79bbd06d647c7f6242336L - R = 0x4141bd5d64ea36c5b0bd21ef28c02da216ed9d04522b1e91L - S = 0x159a6aa852bcc579e821b7bb0994c0861fb08280c38daa09L - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x16b5f93afd0d02246f662761ed8e0dd9504681ed02a253006eb36736b563097ba39f81c8e1bce7a16c1339e345efabbc6baa3efb0612948ae51103382a8ee8bc448e3ef71e9f6f7a9676694831d7f5dd0db5446f179bcb737d4a526367a447bfe2c857521c7f40b6d7d7e01a180d92431fb0bbd29c04a0c420a57b3ed26ccd8aL - Qx = 0xfd14cdf1607f5efb7b1793037b15bdf4baa6f7c16341ab0bL - Qy = 0x83fa0795cc6c4795b9016dac928fd6bac32f3229a96312c4L - R = 0x8dfdb832951e0167c5d762a473c0416c5c15bc1195667dc1L - S = 0x1720288a2dc13fa1ec78f763f8fe2ff7354a7e6fdde44520L - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x08a2024b61b79d260e3bb43ef15659aec89e5b560199bc82cf7c65c77d39192e03b9a895d766655105edd9188242b91fbde4167f7862d4ddd61e5d4ab55196683d4f13ceb90d87aea6e07eb50a874e33086c4a7cb0273a8e1c4408f4b846bceae1ebaac1b2b2ea851a9b09de322efe34cebe601653efd6ddc876ce8c2f2072fbL - Qx = 0x674f941dc1a1f8b763c9334d726172d527b90ca324db8828L - Qy = 0x65adfa32e8b236cb33a3e84cf59bfb9417ae7e8ede57a7ffL - R = 0x9508b9fdd7daf0d8126f9e2bc5a35e4c6d800b5b804d7796L - S = 0x36f2bf6b21b987c77b53bb801b3435a577e3d493744bfab0L - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x1843aba74b0789d4ac6b0b8923848023a644a7b70afa23b1191829bbe4397ce15b629bf21a8838298653ed0c19222b95fa4f7390d1b4c844d96e645537e0aae98afb5c0ac3bd0e4c37f8daaff25556c64e98c319c52687c904c4de7240a1cc55cd9756b7edaef184e6e23b385726e9ffcba8001b8f574987c1a3fedaaa83ca6dL - Qx = 0x10ecca1aad7220b56a62008b35170bfd5e35885c4014a19fL - Qy = 0x04eb61984c6c12ade3bc47f3c629ece7aa0a033b9948d686L - R = 0x82bfa4e82c0dfe9274169b86694e76ce993fd83b5c60f325L - S = 0xa97685676c59a65dbde002fe9d613431fb183e8006d05633L - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x5a478f4084ddd1a7fea038aa9732a822106385797d02311aeef4d0264f824f698df7a48cfb6b578cf3da416bc0799425bb491be5b5ecc37995b85b03420a98f2c4dc5c31a69a379e9e322fbe706bbcaf0f77175e05cbb4fa162e0da82010a278461e3e974d137bc746d1880d6eb02aa95216014b37480d84b87f717bb13f76e1L - Qx = 0x6636653cb5b894ca65c448277b29da3ad101c4c2300f7c04L - Qy = 0xfdf1cbb3fc3fd6a4f890b59e554544175fa77dbdbeb656c1L - R = 0xeac2ddecddfb79931a9c3d49c08de0645c783a24cb365e1cL - S = 0x3549fee3cfa7e5f93bc47d92d8ba100e881a2a93c22f8d50L - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0xc598774259a058fa65212ac57eaa4f52240e629ef4c310722088292d1d4af6c39b49ce06ba77e4247b20637174d0bd67c9723feb57b5ead232b47ea452d5d7a089f17c00b8b6767e434a5e16c231ba0efa718a340bf41d67ea2d295812ff1b9277daacb8bc27b50ea5e6443bcf95ef4e9f5468fe78485236313d53d1c68f6ba2L - Qx = 0xa82bd718d01d354001148cd5f69b9ebf38ff6f21898f8aaaL - Qy = 0xe67ceede07fc2ebfafd62462a51e4b6c6b3d5b537b7caf3eL - R = 0x4d292486c620c3de20856e57d3bb72fcde4a73ad26376955L - S = 0xa85289591a6081d5728825520e62ff1c64f94235c04c7f95L - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0xca98ed9db081a07b7557f24ced6c7b9891269a95d2026747add9e9eb80638a961cf9c71a1b9f2c29744180bd4c3d3db60f2243c5c0b7cc8a8d40a3f9a7fc910250f2187136ee6413ffc67f1a25e1c4c204fa9635312252ac0e0481d89b6d53808f0c496ba87631803f6c572c1f61fa049737fdacce4adff757afed4f05beb658L - Qx = 0x7d3b016b57758b160c4fca73d48df07ae3b6b30225126c2fL - Qy = 0x4af3790d9775742bde46f8da876711be1b65244b2b39e7ecL - R = 0x95f778f5f656511a5ab49a5d69ddd0929563c29cbc3a9e62L - S = 0x75c87fc358c251b4c83d2dd979faad496b539f9f2ee7a289L - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x31dd9a54c8338bea06b87eca813d555ad1850fac9742ef0bbe40dad400e10288acc9c11ea7dac79eb16378ebea9490e09536099f1b993e2653cd50240014c90a9c987f64545abc6a536b9bd2435eb5e911fdfde2f13be96ea36ad38df4ae9ea387b29cced599af777338af2794820c9cce43b51d2112380a35802ab7e396c97aL - Qx = 0x9362f28c4ef96453d8a2f849f21e881cd7566887da8beb4aL - Qy = 0xe64d26d8d74c48a024ae85d982ee74cd16046f4ee5333905L - R = 0xf3923476a296c88287e8de914b0b324ad5a963319a4fe73bL - S = 0xf0baeed7624ed00d15244d8ba2aede085517dbdec8ac65f5L - test_signature_validity( Msg, Qx, Qy, R, S, True ) - - Msg = 0xb2b94e4432267c92f9fdb9dc6040c95ffa477652761290d3c7de312283f6450d89cc4aabe748554dfb6056b2d8e99c7aeaad9cdddebdee9dbc099839562d9064e68e7bb5f3a6bba0749ca9a538181fc785553a4000785d73cc207922f63e8ce1112768cb1de7b673aed83a1e4a74592f1268d8e2a4e9e63d414b5d442bd0456dL - Qx = 0xcc6fc032a846aaac25533eb033522824f94e670fa997ecefL - Qy = 0xe25463ef77a029eccda8b294fd63dd694e38d223d30862f1L - R = 0x066b1d07f3a40e679b620eda7f550842a35c18b80c5ebe06L - S = 0xa0b0fb201e8f2df65e2c4508ef303bdc90d934016f16b2dcL - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x4366fcadf10d30d086911de30143da6f579527036937007b337f7282460eae5678b15cccda853193ea5fc4bc0a6b9d7a31128f27e1214988592827520b214eed5052f7775b750b0c6b15f145453ba3fee24a085d65287e10509eb5d5f602c440341376b95c24e5c4727d4b859bfe1483d20538acdd92c7997fa9c614f0f839d7L - Qx = 0x955c908fe900a996f7e2089bee2f6376830f76a19135e753L - Qy = 0xba0c42a91d3847de4a592a46dc3fdaf45a7cc709b90de520L - R = 0x1f58ad77fc04c782815a1405b0925e72095d906cbf52a668L - S = 0xf2e93758b3af75edf784f05a6761c9b9a6043c66b845b599L - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x543f8af57d750e33aa8565e0cae92bfa7a1ff78833093421c2942cadf9986670a5ff3244c02a8225e790fbf30ea84c74720abf99cfd10d02d34377c3d3b41269bea763384f372bb786b5846f58932defa68023136cd571863b304886e95e52e7877f445b9364b3f06f3c28da12707673fecb4b8071de06b6e0a3c87da160cef3L - Qx = 0x31f7fa05576d78a949b24812d4383107a9a45bb5fccdd835L - Qy = 0x8dc0eb65994a90f02b5e19bd18b32d61150746c09107e76bL - R = 0xbe26d59e4e883dde7c286614a767b31e49ad88789d3a78ffL - S = 0x8762ca831c1ce42df77893c9b03119428e7a9b819b619068L - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0xd2e8454143ce281e609a9d748014dcebb9d0bc53adb02443a6aac2ffe6cb009f387c346ecb051791404f79e902ee333ad65e5c8cb38dc0d1d39a8dc90add5023572720e5b94b190d43dd0d7873397504c0c7aef2727e628eb6a74411f2e400c65670716cb4a815dc91cbbfeb7cfe8c929e93184c938af2c078584da045e8f8d1L - Qx = 0x66aa8edbbdb5cf8e28ceb51b5bda891cae2df84819fe25c0L - Qy = 0x0c6bc2f69030a7ce58d4a00e3b3349844784a13b8936f8daL - R = 0xa4661e69b1734f4a71b788410a464b71e7ffe42334484f23L - S = 0x738421cf5e049159d69c57a915143e226cac8355e149afe9L - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x6660717144040f3e2f95a4e25b08a7079c702a8b29babad5a19a87654bc5c5afa261512a11b998a4fb36b5d8fe8bd942792ff0324b108120de86d63f65855e5461184fc96a0a8ffd2ce6d5dfb0230cbbdd98f8543e361b3205f5da3d500fdc8bac6db377d75ebef3cb8f4d1ff738071ad0938917889250b41dd1d98896ca06fbL - Qx = 0xbcfacf45139b6f5f690a4c35a5fffa498794136a2353fc77L - Qy = 0x6f4a6c906316a6afc6d98fe1f0399d056f128fe0270b0f22L - R = 0x9db679a3dafe48f7ccad122933acfe9da0970b71c94c21c1L - S = 0x984c2db99827576c0a41a5da41e07d8cc768bc82f18c9da9L - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - - - print "Testing the example code:" - - # Building a public/private key pair from the NIST Curve P-192: - - g = generator_192 - n = g.order() - - # (random.SystemRandom is supposed to provide - # crypto-quality random numbers, but as Debian recently - # illustrated, a systems programmer can accidentally - # demolish this security, so in serious applications - # further precautions are appropriate.) - - randrange = random.SystemRandom().randrange - - secret = randrange( 1, n ) - pubkey = Public_key( g, g * secret ) - privkey = Private_key( pubkey, secret ) - - # Signing a hash value: - - hash = randrange( 1, n ) - signature = privkey.sign( hash, randrange( 1, n ) ) - - # Verifying a signature for a hash value: - - if pubkey.verifies( hash, signature ): - print "Demo verification succeeded." - else: - print "*** Demo verification failed." - - if pubkey.verifies( hash-1, signature ): - print "**** Demo verification failed to reject tampered hash." - else: - print "Demo verification correctly rejected tampered hash." diff --git a/reference/ellipticcurve.py b/reference/ellipticcurve.py deleted file mode 100644 index 80d3d22..0000000 --- a/reference/ellipticcurve.py +++ /dev/null @@ -1,270 +0,0 @@ -#! /usr/bin/env python -# -# Implementation of elliptic curves, for cryptographic applications. -# -# This module doesn't provide any way to choose a random elliptic -# curve, nor to verify that an elliptic curve was chosen randomly, -# because one can simply use NIST's standard curves. -# -# Notes from X9.62-1998 (draft): -# Nomenclature: -# - Q is a public key. -# The "Elliptic Curve Domain Parameters" include: -# - q is the "field size", which in our case equals p. -# - p is a big prime. -# - G is a point of prime order (5.1.1.1). -# - n is the order of G (5.1.1.1). -# Public-key validation (5.2.2): -# - Verify that Q is not the point at infinity. -# - Verify that X_Q and Y_Q are in [0,p-1]. -# - Verify that Q is on the curve. -# - Verify that nQ is the point at infinity. -# Signature generation (5.3): -# - Pick random k from [1,n-1]. -# Signature checking (5.4.2): -# - Verify that r and s are in [1,n-1]. -# -# Version of 2008.11.25. -# -# Revision history: -# 2005.12.31 - Initial version. -# 2008.11.25 - Change CurveFp.is_on to contains_point. -# -# Written in 2005 by Peter Pearson and placed in the public domain. - -import numbertheory - -class CurveFp( object ): - """Elliptic Curve over the field of integers modulo a prime.""" - def __init__( self, p, a, b ): - """The curve of points satisfying y^2 = x^3 + a*x + b (mod p).""" - self.__p = p - self.__a = a - self.__b = b - - def p( self ): - return self.__p - - def a( self ): - return self.__a - - def b( self ): - return self.__b - - def contains_point( self, x, y ): - """Is the point (x,y) on this curve?""" - return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0 - - - -class Point( object ): - """A point on an elliptic curve. Altering x and y is forbidding, - but they can be read by the x() and y() methods.""" - def __init__( self, curve, x, y, order = None ): - """curve, x, y, order; order (optional) is the order of this point.""" - self.__curve = curve - self.__x = x - self.__y = y - self.__order = order - # self.curve is allowed to be None only for INFINITY: - if self.__curve: assert self.__curve.contains_point( x, y ) - if order: assert self * order == INFINITY - - def __cmp__( self, other ): - """Return 0 if the points are identical, 1 otherwise.""" - if self.__curve == other.__curve \ - and self.__x == other.__x \ - and self.__y == other.__y: - return 0 - else: - return 1 - - def __add__( self, other ): - """Add one point to another point.""" - - # X9.62 B.3: - - if other == INFINITY: return self - if self == INFINITY: return other - assert self.__curve == other.__curve - if self.__x == other.__x: - if ( self.__y + other.__y ) % self.__curve.p() == 0: - return INFINITY - else: - return self.double() - - p = self.__curve.p() - - l = ( ( other.__y - self.__y ) * \ - numbertheory.inverse_mod( other.__x - self.__x, p ) ) % p - - x3 = ( l * l - self.__x - other.__x ) % p - y3 = ( l * ( self.__x - x3 ) - self.__y ) % p - - return Point( self.__curve, x3, y3 ) - - def __mul__( self, other ): - """Multiply a point by an integer.""" - - def leftmost_bit( x ): - assert x > 0 - result = 1L - while result <= x: result = 2 * result - return result / 2 - - e = other - if self.__order: e = e % self.__order - if e == 0: return INFINITY - if self == INFINITY: return INFINITY - assert e > 0 - - # From X9.62 D.3.2: - - e3 = 3 * e - negative_self = Point( self.__curve, self.__x, -self.__y, self.__order ) - i = leftmost_bit( e3 ) / 2 - result = self - # print "Multiplying %s by %d (e3 = %d):" % ( self, other, e3 ) - while i > 1: - result = result.double() - if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self - if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self - # print ". . . i = %d, result = %s" % ( i, result ) - i = i / 2 - - return result - - def __rmul__( self, other ): - """Multiply a point by an integer.""" - - return self * other - - def __str__( self ): - if self == INFINITY: return "infinity" - return "(%d,%d)" % ( self.__x, self.__y ) - - def double( self ): - """Return a new point that is twice the old.""" - - # X9.62 B.3: - - p = self.__curve.p() - a = self.__curve.a() - - l = ( ( 3 * self.__x * self.__x + a ) * \ - numbertheory.inverse_mod( 2 * self.__y, p ) ) % p - - x3 = ( l * l - 2 * self.__x ) % p - y3 = ( l * ( self.__x - x3 ) - self.__y ) % p - - return Point( self.__curve, x3, y3 ) - - def x( self ): - return self.__x - - def y( self ): - return self.__y - - def curve( self ): - return self.__curve - - def order( self ): - return self.__order - - -# This one point is the Point At Infinity for all purposes: -INFINITY = Point( None, None, None ) - -if __name__ == "__main__": - - def test_add( c, x1, y1, x2, y2, x3, y3 ): - """We expect that on curve c, (x1,y1) + (x2, y2 ) = (x3, y3).""" - p1 = Point( c, x1, y1 ) - p2 = Point( c, x2, y2 ) - p3 = p1 + p2 - print "%s + %s = %s" % ( p1, p2, p3 ), - if p3.x() != x3 or p3.y() != y3: - print " Failure: should give (%d,%d)." % ( x3, y3 ) - else: - print " Good." - - def test_double( c, x1, y1, x3, y3 ): - """We expect that on curve c, 2*(x1,y1) = (x3, y3).""" - p1 = Point( c, x1, y1 ) - p3 = p1.double() - print "%s doubled = %s" % ( p1, p3 ), - if p3.x() != x3 or p3.y() != y3: - print " Failure: should give (%d,%d)." % ( x3, y3 ) - else: - print " Good." - - def test_multiply( c, x1, y1, m, x3, y3 ): - """We expect that on curve c, m*(x1,y1) = (x3,y3).""" - p1 = Point( c, x1, y1 ) - p3 = p1 * m - print "%s * %d = %s" % ( p1, m, p3 ), - if p3.x() != x3 or p3.y() != y3: - print " Failure: should give (%d,%d)." % ( x3, y3 ) - else: - print " Good." - - # A few tests from X9.62 B.3: - - c = CurveFp( 23, 1, 1 ) - test_add( c, 3, 10, 9, 7, 17, 20 ) - test_double( c, 3, 10, 7, 12 ) - test_add( c, 3, 10, 3, 10, 7, 12 ) # (Should just invoke double.) - test_multiply( c, 3, 10, 2, 7, 12 ) - - # From X9.62 I.1 (p. 96): - - g = Point( c, 13, 7, 7 ) - - check = INFINITY - for i in range( 7 + 1 ): - p = ( i % 7 ) * g - print "%s * %d = %s, expected %s . . ." % ( g, i, p, check ), - if p == check: - print " Good." - else: - print " Bad." - check = check + g - - # NIST Curve P-192: - p = 6277101735386680763835789423207666416083908700390324961279L - r = 6277101735386680763835789423176059013767194773182842284081L - s = 0x3045ae6fc8422f64ed579528d38120eae12196d5L - c = 0x3099d2bbbfcb2538542dcd5fb078b6ef5f3d6fe2c745de65L - b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1L - Gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012L - Gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811L - - c192 = CurveFp( p, -3, b ) - p192 = Point( c192, Gx, Gy, r ) - - # Checking against some sample computations presented - # in X9.62: - - d = 651056770906015076056810763456358567190100156695615665659L - Q = d * p192 - if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5L: - print "p192 * d came out wrong." - else: - print "p192 * d came out right." - - k = 6140507067065001063065065565667405560006161556565665656654L - R = k * p192 - if R.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEADL \ - or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835L: - print "k * p192 came out wrong." - else: - print "k * p192 came out right." - - u1 = 2563697409189434185194736134579731015366492496392189760599L - u2 = 6266643813348617967186477710235785849136406323338782220568L - temp = u1 * p192 + u2 * Q - if temp.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEADL \ - or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835L: - print "u1 * p192 + u2 * Q came out wrong." - else: - print "u1 * p192 + u2 * Q came out right." diff --git a/reference/numbertheory.py b/reference/numbertheory.py deleted file mode 100644 index 7c45d25..0000000 --- a/reference/numbertheory.py +++ /dev/null @@ -1,609 +0,0 @@ -#! /usr/bin/env python -# -# Provide some simple capabilities from number theory. -# -# Version of 2008.11.14. -# -# Written in 2005 and 2006 by Peter Pearson and placed in the public domain. -# Revision history: -# 2008.11.14: Use pow( base, exponent, modulus ) for modular_exp. -# Make gcd and lcm accept arbitrarly many arguments. - - - -import math -import types - - -class Error( Exception ): - """Base class for exceptions in this module.""" - pass - -class SquareRootError( Error ): - pass - -class NegativeExponentError( Error ): - pass - - -def modular_exp( base, exponent, modulus ): - "Raise base to exponent, reducing by modulus" - if exponent < 0: - raise NegativeExponentError( "Negative exponents (%d) not allowed" \ - % exponent ) - return pow( base, exponent, modulus ) -# result = 1L -# x = exponent -# b = base + 0L -# while x > 0: -# if x % 2 > 0: result = (result * b) % modulus -# x = x / 2 -# b = ( b * b ) % modulus -# return result - - -def polynomial_reduce_mod( poly, polymod, p ): - """Reduce poly by polymod, integer arithmetic modulo p. - - Polynomials are represented as lists of coefficients - of increasing powers of x.""" - - # This module has been tested only by extensive use - # in calculating modular square roots. - - # Just to make this easy, require a monic polynomial: - assert polymod[-1] == 1 - - assert len( polymod ) > 1 - - while len( poly ) >= len( polymod ): - if poly[-1] != 0: - for i in range( 2, len( polymod ) + 1 ): - poly[-i] = ( poly[-i] - poly[-1] * polymod[-i] ) % p - poly = poly[0:-1] - - return poly - - - -def polynomial_multiply_mod( m1, m2, polymod, p ): - """Polynomial multiplication modulo a polynomial over ints mod p. - - Polynomials are represented as lists of coefficients - of increasing powers of x.""" - - # This is just a seat-of-the-pants implementation. - - # This module has been tested only by extensive use - # in calculating modular square roots. - - # Initialize the product to zero: - - prod = ( len( m1 ) + len( m2 ) - 1 ) * [0] - - # Add together all the cross-terms: - - for i in range( len( m1 ) ): - for j in range( len( m2 ) ): - prod[i+j] = ( prod[i+j] + m1[i] * m2[j] ) % p - - return polynomial_reduce_mod( prod, polymod, p ) - - - - -def polynomial_exp_mod( base, exponent, polymod, p ): - """Polynomial exponentiation modulo a polynomial over ints mod p. - - Polynomials are represented as lists of coefficients - of increasing powers of x.""" - - # Based on the Handbook of Applied Cryptography, algorithm 2.227. - - # This module has been tested only by extensive use - # in calculating modular square roots. - - assert exponent < p - - if exponent == 0: return [ 1 ] - - G = base - k = exponent - if k%2 == 1: s = G - else: s = [ 1 ] - - while k > 1: - k = k / 2 - G = polynomial_multiply_mod( G, G, polymod, p ) - if k%2 == 1: s = polynomial_multiply_mod( G, s, polymod, p ) - - return s - - - -def jacobi( a, n ): - """Jacobi symbol""" - - # Based on the Handbook of Applied Cryptography (HAC), algorithm 2.149. - - # This function has been tested by comparison with a small - # table printed in HAC, and by extensive use in calculating - # modular square roots. - - assert n >= 3 - assert n%2 == 1 - a = a % n - if a == 0: return 0 - if a == 1: return 1 - a1, e = a, 0 - while a1%2 == 0: - a1, e = a1/2, e+1 - if e%2 == 0 or n%8 == 1 or n%8 == 7: s = 1 - else: s = -1 - if a1 == 1: return s - if n%4 == 3 and a1%4 == 3: s = -s - return s * jacobi( n % a1, a1 ) - - - - -def square_root_mod_prime( a, p ): - """Modular square root of a, mod p, p prime.""" - - # Based on the Handbook of Applied Cryptography, algorithms 3.34 to 3.39. - - # This module has been tested for all values in [0,p-1] for - # every prime p from 3 to 1229. - - assert 0 <= a < p - assert 1 < p - - if a == 0: return 0 - if p == 2: return a - - jac = jacobi( a, p ) - if jac == -1: raise SquareRootError( "%d has no square root modulo %d" \ - % ( a, p ) ) - - if p % 4 == 3: return modular_exp( a, (p+1)/4, p ) - - if p % 8 == 5: - d = modular_exp( a, (p-1)/4, p ) - if d == 1: return modular_exp( a, (p+3)/8, p ) - if d == p-1: return ( 2 * a * modular_exp( 4*a, (p-5)/8, p ) ) % p - raise RuntimeError, "Shouldn't get here." - - for b in range( 2, p ): - if jacobi( b*b-4*a, p ) == -1: - f = ( a, -b, 1 ) - ff = polynomial_exp_mod( ( 0, 1 ), (p+1)/2, f, p ) - assert ff[1] == 0 - return ff[0] - raise RuntimeError, "No b found." - - - -def inverse_mod( a, m ): - """Inverse of a mod m.""" - - if a < 0 or m <= a: a = a % m - - # From Ferguson and Schneier, roughly: - - c, d = a, m - uc, vc, ud, vd = 1, 0, 0, 1 - while c != 0: - q, c, d = divmod( d, c ) + ( c, ) - uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc - - # At this point, d is the GCD, and ud*a+vd*m = d. - # If d == 1, this means that ud is a inverse. - - assert d == 1 - if ud > 0: return ud - else: return ud + m - - -def gcd2(a, b): - """Greatest common divisor using Euclid's algorithm.""" - while a: - a, b = b%a, a - return b - - -def gcd( *a ): - """Greatest common divisor. - - Usage: gcd( [ 2, 4, 6 ] ) - or: gcd( 2, 4, 6 ) - """ - - if len( a ) > 1: return reduce( gcd2, a ) - if hasattr( a[0], "__iter__" ): return reduce( gcd2, a[0] ) - return a[0] - - -def lcm2(a,b): - """Least common multiple of two integers.""" - - return (a*b)/gcd(a,b) - - -def lcm( *a ): - """Least common multiple. - - Usage: lcm( [ 3, 4, 5 ] ) - or: lcm( 3, 4, 5 ) - """ - - if len( a ) > 1: return reduce( lcm2, a ) - if hasattr( a[0], "__iter__" ): return reduce( lcm2, a[0] ) - return a[0] - - - -def factorization( n ): - """Decompose n into a list of (prime,exponent) pairs.""" - - assert isinstance( n, types.IntType ) or isinstance( n, types.LongType ) - - if n < 2: return [] - - result = [] - d = 2 - - # Test the small primes: - - for d in smallprimes: - if d > n: break - q, r = divmod( n, d ) - if r == 0: - count = 1 - while d <= n: - n = q - q, r = divmod( n, d ) - if r != 0: break - count = count + 1 - result.append( ( d, count ) ) - - # If n is still greater than the last of our small primes, - # it may require further work: - - if n > smallprimes[-1]: - if is_prime( n ): # If what's left is prime, it's easy: - result.append( ( n, 1 ) ) - else: # Ugh. Search stupidly for a divisor: - d = smallprimes[-1] - while 1: - d = d + 2 # Try the next divisor. - q, r = divmod( n, d ) - if q < d: break # n < d*d means we're done, n = 1 or prime. - if r == 0: # d divides n. How many times? - count = 1 - n = q - while d <= n: # As long as d might still divide n, - q, r = divmod( n, d ) # see if it does. - if r != 0: break - n = q # It does. Reduce n, increase count. - count = count + 1 - result.append( ( d, count ) ) - if n > 1: result.append( ( n, 1 ) ) - - return result - - - -def phi( n ): - """Return the Euler totient function of n.""" - - assert isinstance( n, types.IntType ) or isinstance( n, types.LongType ) - - if n < 3: return 1 - - result = 1 - ff = factorization( n ) - for f in ff: - e = f[1] - if e > 1: - result = result * f[0] ** (e-1) * ( f[0] - 1 ) - else: - result = result * ( f[0] - 1 ) - return result - - -def carmichael( n ): - """Return Carmichael function of n. - - Carmichael(n) is the smallest integer x such that - m**x = 1 mod n for all m relatively prime to n. - """ - - return carmichael_of_factorized( factorization( n ) ) - - -def carmichael_of_factorized( f_list ): - """Return the Carmichael function of a number that is - represented as a list of (prime,exponent) pairs. - """ - - if len( f_list ) < 1: return 1 - - result = carmichael_of_ppower( f_list[0] ) - for i in range( 1, len( f_list ) ): - result = lcm( result, carmichael_of_ppower( f_list[i] ) ) - - return result - -def carmichael_of_ppower( pp ): - """Carmichael function of the given power of the given prime. - """ - - p, a = pp - if p == 2 and a > 2: return 2**(a-2) - else: return (p-1) * p**(a-1) - - - -def order_mod( x, m ): - """Return the order of x in the multiplicative group mod m. - """ - - # Warning: this implementation is not very clever, and will - # take a long time if m is very large. - - if m <= 1: return 0 - - assert gcd( x, m ) == 1 - - z = x - result = 1 - while z != 1: - z = ( z * x ) % m - result = result + 1 - return result - - -def largest_factor_relatively_prime( a, b ): - """Return the largest factor of a relatively prime to b. - """ - - while 1: - d = gcd( a, b ) - if d <= 1: break - b = d - while 1: - q, r = divmod( a, d ) - if r > 0: - break - a = q - return a - - -def kinda_order_mod( x, m ): - """Return the order of x in the multiplicative group mod m', - where m' is the largest factor of m relatively prime to x. - """ - - return order_mod( x, largest_factor_relatively_prime( m, x ) ) - - -def is_prime( n ): - """Return True if x is prime, False otherwise. - - We use the Miller-Rabin test, as given in Menezes et al. p. 138. - This test is not exact: there are composite values n for which - it returns True. - - In testing the odd numbers from 10000001 to 19999999, - about 66 composites got past the first test, - 5 got past the second test, and none got past the third. - Since factors of 2, 3, 5, 7, and 11 were detected during - preliminary screening, the number of numbers tested by - Miller-Rabin was (19999999 - 10000001)*(2/3)*(4/5)*(6/7) - = 4.57 million. - """ - - # (This is used to study the risk of false positives:) - global miller_rabin_test_count - - miller_rabin_test_count = 0 - - if n <= smallprimes[-1]: - if n in smallprimes: return True - else: return False - - if gcd( n, 2*3*5*7*11 ) != 1: return False - - # Choose a number of iterations sufficient to reduce the - # probability of accepting a composite below 2**-80 - # (from Menezes et al. Table 4.4): - - t = 40 - n_bits = 1 + int( math.log( n, 2 ) ) - for k, tt in ( ( 100, 27 ), - ( 150, 18 ), - ( 200, 15 ), - ( 250, 12 ), - ( 300, 9 ), - ( 350, 8 ), - ( 400, 7 ), - ( 450, 6 ), - ( 550, 5 ), - ( 650, 4 ), - ( 850, 3 ), - ( 1300, 2 ), - ): - if n_bits < k: break - t = tt - - # Run the test t times: - - s = 0 - r = n - 1 - while ( r % 2 ) == 0: - s = s + 1 - r = r / 2 - for i in xrange( t ): - a = smallprimes[ i ] - y = modular_exp( a, r, n ) - if y != 1 and y != n-1: - j = 1 - while j <= s - 1 and y != n - 1: - y = modular_exp( y, 2, n ) - if y == 1: - miller_rabin_test_count = i + 1 - return False - j = j + 1 - if y != n-1: - miller_rabin_test_count = i + 1 - return False - return True - - -def next_prime( starting_value ): - "Return the smallest prime larger than the starting value." - - if starting_value < 2: return 2 - result = ( starting_value + 1 ) | 1 - while not is_prime( result ): result = result + 2 - return result - - -smallprimes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, - 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, - 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, - 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, - 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, - 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, - 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, - 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, - 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, - 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, - 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, - 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, - 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, - 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, - 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, - 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, - 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, - 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, - 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, - 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229] - -miller_rabin_test_count = 0 - -if __name__ == '__main__': - - # Making sure locally defined exceptions work: - # p = modular_exp( 2, -2, 3 ) - # p = square_root_mod_prime( 2, 3 ) - - - print "Testing gcd..." - assert gcd( 3*5*7, 3*5*11, 3*5*13 ) == 3*5 - assert gcd( [ 3*5*7, 3*5*11, 3*5*13 ] ) == 3*5 - assert gcd( 3 ) == 3 - - print "Testing lcm..." - assert lcm( 3, 5*3, 7*3 ) == 3*5*7 - assert lcm( [ 3, 5*3, 7*3 ] ) == 3*5*7 - assert lcm( 3 ) == 3 - - print "Testing next_prime..." - bigprimes = ( 999671, - 999683, - 999721, - 999727, - 999749, - 999763, - 999769, - 999773, - 999809, - 999853, - 999863, - 999883, - 999907, - 999917, - 999931, - 999953, - 999959, - 999961, - 999979, - 999983 ) - - for i in xrange( len( bigprimes ) - 1 ): - assert next_prime( bigprimes[i] ) == bigprimes[ i+1 ] - - error_tally = 0 - - # Test the square_root_mod_prime function: - - for p in smallprimes: - print "Testing square_root_mod_prime for modulus p = %d." % p - squares = [] - - for root in range( 0, 1+p/2 ): - sq = ( root * root ) % p - squares.append( sq ) - calculated = square_root_mod_prime( sq, p ) - if ( calculated * calculated ) % p != sq: - error_tally = error_tally + 1 - print "Failed to find %d as sqrt( %d ) mod %d. Said %d." % \ - ( root, sq, p, calculated ) - - for nonsquare in range( 0, p ): - if nonsquare not in squares: - try: - calculated = square_root_mod_prime( nonsquare, p ) - except SquareRootError: - pass - else: - error_tally = error_tally + 1 - print "Failed to report no root for sqrt( %d ) mod %d." % \ - ( nonsquare, p ) - - # Test the jacobi function: - for m in range( 3, 400, 2 ): - print "Testing jacobi for modulus m = %d." % m - if is_prime( m ): - squares = [] - for root in range( 1, m ): - if jacobi( root * root, m ) != 1: - error_tally = error_tally + 1 - print "jacobi( %d * %d, %d ) != 1" % ( root, root, m ) - squares.append( root * root % m ) - for i in range( 1, m ): - if not i in squares: - if jacobi( i, m ) != -1: - error_tally = error_tally + 1 - print "jacobi( %d, %d ) != -1" % ( i, m ) - else: # m is not prime. - f = factorization( m ) - for a in range( 1, m ): - c = 1 - for i in f: - c = c * jacobi( a, i[0] ) ** i[1] - if c != jacobi( a, m ): - error_tally = error_tally + 1 - print "%d != jacobi( %d, %d )" % ( c, a, m ) - - -# Test the inverse_mod function: - print "Testing inverse_mod . . ." - import random - n_tests = 0 - for i in range( 100 ): - m = random.randint( 20, 10000 ) - for j in range( 100 ): - a = random.randint( 1, m-1 ) - if gcd( a, m ) == 1: - n_tests = n_tests + 1 - inv = inverse_mod( a, m ) - if inv <= 0 or inv >= m or ( a * inv ) % m != 1: - error_tally = error_tally + 1 - print "%d = inverse_mod( %d, %d ) is wrong." % ( inv, a, m ) - assert n_tests > 1000 - print n_tests, " tests of inverse_mod completed." - - print error_tally, "errors detected." - diff --git a/reference/rijndael.py b/reference/rijndael.py deleted file mode 100755 index 7081157..0000000 --- a/reference/rijndael.py +++ /dev/null @@ -1,393 +0,0 @@ -""" -A pure python (slow) implementation of rijndael with a decent interface - -To include - - -from rijndael import rijndael - -To do a key setup - - -r = rijndael(key, block_size = 16) - -key must be a string of length 16, 24, or 32 -blocksize must be 16, 24, or 32. Default is 16 - -To use - - -ciphertext = r.encrypt(plaintext) -plaintext = r.decrypt(ciphertext) - -If any strings are of the wrong length a ValueError is thrown -""" - -# ported from the Java reference code by Bram Cohen, bram@gawth.com, April 2001 -# this code is public domain, unless someone makes -# an intellectual property claim against the reference -# code, in which case it can be made public domain by -# deleting all the comments and renaming all the variables - -import copy -import string - - - -#TREV 2011 - is this still needed? seems not -#----------------------- -#TREV - ADDED BECAUSE THERE'S WARNINGS ABOUT INT OVERFLOW BEHAVIOR CHANGING IN -#2.4..... -#import os -#if os.name != "java": -# import exceptions -# if hasattr(exceptions, "FutureWarning"): -# import warnings -# warnings.filterwarnings("ignore", category=FutureWarning, append=1) -#----------------------- - - - -shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]], - [[0, 0], [1, 5], [2, 4], [3, 3]], - [[0, 0], [1, 7], [3, 5], [4, 4]]] - -# [keysize][block_size] -num_rounds = {16: {16: 10, 24: 12, 32: 14}, 24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}} - -A = [[1, 1, 1, 1, 1, 0, 0, 0], - [0, 1, 1, 1, 1, 1, 0, 0], - [0, 0, 1, 1, 1, 1, 1, 0], - [0, 0, 0, 1, 1, 1, 1, 1], - [1, 0, 0, 0, 1, 1, 1, 1], - [1, 1, 0, 0, 0, 1, 1, 1], - [1, 1, 1, 0, 0, 0, 1, 1], - [1, 1, 1, 1, 0, 0, 0, 1]] - -# produce log and alog tables, needed for multiplying in the -# field GF(2^m) (generator = 3) -alog = [1] -for i in xrange(255): - j = (alog[-1] << 1) ^ alog[-1] - if j & 0x100 != 0: - j ^= 0x11B - alog.append(j) - -log = [0] * 256 -for i in xrange(1, 255): - log[alog[i]] = i - -# multiply two elements of GF(2^m) -def mul(a, b): - if a == 0 or b == 0: - return 0 - return alog[(log[a & 0xFF] + log[b & 0xFF]) % 255] - -# substitution box based on F^{-1}(x) -box = [[0] * 8 for i in xrange(256)] -box[1][7] = 1 -for i in xrange(2, 256): - j = alog[255 - log[i]] - for t in xrange(8): - box[i][t] = (j >> (7 - t)) & 0x01 - -B = [0, 1, 1, 0, 0, 0, 1, 1] - -# affine transform: box[i] <- B + A*box[i] -cox = [[0] * 8 for i in xrange(256)] -for i in xrange(256): - for t in xrange(8): - cox[i][t] = B[t] - for j in xrange(8): - cox[i][t] ^= A[t][j] * box[i][j] - -# S-boxes and inverse S-boxes -S = [0] * 256 -Si = [0] * 256 -for i in xrange(256): - S[i] = cox[i][0] << 7 - for t in xrange(1, 8): - S[i] ^= cox[i][t] << (7-t) - Si[S[i] & 0xFF] = i - -# T-boxes -G = [[2, 1, 1, 3], - [3, 2, 1, 1], - [1, 3, 2, 1], - [1, 1, 3, 2]] - -AA = [[0] * 8 for i in xrange(4)] - -for i in xrange(4): - for j in xrange(4): - AA[i][j] = G[i][j] - AA[i][i+4] = 1 - -for i in xrange(4): - pivot = AA[i][i] - if pivot == 0: - t = i + 1 - while AA[t][i] == 0 and t < 4: - t += 1 - assert t != 4, 'G matrix must be invertible' - for j in xrange(8): - AA[i][j], AA[t][j] = AA[t][j], AA[i][j] - pivot = AA[i][i] - for j in xrange(8): - if AA[i][j] != 0: - AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255] - for t in xrange(4): - if i != t: - for j in xrange(i+1, 8): - AA[t][j] ^= mul(AA[i][j], AA[t][i]) - AA[t][i] = 0 - -iG = [[0] * 4 for i in xrange(4)] - -for i in xrange(4): - for j in xrange(4): - iG[i][j] = AA[i][j + 4] - -def mul4(a, bs): - if a == 0: - return 0 - r = 0 - for b in bs: - r <<= 8 - if b != 0: - r = r | mul(a, b) - return r - -T1 = [] -T2 = [] -T3 = [] -T4 = [] -T5 = [] -T6 = [] -T7 = [] -T8 = [] -U1 = [] -U2 = [] -U3 = [] -U4 = [] - -for t in xrange(256): - s = S[t] - T1.append(mul4(s, G[0])) - T2.append(mul4(s, G[1])) - T3.append(mul4(s, G[2])) - T4.append(mul4(s, G[3])) - - s = Si[t] - T5.append(mul4(s, iG[0])) - T6.append(mul4(s, iG[1])) - T7.append(mul4(s, iG[2])) - T8.append(mul4(s, iG[3])) - - U1.append(mul4(t, iG[0])) - U2.append(mul4(t, iG[1])) - U3.append(mul4(t, iG[2])) - U4.append(mul4(t, iG[3])) - -# round constants -rcon = [1] -r = 1 -for t in xrange(1, 30): - r = mul(2, r) - rcon.append(r) - -del A -del AA -del pivot -del B -del G -del box -del log -del alog -del i -del j -del r -del s -del t -del mul -del mul4 -del cox -del iG - -class rijndael: - def __init__(self, key, block_size = 16): - if block_size != 16 and block_size != 24 and block_size != 32: - raise ValueError('Invalid block size: ' + str(block_size)) - if len(key) != 16 and len(key) != 24 and len(key) != 32: - raise ValueError('Invalid key size: ' + str(len(key))) - self.block_size = block_size - - ROUNDS = num_rounds[len(key)][block_size] - BC = block_size / 4 - # encryption round keys - Ke = [[0] * BC for i in xrange(ROUNDS + 1)] - # decryption round keys - Kd = [[0] * BC for i in xrange(ROUNDS + 1)] - ROUND_KEY_COUNT = (ROUNDS + 1) * BC - KC = len(key) / 4 - - # copy user material bytes into temporary ints - tk = [] - for i in xrange(0, KC): - tk.append((ord(key[i * 4]) << 24) | (ord(key[i * 4 + 1]) << 16) | - (ord(key[i * 4 + 2]) << 8) | ord(key[i * 4 + 3])) - - # copy values into round key arrays - t = 0 - j = 0 - while j < KC and t < ROUND_KEY_COUNT: - Ke[t / BC][t % BC] = tk[j] - Kd[ROUNDS - (t / BC)][t % BC] = tk[j] - j += 1 - t += 1 - tt = 0 - rconpointer = 0 - while t < ROUND_KEY_COUNT: - # extrapolate using phi (the round key evolution function) - tt = tk[KC - 1] - tk[0] ^= (S[(tt >> 16) & 0xFF] & 0xFF) << 24 ^ \ - (S[(tt >> 8) & 0xFF] & 0xFF) << 16 ^ \ - (S[ tt & 0xFF] & 0xFF) << 8 ^ \ - (S[(tt >> 24) & 0xFF] & 0xFF) ^ \ - (rcon[rconpointer] & 0xFF) << 24 - rconpointer += 1 - if KC != 8: - for i in xrange(1, KC): - tk[i] ^= tk[i-1] - else: - for i in xrange(1, KC / 2): - tk[i] ^= tk[i-1] - tt = tk[KC / 2 - 1] - tk[KC / 2] ^= (S[ tt & 0xFF] & 0xFF) ^ \ - (S[(tt >> 8) & 0xFF] & 0xFF) << 8 ^ \ - (S[(tt >> 16) & 0xFF] & 0xFF) << 16 ^ \ - (S[(tt >> 24) & 0xFF] & 0xFF) << 24 - for i in xrange(KC / 2 + 1, KC): - tk[i] ^= tk[i-1] - # copy values into round key arrays - j = 0 - while j < KC and t < ROUND_KEY_COUNT: - Ke[t / BC][t % BC] = tk[j] - Kd[ROUNDS - (t / BC)][t % BC] = tk[j] - j += 1 - t += 1 - # inverse MixColumn where needed - for r in xrange(1, ROUNDS): - for j in xrange(BC): - tt = Kd[r][j] - Kd[r][j] = U1[(tt >> 24) & 0xFF] ^ \ - U2[(tt >> 16) & 0xFF] ^ \ - U3[(tt >> 8) & 0xFF] ^ \ - U4[ tt & 0xFF] - self.Ke = Ke - self.Kd = Kd - - def encrypt(self, plaintext): - if len(plaintext) != self.block_size: - raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext))) - Ke = self.Ke - - BC = self.block_size / 4 - ROUNDS = len(Ke) - 1 - if BC == 4: - SC = 0 - elif BC == 6: - SC = 1 - else: - SC = 2 - s1 = shifts[SC][1][0] - s2 = shifts[SC][2][0] - s3 = shifts[SC][3][0] - a = [0] * BC - # temporary work array - t = [] - # plaintext to ints + key - for i in xrange(BC): - t.append((ord(plaintext[i * 4 ]) << 24 | - ord(plaintext[i * 4 + 1]) << 16 | - ord(plaintext[i * 4 + 2]) << 8 | - ord(plaintext[i * 4 + 3]) ) ^ Ke[0][i]) - # apply round transforms - for r in xrange(1, ROUNDS): - for i in xrange(BC): - a[i] = (T1[(t[ i ] >> 24) & 0xFF] ^ - T2[(t[(i + s1) % BC] >> 16) & 0xFF] ^ - T3[(t[(i + s2) % BC] >> 8) & 0xFF] ^ - T4[ t[(i + s3) % BC] & 0xFF] ) ^ Ke[r][i] - t = copy.copy(a) - # last round is special - result = [] - for i in xrange(BC): - tt = Ke[ROUNDS][i] - result.append((S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) - result.append((S[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) - result.append((S[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) - result.append((S[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF) - return string.join(map(chr, result), '') - - def decrypt(self, ciphertext): - if len(ciphertext) != self.block_size: - raise ValueError('wrong block length, expected ' + str(self.block_size) + ' got ' + str(len(plaintext))) - Kd = self.Kd - - BC = self.block_size / 4 - ROUNDS = len(Kd) - 1 - if BC == 4: - SC = 0 - elif BC == 6: - SC = 1 - else: - SC = 2 - s1 = shifts[SC][1][1] - s2 = shifts[SC][2][1] - s3 = shifts[SC][3][1] - a = [0] * BC - # temporary work array - t = [0] * BC - # ciphertext to ints + key - for i in xrange(BC): - t[i] = (ord(ciphertext[i * 4 ]) << 24 | - ord(ciphertext[i * 4 + 1]) << 16 | - ord(ciphertext[i * 4 + 2]) << 8 | - ord(ciphertext[i * 4 + 3]) ) ^ Kd[0][i] - # apply round transforms - for r in xrange(1, ROUNDS): - for i in xrange(BC): - a[i] = (T5[(t[ i ] >> 24) & 0xFF] ^ - T6[(t[(i + s1) % BC] >> 16) & 0xFF] ^ - T7[(t[(i + s2) % BC] >> 8) & 0xFF] ^ - T8[ t[(i + s3) % BC] & 0xFF] ) ^ Kd[r][i] - t = copy.copy(a) - # last round is special - result = [] - for i in xrange(BC): - tt = Kd[ROUNDS][i] - result.append((Si[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) - result.append((Si[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) - result.append((Si[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) - result.append((Si[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF) - return string.join(map(chr, result), '') - -def encrypt(key, block): - return rijndael(key, len(block)).encrypt(block) - -def decrypt(key, block): - return rijndael(key, len(block)).decrypt(block) - -def test(): - def t(kl, bl): - b = 'b' * bl - r = rijndael('a' * kl, bl) - assert r.decrypt(r.encrypt(b)) == b - t(16, 16) - t(16, 24) - t(16, 32) - t(24, 16) - t(24, 24) - t(24, 32) - t(32, 16) - t(32, 24) - t(32, 32) - diff --git a/scripts/TACK.py b/scripts/TACK.py deleted file mode 100755 index 076c808..0000000 --- a/scripts/TACK.py +++ /dev/null @@ -1,500 +0,0 @@ -#! /usr/bin/env python - -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from TACKpy import TACK, TACK_Break_Sig, TACK_Extension, \ - TACK_KeyFile, TACK_KeyFileViewer, \ - SSL_Cert, __version__, \ - m2cryptoLoaded, \ - posixTimeToStr, selfTest, pemSniff, \ - parseDurationArg, parseTimeArg, \ - TACK_Activation - -################ MAIN ### - -import time, math, sys, getpass, getopt - -def printError(s): - """Print error message and exit""" - sys.stderr.write("ERROR: %s\n" % s) - sys.exit(-1) - -def handleArgs(argv, argString, mandatoryString="", flags="", - tackcertFlag=False): - """Helper function for handling cmdline args. - -argv should be sys.argv[2:], i.e. the cmdline args minus "TACK ". -argString is a string with each char indicating a supported arg. -mandatoryString is a string with each char indicating a mandatory arg. - -Allowed chars in argString: "poickgmdesbn" -Allowed chars in mandatoryString: "ickd" -Allowed chars in flags: "v" - -Returns a list populated with an entry (or entries) for each char in -argString. The list is populated in "poickgdes" order, regardless of -argString order. - -Even if a char is not used as an argument, it will still return a value, -usually None. Note that the caller has to be careful to unpack the return -values in the correct order. -""" - # Convert to getopt argstring format: - # Add ":" after each arg, ie "abc" -> "a:b:c:" - getOptArgString = ":".join(argString) + ":" - getOptArgString += flags - try: - opts, argv = getopt.getopt(argv, getOptArgString) - except getopt.GetoptError as e: - printError(e) - # Default values if arg not present - password = None - outputFile = (sys.stdout, None) - inTack = None - inCert = None - inKey = None - keyPem = None # Temporary to load PEM'd key - outBreak = None - generation = None - min_generation = None - expiration = None - breakSigs = None - verbose = False - numArg = None - for opt, arg in opts: - if opt == "-p": - password = arg - elif opt == "-o": - # Defer opening it because -n might be set - outputFile = (None, arg) - elif opt == "-i": - try: - s = open(arg, "rU").read() - except IOError: - printError("Error opening TACK file: %s" % arg) - if not tackcertFlag or pemSniff(s, "TACK"): - try: - inTack = TACK() - inTack.parsePem(s) - except SyntaxError: - printError("TACK malformed: %s" % arg) - elif pemSniff(s, "CERTIFICATE"): - try: - inCert = SSL_Cert() - inCert.parsePem(s) - # OK, this is ugly, but we're returning the SSL_Cert - # via the "inTack" variable in the tackcertFlag=True - # case... - inTack = inCert - except AssertionError:#SyntaxError: - printError("TACK Certificate malformed: %s" % arg) - raise - else: - printError("Input must be either TACK or TACK certificate.") - elif opt == "-c": - try: - inCert = SSL_Cert() - inCert.open(arg) - except SyntaxError: - printError("SSL certificate malformed: %s" % arg) - except IOError: - printError("Error opening SSL certificate: %s" % arg) - elif opt == "-k": - try: - keyPem = open(arg, "rU").read() - except IOError: - printError("Error opening TACK Key File: %s" % arg) - elif opt == "-g": - try: - generation = int(arg) # Could raise ValueError - if generation < 0 or generation>255: - raise ValueError() - except ValueError: - printError("Bad generation: %s" % arg) - elif opt == "-m": - try: - min_generation = int(arg) # Could raise ValueError - if min_generation < 0 or min_generation>255: - raise ValueError() - except ValueError: - printError("Bad min_generation: %s" % arg) - elif opt == "-e": - # parseTimeArg will error and exit if arg is malformed - try: - expiration = parseTimeArg(arg) - except SyntaxError as e: - printError(e) - elif opt == "-b": - try: - breakSigsPem = open(arg, "rU").read() - except IOError: - printError("Error opening Break Sigs file: %s" % arg) - try: - breakSigs = TACK_Break_Sig.parsePemList(breakSigsPem) - except SyntaxError: - printError("Break Sigs malformed: %s" % arg) - elif opt == "-v": - verbose = True - elif opt == "-n": - try: - leftArg, rightArg = arg.split("@") # could raise ValueError - numTacks = int(leftArg) # could raise ValueError - interval = parseDurationArg(rightArg) # SyntaxError - if numTacks < 1 or numTacks >= 10000: - raise ValueError() - numArg = (numTacks, interval) - except (ValueError, SyntaxError): - printError("Bad -n NUMTACKS: %s:" % arg) - else: - assert(False) - if argv: - printError("Unknown arguments: %s" % argv) - - # Check that mandatory args were present - if "k" in mandatoryString and not keyPem: - printError("-k missing (TACK Key)") - if "c" in mandatoryString and not inCert: - printError("-c missing (SSL certificate)") - if "i" in mandatoryString and not inTack: - printError("-i missing (TACK)") - - # Load the key, prompting for password if not specified on cmdline - if keyPem: - try: - inKey = TACK_KeyFile() - if password: - if not inKey.parsePem(keyPem, password): - printError("Bad password") - else: - while 1: - password = getpass.getpass("Enter password for key file: ") - if inKey.parsePem(keyPem, password): - break - sys.stderr.write("PASSWORD INCORRECT!\n") - except SyntaxError: - printError("Error processing TACK Key File") - - # If -o and not -n, then open the output file - if outputFile[1]: - try: - if not numArg: - outputFile = (open(outputFile[1], "w"), outputFile[1]) - except IOError: - printError("Error opening output file: %s" % arg) - - if min_generation is None: - min_generation = 0 - if generation is None: - generation = min_generation - else: - if generation < min_generation: - printError("generation must be >= min_generation") - - # Populate the return list - retList = [] - if "p" in argString: - retList.append(password) - if "o" in argString: - retList.append(outputFile) - if "i" in argString: - retList.append(inTack) - if "c" in argString: - retList.append(inCert) - retList.append(inCert.key_sha256) - if "k" in argString: - retList.append(inKey) - if "g" in argString: - retList.append(generation) - if "m" in argString: - retList.append(min_generation) - if "e" in argString: - if not expiration: - # If not specified and not -n, - # round up to next minute from certificate - if not numArg: - expiration = int(math.ceil(inCert.notAfter / 60.0)) - retList.append(expiration) - if "b" in argString: - retList.append(breakSigs) - if "n" in argString: - retList.append(numArg) - if "v" in flags: - retList.append(verbose) - return retList - -def addPemComments(inStr): - """Add pre-PEM metadata/comments to PEM strings.""" - versionStr = __version__ - timeStr = posixTimeToStr(time.time(), True) - outStr = "Created by TACK.py %s\nCreated at %s\n%s" % \ - (versionStr, timeStr, inStr) - return outStr - -def genkeyCmd(argv): - """Handle "TACK genkey " command.""" - password, (outputFile,_), verbose = handleArgs(argv, "po", flags="v") - kf = TACK_KeyFile() - kf.create() # EC key is generated here - if not password: - password, password2 = "this", "that" - while password != password2: - password = getpass.getpass("Choose password for key file: ") - password2 = getpass.getpass("Re-enter password for key file: ") - if password != password2: - sys.stderr.write("PASSWORDS DON'T MATCH!\n") - outputFile.write(addPemComments(kf.writePem(password))) - if verbose: - sys.stderr.write(kf.writeText()+"\n") - -def signCmd(argv): - """Handle "TACK sign " command.""" - (password, (outputFile,outputFilename), inCert, hash, inKey, - generation, min_generation, expiration, - numArg, verbose) = \ - handleArgs(argv, "pockgmen", "kcd", flags="v") - - if not numArg: # No -n - tack = TACK() - tack.create(inKey.public_key, min_generation, generation, - expiration, hash, inKey.sign) - outputFile.write(addPemComments(tack.writePem())) - if verbose: - sys.stderr.write(tack.writeText()+"\n") - else: - (numTacks, interval) = numArg - if not outputFilename: - printError("-o required with -n") - if not expiration: - printError("-e required with -n") - for x in range(numTacks): - tack = TACK() - tack.create(inKey.public_key, min_generation, generation, - expiration, hash, inKey.sign) - outputFile = open(outputFilename+"_%04d.pem" % x, "w") - outputFile.write(addPemComments(tack.writePem())) - outputFile.close() - if verbose: - sys.stderr.write(tack.writeText()+"\n") - expiration += interval - -def breakCmd(argv): - """Handle "TACK break " command.""" - password, (outputFile,_), inKey, verbose = \ - handleArgs(argv, "pok", "k", flags="v") - - breakSig = TACK_Break_Sig() - breakSig.create(inKey.public_key, inKey.sign) - outputFile.write(addPemComments(breakSig.writePem())) - if verbose: - sys.stderr.write(breakSig.writeText()+"\n") - -def tackcertCmd(argv): - """Handle "TACK tackcert " command.""" - (outputFile,_), X, breakSigs, verbose = \ - handleArgs(argv, "oib", "i", tackcertFlag=True, flags="v") - if isinstance(X, TACK): - tack = X - tackExt = TACK_Extension() - tackExt.create(tack, breakSigs, TACK_Activation.disabled) #!!! - tc = SSL_Cert() - tc.create(tackExt) - outputFile.write(tc.writePem()) - if verbose: - sys.stderr.write(tackExt.writeText()+"\n") - elif isinstance(X, SSL_Cert): - if breakSigs: - printError("invalid arguments: Break Sigs with TACK Cert.") - sslCert = X - s = "" - if sslCert.tackExt: - if sslCert.tackExt.tack: - s += sslCert.tackExt.tack.writePem() - if sslCert.tackExt.break_sigs: - for bs in sslCert.tackExt.break_sigs: - s += bs.writePem() - print(s) - if verbose: - sys.stderr.write(sslCert.writeText()+"\n") - - -def viewCmd(argv): - """Handle "TACK view " command.""" - if len(argv) < 1: - printError("Missing argument: file to view") - if len(argv) > 1: - printError("Can only view one file") - try: - # Read both binary (bytearray) and text (str) versions of the input - b = bytearray(open(argv[0], "rb").read()) - try: - s = open(argv[0], "rU").read() - except UnicodeDecodeError: - # Python3 error, so it must be a binary file; not text - s = None - except IOError: - printError("Error opening file: %s" % argv[0]) - - fileType = None - try: - if s: - if pemSniff(s, "TACK PRIVATE KEY"): - fileType = "Private Key" - kfv = TACK_KeyFileViewer() - kfv.parse(s) - print(kfv.writeText()) - return - elif pemSniff(s, "TACK"): - fileType = "TACK" - t = TACK() - t.parsePem(s) - print(t.writeText()) - return - elif pemSniff(s, "TACK BREAK SIG"): - fileType = "Break Sig" - tbsList = TACK_Break_Sig.parsePemList(s) - s = "" - for tbs in tbsList: - s += tbs.writeText() - print(s) - return - elif pemSniff(s, "CERTIFICATE"): - fileType = "Certificate" - sslc = SSL_Cert() - sslc.parsePem(s) - print(sslc.writeText()) - return - # Is it an SSL certificate? - try: - sslc = SSL_Cert() - sslc.parse(b) - print(sslc.writeText()) - except SyntaxError: - printError("Unrecognized file type") - except SyntaxError as e: - printError("Error parsing %s: %s" % (fileType, e)) - -def testCmd(argv): - assert(selfTest() == 1) - print("OK") - -def printUsage(s=None): - if m2cryptoLoaded: - crypto = "M2Crypto/OpenSSL" - else: - crypto = "Python crypto" - if s: - print("ERROR: %s" % s) - print("""\nTACK.py version %s (using %s) - -Commands (use "help " to see optional args): - genkey - sign -k KEY -c CERT - break -k KEY - view FILE - test - help COMMAND -""" % (__version__, crypto)) - sys.exit(-1) - -def helpCmd(argv): - """Handle "TACK help " command.""" - if len(argv) == 0: - printUsage() - cmd = argv[0] - if cmd == "genkey"[:len(cmd)]: - print( \ -"""Creates a new TACK key file. - - genkey - -Optional arguments: - -v : Verbose - -o FILE : Write the output to this file (instead of stdout) - -p PASSWORD : Use this TACK key password instead of prompting -""") - elif cmd == "sign"[:len(cmd)]: - s = posixTimeToStr(time.time()) - print( \ -"""Creates a TACK based on a target SSL certificate. - - sign -k KEY -c CERT - - -k KEY : Use this TACK key file - -c CERT : Sign this SSL certificate's public key - -Optional arguments: - -v : Verbose - -o FILE : Write the output to this file (instead of stdout) - -p PASSWORD : Use this TACK key password instead of prompting - -m MIN_GENERATION : Use this min_generation number (0-255) - -g GENERATION : Use this generation number (0-255) - -e EXPIRATION : Use this UTC time for expiration - ("%s", "%sZ", - "%sZ", "%sZ" etc.) - Or, specify a duration from current time: - ("5m", "30d", "1d12h5m", "0m", etc.) - - n NUM@INTERVAL : Generate NUM TACKs, with expiration times spaced - out by INTERVAL (see -d for INTERVAL syntax). The - -o argument is used as a filename prefix, and the - -e argument is used as the first expiration time. -""" % (s, s[:13], s[:10], s[:4])) - elif cmd == "break"[:len(cmd)]: - print( \ -"""Creates a break signature based on an input TACK key file. - - break -k KEY - - -k KEY : Use this TACK key file - -Optional arguments: - -v : Verbose - -o FILE : Write the output to this file (instead of stdout) - -p PASSWORD : Use this TACK key password instead of prompting -""") - elif cmd == "view"[:len(cmd)]: - print("""Views a TACK, TACK Key, TACK Break Sig, or SSL certificate. - - view -""") - elif cmd == "tackcert"[:len(cmd)]: - print( \ -"""Creates a TACK certificate with the input TACK and optional Break Sigs. - -(Alternatively, if input is a TACK certificate, writes out the TACK and/or -Break Signatures as PEM files). - - tackcert -i (TACK or CERT) - -Optional arguments: - -v : Verbose - -b BREAKSIGS : Include Break Signatures from this file. - -o FILE : Write the output to this file (instead of stdout) -""") - elif cmd == "test"[:len(cmd)]: - print( \ -"""Runs self-tests. -""") - else: - printError("Help requested for unknown command") - - -if __name__ == '__main__': - if len(sys.argv) < 2: - printUsage("Missing command") - elif sys.argv[1] == "genkey"[:len(sys.argv[1])]: - genkeyCmd(sys.argv[2:]) - elif sys.argv[1] == "sign"[:len(sys.argv[1])]: - signCmd(sys.argv[2:]) - elif sys.argv[1] == "break"[:len(sys.argv[1])]: - breakCmd(sys.argv[2:]) - elif sys.argv[1] == "tackcert"[:len(sys.argv[1])]: - tackcertCmd(sys.argv[2:]) - elif sys.argv[1] == "view"[:len(sys.argv[1])]: - viewCmd(sys.argv[2:]) - elif sys.argv[1] == "test"[:len(sys.argv[1])]: - testCmd(sys.argv[2:]) - elif sys.argv[1] == "help"[:len(sys.argv[1])]: - helpCmd(sys.argv[2:]) - else: - printUsage("Unknown command: %s" % sys.argv[1]) \ No newline at end of file diff --git a/setup.py b/setup.py index 0cb33a2..cf69def 100755 --- a/setup.py +++ b/setup.py @@ -4,6 +4,10 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. +import os +import shutil + +shutil.copyfile("tack.py", "tack/tack") setup(name="TACKpy", version="0.9.6", @@ -12,5 +16,15 @@ url="https://github.com/trevp/TACKpy", description="TACKpy implements TACK in python", license="public domain", - scripts=["scripts/TACK.py"], - packages=["TACKpy"]) + scripts=["tack/tack"], + packages=["tack"], + install_requires=['M2Crypto']) + +print "Cleaning up..." +if os.path.exists("build/"): + shutil.rmtree("build/") + +try: + os.remove("tack/tack") +except: + pass diff --git a/tack.py b/tack.py new file mode 100755 index 0000000..e1117f7 --- /dev/null +++ b/tack.py @@ -0,0 +1,29 @@ +#! /usr/bin/env python + +# Author: Trevor Perrin +# See the LICENSE file for legal information regarding use of this file. +import sys +from tack.commands.BreakCommand import BreakCommand +from tack.commands.CertificateCommand import CertificateCommand +from tack.commands.GenerateKeyCommand import GenerateKeyCommand +from tack.commands.HelpCommand import HelpCommand +from tack.commands.SignCommand import SignCommand +from tack.commands.ViewCommand import ViewCommand + +if __name__ == '__main__': + if len(sys.argv) < 2: + HelpCommand.printGeneralUsage("Missing command") + elif sys.argv[1] == "genkey"[:len(sys.argv[1])]: + GenerateKeyCommand(sys.argv[2:]).execute() + elif sys.argv[1] == "sign"[:len(sys.argv[1])]: + SignCommand(sys.argv[2:]).execute() + elif sys.argv[1] == "break"[:len(sys.argv[1])]: + BreakCommand(sys.argv[2:]).execute() + elif sys.argv[1] == "tackcert"[:len(sys.argv[1])]: + CertificateCommand(sys.argv[2:]).execute() + elif sys.argv[1] == "view"[:len(sys.argv[1])]: + ViewCommand(sys.argv[2:]).execute() + elif sys.argv[1] == "help"[:len(sys.argv[1])]: + HelpCommand(sys.argv[2:]).execute() + else: + HelpCommand.printGeneralUsage("Unknown command: %s" % sys.argv[1]) diff --git a/tack/InvalidPasswordException.py b/tack/InvalidPasswordException.py new file mode 100644 index 0000000..7005c95 --- /dev/null +++ b/tack/InvalidPasswordException.py @@ -0,0 +1,4 @@ + +class InvalidPasswordException(Exception): + def __init__(self, args): + Exception.__init__(self, args) \ No newline at end of file diff --git a/tack/__init__.py b/tack/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tack/commands/BreakCommand.py b/tack/commands/BreakCommand.py new file mode 100644 index 0000000..6a97ff2 --- /dev/null +++ b/tack/commands/BreakCommand.py @@ -0,0 +1,33 @@ +import sys +from tack.commands.Command import Command +from tack.structures.TackBreakSig import TackBreakSig + +class BreakCommand(Command): + + def __init__(self, argv): + Command.__init__(self, argv, "pok", "v") + self.password = self.getPassword() + self.outputFile, self.outputFileName = self.getOutputFile() + self.key = self.getKey(self.getPassword()) + + def execute(self): + breakSig = TackBreakSig.createFromParameters(self.key.getPublicKey(), self.key) + self.outputFile.write(self.addPemComments(breakSig.serializeAsPem())) + + if self.isVerbose(): + sys.stderr.write(str(breakSig) + "\n") + + @staticmethod + def printHelp(): + print(\ + """Creates a break signature based on an input TACK key file. + + break -k KEY + + -k KEY : Use this TACK key file + + Optional arguments: + -v : Verbose + -o FILE : Write the output to this file (instead of stdout) + -p PASSWORD : Use this TACK key password instead of prompting + """) diff --git a/tack/commands/CertificateCommand.py b/tack/commands/CertificateCommand.py new file mode 100644 index 0000000..bba8fde --- /dev/null +++ b/tack/commands/CertificateCommand.py @@ -0,0 +1,106 @@ +import sys +from tack.commands.Command import Command +from tack.structures.Tack import Tack +from tack.structures.TackActivation import TackActivation +from tack.structures.TackBreakSig import TackBreakSig +from tack.structures.TackExtension import TackExtension +from tack.tls.TlsCertificate import TlsCertificate +from tack.util.PEMDecoder import PEMDecoder + +class CertificateCommand(Command): + + def __init__(self, argv): + Command.__init__(self, argv, "oib", "v") + + self.outputFile, self.outputFileName = self.getOutputFile() + self.inputTack = self._getInputTack() + self.inputCertificate = self._getInputCertificate() + self.breakSignatures = self._getBreakSignatures() + + if self.inputTack is None and self.inputCertificate is None: + self.printError("-i missing") + + def execute(self): + if self.inputTack is not None: + tackExtension = TackExtension.createFromParameters(self.inputTack, self.breakSignatures, + TackActivation.DISABLED) + tlsCertificate = TlsCertificate() + tlsCertificate.create(tackExtension) + + self.outputFile.write(tlsCertificate.writePem()) + + if self.isVerbose(): + sys.stderr.write(str(tackExtension) + "\n") + + elif self.inputCertificate is not None: + if self.breakSignatures is not None: + self.printError("Invalid arguments: break sigs with TACK cert.") + + s = "" + if self.inputCertificate.tackExt: + if self.inputCertificate.tackExt.tack: + s += self.inputCertificate.tackExt.tack.serializeAsPem() + if self.inputCertificate.tackExt.break_sigs: + for bs in self.inputCertificate.tackExt.break_sigs: + s += bs.serializeAsPem() + + self.outputFile.write(s) + + if self.isVerbose(): + sys.stderr.write(self.inputCertificate.writeText() + "\n") + + def _getBreakSignatures(self): + fileName = self._getOptionValue("-b") + + if fileName is None: + return None + + contents = open(fileName, "r").read() + + return TackBreakSig.createFromPem(contents) + + def _getInputTack(self): + contents = self._getInputFileContents() + + if contents is None: + return None + + if PEMDecoder(contents).containsEncoded("TACK"): + return Tack.createFromPem(contents) + + return None + + def _getInputCertificate(self): + contents = self._getInputFileContents() + + if contents is None: + return None + + if PEMDecoder(contents).containsEncoded("CERTIFICATE"): + certificate = TlsCertificate() + certificate.open(self._getOptionValue("-i")) + return certificate + + def _getInputFileContents(self): + fileName = self._getOptionValue("-i") + + if fileName is None: + return None + + return open(fileName, "r").read() + + @staticmethod + def printHelp(): + print(\ + """Creates a TACK certificate with the input TACK and optional Break Sigs. + +(Alternatively, if input is a TACK certificate, writes out the TACK and/or +Break Signatures as PEM files). + +tackcert -i (TACK or CERT) + +Optional arguments: + -v : Verbose + -b BREAKSIGS : Include Break Signatures from this file. + -o FILE : Write the output to this file (instead of stdout) + """) diff --git a/tack/commands/Command.py b/tack/commands/Command.py new file mode 100644 index 0000000..f72b804 --- /dev/null +++ b/tack/commands/Command.py @@ -0,0 +1,93 @@ +import getopt +import getpass +import sys +import time +from tack.structures.TackKeyFile import TackKeyFile +from tack.util.Time import Time +from tack.version import __version__ +from tack.InvalidPasswordException import InvalidPasswordException + +class Command: + + def __init__(self, argv, options, flags): + try: + self.argv = argv + self.flags = flags + self.options = ":".join(options) + ":" + self.values, self.remainder = getopt.getopt(argv, self.options + self.flags) + except getopt.GetoptError as e: + self.printError(e) + + def isVerbose(self): + return self._containsOption("-v") + + def getPassword(self): + return self._getOptionValue("-p") + + def getKey(self, password): + keyPemFile = self._getOptionValue("-k") + + if not keyPemFile: + self.printError("-k missing (TACK Key)") + + if not password: + password = self._promptPassword() + + try: + keyPemData = open(keyPemFile, "rU").read() + + while True: + try: + inKey = TackKeyFile.createFromPem(keyPemData, password) + return inKey + except InvalidPasswordException, ipe: + sys.stderr.write("Password incorrect!\n") + password = self._promptPassword() + except SyntaxError: + self.printError("Error processing TACK Key File") + + except IOError: + self.printError("Error opening TACK Key File: %s" % keyPemFile) + + + def getOutputFile(self): + output = None + + try: + output = self._getOptionValue("-o") + + if output is None: + return sys.stdout, None + else: + return open(output, "w"), output + except IOError: + self.printError("Error opening output file: %s" % output) + + def addPemComments(self, inStr): + """Add pre-PEM metadata/comments to PEM strings.""" + versionStr = __version__ + timeStr = Time.posixTimeToStr(time.time(), True) + outStr = "Created by tack.py %s\nCreated at %s\n%s" %\ + (versionStr, timeStr, inStr) + return outStr + + def _promptPassword(self): + return getpass.getpass("Enter password for key file: ") + + def _getOptionValue(self, flag): + for option, value in self.values: + if option == flag: + return value + + return None + + def _containsOption(self, flag): + for option, value in self.values: + if option == flag: + return True + + def printError(self, error): + """Print error message and exit""" + sys.stderr.write("ERROR: %s\n" % error) + sys.exit(-1) + diff --git a/tack/commands/GenerateKeyCommand.py b/tack/commands/GenerateKeyCommand.py new file mode 100644 index 0000000..ae2a336 --- /dev/null +++ b/tack/commands/GenerateKeyCommand.py @@ -0,0 +1,46 @@ +import getpass +import sys +from tack.commands.Command import Command +from tack.structures.TackKeyFile import TackKeyFile + +class GenerateKeyCommand(Command): + + def __init__(self, argv): + Command.__init__(self, argv, "po", "v") + self.password = self.getPassword() + self.outputFile, self.outputFileName = self.getOutputFile() + + def execute(self): + password = self._getPassword() + keyFile = TackKeyFile.createRandom(password) + self.outputFile.write(self.addPemComments(keyFile.serializeAsPem())) + + if self.isVerbose(): + sys.stderr.write(str(keyFile) + "\n") + + def _getPassword(self): + if not self.password: + password, password2 = "this", "that" + while password != password2: + password = getpass.getpass("Choose password for key file: ") + password2 = getpass.getpass("Re-enter password for key file: ") + + if password != password2: + sys.stderr.write("PASSWORDS DON'T MATCH!\n") + + self.password = password + + return self.password + + @staticmethod + def printHelp(): + print(\ + """Creates a new TACK key file. + + genkey + + Optional arguments: + -v : Verbose + -o FILE : Write the output to this file (instead of stdout) + -p PASSWORD : Use this TACK key password instead of prompting + """) diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py new file mode 100644 index 0000000..f97c6be --- /dev/null +++ b/tack/commands/HelpCommand.py @@ -0,0 +1,52 @@ +import sys +from tack.commands.CertificateCommand import CertificateCommand +from tack.version import __version__ +from tack.commands.BreakCommand import BreakCommand +from tack.commands.Command import Command +from tack.commands.GenerateKeyCommand import GenerateKeyCommand +from tack.commands.SignCommand import SignCommand +from tack.commands.ViewCommand import ViewCommand + +class HelpCommand(Command): + + COMMANDS = {"genkey" : GenerateKeyCommand, "sign" : SignCommand, + "break" : BreakCommand, "view" : ViewCommand, + "tackcert" : CertificateCommand} + + def __init__(self, argv): + Command.__init__(self, argv, "", "") + + if len(argv) < 1: + HelpCommand.printGeneralUsage() + + self.command = argv[0] + + if not self.command in HelpCommand.COMMANDS: + self.printError("%s not a valid command." % self.command) + + def execute(self): + HelpCommand.COMMANDS[self.command].printHelp() + + @staticmethod + def printHelp(): + print(\ + """Provides help for individual commands. + + help + """) + + @staticmethod + def printGeneralUsage(message=None): + print "Error: %s" % message + print(\ + """\ntack.py version %s + +Commands (use "help " to see optional args): + genkey + sign -k KEY -c CERT + break -k KEY + tackcert -i TACK + view FILE + help COMMAND +""" % __version__) + sys.exit(-1) diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py new file mode 100644 index 0000000..388205a --- /dev/null +++ b/tack/commands/SignCommand.py @@ -0,0 +1,155 @@ +import sys +import time +import math +from tack.commands.Command import Command +from tack.structures.Tack import Tack +from tack.tls.TlsCertificate import TlsCertificate +from tack.util.Time import Time + +class SignCommand(Command): + + def __init__(self, argv): + Command.__init__(self, argv, "kcopmgen", "v") + + self.password = self.getPassword() + self.outputFile, self.outputFileName = self.getOutputFile() + self.key = self.getKey(self.password) + + self.certificate = self._getCertificate() + self.generation = self._getGeneration() + self.min_generation = self._getMinGeneration() + self.expiration = self._getExpiration() + self.numArg = self._getTackCount() + + + def execute(self): + if not self.numArg: + tack = Tack.createFromParameters(self.key.getPublicKey(), self.key, self.min_generation, + self.generation, self.expiration, self.certificate.key_sha256) + + self.outputFile.write(self.addPemComments(tack.serializeAsPem())) + + if self.isVerbose(): + sys.stderr.write(str(tack) + "\n") + else: + (numTacks, interval) = self.numArg + + if not self.outputFileName: + self.printError("-o required with -n") + + for x in range(numTacks): + tack = Tack.createFromParameters(self.key.getPublicKey(), self.key, self.min_generation, + self.generation, self.expiration, self.certificate.key_sha256) + + outputFile = open(self.outputFileName + "_%04d.pem" % x, "w") + outputFile.write(self.addPemComments(tack.serializeAsPem())) + outputFile.close() + + if self.isVerbose(): + sys.stderr.write(str(tack) + "\n") + + self.expiration += interval + + def _getCertificate(self): + certificateFile = self._getOptionValue("-c") + + if not certificateFile: + self.printError("-c missing (Certificate)") + + try: + inCert = TlsCertificate() + inCert.open(certificateFile) + return inCert + except SyntaxError: + self.printError("SSL certificate malformed: %s" % certificateFile) + except IOError: + self.printError("Error opening SSL certificate: %s" % certificateFile) + + def _getExpiration(self): + expiration = self._getOptionValue("-e") + + if expiration is None and self._getTackCount() is None: + return int(math.ceil(self._getCertificate().notAfter / 60.0)) + else: + try: + return Time.parseTimeArg(expiration) + except SyntaxError as e: + self.printError(e) + + def _getTackCount(self): + tackCount = self._getOptionValue("-n") + + if tackCount is None: + return None + + try: + leftArg, rightArg = tackCount.split("@") # could raise ValueError + numTacks = int(leftArg) # could raise ValueError + interval = Time.parseDurationArg(rightArg) # SyntaxError + if numTacks < 1 or numTacks >= 10000: + raise ValueError() + return numTacks, interval + except (ValueError, SyntaxError): + self.printError("Bad -n NUMTACKS: %s:" % tackCount) + + def _getGeneration(self): + generation = self._getOptionValue("-g") + + if generation is None: + generation = self._getMinGeneration() + + try: + generation = int(generation) # Could raise ValueError + if generation < 0 or generation>255: + raise ValueError() + except ValueError: + self.printError("Bad generation: %s" % generation) + + if generation < self._getMinGeneration(): + self.printError("generation must be >= min_generation") + + return generation + + def _getMinGeneration(self): + min_generation = self._getOptionValue("-m") + + if min_generation is None: + min_generation = 0 + + try: + min_generation = int(min_generation) # Could raise ValueError + if min_generation < 0 or min_generation>255: + raise ValueError() + except ValueError: + self.printError("Bad min_generation: %s" % min_generation) + + return min_generation + + @staticmethod + def printHelp(): + s = Time.posixTimeToStr(time.time()) + print(\ + """Creates a TACK based on a target SSL certificate. + + sign -k KEY -c CERT + + -k KEY : Use this TACK key file + -c CERT : Sign this SSL certificate's public key + + Optional arguments: + -v : Verbose + -o FILE : Write the output to this file (instead of stdout) + -p PASSWORD : Use this TACK key password instead of prompting + -m MIN_GENERATION : Use this min_generation number (0-255) + -g GENERATION : Use this generation number (0-255) + -e EXPIRATION : Use this UTC time for expiration + ("%s", "%sZ", + "%sZ", "%sZ" etc.) + Or, specify a duration from current time: + ("5m", "30d", "1d12h5m", "0m", etc.) + - n NUM@INTERVAL : Generate NUM TACKs, with expiration times spaced + out by INTERVAL (see -d for INTERVAL syntax). The + -o argument is used as a filename prefix, and the + -e argument is used as the first expiration time. + """ % (s, s[:13], s[:10], s[:4])) + diff --git a/tack/commands/ViewCommand.py b/tack/commands/ViewCommand.py new file mode 100644 index 0000000..fc14d12 --- /dev/null +++ b/tack/commands/ViewCommand.py @@ -0,0 +1,80 @@ +from tack.commands.Command import Command +from tack.structures.Tack import Tack +from tack.structures.TackKeyFile import TackKeyFile +from tack.structures.TackBreakSig import TackBreakSig +from tack.tls.TlsCertificate import TlsCertificate +from tack.util.PEMDecoder import PEMDecoder + +class ViewCommand(Command): + + def __init__(self, argv): + Command.__init__(self, argv, "", "") + + if len(argv) < 1: + self.printError("Missing argument: file to view") + if len(argv) > 1: + self.printError("Can only view one file") + + + def _readFile(self, argv): + try: + # Read both binary (bytearray) and text (str) versions of the input + b = bytearray(open(argv[0], "rb").read()) + try: + s = open(argv[0], "rU").read() + except UnicodeDecodeError: + # Python3 error, so it must be a binary file; not text + s = None + + return s, b + except IOError: + self.printError("Error opening file: %s" % argv[0]) + + def execute(self): + text, binary = self._readFile(self.argv) + fileType = None + + try: + if text: + decoder = PEMDecoder(text) + if decoder.containsEncoded("TACK PRIVATE KEY"): + fileType = "Private Key" + kf = TackKeyFile.createFromPem(text, None) + print(str(kf)) + return + elif decoder.containsEncoded("TACK"): + fileType = "TACK" + tack = Tack.createFromPem(text) + print(str(tack)) + return + elif decoder.containsEncoded("TACK BREAK SIG"): + fileType = "Break Sig" + tbsList = TackBreakSig.createFromPemList(text) + s = "" + for tbs in tbsList: + s += str(tbs) + print(s) + return + elif decoder.containsEncoded( "CERTIFICATE"): + fileType = "Certificate" + sslc = TlsCertificate() + sslc.parsePem(text) + print(sslc.writeText()) + return + # Is it an SSL certificate? + try: + sslc = TlsCertificate() + sslc.parse(binary) + print(sslc.writeText()) + except SyntaxError: + self.printError("Unrecognized file type") + except SyntaxError as e: + self.printError("Error parsing %s: %s" % (fileType, e)) + + @staticmethod + def printHelp(): + print(\ + """Views a TACK, TACK Key, TACK Break Sig, or SSL certificate. + + view + """) diff --git a/tack/commands/__init__.py b/tack/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/TACKpy/compat.py b/tack/compat.py similarity index 68% rename from TACKpy/compat.py rename to tack/compat.py index 2a993eb..e705c0d 100644 --- a/TACKpy/compat.py +++ b/tack/compat.py @@ -77,35 +77,3 @@ def b2a_base32(b): def bytesToStrAscii(b): return str(b) - - -def testCompat(): - print("Testing COMPAT") - assert(isinstance(a2b_hex("00"), bytearray)) - assert(a2b_hex("0102cdef") == bytearray([0x01,0x02,0xcd,0xef])) - assert(a2b_hex("0102CDEF") == bytearray([0x01,0x02,0xcd,0xef])) - try: - assert(a2b_hex("c0102cdef") == bytearray([0x01,0x02,0xcd,0xef])) - assert(False) - except SyntaxError: - pass - try: - assert(a2b_hex("xx0102cdef") == bytearray([0x01,0x02,0xcd,0xef])) - assert(False) - except SyntaxError: - pass - - assert(isinstance(a2b_base64("0000"), bytearray)) - assert(a2b_base64("Zm9vYg==") == bytearray(b"foob")) - - assert(b2a_hex(bytearray([0x01,0x02,0xcd,0xef])) == "0102cdef") - assert(b2a_hex(bytearray([0x00])) == "00") - assert(b2a_hex(bytearray([0xFF])) == "ff") - - assert(b2a_base64(bytearray(b"foob")) == "Zm9vYg==\n") - assert(b2a_base64(bytearray(b"fooba")) == "Zm9vYmE=\n") - assert(b2a_base64(bytearray(b"foobar")) == "Zm9vYmFy\n") - - assert(bytesToStrAscii(bytearray(b"abcd123")) == "abcd123") - return 1 - \ No newline at end of file diff --git a/tack/crypto/AES.py b/tack/crypto/AES.py new file mode 100644 index 0000000..77fe1f5 --- /dev/null +++ b/tack/crypto/AES.py @@ -0,0 +1,59 @@ +from M2Crypto import m2 + +class AES: + def __init__(self, key, IV): + if len(key) not in (16, 24, 32): + raise AssertionError() + + if len(IV) != 16: + raise AssertionError() + + self.isBlockCipher = True + self.block_size = 16 + self.key = key + self.IV = IV + + if len(key)==16: + self.name = "aes128" + elif len(key)==24: + self.name = "aes192" + elif len(key)==32: + self.name = "aes256" + else: + raise AssertionError() + + def encrypt(self, plaintext): + assert(len(plaintext) % 16 == 0) + context = self._createContext(1) + ciphertext = m2.cipher_update(context, plaintext) + m2.cipher_ctx_free(context) + self.IV = ciphertext[-self.block_size:] + return bytearray(ciphertext) + + def decrypt(self, ciphertext): + assert(len(ciphertext) % 16 == 0) + context = self._createContext(0) + #I think M2Crypto has a bug - it fails to decrypt and return the last block passed in. + #To work around this, we append sixteen zeros to the string, below: + plaintext = m2.cipher_update(context, ciphertext+('\0'*16)) + + #If this bug is ever fixed, then plaintext will end up having a garbage + #plaintext block on the end. That's okay - the below code will discard it. + plaintext = plaintext[:len(ciphertext)] + m2.cipher_ctx_free(context) + self.IV = ciphertext[-self.block_size:] + return bytearray(plaintext) + + def _createContext(self, encrypt): + context = m2.cipher_ctx_new() + if len(self.key)==16: + cipherType = m2.aes_128_cbc() + elif len(self.key)==24: + cipherType = m2.aes_192_cbc() + elif len(self.key)==32: + cipherType = m2.aes_256_cbc() + else: + raise AssertionError("Key is bad size: %s" % len(self.key)) + + m2.cipher_init(context, cipherType, self.key, self.IV, encrypt) + return context \ No newline at end of file diff --git a/TACKpy/asn1.py b/tack/crypto/ASN1.py similarity index 65% rename from TACKpy/asn1.py rename to tack/crypto/ASN1.py index 458d2e7..1961505 100644 --- a/TACKpy/asn1.py +++ b/tack/crypto/ASN1.py @@ -1,10 +1,9 @@ # Author: Trevor Perrin # See the LICENSE file for legal information regarding use of this file. -from .struct_parser import * -from .cryptomath import * - ################ ASN1 ### +from tack.tls.TlsStructure import TlsStructure + def asn1Length(x): """Return a bytearray encoding an ASN1 length field based on input length. @@ -24,7 +23,7 @@ def asn1Length(x): return bytearray([0x81,x]) if x < 65536: return bytearray([0x82, int(x//256), x % 256]) - assert(False) + assert False def toAsn1IntBytes(b): """Return a bytearray containing ASN.1 integer based on input bytearray. @@ -56,9 +55,9 @@ def fromAsn1IntBytes(b, size): if len(b) > size+1: raise SyntaxError("ASN.1 integer is too big") if len(b)==size+1: # This can occur if the integer's high bit was set - if b[0] != 0: + if b[0]: raise SyntaxError("ASN.1 integer too big") - if (b[1] & 0x80) == 0: + if not (b[1] & 0x80): raise SyntaxError("ASN.1 integer has excess zero padding") return b[1:] else: @@ -68,7 +67,7 @@ def fromAsn1IntBytes(b, size): #Takes a byte array which has a DER TLV field at its head class ASN1Parser: def __init__(self, bytes, offset = 0): - p = Parser(bytes) + p = TlsStructure(bytes) self.type = p.getInt(1) #skip Type #Get Length @@ -87,7 +86,7 @@ def __init__(self, bytes, offset = 0): #Assuming this is a sequence... def getChild(self, which): - p = Parser(self.value) + p = TlsStructure(self.value) for x in range(which+1): if p.index == len(p.bytes): return None @@ -95,8 +94,8 @@ def getChild(self, which): p.getInt(1) #skip Type length = self._getASN1Length(p) p.getBytes(length) - return ASN1Parser(p.bytes[markIndex : p.index], \ - self.offset + self.headerLength + markIndex) + return ASN1Parser(p.bytes[markIndex : p.index], + self.offset + self.headerLength + markIndex) #Assuming this is a tagged element... def getTagged(self): @@ -118,37 +117,4 @@ def _getASN1Length(self, p): lengthLength = firstLength & 0x7F return p.getInt(lengthLength) - -def testASN1(): - print("Testing ASN1") - assert(asn1Length(7) == bytearray([7])) - assert(asn1Length(0x7F) == bytearray([0x7F])) - assert(asn1Length(0x80) == bytearray([0x81,0x80])) - assert(asn1Length(0x81) == bytearray([0x81,0x81])) - assert(asn1Length(0xFF) == bytearray([0x81,0xFF])) - assert(asn1Length(0x0100) == bytearray([0x82,0x01,0x00])) - assert(asn1Length(0x0101) == bytearray([0x82,0x01,0x01])) - assert(asn1Length(0xFFFF) == bytearray([0x82,0xFF,0xFF])) - - assert(toAsn1IntBytes(bytearray([0xFF])) == bytearray([0x00,0xFF])) - assert(toAsn1IntBytes(bytearray([0x7F])) == bytearray([0x7F])) - assert(toAsn1IntBytes(bytearray([0x00])) == bytearray([0x00])) - assert(toAsn1IntBytes(bytearray([0x00,0x00])) == bytearray([0x00])) - assert(toAsn1IntBytes(bytearray([0x00,0x01])) == bytearray([0x01])) - assert(toAsn1IntBytes(bytearray([0,0xFF])) == bytearray([0,0xFF])) - assert(toAsn1IntBytes(bytearray([0,0,0,0xFF])) == bytearray([0,0xFF])) - assert(toAsn1IntBytes(bytearray([0,0,0,1,1])) == bytearray([1,1])) - - assert(bytearray([0xFF]) == fromAsn1IntBytes(bytearray([0x00,0xFF]),1)) - assert(bytearray([0x7F]) == fromAsn1IntBytes(bytearray([0x7F]),1)) - assert(bytearray([0x00]) == fromAsn1IntBytes(bytearray([0x00]),1)) - assert(bytearray([0x00,0x00]) == fromAsn1IntBytes(bytearray([0x00]),2)) - assert(bytearray([0x00,0x01]) == fromAsn1IntBytes(bytearray([0x01]),2)) - assert(bytearray([0,0xFF]) == fromAsn1IntBytes(bytearray([0,0xFF]),2)) - assert(bytearray([0,0,0,0xFF]) == fromAsn1IntBytes(bytearray([0,0xFF]),4)) - assert(bytearray([0,0,0,1,1]) == fromAsn1IntBytes(bytearray([1,1]),5)) - #!!! Add testing for ASN1Parser - return 1 - - diff --git a/tack/crypto/Digest.py b/tack/crypto/Digest.py new file mode 100644 index 0000000..5b90fda --- /dev/null +++ b/tack/crypto/Digest.py @@ -0,0 +1,14 @@ +import hashlib +import hmac +from tack.compat import compat26Str + +class Digest: + @staticmethod + def SHA256(b): + "Return a 32-byte bytearray which is the SHA256 of input bytearray." + return bytearray(hashlib.sha256(compat26Str(b)).digest()) + + @staticmethod + def HMAC_SHA256(k, b): + """Return a 32-byte bytearray which is HMAC-SHA256 of key and input.""" + return bytearray(hmac.new(bytes(k), bytes(b), hashlib.sha256).digest()) diff --git a/tack/crypto/ECDSA.py b/tack/crypto/ECDSA.py new file mode 100644 index 0000000..0863998 --- /dev/null +++ b/tack/crypto/ECDSA.py @@ -0,0 +1,171 @@ +"""The following three "wrapper" functions are used for working with ECDSA: + ec256Generate + ecdsa256Sign + ecdsa256Verify + +These wrapper functions operate on bytearrays: + privateKey is a bytearray of length 32 + publicKey is a bytearray of length 64 + signature is a bytearray of length 64 + dataToSign/Verify is an arbitrary-length bytearray + +Because M2Crypto operates on ASN.1-encoded signatures, and traditional OpenSSL +PEM-encoded public and private keys, there is a fair bit of data munging to +convert to/from M2Crypto formats. +""" +from M2Crypto import EC, BIO +import math +from tack.asn1.ASN1Object import ASN1Object +from tack.crypto.ASN1 import ASN1Parser, fromAsn1IntBytes, toAsn1IntBytes, asn1Length +from tack.crypto.Digest import Digest +from tack.util.PEMDecoder import PEMDecoder +from tack.util.PEMEncoder import PEMEncoder +from tack.compat import a2b_hex + +def ec256Generate(): + # Generate M2Crypto.EC.EC object + m2EC = EC.gen_params(EC.NID_X9_62_prime256v1) + m2EC.gen_key() + + # Get the ASN.1 ECPrivateKey for the object + m2Bio = BIO.MemoryBuffer() + m2EC.save_key_bio(m2Bio, cipher=None) + pemPrivKeyBytes = m2Bio.getvalue() + + # Parse the ASN.1 ECPrivateKey into byte arrays + # for the 32-byte priv key, and 64-byte pub key + (privateKey, publicKey) = _parseECPrivateKey(pemPrivKeyBytes) + return privateKey, publicKey + +def ecdsa256Sign(privateKey, publicKey, dataToSign): + # Write the passed-in private key byte array into PEM form + # Then create M2Crypto EC object from ASN.1 form + pemPrivKeyBytes = _writeECPrivateKey(privateKey, publicKey) + m2EC = EC.load_key_bio(BIO.MemoryBuffer(pemPrivKeyBytes)) + + # Produce ASN.1 signature + hash = Digest.SHA256(dataToSign) + asn1SigBytes = m2EC.sign_dsa_asn1(hash) + + # Convert stupid ASN.1 signature into 64-byte signature + # Double-check before returning + sigBytes = _parseECSignature(asn1SigBytes) + assert(ecdsa256Verify(publicKey, dataToSign, sigBytes)) + return sigBytes + +def ecdsa256Verify(publicKey, dataToVerify, signature): + # Write the passed-in public key byte array into PEM form + # Then create M2Crypto EC_pub + pemPubKeyBytes = _writeECPublicKey(publicKey) + m2ECpub = EC.load_pub_key_bio(BIO.MemoryBuffer(pemPubKeyBytes)) + + # Convert 64-byte signature into a stupid ASN.1 signature + asn1SigBytes = _writeECSignature(signature) + hash = Digest.SHA256(dataToVerify) + return m2ECpub.verify_dsa_asn1(hash, asn1SigBytes) + +# Marshal/unmarshal PEM-wrapped ECPrivateKey and ASN.1 Signatures +def _parseECPrivateKey(pemPrivKeyBytes): + """Parse a bytearray containing a PEM-encoded ECPrivatey. + + Return a pair of (32-byte bytearray, 64-byte bytearray) + containing the (privateKey, publicKey) + """ + b = PEMDecoder(pemPrivKeyBytes).getDecoded("EC PRIVATE KEY") + p = ASN1Parser(b) + # The private key is stored as an ASN.1 integer which may + # need to have zero padding removed (if 33 bytes) or added + # (if < 32 bytes): + privateKey = p.getChild(1).value + privateKey = fromAsn1IntBytes(privateKey, 32) + # There is a 00 04 byte prior to the 64-byte public key + # I'm not sure why M2Crypto has the 00 byte there?, + # some ASN1 thing - the 04 byte signals "uncompressed" + # per SECG. Anyways, strip both those bytes off ([2:]) + publicKey = p.getChild(3).getTagged().value[2:] + assert(len(privateKey) == 32) + assert(len(publicKey) == 64) + return privateKey, publicKey + +def _writeECPrivateKey(privateKey, publicKey): + assert(len(privateKey) == 32) + assert(len(publicKey) == 64) + bytes1 = a2b_hex("02010104") + bytes2 = a2b_hex("a00a06082a8648ce3d030107a14403420004") + privateKey = toAsn1IntBytes(privateKey) + b = bytes1 + asn1Length(len(privateKey)) + privateKey +\ + bytes2 + publicKey + b = bytearray([0x30]) + asn1Length(len(b)) + b + pemPrivKeyBytes = PEMEncoder(b).getEncoded("EC PRIVATE KEY") + # pemPrivKeyBytes = pem(b, "EC PRIVATE KEY") + return pemPrivKeyBytes + +def _writeECPublicKey(publicKey): + assert(len(publicKey) == 64) + bytes1 = a2b_hex("3059301306072a8648ce3d020106082a8648ce3d03010703420004") + asn1KeyBytes = bytes1 + publicKey + pemPubKeyBytes = PEMEncoder(asn1KeyBytes).getEncoded("PUBLIC KEY") + # pemPubKeyBytes = pem(asn1KeyBytes, "PUBLIC KEY") + return pemPubKeyBytes + +def _parseECSignature(asn1SigBytes): + p = ASN1Parser(bytearray(asn1SigBytes)) + r = _bytesToNumber(p.getChild(0).value) + s = _bytesToNumber(p.getChild(1).value) + return _numberToBytes(r, 32) + _numberToBytes(s, 32) + +def _writeECSignature(ecSigBytes): + assert(len(ecSigBytes) == 64) + asn1R = toAsn1IntBytes(ecSigBytes[:32]) + asn1S = toAsn1IntBytes(ecSigBytes[32:]) + # Add ASN1 Type=2(int), and Length fields + asn1R = bytearray([2]) + asn1Length(len(asn1R)) + asn1R + asn1S = bytearray([2]) + asn1Length(len(asn1S)) + asn1S + # Add ASN1 Type=0x30(Sequence) and Length fields + asn1ECSigBytes = bytearray([0x30]) +\ + asn1Length(len(asn1R+asn1S)) + asn1R + asn1S + return asn1ECSigBytes + +def _bytesToNumber(bytes): + "Convert a sequence of bytes (eg bytearray) into integer." + total = 0 + multiplier = 1 + for count in range(len(bytes)-1, -1, -1): + byte = bytes[count] + total += multiplier * byte + multiplier *= 256 + return total + +def _numberToBytes(n, howManyBytes=None): + """Convert an integer into a bytearray, zero-pad to howManyBytes. + + The returned bytearray may be smaller than howManyBytes, but will + not be larger. The returned bytearray will contain a big-endian + encoding of the input integer (n). + """ + if not howManyBytes: + howManyBytes = _numBytes(n) + bytes = bytearray(howManyBytes) + for count in range(howManyBytes-1, -1, -1): + bytes[count] = int(n % 256) + n >>= 8 + return bytes + +def _numBytes(n): + "Return the number of bytes needed to represent the integer n." + if n==0: + return 0 + bits = _numBits(n) + return int(math.ceil(bits / 8.0)) + +def _numBits(n): + "Return the number of bits needed to represent the integer n." + if n==0: + return 0 + s = "%x" % n + return ((len(s)-1)*4) +\ + {'0':0, '1':1, '2':2, '3':2, + '4':3, '5':3, '6':3, '7':3, + '8':4, '9':4, 'a':4, 'b':4, + 'c':4, 'd':4, 'e':4, 'f':4, + }[s[0]] diff --git a/tack/crypto/PBKDF2.py b/tack/crypto/PBKDF2.py new file mode 100644 index 0000000..9a902f0 --- /dev/null +++ b/tack/crypto/PBKDF2.py @@ -0,0 +1,18 @@ +from tack.crypto.Digest import Digest + +class PBKDF2: + + @staticmethod + def _xorbytes(s1, s2): + return bytearray([a^b for a,b in zip(s1,s2)]) + + # Uses PBKDF2-HMAC-SHA256 to produce a 32-byte key + @staticmethod + def hmac_sha256(password, salt, iter_count): + m = salt + bytearray([0,0,0,1]) + result = bytearray(32) + for c in range(iter_count): + m = Digest.HMAC_SHA256(bytearray(password, "ascii"), m) + result = PBKDF2._xorbytes(m, result) + + return result diff --git a/tack/crypto/__init__.py b/tack/crypto/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py new file mode 100644 index 0000000..a3536f7 --- /dev/null +++ b/tack/structures/Tack.py @@ -0,0 +1,98 @@ +from tack.crypto.ECDSA import ecdsa256Verify +from tack.structures.TackId import TackId +from tack.tls.TlsStructure import TlsStructure +from tack.tls.TlsStructureWriter import TlsStructureWriter +from tack.util.Util import Util +from tack.util.PEMDecoder import PEMDecoder +from tack.util.PEMEncoder import PEMEncoder +from tack.util.Time import Time + +class Tack(TlsStructure): + LENGTH = 166 + + def __init__(self, data=None): + TlsStructure.__init__(self, data) + if data is not None: + self.public_key = self.getBytes(64) + self.min_generation = self.getInt(1) + self.generation = self.getInt(1) + self.expiration = self.getInt(4) + self.target_hash = self.getBytes(32) + self.signature = self.getBytes(64) + + if not self._verifySignature(): + raise SyntaxError("Signature verification failure") + if self.index != len(data): + raise SyntaxError("Excess bytes in TACK") + + @classmethod + def createFromPem(cls, pem): + data = PEMDecoder(pem).getDecoded("TACK") + + if len(data) != Tack.LENGTH: + raise SyntaxError("TACK is the wrong size. %s, should be %s" % (len(data), Tack.LENGTH)) + + return cls(data) + + @classmethod + def createFromParameters(cls, public_key, private_key, min_generation, generation, expiration, target_hash): + assert(len(public_key) == 64) + assert(0 <= min_generation <= 255) + assert(0 <= generation <= 255 and + generation >= min_generation) + assert(0 <= expiration <= 2**32-1) + assert(len(target_hash) == 32) + + tack = cls() + tack.public_key = public_key + tack.min_generation = min_generation + tack.generation = generation + tack.expiration = expiration + tack.target_hash = target_hash + tack.signature = private_key.getSignature(tack._getDataToSign()) + + return tack + + def getTackId(self): + return str(TackId(self.public_key)) + + def serialize(self): + writer = TlsStructureWriter(64) + writer.add(self.signature, 64) + return self._serializePrelude() + writer.getBytes() + + def serializeAsPem(self): + return PEMEncoder(self.serialize()).getEncoded("TACK") + + def _serializePrelude(self): + writer = TlsStructureWriter(Tack.LENGTH - 64) + writer.add(self.public_key, 64) + writer.add(self.min_generation, 1) + writer.add(self.generation, 1) + writer.add(self.expiration, 4) + writer.add(self.target_hash, 32) + return writer.getBytes() + + def _getDataToSign(self): + return bytearray("tack_sig", "ascii") + self._serializePrelude() + + def _verifySignature(self): + bytesToVerify = self._getDataToSign() + return ecdsa256Verify(self.public_key, bytesToVerify, self.signature) + + def __str__(self): + """Return a readable string describing this TACK. + + Used by the "TACK view" command to display TACK objects.""" + s =\ + """TACK ID = %s +min_generation = %d +generation = %d +expiration = %s +target_hash = %s\n""" %\ + (self.getTackId(), + self.min_generation, + self.generation, + Time.posixTimeToStr(self.expiration*60), + Util.writeBytes(self.target_hash)) + return s diff --git a/tack/structures/TackActivation.py b/tack/structures/TackActivation.py new file mode 100644 index 0000000..ba41476 --- /dev/null +++ b/tack/structures/TackActivation.py @@ -0,0 +1,6 @@ + +class TackActivation: + DISABLED = 0 + ENABLED = 1 + ALL = (DISABLED, ENABLED) + STRINGS = ["disabled", "enabled"] \ No newline at end of file diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py new file mode 100644 index 0000000..077a14e --- /dev/null +++ b/tack/structures/TackBreakSig.py @@ -0,0 +1,73 @@ +from tack.crypto.ECDSA import ecdsa256Verify +from tack.structures.TackId import TackId +from tack.tls.TlsStructure import TlsStructure +from tack.tls.TlsStructureWriter import TlsStructureWriter +from tack.util.PEMDecoder import PEMDecoder +from tack.util.PEMEncoder import PEMEncoder + +class TackBreakSig(TlsStructure): + LENGTH = 128 + + def __init__(self, data=None): + TlsStructure.__init__(self, data) + + if data is not None and len(data) != TackBreakSig.LENGTH: + raise SyntaxError("Break signature is the wrong size. Is %s and should be %s." % (len(data), TackBreakSig.LENGTH)) + + if data is not None: + self.public_key = self.getBytes(64) + self.signature = self.getBytes(64) + + if not self._verifySignature(): + raise SyntaxError("Signature verification failure") + + if self.index != len(data): + raise SyntaxError("Excess bytes in TACK_Break_Sig") + + @classmethod + def createFromPem(cls, data): + return cls(PEMDecoder(data).getDecoded("TACK BREAK SIG")) + + @classmethod + def createFromPemList(cls, data): + """Parse a string containing a sequence of PEM Break Sigs. + + Raise a SyntaxError if input is malformed. + """ + breakSigs = [] + bList = PEMDecoder(data).getDecodedList("TACK BREAK SIG") + for b in bList: + breakSigs.append(TackBreakSig(b)) + + return breakSigs + + @classmethod + def createFromParameters(cls, public_key, private_key): + assert(len(public_key) == 64) + tackBreakSig = cls() + tackBreakSig.public_key = public_key + tackBreakSig.signature = private_key.getSignature(bytearray("tack_break_sig", "ascii")) + + return tackBreakSig + + def serialize(self): + """Return a bytearray containing the TACK_Break_Sig.""" + w = TlsStructureWriter(TackBreakSig.LENGTH) + w.add(self.public_key, 64) + w.add(self.signature, 64) + assert(w.index == len(w.bytes)) + return w.getBytes() + + def serializeAsPem(self): + return PEMEncoder(self.serialize()).getEncoded("TACK BREAK SIG") + + def getTackId(self): + return str(TackId(self.public_key)) + + def _verifySignature(self): + return ecdsa256Verify(self.public_key, bytearray("tack_break_sig", "ascii"), self.signature) + + def __str__(self): + """Return a readable string describing this TACK_Break_Sig. + Used by the "TACK view" command to display TACK objects.""" + return "Breaks TACK ID = %s\n" % self.getTackId() diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py new file mode 100644 index 0000000..ad695e6 --- /dev/null +++ b/tack/structures/TackExtension.py @@ -0,0 +1,101 @@ +from tack.structures.Tack import Tack +from tack.structures.TackActivation import TackActivation +from tack.structures.TackBreakSig import TackBreakSig +from tack.tls.TlsStructure import TlsStructure +from tack.tls.TlsStructureWriter import TlsStructureWriter + +class TackExtension(TlsStructure): + + def __init__(self, data=None): + TlsStructure.__init__(self, data) + if data is not None: + self.tack = self._parseTack() + self.break_sigs = self._parseBreakSigs() + self.pin_activation = self.getInt(1) + + if self.pin_activation not in TackActivation.ALL: + raise SyntaxError("Bad pin_activation value") + + if self.index != len(data): + raise SyntaxError("Excess bytes in TACK_Extension") + + @classmethod + def createFromParameters(cls, tack, break_sigs, pin_activation): + tackExtension = cls() + tackExtension.tack = tack + tackExtension.break_sigs = break_sigs + tackExtension.pin_activation = pin_activation + + return tackExtension + + def serialize(self): + w = TlsStructureWriter(self._getSerializedLength()) + + if self.tack: + w.add(Tack.LENGTH, 1) + w.add(self.tack.serialize(), Tack.LENGTH) + else: + w.add(0, 1) + + if self.break_sigs: + w.add(len(self.break_sigs) * TackBreakSig.LENGTH, 2) + for break_sig in self.break_sigs: + w.add(break_sig.serialize(), TackBreakSig.LENGTH) + else: + w.add(0, 2) + + w.add(self.pin_activation, 1) + + assert(w.index == len(w.bytes)) # did we fill entire bytearray? + return w.getBytes() + + def isEmpty(self): + return not self.tack and not self.break_sigs + + def _getSerializedLength(self): + length = 0 + if self.tack: + length += Tack.LENGTH + + if self.break_sigs: + length += len(self.break_sigs) * TackBreakSig.LENGTH + + return length + 4 + + def _parseTack(self): + tackLen = self.getInt(1) + + if tackLen != Tack.LENGTH: + raise SyntaxError("TACK wrong size") + + return Tack(self.getBytes(tackLen)) + + def _parseBreakSigs(self): + sigsLen = self.getInt(2) + + if sigsLen > 1024: + raise SyntaxError("break_sigs too large") + elif sigsLen % TackBreakSig.LENGTH != 0: + raise SyntaxError("break_sigs wrong size") + + break_sigs = [] + b2 = self.getBytes(sigsLen) + while b2: + break_sigs.append(TackBreakSig(b2[:TackBreakSig.LENGTH])) + b2 = b2[TackBreakSig.LENGTH:] + + return break_sigs + + def __str__(self): + result = "" + + if self.tack: + result += str(self.tack) + + if self.break_sigs: + for break_sig in self.break_sigs: + result += str(break_sig) + + result += "pin_activation = %s\n" % TackActivation.STRINGS[self.pin_activation] + + return result diff --git a/tack/structures/TackId.py b/tack/structures/TackId.py new file mode 100644 index 0000000..ae519a2 --- /dev/null +++ b/tack/structures/TackId.py @@ -0,0 +1,18 @@ +from tack.compat import b2a_base32 +from tack.crypto.Digest import Digest + +class TackId(): + + def __init__(self, public_key): + self.id = self._hashToTackId(Digest.SHA256(public_key)) + + def getId(self): + return self.id + + def _hashToTackId(self, b): + assert(len(b) == 32) + s = b2a_base32(b).lower()[:25] + return "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) + + def __str__(self): + return self.id \ No newline at end of file diff --git a/tack/structures/TackKeyFile.py b/tack/structures/TackKeyFile.py new file mode 100644 index 0000000..5a6d633 --- /dev/null +++ b/tack/structures/TackKeyFile.py @@ -0,0 +1,125 @@ +""" +File format: + + version 1 byte = 0x01 + iter_count 4 bytes = uint32, bigendian + salt 16 bytes + EC privkey 32 bytes } aes256-cbc(IV=0) } hmac-sha256 + EC pubkey 64 bytes } hmac-sha256 + HMAC 32 bytes + + Total 149 + +The AES256-CBC and HMAC-SHA256 steps require independent +32-byte keys (encKey and authKey, respectively). + +These keys are derived from a 32-byte masterKey. The masterKey is +derived from a password via PBKDF2-HMAC-SHA26: + + masterKey = PBKDF2-HMAC-SHA256(password, salt, iter_count) + encKey = HMAC-SHA256(masterKey, 0x01) + authKey = HMAC-SHA256(masterKey, 0x02) +""" +import os +from tack.InvalidPasswordException import InvalidPasswordException +from tack.crypto.PBKDF2 import PBKDF2 +from tack.crypto.AES import AES +from tack.crypto.Digest import Digest +from tack.crypto.ECDSA import ec256Generate, ecdsa256Sign +from tack.structures.TackId import TackId +from tack.tls.TlsStructure import TlsStructure +from tack.tls.TlsStructureWriter import TlsStructureWriter +from tack.util.Util import Util +from tack.util.PEMDecoder import PEMDecoder +from tack.util.PEMEncoder import PEMEncoder + +class TackKeyFile(TlsStructure): + LENGTH = 149 # length of keyfile in bytes + + def __init__(self, data=None, password=None): + TlsStructure.__init__(self, data) + if data is not None: + self.version = self.getInt(1) + + if self.version != 1: + raise SyntaxError("Bad version in Secret File") + + self.password = password + self.iter_count = self.getInt(4) + self.salt = self.getBytes(16) + self.ciphertext = self.getBytes(32) + self.public_key = self.getBytes(64) + self.mac = bytearray(self.getBytes(32)) + + if self.password is not None: + self.private_key = self._decryptKey(password, self.salt, self.ciphertext, + self.iter_count, self.public_key, self.mac) + + @classmethod + def createRandom(cls, password): + tackKeyFile = cls() + tackKeyFile.password = password + tackKeyFile.version = 1 + tackKeyFile.iter_count = 8192 + tackKeyFile.salt = bytearray(os.urandom(16)) + tackKeyFile.private_key, tackKeyFile.public_key = ec256Generate() + tackKeyFile.ciphertext, tackKeyFile.mac = tackKeyFile._encryptKey(password, tackKeyFile.salt, + tackKeyFile.iter_count, + tackKeyFile.public_key, + tackKeyFile.private_key) + return tackKeyFile + + @classmethod + def createFromPem(cls, pem, password): + return cls(PEMDecoder(pem).getDecoded("TACK PRIVATE KEY"), password) + + def getPublicKey(self): + return self.public_key + + def getSignature(self, data): + return ecdsa256Sign(self.private_key, self.public_key, data) + + def serialize(self): + w = TlsStructureWriter(TackKeyFile.LENGTH) + w.add(self.version, 1) + w.add(self.iter_count, 4) + w.add(self.salt, 16) + w.add(self.ciphertext, 32) + w.add(self.public_key, 64) + w.add(self.mac, 32) + assert(w.index == len(w.bytes)) # did we fill entire bytearray? + + return w.getBytes() + + def serializeAsPem(self): + return PEMEncoder(self.serialize()).getEncoded("TACK PRIVATE KEY") + + def _encryptKey(self, password, salt, iter_count, public_key, private_key): + encKey, authKey = self._deriveKeys(password, salt, iter_count) + ciphertext = AES(encKey, bytearray(16)).encrypt(private_key) + macData = ciphertext + public_key + mac = Digest.HMAC_SHA256(authKey, macData) + return ciphertext, mac + + + def _decryptKey(self, password, salt, ciphertext, iter_count, public_key, mac): + encKey, authKey = self._deriveKeys(password, salt, iter_count) + macData = ciphertext + public_key + calcMac = Digest.HMAC_SHA256(authKey, macData) + + if not Util.constTimeCompare(calcMac, mac): + raise InvalidPasswordException("Bad password") + + return AES(encKey, bytearray(16)).decrypt(ciphertext) + + # Uses PBKDF2, then HMAC-SHA256 as PRF to derive independent 32-byte keys + def _deriveKeys(self, password, salt, iter_count): + assert(iter_count>0) + masterKey = PBKDF2.hmac_sha256(password, salt, iter_count) + encKey = Digest.HMAC_SHA256(masterKey, bytearray([1])) + authKey = Digest.HMAC_SHA256(masterKey, bytearray([2])) + return encKey, authKey + + def __str__(self): + return """TACK ID = %s\n""" % str(TackId(self.public_key)) + diff --git a/tack/structures/TackVersion.py b/tack/structures/TackVersion.py new file mode 100644 index 0000000..02161dd --- /dev/null +++ b/tack/structures/TackVersion.py @@ -0,0 +1,3 @@ + +class TackVersion: + V1 = 0 \ No newline at end of file diff --git a/tack/structures/__init__.py b/tack/structures/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tack/tls/TlsCertificate.py b/tack/tls/TlsCertificate.py new file mode 100644 index 0000000..d86efd5 --- /dev/null +++ b/tack/tls/TlsCertificate.py @@ -0,0 +1,184 @@ +from tack.compat import a2b_hex +from tack.crypto.ASN1 import ASN1Parser, asn1Length +from tack.crypto.Digest import Digest +from tack.structures.TackExtension import TackExtension +from tack.structures.TackVersion import TackVersion +from tack.util.PEMDecoder import PEMDecoder +from tack.util.PEMEncoder import PEMEncoder +from tack.util.Time import Time +from tack.util.Util import Util + +class TlsCertificate: + + OID_TACK = bytearray(b"\x2B\x06\x01\x04\x01\x82\xB0\x34\x01") + +# def __init__(self, data): +# if data is not None: +# self.serialized = data +# self.certificate = X509.load_cert_string(data) +# self.notAfter = time.mktime(self.certificate.get_not_after().get_datetime().timetuple()) +# self.cert_sha256 = bytearray(base64.b16decode(self.certificate.get_fingerprint(md='sha256'))) +# self.key_sha256 = Util.SHA256(self.certificate.get_pubkey().as_der()) +# self.tackExt = self._parseTackExtension(data) + + def __init__(self): + self.key_sha256 = bytearray(32) + self.cert_sha256 = bytearray(32) + self.notAfter = 0 + # Below values are populated for TACK certs + self.tackExt = None + # Below values hold cert contents excluding TACK stuff + self.preExtBytes = None + self.extBytes = None + self.postExtBytes = None + + def create(self, tackExt = None): + self.tackExt = tackExt + self.preExtBytes = a2b_hex( + "a003020102020100300d06092a864886f70d0101050500300f310d300b06035504031" + "3045441434b301e170d3031303730353138303534385a170d34343037303431383035" + "34385a300f310d300b060355040313045441434b301f300d06092a864886f70d01010" + "10500030e00300b0204010203050203010001") + # Below is BasicConstraints, saving space by omitting + #self.extBytes = binascii.a2b_hex(\ + #"300c0603551d13040530030101ff") + self.extBytes = bytearray() + self.postExtBytes = a2b_hex( + "300d06092a864886f70d01010505000303003993") + + def open(self, filename): + # May raise IOError or SyntaxError + try: + sslStr = open(filename, "rU").read() # IOError, UnicodeDecodeError + self.parsePem(sslStr) # SyntaxError + return + except (UnicodeDecodeError, SyntaxError): + # File had non-text chars in it (python3), *OR* + # File did not PEM-decode + pass + sslBytes = bytearray(open(filename, "rb").read()) # IOError + self.parse(sslBytes) # SyntaxError + + def matches(self, tack): + if tack.version == TackVersion.V1: + return self.key_sha256 == tack.sig.target_sha256 + return False + + def parsePem(self, s): + b = PEMDecoder(s).getDecoded("CERTIFICATE") + self.parse(b) + + def parse(self, b): + p = ASN1Parser(b) + + #Get the tbsCertificate + tbsCertificateP = p.getChild(0) + + #Is the optional version field present? + #This determines which index the key is at + if tbsCertificateP.value[0]==0xA0: + subjectPublicKeyInfoIndex = 6 + validityIndex = 4 + else: + subjectPublicKeyInfoIndex = 5 + validityIndex = 3 + #Get the subjectPublicKeyInfo + spkiP = tbsCertificateP.getChild(subjectPublicKeyInfoIndex) + + #Parse the notAfter time + validityP = tbsCertificateP.getChild(validityIndex) + notAfterP = validityP.getChild(1) + if notAfterP.type == 0x17: # UTCTime + self.notAfter = Time.parseASN1UTCTime(notAfterP.value) + elif notAfterP.type == 0x18: # GeneralizedTime + self.notAfter = Time.parseASN1GeneralizedTime(notAfterP.value) + else: + raise SyntaxError() + + # Get the hash values + self.cert_sha256 = Digest.SHA256(b) + self.key_sha256 = Digest.SHA256(spkiP.getTotalBytes()) + + # Check if this is a TACK certificate: + #Get the tbsCertificate + versionP = tbsCertificateP.getChild(0) + if versionP.type != 0xA0: # i.e. tag of [0], version + return # X.509 version field not present + versionPP = versionP.getTagged() + if versionPP.value != bytearray([0x02]): + return # X.509 version field does not equal v3 + + # Find extensions element + x = 0 + while 1: + certFieldP = tbsCertificateP.getChild(x) + if not certFieldP: + raise SyntaxError("X.509 extensions not present") + if certFieldP.type == 0xA3: # i.e. tag of [3], extensions + break + x += 1 + + self.preExtBytes = b[versionP.offset : certFieldP.offset] + self.extBytes = bytearray() + + # Iterate through extensions + x = 0 + certFieldPP = certFieldP.getTagged() + while 1: + extFieldP = certFieldPP.getChild(x) + if not extFieldP: + break + + # Check the extnID and parse out TACK if present + extnIDP = extFieldP.getChild(0) + if extnIDP.value == TlsCertificate.OID_TACK: + if self.tackExt: + raise SyntaxError("More than one TACK Extension") + + # OK! We found a TACK, parse it.. + self.tackExt = TackExtension(extFieldP.getChild(1).value) + else: + # Collect all non-TACK extensions: + self.extBytes += b[extFieldP.offset :\ + extFieldP.offset + extFieldP.getTotalLength()] + x += 1 + + # Finish copying the tail of the certificate + self.postExtBytes = b[certFieldP.offset + certFieldP.getTotalLength():] + + def write(self): + b = bytearray(0) + if self.tackExt: + # type=SEQ,len=?,type=6,len=9(for OID), + # type=4,len=?,TACK + TACKBytes = self.tackExt.serialize() + b = bytearray([4]) + asn1Length(len(TACKBytes)) + TACKBytes + b = bytearray([6,9]) + TlsCertificate.OID_TACK + b + b = bytearray([0x30]) + asn1Length(len(b)) + b + + b = b + self.extBytes # add non-TACK extensions after TACK + # Add length fields for extensions and its enclosing tag + b = bytearray([0x30]) + asn1Length(len(b)) + b + b = bytearray([0xA3]) + asn1Length(len(b)) + b + # Add prefix of tbsCertificate, then its type/length fields + b = self.preExtBytes + b + b = bytearray([0x30]) + asn1Length(len(b)) + b + # Add postfix of Certificate (ie SignatureAlgorithm, SignatureValue) + # then its prefix'd type/length fields + b = b + self.postExtBytes + b = bytearray([0x30]) + asn1Length(len(b)) + b + return b + + def writePem(self): + b = self.write() + return PEMEncoder(b).getEncoded("CERTIFICATE") + def writeText(self): + s =\ + """key_sha256 = 0x%s + notAfter = %s + """ % (\ + Util.writeBytes(self.key_sha256), + Time.posixTimeToStr(self.notAfter, True)) + if self.tackExt: + s += "\n" + str(self.tackExt) + return s diff --git a/tack/tls/TlsStructure.py b/tack/tls/TlsStructure.py new file mode 100644 index 0000000..684803f --- /dev/null +++ b/tack/tls/TlsStructure.py @@ -0,0 +1,29 @@ + +class TlsStructure: + def __init__(self, bytes): + self.bytes = bytes + self.index = 0 + + def getInt(self, elementLength): + """Reads an integer of 'length' bytes""" + if self.index + elementLength > len(self.bytes): + raise SyntaxError("Reading %s at index %s but only %s bytes remaining." % (elementLength, self.index, len(self.bytes))) + x = 0 + for count in range(elementLength): + x <<= 8 + x |= self.bytes[self.index] + self.index += 1 + return x + + def getBytes(self, elementLength): + """Reads some number of bytes as determined by 'elementLength'""" + bytes = self.bytes[self.index : self.index + elementLength] + self.index += elementLength + return bytes + + def getVarSeqBytes(self, elementLength, lengthLength): + dataLength = self.getInt(lengthLength) + if dataLength % elementLength != 0: + raise SyntaxError() + return [self.getBytes(elementLength) for x in\ + range(dataLength//elementLength)] diff --git a/tack/tls/TlsStructureWriter.py b/tack/tls/TlsStructureWriter.py new file mode 100644 index 0000000..0a52df1 --- /dev/null +++ b/tack/tls/TlsStructureWriter.py @@ -0,0 +1,30 @@ +class TlsStructureWriter: + def __init__(self, totalLength): + self.index = 0 + self.bytes = bytearray(totalLength) + + def add(self, x, elementLength): + """Writes 'elementLength' bytes, input is either an integer + (written as big-endian) or a sequence of bytes""" + if isinstance(x, int): + assert(0 <= x < 2**(8*elementLength)) + newIndex = self.index + elementLength-1 + while newIndex >= self.index: + self.bytes[newIndex] = x & 0xFF + x >>= 8 + newIndex -= 1 + else: + assert(len(x) == elementLength) + for i in range(elementLength): + self.bytes[self.index + i] = x[i] + self.index += elementLength + + def addVarSeq(self, seq, elementLength, lengthLength): + """Writes a sequence of elements prefixed by a + total-length field of lengthLength bytes""" + self.add(len(seq)*elementLength, lengthLength) + for e in seq: + self.add(e, elementLength) + + def getBytes(self): + return self.bytes \ No newline at end of file diff --git a/tack/tls/__init__.py b/tack/tls/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tack/util/PEMDecoder.py b/tack/util/PEMDecoder.py new file mode 100644 index 0000000..d3083e4 --- /dev/null +++ b/tack/util/PEMDecoder.py @@ -0,0 +1,79 @@ +from tack.compat import a2b_base64 + +class PEMDecoder: + + def __init__(self, data): + self.data = data + + def containsEncoded(self, name): + searchStr = "-----BEGIN %s-----" % name + return searchStr in self.data + + def getDecoded(self, name): + """Decode a PEM string into a bytearray of its payload. + + The input must contain an appropriate PEM prefix and postfix + based on the input name string, e.g. for name="CERTIFICATE": + + -----BEGIN CERTIFICATE----- + MIIBXDCCAUSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRUQUNL + ... + KoZIhvcNAQEFBQADAwA5kw== + -----END CERTIFICATE----- + + The first such PEM block in the input will be found, and its + payload will be base64 decoded and returned. + """ + prefix = "-----BEGIN %s-----" % name + postfix = "-----END %s-----" % name + start = self.data.find(prefix) + if start == -1: + raise SyntaxError("Missing PEM prefix") + end = self.data.find(postfix, start+len(prefix)) + if end == -1: + raise SyntaxError("Missing PEM postfix") + s = self.data[start+len("-----BEGIN %s-----" % name) : end] + retBytes = a2b_base64(s) + return retBytes + + + def getDecodedList(self, name): + """Decode a sequence of PEM blocks into a list of bytearrays. + + The input must contain any number of PEM blocks, each with the appropriate + PEM prefix and postfix based on the input name string, e.g. for + name="TACK BREAK SIG". Arbitrary text can appear between and before and + after the PEM blocks. For example: + + " Created by tack.py 0.9.3 Created at 2012-02-01T00:30:10Z -----BEGIN TACK + BREAK SIG----- + ATKhrz5C6JHJW8BF5fLVrnQss6JnWVyEaC0p89LNhKPswvcC9/s6+vWLd9snYTUv + YMEBdw69PUP8JB4AdqA3K6Ap0Fgd9SSTOECeAKOUAym8zcYaXUwpk0+WuPYa7Zmm + SkbOlK4ywqt+amhWbg9txSGUwFO5tWUHT3QrnRlE/e3PeNFXLx5Bckg= -----END TACK + BREAK SIG----- Created by tack.py 0.9.3 Created at 2012-02-01T00:30:11Z + -----BEGIN TACK BREAK SIG----- + ATKhrz5C6JHJW8BF5fLVrnQss6JnWVyEaC0p89LNhKPswvcC9/s6+vWLd9snYTUv + YMEBdw69PUP8JB4AdqA3K6BVCWfcjN36lx6JwxmZQncS6sww7DecFO/qjSePCxwM + +kdDqX/9/183nmjx6bf0ewhPXkA0nVXsDYZaydN8rJU1GaMlnjcIYxY= -----END TACK + BREAK SIG----- " + + All such PEM blocks will be found, decoded, and return in an ordered list + of bytearrays, which may have zero elements if not PEM blocks are found. + """ + bList = [] + prefix = "-----BEGIN %s-----" % name + postfix = "-----END %s-----" % name + + s = self.data + + while 1: + start = s.find(prefix) + if start == -1: + return bList + end = s.find(postfix, start+len(prefix)) + if end == -1: + raise SyntaxError("Missing PEM postfix") + s2 = s[start+len(prefix) : end] + retBytes = a2b_base64(s2) # May raise SyntaxError + bList.append(retBytes) + s = s[end+len(postfix) : ] diff --git a/tack/util/PEMEncoder.py b/tack/util/PEMEncoder.py new file mode 100644 index 0000000..c6bd63d --- /dev/null +++ b/tack/util/PEMEncoder.py @@ -0,0 +1,29 @@ +from binascii import b2a_base64 + +class PEMEncoder: + + def __init__(self, data): + self.data = data + + def getEncoded(self, name): + """Encode a payload bytearray into a PEM string. + + The input will be base64 encoded, then wrapped in a PEM prefix/postfix + based on the name string, e.g. for name="CERTIFICATE": + + -----BEGIN CERTIFICATE----- + MIIBXDCCAUSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRUQUNL + ... + KoZIhvcNAQEFBQADAwA5kw== + -----END CERTIFICATE----- + """ + s1 = b2a_base64(self.data)[:-1] # remove terminating \n + s2 = "" + while s1: + s2 += s1[:64] + "\n" + s1 = s1[64:] + + s = ("-----BEGIN %s-----\n" % name) + s2 +\ + ("-----END %s-----\n" % name) + + return s diff --git a/tack/util/Time.py b/tack/util/Time.py new file mode 100644 index 0000000..8aa12fb --- /dev/null +++ b/tack/util/Time.py @@ -0,0 +1,122 @@ +# Author: Trevor Perrin +# See the LICENSE file for legal information regarding use of this file. + +from tack.compat import * + +################ TIME FUNCS ### + +import time, calendar, math + +class Time: + # u in seconds + @staticmethod + def posixTimeToStr(u, includeSeconds=False): + t = time.gmtime(u) + if includeSeconds: + s = time.strftime("%Y-%m-%dT%H:%M:%SZ", t) + else: + s = time.strftime("%Y-%m-%dT%H:%MZ", t) + return s + + # u in minutes + @staticmethod + def durationToStr(u): + s = "" + if u >= (1440): # 1440 minutes per day + s += "%dd" % (u//1440) + u %= 1440 + if u >= (60): # 60 minutes per hour + s += "%dh" % (u//60) + u %= 60 + if u>0 or not s: + s += "%dm" % u + return s + + @staticmethod + def parseTimeArg(arg): + # First, see if they specified time as a duration + try: + mins = Time.parseDurationArg(arg) + return int(math.ceil(time.time() / 60.0)) + mins + except SyntaxError: + pass + + # Otherwise, allow them to specify as much or as little of + # ISO8601 as they want, but must end with "Z" + patterns = ["%Y-%m-%dT%H:%MZ", "%Y-%m-%dT%HZ", + "%Y-%m-%dZ", "%Y-%mZ", "%YZ"] + t = None + for p in patterns: + try: + t = time.strptime(arg, p) + break + except ValueError: + pass + if not t: + s = Time.posixTimeToStr(time.time()) + raise SyntaxError( + '''Invalid time format, use e.g. "%s" (current time) + or some prefix, such as: "%sZ", "%sZ", or "%sZ", + *OR* some duration, such as "5m", "30d", "1d12h5m", etc."''' % + (s, s[:13], s[:10], s[:4])) + u = int(calendar.timegm(t)//60) + if u < 0: + raise SyntaxError("Time too early, epoch starts at 1970.") + return u + + @staticmethod + def parseDurationArg(arg): + arg = arg.upper() + foundSomething = False + try: + mins = 0 + while 1: + i = arg.find("D") + if i != -1: + mins += 1440 * int(arg[:i]) + arg = arg[i+1:] + foundSomething = True + i = arg.find("H") + if i != -1: + mins += 60 * int(arg[:i]) + arg = arg[i+1:] + foundSomething = True + i = arg.find("M") + if i != -1: + mins += int(arg[:i]) + arg = arg[i+1:] + foundSomething = True + if arg: + raise SyntaxError() + if not foundSomething: + raise SyntaxError() + return mins + except: + raise SyntaxError() + + # Return UNIX time int + @staticmethod + def parseASN1UTCTime(b): + try: + if b[-1] != ord("Z"): + raise SyntaxError() + if len(b) == len("YYMHDDHHMMSSZ"): + pass + elif len(b) == len("YYHHDDHHMMZ"): + b = b[:-1] + b"00Z" + else: + raise SyntaxError() + year = int(b[:2]) + if year < 50: + b = b"20" + b + else: + b = b"19" + b + except: + raise SyntaxError() + return Time.parseASN1GeneralizedTime(b) + + @staticmethod + def parseASN1GeneralizedTime(b): + t = time.strptime(bytesToStrAscii(b), "%Y%m%d%H%M%SZ") + return int(calendar.timegm(t)) + diff --git a/tack/util/Util.py b/tack/util/Util.py new file mode 100644 index 0000000..3a2aaa7 --- /dev/null +++ b/tack/util/Util.py @@ -0,0 +1,31 @@ +from tack.compat import b2a_hex + +class Util: + @staticmethod + def writeBytes(b): + """Write hex-encoded byte array with 16 bytes (32 chars) per line""" + s = b2a_hex(b) + retVal = "" + while s: + retVal += s[:32] + s = s[32:] + if len(s): + retVal += "\n " + return retVal + + @staticmethod + def constTimeCompare(a, b): + """Compare two sequences of integer (eg bytearrays) without a timing leak. + + This function is secure when comparing against secret values, such as + passwords, MACs, etc., where a more naive, early-exit comparison loop + would leak information that could be used to extract the secret. + """ + if len(a) != len(b): + return False + result = 0 + for x in range(len(a)): + result |= a[x]^b[x] + if result: + return False + return True diff --git a/tack/util/__init__.py b/tack/util/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/TACKpy/version.py b/tack/version.py similarity index 100% rename from TACKpy/version.py rename to tack/version.py diff --git a/testdata/serverX509Cert.der b/testdata/serverX509Cert.der deleted file mode 100644 index 2035cc522b541e110cd7a2497bcf6e3d0dea696f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 643 zcmXqLVyZW2Vtm2G$?%}`LHGIXwRa47**LY@JlekVGBUEVG8i;A7;+nMvN4CUun9AT z2E#ZU!c5MNh5`nBAQ^UHcK^ieoW#5oLw=xspa_>RTS!rAnR7l&JvUgrkbwY5mRXn+ zBC7y2L%~1}ZX+k7m|#gxaZYAQszV7-e}P_JYKeiIIIp3RfsuikfuVtgiA9t+uZfX? zp^+t&YtYyRvxPh-@)&S|T*WQI9Hi!AC~hDMaU)+zYH^7k+!;hTv~fOi=rOV~FgNxB zL$H&nv5{frp-agtYi3MQFg&vIWyBYuj=-snOixezIC@xrBL7+qr%a}^tN;HKKUJ6S zwELSx@d8WZCe8KMTMvjyX1&QtQ*v=R$ZfCkKYIR);LA4mTRmRtoeKVJdb(|L*o&X{ zQ}5NxGjn)cl`FJz`ZH^`m|OC7CpOfmcTAafH)#JQ$=VYuoWCin=88}96dJ6C28B*1kXFqCrknW)`;2q diff --git a/testdata/serverX509Cert.pem b/testdata/serverX509Cert.pem deleted file mode 100644 index f3c86e4..0000000 --- a/testdata/serverX509Cert.pem +++ /dev/null @@ -1,16 +0,0 @@ ------BEGIN CERTIFICATE----- -MIICfzCCAegCCQDgdeCLz7d93DANBgkqhkiG9w0BAQUFADCBgDELMAkGA1UEBhMC -VVMxCzAJBgNVBAgTAkNBMRAwDgYDVQQHEwdPYWtsYW5kMQ8wDQYDVQQKEwZUcmV2 -Q28xCzAJBgNVBAsTAkNBMRIwEAYDVQQDEwlUcmV2Q28gQ0ExIDAeBgkqhkiG9w0B -CQEWEXRsc2xpdGVAdHJldnAubmV0MB4XDTEyMDIwNjAxMDg0OFoXDTQyMDEyOTAx -MDg0OFowgYYxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEQMA4GA1UEBxMHT2Fr -bGFuZDEPMA0GA1UEChMGVHJldkNvMQwwCgYDVQQLFANSJkQxFzAVBgNVBAMTDnRl -c3QudHJldnAubmV0MSAwHgYJKoZIhvcNAQkBFhF0bHNsaXRlQHRyZXZwLm5ldDCB -nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqcLSY6l8mJQgMcSp6Vj0EohRlYEC -5cj4xcMvkQ+tKEJpAs2r//4Xyn5vQrv2GHOgOTOCKa87tcAWGWrsbGYiRETBCz8k -/1uf6FPTPN+FSOkuylPzNcuGk1bo+d9l3nyeNkDjem0SqZfmOwZc2h9+yLB8J4iU -lt1Sv9IZfcioQ/cCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCz201zjXEC2yTuJBN3 -XT3Q/HXxPOnwkyU3+gKCGJmXeGoVoFDD23RbJ0Nabux1vnQHC4YsHNXPN1b++UOo -NcewUzJbrg9R7VP1teQsLEfaj26oeJkp2yls0b1PrWu/4aTpG7VORdPBeiCn7Alu -+3uDeYqQxcaaRGK2rWBJ3OeQkg== ------END CERTIFICATE----- diff --git a/tests/CertificateTest.py b/tests/CertificateTest.py new file mode 100644 index 0000000..766f2ba --- /dev/null +++ b/tests/CertificateTest.py @@ -0,0 +1,50 @@ +import unittest +from tack.tls.TlsCertificate import TlsCertificate +from tack.compat import a2b_hex +from tack.util.Time import Time + +class CertificateTest(unittest.TestCase): + + def test_Certificate(self): + s = """ +-----BEGIN CERTIFICATE----- +MIIFSzCCBDOgAwIBAgIHJ6JvWHUrOTANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE +BhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAY +BgNVBAoTEUdvRGFkZHkuY29tLCBJbmMuMTMwMQYDVQQLEypodHRwOi8vY2VydGlm +aWNhdGVzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkxMDAuBgNVBAMTJ0dvIERhZGR5 +IFNlY3VyZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTERMA8GA1UEBRMIMDc5Njky +ODcwHhcNMTEwNzA4MDAxOTU3WhcNMTIwNzA4MDAxOTU3WjBPMRQwEgYDVQQKFAsq +LnRyZXZwLm5ldDEhMB8GA1UECxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMRQw +EgYDVQQDFAsqLnRyZXZwLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC +ggEBAMgawQKi4zY4TTz1RNL7klt/ibvjG+jGqBYlc6qjUiTQORD3fUrdAF83Alav +JiC3rrwfvarL8KpPn7zQQOOk+APwzFxn0sVphDvAN8E7xI/cC7es08EYA9/DDN7r +VTe/wvbs77CL5AniRSJyAP5puvSUHgixingTgYmnkIgC+3ZFqyfz2uenxvkPkoUT +QEBkm2uEcBOwBMXAih1fdsuhEiJ9qpmejpIEvxLIDoMnCWTPs897zhwr3epQkn5g +lKQ9H+FnEo5Jf8YBM4YhAzwG/8pyfc8NtOHafKUb5PhSIC7Vy7N2EBQ4y9kDOZc+ +r0Vguq4p+Nncc32JI/i1Cdj/lO0CAwEAAaOCAa4wggGqMA8GA1UdEwEB/wQFMAMB +AQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA4GA1UdDwEB/wQEAwIF +oDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmdvZGFkZHkuY29tL2dkczEt +NTIuY3JsME0GA1UdIARGMEQwQgYLYIZIAYb9bQEHFwEwMzAxBggrBgEFBQcCARYl +aHR0cHM6Ly9jZXJ0cy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5LzCBgAYIKwYBBQUH +AQEEdDByMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5nb2RhZGR5LmNvbS8wSgYI +KwYBBQUHMAKGPmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3Np +dG9yeS9nZF9pbnRlcm1lZGlhdGUuY3J0MB8GA1UdIwQYMBaAFP2sYTKTbEXW4u6F +X5q653aZaMznMCEGA1UdEQQaMBiCCyoudHJldnAubmV0ggl0cmV2cC5uZXQwHQYD +VR0OBBYEFCYv4a9+enZGS27wqAv+TPfJOOb7MA0GCSqGSIb3DQEBBQUAA4IBAQA+ +2OKO77vpwKtoKddDtamBokiVhHrfw0c7ALGysOXtss1CKV2WgH4FdNuh9pFkVZB2 +mKZ7keS7EMW11OzgBR3pRRk0AkNYtDsOJEXA2+1NLFgrtdujHrDX4WIoi9MGbqB5 +TfK08XufM7OP3yXDLtMxyUtyjprFhdxPE+9p/GJ0IVdZrMmzYTjyCOO8+okY9zAQ +RVUKuxd+eEaH3BpPAau4MP2n24gy6WEsJ2auB81ee9fDnx/tfKPqvyuc4r4/Z4aL +5CvQvlPHaG/TTXXNh3pZFl3d/J5/76ZfeQzQtZ+dCrE4a4601Q4hBBXEq5gQfaof +H4yTGzfDv+JLIICAIcCs +-----END CERTIFICATE-----""" + sslc = TlsCertificate() + sslc.parsePem(s) + assert(sslc.key_sha256 == a2b_hex("ffd30bcb84dbbc211a510875694354c58863d84fb7fc5853dfe36f4be2eb2e50")) + assert(sslc.cert_sha256 == a2b_hex("1a50e3de3a153f33b314b67c1aacc2f59fc99c49b8449c33dcc3665663e2bff1")) + assert(Time.posixTimeToStr(sslc.notAfter, True) == "2012-07-08T00:19:57Z") + assert(isinstance(sslc.writeText(), str)) + return 1 + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/CompatTest.py b/tests/CompatTest.py new file mode 100644 index 0000000..2f9bff1 --- /dev/null +++ b/tests/CompatTest.py @@ -0,0 +1,39 @@ +import unittest +from tack.compat import a2b_hex +from tack.compat import a2b_base64 +from tack.compat import b2a_hex +from tack.compat import b2a_base64 +from tack.compat import bytesToStrAscii + +class CompatTest(unittest.TestCase): + + def test_Compat(self): + assert(isinstance(a2b_hex("00"), bytearray)) + assert(a2b_hex("0102cdef") == bytearray([0x01,0x02,0xcd,0xef])) + assert(a2b_hex("0102CDEF") == bytearray([0x01,0x02,0xcd,0xef])) + try: + assert(a2b_hex("c0102cdef") == bytearray([0x01,0x02,0xcd,0xef])) + assert False + except SyntaxError: + pass + try: + assert(a2b_hex("xx0102cdef") == bytearray([0x01,0x02,0xcd,0xef])) + assert False + except SyntaxError: + pass + + assert(isinstance(a2b_base64("0000"), bytearray)) + assert(a2b_base64("Zm9vYg==") == bytearray(b"foob")) + + assert(b2a_hex(bytearray([0x01,0x02,0xcd,0xef])) == "0102cdef") + assert(b2a_hex(bytearray([0x00])) == "00") + assert(b2a_hex(bytearray([0xFF])) == "ff") + + assert(b2a_base64(bytearray(b"foob")) == "Zm9vYg==\n") + assert(b2a_base64(bytearray(b"fooba")) == "Zm9vYmE=\n") + assert(b2a_base64(bytearray(b"foobar")) == "Zm9vYmFy\n") + + assert(bytesToStrAscii(bytearray(b"abcd123")) == "abcd123") + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/CryptoTest.py b/tests/CryptoTest.py new file mode 100644 index 0000000..a492a41 --- /dev/null +++ b/tests/CryptoTest.py @@ -0,0 +1,56 @@ +import unittest +from TACKpy.ecdsa_wrappers import ecdsa256Verify +from tack.compat import a2b_hex +from tack.crypto.AES import AES +from tack.crypto.ASN1 import asn1Length, toAsn1IntBytes, fromAsn1IntBytes +from tack.crypto.ECDSA import ec256Generate, ecdsa256Sign + +class CryptoTest(unittest.TestCase): + + def test_AES(self): + key = a2b_hex("c286696d887c9aa0611bbb3e2025a45a") + IV = a2b_hex("562e17996d093d28ddb3ba695a2e6f58") + plaintext = a2b_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") + ciphertext = a2b_hex("d296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1") + + assert(AES(key, IV).encrypt(plaintext) == ciphertext) + assert(AES(key, IV).decrypt(ciphertext) == plaintext) + + def test_ECDSA(self): + privateKey, publicKey = ec256Generate() + data = bytearray([0,1,2,3]) + badData = bytearray([0,1,2,4]) + signature = ecdsa256Sign(privateKey, publicKey, data) + assert(ecdsa256Verify(publicKey, data, signature)) + assert(not ecdsa256Verify(publicKey, badData, signature)) + + def test_ASN1(self): + assert(asn1Length(7) == bytearray([7])) + assert(asn1Length(0x7F) == bytearray([0x7F])) + assert(asn1Length(0x80) == bytearray([0x81,0x80])) + assert(asn1Length(0x81) == bytearray([0x81,0x81])) + assert(asn1Length(0xFF) == bytearray([0x81,0xFF])) + assert(asn1Length(0x0100) == bytearray([0x82,0x01,0x00])) + assert(asn1Length(0x0101) == bytearray([0x82,0x01,0x01])) + assert(asn1Length(0xFFFF) == bytearray([0x82,0xFF,0xFF])) + + assert(toAsn1IntBytes(bytearray([0xFF])) == bytearray([0x00,0xFF])) + assert(toAsn1IntBytes(bytearray([0x7F])) == bytearray([0x7F])) + assert(toAsn1IntBytes(bytearray([0x00])) == bytearray([0x00])) + assert(toAsn1IntBytes(bytearray([0x00,0x00])) == bytearray([0x00])) + assert(toAsn1IntBytes(bytearray([0x00,0x01])) == bytearray([0x01])) + assert(toAsn1IntBytes(bytearray([0,0xFF])) == bytearray([0,0xFF])) + assert(toAsn1IntBytes(bytearray([0,0,0,0xFF])) == bytearray([0,0xFF])) + assert(toAsn1IntBytes(bytearray([0,0,0,1,1])) == bytearray([1,1])) + + assert(bytearray([0xFF]) == fromAsn1IntBytes(bytearray([0x00,0xFF]),1)) + assert(bytearray([0x7F]) == fromAsn1IntBytes(bytearray([0x7F]),1)) + assert(bytearray([0x00]) == fromAsn1IntBytes(bytearray([0x00]),1)) + assert(bytearray([0x00,0x00]) == fromAsn1IntBytes(bytearray([0x00]),2)) + assert(bytearray([0x00,0x01]) == fromAsn1IntBytes(bytearray([0x01]),2)) + assert(bytearray([0,0xFF]) == fromAsn1IntBytes(bytearray([0,0xFF]),2)) + assert(bytearray([0,0,0,0xFF]) == fromAsn1IntBytes(bytearray([0,0xFF]),4)) + assert(bytearray([0,0,0,1,1]) == fromAsn1IntBytes(bytearray([1,1]),5)) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/StructuresTest.py b/tests/StructuresTest.py new file mode 100644 index 0000000..0e4536c --- /dev/null +++ b/tests/StructuresTest.py @@ -0,0 +1,97 @@ +import unittest +from tack.compat import a2b_hex +from tack.structures.Tack import Tack +from tack.structures.TackBreakSig import TackBreakSig +from tack.structures.TackKeyFile import TackKeyFile +from tack.util.Time import Time + +class StructuresTest(unittest.TestCase): + + def test_Tack(self): + s = """ +-----BEGIN TACK----- +TAmsAZIpzR+MYwQrsujLhesvpu3dRc5ROhfgySqUVkU1p1hdXo+PwQrmaQo9B9+o +hecRrWElh3yThwgYQRgbSwAAAY0cQDHeDLGfKtuw0c17GzHvjuPrWbdEWa75S0gL +7u64XGTJQUtzAwXIWOkQEQ0BRUlbzcGEa9a1PBhjmmWFNF+kGAswhLnXc5qL4y/Z +PDUV0rzIIYjXP58T5pphGKRgLlK3Aw== +-----END TACK-----""" + + t = Tack().createFromPem(s) + + assert(t.public_key == a2b_hex("4c09ac019229cd1f8c63042bb2e8" + "cb85eb2fa6eddd45ce513a17e0c9" + "2a94564535a7585d5e8f8fc10ae6" + "690a3d07dfa885e711ad6125877c" + "9387081841181b4b")) + assert(Time.posixTimeToStr(t.expiration*60) == "2019-06-25T22:24Z") + assert(t.generation == 0) + assert(t.target_hash == a2b_hex("31de0cb19f2adbb0d1cd7b1b31ef8ee3eb59b74459aef94b480beeeeb85c64c9")) + assert(t.signature == a2b_hex("414b730305c858e910110d0145495" + "bcdc1846bd6b53c18639a6585345f" + "a4180b3084b9d7739a8be32fd93c3" + "515d2bcc82188d73f9f13e69a6118" + "a4602e52b703")) + + def test_BreakSig(self): + s = """ +-----BEGIN TACK BREAK SIG----- +TAmsAZIpzR+MYwQrsujLhesvpu3dRc5ROhfgySqUVkU1p1hdXo+PwQrmaQo9B9+o +hecRrWElh3yThwgYQRgbS0HynTQCmrY48oJsQtarSMoxnRNYHaaYOXwu9+4ur8mX +wjKhIA9fXWNxuP73ZoicU+qC4bZjMN+WKuy7k8bSQZY= +-----END TACK BREAK SIG-----""" + + tbs = TackBreakSig.createFromPem(s) + assert(tbs.getTackId() == "nkufh.czttd.5cmlw.7cxtv.k6srn") + assert(tbs.signature == a2b_hex("41f29d34029ab638f2826c42d6a" + "b48ca319d13581da698397c2ef7" + "ee2eafc997c232a1200f5f5d637" + "1b8fef766889c53ea82e1b66330" + "df962aecbb93c6d24196")) + + def test_BreakSigList(self): + s = """ +-----BEGIN TACK BREAK SIG----- +TAmsAZIpzR+MYwQrsujLhesvpu3dRc5ROhfgySqUVkU1p1hdXo+PwQrmaQo9B9+o +hecRrWElh3yThwgYQRgbS0HynTQCmrY48oJsQtarSMoxnRNYHaaYOXwu9+4ur8mX +wjKhIA9fXWNxuP73ZoicU+qC4bZjMN+WKuy7k8bSQZY= +-----END TACK BREAK SIG----- +Created by TACK.py 0.9.6 +Created at 2012-05-10T00:54:10Z +-----BEGIN TACK BREAK SIG----- +73nkbxCcvFnrCIlcgtZx4iPevqxUFd9RFUNU18xfqzTCU8hV0jwYerdCwt8+VbkQ +OvHEbbRHmGAX8yseGrYX1dNuoFfSN1fCLY08u/0NU+x8fmJ6tEewegVAHguw67eR +PgegVlKuDULIASht9fvs6xTfxcFJDUgNaenZfcqAgAI= +-----END TACK BREAK SIG----- +""" + tbsList = TackBreakSig.createFromPemList(s) + assert(tbsList[0].getTackId() == "nkufh.czttd.5cmlw.7cxtv.k6srn") + assert(tbsList[1].getTackId() == "6xwgu.ydz7m.7cki3.kizmd.pt2f2") + assert(len(tbsList) == 2) + return 1 + + def test_KeyFile(self): + s = """ + -----BEGIN TACK PRIVATE KEY----- + AQAAIAAjOxiOdpiMo5qWidXwBTqJHxW5X1zRDBOA4ldqqFuKOSh6JJdrbXk1WsMN + X/gyaVuHMBhC/g/rjtu/EnmIHoUuT9348iXeeROaLVRPdNqwr+5KEfjtTY7uXA6Q + mhRUn+XmDePKRucRHYkcQaFPnzglrQ120Dh6aXD4PbtJMWajJtzTMvtEo9pNZhoM + QTNZNoM= + -----END TACK PRIVATE KEY-----""" + publicKey = a2b_hex("87301842fe0feb8edbbf1279881e852e" + "4fddf8f225de79139a2d544f74dab0af" + "ee4a11f8ed4d8eee5c0e909a14549fe5" + "e60de3ca46e7111d891c41a14f9f3825") + privateKey = a2b_hex("fc815de8b1de13a436e9cd69742cbf2c" + "d4c1c9bb33e023401d9291cf2781b754") + kf = TackKeyFile.createFromPem(s, "asdf") + assert(kf.public_key == publicKey) + assert(kf.private_key == privateKey) + kf2 = TackKeyFile.createFromPem(kf.serializeAsPem(), "asdf") + assert(kf2.public_key == publicKey) + assert(kf2.private_key == privateKey) + kf3 = TackKeyFile.createRandom("123") + kf4 = TackKeyFile.createFromPem(kf3.serializeAsPem(), "123") + assert(kf3.public_key == kf4.public_key) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/TimeTest.py b/tests/TimeTest.py new file mode 100644 index 0000000..37a03c3 --- /dev/null +++ b/tests/TimeTest.py @@ -0,0 +1,41 @@ +import unittest +from tack.util.Time import Time + +class TimeTest(unittest.TestCase): + + def test_posix(self): + assert(Time.posixTimeToStr(1234567890, True) == "2009-02-13T23:31:30Z") + assert(Time.posixTimeToStr(1234567890) == "2009-02-13T23:31Z") + + def test_duration(self): + assert(Time.durationToStr(0) == "0m") + assert(Time.durationToStr(59) == "59m") + assert(Time.durationToStr(60) == "1h") + assert(Time.durationToStr(61) == "1h1m") + assert(Time.durationToStr(1439) == "23h59m") + assert(Time.durationToStr(1440) == "1d") + assert(Time.durationToStr(1441) == "1d1m") + assert(Time.durationToStr(1500) == "1d1h") + assert(Time.durationToStr(1501) == "1d1h1m") + assert(Time.durationToStr(1440*37+122) == "37d2h2m") + + assert(0 == Time.parseDurationArg("0m")) + assert(59 == Time.parseDurationArg("59m")) + assert(60 == Time.parseDurationArg("1h")) + assert(61 == Time.parseDurationArg("1h1m")) + assert(1439 == Time.parseDurationArg("23h59m")) + assert(1440 == Time.parseDurationArg("1d")) + assert(1441 == Time.parseDurationArg("1d1m")) + assert(1500 == Time.parseDurationArg("1d1h")) + assert(1501 == Time.parseDurationArg("1d1h1m")) + assert(1440*37+122 == Time.parseDurationArg("37d2h2m")) + + def test_string(self): + assert(Time.parseTimeArg("2012-07-20T05:40Z")*60 == 1342762800) + assert(Time.parseTimeArg("2012-07-20T05Z")*60 == 1342760400) + assert(Time.parseTimeArg("2012-07-20Z")*60 == 1342742400) + assert(Time.parseTimeArg("2012-07Z")*60 == 1341100800) + assert(Time.parseTimeArg("2012Z")*60 == 1325376000) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 From e78a99bf3cb74e7216bee5231bffd7b5b7d4e649 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 10 May 2012 12:25:54 +0300 Subject: [PATCH 08/94] Slightly improve some error msgs. --- TACKpy/tack_structures.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/TACKpy/tack_structures.py b/TACKpy/tack_structures.py index 8c5c869..a98fed5 100644 --- a/TACKpy/tack_structures.py +++ b/TACKpy/tack_structures.py @@ -237,7 +237,7 @@ def parse(self, b, verifyFunc=ecdsa256Verify): tackLen = p.getInt(1) if tackLen: if tackLen != TACK.length: - raise SyntaxError("TACK wrong size") + raise SyntaxError("TACK wrong size: %d" % tackLen) else: b2 = p.getBytes(tackLen) self.tack = TACK() @@ -245,9 +245,9 @@ def parse(self, b, verifyFunc=ecdsa256Verify): sigsLen = p.getInt(2) if sigsLen >1024: - raise SyntaxError("break_sigs too large") + raise SyntaxError("break_sigs too large: %d" % sigsLen) elif sigsLen % TACK_Break_Sig.length != 0: - raise SyntaxError("break sigs wrong size") + raise SyntaxError("break sigs wrong size: %d" % sigsLen) else: self.break_sigs = [] b2 = p.getBytes(sigsLen) From 9570e7918d0c7f23a2802548cad4cee4c3f4ae11 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 10 May 2012 12:38:05 +0300 Subject: [PATCH 09/94] Cleanup usage and error strings. --- scripts/TACK.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/scripts/TACK.py b/scripts/TACK.py index 076c808..02a0613 100755 --- a/scripts/TACK.py +++ b/scripts/TACK.py @@ -97,9 +97,9 @@ def handleArgs(argv, argString, mandatoryString="", flags="", inCert = SSL_Cert() inCert.open(arg) except SyntaxError: - printError("SSL certificate malformed: %s" % arg) + printError("Certificate malformed: %s" % arg) except IOError: - printError("Error opening SSL certificate: %s" % arg) + printError("Error opening certificate: %s" % arg) elif opt == "-k": try: keyPem = open(arg, "rU").read() @@ -155,7 +155,7 @@ def handleArgs(argv, argString, mandatoryString="", flags="", if "k" in mandatoryString and not keyPem: printError("-k missing (TACK Key)") if "c" in mandatoryString and not inCert: - printError("-c missing (SSL certificate)") + printError("-c missing (certificate)") if "i" in mandatoryString and not inTack: printError("-i missing (TACK)") @@ -416,12 +416,12 @@ def helpCmd(argv): elif cmd == "sign"[:len(cmd)]: s = posixTimeToStr(time.time()) print( \ -"""Creates a TACK based on a target SSL certificate. +"""Creates a TACK based on a target certificate. sign -k KEY -c CERT -k KEY : Use this TACK key file - -c CERT : Sign this SSL certificate's public key + -c CERT : Sign this certificate's public key Optional arguments: -v : Verbose @@ -432,10 +432,11 @@ def helpCmd(argv): -e EXPIRATION : Use this UTC time for expiration ("%s", "%sZ", "%sZ", "%sZ" etc.) - Or, specify a duration from current time: - ("5m", "30d", "1d12h5m", "0m", etc.) + Or, specify a delta from current time: + ("5m", "30d", "1d12h5m", "0m", etc.) + If not specified, the certificate's notAfter is used. - n NUM@INTERVAL : Generate NUM TACKs, with expiration times spaced - out by INTERVAL (see -d for INTERVAL syntax). The + out by INTERVAL (see -e for delta syntax). The -o argument is used as a filename prefix, and the -e argument is used as the first expiration time. """ % (s, s[:13], s[:10], s[:4])) @@ -453,7 +454,7 @@ def helpCmd(argv): -p PASSWORD : Use this TACK key password instead of prompting """) elif cmd == "view"[:len(cmd)]: - print("""Views a TACK, TACK Key, TACK Break Sig, or SSL certificate. + print("""Views a TACK, TACK Key, TACK Break Sig, or certificate. view """) From c596f459667fecf76236443132ceeedc266ed231 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 10 May 2012 20:32:07 +0300 Subject: [PATCH 10/94] Changed SNI to SHOULD, added note about pin_activation with no TACK. --- draft-tack.txt | 66 +++++++++++++++++++++++++------------------------- draft-tack.xml | 13 +++++----- 2 files changed, 40 insertions(+), 39 deletions(-) diff --git a/draft-tack.txt b/draft-tack.txt index ea02135..a79e98d 100644 --- a/draft-tack.txt +++ b/draft-tack.txt @@ -3,8 +3,8 @@ TLS Working Group M. Marlinspike Internet-Draft T. Perrin, Ed. -Intended status: Standards Track May 5, 2012 -Expires: November 6, 2012 +Intended status: Standards Track May 10, 2012 +Expires: November 11, 2012 Trust Assertions for Certificate Keys @@ -35,7 +35,7 @@ Status of this Memo time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress." - This Internet-Draft will expire on November 6, 2012. + This Internet-Draft will expire on November 11, 2012. Copyright Notice @@ -52,7 +52,7 @@ Copyright Notice -Marlinspike & Perrin Expires November 6, 2012 [Page 1] +Marlinspike & Perrin Expires November 11, 2012 [Page 1] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -108,7 +108,7 @@ Table of Contents -Marlinspike & Perrin Expires November 6, 2012 [Page 2] +Marlinspike & Perrin Expires November 11, 2012 [Page 2] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -164,7 +164,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 3] +Marlinspike & Perrin Expires November 11, 2012 [Page 3] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -220,7 +220,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 4] +Marlinspike & Perrin Expires November 11, 2012 [Page 4] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -276,7 +276,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 5] +Marlinspike & Perrin Expires November 11, 2012 [Page 5] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -332,7 +332,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 6] +Marlinspike & Perrin Expires November 11, 2012 [Page 6] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -388,7 +388,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 7] +Marlinspike & Perrin Expires November 11, 2012 [Page 7] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -444,7 +444,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 8] +Marlinspike & Perrin Expires November 11, 2012 [Page 8] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -461,7 +461,8 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 TACK_Extension MAY be used by clients to activate or extend the activation of TACK pins. This field is typically toggled from a disabled to an enabled state once TACKs have been deployed to all - TLS servers for a hostname. + TLS servers for a hostname. Enabling pin_activation when there is + no TACK present has no effect. Note that both the "tack" and "break_sigs" fields MAY be empty. @@ -499,8 +500,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 - -Marlinspike & Perrin Expires November 6, 2012 [Page 9] +Marlinspike & Perrin Expires November 11, 2012 [Page 9] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -537,11 +537,11 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 5.2. High-level client processing - A TACK client SHALL send the "tack" extension defined previously, as - well as the "server_name" extension from [RFC6066] indicating the - hostname the client is contacting. If not resuming a session, the - server MAY respond with a TACK_Extension. A TACK client SHALL - perform the following steps prior to using a non-resumed connection: + A TACK client SHALL send the "tack" extension defined previously, and + SHOULD send the "server_name" extension from [RFC6066]. If not + resuming a session, the server MAY respond with a TACK_Extension. A + TACK client SHALL perform the following steps prior to using a non- + resumed connection: 1. Check whether the TLS handshake is "well-formed". @@ -556,7 +556,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 10] +Marlinspike & Perrin Expires November 11, 2012 [Page 10] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -612,7 +612,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 11] +Marlinspike & Perrin Expires November 11, 2012 [Page 11] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -668,7 +668,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 12] +Marlinspike & Perrin Expires November 11, 2012 [Page 12] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -724,7 +724,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 13] +Marlinspike & Perrin Expires November 11, 2012 [Page 13] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -780,7 +780,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 14] +Marlinspike & Perrin Expires November 11, 2012 [Page 14] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -836,7 +836,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 15] +Marlinspike & Perrin Expires November 11, 2012 [Page 15] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -892,7 +892,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 16] +Marlinspike & Perrin Expires November 11, 2012 [Page 16] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -948,7 +948,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 17] +Marlinspike & Perrin Expires November 11, 2012 [Page 17] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -1004,7 +1004,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 18] +Marlinspike & Perrin Expires November 11, 2012 [Page 18] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -1060,7 +1060,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 19] +Marlinspike & Perrin Expires November 11, 2012 [Page 19] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -1116,7 +1116,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 20] +Marlinspike & Perrin Expires November 11, 2012 [Page 20] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -1172,7 +1172,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 21] +Marlinspike & Perrin Expires November 11, 2012 [Page 21] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -1228,7 +1228,7 @@ Internet-Draft Trust Assertions for Certificate Keys May 2012 -Marlinspike & Perrin Expires November 6, 2012 [Page 22] +Marlinspike & Perrin Expires November 11, 2012 [Page 22] Internet-Draft Trust Assertions for Certificate Keys May 2012 @@ -1284,5 +1284,5 @@ Authors' Addresses -Marlinspike & Perrin Expires November 6, 2012 [Page 23] +Marlinspike & Perrin Expires November 11, 2012 [Page 23] diff --git a/draft-tack.xml b/draft-tack.xml index 971eab8..2dd4143 100644 --- a/draft-tack.xml +++ b/draft-tack.xml @@ -352,7 +352,8 @@ break signatures. If pin activation is enabled, then the TACK_Extension MAY be used by clients to activate or extend the activation of TACK pins. This field is typically toggled from a disabled to an enabled state once TACKs have been deployed to -all TLS servers for a hostname. +all TLS servers for a hostname. Enabling pin_activation when there is no TACK +present has no effect. @@ -429,11 +430,11 @@ until that time. -A TACK client SHALL send the "tack" extension defined previously, as well as -the "server_name" extension from indicating the -hostname the client is contacting. If not resuming a session, the server MAY -respond with a TACK_Extension. A TACK client SHALL perform the following steps -prior to using a non-resumed connection: +A TACK client SHALL send the "tack" extension defined previously, and SHOULD +send the "server_name" extension from . If not +resuming a session, the server MAY respond with a TACK_Extension. A TACK +client SHALL perform the following steps prior to using a non-resumed +connection: Check whether the TLS handshake is "well-formed". From e64d7b9674186eaadace2ae17a4e0e248908165c Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Thu, 10 May 2012 13:58:40 -0700 Subject: [PATCH 11/94] Consolidated EC logic into explicit key classes. Moved the ECC functions into classes that represent the identity and operation of private and public EC keys. --- tack/commands/BreakCommand.py | 2 +- tack/commands/SignCommand.py | 2 +- tack/crypto/ECDSA.py | 171 -------------------------------- tack/crypto/ECGenerator.py | 46 +++++++++ tack/crypto/ECPrivateKey.py | 98 ++++++++++++++++++ tack/crypto/ECPublicKey.py | 55 ++++++++++ tack/structures/Tack.py | 13 ++- tack/structures/TackBreakSig.py | 11 +- tack/structures/TackId.py | 18 ---- tack/structures/TackKeyFile.py | 36 +++---- tack/tls/TlsStructure.py | 3 +- tests/CryptoTest.py | 12 +-- tests/StructuresTest.py | 12 +-- 13 files changed, 245 insertions(+), 234 deletions(-) delete mode 100644 tack/crypto/ECDSA.py create mode 100644 tack/crypto/ECGenerator.py create mode 100644 tack/crypto/ECPrivateKey.py create mode 100644 tack/crypto/ECPublicKey.py delete mode 100644 tack/structures/TackId.py diff --git a/tack/commands/BreakCommand.py b/tack/commands/BreakCommand.py index 6a97ff2..fbc11af 100644 --- a/tack/commands/BreakCommand.py +++ b/tack/commands/BreakCommand.py @@ -11,7 +11,7 @@ def __init__(self, argv): self.key = self.getKey(self.getPassword()) def execute(self): - breakSig = TackBreakSig.createFromParameters(self.key.getPublicKey(), self.key) + breakSig = TackBreakSig.createFromParameters(self.key.getPublicKey(), self.key.getPrivateKey()) self.outputFile.write(self.addPemComments(breakSig.serializeAsPem())) if self.isVerbose(): diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index 388205a..61e99f0 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -24,7 +24,7 @@ def __init__(self, argv): def execute(self): if not self.numArg: - tack = Tack.createFromParameters(self.key.getPublicKey(), self.key, self.min_generation, + tack = Tack.createFromParameters(self.key.getPublicKey(), self.key.getPrivateKey(), self.min_generation, self.generation, self.expiration, self.certificate.key_sha256) self.outputFile.write(self.addPemComments(tack.serializeAsPem())) diff --git a/tack/crypto/ECDSA.py b/tack/crypto/ECDSA.py deleted file mode 100644 index 0863998..0000000 --- a/tack/crypto/ECDSA.py +++ /dev/null @@ -1,171 +0,0 @@ -"""The following three "wrapper" functions are used for working with ECDSA: - ec256Generate - ecdsa256Sign - ecdsa256Verify - -These wrapper functions operate on bytearrays: - privateKey is a bytearray of length 32 - publicKey is a bytearray of length 64 - signature is a bytearray of length 64 - dataToSign/Verify is an arbitrary-length bytearray - -Because M2Crypto operates on ASN.1-encoded signatures, and traditional OpenSSL -PEM-encoded public and private keys, there is a fair bit of data munging to -convert to/from M2Crypto formats. -""" -from M2Crypto import EC, BIO -import math -from tack.asn1.ASN1Object import ASN1Object -from tack.crypto.ASN1 import ASN1Parser, fromAsn1IntBytes, toAsn1IntBytes, asn1Length -from tack.crypto.Digest import Digest -from tack.util.PEMDecoder import PEMDecoder -from tack.util.PEMEncoder import PEMEncoder -from tack.compat import a2b_hex - -def ec256Generate(): - # Generate M2Crypto.EC.EC object - m2EC = EC.gen_params(EC.NID_X9_62_prime256v1) - m2EC.gen_key() - - # Get the ASN.1 ECPrivateKey for the object - m2Bio = BIO.MemoryBuffer() - m2EC.save_key_bio(m2Bio, cipher=None) - pemPrivKeyBytes = m2Bio.getvalue() - - # Parse the ASN.1 ECPrivateKey into byte arrays - # for the 32-byte priv key, and 64-byte pub key - (privateKey, publicKey) = _parseECPrivateKey(pemPrivKeyBytes) - return privateKey, publicKey - -def ecdsa256Sign(privateKey, publicKey, dataToSign): - # Write the passed-in private key byte array into PEM form - # Then create M2Crypto EC object from ASN.1 form - pemPrivKeyBytes = _writeECPrivateKey(privateKey, publicKey) - m2EC = EC.load_key_bio(BIO.MemoryBuffer(pemPrivKeyBytes)) - - # Produce ASN.1 signature - hash = Digest.SHA256(dataToSign) - asn1SigBytes = m2EC.sign_dsa_asn1(hash) - - # Convert stupid ASN.1 signature into 64-byte signature - # Double-check before returning - sigBytes = _parseECSignature(asn1SigBytes) - assert(ecdsa256Verify(publicKey, dataToSign, sigBytes)) - return sigBytes - -def ecdsa256Verify(publicKey, dataToVerify, signature): - # Write the passed-in public key byte array into PEM form - # Then create M2Crypto EC_pub - pemPubKeyBytes = _writeECPublicKey(publicKey) - m2ECpub = EC.load_pub_key_bio(BIO.MemoryBuffer(pemPubKeyBytes)) - - # Convert 64-byte signature into a stupid ASN.1 signature - asn1SigBytes = _writeECSignature(signature) - hash = Digest.SHA256(dataToVerify) - return m2ECpub.verify_dsa_asn1(hash, asn1SigBytes) - -# Marshal/unmarshal PEM-wrapped ECPrivateKey and ASN.1 Signatures -def _parseECPrivateKey(pemPrivKeyBytes): - """Parse a bytearray containing a PEM-encoded ECPrivatey. - - Return a pair of (32-byte bytearray, 64-byte bytearray) - containing the (privateKey, publicKey) - """ - b = PEMDecoder(pemPrivKeyBytes).getDecoded("EC PRIVATE KEY") - p = ASN1Parser(b) - # The private key is stored as an ASN.1 integer which may - # need to have zero padding removed (if 33 bytes) or added - # (if < 32 bytes): - privateKey = p.getChild(1).value - privateKey = fromAsn1IntBytes(privateKey, 32) - # There is a 00 04 byte prior to the 64-byte public key - # I'm not sure why M2Crypto has the 00 byte there?, - # some ASN1 thing - the 04 byte signals "uncompressed" - # per SECG. Anyways, strip both those bytes off ([2:]) - publicKey = p.getChild(3).getTagged().value[2:] - assert(len(privateKey) == 32) - assert(len(publicKey) == 64) - return privateKey, publicKey - -def _writeECPrivateKey(privateKey, publicKey): - assert(len(privateKey) == 32) - assert(len(publicKey) == 64) - bytes1 = a2b_hex("02010104") - bytes2 = a2b_hex("a00a06082a8648ce3d030107a14403420004") - privateKey = toAsn1IntBytes(privateKey) - b = bytes1 + asn1Length(len(privateKey)) + privateKey +\ - bytes2 + publicKey - b = bytearray([0x30]) + asn1Length(len(b)) + b - pemPrivKeyBytes = PEMEncoder(b).getEncoded("EC PRIVATE KEY") - # pemPrivKeyBytes = pem(b, "EC PRIVATE KEY") - return pemPrivKeyBytes - -def _writeECPublicKey(publicKey): - assert(len(publicKey) == 64) - bytes1 = a2b_hex("3059301306072a8648ce3d020106082a8648ce3d03010703420004") - asn1KeyBytes = bytes1 + publicKey - pemPubKeyBytes = PEMEncoder(asn1KeyBytes).getEncoded("PUBLIC KEY") - # pemPubKeyBytes = pem(asn1KeyBytes, "PUBLIC KEY") - return pemPubKeyBytes - -def _parseECSignature(asn1SigBytes): - p = ASN1Parser(bytearray(asn1SigBytes)) - r = _bytesToNumber(p.getChild(0).value) - s = _bytesToNumber(p.getChild(1).value) - return _numberToBytes(r, 32) + _numberToBytes(s, 32) - -def _writeECSignature(ecSigBytes): - assert(len(ecSigBytes) == 64) - asn1R = toAsn1IntBytes(ecSigBytes[:32]) - asn1S = toAsn1IntBytes(ecSigBytes[32:]) - # Add ASN1 Type=2(int), and Length fields - asn1R = bytearray([2]) + asn1Length(len(asn1R)) + asn1R - asn1S = bytearray([2]) + asn1Length(len(asn1S)) + asn1S - # Add ASN1 Type=0x30(Sequence) and Length fields - asn1ECSigBytes = bytearray([0x30]) +\ - asn1Length(len(asn1R+asn1S)) + asn1R + asn1S - return asn1ECSigBytes - -def _bytesToNumber(bytes): - "Convert a sequence of bytes (eg bytearray) into integer." - total = 0 - multiplier = 1 - for count in range(len(bytes)-1, -1, -1): - byte = bytes[count] - total += multiplier * byte - multiplier *= 256 - return total - -def _numberToBytes(n, howManyBytes=None): - """Convert an integer into a bytearray, zero-pad to howManyBytes. - - The returned bytearray may be smaller than howManyBytes, but will - not be larger. The returned bytearray will contain a big-endian - encoding of the input integer (n). - """ - if not howManyBytes: - howManyBytes = _numBytes(n) - bytes = bytearray(howManyBytes) - for count in range(howManyBytes-1, -1, -1): - bytes[count] = int(n % 256) - n >>= 8 - return bytes - -def _numBytes(n): - "Return the number of bytes needed to represent the integer n." - if n==0: - return 0 - bits = _numBits(n) - return int(math.ceil(bits / 8.0)) - -def _numBits(n): - "Return the number of bits needed to represent the integer n." - if n==0: - return 0 - s = "%x" % n - return ((len(s)-1)*4) +\ - {'0':0, '1':1, '2':2, '3':2, - '4':3, '5':3, '6':3, '7':3, - '8':4, '9':4, 'a':4, 'b':4, - 'c':4, 'd':4, 'e':4, 'f':4, - }[s[0]] diff --git a/tack/crypto/ECGenerator.py b/tack/crypto/ECGenerator.py new file mode 100644 index 0000000..8b4d997 --- /dev/null +++ b/tack/crypto/ECGenerator.py @@ -0,0 +1,46 @@ +from M2Crypto import EC, BIO +from tack.crypto.ASN1 import ASN1Parser, fromAsn1IntBytes +from tack.crypto.ECPrivateKey import ECPrivateKey +from tack.crypto.ECPublicKey import ECPublicKey +from tack.util.PEMDecoder import PEMDecoder + +class ECGenerator: + + def generateECKeyPair(self): + # Generate M2Crypto.EC.EC object + ec = EC.gen_params(EC.NID_X9_62_prime256v1) + ec.gen_key() + + rawPrivateKey, rawPublicKey = self._constructRawKeysFromEc(ec) + + return ECPublicKey(rawPublicKey, ec), ECPrivateKey(rawPrivateKey, rawPublicKey, ec) + + def _constructRawKeysFromEc(self, ec): + derEncodedKeys = self._getDerEncodedKeysFromEc(ec) + parser = ASN1Parser(derEncodedKeys) + + # The private key is stored as an ASN.1 integer which may + # need to have zero padding removed (if 33 bytes) or added + # (if < 32 bytes): + rawPrivateKey = parser.getChild(1).value + rawPrivateKey = fromAsn1IntBytes(rawPrivateKey, 32) + + # There is a 00 04 byte prior to the 64-byte public key + # I'm not sure why M2Crypto has the 00 byte there?, + # some ASN1 thing - the 04 byte signals "uncompressed" + # per SECG. Anyways, strip both those bytes off ([2:]) + rawPublicKey = parser.getChild(3).getTagged().value[2:] + + assert(len(rawPrivateKey) == 32) + assert(len(rawPublicKey) == 64) + + return rawPrivateKey, rawPublicKey + + def _getDerEncodedKeysFromEc(self, ec): + # Get the ASN.1 ECPrivateKey for the object + bio = BIO.MemoryBuffer() + ec.save_key_bio(bio, cipher=None) + pemEncodedKeys = bio.getvalue() + + return PEMDecoder(pemEncodedKeys).getDecoded("EC PRIVATE KEY") + diff --git a/tack/crypto/ECPrivateKey.py b/tack/crypto/ECPrivateKey.py new file mode 100644 index 0000000..6f439e9 --- /dev/null +++ b/tack/crypto/ECPrivateKey.py @@ -0,0 +1,98 @@ +from M2Crypto import EC, BIO +import math +from tack.compat import a2b_hex +from tack.crypto.ASN1 import toAsn1IntBytes, asn1Length, ASN1Parser +from tack.crypto.Digest import Digest +from tack.crypto.ECPublicKey import ECPublicKey +from tack.util.PEMEncoder import PEMEncoder + +class ECPrivateKey: + + def __init__(self, rawPrivateKey, rawPublicKey, ec=None): + assert(rawPrivateKey is not None and rawPublicKey is not None) + self.ec = ec + self.rawPrivateKey = rawPrivateKey + self.rawPublicKey = rawPublicKey + + if not self.ec: + self.ec = self._constructEcFromRawKeys(rawPrivateKey, rawPublicKey) + + def getSignature(self, data): + # Produce ASN.1 signature + hash = Digest.SHA256(data) + asn1SigBytes = self.ec.sign_dsa_asn1(hash) + + # Convert stupid ASN.1 signature into 64-byte signature + # Double-check before returning + sigBytes = self._convertToRawSignature(asn1SigBytes) + + assert(ECPublicKey(self.rawPublicKey, self.ec).verify(data, sigBytes)) + return sigBytes + + def getRawKey(self): + return self.rawPrivateKey + + def _constructEcFromRawKeys(self, rawPrivateKey, rawPublicKey): + assert(len(rawPrivateKey) == 32) + assert(len(rawPublicKey) == 64) + bytes1 = a2b_hex("02010104") + bytes2 = a2b_hex("a00a06082a8648ce3d030107a14403420004") + rawPrivateKey = toAsn1IntBytes(rawPrivateKey) + b = bytes1 + asn1Length(len(rawPrivateKey)) + rawPrivateKey +\ + bytes2 + rawPublicKey + b = bytearray([0x30]) + asn1Length(len(b)) + b + pemPrivKeyBytes = PEMEncoder(b).getEncoded("EC PRIVATE KEY") + + return EC.load_key_bio(BIO.MemoryBuffer(pemPrivKeyBytes)) + + + def _convertToRawSignature(self, signature): + parser = ASN1Parser(bytearray(signature)) + r = self._bytesToNumber(parser.getChild(0).value) + s = self._bytesToNumber(parser.getChild(1).value) + return self._numberToBytes(r, 32) + self._numberToBytes(s, 32) + + + def _bytesToNumber(self, bytes): + "Convert a sequence of bytes (eg bytearray) into integer." + total = 0 + multiplier = 1 + for count in range(len(bytes)-1, -1, -1): + byte = bytes[count] + total += multiplier * byte + multiplier *= 256 + return total + + def _numberToBytes(self, n, howManyBytes=None): + """Convert an integer into a bytearray, zero-pad to howManyBytes. + + The returned bytearray may be smaller than howManyBytes, but will + not be larger. The returned bytearray will contain a big-endian + encoding of the input integer (n). + """ + if not howManyBytes: + howManyBytes = self._numBytes(n) + bytes = bytearray(howManyBytes) + for count in range(howManyBytes-1, -1, -1): + bytes[count] = int(n % 256) + n >>= 8 + return bytes + + def _numBytes(self, n): + "Return the number of bytes needed to represent the integer n." + if not n: + return 0 + bits = self._numBits(n) + return int(math.ceil(bits / 8.0)) + + def _numBits(self, n): + "Return the number of bits needed to represent the integer n." + if not n: + return 0 + s = "%x" % n + return ((len(s)-1)*4) +\ + {'0':0, '1':1, '2':2, '3':2, + '4':3, '5':3, '6':3, '7':3, + '8':4, '9':4, 'a':4, 'b':4, + 'c':4, 'd':4, 'e':4, 'f':4, + }[s[0]] diff --git a/tack/crypto/ECPublicKey.py b/tack/crypto/ECPublicKey.py new file mode 100644 index 0000000..0579f39 --- /dev/null +++ b/tack/crypto/ECPublicKey.py @@ -0,0 +1,55 @@ +from M2Crypto import EC, BIO +from tack.compat import a2b_hex +from tack.compat import b2a_base32 +from tack.crypto.ASN1 import toAsn1IntBytes, asn1Length +from tack.crypto.Digest import Digest +from tack.util.PEMEncoder import PEMEncoder + +class ECPublicKey: + + def __init__(self, rawPublicKey, ec=None): + assert(rawPublicKey is not None) + self.ec = ec + self.rawPublicKey = rawPublicKey + + if not self.ec: + self.ec = self._constructEcFromRawKey(self.rawPublicKey) + + def verify(self, data, signature): + # Convert 64-byte signature into a stupid ASN.1 signature + asn1SigBytes = self._convertToAsn1Signature(signature) + hash = Digest.SHA256(data) + + return self.ec.verify_dsa_asn1(hash, asn1SigBytes) + + def getRawKey(self): + return self.rawPublicKey + + def getFingerprint(self): + digest = Digest.SHA256(self.rawPublicKey) + assert(len(digest) == 32) + s = b2a_base32(digest).lower()[:25] + return "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) + + def _constructEcFromRawKey(self, rawPublicKey): + assert(len(rawPublicKey) == 64) + bytes1 = a2b_hex("3059301306072a8648ce3d020106082a8648ce3d03010703420004") + asn1KeyBytes = bytes1 + rawPublicKey + pemPubKeyBytes = PEMEncoder(asn1KeyBytes).getEncoded("PUBLIC KEY") + + return EC.load_pub_key_bio(BIO.MemoryBuffer(pemPubKeyBytes)) + + def _convertToAsn1Signature(self, signature): + assert(len(signature) == 64) + asn1R = toAsn1IntBytes(signature[:32]) + asn1S = toAsn1IntBytes(signature[32:]) + # Add ASN1 Type=2(int), and Length fields + asn1R = bytearray([2]) + asn1Length(len(asn1R)) + asn1R + asn1S = bytearray([2]) + asn1Length(len(asn1S)) + asn1S + # Add ASN1 Type=0x30(Sequence) and Length fields + asn1ECSigBytes = bytearray([0x30]) +\ + asn1Length(len(asn1R+asn1S)) + asn1R + asn1S + return asn1ECSigBytes + + def __str__(self): + return self.getFingerprint() \ No newline at end of file diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index a3536f7..9d7055c 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -1,5 +1,4 @@ -from tack.crypto.ECDSA import ecdsa256Verify -from tack.structures.TackId import TackId +from tack.crypto.ECPublicKey import ECPublicKey from tack.tls.TlsStructure import TlsStructure from tack.tls.TlsStructureWriter import TlsStructureWriter from tack.util.Util import Util @@ -13,7 +12,7 @@ class Tack(TlsStructure): def __init__(self, data=None): TlsStructure.__init__(self, data) if data is not None: - self.public_key = self.getBytes(64) + self.public_key = ECPublicKey(self.getBytes(64)) self.min_generation = self.getInt(1) self.generation = self.getInt(1) self.expiration = self.getInt(4) @@ -36,7 +35,7 @@ def createFromPem(cls, pem): @classmethod def createFromParameters(cls, public_key, private_key, min_generation, generation, expiration, target_hash): - assert(len(public_key) == 64) + assert(len(public_key.getRawKey()) == 64) assert(0 <= min_generation <= 255) assert(0 <= generation <= 255 and generation >= min_generation) @@ -54,7 +53,7 @@ def createFromParameters(cls, public_key, private_key, min_generation, generatio return tack def getTackId(self): - return str(TackId(self.public_key)) + return str(self.public_key) def serialize(self): writer = TlsStructureWriter(64) @@ -66,7 +65,7 @@ def serializeAsPem(self): def _serializePrelude(self): writer = TlsStructureWriter(Tack.LENGTH - 64) - writer.add(self.public_key, 64) + writer.add(self.public_key.getRawKey(), 64) writer.add(self.min_generation, 1) writer.add(self.generation, 1) writer.add(self.expiration, 4) @@ -78,7 +77,7 @@ def _getDataToSign(self): def _verifySignature(self): bytesToVerify = self._getDataToSign() - return ecdsa256Verify(self.public_key, bytesToVerify, self.signature) + return self.public_key.verify(bytesToVerify, self.signature) def __str__(self): """Return a readable string describing this TACK. diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index 077a14e..fb9a4da 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -1,5 +1,4 @@ -from tack.crypto.ECDSA import ecdsa256Verify -from tack.structures.TackId import TackId +from tack.crypto.ECPublicKey import ECPublicKey from tack.tls.TlsStructure import TlsStructure from tack.tls.TlsStructureWriter import TlsStructureWriter from tack.util.PEMDecoder import PEMDecoder @@ -15,7 +14,7 @@ def __init__(self, data=None): raise SyntaxError("Break signature is the wrong size. Is %s and should be %s." % (len(data), TackBreakSig.LENGTH)) if data is not None: - self.public_key = self.getBytes(64) + self.public_key = ECPublicKey(self.getBytes(64)) self.signature = self.getBytes(64) if not self._verifySignature(): @@ -53,7 +52,7 @@ def createFromParameters(cls, public_key, private_key): def serialize(self): """Return a bytearray containing the TACK_Break_Sig.""" w = TlsStructureWriter(TackBreakSig.LENGTH) - w.add(self.public_key, 64) + w.add(self.public_key.getRawKey(), 64) w.add(self.signature, 64) assert(w.index == len(w.bytes)) return w.getBytes() @@ -62,10 +61,10 @@ def serializeAsPem(self): return PEMEncoder(self.serialize()).getEncoded("TACK BREAK SIG") def getTackId(self): - return str(TackId(self.public_key)) + return str(self.public_key) def _verifySignature(self): - return ecdsa256Verify(self.public_key, bytearray("tack_break_sig", "ascii"), self.signature) + return self.public_key.verify(bytearray("tack_break_sig"), self.signature) def __str__(self): """Return a readable string describing this TACK_Break_Sig. diff --git a/tack/structures/TackId.py b/tack/structures/TackId.py deleted file mode 100644 index ae519a2..0000000 --- a/tack/structures/TackId.py +++ /dev/null @@ -1,18 +0,0 @@ -from tack.compat import b2a_base32 -from tack.crypto.Digest import Digest - -class TackId(): - - def __init__(self, public_key): - self.id = self._hashToTackId(Digest.SHA256(public_key)) - - def getId(self): - return self.id - - def _hashToTackId(self, b): - assert(len(b) == 32) - s = b2a_base32(b).lower()[:25] - return "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) - - def __str__(self): - return self.id \ No newline at end of file diff --git a/tack/structures/TackKeyFile.py b/tack/structures/TackKeyFile.py index 5a6d633..56a6e30 100644 --- a/tack/structures/TackKeyFile.py +++ b/tack/structures/TackKeyFile.py @@ -22,11 +22,12 @@ """ import os from tack.InvalidPasswordException import InvalidPasswordException +from tack.crypto.ECGenerator import ECGenerator +from tack.crypto.ECPrivateKey import ECPrivateKey +from tack.crypto.ECPublicKey import ECPublicKey from tack.crypto.PBKDF2 import PBKDF2 from tack.crypto.AES import AES from tack.crypto.Digest import Digest -from tack.crypto.ECDSA import ec256Generate, ecdsa256Sign -from tack.structures.TackId import TackId from tack.tls.TlsStructure import TlsStructure from tack.tls.TlsStructureWriter import TlsStructureWriter from tack.util.Util import Util @@ -48,12 +49,13 @@ def __init__(self, data=None, password=None): self.iter_count = self.getInt(4) self.salt = self.getBytes(16) self.ciphertext = self.getBytes(32) - self.public_key = self.getBytes(64) + self.public_key = ECPublicKey(self.getBytes(64)) self.mac = bytearray(self.getBytes(32)) if self.password is not None: - self.private_key = self._decryptKey(password, self.salt, self.ciphertext, - self.iter_count, self.public_key, self.mac) + rawPrivateKey = self._decryptKey(password, self.salt, self.ciphertext, + self.iter_count, self.public_key, self.mac) + self.private_key = ECPrivateKey(rawPrivateKey, self.public_key.getRawKey()) @classmethod def createRandom(cls, password): @@ -62,11 +64,11 @@ def createRandom(cls, password): tackKeyFile.version = 1 tackKeyFile.iter_count = 8192 tackKeyFile.salt = bytearray(os.urandom(16)) - tackKeyFile.private_key, tackKeyFile.public_key = ec256Generate() - tackKeyFile.ciphertext, tackKeyFile.mac = tackKeyFile._encryptKey(password, tackKeyFile.salt, - tackKeyFile.iter_count, - tackKeyFile.public_key, - tackKeyFile.private_key) + tackKeyFile.public_key, tackKeyFile.private_key = ECGenerator().generateECKeyPair() + tackKeyFile.ciphertext, tackKeyFile.mac = tackKeyFile._encryptKey(password, tackKeyFile.salt, + tackKeyFile.iter_count, + tackKeyFile.public_key, + tackKeyFile.private_key) return tackKeyFile @classmethod @@ -76,8 +78,8 @@ def createFromPem(cls, pem, password): def getPublicKey(self): return self.public_key - def getSignature(self, data): - return ecdsa256Sign(self.private_key, self.public_key, data) + def getPrivateKey(self): + return self.private_key def serialize(self): w = TlsStructureWriter(TackKeyFile.LENGTH) @@ -85,7 +87,7 @@ def serialize(self): w.add(self.iter_count, 4) w.add(self.salt, 16) w.add(self.ciphertext, 32) - w.add(self.public_key, 64) + w.add(self.public_key.getRawKey(), 64) w.add(self.mac, 32) assert(w.index == len(w.bytes)) # did we fill entire bytearray? @@ -96,15 +98,15 @@ def serializeAsPem(self): def _encryptKey(self, password, salt, iter_count, public_key, private_key): encKey, authKey = self._deriveKeys(password, salt, iter_count) - ciphertext = AES(encKey, bytearray(16)).encrypt(private_key) - macData = ciphertext + public_key + ciphertext = AES(encKey, bytearray(16)).encrypt(private_key.getRawKey()) + macData = ciphertext + public_key.getRawKey() mac = Digest.HMAC_SHA256(authKey, macData) return ciphertext, mac def _decryptKey(self, password, salt, ciphertext, iter_count, public_key, mac): encKey, authKey = self._deriveKeys(password, salt, iter_count) - macData = ciphertext + public_key + macData = ciphertext + public_key.getRawKey() calcMac = Digest.HMAC_SHA256(authKey, macData) if not Util.constTimeCompare(calcMac, mac): @@ -121,5 +123,5 @@ def _deriveKeys(self, password, salt, iter_count): return encKey, authKey def __str__(self): - return """TACK ID = %s\n""" % str(TackId(self.public_key)) + return """TACK ID = %s\n""" % str(self.public_key) diff --git a/tack/tls/TlsStructure.py b/tack/tls/TlsStructure.py index 684803f..e492319 100644 --- a/tack/tls/TlsStructure.py +++ b/tack/tls/TlsStructure.py @@ -7,7 +7,8 @@ def __init__(self, bytes): def getInt(self, elementLength): """Reads an integer of 'length' bytes""" if self.index + elementLength > len(self.bytes): - raise SyntaxError("Reading %s at index %s but only %s bytes remaining." % (elementLength, self.index, len(self.bytes))) + raise SyntaxError("Reading %s at index %s but only %s bytes remaining." \ + % (elementLength, self.index, len(self.bytes))) x = 0 for count in range(elementLength): x <<= 8 diff --git a/tests/CryptoTest.py b/tests/CryptoTest.py index a492a41..c45ed8f 100644 --- a/tests/CryptoTest.py +++ b/tests/CryptoTest.py @@ -1,9 +1,8 @@ import unittest -from TACKpy.ecdsa_wrappers import ecdsa256Verify from tack.compat import a2b_hex from tack.crypto.AES import AES from tack.crypto.ASN1 import asn1Length, toAsn1IntBytes, fromAsn1IntBytes -from tack.crypto.ECDSA import ec256Generate, ecdsa256Sign +from tack.crypto.ECGenerator import ECGenerator class CryptoTest(unittest.TestCase): @@ -17,12 +16,13 @@ def test_AES(self): assert(AES(key, IV).decrypt(ciphertext) == plaintext) def test_ECDSA(self): - privateKey, publicKey = ec256Generate() + publicKey, privateKey = ECGenerator().generateECKeyPair() data = bytearray([0,1,2,3]) badData = bytearray([0,1,2,4]) - signature = ecdsa256Sign(privateKey, publicKey, data) - assert(ecdsa256Verify(publicKey, data, signature)) - assert(not ecdsa256Verify(publicKey, badData, signature)) + + signature = privateKey.getSignature(data) + assert(publicKey.verify(data, signature)) + assert(not publicKey.verify(badData, signature)) def test_ASN1(self): assert(asn1Length(7) == bytearray([7])) diff --git a/tests/StructuresTest.py b/tests/StructuresTest.py index 0e4536c..191825b 100644 --- a/tests/StructuresTest.py +++ b/tests/StructuresTest.py @@ -18,7 +18,7 @@ def test_Tack(self): t = Tack().createFromPem(s) - assert(t.public_key == a2b_hex("4c09ac019229cd1f8c63042bb2e8" + assert(t.public_key.getRawKey() == a2b_hex("4c09ac019229cd1f8c63042bb2e8" "cb85eb2fa6eddd45ce513a17e0c9" "2a94564535a7585d5e8f8fc10ae6" "690a3d07dfa885e711ad6125877c" @@ -84,14 +84,14 @@ def test_KeyFile(self): privateKey = a2b_hex("fc815de8b1de13a436e9cd69742cbf2c" "d4c1c9bb33e023401d9291cf2781b754") kf = TackKeyFile.createFromPem(s, "asdf") - assert(kf.public_key == publicKey) - assert(kf.private_key == privateKey) + assert(kf.getPublicKey().getRawKey() == publicKey) + assert(kf.getPrivateKey().getRawKey() == privateKey) kf2 = TackKeyFile.createFromPem(kf.serializeAsPem(), "asdf") - assert(kf2.public_key == publicKey) - assert(kf2.private_key == privateKey) + assert(kf2.getPublicKey().getRawKey() == publicKey) + assert(kf2.getPrivateKey().getRawKey() == privateKey) kf3 = TackKeyFile.createRandom("123") kf4 = TackKeyFile.createFromPem(kf3.serializeAsPem(), "123") - assert(kf3.public_key == kf4.public_key) + assert(kf3.getPublicKey().getRawKey() == kf4.getPublicKey().getRawKey()) if __name__ == '__main__': unittest.main() \ No newline at end of file From 67afde842ad8c151a5a92608fe516e42dfbde222 Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 11 May 2012 19:59:02 +0300 Subject: [PATCH 12/94] Fixed "make test" bugs. --- tack/commands/CertificateCommand.py | 2 +- tack/commands/Command.py | 3 +++ tack/commands/SignCommand.py | 9 +++++++-- tack/structures/TackBreakSig.py | 1 - tack/tls/TlsCertificate.py | 4 ++-- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/tack/commands/CertificateCommand.py b/tack/commands/CertificateCommand.py index bba8fde..0699922 100644 --- a/tack/commands/CertificateCommand.py +++ b/tack/commands/CertificateCommand.py @@ -57,7 +57,7 @@ def _getBreakSignatures(self): contents = open(fileName, "r").read() - return TackBreakSig.createFromPem(contents) + return TackBreakSig.createFromPemList(contents) def _getInputTack(self): contents = self._getInputFileContents() diff --git a/tack/commands/Command.py b/tack/commands/Command.py index f72b804..3b98354 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -63,6 +63,9 @@ def getOutputFile(self): except IOError: self.printError("Error opening output file: %s" % output) + def getOutputFileName(self): + return self._getOptionValue("-o") + def addPemComments(self, inStr): """Add pre-PEM metadata/comments to PEM strings.""" versionStr = __version__ diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index 61e99f0..f8abb48 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -12,7 +12,6 @@ def __init__(self, argv): Command.__init__(self, argv, "kcopmgen", "v") self.password = self.getPassword() - self.outputFile, self.outputFileName = self.getOutputFile() self.key = self.getKey(self.password) self.certificate = self._getCertificate() @@ -20,10 +19,16 @@ def __init__(self, argv): self.min_generation = self._getMinGeneration() self.expiration = self._getExpiration() self.numArg = self._getTackCount() + # If -n, then -o is a filename prefix only, so is not opened + if self.numArg: + self.outputFileName = self.getOutputFileName() + return + self.outputFile, self.outputFileName = self.getOutputFile() def execute(self): if not self.numArg: + tack = Tack.createFromParameters(self.key.getPublicKey(), self.key.getPrivateKey(), self.min_generation, self.generation, self.expiration, self.certificate.key_sha256) @@ -38,7 +43,7 @@ def execute(self): self.printError("-o required with -n") for x in range(numTacks): - tack = Tack.createFromParameters(self.key.getPublicKey(), self.key, self.min_generation, + tack = Tack.createFromParameters(self.key.getPublicKey(), self.key.getPrivateKey(), self.min_generation, self.generation, self.expiration, self.certificate.key_sha256) outputFile = open(self.outputFileName + "_%04d.pem" % x, "w") diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index fb9a4da..0e86a9b 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -42,7 +42,6 @@ def createFromPemList(cls, data): @classmethod def createFromParameters(cls, public_key, private_key): - assert(len(public_key) == 64) tackBreakSig = cls() tackBreakSig.public_key = public_key tackBreakSig.signature = private_key.getSignature(bytearray("tack_break_sig", "ascii")) diff --git a/tack/tls/TlsCertificate.py b/tack/tls/TlsCertificate.py index d86efd5..4310910 100644 --- a/tack/tls/TlsCertificate.py +++ b/tack/tls/TlsCertificate.py @@ -175,8 +175,8 @@ def writePem(self): def writeText(self): s =\ """key_sha256 = 0x%s - notAfter = %s - """ % (\ +notAfter = %s +""" % (\ Util.writeBytes(self.key_sha256), Time.posixTimeToStr(self.notAfter, True)) if self.tackExt: From dad6c0dafef8d11332fc45b3212c3137243bb3cd Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 11 May 2012 20:48:34 +0300 Subject: [PATCH 13/94] Fleshing out setup.py list of packages. --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index cf69def..a1f1dfb 100755 --- a/setup.py +++ b/setup.py @@ -17,7 +17,8 @@ description="TACKpy implements TACK in python", license="public domain", scripts=["tack/tack"], - packages=["tack"], + packages=["tack", "tack/commands", "tack/crypto", "tack/structures", + "tack/tls", "tack/util"], install_requires=['M2Crypto']) print "Cleaning up..." From 57ed4a245bd361b51b0512cbae1af7cb7637a66d Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 11 May 2012 20:51:07 +0300 Subject: [PATCH 14/94] Added Makefile with default, clean, install, test. --- Makefile | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..afca253 --- /dev/null +++ b/Makefile @@ -0,0 +1,52 @@ + +TESTDIR = testoutput + +.PHONY : default +default: + @echo To install tackpy run \"./setup.py install\" or \"make install\" + +.PHONY: install +install: + ./setup.py install + +.PHONY : clean +clean: + rm -f `find . -name *.pyc` + rm -rf build + rm -rf $(TESTDIR) + +# Variables for testing +TESTDIR = testoutput +EXEC = ./tack.py +CERT1 = ./testdata/serverX509Cert.pem +CERT2 = ./testdata/serverX509Cert.der + +.PHONY: test +test: + rm -rf $(TESTDIR) + mkdir $(TESTDIR) + #$(EXEC) test + # NOTE: USE 'asdf' for passwords... + $(EXEC) genkey > $(TESTDIR)/TACK_Key1.pem + $(EXEC) genkey -p asdf > $(TESTDIR)/TACK_Key2.pem + $(EXEC) genkey -p asdf -o $(TESTDIR)/TACK_Key3.pem + $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -c $(CERT1) > $(TESTDIR)/TACK1.pem + $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT2) -o $(TESTDIR)/TACK2.pem + $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) -m2 -o $(TESTDIR)/TACK3.pem + $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) -o $(TESTDIR)/TACK4.pem + $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -e 2030-06-06Z -c $(CERT1) -o $(TESTDIR)/TACK5.pem + $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -g2 -m2 -c $(CERT1) -o $(TESTDIR)/TACK6.pem + $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -m250 -g251 -c $(CERT1) -o $(TESTDIR)/T6 -e 2013-01-02Z -n 3@1d + $(EXEC) break -k $(TESTDIR)/TACK_Key1.pem -p asdf > $(TESTDIR)/TACK_Break_Sig1.pem + $(EXEC) b -k $(TESTDIR)/TACK_Key2.pem -p asdf -o $(TESTDIR)/TACK_Break_Sig2.pem + cat $(TESTDIR)/TACK_Break_Sig1.pem $(TESTDIR)/TACK_Break_Sig2.pem > $(TESTDIR)/TACK_Break_Sigs.pem + $(EXEC) tackcert -i $(TESTDIR)/TACK3.pem > $(TESTDIR)/TACK_Cert3.pem + $(EXEC) tackcert -i $(TESTDIR)/TACK4.pem -b $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_Cert4.pem + $(EXEC) tackcert -i $(TESTDIR)/TACK_Cert3.pem > $(TESTDIR)/TACK3_FromCert.pem + $(EXEC) view $(TESTDIR)/TACK_Key1.pem > $(TESTDIR)/TACK_View_Key1.txt + $(EXEC) view $(TESTDIR)/TACK1.pem > $(TESTDIR)/TACK_View1.txt + $(EXEC) v $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_View_Break_Sigs.txt + $(EXEC) v $(CERT1) > $(TESTDIR)/TACK_View_Cert1.txt + $(EXEC) v $(CERT2) > $(TESTDIR)/TACK_View_Cert2.txt + $(EXEC) v $(TESTDIR)/TACK_Cert3.pem > $(TESTDIR)/TACK_View_TACK_Cert3.txt + @echo OK From dae1e6bcac2ed4cf5c136cd1785f9e6b2427e37a Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 11 May 2012 21:55:48 +0300 Subject: [PATCH 15/94] Renamed createFromParameters() -> create. --- tack/commands/BreakCommand.py | 2 +- tack/commands/CertificateCommand.py | 4 ++-- tack/commands/SignCommand.py | 8 ++++---- tack/structures/Tack.py | 2 +- tack/structures/TackBreakSig.py | 2 +- tack/structures/TackExtension.py | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/tack/commands/BreakCommand.py b/tack/commands/BreakCommand.py index fbc11af..948db73 100644 --- a/tack/commands/BreakCommand.py +++ b/tack/commands/BreakCommand.py @@ -11,7 +11,7 @@ def __init__(self, argv): self.key = self.getKey(self.getPassword()) def execute(self): - breakSig = TackBreakSig.createFromParameters(self.key.getPublicKey(), self.key.getPrivateKey()) + breakSig = TackBreakSig.create(self.key.getPublicKey(), self.key.getPrivateKey()) self.outputFile.write(self.addPemComments(breakSig.serializeAsPem())) if self.isVerbose(): diff --git a/tack/commands/CertificateCommand.py b/tack/commands/CertificateCommand.py index 0699922..c198249 100644 --- a/tack/commands/CertificateCommand.py +++ b/tack/commands/CertificateCommand.py @@ -22,8 +22,8 @@ def __init__(self, argv): def execute(self): if self.inputTack is not None: - tackExtension = TackExtension.createFromParameters(self.inputTack, self.breakSignatures, - TackActivation.DISABLED) + tackExtension = TackExtension.create(self.inputTack, self.breakSignatures, + TackActivation.DISABLED) tlsCertificate = TlsCertificate() tlsCertificate.create(tackExtension) diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index f8abb48..1707855 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -29,8 +29,8 @@ def __init__(self, argv): def execute(self): if not self.numArg: - tack = Tack.createFromParameters(self.key.getPublicKey(), self.key.getPrivateKey(), self.min_generation, - self.generation, self.expiration, self.certificate.key_sha256) + tack = Tack.create(self.key.getPublicKey(), self.key.getPrivateKey(), self.min_generation, + self.generation, self.expiration, self.certificate.key_sha256) self.outputFile.write(self.addPemComments(tack.serializeAsPem())) @@ -43,8 +43,8 @@ def execute(self): self.printError("-o required with -n") for x in range(numTacks): - tack = Tack.createFromParameters(self.key.getPublicKey(), self.key.getPrivateKey(), self.min_generation, - self.generation, self.expiration, self.certificate.key_sha256) + tack = Tack.create(self.key.getPublicKey(), self.key.getPrivateKey(), self.min_generation, + self.generation, self.expiration, self.certificate.key_sha256) outputFile = open(self.outputFileName + "_%04d.pem" % x, "w") outputFile.write(self.addPemComments(tack.serializeAsPem())) diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index 9d7055c..ab7fd0a 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -34,7 +34,7 @@ def createFromPem(cls, pem): return cls(data) @classmethod - def createFromParameters(cls, public_key, private_key, min_generation, generation, expiration, target_hash): + def create(cls, public_key, private_key, min_generation, generation, expiration, target_hash): assert(len(public_key.getRawKey()) == 64) assert(0 <= min_generation <= 255) assert(0 <= generation <= 255 and diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index 0e86a9b..28160df 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -41,7 +41,7 @@ def createFromPemList(cls, data): return breakSigs @classmethod - def createFromParameters(cls, public_key, private_key): + def create(cls, public_key, private_key): tackBreakSig = cls() tackBreakSig.public_key = public_key tackBreakSig.signature = private_key.getSignature(bytearray("tack_break_sig", "ascii")) diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index ad695e6..04a6914 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -20,7 +20,7 @@ def __init__(self, data=None): raise SyntaxError("Excess bytes in TACK_Extension") @classmethod - def createFromParameters(cls, tack, break_sigs, pin_activation): + def create(cls, tack, break_sigs, pin_activation): tackExtension = cls() tackExtension.tack = tack tackExtension.break_sigs = break_sigs From f4bfaf76275fa7a0659c38a5884eadd94d129955 Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 11 May 2012 22:02:01 +0300 Subject: [PATCH 16/94] (merge from TACKpy) Split sig verification out of parsing. --- tack/structures/Tack.py | 5 +++-- tack/structures/TackBreakSig.py | 3 --- tack/structures/TackExtension.py | 9 +++++++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index ab7fd0a..08d5f44 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -19,8 +19,9 @@ def __init__(self, data=None): self.target_hash = self.getBytes(32) self.signature = self.getBytes(64) - if not self._verifySignature(): - raise SyntaxError("Signature verification failure") + if self.generation < self.min_generation: + raise SyntaxError("Generation less than min_generation") + if self.index != len(data): raise SyntaxError("Excess bytes in TACK") diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index 28160df..1279421 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -17,9 +17,6 @@ def __init__(self, data=None): self.public_key = ECPublicKey(self.getBytes(64)) self.signature = self.getBytes(64) - if not self._verifySignature(): - raise SyntaxError("Signature verification failure") - if self.index != len(data): raise SyntaxError("Excess bytes in TACK_Break_Sig") diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index 04a6914..83750a4 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -52,6 +52,15 @@ def serialize(self): def isEmpty(self): return not self.tack and not self.break_sigs + def verifySignatures(self): + if self.tack: + if not self.tack.verifySignature(): + return False + for break_sig in self.break_sigs: + if not break_sig.verifySignature(): + return False + return True + def _getSerializedLength(self): length = 0 if self.tack: From c71d7a223af416bd24ecdcfddc3befdc96bf2153 Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 11 May 2012 22:04:09 +0300 Subject: [PATCH 17/94] (merge from TACKpy) Fix TlsCertificate.matches(). --- tack/tls/TlsCertificate.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/tack/tls/TlsCertificate.py b/tack/tls/TlsCertificate.py index 4310910..0c2076e 100644 --- a/tack/tls/TlsCertificate.py +++ b/tack/tls/TlsCertificate.py @@ -60,9 +60,7 @@ def open(self, filename): self.parse(sslBytes) # SyntaxError def matches(self, tack): - if tack.version == TackVersion.V1: - return self.key_sha256 == tack.sig.target_sha256 - return False + return self.key_sha256 == tack.target_hash def parsePem(self, s): b = PEMDecoder(s).getDecoded("CERTIFICATE") From 61681742a3283670ae9c525f6afbe82d05d5f167 Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 11 May 2012 22:09:56 +0300 Subject: [PATCH 18/94] (merging TACKpy) Fix parsing of TACK_Extension per latest spec. --- tack/structures/TackExtension.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index 83750a4..6cd949e 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -73,11 +73,10 @@ def _getSerializedLength(self): def _parseTack(self): tackLen = self.getInt(1) - - if tackLen != Tack.LENGTH: - raise SyntaxError("TACK wrong size") - - return Tack(self.getBytes(tackLen)) + if tackLen: + if tackLen != Tack.LENGTH: + raise SyntaxError("TACK wrong size") + return Tack(self.getBytes(tackLen)) def _parseBreakSigs(self): sigsLen = self.getInt(2) From 81a075941b97c83f7d5bb74355e75def00af2673 Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 11 May 2012 22:16:23 +0300 Subject: [PATCH 19/94] (merging TACKpy) pin_activation converts input to boolean, more convenient for tlslite. --- tack/structures/TackExtension.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index 6cd949e..a2dd885 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -1,6 +1,7 @@ from tack.structures.Tack import Tack from tack.structures.TackActivation import TackActivation from tack.structures.TackBreakSig import TackBreakSig +from tack.structures.TackActivation import TackActivation from tack.tls.TlsStructure import TlsStructure from tack.tls.TlsStructureWriter import TlsStructureWriter @@ -24,7 +25,10 @@ def create(cls, tack, break_sigs, pin_activation): tackExtension = cls() tackExtension.tack = tack tackExtension.break_sigs = break_sigs - tackExtension.pin_activation = pin_activation + if not pin_activation: + tackExtension.pin_activation = TackActivation.DISABLED + else: + tackExtension.pin_activation = TackActivation.ENABLED return tackExtension From 819d1be18f94c87371a938603e568e4f16db8447 Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 11 May 2012 22:23:30 +0300 Subject: [PATCH 20/94] (merging TACKpy) Clean up length-checks on parsing. --- tack/structures/Tack.py | 11 +++++------ tack/structures/TackBreakSig.py | 6 +++--- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index 08d5f44..3ca04dd 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -11,7 +11,11 @@ class Tack(TlsStructure): def __init__(self, data=None): TlsStructure.__init__(self, data) + if data is not None: + if len(data) != Tack.LENGTH: + raise SyntaxError("TACK is the wrong size. Is %s and should be %s" % (len(data), Tack.LENGTH)) + self.public_key = ECPublicKey(self.getBytes(64)) self.min_generation = self.getInt(1) self.generation = self.getInt(1) @@ -27,12 +31,7 @@ def __init__(self, data=None): @classmethod def createFromPem(cls, pem): - data = PEMDecoder(pem).getDecoded("TACK") - - if len(data) != Tack.LENGTH: - raise SyntaxError("TACK is the wrong size. %s, should be %s" % (len(data), Tack.LENGTH)) - - return cls(data) + return cls(PEMDecoder(pem).getDecoded("TACK")) @classmethod def create(cls, public_key, private_key, min_generation, generation, expiration, target_hash): diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index 1279421..226e3a1 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -10,10 +10,10 @@ class TackBreakSig(TlsStructure): def __init__(self, data=None): TlsStructure.__init__(self, data) - if data is not None and len(data) != TackBreakSig.LENGTH: - raise SyntaxError("Break signature is the wrong size. Is %s and should be %s." % (len(data), TackBreakSig.LENGTH)) - if data is not None: + if len(data) != TackBreakSig.LENGTH: + raise SyntaxError("Break signature is the wrong size. Is %s and should be %s." % (len(data), TackBreakSig.LENGTH)) + self.public_key = ECPublicKey(self.getBytes(64)) self.signature = self.getBytes(64) From 300207d6d3c4bf1ffb229e20c50d0b31070c037c Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 11 May 2012 22:46:18 +0300 Subject: [PATCH 21/94] Cleanup some usage and error strings. --- Makefile | 1 + tack/commands/BreakCommand.py | 16 ++++----- tack/commands/CertificateCommand.py | 4 +-- tack/commands/GenerateKeyCommand.py | 14 ++++---- tack/commands/HelpCommand.py | 9 +++-- tack/commands/SignCommand.py | 51 +++++++++++++++-------------- tack/commands/ViewCommand.py | 8 ++--- tack/structures/TackExtension.py | 6 ++-- 8 files changed, 55 insertions(+), 54 deletions(-) diff --git a/Makefile b/Makefile index afca253..a421f09 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ TESTDIR = testoutput .PHONY : default default: @echo To install tackpy run \"./setup.py install\" or \"make install\" + @echo .PHONY: install install: diff --git a/tack/commands/BreakCommand.py b/tack/commands/BreakCommand.py index 948db73..83296a5 100644 --- a/tack/commands/BreakCommand.py +++ b/tack/commands/BreakCommand.py @@ -20,14 +20,14 @@ def execute(self): @staticmethod def printHelp(): print(\ - """Creates a break signature based on an input TACK key file. +"""Creates a break signature based on an input TACK key file. - break -k KEY + break -k KEY - -k KEY : Use this TACK key file + -k KEY : Use this TACK key file - Optional arguments: - -v : Verbose - -o FILE : Write the output to this file (instead of stdout) - -p PASSWORD : Use this TACK key password instead of prompting - """) +Optional arguments: + -v : Verbose + -o FILE : Write the output to this file (instead of stdout) + -p PASSWORD : Use this TACK key password instead of prompting +""") diff --git a/tack/commands/CertificateCommand.py b/tack/commands/CertificateCommand.py index c198249..673db1b 100644 --- a/tack/commands/CertificateCommand.py +++ b/tack/commands/CertificateCommand.py @@ -92,7 +92,7 @@ def _getInputFileContents(self): @staticmethod def printHelp(): print(\ - """Creates a TACK certificate with the input TACK and optional Break Sigs. +"""Creates a TACK certificate with the input TACK and optional Break Sigs. (Alternatively, if input is a TACK certificate, writes out the TACK and/or Break Signatures as PEM files). @@ -103,4 +103,4 @@ def printHelp(): -v : Verbose -b BREAKSIGS : Include Break Signatures from this file. -o FILE : Write the output to this file (instead of stdout) - """) +""") diff --git a/tack/commands/GenerateKeyCommand.py b/tack/commands/GenerateKeyCommand.py index ae2a336..ca57540 100644 --- a/tack/commands/GenerateKeyCommand.py +++ b/tack/commands/GenerateKeyCommand.py @@ -35,12 +35,12 @@ def _getPassword(self): @staticmethod def printHelp(): print(\ - """Creates a new TACK key file. +"""Creates a new TACK key file. - genkey + genkey - Optional arguments: - -v : Verbose - -o FILE : Write the output to this file (instead of stdout) - -p PASSWORD : Use this TACK key password instead of prompting - """) +Optional arguments: + -v : Verbose + -o FILE : Write the output to this file (instead of stdout) + -p PASSWORD : Use this TACK key password instead of prompting +""") diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index f97c6be..16646aa 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -30,22 +30,21 @@ def execute(self): @staticmethod def printHelp(): print(\ - """Provides help for individual commands. +"""Provides help for individual commands. - help - """) +help +""") @staticmethod def printGeneralUsage(message=None): print "Error: %s" % message print(\ - """\ntack.py version %s +"""\ntack.py version %s Commands (use "help " to see optional args): genkey sign -k KEY -c CERT break -k KEY - tackcert -i TACK view FILE help COMMAND """ % __version__) diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index 1707855..34792bd 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -66,9 +66,9 @@ def _getCertificate(self): inCert.open(certificateFile) return inCert except SyntaxError: - self.printError("SSL certificate malformed: %s" % certificateFile) + self.printError("Certificate malformed: %s" % certificateFile) except IOError: - self.printError("Error opening SSL certificate: %s" % certificateFile) + self.printError("Error opening certificate: %s" % certificateFile) def _getExpiration(self): expiration = self._getOptionValue("-e") @@ -134,27 +134,28 @@ def _getMinGeneration(self): def printHelp(): s = Time.posixTimeToStr(time.time()) print(\ - """Creates a TACK based on a target SSL certificate. - - sign -k KEY -c CERT - - -k KEY : Use this TACK key file - -c CERT : Sign this SSL certificate's public key - - Optional arguments: - -v : Verbose - -o FILE : Write the output to this file (instead of stdout) - -p PASSWORD : Use this TACK key password instead of prompting - -m MIN_GENERATION : Use this min_generation number (0-255) - -g GENERATION : Use this generation number (0-255) - -e EXPIRATION : Use this UTC time for expiration - ("%s", "%sZ", - "%sZ", "%sZ" etc.) - Or, specify a duration from current time: - ("5m", "30d", "1d12h5m", "0m", etc.) - - n NUM@INTERVAL : Generate NUM TACKs, with expiration times spaced - out by INTERVAL (see -d for INTERVAL syntax). The - -o argument is used as a filename prefix, and the - -e argument is used as the first expiration time. - """ % (s, s[:13], s[:10], s[:4])) +"""Creates a TACK based on a target certificate. + + sign -k KEY -c CERT + + -k KEY : Use this TACK key file + -c CERT : Sign this certificate's public key + +Optional arguments: + -v : Verbose + -o FILE : Write the output to this file (instead of stdout) + -p PASSWORD : Use this TACK key password instead of prompting + -m MIN_GENERATION : Use this min_generation number (0-255) + -g GENERATION : Use this generation number (0-255) + -e EXPIRATION : Use this UTC time for expiration + ("%s", "%sZ", + "%sZ", "%sZ" etc.) + Or, specify a delta from current time: + ("5m", "30d", "1d12h5m", "0m", etc.) + If not specified, the certificate's notAfter is used. + - n NUM@INTERVAL : Generate NUM TACKs, with expiration times spaced + out by INTERVAL (see -e for delta syntax). The + -o argument is used as a filename prefix, and the + -e argument is used as the first expiration time. +""" % (s, s[:13], s[:10], s[:4])) diff --git a/tack/commands/ViewCommand.py b/tack/commands/ViewCommand.py index fc14d12..4002ee6 100644 --- a/tack/commands/ViewCommand.py +++ b/tack/commands/ViewCommand.py @@ -61,7 +61,7 @@ def execute(self): sslc.parsePem(text) print(sslc.writeText()) return - # Is it an SSL certificate? + # Is it a certificate? try: sslc = TlsCertificate() sslc.parse(binary) @@ -74,7 +74,7 @@ def execute(self): @staticmethod def printHelp(): print(\ - """Views a TACK, TACK Key, TACK Break Sig, or SSL certificate. +"""Views a TACK, TACK Key, TACK Break Sig, or certificate. - view - """) +view +""") diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index a2dd885..9f427de 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -79,16 +79,16 @@ def _parseTack(self): tackLen = self.getInt(1) if tackLen: if tackLen != Tack.LENGTH: - raise SyntaxError("TACK wrong size") + raise SyntaxError("TACK wrong size: %d" % tackLen) return Tack(self.getBytes(tackLen)) def _parseBreakSigs(self): sigsLen = self.getInt(2) if sigsLen > 1024: - raise SyntaxError("break_sigs too large") + raise SyntaxError("break_sigs too large: %d" % sigsLen) elif sigsLen % TackBreakSig.LENGTH != 0: - raise SyntaxError("break_sigs wrong size") + raise SyntaxError("break_sigs wrong size: %d" % sigsLen) break_sigs = [] b2 = self.getBytes(sigsLen) From d3d2cca5b672e295af769a66cbe8eb4a9ba07e9c Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 11 May 2012 23:04:58 +0300 Subject: [PATCH 22/94] Trivial changes. --- tack/commands/BreakCommand.py | 2 +- tack/commands/CertificateCommand.py | 2 +- tack/commands/Command.py | 2 +- tack/commands/GenerateKeyCommand.py | 2 +- tack/commands/HelpCommand.py | 4 ++-- tack/commands/SignCommand.py | 2 +- tack/commands/ViewCommand.py | 2 +- 7 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tack/commands/BreakCommand.py b/tack/commands/BreakCommand.py index 83296a5..a2963f3 100644 --- a/tack/commands/BreakCommand.py +++ b/tack/commands/BreakCommand.py @@ -19,7 +19,7 @@ def execute(self): @staticmethod def printHelp(): - print(\ + print( """Creates a break signature based on an input TACK key file. break -k KEY diff --git a/tack/commands/CertificateCommand.py b/tack/commands/CertificateCommand.py index 673db1b..ab4329a 100644 --- a/tack/commands/CertificateCommand.py +++ b/tack/commands/CertificateCommand.py @@ -91,7 +91,7 @@ def _getInputFileContents(self): @staticmethod def printHelp(): - print(\ + print( """Creates a TACK certificate with the input TACK and optional Break Sigs. (Alternatively, if input is a TACK certificate, writes out the TACK and/or diff --git a/tack/commands/Command.py b/tack/commands/Command.py index 3b98354..556fa80 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -40,7 +40,7 @@ def getKey(self, password): try: inKey = TackKeyFile.createFromPem(keyPemData, password) return inKey - except InvalidPasswordException, ipe: + except InvalidPasswordException as ipe: sys.stderr.write("Password incorrect!\n") password = self._promptPassword() except SyntaxError: diff --git a/tack/commands/GenerateKeyCommand.py b/tack/commands/GenerateKeyCommand.py index ca57540..5778d07 100644 --- a/tack/commands/GenerateKeyCommand.py +++ b/tack/commands/GenerateKeyCommand.py @@ -34,7 +34,7 @@ def _getPassword(self): @staticmethod def printHelp(): - print(\ + print( """Creates a new TACK key file. genkey diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index 16646aa..5fd7bc8 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -29,7 +29,7 @@ def execute(self): @staticmethod def printHelp(): - print(\ + print( """Provides help for individual commands. help @@ -38,7 +38,7 @@ def printHelp(): @staticmethod def printGeneralUsage(message=None): print "Error: %s" % message - print(\ + print( """\ntack.py version %s Commands (use "help " to see optional args): diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index 34792bd..95acbec 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -133,7 +133,7 @@ def _getMinGeneration(self): @staticmethod def printHelp(): s = Time.posixTimeToStr(time.time()) - print(\ + print( """Creates a TACK based on a target certificate. sign -k KEY -c CERT diff --git a/tack/commands/ViewCommand.py b/tack/commands/ViewCommand.py index 4002ee6..a8d0658 100644 --- a/tack/commands/ViewCommand.py +++ b/tack/commands/ViewCommand.py @@ -73,7 +73,7 @@ def execute(self): @staticmethod def printHelp(): - print(\ + print( """Views a TACK, TACK Key, TACK Break Sig, or certificate. view From 95e8c592233dab98c11789f1df23a3f8f707fefe Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 11 May 2012 23:30:13 +0300 Subject: [PATCH 23/94] Various renamings, KeyFile cleanup, generate key outside Keyfile.create. getSignature -> sign. TackCount -> NumArg Key -> KeyFile. --- tack/commands/BreakCommand.py | 4 +-- tack/commands/Command.py | 2 +- tack/commands/GenerateKeyCommand.py | 4 ++- tack/commands/SignCommand.py | 20 +++++++------- tack/crypto/ECPrivateKey.py | 2 +- tack/structures/Tack.py | 2 +- tack/structures/TackBreakSig.py | 2 +- tack/structures/TackKeyFile.py | 43 ++++++++++++----------------- 8 files changed, 37 insertions(+), 42 deletions(-) diff --git a/tack/commands/BreakCommand.py b/tack/commands/BreakCommand.py index a2963f3..8c08b0a 100644 --- a/tack/commands/BreakCommand.py +++ b/tack/commands/BreakCommand.py @@ -8,10 +8,10 @@ def __init__(self, argv): Command.__init__(self, argv, "pok", "v") self.password = self.getPassword() self.outputFile, self.outputFileName = self.getOutputFile() - self.key = self.getKey(self.getPassword()) + self.keyfile = self.getKeyFile(self.getPassword()) def execute(self): - breakSig = TackBreakSig.create(self.key.getPublicKey(), self.key.getPrivateKey()) + breakSig = TackBreakSig.create(self.keyfile.getPublicKey(), self.keyfile.getPrivateKey()) self.outputFile.write(self.addPemComments(breakSig.serializeAsPem())) if self.isVerbose(): diff --git a/tack/commands/Command.py b/tack/commands/Command.py index 556fa80..2c81fc9 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -24,7 +24,7 @@ def isVerbose(self): def getPassword(self): return self._getOptionValue("-p") - def getKey(self, password): + def getKeyFile(self, password): keyPemFile = self._getOptionValue("-k") if not keyPemFile: diff --git a/tack/commands/GenerateKeyCommand.py b/tack/commands/GenerateKeyCommand.py index 5778d07..f1aaee4 100644 --- a/tack/commands/GenerateKeyCommand.py +++ b/tack/commands/GenerateKeyCommand.py @@ -1,6 +1,7 @@ import getpass import sys from tack.commands.Command import Command +from tack.crypto.ECGenerator import ECGenerator from tack.structures.TackKeyFile import TackKeyFile class GenerateKeyCommand(Command): @@ -12,7 +13,8 @@ def __init__(self, argv): def execute(self): password = self._getPassword() - keyFile = TackKeyFile.createRandom(password) + public_key, private_key = ECGenerator().generateECKeyPair() + keyFile = TackKeyFile.create(public_key, private_key, password) self.outputFile.write(self.addPemComments(keyFile.serializeAsPem())) if self.isVerbose(): diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index 95acbec..6b0b51e 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -12,13 +12,13 @@ def __init__(self, argv): Command.__init__(self, argv, "kcopmgen", "v") self.password = self.getPassword() - self.key = self.getKey(self.password) + self.keyfile = self.getKeyFile(self.password) self.certificate = self._getCertificate() self.generation = self._getGeneration() self.min_generation = self._getMinGeneration() self.expiration = self._getExpiration() - self.numArg = self._getTackCount() + self.numArg = self._getNumArg() # If -n, then -o is a filename prefix only, so is not opened if self.numArg: self.outputFileName = self.getOutputFileName() @@ -29,7 +29,7 @@ def __init__(self, argv): def execute(self): if not self.numArg: - tack = Tack.create(self.key.getPublicKey(), self.key.getPrivateKey(), self.min_generation, + tack = Tack.create(self.keyfile.getPublicKey(), self.keyfile.getPrivateKey(), self.min_generation, self.generation, self.expiration, self.certificate.key_sha256) self.outputFile.write(self.addPemComments(tack.serializeAsPem())) @@ -43,7 +43,7 @@ def execute(self): self.printError("-o required with -n") for x in range(numTacks): - tack = Tack.create(self.key.getPublicKey(), self.key.getPrivateKey(), self.min_generation, + tack = Tack.create(self.keyfile.getPublicKey(), self.keyfile.getPrivateKey(), self.min_generation, self.generation, self.expiration, self.certificate.key_sha256) outputFile = open(self.outputFileName + "_%04d.pem" % x, "w") @@ -73,7 +73,7 @@ def _getCertificate(self): def _getExpiration(self): expiration = self._getOptionValue("-e") - if expiration is None and self._getTackCount() is None: + if expiration is None and self._getNumArg() is None: return int(math.ceil(self._getCertificate().notAfter / 60.0)) else: try: @@ -81,21 +81,21 @@ def _getExpiration(self): except SyntaxError as e: self.printError(e) - def _getTackCount(self): - tackCount = self._getOptionValue("-n") + def _getNumArg(self): + numArgRaw = self._getOptionValue("-n") - if tackCount is None: + if numArgRaw is None: return None try: - leftArg, rightArg = tackCount.split("@") # could raise ValueError + leftArg, rightArg = numArgRaw.split("@") # could raise ValueError numTacks = int(leftArg) # could raise ValueError interval = Time.parseDurationArg(rightArg) # SyntaxError if numTacks < 1 or numTacks >= 10000: raise ValueError() return numTacks, interval except (ValueError, SyntaxError): - self.printError("Bad -n NUMTACKS: %s:" % tackCount) + self.printError("Bad -n NUMTACKS: %s:" % numArgRaw) def _getGeneration(self): generation = self._getOptionValue("-g") diff --git a/tack/crypto/ECPrivateKey.py b/tack/crypto/ECPrivateKey.py index 6f439e9..5fd8dfa 100644 --- a/tack/crypto/ECPrivateKey.py +++ b/tack/crypto/ECPrivateKey.py @@ -17,7 +17,7 @@ def __init__(self, rawPrivateKey, rawPublicKey, ec=None): if not self.ec: self.ec = self._constructEcFromRawKeys(rawPrivateKey, rawPublicKey) - def getSignature(self, data): + def sign(self, data): # Produce ASN.1 signature hash = Digest.SHA256(data) asn1SigBytes = self.ec.sign_dsa_asn1(hash) diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index 3ca04dd..256bce2 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -48,7 +48,7 @@ def create(cls, public_key, private_key, min_generation, generation, expiration, tack.generation = generation tack.expiration = expiration tack.target_hash = target_hash - tack.signature = private_key.getSignature(tack._getDataToSign()) + tack.signature = private_key.sign(tack._getDataToSign()) return tack diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index 226e3a1..4b893d1 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -41,7 +41,7 @@ def createFromPemList(cls, data): def create(cls, public_key, private_key): tackBreakSig = cls() tackBreakSig.public_key = public_key - tackBreakSig.signature = private_key.getSignature(bytearray("tack_break_sig", "ascii")) + tackBreakSig.signature = private_key.sign(bytearray("tack_break_sig", "ascii")) return tackBreakSig diff --git a/tack/structures/TackKeyFile.py b/tack/structures/TackKeyFile.py index 56a6e30..3eda257 100644 --- a/tack/structures/TackKeyFile.py +++ b/tack/structures/TackKeyFile.py @@ -22,7 +22,6 @@ """ import os from tack.InvalidPasswordException import InvalidPasswordException -from tack.crypto.ECGenerator import ECGenerator from tack.crypto.ECPrivateKey import ECPrivateKey from tack.crypto.ECPublicKey import ECPublicKey from tack.crypto.PBKDF2 import PBKDF2 @@ -43,32 +42,26 @@ def __init__(self, data=None, password=None): self.version = self.getInt(1) if self.version != 1: - raise SyntaxError("Bad version in Secret File") + raise SyntaxError("Bad version in Key File") - self.password = password self.iter_count = self.getInt(4) self.salt = self.getBytes(16) self.ciphertext = self.getBytes(32) self.public_key = ECPublicKey(self.getBytes(64)) - self.mac = bytearray(self.getBytes(32)) + self.mac = self.getBytes(32) - if self.password is not None: - rawPrivateKey = self._decryptKey(password, self.salt, self.ciphertext, - self.iter_count, self.public_key, self.mac) + if password is not None: + rawPrivateKey = self._decrypt(password) self.private_key = ECPrivateKey(rawPrivateKey, self.public_key.getRawKey()) @classmethod - def createRandom(cls, password): + def create(cls, public_key, private_key, password): tackKeyFile = cls() - tackKeyFile.password = password tackKeyFile.version = 1 tackKeyFile.iter_count = 8192 tackKeyFile.salt = bytearray(os.urandom(16)) - tackKeyFile.public_key, tackKeyFile.private_key = ECGenerator().generateECKeyPair() - tackKeyFile.ciphertext, tackKeyFile.mac = tackKeyFile._encryptKey(password, tackKeyFile.salt, - tackKeyFile.iter_count, - tackKeyFile.public_key, - tackKeyFile.private_key) + tackKeyFile.public_key, tackKeyFile.private_key = public_key, private_key + tackKeyFile._encrypt(password) return tackKeyFile @classmethod @@ -96,23 +89,23 @@ def serialize(self): def serializeAsPem(self): return PEMEncoder(self.serialize()).getEncoded("TACK PRIVATE KEY") - def _encryptKey(self, password, salt, iter_count, public_key, private_key): - encKey, authKey = self._deriveKeys(password, salt, iter_count) - ciphertext = AES(encKey, bytearray(16)).encrypt(private_key.getRawKey()) - macData = ciphertext + public_key.getRawKey() + def _encrypt(self, password): + encKey, authKey = self._deriveKeys(password, self.salt, self.iter_count) + ciphertext = AES(encKey, bytearray(16)).encrypt(self.private_key.getRawKey()) + macData = ciphertext + self.public_key.getRawKey() mac = Digest.HMAC_SHA256(authKey, macData) - return ciphertext, mac + self.ciphertext = ciphertext + self.mac = mac - - def _decryptKey(self, password, salt, ciphertext, iter_count, public_key, mac): - encKey, authKey = self._deriveKeys(password, salt, iter_count) - macData = ciphertext + public_key.getRawKey() + def _decrypt(self, password): + encKey, authKey = self._deriveKeys(password, self.salt, self.iter_count) + macData = self.ciphertext + self.public_key.getRawKey() calcMac = Digest.HMAC_SHA256(authKey, macData) - if not Util.constTimeCompare(calcMac, mac): + if not Util.constTimeCompare(calcMac, self.mac): raise InvalidPasswordException("Bad password") - return AES(encKey, bytearray(16)).decrypt(ciphertext) + return AES(encKey, bytearray(16)).decrypt(self.ciphertext) # Uses PBKDF2, then HMAC-SHA256 as PRF to derive independent 32-byte keys def _deriveKeys(self, password, salt, iter_count): From 562ec45db894780431dcb7e6fde38e7f5ba9f237 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sat, 12 May 2012 11:12:11 +0300 Subject: [PATCH 24/94] Adding testdata for "make test". --- testdata/serverX509Cert.der | Bin 0 -> 643 bytes testdata/serverX509Cert.pem | 16 ++++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 testdata/serverX509Cert.der create mode 100644 testdata/serverX509Cert.pem diff --git a/testdata/serverX509Cert.der b/testdata/serverX509Cert.der new file mode 100644 index 0000000000000000000000000000000000000000..2035cc522b541e110cd7a2497bcf6e3d0dea696f GIT binary patch literal 643 zcmXqLVyZW2Vtm2G$?%}`LHGIXwRa47**LY@JlekVGBUEVG8i;A7;+nMvN4CUun9AT z2E#ZU!c5MNh5`nBAQ^UHcK^ieoW#5oLw=xspa_>RTS!rAnR7l&JvUgrkbwY5mRXn+ zBC7y2L%~1}ZX+k7m|#gxaZYAQszV7-e}P_JYKeiIIIp3RfsuikfuVtgiA9t+uZfX? zp^+t&YtYyRvxPh-@)&S|T*WQI9Hi!AC~hDMaU)+zYH^7k+!;hTv~fOi=rOV~FgNxB zL$H&nv5{frp-agtYi3MQFg&vIWyBYuj=-snOixezIC@xrBL7+qr%a}^tN;HKKUJ6S zwELSx@d8WZCe8KMTMvjyX1&QtQ*v=R$ZfCkKYIR);LA4mTRmRtoeKVJdb(|L*o&X{ zQ}5NxGjn)cl`FJz`ZH^`m|OC7CpOfmcTAafH)#JQ$=VYuoWCin=88}96dJ6C28B*1kXFqCrknW)`;2q literal 0 HcmV?d00001 diff --git a/testdata/serverX509Cert.pem b/testdata/serverX509Cert.pem new file mode 100644 index 0000000..f3c86e4 --- /dev/null +++ b/testdata/serverX509Cert.pem @@ -0,0 +1,16 @@ +-----BEGIN CERTIFICATE----- +MIICfzCCAegCCQDgdeCLz7d93DANBgkqhkiG9w0BAQUFADCBgDELMAkGA1UEBhMC +VVMxCzAJBgNVBAgTAkNBMRAwDgYDVQQHEwdPYWtsYW5kMQ8wDQYDVQQKEwZUcmV2 +Q28xCzAJBgNVBAsTAkNBMRIwEAYDVQQDEwlUcmV2Q28gQ0ExIDAeBgkqhkiG9w0B +CQEWEXRsc2xpdGVAdHJldnAubmV0MB4XDTEyMDIwNjAxMDg0OFoXDTQyMDEyOTAx +MDg0OFowgYYxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEQMA4GA1UEBxMHT2Fr +bGFuZDEPMA0GA1UEChMGVHJldkNvMQwwCgYDVQQLFANSJkQxFzAVBgNVBAMTDnRl +c3QudHJldnAubmV0MSAwHgYJKoZIhvcNAQkBFhF0bHNsaXRlQHRyZXZwLm5ldDCB +nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAqcLSY6l8mJQgMcSp6Vj0EohRlYEC +5cj4xcMvkQ+tKEJpAs2r//4Xyn5vQrv2GHOgOTOCKa87tcAWGWrsbGYiRETBCz8k +/1uf6FPTPN+FSOkuylPzNcuGk1bo+d9l3nyeNkDjem0SqZfmOwZc2h9+yLB8J4iU +lt1Sv9IZfcioQ/cCAwEAATANBgkqhkiG9w0BAQUFAAOBgQCz201zjXEC2yTuJBN3 +XT3Q/HXxPOnwkyU3+gKCGJmXeGoVoFDD23RbJ0Nabux1vnQHC4YsHNXPN1b++UOo +NcewUzJbrg9R7VP1teQsLEfaj26oeJkp2yls0b1PrWu/4aTpG7VORdPBeiCn7Alu ++3uDeYqQxcaaRGK2rWBJ3OeQkg== +-----END CERTIFICATE----- From e4f382effa6a19af76ff358965518990de667122 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sat, 12 May 2012 11:28:31 +0300 Subject: [PATCH 25/94] Minor cleanups: rename PEM to encode/decode, remove serializePrelude. --- tack/crypto/ECGenerator.py | 2 +- tack/crypto/ECPrivateKey.py | 2 +- tack/crypto/ECPublicKey.py | 2 +- tack/structures/Tack.py | 18 +++++++----------- tack/structures/TackBreakSig.py | 6 +++--- tack/structures/TackKeyFile.py | 4 ++-- tack/tls/TlsCertificate.py | 4 ++-- tack/util/PEMDecoder.py | 4 ++-- tack/util/PEMEncoder.py | 2 +- 9 files changed, 20 insertions(+), 24 deletions(-) diff --git a/tack/crypto/ECGenerator.py b/tack/crypto/ECGenerator.py index 8b4d997..79588f0 100644 --- a/tack/crypto/ECGenerator.py +++ b/tack/crypto/ECGenerator.py @@ -42,5 +42,5 @@ def _getDerEncodedKeysFromEc(self, ec): ec.save_key_bio(bio, cipher=None) pemEncodedKeys = bio.getvalue() - return PEMDecoder(pemEncodedKeys).getDecoded("EC PRIVATE KEY") + return PEMDecoder(pemEncodedKeys).decode("EC PRIVATE KEY") diff --git a/tack/crypto/ECPrivateKey.py b/tack/crypto/ECPrivateKey.py index 5fd8dfa..2d3c31a 100644 --- a/tack/crypto/ECPrivateKey.py +++ b/tack/crypto/ECPrivateKey.py @@ -41,7 +41,7 @@ def _constructEcFromRawKeys(self, rawPrivateKey, rawPublicKey): b = bytes1 + asn1Length(len(rawPrivateKey)) + rawPrivateKey +\ bytes2 + rawPublicKey b = bytearray([0x30]) + asn1Length(len(b)) + b - pemPrivKeyBytes = PEMEncoder(b).getEncoded("EC PRIVATE KEY") + pemPrivKeyBytes = PEMEncoder(b).encode("EC PRIVATE KEY") return EC.load_key_bio(BIO.MemoryBuffer(pemPrivKeyBytes)) diff --git a/tack/crypto/ECPublicKey.py b/tack/crypto/ECPublicKey.py index 0579f39..379372c 100644 --- a/tack/crypto/ECPublicKey.py +++ b/tack/crypto/ECPublicKey.py @@ -35,7 +35,7 @@ def _constructEcFromRawKey(self, rawPublicKey): assert(len(rawPublicKey) == 64) bytes1 = a2b_hex("3059301306072a8648ce3d020106082a8648ce3d03010703420004") asn1KeyBytes = bytes1 + rawPublicKey - pemPubKeyBytes = PEMEncoder(asn1KeyBytes).getEncoded("PUBLIC KEY") + pemPubKeyBytes = PEMEncoder(asn1KeyBytes).encode("PUBLIC KEY") return EC.load_pub_key_bio(BIO.MemoryBuffer(pemPubKeyBytes)) diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index 256bce2..0e7b870 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -31,7 +31,7 @@ def __init__(self, data=None): @classmethod def createFromPem(cls, pem): - return cls(PEMDecoder(pem).getDecoded("TACK")) + return cls(PEMDecoder(pem).decode("TACK")) @classmethod def create(cls, public_key, private_key, min_generation, generation, expiration, target_hash): @@ -56,24 +56,20 @@ def getTackId(self): return str(self.public_key) def serialize(self): - writer = TlsStructureWriter(64) - writer.add(self.signature, 64) - return self._serializePrelude() + writer.getBytes() - - def serializeAsPem(self): - return PEMEncoder(self.serialize()).getEncoded("TACK") - - def _serializePrelude(self): - writer = TlsStructureWriter(Tack.LENGTH - 64) + writer = TlsStructureWriter(Tack.LENGTH) writer.add(self.public_key.getRawKey(), 64) writer.add(self.min_generation, 1) writer.add(self.generation, 1) writer.add(self.expiration, 4) writer.add(self.target_hash, 32) + writer.add(self.signature, 64) return writer.getBytes() + def serializeAsPem(self): + return PEMEncoder(self.serialize()).encode("TACK") + def _getDataToSign(self): - return bytearray("tack_sig", "ascii") + self._serializePrelude() + return bytearray("tack_sig", "ascii") + self._serialize()[:-64] def _verifySignature(self): bytesToVerify = self._getDataToSign() diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index 4b893d1..aa2ce57 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -22,7 +22,7 @@ def __init__(self, data=None): @classmethod def createFromPem(cls, data): - return cls(PEMDecoder(data).getDecoded("TACK BREAK SIG")) + return cls(PEMDecoder(data).decode("TACK BREAK SIG")) @classmethod def createFromPemList(cls, data): @@ -31,7 +31,7 @@ def createFromPemList(cls, data): Raise a SyntaxError if input is malformed. """ breakSigs = [] - bList = PEMDecoder(data).getDecodedList("TACK BREAK SIG") + bList = PEMDecoder(data).decodeList("TACK BREAK SIG") for b in bList: breakSigs.append(TackBreakSig(b)) @@ -54,7 +54,7 @@ def serialize(self): return w.getBytes() def serializeAsPem(self): - return PEMEncoder(self.serialize()).getEncoded("TACK BREAK SIG") + return PEMEncoder(self.serialize()).encode("TACK BREAK SIG") def getTackId(self): return str(self.public_key) diff --git a/tack/structures/TackKeyFile.py b/tack/structures/TackKeyFile.py index 3eda257..675aff4 100644 --- a/tack/structures/TackKeyFile.py +++ b/tack/structures/TackKeyFile.py @@ -66,7 +66,7 @@ def create(cls, public_key, private_key, password): @classmethod def createFromPem(cls, pem, password): - return cls(PEMDecoder(pem).getDecoded("TACK PRIVATE KEY"), password) + return cls(PEMDecoder(pem).decode("TACK PRIVATE KEY"), password) def getPublicKey(self): return self.public_key @@ -87,7 +87,7 @@ def serialize(self): return w.getBytes() def serializeAsPem(self): - return PEMEncoder(self.serialize()).getEncoded("TACK PRIVATE KEY") + return PEMEncoder(self.serialize()).encode("TACK PRIVATE KEY") def _encrypt(self, password): encKey, authKey = self._deriveKeys(password, self.salt, self.iter_count) diff --git a/tack/tls/TlsCertificate.py b/tack/tls/TlsCertificate.py index 0c2076e..bece0c8 100644 --- a/tack/tls/TlsCertificate.py +++ b/tack/tls/TlsCertificate.py @@ -63,7 +63,7 @@ def matches(self, tack): return self.key_sha256 == tack.target_hash def parsePem(self, s): - b = PEMDecoder(s).getDecoded("CERTIFICATE") + b = PEMDecoder(s).decode("CERTIFICATE") self.parse(b) def parse(self, b): @@ -169,7 +169,7 @@ def write(self): def writePem(self): b = self.write() - return PEMEncoder(b).getEncoded("CERTIFICATE") + return PEMEncoder(b).encode("CERTIFICATE") def writeText(self): s =\ """key_sha256 = 0x%s diff --git a/tack/util/PEMDecoder.py b/tack/util/PEMDecoder.py index d3083e4..0ade9af 100644 --- a/tack/util/PEMDecoder.py +++ b/tack/util/PEMDecoder.py @@ -9,7 +9,7 @@ def containsEncoded(self, name): searchStr = "-----BEGIN %s-----" % name return searchStr in self.data - def getDecoded(self, name): + def decode(self, name): """Decode a PEM string into a bytearray of its payload. The input must contain an appropriate PEM prefix and postfix @@ -37,7 +37,7 @@ def getDecoded(self, name): return retBytes - def getDecodedList(self, name): + def decodeList(self, name): """Decode a sequence of PEM blocks into a list of bytearrays. The input must contain any number of PEM blocks, each with the appropriate diff --git a/tack/util/PEMEncoder.py b/tack/util/PEMEncoder.py index c6bd63d..37a7377 100644 --- a/tack/util/PEMEncoder.py +++ b/tack/util/PEMEncoder.py @@ -5,7 +5,7 @@ class PEMEncoder: def __init__(self, data): self.data = data - def getEncoded(self, name): + def encode(self, name): """Encode a payload bytearray into a PEM string. The input will be base64 encoded, then wrapped in a PEM prefix/postfix From a95282c9ba728aa57294b3d12135299065e2f211 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sat, 12 May 2012 11:57:56 +0300 Subject: [PATCH 26/94] Add back in serializePreSig, make verifySignature() public. --- tack/structures/Tack.py | 26 ++++++++++++++------------ tack/structures/TackBreakSig.py | 2 +- tack/tls/TlsCertificate.py | 2 +- tack/tls/TlsStructureWriter.py | 1 + 4 files changed, 17 insertions(+), 14 deletions(-) diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index 0e7b870..436a095 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -56,25 +56,27 @@ def getTackId(self): return str(self.public_key) def serialize(self): - writer = TlsStructureWriter(Tack.LENGTH) - writer.add(self.public_key.getRawKey(), 64) - writer.add(self.min_generation, 1) - writer.add(self.generation, 1) - writer.add(self.expiration, 4) - writer.add(self.target_hash, 32) - writer.add(self.signature, 64) - return writer.getBytes() + return self._serializePreSig() + self.signature def serializeAsPem(self): return PEMEncoder(self.serialize()).encode("TACK") - def _getDataToSign(self): - return bytearray("tack_sig", "ascii") + self._serialize()[:-64] - - def _verifySignature(self): + def verifySignature(self): bytesToVerify = self._getDataToSign() return self.public_key.verify(bytesToVerify, self.signature) + def _getDataToSign(self): + return bytearray("tack_sig", "ascii") + self._serializePreSig() + + def _serializePreSig(self): + writer = TlsStructureWriter(Tack.LENGTH-64) + writer.add(self.public_key.getRawKey(), 64) + writer.add(self.min_generation, 1) + writer.add(self.generation, 1) + writer.add(self.expiration, 4) + writer.add(self.target_hash, 32) + return writer.getBytes() + def __str__(self): """Return a readable string describing this TACK. diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index aa2ce57..5f0dee0 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -59,7 +59,7 @@ def serializeAsPem(self): def getTackId(self): return str(self.public_key) - def _verifySignature(self): + def verifySignature(self): return self.public_key.verify(bytearray("tack_break_sig"), self.signature) def __str__(self): diff --git a/tack/tls/TlsCertificate.py b/tack/tls/TlsCertificate.py index bece0c8..b2ad7f1 100644 --- a/tack/tls/TlsCertificate.py +++ b/tack/tls/TlsCertificate.py @@ -172,7 +172,7 @@ def writePem(self): return PEMEncoder(b).encode("CERTIFICATE") def writeText(self): s =\ - """key_sha256 = 0x%s + """key_sha256 = %s notAfter = %s """ % (\ Util.writeBytes(self.key_sha256), diff --git a/tack/tls/TlsStructureWriter.py b/tack/tls/TlsStructureWriter.py index 0a52df1..81a1e8b 100644 --- a/tack/tls/TlsStructureWriter.py +++ b/tack/tls/TlsStructureWriter.py @@ -27,4 +27,5 @@ def addVarSeq(self, seq, elementLength, lengthLength): self.add(e, elementLength) def getBytes(self): + assert(self.index == len(self.bytes)) return self.bytes \ No newline at end of file From 4e8e41cb83bf8616505c9502fa1adf0bc4b19d5c Mon Sep 17 00:00:00 2001 From: Trevor Date: Sat, 12 May 2012 12:00:44 +0300 Subject: [PATCH 27/94] Move length check into getBytes(). --- tack/structures/TackBreakSig.py | 1 - tack/structures/TackExtension.py | 1 - tack/structures/TackKeyFile.py | 2 -- 3 files changed, 4 deletions(-) diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index 5f0dee0..f997a85 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -50,7 +50,6 @@ def serialize(self): w = TlsStructureWriter(TackBreakSig.LENGTH) w.add(self.public_key.getRawKey(), 64) w.add(self.signature, 64) - assert(w.index == len(w.bytes)) return w.getBytes() def serializeAsPem(self): diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index 9f427de..11b20b4 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -50,7 +50,6 @@ def serialize(self): w.add(self.pin_activation, 1) - assert(w.index == len(w.bytes)) # did we fill entire bytearray? return w.getBytes() def isEmpty(self): diff --git a/tack/structures/TackKeyFile.py b/tack/structures/TackKeyFile.py index 675aff4..ef10438 100644 --- a/tack/structures/TackKeyFile.py +++ b/tack/structures/TackKeyFile.py @@ -82,8 +82,6 @@ def serialize(self): w.add(self.ciphertext, 32) w.add(self.public_key.getRawKey(), 64) w.add(self.mac, 32) - assert(w.index == len(w.bytes)) # did we fill entire bytearray? - return w.getBytes() def serializeAsPem(self): From 9e6c7df3ff39f4f8798716039d69c7ae1e1e7fc6 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sat, 12 May 2012 12:11:16 +0300 Subject: [PATCH 28/94] Remove TackVersion. --- tack/commands/SignCommand.py | 3 ++- tack/structures/TackVersion.py | 3 --- tack/tls/TlsCertificate.py | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) delete mode 100644 tack/structures/TackVersion.py diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index 6b0b51e..29fc697 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -28,7 +28,7 @@ def __init__(self, argv): def execute(self): if not self.numArg: - + #We are only signing a single TACK (this is the typical mode) tack = Tack.create(self.keyfile.getPublicKey(), self.keyfile.getPrivateKey(), self.min_generation, self.generation, self.expiration, self.certificate.key_sha256) @@ -37,6 +37,7 @@ def execute(self): if self.isVerbose(): sys.stderr.write(str(tack) + "\n") else: + # We are signing multiple TACKs, since "-n" was specified (numTacks, interval) = self.numArg if not self.outputFileName: diff --git a/tack/structures/TackVersion.py b/tack/structures/TackVersion.py deleted file mode 100644 index 02161dd..0000000 --- a/tack/structures/TackVersion.py +++ /dev/null @@ -1,3 +0,0 @@ - -class TackVersion: - V1 = 0 \ No newline at end of file diff --git a/tack/tls/TlsCertificate.py b/tack/tls/TlsCertificate.py index b2ad7f1..5a675d5 100644 --- a/tack/tls/TlsCertificate.py +++ b/tack/tls/TlsCertificate.py @@ -2,7 +2,6 @@ from tack.crypto.ASN1 import ASN1Parser, asn1Length from tack.crypto.Digest import Digest from tack.structures.TackExtension import TackExtension -from tack.structures.TackVersion import TackVersion from tack.util.PEMDecoder import PEMDecoder from tack.util.PEMEncoder import PEMEncoder from tack.util.Time import Time From ebdfaef6c05f32f49bb458fa064279645c00130e Mon Sep 17 00:00:00 2001 From: Trevor Date: Sun, 13 May 2012 15:40:20 +0300 Subject: [PATCH 29/94] First version of ctypes ECDSA, passes "make test". --- setup.py | 3 +- tack.py | 4 ++ tack/crypto/ECGenerator.py | 55 +++++++-------- tack/crypto/ECPrivateKey.py | 132 ++++++++++++++---------------------- tack/crypto/ECPublicKey.py | 74 +++++++++++++------- 5 files changed, 129 insertions(+), 139 deletions(-) diff --git a/setup.py b/setup.py index a1f1dfb..cf9e72a 100755 --- a/setup.py +++ b/setup.py @@ -18,8 +18,7 @@ license="public domain", scripts=["tack/tack"], packages=["tack", "tack/commands", "tack/crypto", "tack/structures", - "tack/tls", "tack/util"], - install_requires=['M2Crypto']) + "tack/tls", "tack/util"]) print "Cleaning up..." if os.path.exists("build/"): diff --git a/tack.py b/tack.py index e1117f7..aad5930 100755 --- a/tack.py +++ b/tack.py @@ -9,6 +9,10 @@ from tack.commands.HelpCommand import HelpCommand from tack.commands.SignCommand import SignCommand from tack.commands.ViewCommand import ViewCommand +from tack.crypto.OpenSSL import openssl + +openssl.initialize() + if __name__ == '__main__': if len(sys.argv) < 2: diff --git a/tack/crypto/ECGenerator.py b/tack/crypto/ECGenerator.py index 79588f0..44939d1 100644 --- a/tack/crypto/ECGenerator.py +++ b/tack/crypto/ECGenerator.py @@ -1,46 +1,39 @@ -from M2Crypto import EC, BIO + +import ctypes, sys from tack.crypto.ASN1 import ASN1Parser, fromAsn1IntBytes from tack.crypto.ECPrivateKey import ECPrivateKey from tack.crypto.ECPublicKey import ECPublicKey +from tack.crypto.OpenSSL import openssl as o from tack.util.PEMDecoder import PEMDecoder class ECGenerator: def generateECKeyPair(self): - # Generate M2Crypto.EC.EC object - ec = EC.gen_params(EC.NID_X9_62_prime256v1) - ec.gen_key() - - rawPrivateKey, rawPublicKey = self._constructRawKeysFromEc(ec) - - return ECPublicKey(rawPublicKey, ec), ECPrivateKey(rawPrivateKey, rawPublicKey, ec) - - def _constructRawKeysFromEc(self, ec): - derEncodedKeys = self._getDerEncodedKeysFromEc(ec) - parser = ASN1Parser(derEncodedKeys) + try: + ec_key = None - # The private key is stored as an ASN.1 integer which may - # need to have zero padding removed (if 33 bytes) or added - # (if < 32 bytes): - rawPrivateKey = parser.getChild(1).value - rawPrivateKey = fromAsn1IntBytes(rawPrivateKey, 32) + # Generate the new key + ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) + o.EC_KEY_generate_key(ec_key) - # There is a 00 04 byte prior to the 64-byte public key - # I'm not sure why M2Crypto has the 00 byte there?, - # some ASN1 thing - the 04 byte signals "uncompressed" - # per SECG. Anyways, strip both those bytes off ([2:]) - rawPublicKey = parser.getChild(3).getTagged().value[2:] + # Extract the key's public and private values as byte strings + pubBuf = o.bytesToBuf(bytearray(1+64)) # [0x04] ... + privBuf = o.bytesToBuf(bytearray(32)) - assert(len(rawPrivateKey) == 32) - assert(len(rawPublicKey) == 64) + ec_point = o.EC_KEY_get0_public_key(ec_key) # doesn't need free + ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) # doesn't need free + o.EC_POINT_point2oct(ec_group, ec_point, o.POINT_CONVERSION_UNCOMPRESSED, pubBuf, 65, None) - return rawPrivateKey, rawPublicKey + bignum = o.EC_KEY_get0_private_key(ec_key) # doesn't need free + privLen = o.BN_bn2bin(bignum, privBuf) - def _getDerEncodedKeysFromEc(self, ec): - # Get the ASN.1 ECPrivateKey for the object - bio = BIO.MemoryBuffer() - ec.save_key_bio(bio, cipher=None) - pemEncodedKeys = bio.getvalue() + # Convert the public and private keys into fixed-length 64 and 32 byte arrays + # Leading zeros are added to priv key, leading byte (0x04) stripped from pub key + publicKey = bytearray(pubBuf[1:65]) + privateKey = bytearray(32-privLen) + bytearray(privBuf[:privLen]) - return PEMDecoder(pemEncodedKeys).decode("EC PRIVATE KEY") + return (ECPublicKey(publicKey), ECPrivateKey(privateKey, publicKey, ec_key)) + finally: + o.EC_KEY_free(ec_key) + diff --git a/tack/crypto/ECPrivateKey.py b/tack/crypto/ECPrivateKey.py index 2d3c31a..28e6f5e 100644 --- a/tack/crypto/ECPrivateKey.py +++ b/tack/crypto/ECPrivateKey.py @@ -1,98 +1,70 @@ -from M2Crypto import EC, BIO -import math +import math, ctypes from tack.compat import a2b_hex +from tack.compat import bytesToStrAscii from tack.crypto.ASN1 import toAsn1IntBytes, asn1Length, ASN1Parser from tack.crypto.Digest import Digest from tack.crypto.ECPublicKey import ECPublicKey +from tack.crypto.OpenSSL import openssl as o from tack.util.PEMEncoder import PEMEncoder class ECPrivateKey: - def __init__(self, rawPrivateKey, rawPublicKey, ec=None): + def __init__(self, rawPrivateKey, rawPublicKey, ec_key=None): + self.ec_key = None # In case of early destruction assert(rawPrivateKey is not None and rawPublicKey is not None) - self.ec = ec + assert(len(rawPrivateKey)==32 and len(rawPublicKey) == 64) + self.rawPrivateKey = rawPrivateKey self.rawPublicKey = rawPublicKey - - if not self.ec: - self.ec = self._constructEcFromRawKeys(rawPrivateKey, rawPublicKey) - - def sign(self, data): - # Produce ASN.1 signature - hash = Digest.SHA256(data) - asn1SigBytes = self.ec.sign_dsa_asn1(hash) - - # Convert stupid ASN.1 signature into 64-byte signature - # Double-check before returning - sigBytes = self._convertToRawSignature(asn1SigBytes) - - assert(ECPublicKey(self.rawPublicKey, self.ec).verify(data, sigBytes)) + if ec_key: + self.ec_key = o.EC_KEY_dup(ec_key) + else: + self.ec_key = self._constructEcFromRawKey(self.rawPrivateKey) + + def __del__(self): + o.EC_KEY_free(self.ec_key) + + def sign(self, data): + try: + ecdsa_sig = None + + # Hash and apply ECDSA + hashBuf = o.bytesToBuf(Digest.SHA256(data)) + ecdsa_sig = o.ECDSA_do_sign(hashBuf, 32, self.ec_key) # needs free + + # Encode the signature into 64 bytes + rBuf = ctypes.create_string_buffer(32) + sBuf = ctypes.create_string_buffer(32) + + rLen = o.BN_bn2bin(ecdsa_sig.contents.r, rBuf) + sLen = o.BN_bn2bin(ecdsa_sig.contents.s, sBuf) + + rBytes = bytearray(32-rLen) + bytearray(rBuf[:rLen]) + sBytes = bytearray(32-sLen) + bytearray(sBuf[:sLen]) + sigBytes = rBytes + sBytes + finally: + o.ECDSA_SIG_free(ecdsa_sig) + + # Double-check the signature before returning + assert(ECPublicKey(self.rawPublicKey).verify(data, sigBytes)) return sigBytes def getRawKey(self): return self.rawPrivateKey + + def _constructEcFromRawKey(self, rawPrivateKey): + try: + privBignum, ec_key = None, None + + ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) # needs free + privBuf = o.bytesToBuf(rawPrivateKey) + privBignum = o.BN_new() # needs free + o.BN_bin2bn(privBuf, 32, privBignum) + o.EC_KEY_set_private_key(ec_key, privBignum) + return o.EC_KEY_dup(ec_key) + finally: + o.BN_free(privBignum) + o.EC_KEY_free(ec_key) - def _constructEcFromRawKeys(self, rawPrivateKey, rawPublicKey): - assert(len(rawPrivateKey) == 32) - assert(len(rawPublicKey) == 64) - bytes1 = a2b_hex("02010104") - bytes2 = a2b_hex("a00a06082a8648ce3d030107a14403420004") - rawPrivateKey = toAsn1IntBytes(rawPrivateKey) - b = bytes1 + asn1Length(len(rawPrivateKey)) + rawPrivateKey +\ - bytes2 + rawPublicKey - b = bytearray([0x30]) + asn1Length(len(b)) + b - pemPrivKeyBytes = PEMEncoder(b).encode("EC PRIVATE KEY") - - return EC.load_key_bio(BIO.MemoryBuffer(pemPrivKeyBytes)) - - - def _convertToRawSignature(self, signature): - parser = ASN1Parser(bytearray(signature)) - r = self._bytesToNumber(parser.getChild(0).value) - s = self._bytesToNumber(parser.getChild(1).value) - return self._numberToBytes(r, 32) + self._numberToBytes(s, 32) - - - def _bytesToNumber(self, bytes): - "Convert a sequence of bytes (eg bytearray) into integer." - total = 0 - multiplier = 1 - for count in range(len(bytes)-1, -1, -1): - byte = bytes[count] - total += multiplier * byte - multiplier *= 256 - return total - - def _numberToBytes(self, n, howManyBytes=None): - """Convert an integer into a bytearray, zero-pad to howManyBytes. - - The returned bytearray may be smaller than howManyBytes, but will - not be larger. The returned bytearray will contain a big-endian - encoding of the input integer (n). - """ - if not howManyBytes: - howManyBytes = self._numBytes(n) - bytes = bytearray(howManyBytes) - for count in range(howManyBytes-1, -1, -1): - bytes[count] = int(n % 256) - n >>= 8 - return bytes - def _numBytes(self, n): - "Return the number of bytes needed to represent the integer n." - if not n: - return 0 - bits = self._numBits(n) - return int(math.ceil(bits / 8.0)) - def _numBits(self, n): - "Return the number of bits needed to represent the integer n." - if not n: - return 0 - s = "%x" % n - return ((len(s)-1)*4) +\ - {'0':0, '1':1, '2':2, '3':2, - '4':3, '5':3, '6':3, '7':3, - '8':4, '9':4, 'a':4, 'b':4, - 'c':4, 'd':4, 'e':4, 'f':4, - }[s[0]] diff --git a/tack/crypto/ECPublicKey.py b/tack/crypto/ECPublicKey.py index 379372c..10e45e8 100644 --- a/tack/crypto/ECPublicKey.py +++ b/tack/crypto/ECPublicKey.py @@ -1,26 +1,51 @@ -from M2Crypto import EC, BIO +import ctypes from tack.compat import a2b_hex from tack.compat import b2a_base32 +from tack.compat import bytesToStrAscii from tack.crypto.ASN1 import toAsn1IntBytes, asn1Length from tack.crypto.Digest import Digest +from tack.crypto.OpenSSL import openssl as o from tack.util.PEMEncoder import PEMEncoder class ECPublicKey: - def __init__(self, rawPublicKey, ec=None): + def __init__(self, rawPublicKey, ec_key=None): + self.ec_key = None # In case of early destruction assert(rawPublicKey is not None) - self.ec = ec + assert(len(rawPublicKey) == 64) + self.rawPublicKey = rawPublicKey + if ec_key: + self.ec_key = o.EC_KEY_dup(ec_key) + else: + self.ec_key = self._constructEcFromRawKey(self.rawPublicKey) - if not self.ec: - self.ec = self._constructEcFromRawKey(self.rawPublicKey) + def __del__(self): + o.EC_KEY_free(self.ec_key) def verify(self, data, signature): - # Convert 64-byte signature into a stupid ASN.1 signature - asn1SigBytes = self._convertToAsn1Signature(signature) - hash = Digest.SHA256(data) + assert(len(signature) == 64) + try: + ecdsa_sig = None - return self.ec.verify_dsa_asn1(hash, asn1SigBytes) + # Create ECDSA_SIG + ecdsa_sig = o.ECDSA_SIG_new() + rBuf = o.bytesToBuf(signature[ : 32]) + sBuf = o.bytesToBuf(signature[32 : ]) + o.BN_bin2bn(rBuf, 32, ecdsa_sig.contents.r) + o.BN_bin2bn(sBuf, 32, ecdsa_sig.contents.s) + + # Hash and verify ECDSA + hashBuf = o.bytesToBuf(Digest.SHA256(data)) + retval = o.ECDSA_do_verify(hashBuf, 32, ecdsa_sig, self.ec_key) + if retval == 1: + return True + elif retval == 0: + return False + else: + assert(False) + finally: + o.ECDSA_SIG_free(ecdsa_sig) def getRawKey(self): return self.rawPublicKey @@ -32,24 +57,21 @@ def getFingerprint(self): return "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) def _constructEcFromRawKey(self, rawPublicKey): - assert(len(rawPublicKey) == 64) - bytes1 = a2b_hex("3059301306072a8648ce3d020106082a8648ce3d03010703420004") - asn1KeyBytes = bytes1 + rawPublicKey - pemPubKeyBytes = PEMEncoder(asn1KeyBytes).encode("PUBLIC KEY") - - return EC.load_pub_key_bio(BIO.MemoryBuffer(pemPubKeyBytes)) + try: + ec_point, ec_key = None, None - def _convertToAsn1Signature(self, signature): - assert(len(signature) == 64) - asn1R = toAsn1IntBytes(signature[:32]) - asn1S = toAsn1IntBytes(signature[32:]) - # Add ASN1 Type=2(int), and Length fields - asn1R = bytearray([2]) + asn1Length(len(asn1R)) + asn1R - asn1S = bytearray([2]) + asn1Length(len(asn1S)) + asn1S - # Add ASN1 Type=0x30(Sequence) and Length fields - asn1ECSigBytes = bytearray([0x30]) +\ - asn1Length(len(asn1R+asn1S)) + asn1R + asn1S - return asn1ECSigBytes + ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) # needs free + ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) # doesn't need free + ec_point = o.EC_POINT_new(ec_group) + + # Add 0x04 byte to signal "uncompressed" public key + pubBuf = o.bytesToBuf(bytearray([0x04]) + rawPublicKey) + o.EC_POINT_oct2point(ec_group, ec_point, pubBuf, 65, None) + o.EC_KEY_set_public_key(ec_key, ec_point) + return o.EC_KEY_dup(ec_key) + finally: + o.EC_POINT_free(ec_point) + o.EC_KEY_free(ec_key) def __str__(self): return self.getFingerprint() \ No newline at end of file From 393592d9ce7a84dff03403642e368bb84e988278 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sun, 13 May 2012 15:52:54 +0300 Subject: [PATCH 30/94] Display OpenSSL version. --- tack/commands/HelpCommand.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index 5fd7bc8..8e297a7 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -6,6 +6,7 @@ from tack.commands.GenerateKeyCommand import GenerateKeyCommand from tack.commands.SignCommand import SignCommand from tack.commands.ViewCommand import ViewCommand +from tack.crypto.OpenSSL import openssl as o class HelpCommand(Command): @@ -37,9 +38,10 @@ def printHelp(): @staticmethod def printGeneralUsage(message=None): + cryptoVersion = "(using \"%s\")" % o.SSLeay_version(0) print "Error: %s" % message print( -"""\ntack.py version %s +"""\ntack.py version %s %s Commands (use "help " to see optional args): genkey @@ -47,5 +49,5 @@ def printGeneralUsage(message=None): break -k KEY view FILE help COMMAND -""" % __version__) +""" % (__version__, cryptoVersion)) sys.exit(-1) From ad518419c3919b655eda525c50cdcb1d91ae51a7 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sun, 13 May 2012 16:02:29 +0300 Subject: [PATCH 31/94] Some README and LICENSE edits. --- LICENSE | 5 +++-- README | 33 +++++++++++++++------------------ 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/LICENSE b/LICENSE index 3857b68..a41f96d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,10 +1,11 @@ -TACKpy includes code from different sources. All code is dedicated to the +Tackpy includes code from different sources. All code is dedicated to the public domain by its authors. In particular: - -Code written by Trevor Perrin is available under the following terms: +Code written by Trevor Perrin and Moxie Marlinspike is available under the +following terms: This is free and unencumbered software released into the public domain. diff --git a/README b/README index a513374..82bc44d 100644 --- a/README +++ b/README @@ -1,32 +1,29 @@ -TACKpy version 0.9.6 Feb 23 2012 -Trevor Perrin +tackpy version 0.9.6 Feb 23 2012 ============================================================================ Licenses/Acknowledgements ========================== -TACKpy is written (mostly) by Trevor Perrin. It includes crypto code from -Peter Pearson (ECDSA) and Bram Cohen (AES). +Tackpy is written by Trevor Perrin and Moxie Marlinspike. It includes crypto +code from Peter Pearson (ECDSA) and Bram Cohen (AES). -All code in TACKpy has been dedicated to the public domain by its authors. See +All code in tackpy has been dedicated to the public domain by its authors. See the LICENSE file for details. Installation ============= -TACKpy requires Python 2.6 or greater, or Python 3. +Tackpy requires Python 2.6 or greater, or Python 3. Run "python setup.py install" or "make install". This installs: - - The "TACKpy" library for use by other Python programs (such as TLS Lite). - - The "TACK.py" command for working with TACKs. + - The "tack" library for use by other Python programs (such as TLS Lite). + - The "tack" command for working with TACKs. -To use TACK.py without installation you can run "selfcontained/TACK.py". +To use the "tack" command without installation you can run +"selfcontained/tack". -If you have M2Crypto installed, TACKpy will use it for elliptic curve and AES -operations. - -TACK.py quick start -==================== +Quick start +============ You will need to create one or more TACK keys to "pin" your hostnames to. You should use a different key for each hostname, unless those hostnames are closely related (such as aliases for the same host, or hosts sharing a TLS @@ -34,7 +31,7 @@ private key). Once you decide how many TACK keys you need, and the assignment of hostnames to keys, do the following: Create a TACK key: - 1) Run "TACK.py genkey > KEY.pem" (replace "KEY" with a specific name) + 1) Run "tack genkey > KEY.pem" (replace "KEY" with a specific name) 2) Back up the key file where it won't be lost or stolen. If a hostname is using TACK, each server at that hostname must have a TACK @@ -42,7 +39,7 @@ that signs the public key in the server's certificate. To create and deploy these TACKs, do the following: Create a TACK for a certificate's public key: - 1) Run "TACK.py sign -k KEY.pem -c CERT > TACK.pem". + 1) Run "tack sign -k KEY.pem -c CERT > TACK.pem". Deploy TACKs to a hostname 1) Deploy TACKs to each server at the hostname. @@ -93,7 +90,7 @@ A server can have up to eight break signatures. However, keep in mind that break signatures add to TLS handshake overhead, so are best avoided. Create a break signature for a TACK: - 1) Run "TACK.py break -k KEY.pem > TACK_Break_Sig.pem" + 1) Run "tack break -k KEY.pem > TACK_Break_Sig.pem" 2) Add the break signature to your web server. @@ -140,7 +137,7 @@ You can generate a batch of TACKs with the "-n NUM@INTERVAL" argument to expiration times. The "-o" argument is taken as a filename prefix, and the "-e" time is used as the first expiration time. Example: -TACK.py sign -k KEY.pem -c CERT -n 365@1d -e 2013-01-02Z -o T1 +tack sign -k KEY.pem -c CERT -n 365@1d -e 2013-01-02Z -o T1 produces 365 TACKs, one expiring at midnight (UTC) each day of 2013: T1_0000.pem From 3ceddfd54603623077ea83aff10c0b72b2f381f8 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sun, 13 May 2012 18:03:15 +0300 Subject: [PATCH 32/94] Finish ECDSA / ctypes checkin. --- tack/compat.py | 4 +-- tack/crypto/ECGenerator.py | 21 +++++++------ tack/crypto/ECPrivateKey.py | 14 ++++----- tack/crypto/ECPublicKey.py | 19 ++++++------ tack/crypto/OpenSSL.py | 62 +++++++++++++++++++++++++++++++++++++ tack/util/Time.py | 2 +- tests/CompatTest.py | 4 +-- 7 files changed, 96 insertions(+), 30 deletions(-) create mode 100644 tack/crypto/OpenSSL.py diff --git a/tack/compat.py b/tack/compat.py index e705c0d..4fbdeff 100644 --- a/tack/compat.py +++ b/tack/compat.py @@ -38,7 +38,7 @@ def b2a_base64(b): def b2a_base32(b): return base64.b32encode(b).decode("ascii") - def bytesToStrAscii(b): + def bytesToStr(b): return str(b, "ascii") def compat26Str(x): return x @@ -75,5 +75,5 @@ def b2a_base64(b): def b2a_base32(b): return base64.b32encode(str(b)) - def bytesToStrAscii(b): + def bytesToStr(b): return str(b) diff --git a/tack/crypto/ECGenerator.py b/tack/crypto/ECGenerator.py index 44939d1..96b279a 100644 --- a/tack/crypto/ECGenerator.py +++ b/tack/crypto/ECGenerator.py @@ -1,5 +1,6 @@ import ctypes, sys +from tack.compat import bytesToStr from tack.crypto.ASN1 import ASN1Parser, fromAsn1IntBytes from tack.crypto.ECPrivateKey import ECPrivateKey from tack.crypto.ECPublicKey import ECPublicKey @@ -10,30 +11,32 @@ class ECGenerator: def generateECKeyPair(self): try: - ec_key = None + ec_key, ec_group = None, None # Generate the new key ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) o.EC_KEY_generate_key(ec_key) # Extract the key's public and private values as byte strings - pubBuf = o.bytesToBuf(bytearray(1+64)) # [0x04] ... - privBuf = o.bytesToBuf(bytearray(32)) + pubBuf = bytesToStr(bytearray(1+64)) # [0x04] ... + privBuf = bytesToStr(bytearray(32)) - ec_point = o.EC_KEY_get0_public_key(ec_key) # doesn't need free - ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) # doesn't need free + ec_point = o.EC_KEY_get0_public_key(ec_key) + ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) o.EC_POINT_point2oct(ec_group, ec_point, o.POINT_CONVERSION_UNCOMPRESSED, pubBuf, 65, None) - bignum = o.EC_KEY_get0_private_key(ec_key) # doesn't need free + bignum = o.EC_KEY_get0_private_key(ec_key) privLen = o.BN_bn2bin(bignum, privBuf) # Convert the public and private keys into fixed-length 64 and 32 byte arrays # Leading zeros are added to priv key, leading byte (0x04) stripped from pub key - publicKey = bytearray(pubBuf[1:65]) - privateKey = bytearray(32-privLen) + bytearray(privBuf[:privLen]) + rawPublicKey = bytearray(pubBuf[1:65]) + rawPrivateKey = bytearray(32-privLen) + bytearray(privBuf[:privLen]) - return (ECPublicKey(publicKey), ECPrivateKey(privateKey, publicKey, ec_key)) + return (ECPublicKey(rawPublicKey, ec_key), + ECPrivateKey(rawPrivateKey, rawPublicKey, ec_key)) finally: o.EC_KEY_free(ec_key) + o.EC_GROUP_free(ec_group) diff --git a/tack/crypto/ECPrivateKey.py b/tack/crypto/ECPrivateKey.py index 28e6f5e..9656a0d 100644 --- a/tack/crypto/ECPrivateKey.py +++ b/tack/crypto/ECPrivateKey.py @@ -1,6 +1,6 @@ import math, ctypes from tack.compat import a2b_hex -from tack.compat import bytesToStrAscii +from tack.compat import bytesToStr from tack.crypto.ASN1 import toAsn1IntBytes, asn1Length, ASN1Parser from tack.crypto.Digest import Digest from tack.crypto.ECPublicKey import ECPublicKey @@ -29,12 +29,12 @@ def sign(self, data): ecdsa_sig = None # Hash and apply ECDSA - hashBuf = o.bytesToBuf(Digest.SHA256(data)) - ecdsa_sig = o.ECDSA_do_sign(hashBuf, 32, self.ec_key) # needs free + hashBuf = bytesToStr(Digest.SHA256(data)) + ecdsa_sig = o.ECDSA_do_sign(hashBuf, 32, self.ec_key) # Encode the signature into 64 bytes - rBuf = ctypes.create_string_buffer(32) - sBuf = ctypes.create_string_buffer(32) + rBuf = bytesToStr(bytearray(32)) + sBuf = bytesToStr(bytearray(32)) rLen = o.BN_bn2bin(ecdsa_sig.contents.r, rBuf) sLen = o.BN_bn2bin(ecdsa_sig.contents.s, sBuf) @@ -56,8 +56,8 @@ def _constructEcFromRawKey(self, rawPrivateKey): try: privBignum, ec_key = None, None - ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) # needs free - privBuf = o.bytesToBuf(rawPrivateKey) + ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) + privBuf = bytesToStr(rawPrivateKey) privBignum = o.BN_new() # needs free o.BN_bin2bn(privBuf, 32, privBignum) o.EC_KEY_set_private_key(ec_key, privBignum) diff --git a/tack/crypto/ECPublicKey.py b/tack/crypto/ECPublicKey.py index 10e45e8..39059c6 100644 --- a/tack/crypto/ECPublicKey.py +++ b/tack/crypto/ECPublicKey.py @@ -1,7 +1,7 @@ import ctypes from tack.compat import a2b_hex from tack.compat import b2a_base32 -from tack.compat import bytesToStrAscii +from tack.compat import bytesToStr from tack.crypto.ASN1 import toAsn1IntBytes, asn1Length from tack.crypto.Digest import Digest from tack.crypto.OpenSSL import openssl as o @@ -30,13 +30,13 @@ def verify(self, data, signature): # Create ECDSA_SIG ecdsa_sig = o.ECDSA_SIG_new() - rBuf = o.bytesToBuf(signature[ : 32]) - sBuf = o.bytesToBuf(signature[32 : ]) + rBuf = bytesToStr(signature[ : 32]) + sBuf = bytesToStr(signature[32 : ]) o.BN_bin2bn(rBuf, 32, ecdsa_sig.contents.r) o.BN_bin2bn(sBuf, 32, ecdsa_sig.contents.s) # Hash and verify ECDSA - hashBuf = o.bytesToBuf(Digest.SHA256(data)) + hashBuf = bytesToStr(Digest.SHA256(data)) retval = o.ECDSA_do_verify(hashBuf, 32, ecdsa_sig, self.ec_key) if retval == 1: return True @@ -58,20 +58,21 @@ def getFingerprint(self): def _constructEcFromRawKey(self, rawPublicKey): try: - ec_point, ec_key = None, None + ec_key, ec_group, ec_point = None, None, None - ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) # needs free - ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) # doesn't need free + ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) + ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) ec_point = o.EC_POINT_new(ec_group) # Add 0x04 byte to signal "uncompressed" public key - pubBuf = o.bytesToBuf(bytearray([0x04]) + rawPublicKey) + pubBuf = bytesToStr(bytearray([0x04]) + rawPublicKey) o.EC_POINT_oct2point(ec_group, ec_point, pubBuf, 65, None) o.EC_KEY_set_public_key(ec_key, ec_point) return o.EC_KEY_dup(ec_key) finally: - o.EC_POINT_free(ec_point) o.EC_KEY_free(ec_key) + o.EC_KEY_free(ec_group) + o.EC_POINT_free(ec_point) def __str__(self): return self.getFingerprint() \ No newline at end of file diff --git a/tack/crypto/OpenSSL.py b/tack/crypto/OpenSSL.py new file mode 100644 index 0000000..04d2ec2 --- /dev/null +++ b/tack/crypto/OpenSSL.py @@ -0,0 +1,62 @@ + +from ctypes import * +from ctypes.util import find_library + +class OpenSSL: + + def initialize(self): + libraryName = find_library("crypto") + self._lib = cdll.LoadLibrary(libraryName) + + self.POINT_CONVERSION_UNCOMPRESSED = 4 + + class ECDSA_SIG(Structure): + _fields_ = [("r", c_void_p), ("s", c_void_p)] + + self._add("SSLeay_version", ret=c_char_p) + self._add("OBJ_txt2nid", args=[c_char_p]) + self._add("EC_KEY_new_by_curve_name", ret=c_void_p, args=[c_int]) + self._add("EC_KEY_free", args=[c_void_p], skipWrap=True) + self._add("EC_KEY_dup", ret=c_void_p, args=[c_void_p]) + self._add("EC_KEY_generate_key", args=[c_void_p]) + self._add("EC_KEY_get0_public_key", ret=c_void_p, args=[c_void_p]) + self._add("EC_KEY_set_public_key", args=[c_void_p, c_void_p]) + self._add("EC_KEY_get0_private_key", ret=c_void_p, args=[c_void_p]) + self._add("EC_KEY_set_private_key", args=[c_void_p, c_void_p]) + self._add("EC_GROUP_new_by_curve_name", ret=c_void_p, args=[c_int]) + self._add("EC_GROUP_free", args=[c_void_p], skipWrap=True) + self._add("EC_POINT_new", ret=c_void_p, args=[c_void_p]) + self._add("EC_POINT_free", args=[c_void_p], skipWrap=True) + self._add("EC_POINT_oct2point", args=[c_void_p, c_void_p, c_void_p, c_int, c_void_p]) + self._add("EC_POINT_point2oct", args=[c_void_p, c_void_p, c_int, c_void_p, c_int, c_void_p] ) + self._add("ECDSA_do_sign", ret=POINTER(ECDSA_SIG), args=[c_void_p, c_int, c_void_p]) + self._add("ECDSA_do_verify", args=[c_void_p, c_int, POINTER(ECDSA_SIG), c_void_p], skipWrap=True) + self._add("ECDSA_SIG_new", ret=POINTER(ECDSA_SIG)) + self._add("ECDSA_SIG_free", args=[c_void_p], skipWrap=True) + self._add("BN_new", ret=c_void_p) + self._add("BN_free", args=[c_void_p], skipWrap=True) + self._add("BN_bn2bin", args=[c_void_p, c_void_p]) + self._add("BN_bin2bn", args=[c_void_p, c_int, c_void_p]) + + def _add(self, name, ret=None, args=None, skipWrap=False): + func = getattr(self._lib, name) + if ret: + func.restype = ret + if args: + func.argtypes = args + + if skipWrap: + setattr(self, name, func) + else: + def wrappedFunc(*a): + retval = func(*a) + assert(retval) + return retval + setattr(self, name, wrappedFunc) + +# Singleton, initialize() this once then use it +openssl = OpenSSL() + + + + \ No newline at end of file diff --git a/tack/util/Time.py b/tack/util/Time.py index 8aa12fb..e88a1aa 100644 --- a/tack/util/Time.py +++ b/tack/util/Time.py @@ -117,6 +117,6 @@ def parseASN1UTCTime(b): @staticmethod def parseASN1GeneralizedTime(b): - t = time.strptime(bytesToStrAscii(b), "%Y%m%d%H%M%SZ") + t = time.strptime(bytesToStr(b), "%Y%m%d%H%M%SZ") return int(calendar.timegm(t)) diff --git a/tests/CompatTest.py b/tests/CompatTest.py index 2f9bff1..35acfe1 100644 --- a/tests/CompatTest.py +++ b/tests/CompatTest.py @@ -3,7 +3,7 @@ from tack.compat import a2b_base64 from tack.compat import b2a_hex from tack.compat import b2a_base64 -from tack.compat import bytesToStrAscii +from tack.compat import bytesToStr class CompatTest(unittest.TestCase): @@ -33,7 +33,7 @@ def test_Compat(self): assert(b2a_base64(bytearray(b"fooba")) == "Zm9vYmE=\n") assert(b2a_base64(bytearray(b"foobar")) == "Zm9vYmFy\n") - assert(bytesToStrAscii(bytearray(b"abcd123")) == "abcd123") + assert(bytesToStr(bytearray(b"abcd123")) == "abcd123") if __name__ == '__main__': unittest.main() \ No newline at end of file From c9c09b1b68204e2549760363385367b6f3d6be29 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sun, 13 May 2012 18:17:54 +0300 Subject: [PATCH 33/94] Change it back to do sig verification on parsing - safer. --- tack/structures/Tack.py | 10 +++++++--- tack/structures/TackBreakSig.py | 4 ++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index 436a095..65b07dd 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -23,12 +23,16 @@ def __init__(self, data=None): self.target_hash = self.getBytes(32) self.signature = self.getBytes(64) - if self.generation < self.min_generation: - raise SyntaxError("Generation less than min_generation") - if self.index != len(data): raise SyntaxError("Excess bytes in TACK") + if self.generation < self.min_generation: + raise SyntaxError("Generation less than min_generation") + + if not self.verifySignature(): + raise SyntaxError("TACK has bad signature") + + @classmethod def createFromPem(cls, pem): return cls(PEMDecoder(pem).decode("TACK")) diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index f997a85..c68ccc0 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -19,6 +19,10 @@ def __init__(self, data=None): if self.index != len(data): raise SyntaxError("Excess bytes in TACK_Break_Sig") + + if not self.verifySignature(): + raise SyntaxError("TACK_Break_Sig has bad signature") + @classmethod def createFromPem(cls, data): From a2378d8322048a53f4cdde1f4d878a9f3728dd52 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sun, 13 May 2012 20:51:23 +0300 Subject: [PATCH 34/94] OpenSSL/ctypes AES --- tack/crypto/AES.py | 73 +++++++++++++++++++++--------------------- tack/crypto/OpenSSL.py | 13 +++++++- 2 files changed, 48 insertions(+), 38 deletions(-) diff --git a/tack/crypto/AES.py b/tack/crypto/AES.py index 77fe1f5..a87bbc7 100644 --- a/tack/crypto/AES.py +++ b/tack/crypto/AES.py @@ -1,59 +1,58 @@ -from M2Crypto import m2 +import ctypes +from tack.crypto.OpenSSL import openssl as o +from tack.compat import bytesToStr class AES: def __init__(self, key, IV): - if len(key) not in (16, 24, 32): - raise AssertionError() - if len(IV) != 16: raise AssertionError() - self.isBlockCipher = True - self.block_size = 16 + self.blockSize = 16 self.key = key self.IV = IV if len(key)==16: self.name = "aes128" + self.cipherType = o.EVP_aes_128_cbc() elif len(key)==24: self.name = "aes192" + self.cipherType = o.EVP_aes_192_cbc() elif len(key)==32: self.name = "aes256" + self.cipherType = o.EVP_aes_256_cbc() else: raise AssertionError() def encrypt(self, plaintext): - assert(len(plaintext) % 16 == 0) - context = self._createContext(1) - ciphertext = m2.cipher_update(context, plaintext) - m2.cipher_ctx_free(context) - self.IV = ciphertext[-self.block_size:] - return bytearray(ciphertext) + return self._transform(plaintext, 1) def decrypt(self, ciphertext): - assert(len(ciphertext) % 16 == 0) - context = self._createContext(0) - #I think M2Crypto has a bug - it fails to decrypt and return the last block passed in. - #To work around this, we append sixteen zeros to the string, below: - plaintext = m2.cipher_update(context, ciphertext+('\0'*16)) - - #If this bug is ever fixed, then plaintext will end up having a garbage - #plaintext block on the end. That's okay - the below code will discard it. - plaintext = plaintext[:len(ciphertext)] - m2.cipher_ctx_free(context) - self.IV = ciphertext[-self.block_size:] - return bytearray(plaintext) - - def _createContext(self, encrypt): - context = m2.cipher_ctx_new() - if len(self.key)==16: - cipherType = m2.aes_128_cbc() - elif len(self.key)==24: - cipherType = m2.aes_192_cbc() - elif len(self.key)==32: - cipherType = m2.aes_256_cbc() + return self._transform(ciphertext, 0) + + def _transform(self, inBytes, encrypt): + assert(len(inBytes) % 16 == 0) + ctx = None + inBuf = bytesToStr(inBytes) + outBuf = ctypes.create_string_buffer(len(inBytes)) + try: + # Create the CIPHER_CTX + ctx = o.EVP_CIPHER_CTX_new() + o.EVP_CIPHER_CTX_init(ctx) + o.EVP_CipherInit(ctx, self.cipherType, bytesToStr(self.key), bytesToStr(self.IV), encrypt) + o.EVP_CIPHER_CTX_set_padding(ctx, 0) + + # Encrypt or Decrypt + outLen = ctypes.c_int() + o.EVP_CipherUpdate(ctx, outBuf, ctypes.byref(outLen), inBuf, len(inBytes)) + assert(outLen.value == len(inBytes)) + outBytes = bytearray(outBuf[:len(inBytes)]) + finally: + o.EVP_CIPHER_CTX_cleanup(ctx) + o.EVP_CIPHER_CTX_free(ctx) + + # Update the chaining + if encrypt: + self.IV = outBytes[-self.blockSize:] else: - raise AssertionError("Key is bad size: %s" % len(self.key)) - - m2.cipher_init(context, cipherType, self.key, self.IV, encrypt) - return context \ No newline at end of file + self.IV = inBytes[-self.blockSize:] + return outBytes diff --git a/tack/crypto/OpenSSL.py b/tack/crypto/OpenSSL.py index 04d2ec2..1feeaab 100644 --- a/tack/crypto/OpenSSL.py +++ b/tack/crypto/OpenSSL.py @@ -37,7 +37,18 @@ class ECDSA_SIG(Structure): self._add("BN_free", args=[c_void_p], skipWrap=True) self._add("BN_bn2bin", args=[c_void_p, c_void_p]) self._add("BN_bin2bn", args=[c_void_p, c_int, c_void_p]) - + + self._add("EVP_CIPHER_CTX_new", ret=c_void_p) + self._add("EVP_CIPHER_CTX_init", args=[c_void_p]) + self._add("EVP_CIPHER_CTX_cleanup", args=[c_void_p]) + self._add("EVP_CIPHER_CTX_free", args=[c_void_p], skipWrap=True) + self._add("EVP_CIPHER_CTX_set_padding", args=[c_void_p, c_int]) + self._add("EVP_aes_128_cbc", ret=c_void_p) + self._add("EVP_aes_192_cbc", ret=c_void_p) + self._add("EVP_aes_256_cbc", ret=c_void_p) + self._add("EVP_CipherInit", args=[c_void_p, c_void_p, c_void_p, c_void_p, c_int]) + self._add("EVP_CipherUpdate", args=[c_void_p, c_void_p, c_void_p, c_void_p, c_int]) + def _add(self, name, ret=None, args=None, skipWrap=False): func = getattr(self._lib, name) if ret: From 2d5e79e9280bdf9eab97cbad42ed83cd7c732fda Mon Sep 17 00:00:00 2001 From: Trevor Date: Sun, 13 May 2012 21:05:16 +0300 Subject: [PATCH 35/94] Clean help output - remove extra linefeeds, improve "tack help". --- tack/commands/BreakCommand.py | 2 +- tack/commands/CertificateCommand.py | 2 +- tack/commands/GenerateKeyCommand.py | 2 +- tack/commands/HelpCommand.py | 9 +++++---- tack/commands/SignCommand.py | 4 ++-- tack/commands/ViewCommand.py | 11 ++++++----- 6 files changed, 16 insertions(+), 14 deletions(-) diff --git a/tack/commands/BreakCommand.py b/tack/commands/BreakCommand.py index 8c08b0a..8f43614 100644 --- a/tack/commands/BreakCommand.py +++ b/tack/commands/BreakCommand.py @@ -15,7 +15,7 @@ def execute(self): self.outputFile.write(self.addPemComments(breakSig.serializeAsPem())) if self.isVerbose(): - sys.stderr.write(str(breakSig) + "\n") + sys.stderr.write(str(breakSig)) @staticmethod def printHelp(): diff --git a/tack/commands/CertificateCommand.py b/tack/commands/CertificateCommand.py index ab4329a..4bdecfd 100644 --- a/tack/commands/CertificateCommand.py +++ b/tack/commands/CertificateCommand.py @@ -47,7 +47,7 @@ def execute(self): self.outputFile.write(s) if self.isVerbose(): - sys.stderr.write(self.inputCertificate.writeText() + "\n") + sys.stderr.write(self.inputCertificate.writeText()) def _getBreakSignatures(self): fileName = self._getOptionValue("-b") diff --git a/tack/commands/GenerateKeyCommand.py b/tack/commands/GenerateKeyCommand.py index f1aaee4..be34a3f 100644 --- a/tack/commands/GenerateKeyCommand.py +++ b/tack/commands/GenerateKeyCommand.py @@ -18,7 +18,7 @@ def execute(self): self.outputFile.write(self.addPemComments(keyFile.serializeAsPem())) if self.isVerbose(): - sys.stderr.write(str(keyFile) + "\n") + sys.stderr.write(str(keyFile)) def _getPassword(self): if not self.password: diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index 8e297a7..14f6c10 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -38,10 +38,11 @@ def printHelp(): @staticmethod def printGeneralUsage(message=None): - cryptoVersion = "(using \"%s\")" % o.SSLeay_version(0) - print "Error: %s" % message - print( -"""\ntack.py version %s %s + cryptoVersion = "(%s)" % o.SSLeay_version(0) + if message: + print "Error: %s\n" % message + sys.stdout.write( +"""tack.py version %s %s Commands (use "help " to see optional args): genkey diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index 29fc697..e17c15b 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -35,7 +35,7 @@ def execute(self): self.outputFile.write(self.addPemComments(tack.serializeAsPem())) if self.isVerbose(): - sys.stderr.write(str(tack) + "\n") + sys.stderr.write(str(tack)) else: # We are signing multiple TACKs, since "-n" was specified (numTacks, interval) = self.numArg @@ -52,7 +52,7 @@ def execute(self): outputFile.close() if self.isVerbose(): - sys.stderr.write(str(tack) + "\n") + sys.stderr.write(str(tack)) self.expiration += interval diff --git a/tack/commands/ViewCommand.py b/tack/commands/ViewCommand.py index a8d0658..5f4b735 100644 --- a/tack/commands/ViewCommand.py +++ b/tack/commands/ViewCommand.py @@ -1,3 +1,4 @@ +import sys from tack.commands.Command import Command from tack.structures.Tack import Tack from tack.structures.TackKeyFile import TackKeyFile @@ -40,12 +41,12 @@ def execute(self): if decoder.containsEncoded("TACK PRIVATE KEY"): fileType = "Private Key" kf = TackKeyFile.createFromPem(text, None) - print(str(kf)) + sys.stdout.write(str(kf)) return elif decoder.containsEncoded("TACK"): fileType = "TACK" tack = Tack.createFromPem(text) - print(str(tack)) + sys.stdout.write(str(tack)) return elif decoder.containsEncoded("TACK BREAK SIG"): fileType = "Break Sig" @@ -53,19 +54,19 @@ def execute(self): s = "" for tbs in tbsList: s += str(tbs) - print(s) + sys.stdout.write(s) return elif decoder.containsEncoded( "CERTIFICATE"): fileType = "Certificate" sslc = TlsCertificate() sslc.parsePem(text) - print(sslc.writeText()) + sys.stdout.write(sslc.writeText()) return # Is it a certificate? try: sslc = TlsCertificate() sslc.parse(binary) - print(sslc.writeText()) + sys.stdout.write(sslc.writeText()) except SyntaxError: self.printError("Unrecognized file type") except SyntaxError as e: From 620a72c75c75f4e7df46424f18fb6999383a8ed9 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sun, 13 May 2012 22:03:59 +0300 Subject: [PATCH 36/94] Move OpenSSL into crypto/openssl, add factories, in prep for python crypto. --- tack.py | 2 +- tack/commands/GenerateKeyCommand.py | 4 +- tack/commands/HelpCommand.py | 2 +- tack/crypto/AES.py | 62 ++---------- tack/crypto/ECGenerator.py | 42 -------- tack/crypto/ECPrivateKey.py | 81 +++------------- tack/crypto/ECPublicKey.py | 81 ++-------------- tack/crypto/OpenSSL.py | 73 -------------- tack/crypto/openssl/OpenSSL.py | 80 +++++++++++++++ tack/crypto/openssl/OpenSSL_AES.py | 58 +++++++++++ tack/crypto/openssl/OpenSSL_ECPrivateKey.py | 102 ++++++++++++++++++++ tack/crypto/openssl/OpenSSL_ECPublicKey.py | 78 +++++++++++++++ tack/crypto/openssl/__init__.py | 0 tack/structures/Tack.py | 2 +- tack/structures/TackBreakSig.py | 2 +- tack/structures/TackKeyFile.py | 8 +- 16 files changed, 356 insertions(+), 321 deletions(-) delete mode 100644 tack/crypto/ECGenerator.py delete mode 100644 tack/crypto/OpenSSL.py create mode 100644 tack/crypto/openssl/OpenSSL.py create mode 100644 tack/crypto/openssl/OpenSSL_AES.py create mode 100644 tack/crypto/openssl/OpenSSL_ECPrivateKey.py create mode 100644 tack/crypto/openssl/OpenSSL_ECPublicKey.py create mode 100644 tack/crypto/openssl/__init__.py diff --git a/tack.py b/tack.py index aad5930..df4bd2c 100755 --- a/tack.py +++ b/tack.py @@ -9,7 +9,7 @@ from tack.commands.HelpCommand import HelpCommand from tack.commands.SignCommand import SignCommand from tack.commands.ViewCommand import ViewCommand -from tack.crypto.OpenSSL import openssl +from tack.crypto.openssl.OpenSSL import openssl openssl.initialize() diff --git a/tack/commands/GenerateKeyCommand.py b/tack/commands/GenerateKeyCommand.py index be34a3f..010f054 100644 --- a/tack/commands/GenerateKeyCommand.py +++ b/tack/commands/GenerateKeyCommand.py @@ -1,7 +1,7 @@ import getpass import sys from tack.commands.Command import Command -from tack.crypto.ECGenerator import ECGenerator +from tack.crypto.ECPrivateKey import ECPrivateKey from tack.structures.TackKeyFile import TackKeyFile class GenerateKeyCommand(Command): @@ -13,7 +13,7 @@ def __init__(self, argv): def execute(self): password = self._getPassword() - public_key, private_key = ECGenerator().generateECKeyPair() + public_key, private_key = ECPrivateKey.generateECKeyPair() keyFile = TackKeyFile.create(public_key, private_key, password) self.outputFile.write(self.addPemComments(keyFile.serializeAsPem())) diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index 14f6c10..3b3483f 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -6,7 +6,7 @@ from tack.commands.GenerateKeyCommand import GenerateKeyCommand from tack.commands.SignCommand import SignCommand from tack.commands.ViewCommand import ViewCommand -from tack.crypto.OpenSSL import openssl as o +from tack.crypto.openssl.OpenSSL import openssl as o class HelpCommand(Command): diff --git a/tack/crypto/AES.py b/tack/crypto/AES.py index a87bbc7..e8df8aa 100644 --- a/tack/crypto/AES.py +++ b/tack/crypto/AES.py @@ -1,58 +1,10 @@ -import ctypes -from tack.crypto.OpenSSL import openssl as o -from tack.compat import bytesToStr +from .openssl.OpenSSL import openssl +from .openssl.OpenSSL_AES import OpenSSL_AES class AES: - def __init__(self, key, IV): - if len(IV) != 16: - raise AssertionError() - self.blockSize = 16 - self.key = key - self.IV = IV - - if len(key)==16: - self.name = "aes128" - self.cipherType = o.EVP_aes_128_cbc() - elif len(key)==24: - self.name = "aes192" - self.cipherType = o.EVP_aes_192_cbc() - elif len(key)==32: - self.name = "aes256" - self.cipherType = o.EVP_aes_256_cbc() - else: - raise AssertionError() - - def encrypt(self, plaintext): - return self._transform(plaintext, 1) - - def decrypt(self, ciphertext): - return self._transform(ciphertext, 0) - - def _transform(self, inBytes, encrypt): - assert(len(inBytes) % 16 == 0) - ctx = None - inBuf = bytesToStr(inBytes) - outBuf = ctypes.create_string_buffer(len(inBytes)) - try: - # Create the CIPHER_CTX - ctx = o.EVP_CIPHER_CTX_new() - o.EVP_CIPHER_CTX_init(ctx) - o.EVP_CipherInit(ctx, self.cipherType, bytesToStr(self.key), bytesToStr(self.IV), encrypt) - o.EVP_CIPHER_CTX_set_padding(ctx, 0) - - # Encrypt or Decrypt - outLen = ctypes.c_int() - o.EVP_CipherUpdate(ctx, outBuf, ctypes.byref(outLen), inBuf, len(inBytes)) - assert(outLen.value == len(inBytes)) - outBytes = bytearray(outBuf[:len(inBytes)]) - finally: - o.EVP_CIPHER_CTX_cleanup(ctx) - o.EVP_CIPHER_CTX_free(ctx) - - # Update the chaining - if encrypt: - self.IV = outBytes[-self.blockSize:] - else: - self.IV = inBytes[-self.blockSize:] - return outBytes + @staticmethod + def new(key, IV): + if openssl.enabled: + return OpenSSL_AES(key, IV) + assert(False) diff --git a/tack/crypto/ECGenerator.py b/tack/crypto/ECGenerator.py deleted file mode 100644 index 96b279a..0000000 --- a/tack/crypto/ECGenerator.py +++ /dev/null @@ -1,42 +0,0 @@ - -import ctypes, sys -from tack.compat import bytesToStr -from tack.crypto.ASN1 import ASN1Parser, fromAsn1IntBytes -from tack.crypto.ECPrivateKey import ECPrivateKey -from tack.crypto.ECPublicKey import ECPublicKey -from tack.crypto.OpenSSL import openssl as o -from tack.util.PEMDecoder import PEMDecoder - -class ECGenerator: - - def generateECKeyPair(self): - try: - ec_key, ec_group = None, None - - # Generate the new key - ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) - o.EC_KEY_generate_key(ec_key) - - # Extract the key's public and private values as byte strings - pubBuf = bytesToStr(bytearray(1+64)) # [0x04] ... - privBuf = bytesToStr(bytearray(32)) - - ec_point = o.EC_KEY_get0_public_key(ec_key) - ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) - o.EC_POINT_point2oct(ec_group, ec_point, o.POINT_CONVERSION_UNCOMPRESSED, pubBuf, 65, None) - - bignum = o.EC_KEY_get0_private_key(ec_key) - privLen = o.BN_bn2bin(bignum, privBuf) - - # Convert the public and private keys into fixed-length 64 and 32 byte arrays - # Leading zeros are added to priv key, leading byte (0x04) stripped from pub key - rawPublicKey = bytearray(pubBuf[1:65]) - rawPrivateKey = bytearray(32-privLen) + bytearray(privBuf[:privLen]) - - return (ECPublicKey(rawPublicKey, ec_key), - ECPrivateKey(rawPrivateKey, rawPublicKey, ec_key)) - finally: - o.EC_KEY_free(ec_key) - o.EC_GROUP_free(ec_group) - - diff --git a/tack/crypto/ECPrivateKey.py b/tack/crypto/ECPrivateKey.py index 9656a0d..cdb7721 100644 --- a/tack/crypto/ECPrivateKey.py +++ b/tack/crypto/ECPrivateKey.py @@ -1,70 +1,17 @@ -import math, ctypes -from tack.compat import a2b_hex -from tack.compat import bytesToStr -from tack.crypto.ASN1 import toAsn1IntBytes, asn1Length, ASN1Parser -from tack.crypto.Digest import Digest -from tack.crypto.ECPublicKey import ECPublicKey -from tack.crypto.OpenSSL import openssl as o +from .openssl.OpenSSL_ECPrivateKey import OpenSSL_ECPrivateKey +from .openssl.OpenSSL import openssl as o from tack.util.PEMEncoder import PEMEncoder class ECPrivateKey: - - def __init__(self, rawPrivateKey, rawPublicKey, ec_key=None): - self.ec_key = None # In case of early destruction - assert(rawPrivateKey is not None and rawPublicKey is not None) - assert(len(rawPrivateKey)==32 and len(rawPublicKey) == 64) - - self.rawPrivateKey = rawPrivateKey - self.rawPublicKey = rawPublicKey - if ec_key: - self.ec_key = o.EC_KEY_dup(ec_key) - else: - self.ec_key = self._constructEcFromRawKey(self.rawPrivateKey) - - def __del__(self): - o.EC_KEY_free(self.ec_key) - - def sign(self, data): - try: - ecdsa_sig = None - - # Hash and apply ECDSA - hashBuf = bytesToStr(Digest.SHA256(data)) - ecdsa_sig = o.ECDSA_do_sign(hashBuf, 32, self.ec_key) - - # Encode the signature into 64 bytes - rBuf = bytesToStr(bytearray(32)) - sBuf = bytesToStr(bytearray(32)) - - rLen = o.BN_bn2bin(ecdsa_sig.contents.r, rBuf) - sLen = o.BN_bn2bin(ecdsa_sig.contents.s, sBuf) - - rBytes = bytearray(32-rLen) + bytearray(rBuf[:rLen]) - sBytes = bytearray(32-sLen) + bytearray(sBuf[:sLen]) - sigBytes = rBytes + sBytes - finally: - o.ECDSA_SIG_free(ecdsa_sig) - - # Double-check the signature before returning - assert(ECPublicKey(self.rawPublicKey).verify(data, sigBytes)) - return sigBytes - - def getRawKey(self): - return self.rawPrivateKey - - def _constructEcFromRawKey(self, rawPrivateKey): - try: - privBignum, ec_key = None, None - - ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) - privBuf = bytesToStr(rawPrivateKey) - privBignum = o.BN_new() # needs free - o.BN_bin2bn(privBuf, 32, privBignum) - o.EC_KEY_set_private_key(ec_key, privBignum) - return o.EC_KEY_dup(ec_key) - finally: - o.BN_free(privBignum) - o.EC_KEY_free(ec_key) - - - + + @staticmethod + def generateECKeyPair(): + if o.enabled: + return OpenSSL_ECPrivateKey.generateECKeyPair() + assert(False) + + @staticmethod + def new(rawPrivateKey, rawPublicKey): + if o.enabled: + return OpenSSL_ECPrivateKey(rawPrivateKey, rawPublicKey) + assert(False) diff --git a/tack/crypto/ECPublicKey.py b/tack/crypto/ECPublicKey.py index 39059c6..c6388dd 100644 --- a/tack/crypto/ECPublicKey.py +++ b/tack/crypto/ECPublicKey.py @@ -1,78 +1,11 @@ -import ctypes -from tack.compat import a2b_hex -from tack.compat import b2a_base32 -from tack.compat import bytesToStr -from tack.crypto.ASN1 import toAsn1IntBytes, asn1Length -from tack.crypto.Digest import Digest -from tack.crypto.OpenSSL import openssl as o +from .openssl.OpenSSL import openssl as o +from .openssl.OpenSSL_ECPublicKey import OpenSSL_ECPublicKey from tack.util.PEMEncoder import PEMEncoder class ECPublicKey: - def __init__(self, rawPublicKey, ec_key=None): - self.ec_key = None # In case of early destruction - assert(rawPublicKey is not None) - assert(len(rawPublicKey) == 64) - - self.rawPublicKey = rawPublicKey - if ec_key: - self.ec_key = o.EC_KEY_dup(ec_key) - else: - self.ec_key = self._constructEcFromRawKey(self.rawPublicKey) - - def __del__(self): - o.EC_KEY_free(self.ec_key) - - def verify(self, data, signature): - assert(len(signature) == 64) - try: - ecdsa_sig = None - - # Create ECDSA_SIG - ecdsa_sig = o.ECDSA_SIG_new() - rBuf = bytesToStr(signature[ : 32]) - sBuf = bytesToStr(signature[32 : ]) - o.BN_bin2bn(rBuf, 32, ecdsa_sig.contents.r) - o.BN_bin2bn(sBuf, 32, ecdsa_sig.contents.s) - - # Hash and verify ECDSA - hashBuf = bytesToStr(Digest.SHA256(data)) - retval = o.ECDSA_do_verify(hashBuf, 32, ecdsa_sig, self.ec_key) - if retval == 1: - return True - elif retval == 0: - return False - else: - assert(False) - finally: - o.ECDSA_SIG_free(ecdsa_sig) - - def getRawKey(self): - return self.rawPublicKey - - def getFingerprint(self): - digest = Digest.SHA256(self.rawPublicKey) - assert(len(digest) == 32) - s = b2a_base32(digest).lower()[:25] - return "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) - - def _constructEcFromRawKey(self, rawPublicKey): - try: - ec_key, ec_group, ec_point = None, None, None - - ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) - ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) - ec_point = o.EC_POINT_new(ec_group) - - # Add 0x04 byte to signal "uncompressed" public key - pubBuf = bytesToStr(bytearray([0x04]) + rawPublicKey) - o.EC_POINT_oct2point(ec_group, ec_point, pubBuf, 65, None) - o.EC_KEY_set_public_key(ec_key, ec_point) - return o.EC_KEY_dup(ec_key) - finally: - o.EC_KEY_free(ec_key) - o.EC_KEY_free(ec_group) - o.EC_POINT_free(ec_point) - - def __str__(self): - return self.getFingerprint() \ No newline at end of file + @staticmethod + def new(rawPublicKey): + if o.enabled: + return OpenSSL_ECPublicKey(rawPublicKey) + assert(False) diff --git a/tack/crypto/OpenSSL.py b/tack/crypto/OpenSSL.py deleted file mode 100644 index 1feeaab..0000000 --- a/tack/crypto/OpenSSL.py +++ /dev/null @@ -1,73 +0,0 @@ - -from ctypes import * -from ctypes.util import find_library - -class OpenSSL: - - def initialize(self): - libraryName = find_library("crypto") - self._lib = cdll.LoadLibrary(libraryName) - - self.POINT_CONVERSION_UNCOMPRESSED = 4 - - class ECDSA_SIG(Structure): - _fields_ = [("r", c_void_p), ("s", c_void_p)] - - self._add("SSLeay_version", ret=c_char_p) - self._add("OBJ_txt2nid", args=[c_char_p]) - self._add("EC_KEY_new_by_curve_name", ret=c_void_p, args=[c_int]) - self._add("EC_KEY_free", args=[c_void_p], skipWrap=True) - self._add("EC_KEY_dup", ret=c_void_p, args=[c_void_p]) - self._add("EC_KEY_generate_key", args=[c_void_p]) - self._add("EC_KEY_get0_public_key", ret=c_void_p, args=[c_void_p]) - self._add("EC_KEY_set_public_key", args=[c_void_p, c_void_p]) - self._add("EC_KEY_get0_private_key", ret=c_void_p, args=[c_void_p]) - self._add("EC_KEY_set_private_key", args=[c_void_p, c_void_p]) - self._add("EC_GROUP_new_by_curve_name", ret=c_void_p, args=[c_int]) - self._add("EC_GROUP_free", args=[c_void_p], skipWrap=True) - self._add("EC_POINT_new", ret=c_void_p, args=[c_void_p]) - self._add("EC_POINT_free", args=[c_void_p], skipWrap=True) - self._add("EC_POINT_oct2point", args=[c_void_p, c_void_p, c_void_p, c_int, c_void_p]) - self._add("EC_POINT_point2oct", args=[c_void_p, c_void_p, c_int, c_void_p, c_int, c_void_p] ) - self._add("ECDSA_do_sign", ret=POINTER(ECDSA_SIG), args=[c_void_p, c_int, c_void_p]) - self._add("ECDSA_do_verify", args=[c_void_p, c_int, POINTER(ECDSA_SIG), c_void_p], skipWrap=True) - self._add("ECDSA_SIG_new", ret=POINTER(ECDSA_SIG)) - self._add("ECDSA_SIG_free", args=[c_void_p], skipWrap=True) - self._add("BN_new", ret=c_void_p) - self._add("BN_free", args=[c_void_p], skipWrap=True) - self._add("BN_bn2bin", args=[c_void_p, c_void_p]) - self._add("BN_bin2bn", args=[c_void_p, c_int, c_void_p]) - - self._add("EVP_CIPHER_CTX_new", ret=c_void_p) - self._add("EVP_CIPHER_CTX_init", args=[c_void_p]) - self._add("EVP_CIPHER_CTX_cleanup", args=[c_void_p]) - self._add("EVP_CIPHER_CTX_free", args=[c_void_p], skipWrap=True) - self._add("EVP_CIPHER_CTX_set_padding", args=[c_void_p, c_int]) - self._add("EVP_aes_128_cbc", ret=c_void_p) - self._add("EVP_aes_192_cbc", ret=c_void_p) - self._add("EVP_aes_256_cbc", ret=c_void_p) - self._add("EVP_CipherInit", args=[c_void_p, c_void_p, c_void_p, c_void_p, c_int]) - self._add("EVP_CipherUpdate", args=[c_void_p, c_void_p, c_void_p, c_void_p, c_int]) - - def _add(self, name, ret=None, args=None, skipWrap=False): - func = getattr(self._lib, name) - if ret: - func.restype = ret - if args: - func.argtypes = args - - if skipWrap: - setattr(self, name, func) - else: - def wrappedFunc(*a): - retval = func(*a) - assert(retval) - return retval - setattr(self, name, wrappedFunc) - -# Singleton, initialize() this once then use it -openssl = OpenSSL() - - - - \ No newline at end of file diff --git a/tack/crypto/openssl/OpenSSL.py b/tack/crypto/openssl/OpenSSL.py new file mode 100644 index 0000000..085462a --- /dev/null +++ b/tack/crypto/openssl/OpenSSL.py @@ -0,0 +1,80 @@ + +from ctypes import * +from ctypes.util import find_library + +class OpenSSL: + + def initialize(self): + try: + libraryName = find_library("crypto") + self._lib = cdll.LoadLibrary(libraryName) + + self.POINT_CONVERSION_UNCOMPRESSED = 4 + + class ECDSA_SIG(Structure): + _fields_ = [("r", c_void_p), ("s", c_void_p)] + + self._add("SSLeay_version", ret=c_char_p) + self._add("OBJ_txt2nid", args=[c_char_p]) + self._add("EC_KEY_new_by_curve_name", ret=c_void_p, args=[c_int]) + self._add("EC_KEY_free", args=[c_void_p], skipWrap=True) + self._add("EC_KEY_dup", ret=c_void_p, args=[c_void_p]) + self._add("EC_KEY_generate_key", args=[c_void_p]) + self._add("EC_KEY_get0_public_key", ret=c_void_p, args=[c_void_p]) + self._add("EC_KEY_set_public_key", args=[c_void_p, c_void_p]) + self._add("EC_KEY_get0_private_key", ret=c_void_p, args=[c_void_p]) + self._add("EC_KEY_set_private_key", args=[c_void_p, c_void_p]) + self._add("EC_GROUP_new_by_curve_name", ret=c_void_p, args=[c_int]) + self._add("EC_GROUP_free", args=[c_void_p], skipWrap=True) + self._add("EC_POINT_new", ret=c_void_p, args=[c_void_p]) + self._add("EC_POINT_free", args=[c_void_p], skipWrap=True) + self._add("EC_POINT_oct2point", args=[c_void_p, c_void_p, c_void_p, c_int, c_void_p]) + self._add("EC_POINT_point2oct", args=[c_void_p, c_void_p, c_int, c_void_p, c_int, c_void_p] ) + self._add("ECDSA_do_sign", ret=POINTER(ECDSA_SIG), args=[c_void_p, c_int, c_void_p]) + self._add("ECDSA_do_verify", args=[c_void_p, c_int, POINTER(ECDSA_SIG), c_void_p], skipWrap=True) + self._add("ECDSA_SIG_new", ret=POINTER(ECDSA_SIG)) + self._add("ECDSA_SIG_free", args=[c_void_p], skipWrap=True) + self._add("BN_new", ret=c_void_p) + self._add("BN_free", args=[c_void_p], skipWrap=True) + self._add("BN_bn2bin", args=[c_void_p, c_void_p]) + self._add("BN_bin2bn", args=[c_void_p, c_int, c_void_p]) + + self._add("EVP_CIPHER_CTX_new", ret=c_void_p) + self._add("EVP_CIPHER_CTX_init", args=[c_void_p]) + self._add("EVP_CIPHER_CTX_cleanup", args=[c_void_p]) + self._add("EVP_CIPHER_CTX_free", args=[c_void_p], skipWrap=True) + self._add("EVP_CIPHER_CTX_set_padding", args=[c_void_p, c_int]) + self._add("EVP_aes_128_cbc", ret=c_void_p) + self._add("EVP_aes_192_cbc", ret=c_void_p) + self._add("EVP_aes_256_cbc", ret=c_void_p) + self._add("EVP_CipherInit", args=[c_void_p, c_void_p, c_void_p, c_void_p, c_int]) + self._add("EVP_CipherUpdate", args=[c_void_p, c_void_p, c_void_p, c_void_p, c_int]) + + self.enabled = True + except: + self.enabled = False + # raise + + + def _add(self, name, ret=None, args=None, skipWrap=False): + func = getattr(self._lib, name) + if ret: + func.restype = ret + if args: + func.argtypes = args + + if skipWrap: + setattr(self, name, func) + else: + def wrappedFunc(*a): + retval = func(*a) + assert(retval) + return retval + setattr(self, name, wrappedFunc) + +# Singleton, initialize() this once then use it +openssl = OpenSSL() + + + + \ No newline at end of file diff --git a/tack/crypto/openssl/OpenSSL_AES.py b/tack/crypto/openssl/OpenSSL_AES.py new file mode 100644 index 0000000..9a85954 --- /dev/null +++ b/tack/crypto/openssl/OpenSSL_AES.py @@ -0,0 +1,58 @@ +import ctypes +from .OpenSSL import openssl as o +from tack.compat import bytesToStr + +class OpenSSL_AES: + def __init__(self, key, IV): + if len(IV) != 16: + raise AssertionError() + + self.blockSize = 16 + self.key = key + self.IV = IV + + if len(key)==16: + self.name = "aes128" + self.cipherType = o.EVP_aes_128_cbc() + elif len(key)==24: + self.name = "aes192" + self.cipherType = o.EVP_aes_192_cbc() + elif len(key)==32: + self.name = "aes256" + self.cipherType = o.EVP_aes_256_cbc() + else: + raise AssertionError() + + def encrypt(self, plaintext): + return self._transform(plaintext, 1) + + def decrypt(self, ciphertext): + return self._transform(ciphertext, 0) + + def _transform(self, inBytes, encrypt): + assert(len(inBytes) % 16 == 0) + ctx = None + inBuf = bytesToStr(inBytes) + outBuf = ctypes.create_string_buffer(len(inBytes)) + try: + # Create the CIPHER_CTX + ctx = o.EVP_CIPHER_CTX_new() + o.EVP_CIPHER_CTX_init(ctx) + o.EVP_CipherInit(ctx, self.cipherType, bytesToStr(self.key), bytesToStr(self.IV), encrypt) + o.EVP_CIPHER_CTX_set_padding(ctx, 0) + + # Encrypt or Decrypt + outLen = ctypes.c_int() + o.EVP_CipherUpdate(ctx, outBuf, ctypes.byref(outLen), inBuf, len(inBytes)) + assert(outLen.value == len(inBytes)) + outBytes = bytearray(outBuf[:len(inBytes)]) + finally: + o.EVP_CIPHER_CTX_cleanup(ctx) + o.EVP_CIPHER_CTX_free(ctx) + + # Update the chaining + if encrypt: + self.IV = outBytes[-self.blockSize:] + else: + self.IV = inBytes[-self.blockSize:] + return outBytes diff --git a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py new file mode 100644 index 0000000..76b8cb6 --- /dev/null +++ b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py @@ -0,0 +1,102 @@ +import math, ctypes +from tack.compat import a2b_hex +from tack.compat import bytesToStr +from tack.crypto.ASN1 import toAsn1IntBytes, asn1Length, ASN1Parser +from tack.crypto.Digest import Digest +from .OpenSSL_ECPublicKey import OpenSSL_ECPublicKey +from .OpenSSL import openssl as o +from tack.util.PEMEncoder import PEMEncoder + +class OpenSSL_ECPrivateKey: + + @staticmethod + def generateECKeyPair(): + try: + ec_key, ec_group = None, None + + # Generate the new key + ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) + o.EC_KEY_generate_key(ec_key) + + # Extract the key's public and private values as byte strings + pubBuf = bytesToStr(bytearray(1+64)) # [0x04] ... + privBuf = bytesToStr(bytearray(32)) + + ec_point = o.EC_KEY_get0_public_key(ec_key) + ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) + o.EC_POINT_point2oct(ec_group, ec_point, o.POINT_CONVERSION_UNCOMPRESSED, pubBuf, 65, None) + + bignum = o.EC_KEY_get0_private_key(ec_key) + privLen = o.BN_bn2bin(bignum, privBuf) + + # Convert the public and private keys into fixed-length 64 and 32 byte arrays + # Leading zeros are added to priv key, leading byte (0x04) stripped from pub key + rawPublicKey = bytearray(pubBuf[1:65]) + rawPrivateKey = bytearray(32-privLen) + bytearray(privBuf[:privLen]) + + return (OpenSSL_ECPublicKey(rawPublicKey, ec_key), + OpenSSL_ECPrivateKey(rawPrivateKey, rawPublicKey, ec_key)) + finally: + o.EC_KEY_free(ec_key) + o.EC_GROUP_free(ec_group) + + + def __init__(self, rawPrivateKey, rawPublicKey, ec_key=None): + self.ec_key = None # In case of early destruction + assert(rawPrivateKey is not None and rawPublicKey is not None) + assert(len(rawPrivateKey)==32 and len(rawPublicKey) == 64) + + self.rawPrivateKey = rawPrivateKey + self.rawPublicKey = rawPublicKey + if ec_key: + self.ec_key = o.EC_KEY_dup(ec_key) + else: + self.ec_key = self._constructEcFromRawKey(self.rawPrivateKey) + + def __del__(self): + o.EC_KEY_free(self.ec_key) + + def sign(self, data): + try: + ecdsa_sig = None + + # Hash and apply ECDSA + hashBuf = bytesToStr(Digest.SHA256(data)) + ecdsa_sig = o.ECDSA_do_sign(hashBuf, 32, self.ec_key) + + # Encode the signature into 64 bytes + rBuf = bytesToStr(bytearray(32)) + sBuf = bytesToStr(bytearray(32)) + + rLen = o.BN_bn2bin(ecdsa_sig.contents.r, rBuf) + sLen = o.BN_bn2bin(ecdsa_sig.contents.s, sBuf) + + rBytes = bytearray(32-rLen) + bytearray(rBuf[:rLen]) + sBytes = bytearray(32-sLen) + bytearray(sBuf[:sLen]) + sigBytes = rBytes + sBytes + finally: + o.ECDSA_SIG_free(ecdsa_sig) + + # Double-check the signature before returning + assert(OpenSSL_ECPublicKey(self.rawPublicKey).verify(data, sigBytes)) + return sigBytes + + def getRawKey(self): + return self.rawPrivateKey + + def _constructEcFromRawKey(self, rawPrivateKey): + try: + privBignum, ec_key = None, None + + ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) + privBuf = bytesToStr(rawPrivateKey) + privBignum = o.BN_new() # needs free + o.BN_bin2bn(privBuf, 32, privBignum) + o.EC_KEY_set_private_key(ec_key, privBignum) + return o.EC_KEY_dup(ec_key) + finally: + o.BN_free(privBignum) + o.EC_KEY_free(ec_key) + + + diff --git a/tack/crypto/openssl/OpenSSL_ECPublicKey.py b/tack/crypto/openssl/OpenSSL_ECPublicKey.py new file mode 100644 index 0000000..192d64f --- /dev/null +++ b/tack/crypto/openssl/OpenSSL_ECPublicKey.py @@ -0,0 +1,78 @@ +import ctypes +from tack.compat import a2b_hex +from tack.compat import b2a_base32 +from tack.compat import bytesToStr +from tack.crypto.ASN1 import toAsn1IntBytes, asn1Length +from tack.crypto.Digest import Digest +from .OpenSSL import openssl as o +from tack.util.PEMEncoder import PEMEncoder + +class OpenSSL_ECPublicKey: + + def __init__(self, rawPublicKey, ec_key=None): + self.ec_key = None # In case of early destruction + assert(rawPublicKey is not None) + assert(len(rawPublicKey) == 64) + + self.rawPublicKey = rawPublicKey + if ec_key: + self.ec_key = o.EC_KEY_dup(ec_key) + else: + self.ec_key = self._constructEcFromRawKey(self.rawPublicKey) + + def __del__(self): + o.EC_KEY_free(self.ec_key) + + def verify(self, data, signature): + assert(len(signature) == 64) + try: + ecdsa_sig = None + + # Create ECDSA_SIG + ecdsa_sig = o.ECDSA_SIG_new() + rBuf = bytesToStr(signature[ : 32]) + sBuf = bytesToStr(signature[32 : ]) + o.BN_bin2bn(rBuf, 32, ecdsa_sig.contents.r) + o.BN_bin2bn(sBuf, 32, ecdsa_sig.contents.s) + + # Hash and verify ECDSA + hashBuf = bytesToStr(Digest.SHA256(data)) + retval = o.ECDSA_do_verify(hashBuf, 32, ecdsa_sig, self.ec_key) + if retval == 1: + return True + elif retval == 0: + return False + else: + assert(False) + finally: + o.ECDSA_SIG_free(ecdsa_sig) + + def getRawKey(self): + return self.rawPublicKey + + def getFingerprint(self): + digest = Digest.SHA256(self.rawPublicKey) + assert(len(digest) == 32) + s = b2a_base32(digest).lower()[:25] + return "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) + + def _constructEcFromRawKey(self, rawPublicKey): + try: + ec_key, ec_group, ec_point = None, None, None + + ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) + ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) + ec_point = o.EC_POINT_new(ec_group) + + # Add 0x04 byte to signal "uncompressed" public key + pubBuf = bytesToStr(bytearray([0x04]) + rawPublicKey) + o.EC_POINT_oct2point(ec_group, ec_point, pubBuf, 65, None) + o.EC_KEY_set_public_key(ec_key, ec_point) + return o.EC_KEY_dup(ec_key) + finally: + o.EC_KEY_free(ec_key) + o.EC_KEY_free(ec_group) + o.EC_POINT_free(ec_point) + + def __str__(self): + return self.getFingerprint() \ No newline at end of file diff --git a/tack/crypto/openssl/__init__.py b/tack/crypto/openssl/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index 65b07dd..7c0b739 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -16,7 +16,7 @@ def __init__(self, data=None): if len(data) != Tack.LENGTH: raise SyntaxError("TACK is the wrong size. Is %s and should be %s" % (len(data), Tack.LENGTH)) - self.public_key = ECPublicKey(self.getBytes(64)) + self.public_key = ECPublicKey.new(self.getBytes(64)) self.min_generation = self.getInt(1) self.generation = self.getInt(1) self.expiration = self.getInt(4) diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index c68ccc0..d6a1b0e 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -14,7 +14,7 @@ def __init__(self, data=None): if len(data) != TackBreakSig.LENGTH: raise SyntaxError("Break signature is the wrong size. Is %s and should be %s." % (len(data), TackBreakSig.LENGTH)) - self.public_key = ECPublicKey(self.getBytes(64)) + self.public_key = ECPublicKey.new(self.getBytes(64)) self.signature = self.getBytes(64) if self.index != len(data): diff --git a/tack/structures/TackKeyFile.py b/tack/structures/TackKeyFile.py index ef10438..acc6106 100644 --- a/tack/structures/TackKeyFile.py +++ b/tack/structures/TackKeyFile.py @@ -47,12 +47,12 @@ def __init__(self, data=None, password=None): self.iter_count = self.getInt(4) self.salt = self.getBytes(16) self.ciphertext = self.getBytes(32) - self.public_key = ECPublicKey(self.getBytes(64)) + self.public_key = ECPublicKey.new(self.getBytes(64)) self.mac = self.getBytes(32) if password is not None: rawPrivateKey = self._decrypt(password) - self.private_key = ECPrivateKey(rawPrivateKey, self.public_key.getRawKey()) + self.private_key = ECPrivateKey.new(rawPrivateKey, self.public_key.getRawKey()) @classmethod def create(cls, public_key, private_key, password): @@ -89,7 +89,7 @@ def serializeAsPem(self): def _encrypt(self, password): encKey, authKey = self._deriveKeys(password, self.salt, self.iter_count) - ciphertext = AES(encKey, bytearray(16)).encrypt(self.private_key.getRawKey()) + ciphertext = AES.new(encKey, bytearray(16)).encrypt(self.private_key.getRawKey()) macData = ciphertext + self.public_key.getRawKey() mac = Digest.HMAC_SHA256(authKey, macData) self.ciphertext = ciphertext @@ -103,7 +103,7 @@ def _decrypt(self, password): if not Util.constTimeCompare(calcMac, self.mac): raise InvalidPasswordException("Bad password") - return AES(encKey, bytearray(16)).decrypt(self.ciphertext) + return AES.new(encKey, bytearray(16)).decrypt(self.ciphertext) # Uses PBKDF2, then HMAC-SHA256 as PRF to derive independent 32-byte keys def _deriveKeys(self, password, salt, iter_count): From 1b0838f163a567107e853a4c71dd61affb34aa10 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sun, 13 May 2012 22:08:20 +0300 Subject: [PATCH 37/94] oops update setup.py for prev checkin. --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index cf9e72a..98ea860 100755 --- a/setup.py +++ b/setup.py @@ -17,8 +17,8 @@ description="TACKpy implements TACK in python", license="public domain", scripts=["tack/tack"], - packages=["tack", "tack/commands", "tack/crypto", "tack/structures", - "tack/tls", "tack/util"]) + packages=["tack", "tack/commands", "tack/crypto", "tack/crypto/openssl", + "tack/structures", "tack/tls", "tack/util"]) print "Cleaning up..." if os.path.exists("build/"): From 28b209fbfcaeec67674b4355ca67be90b13efe12 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sun, 13 May 2012 22:48:14 +0300 Subject: [PATCH 38/94] Add back ECGenerator. --- tack/commands/GenerateKeyCommand.py | 4 +-- tack/crypto/ECGenerator.py | 10 ++++++ tack/crypto/ECPrivateKey.py | 7 ---- tack/crypto/openssl/OpenSSL_AES.py | 2 +- tack/crypto/openssl/OpenSSL_ECGenerator.py | 39 +++++++++++++++++++++ tack/crypto/openssl/OpenSSL_ECPrivateKey.py | 32 ----------------- 6 files changed, 52 insertions(+), 42 deletions(-) create mode 100644 tack/crypto/ECGenerator.py create mode 100644 tack/crypto/openssl/OpenSSL_ECGenerator.py diff --git a/tack/commands/GenerateKeyCommand.py b/tack/commands/GenerateKeyCommand.py index 010f054..aa9fa25 100644 --- a/tack/commands/GenerateKeyCommand.py +++ b/tack/commands/GenerateKeyCommand.py @@ -1,7 +1,7 @@ import getpass import sys from tack.commands.Command import Command -from tack.crypto.ECPrivateKey import ECPrivateKey +from tack.crypto.ECGenerator import ECGenerator from tack.structures.TackKeyFile import TackKeyFile class GenerateKeyCommand(Command): @@ -13,7 +13,7 @@ def __init__(self, argv): def execute(self): password = self._getPassword() - public_key, private_key = ECPrivateKey.generateECKeyPair() + public_key, private_key = ECGenerator.generateECKeyPair() keyFile = TackKeyFile.create(public_key, private_key, password) self.outputFile.write(self.addPemComments(keyFile.serializeAsPem())) diff --git a/tack/crypto/ECGenerator.py b/tack/crypto/ECGenerator.py new file mode 100644 index 0000000..339d9b9 --- /dev/null +++ b/tack/crypto/ECGenerator.py @@ -0,0 +1,10 @@ +from .openssl.OpenSSL_ECGenerator import OpenSSL_ECGenerator +from .openssl.OpenSSL import openssl as o + +class ECGenerator: + + @staticmethod + def generateECKeyPair(): + if o.enabled: + return OpenSSL_ECGenerator.generateECKeyPair() + assert(False) diff --git a/tack/crypto/ECPrivateKey.py b/tack/crypto/ECPrivateKey.py index cdb7721..a7142cd 100644 --- a/tack/crypto/ECPrivateKey.py +++ b/tack/crypto/ECPrivateKey.py @@ -1,14 +1,7 @@ from .openssl.OpenSSL_ECPrivateKey import OpenSSL_ECPrivateKey from .openssl.OpenSSL import openssl as o -from tack.util.PEMEncoder import PEMEncoder class ECPrivateKey: - - @staticmethod - def generateECKeyPair(): - if o.enabled: - return OpenSSL_ECPrivateKey.generateECKeyPair() - assert(False) @staticmethod def new(rawPrivateKey, rawPublicKey): diff --git a/tack/crypto/openssl/OpenSSL_AES.py b/tack/crypto/openssl/OpenSSL_AES.py index 9a85954..42f11d3 100644 --- a/tack/crypto/openssl/OpenSSL_AES.py +++ b/tack/crypto/openssl/OpenSSL_AES.py @@ -50,7 +50,7 @@ def _transform(self, inBytes, encrypt): o.EVP_CIPHER_CTX_cleanup(ctx) o.EVP_CIPHER_CTX_free(ctx) - # Update the chaining + # Update the CBC chaining if encrypt: self.IV = outBytes[-self.blockSize:] else: diff --git a/tack/crypto/openssl/OpenSSL_ECGenerator.py b/tack/crypto/openssl/OpenSSL_ECGenerator.py new file mode 100644 index 0000000..249cc1c --- /dev/null +++ b/tack/crypto/openssl/OpenSSL_ECGenerator.py @@ -0,0 +1,39 @@ +import math, ctypes +from tack.compat import bytesToStr +from .OpenSSL_ECPublicKey import OpenSSL_ECPublicKey +from .OpenSSL_ECPrivateKey import OpenSSL_ECPrivateKey +from .OpenSSL import openssl as o + +class OpenSSL_ECGenerator: + + @staticmethod + def generateECKeyPair(): + try: + ec_key, ec_group = None, None + + # Generate the new key + ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) + o.EC_KEY_generate_key(ec_key) + + # Extract the key's public and private values as strings + # into pubBuf and privBuf + pubBuf = bytesToStr(bytearray(1+64)) # [0x04] ... + privBuf = bytesToStr(bytearray(32)) + + ec_point = o.EC_KEY_get0_public_key(ec_key) + ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) + o.EC_POINT_point2oct(ec_group, ec_point, o.POINT_CONVERSION_UNCOMPRESSED, pubBuf, 65, None) + + bignum = o.EC_KEY_get0_private_key(ec_key) + privLen = o.BN_bn2bin(bignum, privBuf) + + # Convert the public and private keys into fixed-length 64 and 32 byte arrays + # Leading zeros are added to priv key, leading byte (0x04) stripped from pub key + rawPublicKey = bytearray(pubBuf[1:65]) + rawPrivateKey = bytearray(32-privLen) + bytearray(privBuf[:privLen]) + + return (OpenSSL_ECPublicKey(rawPublicKey, ec_key), + OpenSSL_ECPrivateKey(rawPrivateKey, rawPublicKey, ec_key)) + finally: + o.EC_KEY_free(ec_key) + o.EC_GROUP_free(ec_group) diff --git a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py index 76b8cb6..b267ae1 100644 --- a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py @@ -8,38 +8,6 @@ from tack.util.PEMEncoder import PEMEncoder class OpenSSL_ECPrivateKey: - - @staticmethod - def generateECKeyPair(): - try: - ec_key, ec_group = None, None - - # Generate the new key - ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) - o.EC_KEY_generate_key(ec_key) - - # Extract the key's public and private values as byte strings - pubBuf = bytesToStr(bytearray(1+64)) # [0x04] ... - privBuf = bytesToStr(bytearray(32)) - - ec_point = o.EC_KEY_get0_public_key(ec_key) - ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) - o.EC_POINT_point2oct(ec_group, ec_point, o.POINT_CONVERSION_UNCOMPRESSED, pubBuf, 65, None) - - bignum = o.EC_KEY_get0_private_key(ec_key) - privLen = o.BN_bn2bin(bignum, privBuf) - - # Convert the public and private keys into fixed-length 64 and 32 byte arrays - # Leading zeros are added to priv key, leading byte (0x04) stripped from pub key - rawPublicKey = bytearray(pubBuf[1:65]) - rawPrivateKey = bytearray(32-privLen) + bytearray(privBuf[:privLen]) - - return (OpenSSL_ECPublicKey(rawPublicKey, ec_key), - OpenSSL_ECPrivateKey(rawPrivateKey, rawPublicKey, ec_key)) - finally: - o.EC_KEY_free(ec_key) - o.EC_GROUP_free(ec_group) - def __init__(self, rawPrivateKey, rawPublicKey, ec_key=None): self.ec_key = None # In case of early destruction From 3b512be8002285334822bf106e1cccff51080cb2 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sun, 13 May 2012 23:02:52 +0300 Subject: [PATCH 39/94] Added Python AES. --- tack/crypto/AES.py | 4 +- tack/crypto/python/Python_AES.py | 56 +++++ tack/crypto/python/__init__.py | 0 tack/crypto/python/rijndael.py | 407 +++++++++++++++++++++++++++++++ 4 files changed, 466 insertions(+), 1 deletion(-) create mode 100644 tack/crypto/python/Python_AES.py create mode 100644 tack/crypto/python/__init__.py create mode 100644 tack/crypto/python/rijndael.py diff --git a/tack/crypto/AES.py b/tack/crypto/AES.py index e8df8aa..87215c1 100644 --- a/tack/crypto/AES.py +++ b/tack/crypto/AES.py @@ -1,5 +1,6 @@ from .openssl.OpenSSL import openssl from .openssl.OpenSSL_AES import OpenSSL_AES +from .python.Python_AES import Python_AES class AES: @@ -7,4 +8,5 @@ class AES: def new(key, IV): if openssl.enabled: return OpenSSL_AES(key, IV) - assert(False) + else: + return Python_AES(key, IV) diff --git a/tack/crypto/python/Python_AES.py b/tack/crypto/python/Python_AES.py new file mode 100644 index 0000000..97008de --- /dev/null +++ b/tack/crypto/python/Python_AES.py @@ -0,0 +1,56 @@ + +from .rijndael import rijndael + +class Python_AES: + def __init__(self, key, IV): + self.rijndael = rijndael(key, 16) + self.IV = IV + + def encrypt(self, plaintextBytes): + assert(len(plaintextBytes) % 16 == 0) + ciphertextBytes = plaintextBytes[:] + chainBytes = self.IV + + #CBC Mode: For each block... + for x in range(len(ciphertextBytes)//16): + + #XOR with the chaining block + blockBytes = ciphertextBytes[x*16 : (x*16)+16] + for y in range(16): + blockBytes[y] ^= chainBytes[y] + + #Encrypt it + encryptedBytes = self.rijndael.encrypt(blockBytes) + + #Overwrite the input with the output + for y in range(16): + ciphertextBytes[(x*16)+y] = encryptedBytes[y] + + #Set the next chaining block + chainBytes = encryptedBytes + + self.IV = chainBytes + return ciphertextBytes + + def decrypt(self, ciphertextBytes): + assert(len(ciphertextBytes) % 16 == 0) + plaintextBytes = ciphertextBytes[:] + chainBytes = self.IV + + #CBC Mode: For each block... + for x in range(len(plaintextBytes)//16): + + #Decrypt it + blockBytes = plaintextBytes[x*16 : (x*16)+16] + decryptedBytes = self.rijndael.decrypt(blockBytes) + + #XOR with the chaining block and overwrite the input with output + for y in range(16): + decryptedBytes[y] ^= chainBytes[y] + plaintextBytes[(x*16)+y] = decryptedBytes[y] + + #Set the next chaining block + chainBytes = blockBytes + + self.IV = chainBytes + return plaintextBytes diff --git a/tack/crypto/python/__init__.py b/tack/crypto/python/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tack/crypto/python/rijndael.py b/tack/crypto/python/rijndael.py new file mode 100644 index 0000000..cca8faa --- /dev/null +++ b/tack/crypto/python/rijndael.py @@ -0,0 +1,407 @@ +# Authors: +# Bram Cohen - main author +# Trevor Perrin - minor changes for Python compatibility +# +# See the LICENSE file for legal information regarding use of this file. +# Also see Bram Cohen's statement below + +################ RIJNDAEL ### +# Trevor edited below code for python 3, and ease of packaging + +""" +A pure python (slow) implementation of rijndael with a decent interface + +To include - + +from rijndael import rijndael + +To do a key setup - + +r = rijndael(key, block_size = 16) + +key must be a string of length 16, 24, or 32 +blocksize must be 16, 24, or 32. Default is 16 + +To use - + +ciphertext = r.encrypt(plaintext) +plaintext = r.decrypt(ciphertext) + +If any strings are of the wrong length a ValueError is thrown +""" + +# ported from the Java reference code by Bram Cohen, bram@gawth.com, April 2001 +# this code is public domain, unless someone makes +# an intellectual property claim against the reference +# code, in which case it can be made public domain by +# deleting all the comments and renaming all the variables + +import copy +import string + + + +#TREV 2011 - is this still needed? seems not +#----------------------- +#TREV - ADDED BECAUSE THERE'S WARNINGS ABOUT INT OVERFLOW BEHAVIOR CHANGING IN +#2.4..... +#import os +#if os.name != "java": +# import exceptions +# if hasattr(exceptions, "FutureWarning"): +# import warnings +# warnings.filterwarnings("ignore", category=FutureWarning, append=1) +#----------------------- + + + +shifts = [[[0, 0], [1, 3], [2, 2], [3, 1]], + [[0, 0], [1, 5], [2, 4], [3, 3]], + [[0, 0], [1, 7], [3, 5], [4, 4]]] + +# [keysize][block_size] +num_rounds = {16: {16: 10, 24: 12, 32: 14}, +24: {16: 12, 24: 12, 32: 14}, 32: {16: 14, 24: 14, 32: 14}} + +A = [[1, 1, 1, 1, 1, 0, 0, 0], + [0, 1, 1, 1, 1, 1, 0, 0], + [0, 0, 1, 1, 1, 1, 1, 0], + [0, 0, 0, 1, 1, 1, 1, 1], + [1, 0, 0, 0, 1, 1, 1, 1], + [1, 1, 0, 0, 0, 1, 1, 1], + [1, 1, 1, 0, 0, 0, 1, 1], + [1, 1, 1, 1, 0, 0, 0, 1]] + +# produce log and alog tables, needed for multiplying in the +# field GF(2^m) (generator = 3) +alog = [1] +for i in range(255): + j = (alog[-1] << 1) ^ alog[-1] + if j & 0x100 != 0: + j ^= 0x11B + alog.append(j) + +log = [0] * 256 +for i in range(1, 255): + log[alog[i]] = i + +# multiply two elements of GF(2^m) +def mul(a, b): + if a == 0 or b == 0: + return 0 + return alog[(log[a & 0xFF] + log[b & 0xFF]) % 255] + +# substitution box based on F^{-1}(x) +box = [[0] * 8 for i in range(256)] +box[1][7] = 1 +for i in range(2, 256): + j = alog[255 - log[i]] + for t in range(8): + box[i][t] = (j >> (7 - t)) & 0x01 + +B = [0, 1, 1, 0, 0, 0, 1, 1] + +# affine transform: box[i] <- B + A*box[i] +cox = [[0] * 8 for i in range(256)] +for i in range(256): + for t in range(8): + cox[i][t] = B[t] + for j in range(8): + cox[i][t] ^= A[t][j] * box[i][j] + +# S-boxes and inverse S-boxes +S = [0] * 256 +Si = [0] * 256 +for i in range(256): + S[i] = cox[i][0] << 7 + for t in range(1, 8): + S[i] ^= cox[i][t] << (7-t) + Si[S[i] & 0xFF] = i + +# T-boxes +G = [[2, 1, 1, 3], + [3, 2, 1, 1], + [1, 3, 2, 1], + [1, 1, 3, 2]] + +AA = [[0] * 8 for i in range(4)] + +for i in range(4): + for j in range(4): + AA[i][j] = G[i][j] + AA[i][i+4] = 1 + +for i in range(4): + pivot = AA[i][i] + if pivot == 0: + t = i + 1 + while AA[t][i] == 0 and t < 4: + t += 1 + assert t != 4, 'G matrix must be invertible' + for j in range(8): + AA[i][j], AA[t][j] = AA[t][j], AA[i][j] + pivot = AA[i][i] + for j in range(8): + if AA[i][j] != 0: + AA[i][j] = alog[(255 + log[AA[i][j] & 0xFF] - log[pivot & 0xFF]) % 255] + for t in range(4): + if i != t: + for j in range(i+1, 8): + AA[t][j] ^= mul(AA[i][j], AA[t][i]) + AA[t][i] = 0 + +iG = [[0] * 4 for i in range(4)] + +for i in range(4): + for j in range(4): + iG[i][j] = AA[i][j + 4] + +def mul4(a, bs): + if a == 0: + return 0 + r = 0 + for b in bs: + r <<= 8 + if b != 0: + r = r | mul(a, b) + return r + +T1 = [] +T2 = [] +T3 = [] +T4 = [] +T5 = [] +T6 = [] +T7 = [] +T8 = [] +U1 = [] +U2 = [] +U3 = [] +U4 = [] + +for t in range(256): + s = S[t] + T1.append(mul4(s, G[0])) + T2.append(mul4(s, G[1])) + T3.append(mul4(s, G[2])) + T4.append(mul4(s, G[3])) + + s = Si[t] + T5.append(mul4(s, iG[0])) + T6.append(mul4(s, iG[1])) + T7.append(mul4(s, iG[2])) + T8.append(mul4(s, iG[3])) + + U1.append(mul4(t, iG[0])) + U2.append(mul4(t, iG[1])) + U3.append(mul4(t, iG[2])) + U4.append(mul4(t, iG[3])) + +# round constants +rcon = [1] +r = 1 +for t in range(1, 30): + r = mul(2, r) + rcon.append(r) + +del A +del AA +del pivot +del B +del G +del box +del log +del alog +del i +del j +del r +del s +del t +del mul +del mul4 +del cox +del iG + +class rijndael: + def __init__(self, key, block_size = 16): + if block_size != 16 and block_size != 24 and block_size != 32: + raise ValueError('Invalid block size: ' + str(block_size)) + if len(key) != 16 and len(key) != 24 and len(key) != 32: + raise ValueError('Invalid key size: ' + str(len(key))) + self.block_size = block_size + + ROUNDS = num_rounds[len(key)][block_size] + BC = block_size // 4 + # encryption round keys + Ke = [[0] * BC for i in range(ROUNDS + 1)] + # decryption round keys + Kd = [[0] * BC for i in range(ROUNDS + 1)] + ROUND_KEY_COUNT = (ROUNDS + 1) * BC + KC = len(key) // 4 + + # copy user material bytes into temporary ints + tk = [] + for i in range(0, KC): + tk.append((key[i * 4] << 24) | (key[i * 4 + 1] << 16) | + (key[i * 4 + 2]) << 8 | key[i * 4 + 3]) + + # copy values into round key arrays + t = 0 + j = 0 + while j < KC and t < ROUND_KEY_COUNT: + Ke[t // BC][t % BC] = tk[j] + Kd[ROUNDS - (t // BC)][t % BC] = tk[j] + j += 1 + t += 1 + tt = 0 + rconpointer = 0 + while t < ROUND_KEY_COUNT: + # extrapolate using phi (the round key evolution function) + tt = tk[KC - 1] + tk[0] ^= (S[(tt >> 16) & 0xFF] & 0xFF) << 24 ^ \ + (S[(tt >> 8) & 0xFF] & 0xFF) << 16 ^ \ + (S[ tt & 0xFF] & 0xFF) << 8 ^ \ + (S[(tt >> 24) & 0xFF] & 0xFF) ^ \ + (rcon[rconpointer] & 0xFF) << 24 + rconpointer += 1 + if KC != 8: + for i in range(1, KC): + tk[i] ^= tk[i-1] + else: + for i in range(1, KC // 2): + tk[i] ^= tk[i-1] + tt = tk[KC // 2 - 1] + tk[KC // 2] ^= (S[ tt & 0xFF] & 0xFF) ^ \ + (S[(tt >> 8) & 0xFF] & 0xFF) << 8 ^ \ + (S[(tt >> 16) & 0xFF] & 0xFF) << 16 ^ \ + (S[(tt >> 24) & 0xFF] & 0xFF) << 24 + for i in range(KC // 2 + 1, KC): + tk[i] ^= tk[i-1] + # copy values into round key arrays + j = 0 + while j < KC and t < ROUND_KEY_COUNT: + Ke[t // BC][t % BC] = tk[j] + Kd[ROUNDS - (t // BC)][t % BC] = tk[j] + j += 1 + t += 1 + # inverse MixColumn where needed + for r in range(1, ROUNDS): + for j in range(BC): + tt = Kd[r][j] + Kd[r][j] = U1[(tt >> 24) & 0xFF] ^ \ + U2[(tt >> 16) & 0xFF] ^ \ + U3[(tt >> 8) & 0xFF] ^ \ + U4[ tt & 0xFF] + self.Ke = Ke + self.Kd = Kd + + def encrypt(self, plaintext): + if len(plaintext) != self.block_size: + raise ValueError('wrong block length, expected ' + + str(self.block_size) + ' got ' + str(len(plaintext))) + Ke = self.Ke + + BC = self.block_size // 4 + ROUNDS = len(Ke) - 1 + if BC == 4: + SC = 0 + elif BC == 6: + SC = 1 + else: + SC = 2 + s1 = shifts[SC][1][0] + s2 = shifts[SC][2][0] + s3 = shifts[SC][3][0] + a = [0] * BC + # temporary work array + t = [] + # plaintext to ints + key + for i in range(BC): + t.append((plaintext[i * 4 ] << 24 | + plaintext[i * 4 + 1] << 16 | + plaintext[i * 4 + 2] << 8 | + plaintext[i * 4 + 3] ) ^ Ke[0][i]) + # apply round transforms + for r in range(1, ROUNDS): + for i in range(BC): + a[i] = (T1[(t[ i ] >> 24) & 0xFF] ^ + T2[(t[(i + s1) % BC] >> 16) & 0xFF] ^ + T3[(t[(i + s2) % BC] >> 8) & 0xFF] ^ + T4[ t[(i + s3) % BC] & 0xFF] ) ^ Ke[r][i] + t = copy.copy(a) + # last round is special + result = [] + for i in range(BC): + tt = Ke[ROUNDS][i] + result.append((S[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) + result.append((S[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) + result.append((S[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) + result.append((S[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF) + return bytearray(result) + + def decrypt(self, ciphertext): + if len(ciphertext) != self.block_size: + raise ValueError('wrong block length, expected ' + + str(self.block_size) + ' got ' + str(len(ciphertext))) + Kd = self.Kd + + BC = self.block_size // 4 + ROUNDS = len(Kd) - 1 + if BC == 4: + SC = 0 + elif BC == 6: + SC = 1 + else: + SC = 2 + s1 = shifts[SC][1][1] + s2 = shifts[SC][2][1] + s3 = shifts[SC][3][1] + a = [0] * BC + # temporary work array + t = [0] * BC + # ciphertext to ints + key + for i in range(BC): + t[i] = (ciphertext[i * 4 ] << 24 | + ciphertext[i * 4 + 1] << 16 | + ciphertext[i * 4 + 2] << 8 | + ciphertext[i * 4 + 3] ) ^ Kd[0][i] + # apply round transforms + for r in range(1, ROUNDS): + for i in range(BC): + a[i] = (T5[(t[ i ] >> 24) & 0xFF] ^ + T6[(t[(i + s1) % BC] >> 16) & 0xFF] ^ + T7[(t[(i + s2) % BC] >> 8) & 0xFF] ^ + T8[ t[(i + s3) % BC] & 0xFF] ) ^ Kd[r][i] + t = copy.copy(a) + # last round is special + result = [] + for i in range(BC): + tt = Kd[ROUNDS][i] + result.append((Si[(t[ i ] >> 24) & 0xFF] ^ (tt >> 24)) & 0xFF) + result.append((Si[(t[(i + s1) % BC] >> 16) & 0xFF] ^ (tt >> 16)) & 0xFF) + result.append((Si[(t[(i + s2) % BC] >> 8) & 0xFF] ^ (tt >> 8)) & 0xFF) + result.append((Si[ t[(i + s3) % BC] & 0xFF] ^ tt ) & 0xFF) + return bytearray(result) + +def encrypt(key, block): + return rijndael(key, len(block)).encrypt(block) + +def decrypt(key, block): + return rijndael(key, len(block)).decrypt(block) + +def testRijndael(): + print("Testing RIJNDAEL") + def t(kl, bl): + b = bytearray(b'b') * bl + r = rijndael(bytearray(b'a') * kl, bl) + assert r.decrypt(r.encrypt(b)) == b + t(16, 16) + t(16, 24) + t(16, 32) + t(24, 16) + t(24, 24) + t(24, 32) + t(32, 16) + t(32, 24) + t(32, 32) + return 1 From 0e9f1a2d90b582f8bcbfa50437392ce25e6ce8b7 Mon Sep 17 00:00:00 2001 From: Trevor Date: Mon, 14 May 2012 00:07:22 +0300 Subject: [PATCH 40/94] Python crypto (AES and ECDSA) --- setup.py | 2 +- tack.py | 1 - tack/commands/HelpCommand.py | 5 +- tack/crypto/ECGenerator.py | 4 +- tack/crypto/ECPrivateKey.py | 4 +- tack/crypto/ECPublicKey.py | 5 +- tack/crypto/openssl/OpenSSL_ECPrivateKey.py | 6 +- tack/crypto/openssl/OpenSSL_ECPublicKey.py | 2 - tack/crypto/python/Python_ECGenerator.py | 26 + tack/crypto/python/Python_ECPrivateKey.py | 53 ++ tack/crypto/python/Python_ECPublicKey.py | 37 ++ tack/crypto/python/cryptomath.py | 54 ++ tack/crypto/python/ecdsa.py | 527 +++++++++++++++++ tack/crypto/python/ecdsa_wrappers.py | 233 ++++++++ tack/crypto/python/ellipticcurve.py | 280 +++++++++ tack/crypto/python/numbertheory.py | 622 ++++++++++++++++++++ 16 files changed, 1848 insertions(+), 13 deletions(-) create mode 100644 tack/crypto/python/Python_ECGenerator.py create mode 100644 tack/crypto/python/Python_ECPrivateKey.py create mode 100644 tack/crypto/python/Python_ECPublicKey.py create mode 100644 tack/crypto/python/cryptomath.py create mode 100644 tack/crypto/python/ecdsa.py create mode 100644 tack/crypto/python/ecdsa_wrappers.py create mode 100644 tack/crypto/python/ellipticcurve.py create mode 100644 tack/crypto/python/numbertheory.py diff --git a/setup.py b/setup.py index 98ea860..26750b3 100755 --- a/setup.py +++ b/setup.py @@ -18,7 +18,7 @@ license="public domain", scripts=["tack/tack"], packages=["tack", "tack/commands", "tack/crypto", "tack/crypto/openssl", - "tack/structures", "tack/tls", "tack/util"]) + "tack/crypto/python", "tack/structures", "tack/tls", "tack/util"]) print "Cleaning up..." if os.path.exists("build/"): diff --git a/tack.py b/tack.py index df4bd2c..6d10fb4 100755 --- a/tack.py +++ b/tack.py @@ -13,7 +13,6 @@ openssl.initialize() - if __name__ == '__main__': if len(sys.argv) < 2: HelpCommand.printGeneralUsage("Missing command") diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index 3b3483f..ed0b765 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -38,7 +38,10 @@ def printHelp(): @staticmethod def printGeneralUsage(message=None): - cryptoVersion = "(%s)" % o.SSLeay_version(0) + if o.enabled: + cryptoVersion = "(%s)" % o.SSLeay_version(0) + else: + cryptoVersion = "(python crypto)" if message: print "Error: %s\n" % message sys.stdout.write( diff --git a/tack/crypto/ECGenerator.py b/tack/crypto/ECGenerator.py index 339d9b9..7409f5b 100644 --- a/tack/crypto/ECGenerator.py +++ b/tack/crypto/ECGenerator.py @@ -1,4 +1,5 @@ from .openssl.OpenSSL_ECGenerator import OpenSSL_ECGenerator +from .python.Python_ECGenerator import Python_ECGenerator from .openssl.OpenSSL import openssl as o class ECGenerator: @@ -7,4 +8,5 @@ class ECGenerator: def generateECKeyPair(): if o.enabled: return OpenSSL_ECGenerator.generateECKeyPair() - assert(False) + else: + return Python_ECGenerator.generateECKeyPair() diff --git a/tack/crypto/ECPrivateKey.py b/tack/crypto/ECPrivateKey.py index a7142cd..a153568 100644 --- a/tack/crypto/ECPrivateKey.py +++ b/tack/crypto/ECPrivateKey.py @@ -1,3 +1,4 @@ +from .python.Python_ECPrivateKey import Python_ECPrivateKey from .openssl.OpenSSL_ECPrivateKey import OpenSSL_ECPrivateKey from .openssl.OpenSSL import openssl as o @@ -7,4 +8,5 @@ class ECPrivateKey: def new(rawPrivateKey, rawPublicKey): if o.enabled: return OpenSSL_ECPrivateKey(rawPrivateKey, rawPublicKey) - assert(False) + else: + return Python_ECPrivateKey(rawPrivateKey, rawPublicKey) diff --git a/tack/crypto/ECPublicKey.py b/tack/crypto/ECPublicKey.py index c6388dd..d8e6f51 100644 --- a/tack/crypto/ECPublicKey.py +++ b/tack/crypto/ECPublicKey.py @@ -1,6 +1,6 @@ +from .python.Python_ECPublicKey import Python_ECPublicKey from .openssl.OpenSSL import openssl as o from .openssl.OpenSSL_ECPublicKey import OpenSSL_ECPublicKey -from tack.util.PEMEncoder import PEMEncoder class ECPublicKey: @@ -8,4 +8,5 @@ class ECPublicKey: def new(rawPublicKey): if o.enabled: return OpenSSL_ECPublicKey(rawPublicKey) - assert(False) + else: + return Python_ECPublicKey(rawPublicKey) \ No newline at end of file diff --git a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py index b267ae1..86cb665 100644 --- a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py @@ -1,7 +1,5 @@ -import math, ctypes -from tack.compat import a2b_hex +import ctypes from tack.compat import bytesToStr -from tack.crypto.ASN1 import toAsn1IntBytes, asn1Length, ASN1Parser from tack.crypto.Digest import Digest from .OpenSSL_ECPublicKey import OpenSSL_ECPublicKey from .OpenSSL import openssl as o @@ -41,7 +39,7 @@ def sign(self, data): rBytes = bytearray(32-rLen) + bytearray(rBuf[:rLen]) sBytes = bytearray(32-sLen) + bytearray(sBuf[:sLen]) - sigBytes = rBytes + sBytes + sigBytes = rBytes + sBytes finally: o.ECDSA_SIG_free(ecdsa_sig) diff --git a/tack/crypto/openssl/OpenSSL_ECPublicKey.py b/tack/crypto/openssl/OpenSSL_ECPublicKey.py index 192d64f..caf028a 100644 --- a/tack/crypto/openssl/OpenSSL_ECPublicKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPublicKey.py @@ -1,8 +1,6 @@ import ctypes -from tack.compat import a2b_hex from tack.compat import b2a_base32 from tack.compat import bytesToStr -from tack.crypto.ASN1 import toAsn1IntBytes, asn1Length from tack.crypto.Digest import Digest from .OpenSSL import openssl as o from tack.util.PEMEncoder import PEMEncoder diff --git a/tack/crypto/python/Python_ECGenerator.py b/tack/crypto/python/Python_ECGenerator.py new file mode 100644 index 0000000..1d5c4c4 --- /dev/null +++ b/tack/crypto/python/Python_ECGenerator.py @@ -0,0 +1,26 @@ +import os +from .cryptomath import numberToBytes, bytesToNumber +from .ecdsa import generator_256 +from .Python_ECPublicKey import Python_ECPublicKey +from .Python_ECPrivateKey import Python_ECPrivateKey + +class Python_ECGenerator: + + @staticmethod + def generateECKeyPair(): + # ECDSA key generation per FIPS 186-3 B.4.1 + # (except we use 32 extra random bytes instead of 8 before reduction, + # to be slightly paranoid in case there's an os.urandom bias) + # Random bytes taken from os.urandom + # REVIEW THIS CAREFULLY! CHANGE AT YOUR PERIL! + + # Reduce a 64-byte value from os.urandom to a 32-byte secret key + c = bytesToNumber(bytearray(os.urandom(64))) + n = generator_256.order() + d = (c % (n-1))+1 + rawPrivateKey = numberToBytes(d, 32) + publicKeyPoint = generator_256 * d + rawPublicKey = (numberToBytes(publicKeyPoint.x(), 32) + + numberToBytes(publicKeyPoint.y(), 32)) + return (Python_ECPublicKey(rawPublicKey), + Python_ECPrivateKey(rawPrivateKey, rawPublicKey)) \ No newline at end of file diff --git a/tack/crypto/python/Python_ECPrivateKey.py b/tack/crypto/python/Python_ECPrivateKey.py new file mode 100644 index 0000000..9e48b93 --- /dev/null +++ b/tack/crypto/python/Python_ECPrivateKey.py @@ -0,0 +1,53 @@ +import os +from tack.crypto.Digest import Digest +from .cryptomath import numberToBytes, bytesToNumber +from .ecdsa import generator_256, Public_key, Private_key, Point +from .Python_ECPublicKey import Python_ECPublicKey + +class Python_ECPrivateKey: + + def __init__(self, rawPrivateKey, rawPublicKey): + assert(rawPrivateKey is not None and rawPublicKey is not None) + assert(len(rawPrivateKey)==32 and len(rawPublicKey) == 64) + + self.rawPrivateKey = rawPrivateKey + self.rawPublicKey = rawPublicKey + + def sign(self, data): + privateKeyNum = bytesToNumber(self.rawPrivateKey) + hash = Digest.SHA256(data) + g = generator_256 + n = g.order() + x = bytesToNumber(self.rawPublicKey[ : 32]) + y = bytesToNumber(self.rawPublicKey[32 : ]) + pubkey = Public_key(g, Point(g.curve(), x,y)) + privkey = Private_key(pubkey, privateKeyNum) + + # Generating random nonce k per FIPS 186-3 B.5.1: + # (except we use 32 extra bytes instead of 8 before reduction) + # Random bytes taken from os.urandom as well as HMAC(privkey,hash) + # REVIEW THIS CAREFULLY!!! CHANGE AT YOUR PERIL!!! + + # 64 random bytes and HMAC(rawPrivateKey,hash) are used as key for an + # HMAC PRF to generate a 64-byte value which is reduced modulo + # n to produce the nonce + # + # The use of HMAC(rawPrivateKey,hash) means this construction + # will be secure even if os.urandom fails, however we include + # os.urandom anyways out of an excess of caution + randBytes0 = bytearray(os.urandom(64)) + randBytes0 += Digest.HMAC_SHA256(self.rawPrivateKey, hash) + randBytes = Digest.HMAC_SHA256(randBytes0, bytearray([1])) + randBytes += Digest.HMAC_SHA256(randBytes0, bytearray([2])) + c = bytesToNumber(randBytes) + k = (c % (n-1))+1 + hashNum = bytesToNumber(hash) + sig = privkey.sign(hashNum, k) + sigBytes = numberToBytes(sig.r, 32) + numberToBytes(sig.s, 32) + # Double-check the signature before returning + assert(Python_ECPublicKey(self.rawPublicKey).verify(data, sigBytes)) + return sigBytes + + def getRawKey(self): + return self.rawPrivateKey + diff --git a/tack/crypto/python/Python_ECPublicKey.py b/tack/crypto/python/Python_ECPublicKey.py new file mode 100644 index 0000000..5ac96ea --- /dev/null +++ b/tack/crypto/python/Python_ECPublicKey.py @@ -0,0 +1,37 @@ +import ctypes +from tack.compat import b2a_base32 +from tack.compat import bytesToStr +from tack.crypto.Digest import Digest +from .ecdsa import Public_key, Point, generator_256, Signature +from .cryptomath import bytesToNumber, numberToBytes + +class Python_ECPublicKey: + + def __init__(self, rawPublicKey): + assert(rawPublicKey is not None) + assert(len(rawPublicKey) == 64) + + self.rawPublicKey = rawPublicKey + + def verify(self, data, signature): + assert(len(signature) == 64) + hashNum = bytesToNumber(Digest.SHA256(data)) + g = generator_256 + x = bytesToNumber(self.rawPublicKey[ : 32]) + y = bytesToNumber(self.rawPublicKey[32 : ]) + pubkey = Public_key(g, Point(g.curve(), x,y)) + sig = Signature(bytesToNumber(signature[:32]), + bytesToNumber(signature[32:])) + return pubkey.verifies(hashNum, sig) + + def getRawKey(self): + return self.rawPublicKey + + def getFingerprint(self): + digest = Digest.SHA256(self.rawPublicKey) + assert(len(digest) == 32) + s = b2a_base32(digest).lower()[:25] + return "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) + + def __str__(self): + return self.getFingerprint() \ No newline at end of file diff --git a/tack/crypto/python/cryptomath.py b/tack/crypto/python/cryptomath.py new file mode 100644 index 0000000..4d43e6c --- /dev/null +++ b/tack/crypto/python/cryptomath.py @@ -0,0 +1,54 @@ +# Author: Trevor Perrin +# See the LICENSE file for legal information regarding use of this file. + +################ CRYPTOMATH ### + +import math, hashlib, hmac + +def bytesToNumber(bytes): + "Convert a sequence of bytes (eg bytearray) into integer." + total = 0 + multiplier = 1 + for count in range(len(bytes)-1, -1, -1): + byte = bytes[count] + total += multiplier * byte + multiplier *= 256 + return total + +def numberToBytes(n, howManyBytes=None): + """Convert an integer into a bytearray, zero-pad to howManyBytes. + + The returned bytearray may be smaller than howManyBytes, but will + not be larger. The returned bytearray will contain a big-endian + encoding of the input integer (n). + """ + if not howManyBytes: + howManyBytes = numBytes(n) + bytes = bytearray(howManyBytes) + for count in range(howManyBytes-1, -1, -1): + bytes[count] = int(n % 256) + n >>= 8 + return bytes + +def stringToNumber(s): + "Convert a string - interpreted as a sequence of bytes - into integer." + return bytesToNumber(bytearray(s)) + +def numBits(n): + "Return the number of bits needed to represent the integer n." + if n==0: + return 0 + s = "%x" % n + return ((len(s)-1)*4) + \ + {'0':0, '1':1, '2':2, '3':2, + '4':3, '5':3, '6':3, '7':3, + '8':4, '9':4, 'a':4, 'b':4, + 'c':4, 'd':4, 'e':4, 'f':4, + }[s[0]] + +def numBytes(n): + "Return the number of bytes needed to represent the integer n." + if n==0: + return 0 + bits = numBits(n) + return int(math.ceil(bits / 8.0)) diff --git a/tack/crypto/python/ecdsa.py b/tack/crypto/python/ecdsa.py new file mode 100644 index 0000000..e5d9cbd --- /dev/null +++ b/tack/crypto/python/ecdsa.py @@ -0,0 +1,527 @@ +# Authors: +# Peter Pearson - main author +# Trevor Perrin - minor changes for Python compatibility +# +# See the LICENSE file for legal information regarding use of this file. +# Also see Peter Pearson's statement below + +from .numbertheory import * +from .ellipticcurve import * +#from .compat import * + +################ ECDSA ### +#! /usr/bin/env python +""" +Implementation of Elliptic-Curve Digital Signatures. + +Classes and methods for elliptic-curve signatures: +private keys, public keys, signatures, +NIST prime-modulus curves with modulus lengths of +192, 224, 256, 384, and 521 bits. + +Example: + + # (In real-life applications, you would probably want to + # protect against defects in SystemRandom.) + from random import SystemRandom + randrange = SystemRandom().randrange + + # Generate a public/private key pair using the NIST Curve P-192: + + g = generator_192 + n = g.order() + secret = randrange( 1, n ) + pubkey = Public_key( g, g * secret ) + privkey = Private_key( pubkey, secret ) + + # Signing a hash value: + + hash = randrange( 1, n ) + signature = privkey.sign( hash, randrange( 1, n ) ) + + # Verifying a signature for a hash value: + + if pubkey.verifies( hash, signature ): + print("Demo verification succeeded.") + else: + print("*** Demo verification failed.") + + # Verification fails if the hash value is modified: + + if pubkey.verifies( hash-1, signature ): + print("**** Demo verification failed to reject tampered hash.") + else: + print("Demo verification correctly rejected tampered hash.") + +Version of 2009.05.16. + +Revision history: + 2005.12.31 - Initial version. + 2008.11.25 - Substantial revisions introducing new classes. + 2009.05.16 - Warn against using random.randrange in real applications. + 2009.05.17 - Use random.SystemRandom by default. + +Written in 2005 by Peter Pearson and placed in the public domain. +""" + +class Signature( object ): + """ECDSA signature. + """ + def __init__( self, r, s ): + self.r = r + self.s = s + + + +class Public_key( object ): + """Public key for ECDSA. + """ + + def __init__( self, generator, point ): + """generator is the Point that generates the group, + point is the Point that defines the public key. + """ + + self.curve = generator.curve() + self.generator = generator + self.point = point + n = generator.order() + if not n: + raise RuntimeError("Generator point must have order.") + if not n * point == INFINITY: + raise RuntimeError("Generator point order is bad.") + if point.x() < 0 or n <= point.x() or point.y() < 0 or n <= point.y(): + raise RuntimeError("Generator point has x or y out of range.") + + + def verifies( self, hash, signature ): + """Verify that signature is a valid signature of hash. + Return True if the signature is valid. + """ + + # From X9.62 J.3.1. + + G = self.generator + n = G.order() + r = signature.r + s = signature.s + if r < 1 or r > n-1: return False + if s < 1 or s > n-1: return False + c = inverse_mod( s, n ) + u1 = ( hash * c ) % n + u2 = ( r * c ) % n + xy = u1 * G + u2 * self.point + v = xy.x() % n + return v == r + + + +class Private_key( object ): + """Private key for ECDSA. + """ + + def __init__( self, public_key, secret_multiplier ): + """public_key is of class Public_key; + secret_multiplier is a large integer. + """ + + self.public_key = public_key + self.secret_multiplier = secret_multiplier + + def sign( self, hash, random_k ): + """Return a signature for the provided hash, using the provided + random nonce. It is absolutely vital that random_k be an unpredictable + number in the range [1, self.public_key.point.order()-1]. If + an attacker can guess random_k, he can compute our private key from a + single signature. Also, if an attacker knows a few high-order + bits (or a few low-order bits) of random_k, he can compute our private + key from many signatures. The generation of nonces with adequate + cryptographic strength is very difficult and far beyond the scope + of this comment. + + May raise RuntimeError, in which case retrying with a new + random value k is in order. + """ + + G = self.public_key.generator + n = G.order() + k = random_k % n + p1 = k * G + r = p1.x() + if r == 0: raise RuntimeError("amazingly unlucky random number r") + s = ( inverse_mod( k, n ) * \ + ( hash + ( self.secret_multiplier * r ) % n ) ) % n + if s == 0: raise RuntimeError("amazingly unlucky random number s") + return Signature( r, s ) + + + +def int_to_string( x ): + """Convert integer x into a string of bytes, as per X9.62.""" + assert x >= 0 + if x == 0: return bytearray([0]) + result = bytearray() + while x > 0: + q, r = divmod( x, 256 ) + result = bytearray([r]) + result + x = q + return result + + +def string_to_int( s ): + """Convert a string of bytes into an integer, as per X9.62.""" + result = 0 + for c in s: result = 256 * result + c + return result + + +def digest_integer( m ): + """Convert an integer into a string of bytes, compute + its SHA-1 hash, and convert the result to an integer.""" + # + # I don't expect this function to be used much. I wrote + # it in order to be able to duplicate the examples + # in ECDSAVS. + # + #import sha + from hashlib import sha1 + return string_to_int( bytearray(sha1( compat26Str(int_to_string( m )) ).digest())) + + +def point_is_valid( generator, x, y ): + """Is (x,y) a valid public key based on the specified generator?""" + + # These are the tests specified in X9.62. + + n = generator.order() + curve = generator.curve() + if x < 0 or n <= x or y < 0 or n <= y: + return False + if not curve.contains_point( x, y ): + return False + if not n*Point( curve, x, y ) == \ + INFINITY: + return False + return True + +# TREV: Curve P-192 is included for the unit testing... eventually +# it would be better to convert the unit tests to P-256 +# NIST Curve P-192: +_p = 6277101735386680763835789423207666416083908700390324961279 +_r = 6277101735386680763835789423176059013767194773182842284081 +# s = 0x3045ae6fc8422f64ed579528d38120eae12196d5L +# c = 0x3099d2bbbfcb2538542dcd5fb078b6ef5f3d6fe2c745de65L +_b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1 +_Gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012 +_Gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811 + +curve_192 = CurveFp( _p, -3, _b ) +generator_192 = Point( curve_192, _Gx, _Gy, _r ) + +# NIST Curve P-256: +_p = 115792089210356248762697446949407573530086143415290314195533631308867097853951 +_r = 115792089210356248762697446949407573529996955224135760342422259061068512044369 +# s = 0xc49d360886e704936a6678e1139d26b7819f7e90L +# c = 0x7efba1662985be9403cb055c75d4f7e0ce8d84a9c5114abcaf3177680104fa0dL +_b = 0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b +_Gx = 0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296 +_Gy = 0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5 + +curve_256 = CurveFp( _p, -3, _b ) +generator_256 = Point( curve_256, _Gx, _Gy, _r ) + + +def testECDSA(): + print("Testing ECDSA") + import random + + def test_point_validity( generator, x, y, expected ): + """generator defines the curve; is (x,y) a point on + this curve? "expected" is True if the right answer is Yes.""" + if point_is_valid( generator, x, y ) == expected: + #print("Point validity tested as expected.") + pass + else: + print("*** Point validity test gave wrong result.") + assert() + + def test_signature_validity( Msg, Qx, Qy, R, S, expected ): + """Msg = message, Qx and Qy represent the base point on + elliptic curve c192, R and S are the signature, and + "expected" is True iff the signature is expected to be valid.""" + pubk = Public_key( generator_192, + Point( curve_192, Qx, Qy ) ) + got = pubk.verifies( digest_integer( Msg ), Signature( R, S ) ) + if got == expected: + #print("Signature tested as expected: got %s, expected %s." % \ + # ( got, expected )) + pass + else: + print("*** Signature test failed: got %s, expected %s." % \ + ( got, expected )) + assert() + + #print("NIST Curve P-192:") + p192 = generator_192 + + # From X9.62: + + d = 651056770906015076056810763456358567190100156695615665659 + Q = d * p192 + if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5: + #print("*** p192 * d came out wrong.") + assert() + + k = 6140507067065001063065065565667405560006161556565665656654 + R = k * p192 + if R.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ + or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: + #print("*** k * p192 came out wrong.") + assert() + + u1 = 2563697409189434185194736134579731015366492496392189760599 + u2 = 6266643813348617967186477710235785849136406323338782220568 + temp = u1 * p192 + u2 * Q + if temp.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ + or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: + print("*** u1 * p192 + u2 * Q came out wrong.") + assert() + + e = 968236873715988614170569073515315707566766479517 + pubk = Public_key( generator_192, generator_192 * d ) + privk = Private_key( pubk, d ) + sig = privk.sign( e, k ) + r, s = sig.r, sig.s + if r != 3342403536405981729393488334694600415596881826869351677613 \ + or s != 5735822328888155254683894997897571951568553642892029982342: + print("*** r or s came out wrong.") + assert() + + valid = pubk.verifies( e, sig ) + if valid: pass#print("Signature verified OK.") + else: print("*** Signature failed verification."); assert() + + valid = pubk.verifies( e-1, sig ) + if not valid: pass#print("Forgery was correctly rejected.") + else: print("*** Forgery was erroneously accepted."); assert() + + #print("Testing point validity, as per ECDSAVS.pdf B.2.2:") + + test_point_validity( \ + p192, \ + 0xcd6d0f029a023e9aaca429615b8f577abee685d8257cc83a, \ + 0x00019c410987680e9fb6c0b6ecc01d9a2647c8bae27721bacdfc, \ + False ) + + test_point_validity( + p192, \ + 0x00017f2fce203639e9eaf9fb50b81fc32776b30e3b02af16c73b, \ + 0x95da95c5e72dd48e229d4748d4eee658a9a54111b23b2adb, \ + False ) + + test_point_validity( + p192, \ + 0x4f77f8bc7fccbadd5760f4938746d5f253ee2168c1cf2792, \ + 0x000147156ff824d131629739817edb197717c41aab5c2a70f0f6, \ + False ) + + test_point_validity( + p192, \ + 0xc58d61f88d905293bcd4cd0080bcb1b7f811f2ffa41979f6, \ + 0x8804dc7a7c4c7f8b5d437f5156f3312ca7d6de8a0e11867f, \ + True ) + """ + test_point_validity( + p192, \ + 0xcdf56c1aa3d8afc53c521adf3ffb96734a6a630a4a5b5a70, \ + 0x97c1c44a5fb229007b5ec5d25f7413d170068ffd023caa4e, \ + True ) + + test_point_validity( + p192, \ + 0x89009c0dc361c81e99280c8e91df578df88cdf4b0cdedced, \ + 0x27be44a529b7513e727251f128b34262a0fd4d8ec82377b9, \ + True ) + + test_point_validity( + p192, \ + 0x6a223d00bd22c52833409a163e057e5b5da1def2a197dd15, \ + 0x7b482604199367f1f303f9ef627f922f97023e90eae08abf, \ + True ) + + test_point_validity( + p192, \ + 0x6dccbde75c0948c98dab32ea0bc59fe125cf0fb1a3798eda, \ + 0x0001171a3e0fa60cf3096f4e116b556198de430e1fbd330c8835, \ + False ) + + test_point_validity( + p192, \ + 0xd266b39e1f491fc4acbbbc7d098430931cfa66d55015af12, \ + 0x193782eb909e391a3148b7764e6b234aa94e48d30a16dbb2, \ + False ) + + test_point_validity( + p192, \ + 0x9d6ddbcd439baa0c6b80a654091680e462a7d1d3f1ffeb43, \ + 0x6ad8efc4d133ccf167c44eb4691c80abffb9f82b932b8caa, \ + False ) + + test_point_validity( + p192, \ + 0x146479d944e6bda87e5b35818aa666a4c998a71f4e95edbc, \ + 0xa86d6fe62bc8fbd88139693f842635f687f132255858e7f6, \ + False ) + + test_point_validity( + p192, \ + 0xe594d4a598046f3598243f50fd2c7bd7d380edb055802253, \ + 0x509014c0c4d6b536e3ca750ec09066af39b4c8616a53a923, \ + False ) + """ + + #print("Trying signature-verification tests from ECDSAVS.pdf B.2.4:") + #print("P-192:") + Msg = 0x84ce72aa8699df436059f052ac51b6398d2511e49631bcb7e71f89c499b9ee425dfbc13a5f6d408471b054f2655617cbbaf7937b7c80cd8865cf02c8487d30d2b0fbd8b2c4e102e16d828374bbc47b93852f212d5043c3ea720f086178ff798cc4f63f787b9c2e419efa033e7644ea7936f54462dc21a6c4580725f7f0e7d158 + Qx = 0xd9dbfb332aa8e5ff091e8ce535857c37c73f6250ffb2e7ac + Qy = 0x282102e364feded3ad15ddf968f88d8321aa268dd483ebc4 + R = 0x64dca58a20787c488d11d6dd96313f1b766f2d8efe122916 + S = 0x1ecba28141e84ab4ecad92f56720e2cc83eb3d22dec72479 + test_signature_validity( Msg, Qx, Qy, R, S, True ) + + Msg = 0x94bb5bacd5f8ea765810024db87f4224ad71362a3c28284b2b9f39fab86db12e8beb94aae899768229be8fdb6c4f12f28912bb604703a79ccff769c1607f5a91450f30ba0460d359d9126cbd6296be6d9c4bb96c0ee74cbb44197c207f6db326ab6f5a659113a9034e54be7b041ced9dcf6458d7fb9cbfb2744d999f7dfd63f4 + Qx = 0x3e53ef8d3112af3285c0e74842090712cd324832d4277ae7 + Qy = 0xcc75f8952d30aec2cbb719fc6aa9934590b5d0ff5a83adb7 + R = 0x8285261607283ba18f335026130bab31840dcfd9c3e555af + S = 0x356d89e1b04541afc9704a45e9c535ce4a50929e33d7e06c + test_signature_validity( Msg, Qx, Qy, R, S, True ) + + Msg = 0xf6227a8eeb34afed1621dcc89a91d72ea212cb2f476839d9b4243c66877911b37b4ad6f4448792a7bbba76c63bdd63414b6facab7dc71c3396a73bd7ee14cdd41a659c61c99b779cecf07bc51ab391aa3252386242b9853ea7da67fd768d303f1b9b513d401565b6f1eb722dfdb96b519fe4f9bd5de67ae131e64b40e78c42dd + Qx = 0x16335dbe95f8e8254a4e04575d736befb258b8657f773cb7 + Qy = 0x421b13379c59bc9dce38a1099ca79bbd06d647c7f6242336 + R = 0x4141bd5d64ea36c5b0bd21ef28c02da216ed9d04522b1e91 + S = 0x159a6aa852bcc579e821b7bb0994c0861fb08280c38daa09 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x16b5f93afd0d02246f662761ed8e0dd9504681ed02a253006eb36736b563097ba39f81c8e1bce7a16c1339e345efabbc6baa3efb0612948ae51103382a8ee8bc448e3ef71e9f6f7a9676694831d7f5dd0db5446f179bcb737d4a526367a447bfe2c857521c7f40b6d7d7e01a180d92431fb0bbd29c04a0c420a57b3ed26ccd8a + Qx = 0xfd14cdf1607f5efb7b1793037b15bdf4baa6f7c16341ab0b + Qy = 0x83fa0795cc6c4795b9016dac928fd6bac32f3229a96312c4 + R = 0x8dfdb832951e0167c5d762a473c0416c5c15bc1195667dc1 + S = 0x1720288a2dc13fa1ec78f763f8fe2ff7354a7e6fdde44520 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + """ + Msg = 0x08a2024b61b79d260e3bb43ef15659aec89e5b560199bc82cf7c65c77d39192e03b9a895d766655105edd9188242b91fbde4167f7862d4ddd61e5d4ab55196683d4f13ceb90d87aea6e07eb50a874e33086c4a7cb0273a8e1c4408f4b846bceae1ebaac1b2b2ea851a9b09de322efe34cebe601653efd6ddc876ce8c2f2072fb + Qx = 0x674f941dc1a1f8b763c9334d726172d527b90ca324db8828 + Qy = 0x65adfa32e8b236cb33a3e84cf59bfb9417ae7e8ede57a7ff + R = 0x9508b9fdd7daf0d8126f9e2bc5a35e4c6d800b5b804d7796 + S = 0x36f2bf6b21b987c77b53bb801b3435a577e3d493744bfab0 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x1843aba74b0789d4ac6b0b8923848023a644a7b70afa23b1191829bbe4397ce15b629bf21a8838298653ed0c19222b95fa4f7390d1b4c844d96e645537e0aae98afb5c0ac3bd0e4c37f8daaff25556c64e98c319c52687c904c4de7240a1cc55cd9756b7edaef184e6e23b385726e9ffcba8001b8f574987c1a3fedaaa83ca6d + Qx = 0x10ecca1aad7220b56a62008b35170bfd5e35885c4014a19f + Qy = 0x04eb61984c6c12ade3bc47f3c629ece7aa0a033b9948d686 + R = 0x82bfa4e82c0dfe9274169b86694e76ce993fd83b5c60f325 + S = 0xa97685676c59a65dbde002fe9d613431fb183e8006d05633 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x5a478f4084ddd1a7fea038aa9732a822106385797d02311aeef4d0264f824f698df7a48cfb6b578cf3da416bc0799425bb491be5b5ecc37995b85b03420a98f2c4dc5c31a69a379e9e322fbe706bbcaf0f77175e05cbb4fa162e0da82010a278461e3e974d137bc746d1880d6eb02aa95216014b37480d84b87f717bb13f76e1 + Qx = 0x6636653cb5b894ca65c448277b29da3ad101c4c2300f7c04 + Qy = 0xfdf1cbb3fc3fd6a4f890b59e554544175fa77dbdbeb656c1 + R = 0xeac2ddecddfb79931a9c3d49c08de0645c783a24cb365e1c + S = 0x3549fee3cfa7e5f93bc47d92d8ba100e881a2a93c22f8d50 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0xc598774259a058fa65212ac57eaa4f52240e629ef4c310722088292d1d4af6c39b49ce06ba77e4247b20637174d0bd67c9723feb57b5ead232b47ea452d5d7a089f17c00b8b6767e434a5e16c231ba0efa718a340bf41d67ea2d295812ff1b9277daacb8bc27b50ea5e6443bcf95ef4e9f5468fe78485236313d53d1c68f6ba2 + Qx = 0xa82bd718d01d354001148cd5f69b9ebf38ff6f21898f8aaa + Qy = 0xe67ceede07fc2ebfafd62462a51e4b6c6b3d5b537b7caf3e + R = 0x4d292486c620c3de20856e57d3bb72fcde4a73ad26376955 + S = 0xa85289591a6081d5728825520e62ff1c64f94235c04c7f95 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0xca98ed9db081a07b7557f24ced6c7b9891269a95d2026747add9e9eb80638a961cf9c71a1b9f2c29744180bd4c3d3db60f2243c5c0b7cc8a8d40a3f9a7fc910250f2187136ee6413ffc67f1a25e1c4c204fa9635312252ac0e0481d89b6d53808f0c496ba87631803f6c572c1f61fa049737fdacce4adff757afed4f05beb658 + Qx = 0x7d3b016b57758b160c4fca73d48df07ae3b6b30225126c2f + Qy = 0x4af3790d9775742bde46f8da876711be1b65244b2b39e7ec + R = 0x95f778f5f656511a5ab49a5d69ddd0929563c29cbc3a9e62 + S = 0x75c87fc358c251b4c83d2dd979faad496b539f9f2ee7a289 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x31dd9a54c8338bea06b87eca813d555ad1850fac9742ef0bbe40dad400e10288acc9c11ea7dac79eb16378ebea9490e09536099f1b993e2653cd50240014c90a9c987f64545abc6a536b9bd2435eb5e911fdfde2f13be96ea36ad38df4ae9ea387b29cced599af777338af2794820c9cce43b51d2112380a35802ab7e396c97a + Qx = 0x9362f28c4ef96453d8a2f849f21e881cd7566887da8beb4a + Qy = 0xe64d26d8d74c48a024ae85d982ee74cd16046f4ee5333905 + R = 0xf3923476a296c88287e8de914b0b324ad5a963319a4fe73b + S = 0xf0baeed7624ed00d15244d8ba2aede085517dbdec8ac65f5 + test_signature_validity( Msg, Qx, Qy, R, S, True ) + + Msg = 0xb2b94e4432267c92f9fdb9dc6040c95ffa477652761290d3c7de312283f6450d89cc4aabe748554dfb6056b2d8e99c7aeaad9cdddebdee9dbc099839562d9064e68e7bb5f3a6bba0749ca9a538181fc785553a4000785d73cc207922f63e8ce1112768cb1de7b673aed83a1e4a74592f1268d8e2a4e9e63d414b5d442bd0456d + Qx = 0xcc6fc032a846aaac25533eb033522824f94e670fa997ecef + Qy = 0xe25463ef77a029eccda8b294fd63dd694e38d223d30862f1 + R = 0x066b1d07f3a40e679b620eda7f550842a35c18b80c5ebe06 + S = 0xa0b0fb201e8f2df65e2c4508ef303bdc90d934016f16b2dc + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x4366fcadf10d30d086911de30143da6f579527036937007b337f7282460eae5678b15cccda853193ea5fc4bc0a6b9d7a31128f27e1214988592827520b214eed5052f7775b750b0c6b15f145453ba3fee24a085d65287e10509eb5d5f602c440341376b95c24e5c4727d4b859bfe1483d20538acdd92c7997fa9c614f0f839d7 + Qx = 0x955c908fe900a996f7e2089bee2f6376830f76a19135e753 + Qy = 0xba0c42a91d3847de4a592a46dc3fdaf45a7cc709b90de520 + R = 0x1f58ad77fc04c782815a1405b0925e72095d906cbf52a668 + S = 0xf2e93758b3af75edf784f05a6761c9b9a6043c66b845b599 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x543f8af57d750e33aa8565e0cae92bfa7a1ff78833093421c2942cadf9986670a5ff3244c02a8225e790fbf30ea84c74720abf99cfd10d02d34377c3d3b41269bea763384f372bb786b5846f58932defa68023136cd571863b304886e95e52e7877f445b9364b3f06f3c28da12707673fecb4b8071de06b6e0a3c87da160cef3 + Qx = 0x31f7fa05576d78a949b24812d4383107a9a45bb5fccdd835 + Qy = 0x8dc0eb65994a90f02b5e19bd18b32d61150746c09107e76b + R = 0xbe26d59e4e883dde7c286614a767b31e49ad88789d3a78ff + S = 0x8762ca831c1ce42df77893c9b03119428e7a9b819b619068 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0xd2e8454143ce281e609a9d748014dcebb9d0bc53adb02443a6aac2ffe6cb009f387c346ecb051791404f79e902ee333ad65e5c8cb38dc0d1d39a8dc90add5023572720e5b94b190d43dd0d7873397504c0c7aef2727e628eb6a74411f2e400c65670716cb4a815dc91cbbfeb7cfe8c929e93184c938af2c078584da045e8f8d1 + Qx = 0x66aa8edbbdb5cf8e28ceb51b5bda891cae2df84819fe25c0 + Qy = 0x0c6bc2f69030a7ce58d4a00e3b3349844784a13b8936f8da + R = 0xa4661e69b1734f4a71b788410a464b71e7ffe42334484f23 + S = 0x738421cf5e049159d69c57a915143e226cac8355e149afe9 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x6660717144040f3e2f95a4e25b08a7079c702a8b29babad5a19a87654bc5c5afa261512a11b998a4fb36b5d8fe8bd942792ff0324b108120de86d63f65855e5461184fc96a0a8ffd2ce6d5dfb0230cbbdd98f8543e361b3205f5da3d500fdc8bac6db377d75ebef3cb8f4d1ff738071ad0938917889250b41dd1d98896ca06fb + Qx = 0xbcfacf45139b6f5f690a4c35a5fffa498794136a2353fc77 + Qy = 0x6f4a6c906316a6afc6d98fe1f0399d056f128fe0270b0f22 + R = 0x9db679a3dafe48f7ccad122933acfe9da0970b71c94c21c1 + S = 0x984c2db99827576c0a41a5da41e07d8cc768bc82f18c9da9 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + """ + #print("Testing the example code:") + + # Building a public/private key pair from the NIST Curve P-192: + + g = generator_256 + n = g.order() + + # (random.SystemRandom is supposed to provide + # crypto-quality random numbers, but as Debian recently + # illustrated, a systems programmer can accidentally + # demolish this security, so in serious applications + # further precautions are appropriate.) + + randrange = random.SystemRandom().randrange + + secret = randrange( 1, n ) + pubkey = Public_key( g, g * secret ) + privkey = Private_key( pubkey, secret ) + + # Signing a hash value: + + hash = randrange( 1, n ) + signature = privkey.sign( hash, randrange( 1, n ) ) + + # Verifying a signature for a hash value: + + if pubkey.verifies( hash, signature ): + #print("Demo verification succeeded.") + pass + else: + print("*** Demo verification failed.") + assert() + + if pubkey.verifies( hash-1, signature ): + print("**** Demo verification failed to reject tampered hash.") + assert() + return 1 + diff --git a/tack/crypto/python/ecdsa_wrappers.py b/tack/crypto/python/ecdsa_wrappers.py new file mode 100644 index 0000000..932f1a2 --- /dev/null +++ b/tack/crypto/python/ecdsa_wrappers.py @@ -0,0 +1,233 @@ +# Author: Trevor Perrin +# See the LICENSE file for legal information regarding use of this file. + +from .ecdsa import * +from .cryptomath import * +from .pem import * +from .asn1 import * +from .misc import * +from .m2crypto import * + +################ ECDSA_WRAPPERS ### +"""The following three "wrapper" functions are used for working with ECDSA: + ec256Generate + ecdsa256Sign + ecdsa256Verify + +These wrapper functions operate on bytearrays: + privateKey is a bytearray of length 32 + publicKey is a bytearray of length 64 + signature is a bytearray of length 64 + dataToSign/Verify is an arbitrary-length bytearray + +There are M2Crypto/OpenSSL versions of these functions, as well as +pure Python versions based on Peter Pearson's code (see the +NUMBERTHEORY, ELLIPTICCURVE, and ECDSA sections for pure Python). + +The M2Crypto/OpenSSL versions are loaded and used if present, otherwise +the pure Python versions are used. + +Because M2Crypto operates on ASN.1-encoded signatures, and traditional OpenSSL +PEM-encoded public and private keys, there is a fair bit of data munging to +convert to/from M2Crypto formats. +""" + +import os + +def ec256Generate(): + if m2cryptoLoaded: + return m2crypto_ec256Generate() + else: + return python_ec256Generate() + +def ecdsa256Sign(privateKey, publicKey, dataToSign): + if m2cryptoLoaded: + return m2crypto_ecdsa256Sign(privateKey, publicKey, dataToSign) + else: + return python_ecdsa256Sign(privateKey, publicKey, dataToSign) + +def ecdsa256Verify(publicKey, dataToVerify, signature): + if m2cryptoLoaded: + return m2crypto_ecdsa256Verify(publicKey, dataToVerify, signature) + else: + return python_ecdsa256Verify(publicKey, dataToVerify, signature) + +if m2cryptoLoaded: + + # Marshal/unmarshal PEM-wrapped ECPrivateKey and ASN.1 Signatures + + def _parseECPrivateKey(pemPrivKeyBytes): + """Parse a bytearray containing a PEM-encoded ECPrivatey. + + Return a pair of (32-byte bytearray, 64-byte bytearray) + containing the (privateKey, publicKey) + """ + b = dePem(pemPrivKeyBytes, "EC PRIVATE KEY") + p = ASN1Parser(b) + # The private key is stored as an ASN.1 integer which may + # need to have zero padding removed (if 33 bytes) or added + # (if < 32 bytes): + privateKey = p.getChild(1).value + privateKey = fromAsn1IntBytes(privateKey, 32) + # There is a 00 04 byte prior to the 64-byte public key + # I'm not sure why M2Crypto has the 00 byte there?, + # some ASN1 thing - the 04 byte signals "uncompressed" + # per SECG. Anyways, strip both those bytes off ([2:]) + publicKey = p.getChild(3).getTagged().value[2:] + assert(len(privateKey) == 32) + assert(len(publicKey) == 64) + return (privateKey, publicKey) + + def _writeECPrivateKey(privateKey, publicKey): + assert(len(privateKey) == 32) + assert(len(publicKey) == 64) + bytes1 = a2b_hex("02010104") + bytes2 = a2b_hex("a00a06082a8648ce3d030107a14403420004") + privateKey = toAsn1IntBytes(privateKey) + b = bytes1 + asn1Length(len(privateKey)) + privateKey + \ + bytes2 + publicKey + b = bytearray([0x30]) + asn1Length(len(b)) + b + pemPrivKeyBytes = pem(b, "EC PRIVATE KEY") + return pemPrivKeyBytes + + def _writeECPublicKey(publicKey): + assert(len(publicKey) == 64) + bytes1 = a2b_hex(\ + "3059301306072a8648ce3d020106082a8648ce3d03010703420004") + asn1KeyBytes = bytes1 + publicKey + pemPubKeyBytes = pem(asn1KeyBytes, "PUBLIC KEY") + return pemPubKeyBytes + + def _parseECSignature(asn1SigBytes): + p = ASN1Parser(bytearray(asn1SigBytes)) + r = bytesToNumber(p.getChild(0).value) + s = bytesToNumber(p.getChild(1).value) + return numberToBytes(r, 32) + numberToBytes(s, 32) + + def _writeECSignature(ecSigBytes): + assert(len(ecSigBytes) == 64) + asn1R = toAsn1IntBytes(ecSigBytes[:32]) + asn1S = toAsn1IntBytes(ecSigBytes[32:]) + # Add ASN1 Type=2(int), and Length fields + asn1R = bytearray([2]) + asn1Length(len(asn1R)) + asn1R + asn1S = bytearray([2]) + asn1Length(len(asn1S)) + asn1S + # Add ASN1 Type=0x30(Sequence) and Length fields + asn1ECSigBytes = bytearray([0x30]) + \ + asn1Length(len(asn1R+asn1S)) + asn1R + asn1S + return asn1ECSigBytes + + def m2crypto_ec256Generate(): + # Generate M2Crypto.EC.EC object + m2EC = EC.gen_params(EC.NID_X9_62_prime256v1) + m2EC.gen_key() + # Get the ASN.1 ECPrivateKey for the object + m2Bio = BIO.MemoryBuffer() + m2EC.save_key_bio(m2Bio, cipher=None) + pemPrivKeyBytes = m2Bio.getvalue() + # Parse the ASN.1 ECPrivateKey into byte arrays + # for the 32-byte priv key, and 64-byte pub key + (privateKey, publicKey) = _parseECPrivateKey(pemPrivKeyBytes) + return (privateKey, publicKey) + + def m2crypto_ecdsa256Sign(privateKey, publicKey, dataToSign): + # Write the passed-in private key byte array into PEM form + # Then create M2Crypto EC object from ASN.1 form + pemPrivKeyBytes = _writeECPrivateKey(privateKey, publicKey) + m2EC = EC.load_key_bio(BIO.MemoryBuffer(pemPrivKeyBytes)) + + # Produce ASN.1 signature + hash = SHA256(dataToSign) + asn1SigBytes = m2EC.sign_dsa_asn1(hash) + + # Convert stupid ASN.1 signature into 64-byte signature + # Double-check before returning + sigBytes = _parseECSignature(asn1SigBytes) + assert(ecdsa256Verify(publicKey, dataToSign, sigBytes)) + return sigBytes + + def m2crypto_ecdsa256Verify(publicKey, dataToVerify, signature): + # Write the passed-in public key byte array into PEM form + # Then create M2Crypto EC_pub + pemPubKeyBytes = _writeECPublicKey(publicKey) + m2ECpub = EC.load_pub_key_bio(BIO.MemoryBuffer(pemPubKeyBytes)) + + # Convert 64-byte signature into a stupid ASN.1 signature + asn1SigBytes = _writeECSignature(signature) + hash = SHA256(dataToVerify) + return m2ECpub.verify_dsa_asn1(hash, asn1SigBytes) + + +# Always load the python functions, even if m2crypto loaded: + +def python_ec256Generate(extraRandBytes=None): + # ECDSA key generation per FIPS 186-3 B.4.1 + # (except we use 32 extra random bytes instead of 8 before reduction) + # Random bytes taken from /dev/urandom as well as any extraRandBytes + # REVIEW THIS CAREFULLY! CHANGE AT YOUR PERIL! + randBytes0 = bytearray(os.urandom(64)) + if extraRandBytes: + randBytes0 += bytearray(extraRandBytes) + randBytes = HMAC_SHA256(randBytes0, bytearray([1])) + randBytes+= HMAC_SHA256(randBytes0, bytearray([2])) + c = bytesToNumber(randBytes) + n = generator_256.order() + d = (c % (n-1))+1 + privateKey = numberToBytes(d, 32) + publicKeyPoint = generator_256 * d + publicKey = numberToBytes(publicKeyPoint.x(), 32) + \ + numberToBytes(publicKeyPoint.y(), 32) + return (privateKey, publicKey) + +def python_ecdsa256Sign(privateKey, publicKey, dataToSign): + privateKeyNum = bytesToNumber(privateKey) + hash = SHA256(dataToSign) + g = generator_256 + n = g.order() + x = bytesToNumber(publicKey[:32]) + y = bytesToNumber(publicKey[32:]) + pubkey = Public_key(g, Point(g.curve(), x,y)) + privkey = Private_key(pubkey, privateKeyNum) + + # Generating random nonce k per FIPS 186-3 B.5.1: + # (except we use 32 extra bytes instead of 8 before reduction) + # Random bytes taken from /dev/urandom as well as HMAC(privkey,hash) + # REVIEW THIS CAREFULLY!!! CHANGE AT YOUR PERIL!!! + randBytes0 = bytearray(os.urandom(64)) + randBytes0+= HMAC_SHA256(privateKey, hash) + randBytes = HMAC_SHA256(randBytes0, bytearray([1])) + randBytes+= HMAC_SHA256(randBytes0, bytearray([2])) + c = bytesToNumber(randBytes) + k = (c % (n-1))+1 + hashNum = bytesToNumber(hash) + sig = privkey.sign(hashNum, k) + signature = numberToBytes(sig.r, 32) + numberToBytes(sig.s, 32) + # Double-check value before returning + assert(ecdsa256Verify(publicKey, dataToSign, signature)) + return signature + +def python_ecdsa256Verify(publicKey, dataToVerify, signature): + hashNum = bytesToNumber(SHA256(dataToVerify)) + g = generator_256 + x = bytesToNumber(publicKey[:32]) + y = bytesToNumber(publicKey[32:]) + pubkey = Public_key(g, Point(g.curve(), x,y)) + sig = Signature(bytesToNumber(signature[:32]), + bytesToNumber(signature[32:])) + return pubkey.verifies(hashNum, sig) + +def testECDSAWrappers(): + print("Testing ECDSA WRAPPERS") + privateKey, publicKey = python_ec256Generate() + data = bytearray([0,1,2,3]) + badData = bytearray([0,1,2,4]) + signature = python_ecdsa256Sign(privateKey, publicKey, data) + assert(python_ecdsa256Verify(publicKey, data, signature)) + assert(not python_ecdsa256Verify(publicKey, badData, signature)) + if m2cryptoLoaded: + # See if M2Crypto can verify Python sig + assert(m2crypto_ecdsa256Verify(publicKey, data, signature)) + privateKey, publicKey = m2crypto_ec256Generate() + signature = m2crypto_ecdsa256Sign(privateKey, publicKey, data) + assert(m2crypto_ecdsa256Verify(publicKey, data, signature)) + assert(not m2crypto_ecdsa256Verify(publicKey, badData, signature)) + return 1 diff --git a/tack/crypto/python/ellipticcurve.py b/tack/crypto/python/ellipticcurve.py new file mode 100644 index 0000000..a8810a8 --- /dev/null +++ b/tack/crypto/python/ellipticcurve.py @@ -0,0 +1,280 @@ +# Authors: +# Peter Pearson - main author +# Trevor Perrin - minor changes for Python compatibility +# +# See the LICENSE file for legal information regarding use of this file. +# Also see Peter Pearson's statement below + +from .numbertheory import * + +################ ELLIPTIC CURVE ### + +#! /usr/bin/env python +# +# Implementation of elliptic curves, for cryptographic applications. +# +# This module doesn't provide any way to choose a random elliptic +# curve, nor to verify that an elliptic curve was chosen randomly, +# because one can simply use NIST's standard curves. +# +# Notes from X9.62-1998 (draft): +# Nomenclature: +# - Q is a public key. +# The "Elliptic Curve Domain Parameters" include: +# - q is the "field size", which in our case equals p. +# - p is a big prime. +# - G is a point of prime order (5.1.1.1). +# - n is the order of G (5.1.1.1). +# Public-key validation (5.2.2): +# - Verify that Q is not the point at infinity. +# - Verify that X_Q and Y_Q are in [0,p-1]. +# - Verify that Q is on the curve. +# - Verify that nQ is the point at infinity. +# Signature generation (5.3): +# - Pick random k from [1,n-1]. +# Signature checking (5.4.2): +# - Verify that r and s are in [1,n-1]. +# +# Version of 2008.11.25. +# +# Revision history: +# 2005.12.31 - Initial version. +# 2008.11.25 - Change CurveFp.is_on to contains_point. +# +# Written in 2005 by Peter Pearson and placed in the public domain. + +class CurveFp( object ): + """Elliptic Curve over the field of integers modulo a prime.""" + def __init__( self, p, a, b ): + """The curve of points satisfying y^2 = x^3 + a*x + b (mod p).""" + self.__p = p + self.__a = a + self.__b = b + + def p( self ): + return self.__p + + def a( self ): + return self.__a + + def b( self ): + return self.__b + + def contains_point( self, x, y ): + """Is the point (x,y) on this curve?""" + return ( y * y - ( x * x * x + self.__a * x + self.__b ) ) % self.__p == 0 + + + +class Point( object ): + """A point on an elliptic curve. Altering x and y is forbidding, + but they can be read by the x() and y() methods.""" + def __init__( self, curve, x, y, order = None ): + """curve, x, y, order; order (optional) is the order of this point.""" + self.__curve = curve + self.__x = x + self.__y = y + self.__order = order + # self.curve is allowed to be None only for INFINITY: + if self.__curve: assert self.__curve.contains_point( x, y ) + if order: assert self * order == INFINITY + + def __eq__( self, other ): + """Return 0 if the points are identical, 1 otherwise.""" + if self.__curve == other.__curve \ + and self.__x == other.__x \ + and self.__y == other.__y: + return 1 + else: + return 0 + + def __add__( self, other ): + """Add one point to another point.""" + + # X9.62 B.3: + + if other == INFINITY: return self + if self == INFINITY: return other + assert self.__curve == other.__curve + if self.__x == other.__x: + if ( self.__y + other.__y ) % self.__curve.p() == 0: + return INFINITY + else: + return self.double() + + p = self.__curve.p() + + l = ( ( other.__y - self.__y ) * \ + inverse_mod( other.__x - self.__x, p ) ) % p + + x3 = ( l * l - self.__x - other.__x ) % p + y3 = ( l * ( self.__x - x3 ) - self.__y ) % p + + return Point( self.__curve, x3, y3 ) + + def __mul__( self, other ): + """Multiply a point by an integer.""" + + def leftmost_bit( x ): + assert x > 0 + result = 1 + while result <= x: result = 2 * result + return result // 2 + + e = other + if self.__order: e = e % self.__order + if e == 0: return INFINITY + if self == INFINITY: return INFINITY + assert e > 0 + + # From X9.62 D.3.2: + + e3 = 3 * e + negative_self = Point( self.__curve, self.__x, -self.__y, self.__order ) + i = leftmost_bit( e3 ) // 2 + result = self + # print "Multiplying %s by %d (e3 = %d):" % ( self, other, e3 ) + while i > 1: + result = result.double() + if ( e3 & i ) != 0 and ( e & i ) == 0: result = result + self + if ( e3 & i ) == 0 and ( e & i ) != 0: result = result + negative_self + # print ". . . i = %d, result = %s" % ( i, result ) + i = i // 2 + + return result + + def __rmul__( self, other ): + """Multiply a point by an integer.""" + + return self * other + + def __str__( self ): + if self == INFINITY: return "infinity" + return "(%d,%d)" % ( self.__x, self.__y ) + + def double( self ): + """Return a new point that is twice the old.""" + + # X9.62 B.3: + + p = self.__curve.p() + a = self.__curve.a() + + l = ( ( 3 * self.__x * self.__x + a ) * \ + inverse_mod( 2 * self.__y, p ) ) % p + + x3 = ( l * l - 2 * self.__x ) % p + y3 = ( l * ( self.__x - x3 ) - self.__y ) % p + + return Point( self.__curve, x3, y3 ) + + def x( self ): + return self.__x + + def y( self ): + return self.__y + + def curve( self ): + return self.__curve + + def order( self ): + return self.__order + + +# This one point is the Point At Infinity for all purposes: +INFINITY = Point( None, None, None ) + +def testEllipticCurve(): + + print("Testing ELLIPTIC CURVE") + + def test_add( c, x1, y1, x2, y2, x3, y3 ): + """We expect that on curve c, (x1,y1) + (x2, y2 ) = (x3, y3).""" + p1 = Point( c, x1, y1 ) + p2 = Point( c, x2, y2 ) + p3 = p1 + p2 + #print "%s + %s = %s" % ( p1, p2, p3 ), + if p3.x() != x3 or p3.y() != y3: + print(" Failure: should give (%d,%d)." % ( x3, y3 )) + assert() + + def test_double( c, x1, y1, x3, y3 ): + """We expect that on curve c, 2*(x1,y1) = (x3, y3).""" + p1 = Point( c, x1, y1 ) + p3 = p1.double() + #print "%s doubled = %s" % ( p1, p3 ), + if p3.x() != x3 or p3.y() != y3: + print(" Failure: should give (%d,%d)." % ( x3, y3 )) + assert() + + def test_multiply( c, x1, y1, m, x3, y3 ): + """We expect that on curve c, m*(x1,y1) = (x3,y3).""" + p1 = Point( c, x1, y1 ) + p3 = p1 * m + #print "%s * %d = %s" % ( p1, m, p3 ), + if p3.x() != x3 or p3.y() != y3: + print(" Failure: should give (%d,%d)." % ( x3, y3 )) + assert() + + # A few tests from X9.62 B.3: + + c = CurveFp( 23, 1, 1 ) + test_add( c, 3, 10, 9, 7, 17, 20 ) + test_double( c, 3, 10, 7, 12 ) + test_add( c, 3, 10, 3, 10, 7, 12 ) # (Should just invoke double.) + test_multiply( c, 3, 10, 2, 7, 12 ) + + # From X9.62 I.1 (p. 96): + + g = Point( c, 13, 7, 7 ) + + check = INFINITY + for i in range( 7 + 1 ): + p = ( i % 7 ) * g + #print("%s * %d = %s, expected %s . . ." % ( g, i, p, check )) + if p == check: + #print(" Good.") + pass + else: + print(p.x(), p.y()) + #print(check.x(), check.y()) + #print(" Bad. %s %s %s %s" % (p, check, type(p), type(check))) + assert() + check = check + g + + # NIST Curve P-192: + p = 6277101735386680763835789423207666416083908700390324961279 + r = 6277101735386680763835789423176059013767194773182842284081 + s = 0x3045ae6fc8422f64ed579528d38120eae12196d5 + c = 0x3099d2bbbfcb2538542dcd5fb078b6ef5f3d6fe2c745de65 + b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1 + Gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012 + Gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811 + + c192 = CurveFp( p, -3, b ) + p192 = Point( c192, Gx, Gy, r ) + + # Checking against some sample computations presented + # in X9.62: + + d = 651056770906015076056810763456358567190100156695615665659 + Q = d * p192 + if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5: + print("p192 * d came out wrong.") + assert() + + k = 6140507067065001063065065565667405560006161556565665656654 + R = k * p192 + if R.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ + or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: + print("k * p192 came out wrong.") + assert() + + u1 = 2563697409189434185194736134579731015366492496392189760599 + u2 = 6266643813348617967186477710235785849136406323338782220568 + temp = u1 * p192 + u2 * Q + if temp.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ + or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: + print("u1 * p192 + u2 * Q came out wrong.") + assert() + return 1 diff --git a/tack/crypto/python/numbertheory.py b/tack/crypto/python/numbertheory.py new file mode 100644 index 0000000..0f6f3dd --- /dev/null +++ b/tack/crypto/python/numbertheory.py @@ -0,0 +1,622 @@ +# Authors: +# Peter Pearson - main author +# Trevor Perrin - minor changes for Python compatibility +# +# See the LICENSE file for legal information regarding use of this file. +# Also see Peter Pearson's statement below + +################ NUMBER THEORY ### +#! /usr/bin/env python +# +# Provide some simple capabilities from number theory. +# +# Version of 2008.11.14. +# +# Written in 2005 and 2006 by Peter Pearson and placed in the public domain. +# Revision history: +# 2008.11.14: Use pow( base, exponent, modulus ) for modular_exp. +# Make gcd and lcm accept arbitrarly many arguments. + + + +import math +import types + +# TREV: needed for python3 +from functools import reduce + + +class Error( Exception ): + """Base class for exceptions in this module.""" + pass + +class SquareRootError( Error ): + pass + +class NegativeExponentError( Error ): + pass + + +def modular_exp( base, exponent, modulus ): + "Raise base to exponent, reducing by modulus" + if exponent < 0: + raise NegativeExponentError( "Negative exponents (%d) not allowed" \ + % exponent ) + return pow( base, exponent, modulus ) +# result = 1L +# x = exponent +# b = base + 0L +# while x > 0: +# if x % 2 > 0: result = (result * b) % modulus +# x = x / 2 +# b = ( b * b ) % modulus +# return result + + +def polynomial_reduce_mod( poly, polymod, p ): + """Reduce poly by polymod, integer arithmetic modulo p. + + Polynomials are represented as lists of coefficients + of increasing powers of x.""" + + # This module has been tested only by extensive use + # in calculating modular square roots. + + # Just to make this easy, require a monic polynomial: + assert polymod[-1] == 1 + + assert len( polymod ) > 1 + + while len( poly ) >= len( polymod ): + if poly[-1] != 0: + for i in range( 2, len( polymod ) + 1 ): + poly[-i] = ( poly[-i] - poly[-1] * polymod[-i] ) % p + poly = poly[0:-1] + + return poly + + + +def polynomial_multiply_mod( m1, m2, polymod, p ): + """Polynomial multiplication modulo a polynomial over ints mod p. + + Polynomials are represented as lists of coefficients + of increasing powers of x.""" + + # This is just a seat-of-the-pants implementation. + + # This module has been tested only by extensive use + # in calculating modular square roots. + + # Initialize the product to zero: + + prod = ( len( m1 ) + len( m2 ) - 1 ) * [0] + + # Add together all the cross-terms: + + for i in range( len( m1 ) ): + for j in range( len( m2 ) ): + prod[i+j] = ( prod[i+j] + m1[i] * m2[j] ) % p + + return polynomial_reduce_mod( prod, polymod, p ) + + + + +def polynomial_exp_mod( base, exponent, polymod, p ): + """Polynomial exponentiation modulo a polynomial over ints mod p. + + Polynomials are represented as lists of coefficients + of increasing powers of x.""" + + # Based on the Handbook of Applied Cryptography, algorithm 2.227. + + # This module has been tested only by extensive use + # in calculating modular square roots. + + assert exponent < p + + if exponent == 0: return [ 1 ] + + G = base + k = exponent + if k%2 == 1: s = G + else: s = [ 1 ] + + while k > 1: + k = k // 2 + G = polynomial_multiply_mod( G, G, polymod, p ) + if k%2 == 1: s = polynomial_multiply_mod( G, s, polymod, p ) + + return s + + + +def jacobi( a, n ): + """Jacobi symbol""" + + # Based on the Handbook of Applied Cryptography (HAC), algorithm 2.149. + + # This function has been tested by comparison with a small + # table printed in HAC, and by extensive use in calculating + # modular square roots. + + assert n >= 3 + assert n%2 == 1 + a = a % n + if a == 0: return 0 + if a == 1: return 1 + a1, e = a, 0 + while a1%2 == 0: + a1, e = a1//2, e+1 + if e%2 == 0 or n%8 == 1 or n%8 == 7: s = 1 + else: s = -1 + if a1 == 1: return s + if n%4 == 3 and a1%4 == 3: s = -s + return s * jacobi( n % a1, a1 ) + + + + +def square_root_mod_prime( a, p ): + """Modular square root of a, mod p, p prime.""" + + # Based on the Handbook of Applied Cryptography, algorithms 3.34 to 3.39. + + # This module has been tested for all values in [0,p-1] for + # every prime p from 3 to 1229. + + assert 0 <= a < p + assert 1 < p + + if a == 0: return 0 + if p == 2: return a + + jac = jacobi( a, p ) + if jac == -1: raise SquareRootError( "%d has no square root modulo %d" \ + % ( a, p ) ) + + if p % 4 == 3: return modular_exp( a, (p+1)//4, p ) + + if p % 8 == 5: + d = modular_exp( a, (p-1)//4, p ) + if d == 1: return modular_exp( a, (p+3)//8, p ) + if d == p-1: return ( 2 * a * modular_exp( 4*a, (p-5)//8, p ) ) % p + raise RuntimeError("Shouldn't get here.") + + for b in range( 2, p ): + if jacobi( b*b-4*a, p ) == -1: + f = ( a, -b, 1 ) + ff = polynomial_exp_mod( ( 0, 1 ), (p+1)//2, f, p ) + assert ff[1] == 0 + return ff[0] + raise RuntimeError("No b found.") + + + +def inverse_mod( a, m ): + """Inverse of a mod m.""" + + if a < 0 or m <= a: a = a % m + + # From Ferguson and Schneier, roughly: + + c, d = a, m + uc, vc, ud, vd = 1, 0, 0, 1 + while c != 0: + q, c, d = divmod( d, c ) + ( c, ) + uc, vc, ud, vd = ud - q*uc, vd - q*vc, uc, vc + + # At this point, d is the GCD, and ud*a+vd*m = d. + # If d == 1, this means that ud is a inverse. + + assert d == 1 + if ud > 0: return ud + else: return ud + m + + +def gcd2(a, b): + """Greatest common divisor using Euclid's algorithm.""" + while a: + a, b = b%a, a + return b + + +def gcd( *a ): + """Greatest common divisor. + + Usage: gcd( [ 2, 4, 6 ] ) + or: gcd( 2, 4, 6 ) + """ + if len( a ) > 1: return reduce( gcd2, a ) + if hasattr( a[0], "__iter__" ): return reduce( gcd2, a[0] ) + return a[0] + + +def lcm2(a,b): + """Least common multiple of two integers.""" + + return (a*b)//gcd(a,b) + + +def lcm( *a ): + """Least common multiple. + + Usage: lcm( [ 3, 4, 5 ] ) + or: lcm( 3, 4, 5 ) + """ + + if len( a ) > 1: return reduce( lcm2, a ) + if hasattr( a[0], "__iter__" ): return reduce( lcm2, a[0] ) + return a[0] + + + +def factorization( n ): + """Decompose n into a list of (prime,exponent) pairs.""" + + # TREV: doesn't work on python 3 + #assert isinstance( n, types.IntType ) or isinstance( n, types.LongType ) + + if n < 2: return [] + + result = [] + d = 2 + + # Test the small primes: + + for d in smallprimes: + if d > n: break + q, r = divmod( n, d ) + if r == 0: + count = 1 + while d <= n: + n = q + q, r = divmod( n, d ) + if r != 0: break + count = count + 1 + result.append( ( d, count ) ) + + # If n is still greater than the last of our small primes, + # it may require further work: + + if n > smallprimes[-1]: + if is_prime( n ): # If what's left is prime, it's easy: + result.append( ( n, 1 ) ) + else: # Ugh. Search stupidly for a divisor: + d = smallprimes[-1] + while 1: + d = d + 2 # Try the next divisor. + q, r = divmod( n, d ) + if q < d: break # n < d*d means we're done, n = 1 or prime. + if r == 0: # d divides n. How many times? + count = 1 + n = q + while d <= n: # As long as d might still divide n, + q, r = divmod( n, d ) # see if it does. + if r != 0: break + n = q # It does. Reduce n, increase count. + count = count + 1 + result.append( ( d, count ) ) + if n > 1: result.append( ( n, 1 ) ) + + return result + + + +def phi( n ): + """Return the Euler totient function of n.""" + + assert isinstance( n, types.IntType ) or isinstance( n, types.LongType ) + + if n < 3: return 1 + + result = 1 + ff = factorization( n ) + for f in ff: + e = f[1] + if e > 1: + result = result * f[0] ** (e-1) * ( f[0] - 1 ) + else: + result = result * ( f[0] - 1 ) + return result + + +def carmichael( n ): + """Return Carmichael function of n. + + Carmichael(n) is the smallest integer x such that + m**x = 1 mod n for all m relatively prime to n. + """ + + return carmichael_of_factorized( factorization( n ) ) + + +def carmichael_of_factorized( f_list ): + """Return the Carmichael function of a number that is + represented as a list of (prime,exponent) pairs. + """ + + if len( f_list ) < 1: return 1 + + result = carmichael_of_ppower( f_list[0] ) + for i in range( 1, len( f_list ) ): + result = lcm( result, carmichael_of_ppower( f_list[i] ) ) + + return result + +def carmichael_of_ppower( pp ): + """Carmichael function of the given power of the given prime. + """ + + p, a = pp + if p == 2 and a > 2: return 2**(a-2) + else: return (p-1) * p**(a-1) + + + +def order_mod( x, m ): + """Return the order of x in the multiplicative group mod m. + """ + + # Warning: this implementation is not very clever, and will + # take a long time if m is very large. + + if m <= 1: return 0 + + assert gcd( x, m ) == 1 + + z = x + result = 1 + while z != 1: + z = ( z * x ) % m + result = result + 1 + return result + + +def largest_factor_relatively_prime( a, b ): + """Return the largest factor of a relatively prime to b. + """ + + while 1: + d = gcd( a, b ) + if d <= 1: break + b = d + while 1: + q, r = divmod( a, d ) + if r > 0: + break + a = q + return a + + +def kinda_order_mod( x, m ): + """Return the order of x in the multiplicative group mod m', + where m' is the largest factor of m relatively prime to x. + """ + + return order_mod( x, largest_factor_relatively_prime( m, x ) ) + + +def is_prime( n ): + """Return True if x is prime, False otherwise. + + We use the Miller-Rabin test, as given in Menezes et al. p. 138. + This test is not exact: there are composite values n for which + it returns True. + + In testing the odd numbers from 10000001 to 19999999, + about 66 composites got past the first test, + 5 got past the second test, and none got past the third. + Since factors of 2, 3, 5, 7, and 11 were detected during + preliminary screening, the number of numbers tested by + Miller-Rabin was (19999999 - 10000001)*(2/3)*(4/5)*(6/7) + = 4.57 million. + """ + + # (This is used to study the risk of false positives:) + global miller_rabin_test_count + + miller_rabin_test_count = 0 + + if n <= smallprimes[-1]: + if n in smallprimes: return True + else: return False + + if gcd( n, 2*3*5*7*11 ) != 1: return False + + # Choose a number of iterations sufficient to reduce the + # probability of accepting a composite below 2**-80 + # (from Menezes et al. Table 4.4): + + t = 40 + n_bits = 1 + int( math.log( n, 2 ) ) + for k, tt in ( ( 100, 27 ), + ( 150, 18 ), + ( 200, 15 ), + ( 250, 12 ), + ( 300, 9 ), + ( 350, 8 ), + ( 400, 7 ), + ( 450, 6 ), + ( 550, 5 ), + ( 650, 4 ), + ( 850, 3 ), + ( 1300, 2 ), + ): + if n_bits < k: break + t = tt + + # Run the test t times: + + s = 0 + r = n - 1 + while ( r % 2 ) == 0: + s = s + 1 + r = r // 2 + for i in range( t ): + a = smallprimes[ i ] + y = modular_exp( a, r, n ) + if y != 1 and y != n-1: + j = 1 + while j <= s - 1 and y != n - 1: + y = modular_exp( y, 2, n ) + if y == 1: + miller_rabin_test_count = i + 1 + return False + j = j + 1 + if y != n-1: + miller_rabin_test_count = i + 1 + return False + return True + + +def next_prime( starting_value ): + "Return the smallest prime larger than the starting value." + + if starting_value < 2: return 2 + result = ( starting_value + 1 ) | 1 + while not is_prime( result ): result = result + 2 + return result + + +smallprimes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, + 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, + 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, + 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, + 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, + 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, + 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, + 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, + 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, + 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, + 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, + 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, + 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, + 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, + 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, + 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, + 983, 991, 997, 1009, 1013, 1019, 1021, 1031, 1033, + 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, + 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, + 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229] + + + +def testNumberTheory(): + print("Testing NUMBER THEORY") + miller_rabin_test_count = 0 + + # Making sure locally defined exceptions work: + # p = modular_exp( 2, -2, 3 ) + # p = square_root_mod_prime( 2, 3 ) + + #print "Testing gcd..." + assert gcd( 3*5*7, 3*5*11, 3*5*13 ) == 3*5 + assert gcd( [ 3*5*7, 3*5*11, 3*5*13 ] ) == 3*5 + assert gcd( 3 ) == 3 + + #print "Testing lcm..." + assert lcm( 3, 5*3, 7*3 ) == 3*5*7 + assert lcm( [ 3, 5*3, 7*3 ] ) == 3*5*7 + assert lcm( 3 ) == 3 + + #print "Testing next_prime..." + bigprimes = ( 999671, + 999683, + 999721, + 999727, + 999749, + 999763, + 999769, + 999773, + 999809, + 999853, + 999863, + 999883, + 999907, + 999917, + 999931, + 999953, + 999959, + 999961, + 999979, + 999983 ) + + for i in range( len( bigprimes ) - 1 ): + assert next_prime( bigprimes[i] ) == bigprimes[ i+1 ] + + error_tally = 0 + + # Test the square_root_mod_prime function: + + for p in smallprimes[:50]: + #print "Testing square_root_mod_prime for modulus p = %d." % p + squares = [] + + for root in range( 0, 1+p//2 ): + sq = ( root * root ) % p + squares.append( sq ) + calculated = square_root_mod_prime( sq, p ) + if ( calculated * calculated ) % p != sq: + error_tally = error_tally + 1 + print("Failed to find %d as sqrt( %d ) mod %d. Said %d." % \ + ( root, sq, p, calculated )) + + for nonsquare in range( 0, p ): + if nonsquare not in squares: + try: + calculated = square_root_mod_prime( nonsquare, p ) + except SquareRootError: + pass + else: + error_tally = error_tally + 1 + print("Failed to report no root for sqrt( %d ) mod %d." % \ + ( nonsquare, p )) + + # Test the jacobi function: + for m in range( 3, 100, 2 ): + #print "Testing jacobi for modulus m = %d." % m + if is_prime( m ): + squares = [] + for root in range( 1, m ): + if jacobi( root * root, m ) != 1: + error_tally = error_tally + 1 + print("jacobi( %d * %d, %d ) != 1" % ( root, root, m )) + squares.append( root * root % m ) + for i in range( 1, m ): + if not i in squares: + if jacobi( i, m ) != -1: + error_tally = error_tally + 1 + print("jacobi( %d, %d ) != -1" % ( i, m )) + else: # m is not prime. + f = factorization( m ) + for a in range( 1, m ): + c = 1 + for i in f: + c = c * jacobi( a, i[0] ) ** i[1] + if c != jacobi( a, m ): + error_tally = error_tally + 1 + print("%d != jacobi( %d, %d )" % ( c, a, m )) + + +# Test the inverse_mod function: + #print "Testing inverse_mod . . ." + import random + n_tests = 0 + for i in range( 100 ): + m = random.randint( 20, 10000 ) + for j in range( 100 ): + a = random.randint( 1, m-1 ) + if gcd( a, m ) == 1: + n_tests = n_tests + 1 + inv = inverse_mod( a, m ) + if inv <= 0 or inv >= m or ( a * inv ) % m != 1: + error_tally = error_tally + 1 + print("%d = inverse_mod( %d, %d ) is wrong." % ( inv, a, m )) + assert(False) + #assert n_tests > 1000 + #print n_tests, " tests of inverse_mod completed." + #print error_tally, "errors detected." + assert(error_tally == 0) + return 1 From 97363e5127d9c72211cd8b677e093e168f00e28b Mon Sep 17 00:00:00 2001 From: Trevor Date: Mon, 14 May 2012 00:28:16 +0300 Subject: [PATCH 41/94] Reinstate "make dist", but without MANIFEST. --- Makefile | 5 +++++ setup.py | 23 +++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index a421f09..ad1f9b4 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,15 @@ default: install: ./setup.py install +.PHONY: dist +dist: + ./setup.py sdist + .PHONY : clean clean: rm -f `find . -name *.pyc` rm -rf build + rm -rf dist rm -rf $(TESTDIR) # Variables for testing diff --git a/setup.py b/setup.py index 26750b3..ee777d8 100755 --- a/setup.py +++ b/setup.py @@ -9,16 +9,19 @@ shutil.copyfile("tack.py", "tack/tack") -setup(name="TACKpy", - version="0.9.6", - author="Trevor Perrin", - author_email="tackpy@trevp.net", - url="https://github.com/trevp/TACKpy", - description="TACKpy implements TACK in python", - license="public domain", - scripts=["tack/tack"], - packages=["tack", "tack/commands", "tack/crypto", "tack/crypto/openssl", - "tack/crypto/python", "tack/structures", "tack/tls", "tack/util"]) +setup( name="TACKpy", + version="0.9.6", + author="Trevor Perrin", + author_email="tackpy@trevp.net", + url="https://github.com/trevp/TACKpy", + description="TACKpy implements TACK in python", + license="public domain", + scripts=["tack/tack"], + packages=["tack", "tack/commands", "tack/crypto", "tack/crypto/openssl", + "tack/crypto/python", "tack/structures", "tack/tls", "tack/util"], + data_files=[("", ["LICENSE", "tack.py", "Makefile"]), + ("testdata", ["testdata/serverX509Cert.pem", "testdata/serverX509Cert.der"])] + ) print "Cleaning up..." if os.path.exists("build/"): From 00154c1a52bcd7104a315246943d57490ef0fafb Mon Sep 17 00:00:00 2001 From: Trevor Date: Mon, 14 May 2012 01:02:51 +0300 Subject: [PATCH 42/94] Remove some imports. --- tack/crypto/openssl/OpenSSL_ECPrivateKey.py | 1 - tack/crypto/openssl/OpenSSL_ECPublicKey.py | 1 - 2 files changed, 2 deletions(-) diff --git a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py index 86cb665..1aa572a 100644 --- a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py @@ -3,7 +3,6 @@ from tack.crypto.Digest import Digest from .OpenSSL_ECPublicKey import OpenSSL_ECPublicKey from .OpenSSL import openssl as o -from tack.util.PEMEncoder import PEMEncoder class OpenSSL_ECPrivateKey: diff --git a/tack/crypto/openssl/OpenSSL_ECPublicKey.py b/tack/crypto/openssl/OpenSSL_ECPublicKey.py index caf028a..7e97f28 100644 --- a/tack/crypto/openssl/OpenSSL_ECPublicKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPublicKey.py @@ -3,7 +3,6 @@ from tack.compat import bytesToStr from tack.crypto.Digest import Digest from .OpenSSL import openssl as o -from tack.util.PEMEncoder import PEMEncoder class OpenSSL_ECPublicKey: From 82b21c96b44388ac32b179c1084dcc96bace4277 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Wed, 16 May 2012 03:42:45 -0700 Subject: [PATCH 43/94] Renamed tests to be compatible with default NOSE_TESTMATCH. --- tests/{CertificateTest.py => Certificate_Test.py} | 0 tests/{CompatTest.py => Compat_Test.py} | 0 tests/{CryptoTest.py => Crypto_Test.py} | 0 tests/{StructuresTest.py => Structures_Test.py} | 0 tests/{TimeTest.py => Time_Test.py} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename tests/{CertificateTest.py => Certificate_Test.py} (100%) rename tests/{CompatTest.py => Compat_Test.py} (100%) rename tests/{CryptoTest.py => Crypto_Test.py} (100%) rename tests/{StructuresTest.py => Structures_Test.py} (100%) rename tests/{TimeTest.py => Time_Test.py} (100%) diff --git a/tests/CertificateTest.py b/tests/Certificate_Test.py similarity index 100% rename from tests/CertificateTest.py rename to tests/Certificate_Test.py diff --git a/tests/CompatTest.py b/tests/Compat_Test.py similarity index 100% rename from tests/CompatTest.py rename to tests/Compat_Test.py diff --git a/tests/CryptoTest.py b/tests/Crypto_Test.py similarity index 100% rename from tests/CryptoTest.py rename to tests/Crypto_Test.py diff --git a/tests/StructuresTest.py b/tests/Structures_Test.py similarity index 100% rename from tests/StructuresTest.py rename to tests/Structures_Test.py diff --git a/tests/TimeTest.py b/tests/Time_Test.py similarity index 100% rename from tests/TimeTest.py rename to tests/Time_Test.py From 4a1cd7016b7aa35b3f58bcd092360f8d63b40785 Mon Sep 17 00:00:00 2001 From: Trevor Date: Wed, 16 May 2012 13:48:06 +0300 Subject: [PATCH 44/94] Cleanup AES classes. --- tack/crypto/openssl/OpenSSL_AES.py | 28 ++++++++++----------------- tack/crypto/python/Python_AES.py | 31 ++++++++++++++++++------------ 2 files changed, 29 insertions(+), 30 deletions(-) diff --git a/tack/crypto/openssl/OpenSSL_AES.py b/tack/crypto/openssl/OpenSSL_AES.py index 42f11d3..153242e 100644 --- a/tack/crypto/openssl/OpenSSL_AES.py +++ b/tack/crypto/openssl/OpenSSL_AES.py @@ -2,26 +2,18 @@ from .OpenSSL import openssl as o from tack.compat import bytesToStr +BLOCKSIZE = 16 + class OpenSSL_AES: + def __init__(self, key, IV): + if len(key) != 32: + raise AssertionError() if len(IV) != 16: raise AssertionError() - - self.blockSize = 16 + self.cipherType = o.EVP_aes_256_cbc() self.key = key - self.IV = IV - - if len(key)==16: - self.name = "aes128" - self.cipherType = o.EVP_aes_128_cbc() - elif len(key)==24: - self.name = "aes192" - self.cipherType = o.EVP_aes_192_cbc() - elif len(key)==32: - self.name = "aes256" - self.cipherType = o.EVP_aes_256_cbc() - else: - raise AssertionError() + self.IV = IV def encrypt(self, plaintext): return self._transform(plaintext, 1) @@ -30,7 +22,7 @@ def decrypt(self, ciphertext): return self._transform(ciphertext, 0) def _transform(self, inBytes, encrypt): - assert(len(inBytes) % 16 == 0) + assert(len(inBytes) % BLOCKSIZE == 0) ctx = None inBuf = bytesToStr(inBytes) outBuf = ctypes.create_string_buffer(len(inBytes)) @@ -52,7 +44,7 @@ def _transform(self, inBytes, encrypt): # Update the CBC chaining if encrypt: - self.IV = outBytes[-self.blockSize:] + self.IV = outBytes[-BLOCKSIZE:] else: - self.IV = inBytes[-self.blockSize:] + self.IV = inBytes[-BLOCKSIZE:] return outBytes diff --git a/tack/crypto/python/Python_AES.py b/tack/crypto/python/Python_AES.py index 97008de..2118bd3 100644 --- a/tack/crypto/python/Python_AES.py +++ b/tack/crypto/python/Python_AES.py @@ -1,30 +1,37 @@ from .rijndael import rijndael +BLOCKSIZE = 16 + class Python_AES: + def __init__(self, key, IV): - self.rijndael = rijndael(key, 16) + if len(key) != 32: + raise AssertionError() + if len(IV) != 16: + raise AssertionError() + self.rijndael = rijndael(key, BLOCKSIZE) self.IV = IV def encrypt(self, plaintextBytes): - assert(len(plaintextBytes) % 16 == 0) + assert(len(plaintextBytes) % BLOCKSIZE == 0) ciphertextBytes = plaintextBytes[:] chainBytes = self.IV #CBC Mode: For each block... - for x in range(len(ciphertextBytes)//16): + for x in range(len(ciphertextBytes)//BLOCKSIZE): #XOR with the chaining block - blockBytes = ciphertextBytes[x*16 : (x*16)+16] - for y in range(16): + blockBytes = ciphertextBytes[x*BLOCKSIZE : (x*BLOCKSIZE)+BLOCKSIZE] + for y in range(BLOCKSIZE): blockBytes[y] ^= chainBytes[y] #Encrypt it encryptedBytes = self.rijndael.encrypt(blockBytes) #Overwrite the input with the output - for y in range(16): - ciphertextBytes[(x*16)+y] = encryptedBytes[y] + for y in range(BLOCKSIZE): + ciphertextBytes[(x*BLOCKSIZE)+y] = encryptedBytes[y] #Set the next chaining block chainBytes = encryptedBytes @@ -33,21 +40,21 @@ def encrypt(self, plaintextBytes): return ciphertextBytes def decrypt(self, ciphertextBytes): - assert(len(ciphertextBytes) % 16 == 0) + assert(len(ciphertextBytes) % BLOCKSIZE == 0) plaintextBytes = ciphertextBytes[:] chainBytes = self.IV #CBC Mode: For each block... - for x in range(len(plaintextBytes)//16): + for x in range(len(plaintextBytes)//BLOCKSIZE): #Decrypt it - blockBytes = plaintextBytes[x*16 : (x*16)+16] + blockBytes = plaintextBytes[x*BLOCKSIZE : (x*BLOCKSIZE)+BLOCKSIZE] decryptedBytes = self.rijndael.decrypt(blockBytes) #XOR with the chaining block and overwrite the input with output - for y in range(16): + for y in range(BLOCKSIZE): decryptedBytes[y] ^= chainBytes[y] - plaintextBytes[(x*16)+y] = decryptedBytes[y] + plaintextBytes[(x*BLOCKSIZE)+y] = decryptedBytes[y] #Set the next chaining block chainBytes = blockBytes From f45cb1d9a6be9a5d1ca309a7947f90658791bdb0 Mon Sep 17 00:00:00 2001 From: Trevor Date: Wed, 16 May 2012 13:55:13 +0300 Subject: [PATCH 45/94] Change "make test" to not prompt for password, too annoying. --- Makefile | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index ad1f9b4..1b3d240 100644 --- a/Makefile +++ b/Makefile @@ -32,11 +32,10 @@ test: rm -rf $(TESTDIR) mkdir $(TESTDIR) #$(EXEC) test - # NOTE: USE 'asdf' for passwords... - $(EXEC) genkey > $(TESTDIR)/TACK_Key1.pem + $(EXEC) genkey -p asdf > $(TESTDIR)/TACK_Key1.pem $(EXEC) genkey -p asdf > $(TESTDIR)/TACK_Key2.pem $(EXEC) genkey -p asdf -o $(TESTDIR)/TACK_Key3.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -c $(CERT1) > $(TESTDIR)/TACK1.pem + $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) > $(TESTDIR)/TACK1.pem $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT2) -o $(TESTDIR)/TACK2.pem $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) -m2 -o $(TESTDIR)/TACK3.pem $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) -o $(TESTDIR)/TACK4.pem From 6ce5c1f39aa089f87720947ca0610dfed69bbeac Mon Sep 17 00:00:00 2001 From: Trevor Date: Wed, 16 May 2012 14:32:33 +0300 Subject: [PATCH 46/94] Error strings on loading OpenSSL. --- tack/commands/HelpCommand.py | 2 +- tack/crypto/openssl/OpenSSL.py | 31 ++++++++++++++++++--- tack/crypto/openssl/OpenSSL_ECPrivateKey.py | 2 +- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index ed0b765..7254552 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -41,7 +41,7 @@ def printGeneralUsage(message=None): if o.enabled: cryptoVersion = "(%s)" % o.SSLeay_version(0) else: - cryptoVersion = "(python crypto)" + cryptoVersion = "(python crypto - %s)" % o.initErrorString if message: print "Error: %s\n" % message sys.stdout.write( diff --git a/tack/crypto/openssl/OpenSSL.py b/tack/crypto/openssl/OpenSSL.py index 085462a..54fdb8b 100644 --- a/tack/crypto/openssl/OpenSSL.py +++ b/tack/crypto/openssl/OpenSSL.py @@ -5,15 +5,29 @@ class OpenSSL: def initialize(self): + self.initErrorString = "unknown error loading OpenSSL" + try: libraryName = find_library("crypto") + if not libraryName: + self._setInitError("OpenSSL not found") + return + except: + self._setInitError("OpenSSL not found") + return + + try: self._lib = cdll.LoadLibrary(libraryName) + except: + self._setInitError("error loading OpenSSL ") + return + try: self.POINT_CONVERSION_UNCOMPRESSED = 4 - + class ECDSA_SIG(Structure): _fields_ = [("r", c_void_p), ("s", c_void_p)] - + self._add("SSLeay_version", ret=c_char_p) self._add("OBJ_txt2nid", args=[c_char_p]) self._add("EC_KEY_new_by_curve_name", ret=c_void_p, args=[c_int]) @@ -38,7 +52,7 @@ class ECDSA_SIG(Structure): self._add("BN_free", args=[c_void_p], skipWrap=True) self._add("BN_bn2bin", args=[c_void_p, c_void_p]) self._add("BN_bin2bn", args=[c_void_p, c_int, c_void_p]) - + self._add("EVP_CIPHER_CTX_new", ret=c_void_p) self._add("EVP_CIPHER_CTX_init", args=[c_void_p]) self._add("EVP_CIPHER_CTX_cleanup", args=[c_void_p]) @@ -57,7 +71,12 @@ class ECDSA_SIG(Structure): def _add(self, name, ret=None, args=None, skipWrap=False): - func = getattr(self._lib, name) + try: + func = getattr(self._lib, name) + except: + self._setInitError("error loading OpenSSL:%s" % name) + raise + if ret: func.restype = ret if args: @@ -72,6 +91,10 @@ def wrappedFunc(*a): return retval setattr(self, name, wrappedFunc) + def _setInitError(self, s): + self.enabled = False + self.initErrorString = s + # Singleton, initialize() this once then use it openssl = OpenSSL() diff --git a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py index 1aa572a..e48ab31 100644 --- a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py @@ -55,7 +55,7 @@ def _constructEcFromRawKey(self, rawPrivateKey): ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) privBuf = bytesToStr(rawPrivateKey) - privBignum = o.BN_new() # needs free + privBignum = o.BN_new() o.BN_bin2bn(privBuf, 32, privBignum) o.EC_KEY_set_private_key(ec_key, privBignum) return o.EC_KEY_dup(ec_key) From bc5da2a6d9b5ebb718f5bba05a6b58ac09461d7a Mon Sep 17 00:00:00 2001 From: Trevor Date: Wed, 16 May 2012 20:38:03 +0300 Subject: [PATCH 47/94] Fix ctypes string passing. Python3 fixes (passes make test). --- Makefile | 2 +- tack/commands/HelpCommand.py | 5 ++-- tack/compat.py | 2 +- tack/crypto/openssl/OpenSSL.py | 28 ++++++++++++++++++--- tack/crypto/openssl/OpenSSL_AES.py | 10 +++++--- tack/crypto/openssl/OpenSSL_ECGenerator.py | 24 +++++++++++------- tack/crypto/openssl/OpenSSL_ECPrivateKey.py | 19 ++++++++------ tack/crypto/openssl/OpenSSL_ECPublicKey.py | 17 +++++++------ tack/crypto/python/Python_ECGenerator.py | 10 ++++++-- tack/structures/TackBreakSig.py | 2 +- tack/util/PEMEncoder.py | 3 ++- 11 files changed, 81 insertions(+), 41 deletions(-) diff --git a/Makefile b/Makefile index 1b3d240..0467707 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ clean: # Variables for testing TESTDIR = testoutput -EXEC = ./tack.py +EXEC = python3 ./tack.py CERT1 = ./testdata/serverX509Cert.pem CERT2 = ./testdata/serverX509Cert.der diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index 7254552..a394600 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -1,4 +1,5 @@ import sys +from tack.compat import bytesToStr from tack.commands.CertificateCommand import CertificateCommand from tack.version import __version__ from tack.commands.BreakCommand import BreakCommand @@ -39,11 +40,11 @@ def printHelp(): @staticmethod def printGeneralUsage(message=None): if o.enabled: - cryptoVersion = "(%s)" % o.SSLeay_version(0) + cryptoVersion = "(%s)" % bytesToStr(o.SSLeay_version(0)) else: cryptoVersion = "(python crypto - %s)" % o.initErrorString if message: - print "Error: %s\n" % message + print ("Error: %s\n" % message) sys.stdout.write( """tack.py version %s %s diff --git a/tack/compat.py b/tack/compat.py index 4fbdeff..85f33c5 100644 --- a/tack/compat.py +++ b/tack/compat.py @@ -39,7 +39,7 @@ def b2a_base32(b): return base64.b32encode(b).decode("ascii") def bytesToStr(b): - return str(b, "ascii") + return str(b, "latin-1") def compat26Str(x): return x diff --git a/tack/crypto/openssl/OpenSSL.py b/tack/crypto/openssl/OpenSSL.py index 54fdb8b..ea9e61a 100644 --- a/tack/crypto/openssl/OpenSSL.py +++ b/tack/crypto/openssl/OpenSSL.py @@ -1,6 +1,15 @@ +import sys from ctypes import * from ctypes.util import find_library +from .OpenSSLException import OpenSSLException + +def bytesToC(b): + cbuf = create_string_buffer(bytes(b)) + return cbuf + +def cToBytes(c): + return bytearray(c) class OpenSSL: @@ -19,7 +28,7 @@ def initialize(self): try: self._lib = cdll.LoadLibrary(libraryName) except: - self._setInitError("error loading OpenSSL ") + self._setInitError("error loading OpenSSL") return try: @@ -29,7 +38,12 @@ class ECDSA_SIG(Structure): _fields_ = [("r", c_void_p), ("s", c_void_p)] self._add("SSLeay_version", ret=c_char_p) - self._add("OBJ_txt2nid", args=[c_char_p]) + self._add("OBJ_txt2nid") + self._add("ERR_load_crypto_strings", skipWrap=True) + self._add("ERR_get_error", ret=c_long, skipWrap=True) + self._add("ERR_peek_last_error", ret=c_long, skipWrap=True) + self._add("ERR_error_string", ret=c_char_p, args=[c_long, c_char_p], skipWrap=True) + self._add("EC_KEY_new_by_curve_name", ret=c_void_p, args=[c_int]) self._add("EC_KEY_free", args=[c_void_p], skipWrap=True) self._add("EC_KEY_dup", ret=c_void_p, args=[c_void_p]) @@ -42,7 +56,7 @@ class ECDSA_SIG(Structure): self._add("EC_GROUP_free", args=[c_void_p], skipWrap=True) self._add("EC_POINT_new", ret=c_void_p, args=[c_void_p]) self._add("EC_POINT_free", args=[c_void_p], skipWrap=True) - self._add("EC_POINT_oct2point", args=[c_void_p, c_void_p, c_void_p, c_int, c_void_p]) + self._add("EC_POINT_oct2point", args=[c_void_p, c_void_p, c_void_p, c_size_t, c_void_p]) self._add("EC_POINT_point2oct", args=[c_void_p, c_void_p, c_int, c_void_p, c_int, c_void_p] ) self._add("ECDSA_do_sign", ret=POINTER(ECDSA_SIG), args=[c_void_p, c_int, c_void_p]) self._add("ECDSA_do_verify", args=[c_void_p, c_int, POINTER(ECDSA_SIG), c_void_p], skipWrap=True) @@ -64,6 +78,8 @@ class ECDSA_SIG(Structure): self._add("EVP_CipherInit", args=[c_void_p, c_void_p, c_void_p, c_void_p, c_int]) self._add("EVP_CipherUpdate", args=[c_void_p, c_void_p, c_void_p, c_void_p, c_int]) + self.ERR_load_crypto_strings() + self.enabled = True except: self.enabled = False @@ -87,7 +103,11 @@ def _add(self, name, ret=None, args=None, skipWrap=False): else: def wrappedFunc(*a): retval = func(*a) - assert(retval) + if not retval: + errNum = self.ERR_peek_last_error() + errBuf = create_string_buffer(1024) + errStr = self.ERR_error_string(errNum, errBuf) + raise OpenSSLException(errStr) return retval setattr(self, name, wrappedFunc) diff --git a/tack/crypto/openssl/OpenSSL_AES.py b/tack/crypto/openssl/OpenSSL_AES.py index 153242e..68433ef 100644 --- a/tack/crypto/openssl/OpenSSL_AES.py +++ b/tack/crypto/openssl/OpenSSL_AES.py @@ -1,6 +1,6 @@ import ctypes from .OpenSSL import openssl as o -from tack.compat import bytesToStr +from .OpenSSL import bytesToC, cToBytes BLOCKSIZE = 16 @@ -24,20 +24,22 @@ def decrypt(self, ciphertext): def _transform(self, inBytes, encrypt): assert(len(inBytes) % BLOCKSIZE == 0) ctx = None - inBuf = bytesToStr(inBytes) + inBuf = bytesToC(inBytes) + keyBuf = bytesToC(self.key) + ivBuf = bytesToC(self.IV) outBuf = ctypes.create_string_buffer(len(inBytes)) try: # Create the CIPHER_CTX ctx = o.EVP_CIPHER_CTX_new() o.EVP_CIPHER_CTX_init(ctx) - o.EVP_CipherInit(ctx, self.cipherType, bytesToStr(self.key), bytesToStr(self.IV), encrypt) + o.EVP_CipherInit(ctx, self.cipherType, keyBuf, ivBuf, encrypt) o.EVP_CIPHER_CTX_set_padding(ctx, 0) # Encrypt or Decrypt outLen = ctypes.c_int() o.EVP_CipherUpdate(ctx, outBuf, ctypes.byref(outLen), inBuf, len(inBytes)) assert(outLen.value == len(inBytes)) - outBytes = bytearray(outBuf[:len(inBytes)]) + outBytes = cToBytes(outBuf)[:len(inBytes)] finally: o.EVP_CIPHER_CTX_cleanup(ctx) o.EVP_CIPHER_CTX_free(ctx) diff --git a/tack/crypto/openssl/OpenSSL_ECGenerator.py b/tack/crypto/openssl/OpenSSL_ECGenerator.py index 249cc1c..370b1d3 100644 --- a/tack/crypto/openssl/OpenSSL_ECGenerator.py +++ b/tack/crypto/openssl/OpenSSL_ECGenerator.py @@ -1,8 +1,9 @@ import math, ctypes -from tack.compat import bytesToStr from .OpenSSL_ECPublicKey import OpenSSL_ECPublicKey from .OpenSSL_ECPrivateKey import OpenSSL_ECPrivateKey +from tack.crypto.python.Python_ECPrivateKey import Python_ECPrivateKey from .OpenSSL import openssl as o +from .OpenSSL import bytesToC, cToBytes class OpenSSL_ECGenerator: @@ -12,16 +13,16 @@ def generateECKeyPair(): ec_key, ec_group = None, None # Generate the new key - ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) + ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid(b"prime256v1")) o.EC_KEY_generate_key(ec_key) # Extract the key's public and private values as strings # into pubBuf and privBuf - pubBuf = bytesToStr(bytearray(1+64)) # [0x04] ... - privBuf = bytesToStr(bytearray(32)) + pubBuf = bytesToC(bytearray(1+64)) # [0x04] ... + privBuf = bytesToC(bytearray(32)) ec_point = o.EC_KEY_get0_public_key(ec_key) - ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) + ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid(b"prime256v1")) o.EC_POINT_point2oct(ec_group, ec_point, o.POINT_CONVERSION_UNCOMPRESSED, pubBuf, 65, None) bignum = o.EC_KEY_get0_private_key(ec_key) @@ -29,11 +30,16 @@ def generateECKeyPair(): # Convert the public and private keys into fixed-length 64 and 32 byte arrays # Leading zeros are added to priv key, leading byte (0x04) stripped from pub key - rawPublicKey = bytearray(pubBuf[1:65]) - rawPrivateKey = bytearray(32-privLen) + bytearray(privBuf[:privLen]) + rawPublicKey = cToBytes(pubBuf)[1:65] + rawPrivateKey = bytearray(32-privLen) + (cToBytes(privBuf)[:privLen]) - return (OpenSSL_ECPublicKey(rawPublicKey, ec_key), - OpenSSL_ECPrivateKey(rawPrivateKey, rawPublicKey, ec_key)) + # Test signing and verification with the new key + # sign() does a verify() internally + pub = OpenSSL_ECPublicKey(rawPublicKey) + priv = OpenSSL_ECPrivateKey(rawPrivateKey, rawPublicKey) + priv.sign(b"test") + + return (pub, priv) finally: o.EC_KEY_free(ec_key) o.EC_GROUP_free(ec_group) diff --git a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py index e48ab31..7199664 100644 --- a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py @@ -1,8 +1,10 @@ import ctypes -from tack.compat import bytesToStr from tack.crypto.Digest import Digest from .OpenSSL_ECPublicKey import OpenSSL_ECPublicKey +from tack.crypto.python.Python_ECPublicKey import Python_ECPublicKey from .OpenSSL import openssl as o +from .OpenSSL import bytesToC, cToBytes + class OpenSSL_ECPrivateKey: @@ -26,19 +28,20 @@ def sign(self, data): ecdsa_sig = None # Hash and apply ECDSA - hashBuf = bytesToStr(Digest.SHA256(data)) + hashBuf = bytesToC(Digest.SHA256(data)) ecdsa_sig = o.ECDSA_do_sign(hashBuf, 32, self.ec_key) # Encode the signature into 64 bytes - rBuf = bytesToStr(bytearray(32)) - sBuf = bytesToStr(bytearray(32)) + rBuf = bytesToC(bytearray(32)) + sBuf = bytesToC(bytearray(32)) rLen = o.BN_bn2bin(ecdsa_sig.contents.r, rBuf) sLen = o.BN_bn2bin(ecdsa_sig.contents.s, sBuf) - rBytes = bytearray(32-rLen) + bytearray(rBuf[:rLen]) - sBytes = bytearray(32-sLen) + bytearray(sBuf[:sLen]) + rBytes = bytearray(32-rLen) + cToBytes(rBuf)[:rLen] + sBytes = bytearray(32-sLen) + cToBytes(sBuf)[:sLen] sigBytes = rBytes + sBytes + assert(len(sigBytes) == 64) finally: o.ECDSA_SIG_free(ecdsa_sig) @@ -53,8 +56,8 @@ def _constructEcFromRawKey(self, rawPrivateKey): try: privBignum, ec_key = None, None - ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) - privBuf = bytesToStr(rawPrivateKey) + ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid(b"prime256v1")) + privBuf = bytesToC(rawPrivateKey) privBignum = o.BN_new() o.BN_bin2bn(privBuf, 32, privBignum) o.EC_KEY_set_private_key(ec_key, privBignum) diff --git a/tack/crypto/openssl/OpenSSL_ECPublicKey.py b/tack/crypto/openssl/OpenSSL_ECPublicKey.py index 7e97f28..fba44a5 100644 --- a/tack/crypto/openssl/OpenSSL_ECPublicKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPublicKey.py @@ -1,8 +1,9 @@ -import ctypes +import ctypes, sys from tack.compat import b2a_base32 -from tack.compat import bytesToStr from tack.crypto.Digest import Digest from .OpenSSL import openssl as o +from .OpenSSL import bytesToC, cToBytes + class OpenSSL_ECPublicKey: @@ -27,13 +28,13 @@ def verify(self, data, signature): # Create ECDSA_SIG ecdsa_sig = o.ECDSA_SIG_new() - rBuf = bytesToStr(signature[ : 32]) - sBuf = bytesToStr(signature[32 : ]) + rBuf = bytesToC(signature[ : 32]) + sBuf = bytesToC(signature[32 : ]) o.BN_bin2bn(rBuf, 32, ecdsa_sig.contents.r) o.BN_bin2bn(sBuf, 32, ecdsa_sig.contents.s) # Hash and verify ECDSA - hashBuf = bytesToStr(Digest.SHA256(data)) + hashBuf = bytesToC(Digest.SHA256(data)) retval = o.ECDSA_do_verify(hashBuf, 32, ecdsa_sig, self.ec_key) if retval == 1: return True @@ -57,12 +58,12 @@ def _constructEcFromRawKey(self, rawPublicKey): try: ec_key, ec_group, ec_point = None, None, None - ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) - ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid("prime256v1")) + ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid(b"prime256v1")) + ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid(b"prime256v1")) ec_point = o.EC_POINT_new(ec_group) # Add 0x04 byte to signal "uncompressed" public key - pubBuf = bytesToStr(bytearray([0x04]) + rawPublicKey) + pubBuf = bytesToC(bytearray([0x04]) + rawPublicKey) o.EC_POINT_oct2point(ec_group, ec_point, pubBuf, 65, None) o.EC_KEY_set_public_key(ec_key, ec_point) return o.EC_KEY_dup(ec_key) diff --git a/tack/crypto/python/Python_ECGenerator.py b/tack/crypto/python/Python_ECGenerator.py index 1d5c4c4..1f3e565 100644 --- a/tack/crypto/python/Python_ECGenerator.py +++ b/tack/crypto/python/Python_ECGenerator.py @@ -22,5 +22,11 @@ def generateECKeyPair(): publicKeyPoint = generator_256 * d rawPublicKey = (numberToBytes(publicKeyPoint.x(), 32) + numberToBytes(publicKeyPoint.y(), 32)) - return (Python_ECPublicKey(rawPublicKey), - Python_ECPrivateKey(rawPrivateKey, rawPublicKey)) \ No newline at end of file + + # Test signing and verification with the new key + # sign() does a verify() internally + pub = Python_ECPublicKey(rawPublicKey) + priv = Python_ECPrivateKey(rawPrivateKey, rawPublicKey) + priv.sign(b"test") + + return (pub, priv) \ No newline at end of file diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index d6a1b0e..08f3ed2 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -63,7 +63,7 @@ def getTackId(self): return str(self.public_key) def verifySignature(self): - return self.public_key.verify(bytearray("tack_break_sig"), self.signature) + return self.public_key.verify(bytearray("tack_break_sig", "ascii"), self.signature) def __str__(self): """Return a readable string describing this TACK_Break_Sig. diff --git a/tack/util/PEMEncoder.py b/tack/util/PEMEncoder.py index 37a7377..c5e8536 100644 --- a/tack/util/PEMEncoder.py +++ b/tack/util/PEMEncoder.py @@ -1,4 +1,5 @@ -from binascii import b2a_base64 +from tack.compat import b2a_base64 +import sys class PEMEncoder: From 08d19bcd3495114b189296fedf1fe0d5927ea0f5 Mon Sep 17 00:00:00 2001 From: Trevor Date: Wed, 16 May 2012 22:45:32 +0300 Subject: [PATCH 48/94] OpenSSL support on Windows (libeay32.dll). --- Makefile | 2 +- README | 6 ++++++ setup.py | 2 +- tack/crypto/openssl/OpenSSL.py | 14 ++++++++++---- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index 0467707..1b3d240 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ clean: # Variables for testing TESTDIR = testoutput -EXEC = python3 ./tack.py +EXEC = ./tack.py CERT1 = ./testdata/serverX509Cert.pem CERT2 = ./testdata/serverX509Cert.der diff --git a/README b/README index 82bc44d..00ecdbc 100644 --- a/README +++ b/README @@ -21,6 +21,12 @@ Run "python setup.py install" or "make install". This installs: To use the "tack" command without installation you can run "selfcontained/tack". +Tackpy tries to use OpenSSL for AES and ECDSA operations. If OpenSSL cannot be +loaded, Tackpy will fall back to using python crypto code. On UNIX-like +systems, Tackpy will try to load OpenSSL via the "crypto" shared library. On +Windows, Tackpy will try to load "libeay32.dll" (which must be in the path). +Run "tack help" to see if OpenSSL was loaded. + Quick start ============ diff --git a/setup.py b/setup.py index ee777d8..20006ed 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ shutil.copyfile("tack.py", "tack/tack") -setup( name="TACKpy", +setup( name="tackpy", version="0.9.6", author="Trevor Perrin", author_email="tackpy@trevp.net", diff --git a/tack/crypto/openssl/OpenSSL.py b/tack/crypto/openssl/OpenSSL.py index ea9e61a..91cb45f 100644 --- a/tack/crypto/openssl/OpenSSL.py +++ b/tack/crypto/openssl/OpenSSL.py @@ -16,17 +16,23 @@ class OpenSSL: def initialize(self): self.initErrorString = "unknown error loading OpenSSL" - try: - libraryName = find_library("crypto") + try: + if sys.platform == "win32": + libraryName = find_library("libeay32") + else: + libraryName = find_library("crypto") if not libraryName: self._setInitError("OpenSSL not found") return except: self._setInitError("OpenSSL not found") - return + raise try: - self._lib = cdll.LoadLibrary(libraryName) + if sys.platform == "win32": + self._lib = CDLL(libraryName) + else: + self._lib = cdll.LoadLibrary(libraryName) except: self._setInitError("error loading OpenSSL") return From 22446168c636e2ee3de5627f0fb951235ee6b8cc Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 17 May 2012 00:07:28 +0300 Subject: [PATCH 49/94] Display crypto version (openssl version or error) on -v. --- tack/commands/Command.py | 13 +++++++++++++ tack/commands/GenerateKeyCommand.py | 1 + tack/commands/HelpCommand.py | 8 ++------ tack/commands/SignCommand.py | 2 ++ tack/crypto/openssl/OpenSSL.py | 1 - 5 files changed, 18 insertions(+), 7 deletions(-) diff --git a/tack/commands/Command.py b/tack/commands/Command.py index 2c81fc9..b2a90da 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -4,6 +4,8 @@ import time from tack.structures.TackKeyFile import TackKeyFile from tack.util.Time import Time +from tack.crypto.openssl.OpenSSL import openssl as o +from tack.compat import bytesToStr from tack.version import __version__ from tack.InvalidPasswordException import InvalidPasswordException @@ -18,6 +20,17 @@ def __init__(self, argv, options, flags): except getopt.GetoptError as e: self.printError(e) + @staticmethod + def getCryptoVersion(): + if o.enabled: + cryptoVersion = "%s" % bytesToStr(o.SSLeay_version(0)) + else: + cryptoVersion = "python crypto - %s" % o.initErrorString + return cryptoVersion + + def writeCryptoVersion(self): + sys.stderr.write("Crypto = %s\n" % Command.getCryptoVersion()) + def isVerbose(self): return self._containsOption("-v") diff --git a/tack/commands/GenerateKeyCommand.py b/tack/commands/GenerateKeyCommand.py index aa9fa25..1fd134f 100644 --- a/tack/commands/GenerateKeyCommand.py +++ b/tack/commands/GenerateKeyCommand.py @@ -18,6 +18,7 @@ def execute(self): self.outputFile.write(self.addPemComments(keyFile.serializeAsPem())) if self.isVerbose(): + self.writeCryptoVersion() sys.stderr.write(str(keyFile)) def _getPassword(self): diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index a394600..fb8a072 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -39,14 +39,10 @@ def printHelp(): @staticmethod def printGeneralUsage(message=None): - if o.enabled: - cryptoVersion = "(%s)" % bytesToStr(o.SSLeay_version(0)) - else: - cryptoVersion = "(python crypto - %s)" % o.initErrorString if message: print ("Error: %s\n" % message) sys.stdout.write( -"""tack.py version %s %s +"""tack.py version %s (%s) Commands (use "help " to see optional args): genkey @@ -54,5 +50,5 @@ def printGeneralUsage(message=None): break -k KEY view FILE help COMMAND -""" % (__version__, cryptoVersion)) +""" % (__version__, Command.getCryptoVersion())) sys.exit(-1) diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index e17c15b..8a822aa 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -35,6 +35,7 @@ def execute(self): self.outputFile.write(self.addPemComments(tack.serializeAsPem())) if self.isVerbose(): + self.writeCryptoVersion() sys.stderr.write(str(tack)) else: # We are signing multiple TACKs, since "-n" was specified @@ -52,6 +53,7 @@ def execute(self): outputFile.close() if self.isVerbose(): + self.writeCryptoVersion() sys.stderr.write(str(tack)) self.expiration += interval diff --git a/tack/crypto/openssl/OpenSSL.py b/tack/crypto/openssl/OpenSSL.py index 91cb45f..01732a0 100644 --- a/tack/crypto/openssl/OpenSSL.py +++ b/tack/crypto/openssl/OpenSSL.py @@ -85,7 +85,6 @@ class ECDSA_SIG(Structure): self._add("EVP_CipherUpdate", args=[c_void_p, c_void_p, c_void_p, c_void_p, c_int]) self.ERR_load_crypto_strings() - self.enabled = True except: self.enabled = False From 2fa0d20aa96324e00fa4803487868d288fa8c815 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 17 May 2012 00:52:39 +0300 Subject: [PATCH 50/94] Oops add -v cryptoversion display to tack break command, too. --- tack/commands/BreakCommand.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tack/commands/BreakCommand.py b/tack/commands/BreakCommand.py index 8f43614..75d3564 100644 --- a/tack/commands/BreakCommand.py +++ b/tack/commands/BreakCommand.py @@ -15,6 +15,7 @@ def execute(self): self.outputFile.write(self.addPemComments(breakSig.serializeAsPem())) if self.isVerbose(): + self.writeCryptoVersion() sys.stderr.write(str(breakSig)) @staticmethod From bd708a956cab7844844e6a6fdb512885b0655251 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 17 May 2012 15:27:49 +0300 Subject: [PATCH 51/94] Support for STDIN args on cmdline ("-"). --- Makefile | 12 ++++++------ tack/commands/BreakCommand.py | 2 +- tack/commands/CertificateCommand.py | 17 ++++++++++++++--- tack/commands/Command.py | 5 ++++- tack/commands/SignCommand.py | 29 ++++++++++++++++++++--------- tack/commands/ViewCommand.py | 12 +++++++++--- tack/compat.py | 25 ++++++++++++++++++------- tack/structures/Tack.py | 3 --- tack/structures/TackBreakSig.py | 3 --- tack/tls/TlsCertificate.py | 17 ++++++++--------- 10 files changed, 80 insertions(+), 45 deletions(-) diff --git a/Makefile b/Makefile index 1b3d240..0b94093 100644 --- a/Makefile +++ b/Makefile @@ -36,12 +36,12 @@ test: $(EXEC) genkey -p asdf > $(TESTDIR)/TACK_Key2.pem $(EXEC) genkey -p asdf -o $(TESTDIR)/TACK_Key3.pem $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) > $(TESTDIR)/TACK1.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT2) -o $(TESTDIR)/TACK2.pem + cat $(TESTDIR)/TACK_Key1.pem | $(EXEC) sign -k- -p asdf -c $(CERT2) -o $(TESTDIR)/TACK2.pem $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) -m2 -o $(TESTDIR)/TACK3.pem $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) -o $(TESTDIR)/TACK4.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -e 2030-06-06Z -c $(CERT1) -o $(TESTDIR)/TACK5.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -g2 -m2 -c $(CERT1) -o $(TESTDIR)/TACK6.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -m250 -g251 -c $(CERT1) -o $(TESTDIR)/T6 -e 2013-01-02Z -n 3@1d + $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -e 2030-06-06Z -c $(CERT2) -o $(TESTDIR)/TACK5.pem + cat $(CERT1) | $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -g2 -m2 -c- -o $(TESTDIR)/TACK6.pem + cat $(CERT2) | $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -m250 -g251 -c - -o $(TESTDIR)/T6 -e 2013-01-02Z -n 3@1d $(EXEC) break -k $(TESTDIR)/TACK_Key1.pem -p asdf > $(TESTDIR)/TACK_Break_Sig1.pem $(EXEC) b -k $(TESTDIR)/TACK_Key2.pem -p asdf -o $(TESTDIR)/TACK_Break_Sig2.pem cat $(TESTDIR)/TACK_Break_Sig1.pem $(TESTDIR)/TACK_Break_Sig2.pem > $(TESTDIR)/TACK_Break_Sigs.pem @@ -49,9 +49,9 @@ test: $(EXEC) tackcert -i $(TESTDIR)/TACK4.pem -b $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_Cert4.pem $(EXEC) tackcert -i $(TESTDIR)/TACK_Cert3.pem > $(TESTDIR)/TACK3_FromCert.pem $(EXEC) view $(TESTDIR)/TACK_Key1.pem > $(TESTDIR)/TACK_View_Key1.txt - $(EXEC) view $(TESTDIR)/TACK1.pem > $(TESTDIR)/TACK_View1.txt + cat $(TESTDIR)/TACK1.pem | $(EXEC) view - > $(TESTDIR)/TACK_View1.txt $(EXEC) v $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_View_Break_Sigs.txt $(EXEC) v $(CERT1) > $(TESTDIR)/TACK_View_Cert1.txt - $(EXEC) v $(CERT2) > $(TESTDIR)/TACK_View_Cert2.txt + cat $(CERT2) | $(EXEC) v - > $(TESTDIR)/TACK_View_Cert2.txt $(EXEC) v $(TESTDIR)/TACK_Cert3.pem > $(TESTDIR)/TACK_View_TACK_Cert3.txt @echo OK diff --git a/tack/commands/BreakCommand.py b/tack/commands/BreakCommand.py index 75d3564..2a562ed 100644 --- a/tack/commands/BreakCommand.py +++ b/tack/commands/BreakCommand.py @@ -25,7 +25,7 @@ def printHelp(): break -k KEY - -k KEY : Use this TACK key file + -k KEY : Use this TACK key file ("-" for stdin) Optional arguments: -v : Verbose diff --git a/tack/commands/CertificateCommand.py b/tack/commands/CertificateCommand.py index 4bdecfd..2d6af1f 100644 --- a/tack/commands/CertificateCommand.py +++ b/tack/commands/CertificateCommand.py @@ -55,7 +55,10 @@ def _getBreakSignatures(self): if fileName is None: return None - contents = open(fileName, "r").read() + try: + contents = open(fileName, "r").read() + except IOError: + self.printError("Error opening break signature: %s" % fileName) return TackBreakSig.createFromPemList(contents) @@ -78,7 +81,11 @@ def _getInputCertificate(self): if PEMDecoder(contents).containsEncoded("CERTIFICATE"): certificate = TlsCertificate() - certificate.open(self._getOptionValue("-i")) + certificateName = self._getOptionValue("-i") + try: + certificate.open(open(certificateName, "rb").read()) + except IOError: + self.printError("Error opening certificate: %s" % certificateName) return certificate def _getInputFileContents(self): @@ -86,8 +93,12 @@ def _getInputFileContents(self): if fileName is None: return None + + try: + return open(fileName, "r").read() + except IOError: + self.printError("Error opening input file: %s" % fileName) - return open(fileName, "r").read() @staticmethod def printHelp(): diff --git a/tack/commands/Command.py b/tack/commands/Command.py index b2a90da..ca8835e 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -47,7 +47,10 @@ def getKeyFile(self, password): password = self._promptPassword() try: - keyPemData = open(keyPemFile, "rU").read() + if keyPemFile == "-": + keyPemData = sys.stdin.read() + else: + keyPemData = open(keyPemFile, "rU").read() while True: try: diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index 8a822aa..d261d7c 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -1,6 +1,7 @@ import sys import time import math +from tack.compat import readStdinBinary from tack.commands.Command import Command from tack.structures.Tack import Tack from tack.tls.TlsCertificate import TlsCertificate @@ -17,7 +18,7 @@ def __init__(self, argv): self.certificate = self._getCertificate() self.generation = self._getGeneration() self.min_generation = self._getMinGeneration() - self.expiration = self._getExpiration() + self.expiration = self._getExpiration(self.certificate) self.numArg = self._getNumArg() # If -n, then -o is a filename prefix only, so is not opened if self.numArg: @@ -48,9 +49,13 @@ def execute(self): tack = Tack.create(self.keyfile.getPublicKey(), self.keyfile.getPrivateKey(), self.min_generation, self.generation, self.expiration, self.certificate.key_sha256) - outputFile = open(self.outputFileName + "_%04d.pem" % x, "w") - outputFile.write(self.addPemComments(tack.serializeAsPem())) - outputFile.close() + try: + outputFileName = self.outputFileName + "_%04d.pem" % x + outputFile = open(outputFileName, "w") + outputFile.write(self.addPemComments(tack.serializeAsPem())) + outputFile.close() + except IOError: + self.printError("Error opening output file: %s" % outputFileName) if self.isVerbose(): self.writeCryptoVersion() @@ -65,19 +70,25 @@ def _getCertificate(self): self.printError("-c missing (Certificate)") try: + if certificateFile == "-": + # Read as binary + certificateBytes = readStdinBinary() + else: + certificateBytes = bytearray(open(certificateFile, "rb").read()) + inCert = TlsCertificate() - inCert.open(certificateFile) + inCert.open(certificateBytes) return inCert except SyntaxError: self.printError("Certificate malformed: %s" % certificateFile) except IOError: self.printError("Error opening certificate: %s" % certificateFile) - def _getExpiration(self): + def _getExpiration(self, certificate): expiration = self._getOptionValue("-e") if expiration is None and self._getNumArg() is None: - return int(math.ceil(self._getCertificate().notAfter / 60.0)) + return int(math.ceil(certificate.notAfter / 60.0)) else: try: return Time.parseTimeArg(expiration) @@ -141,8 +152,8 @@ def printHelp(): sign -k KEY -c CERT - -k KEY : Use this TACK key file - -c CERT : Sign this certificate's public key + -k KEY : Use this TACK key file ("-" for stdin) + -c CERT : Sign this certificate's public key ("-" for stdin) Optional arguments: -v : Verbose diff --git a/tack/commands/ViewCommand.py b/tack/commands/ViewCommand.py index 5f4b735..4173b41 100644 --- a/tack/commands/ViewCommand.py +++ b/tack/commands/ViewCommand.py @@ -1,5 +1,7 @@ import sys +from tack.compat import bytesToStr from tack.commands.Command import Command +from tack.compat import readStdinBinary from tack.structures.Tack import Tack from tack.structures.TackKeyFile import TackKeyFile from tack.structures.TackBreakSig import TackBreakSig @@ -20,9 +22,13 @@ def __init__(self, argv): def _readFile(self, argv): try: # Read both binary (bytearray) and text (str) versions of the input - b = bytearray(open(argv[0], "rb").read()) try: - s = open(argv[0], "rU").read() + if argv[0] == "-": + # Read as binary + b = readStdinBinary() + else: + b = bytearray(open(argv[0], "rb").read()) + s = bytesToStr(b, "ascii") except UnicodeDecodeError: # Python3 error, so it must be a binary file; not text s = None @@ -77,5 +83,5 @@ def printHelp(): print( """Views a TACK, TACK Key, TACK Break Sig, or certificate. -view +view ("-" for stdin) """) diff --git a/tack/compat.py b/tack/compat.py index 85f33c5..64b2fce 100644 --- a/tack/compat.py +++ b/tack/compat.py @@ -38,8 +38,12 @@ def b2a_base64(b): def b2a_base32(b): return base64.b32encode(b).decode("ascii") - def bytesToStr(b): - return str(b, "latin-1") + # Decodes all 256 byte values, use "ascii" for first 128 + def bytesToStr(b, encoding="latin-1"): + return b.decode(encoding) + + def readStdinBinary(): + return sys.stdin.buffer.read() def compat26Str(x): return x @@ -50,8 +54,8 @@ def compat26Str(x): return x def compat26Str(x): return str(x) else: def compat26Str(x): return x - - + + def a2b_hex(s): try: b = bytearray(binascii.a2b_hex(s)) @@ -73,7 +77,14 @@ def b2a_base64(b): return binascii.b2a_base64(compat26Str(b)) def b2a_base32(b): - return base64.b32encode(str(b)) + return base64.b32encode(str(b)) + + # This at least returns a byte array, but it doesn't actually + # ready binary data - on a Windows system, CRLFs may still get mangled + # if you try to pipe binary data in... oh well, can live with that + def readStdinBinary(): + return bytearray(sys.stdin.read()) - def bytesToStr(b): - return str(b) + # Decodes all 256 byte values, use "ascii" for first 128 + def bytesToStr(b, encoding="latin-1"): + return b.decode(encoding) diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index 7c0b739..eef027c 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -23,9 +23,6 @@ def __init__(self, data=None): self.target_hash = self.getBytes(32) self.signature = self.getBytes(64) - if self.index != len(data): - raise SyntaxError("Excess bytes in TACK") - if self.generation < self.min_generation: raise SyntaxError("Generation less than min_generation") diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index 08f3ed2..2ff1935 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -16,9 +16,6 @@ def __init__(self, data=None): self.public_key = ECPublicKey.new(self.getBytes(64)) self.signature = self.getBytes(64) - - if self.index != len(data): - raise SyntaxError("Excess bytes in TACK_Break_Sig") if not self.verifySignature(): raise SyntaxError("TACK_Break_Sig has bad signature") diff --git a/tack/tls/TlsCertificate.py b/tack/tls/TlsCertificate.py index 5a675d5..ca3efdf 100644 --- a/tack/tls/TlsCertificate.py +++ b/tack/tls/TlsCertificate.py @@ -1,4 +1,4 @@ -from tack.compat import a2b_hex +from tack.compat import a2b_hex, bytesToStr from tack.crypto.ASN1 import ASN1Parser, asn1Length from tack.crypto.Digest import Digest from tack.structures.TackExtension import TackExtension @@ -45,18 +45,17 @@ def create(self, tackExt = None): self.postExtBytes = a2b_hex( "300d06092a864886f70d01010505000303003993") - def open(self, filename): - # May raise IOError or SyntaxError + def open(self, fileBytes): + # May raise SyntaxError try: - sslStr = open(filename, "rU").read() # IOError, UnicodeDecodeError - self.parsePem(sslStr) # SyntaxError + fileStr = bytesToStr(fileBytes, "ascii") # UnicodeDecodeError + self.parsePem(fileStr) # SyntaxError return - except (UnicodeDecodeError, SyntaxError): - # File had non-text chars in it (python3), *OR* + except (UnicodeDecodeError, SyntaxError) as e: + # File had non-ASCII chars in it, *OR* # File did not PEM-decode pass - sslBytes = bytearray(open(filename, "rb").read()) # IOError - self.parse(sslBytes) # SyntaxError + self.parse(bytearray(fileBytes)) # SyntaxError def matches(self, tack): return self.key_sha256 == tack.target_hash From bf470f3d1d066635354eed76d4a99c7bcd9fa2a3 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 17 May 2012 18:46:57 +0300 Subject: [PATCH 52/94] Add "-x" option to force python crypto. --- Makefile | 8 ++++---- tack/commands/BreakCommand.py | 3 ++- tack/commands/Command.py | 10 +++++++--- tack/commands/GenerateKeyCommand.py | 3 ++- tack/commands/HelpCommand.py | 6 +++--- tack/commands/SignCommand.py | 3 ++- tack/commands/ViewCommand.py | 19 +++++++++++-------- tack/crypto/openssl/OpenSSL.py | 10 +++++----- 8 files changed, 36 insertions(+), 26 deletions(-) diff --git a/Makefile b/Makefile index 0b94093..030d050 100644 --- a/Makefile +++ b/Makefile @@ -33,17 +33,17 @@ test: mkdir $(TESTDIR) #$(EXEC) test $(EXEC) genkey -p asdf > $(TESTDIR)/TACK_Key1.pem - $(EXEC) genkey -p asdf > $(TESTDIR)/TACK_Key2.pem + $(EXEC) genkey -x -p asdf > $(TESTDIR)/TACK_Key2.pem $(EXEC) genkey -p asdf -o $(TESTDIR)/TACK_Key3.pem $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) > $(TESTDIR)/TACK1.pem cat $(TESTDIR)/TACK_Key1.pem | $(EXEC) sign -k- -p asdf -c $(CERT2) -o $(TESTDIR)/TACK2.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) -m2 -o $(TESTDIR)/TACK3.pem + $(EXEC) sign -x -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) -m2 -o $(TESTDIR)/TACK3.pem $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) -o $(TESTDIR)/TACK4.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -e 2030-06-06Z -c $(CERT2) -o $(TESTDIR)/TACK5.pem + $(EXEC) sign -x -k $(TESTDIR)/TACK_Key1.pem -p asdf -e 2030-06-06Z -c $(CERT2) -o $(TESTDIR)/TACK5.pem cat $(CERT1) | $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -g2 -m2 -c- -o $(TESTDIR)/TACK6.pem cat $(CERT2) | $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -m250 -g251 -c - -o $(TESTDIR)/T6 -e 2013-01-02Z -n 3@1d $(EXEC) break -k $(TESTDIR)/TACK_Key1.pem -p asdf > $(TESTDIR)/TACK_Break_Sig1.pem - $(EXEC) b -k $(TESTDIR)/TACK_Key2.pem -p asdf -o $(TESTDIR)/TACK_Break_Sig2.pem + $(EXEC) b -x -k $(TESTDIR)/TACK_Key2.pem -p asdf -o $(TESTDIR)/TACK_Break_Sig2.pem cat $(TESTDIR)/TACK_Break_Sig1.pem $(TESTDIR)/TACK_Break_Sig2.pem > $(TESTDIR)/TACK_Break_Sigs.pem $(EXEC) tackcert -i $(TESTDIR)/TACK3.pem > $(TESTDIR)/TACK_Cert3.pem $(EXEC) tackcert -i $(TESTDIR)/TACK4.pem -b $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_Cert4.pem diff --git a/tack/commands/BreakCommand.py b/tack/commands/BreakCommand.py index 2a562ed..85d9e58 100644 --- a/tack/commands/BreakCommand.py +++ b/tack/commands/BreakCommand.py @@ -5,7 +5,7 @@ class BreakCommand(Command): def __init__(self, argv): - Command.__init__(self, argv, "pok", "v") + Command.__init__(self, argv, "pok", "vx") self.password = self.getPassword() self.outputFile, self.outputFileName = self.getOutputFile() self.keyfile = self.getKeyFile(self.getPassword()) @@ -29,6 +29,7 @@ def printHelp(): Optional arguments: -v : Verbose + -x : Use python crypto (not OpenSSL) -o FILE : Write the output to this file (instead of stdout) -p PASSWORD : Use this TACK key password instead of prompting """) diff --git a/tack/commands/Command.py b/tack/commands/Command.py index ca8835e..deac91c 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -11,12 +11,16 @@ class Command: - def __init__(self, argv, options, flags): + def __init__(self, argv, options, flags, allowArgRemainder=False): try: - self.argv = argv self.flags = flags self.options = ":".join(options) + ":" - self.values, self.remainder = getopt.getopt(argv, self.options + self.flags) + self.values, self.argRemainder = getopt.getopt(argv, self.options + self.flags) + if not allowArgRemainder and self.argRemainder: + self.printError("Too many arguments: %s" % self.argRemainder) + if self._containsOption("-x"): + o.setInitError("requested from command line") + o.enabled = False except getopt.GetoptError as e: self.printError(e) diff --git a/tack/commands/GenerateKeyCommand.py b/tack/commands/GenerateKeyCommand.py index 1fd134f..355ed47 100644 --- a/tack/commands/GenerateKeyCommand.py +++ b/tack/commands/GenerateKeyCommand.py @@ -7,7 +7,7 @@ class GenerateKeyCommand(Command): def __init__(self, argv): - Command.__init__(self, argv, "po", "v") + Command.__init__(self, argv, "po", "vx") self.password = self.getPassword() self.outputFile, self.outputFileName = self.getOutputFile() @@ -44,6 +44,7 @@ def printHelp(): Optional arguments: -v : Verbose + -x : Use python crypto (not OpenSSL) -o FILE : Write the output to this file (instead of stdout) -p PASSWORD : Use this TACK key password instead of prompting """) diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index fb8a072..7a7fded 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -16,12 +16,12 @@ class HelpCommand(Command): "tackcert" : CertificateCommand} def __init__(self, argv): - Command.__init__(self, argv, "", "") + Command.__init__(self, argv, "", "", allowArgRemainder=True) - if len(argv) < 1: + if len(self.argRemainder) < 1 or len(self.argRemainder)>1: HelpCommand.printGeneralUsage() - self.command = argv[0] + self.command = self.argRemainder[0] if not self.command in HelpCommand.COMMANDS: self.printError("%s not a valid command." % self.command) diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index d261d7c..e68cf88 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -10,7 +10,7 @@ class SignCommand(Command): def __init__(self, argv): - Command.__init__(self, argv, "kcopmgen", "v") + Command.__init__(self, argv, "kcopmgen", "vx") self.password = self.getPassword() self.keyfile = self.getKeyFile(self.password) @@ -157,6 +157,7 @@ def printHelp(): Optional arguments: -v : Verbose + -x : Use python crypto (not OpenSSL) -o FILE : Write the output to this file (instead of stdout) -p PASSWORD : Use this TACK key password instead of prompting -m MIN_GENERATION : Use this min_generation number (0-255) diff --git a/tack/commands/ViewCommand.py b/tack/commands/ViewCommand.py index 4173b41..325ea2c 100644 --- a/tack/commands/ViewCommand.py +++ b/tack/commands/ViewCommand.py @@ -11,23 +11,23 @@ class ViewCommand(Command): def __init__(self, argv): - Command.__init__(self, argv, "", "") + Command.__init__(self, argv, "", "x", allowArgRemainder=True) - if len(argv) < 1: + if len(self.argRemainder) < 1: self.printError("Missing argument: file to view") - if len(argv) > 1: + if len(self.argRemainder) > 1: self.printError("Can only view one file") - def _readFile(self, argv): + def _readFile(self, fname): try: # Read both binary (bytearray) and text (str) versions of the input try: - if argv[0] == "-": + if fname == "-": # Read as binary b = readStdinBinary() else: - b = bytearray(open(argv[0], "rb").read()) + b = bytearray(open(fname, "rb").read()) s = bytesToStr(b, "ascii") except UnicodeDecodeError: # Python3 error, so it must be a binary file; not text @@ -38,7 +38,7 @@ def _readFile(self, argv): self.printError("Error opening file: %s" % argv[0]) def execute(self): - text, binary = self._readFile(self.argv) + text, binary = self._readFile(self.argRemainder[0]) fileType = None try: @@ -83,5 +83,8 @@ def printHelp(): print( """Views a TACK, TACK Key, TACK Break Sig, or certificate. -view ("-" for stdin) +view [-x] ("-" for stdin) + +Optional arguments: + -x : Use python crypto (not OpenSSL) to verify signatures """) diff --git a/tack/crypto/openssl/OpenSSL.py b/tack/crypto/openssl/OpenSSL.py index 01732a0..62db17a 100644 --- a/tack/crypto/openssl/OpenSSL.py +++ b/tack/crypto/openssl/OpenSSL.py @@ -22,10 +22,10 @@ def initialize(self): else: libraryName = find_library("crypto") if not libraryName: - self._setInitError("OpenSSL not found") + self.setInitError("OpenSSL not found") return except: - self._setInitError("OpenSSL not found") + self.setInitError("OpenSSL not found") raise try: @@ -34,7 +34,7 @@ def initialize(self): else: self._lib = cdll.LoadLibrary(libraryName) except: - self._setInitError("error loading OpenSSL") + self.setInitError("error loading OpenSSL") return try: @@ -95,7 +95,7 @@ def _add(self, name, ret=None, args=None, skipWrap=False): try: func = getattr(self._lib, name) except: - self._setInitError("error loading OpenSSL:%s" % name) + self.setInitError("error loading OpenSSL:%s" % name) raise if ret: @@ -116,7 +116,7 @@ def wrappedFunc(*a): return retval setattr(self, name, wrappedFunc) - def _setInitError(self, s): + def setInitError(self, s): self.enabled = False self.initErrorString = s From 86eefbcc7563924ec5626b8ab781817bcf5acc93 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 17 May 2012 19:29:28 +0300 Subject: [PATCH 53/94] Better reporting of missing EC in OpenSSL. --- README | 26 ++++++++++++++------------ tack/crypto/openssl/OpenSSL.py | 11 +++++++++-- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/README b/README index 00ecdbc..b6b8333 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -tackpy version 0.9.6 Feb 23 2012 +Tackpy version 0.9.6 Feb 23 2012 ============================================================================ Licenses/Acknowledgements @@ -14,22 +14,24 @@ Installation ============= Tackpy requires Python 2.6 or greater, or Python 3. -Run "python setup.py install" or "make install". This installs: +Run "make install" or "python setup.py install". This installs: - The "tack" library for use by other Python programs (such as TLS Lite). - - The "tack" command for working with TACKs. + - The "tack" command-line tool. -To use the "tack" command without installation you can run -"selfcontained/tack". +To use the command-line tool without installation run "./tack.py". +OpenSSL +-------- Tackpy tries to use OpenSSL for AES and ECDSA operations. If OpenSSL cannot be -loaded, Tackpy will fall back to using python crypto code. On UNIX-like -systems, Tackpy will try to load OpenSSL via the "crypto" shared library. On -Windows, Tackpy will try to load "libeay32.dll" (which must be in the path). -Run "tack help" to see if OpenSSL was loaded. +loaded, Tackpy will fall back to using slower python crypto code. +To use OpenSSL on Windows you will need "libeay32.dll" on your path. On Red +Hat Linux systems you will need to provide your own libcrypto as the system +default does not include elliptic curve functionality. -Quick start -============ + +Quick start with command-line tool +=================================== You will need to create one or more TACK keys to "pin" your hostnames to. You should use a different key for each hostname, unless those hostnames are closely related (such as aliases for the same host, or hosts sharing a TLS @@ -98,7 +100,7 @@ break signatures add to TLS handshake overhead, so are best avoided. Create a break signature for a TACK: 1) Run "tack break -k KEY.pem > TACK_Break_Sig.pem" 2) Add the break signature to your web server. - + - Apache: Set "SSLTACKBreakSigsFile" to a file with break signatures. Advanced uses ============== diff --git a/tack/crypto/openssl/OpenSSL.py b/tack/crypto/openssl/OpenSSL.py index 62db17a..f675a4c 100644 --- a/tack/crypto/openssl/OpenSSL.py +++ b/tack/crypto/openssl/OpenSSL.py @@ -49,8 +49,15 @@ class ECDSA_SIG(Structure): self._add("ERR_get_error", ret=c_long, skipWrap=True) self._add("ERR_peek_last_error", ret=c_long, skipWrap=True) self._add("ERR_error_string", ret=c_char_p, args=[c_long, c_char_p], skipWrap=True) - - self._add("EC_KEY_new_by_curve_name", ret=c_void_p, args=[c_int]) + + # Handle this specially as it may indicate Red Hat's + # OpenSSL without EC + try: + self._add("EC_KEY_new_by_curve_name", ret=c_void_p, args=[c_int]) + except: + self.setInitError("OpenSSL is missing EC functions") + return + self._add("EC_KEY_free", args=[c_void_p], skipWrap=True) self._add("EC_KEY_dup", ret=c_void_p, args=[c_void_p]) self._add("EC_KEY_generate_key", args=[c_void_p]) From df2bbd02aadc780aef66b3c98b7c7c730f868152 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 17 May 2012 22:43:49 +0300 Subject: [PATCH 54/94] Adapt TlsCertificate to new classes, fixup "tackcert" to handle DER/PEM. --- Makefile | 2 +- tack/commands/CertificateCommand.py | 64 ++++++---------- tack/commands/Command.py | 20 ++++- tack/commands/SignCommand.py | 3 +- tack/commands/ViewCommand.py | 31 ++------ tack/crypto/AES.py | 2 +- tack/crypto/ECPrivateKey.py | 2 +- tack/crypto/ECPublicKey.py | 2 +- tack/structures/Tack.py | 35 ++++----- tack/structures/TackBreakSig.py | 21 +++--- tack/structures/TackExtension.py | 18 +++-- tack/structures/TackKeyFile.py | 33 +++++---- tack/tls/TlsCertificate.py | 110 ++++++++++++++-------------- 13 files changed, 163 insertions(+), 180 deletions(-) diff --git a/Makefile b/Makefile index 030d050..fbb5666 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ clean: # Variables for testing TESTDIR = testoutput -EXEC = ./tack.py +EXEC = python3 ./tack.py CERT1 = ./testdata/serverX509Cert.pem CERT2 = ./testdata/serverX509Cert.der diff --git a/tack/commands/CertificateCommand.py b/tack/commands/CertificateCommand.py index 2d6af1f..fa69c36 100644 --- a/tack/commands/CertificateCommand.py +++ b/tack/commands/CertificateCommand.py @@ -13,21 +13,16 @@ def __init__(self, argv): Command.__init__(self, argv, "oib", "v") self.outputFile, self.outputFileName = self.getOutputFile() - self.inputTack = self._getInputTack() - self.inputCertificate = self._getInputCertificate() + (self.inputTack, self.inputCertificate) = self._getInputFile() self.breakSignatures = self._getBreakSignatures() - if self.inputTack is None and self.inputCertificate is None: - self.printError("-i missing") - def execute(self): if self.inputTack is not None: tackExtension = TackExtension.create(self.inputTack, self.breakSignatures, TackActivation.DISABLED) - tlsCertificate = TlsCertificate() - tlsCertificate.create(tackExtension) - self.outputFile.write(tlsCertificate.writePem()) + tlsCertificate = TlsCertificate.create(tackExtension) + self.outputFile.write(tlsCertificate.serializeAsPem()) if self.isVerbose(): sys.stderr.write(str(tackExtension) + "\n") @@ -48,6 +43,8 @@ def execute(self): if self.isVerbose(): sys.stderr.write(self.inputCertificate.writeText()) + else: + assert(False) def _getBreakSignatures(self): fileName = self._getOptionValue("-b") @@ -60,45 +57,28 @@ def _getBreakSignatures(self): except IOError: self.printError("Error opening break signature: %s" % fileName) - return TackBreakSig.createFromPemList(contents) - - def _getInputTack(self): - contents = self._getInputFileContents() - - if contents is None: - return None - - if PEMDecoder(contents).containsEncoded("TACK"): - return Tack.createFromPem(contents) - - return None - - def _getInputCertificate(self): - contents = self._getInputFileContents() - - if contents is None: - return None - - if PEMDecoder(contents).containsEncoded("CERTIFICATE"): - certificate = TlsCertificate() - certificateName = self._getOptionValue("-i") - try: - certificate.open(open(certificateName, "rb").read()) - except IOError: - self.printError("Error opening certificate: %s" % certificateName) - return certificate - - def _getInputFileContents(self): + def _getInputFile(self): fileName = self._getOptionValue("-i") - if fileName is None: - return None - + self.printError("-i missing") try: - return open(fileName, "r").read() + + text, binary = self._readFileTextAndBinary(fileName) + if text: + pem = PEMDecoder(text) + if pem.containsEncoded("TACK"): + return (Tack.createFromPem(text), None) + elif pem.containsEncoded("CERTIFICATE"): + return (None, TlsCertificate.createFromPem(text)) + else: + self.printError("Unrecognized input file: %s" % fileName) + else: + return (None, TlsCertificate(binary)) + except IOError: self.printError("Error opening input file: %s" % fileName) - + except SyntaxError: + self.printError("Error parsing input file: %s" % fileName) @staticmethod def printHelp(): diff --git a/tack/commands/Command.py b/tack/commands/Command.py index deac91c..6f2a0b3 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -5,7 +5,7 @@ from tack.structures.TackKeyFile import TackKeyFile from tack.util.Time import Time from tack.crypto.openssl.OpenSSL import openssl as o -from tack.compat import bytesToStr +from tack.compat import bytesToStr, readStdinBinary from tack.version import __version__ from tack.InvalidPasswordException import InvalidPasswordException @@ -109,6 +109,24 @@ def _containsOption(self, flag): if option == flag: return True + def _readFileTextAndBinary(self, fname): + try: + # Read both binary (bytearray) and text (str) versions of the input + try: + if fname == "-": + # Read as binary + binary = readStdinBinary() + else: + binary = bytearray(open(fname, "rb").read()) + text = bytesToStr(binary, "ascii") + except UnicodeDecodeError: + # So it must be a binary file, not text + text = None + + return text, binary + except IOError: + self.printError("Error opening file: %s" % argv[0]) + def printError(self, error): """Print error message and exit""" sys.stderr.write("ERROR: %s\n" % error) diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index e68cf88..211bbc7 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -76,8 +76,7 @@ def _getCertificate(self): else: certificateBytes = bytearray(open(certificateFile, "rb").read()) - inCert = TlsCertificate() - inCert.open(certificateBytes) + inCert = TlsCertificate.createFromBytes(certificateBytes) return inCert except SyntaxError: self.printError("Certificate malformed: %s" % certificateFile) diff --git a/tack/commands/ViewCommand.py b/tack/commands/ViewCommand.py index 325ea2c..4c7fdac 100644 --- a/tack/commands/ViewCommand.py +++ b/tack/commands/ViewCommand.py @@ -18,27 +18,8 @@ def __init__(self, argv): if len(self.argRemainder) > 1: self.printError("Can only view one file") - - def _readFile(self, fname): - try: - # Read both binary (bytearray) and text (str) versions of the input - try: - if fname == "-": - # Read as binary - b = readStdinBinary() - else: - b = bytearray(open(fname, "rb").read()) - s = bytesToStr(b, "ascii") - except UnicodeDecodeError: - # Python3 error, so it must be a binary file; not text - s = None - - return s, b - except IOError: - self.printError("Error opening file: %s" % argv[0]) - def execute(self): - text, binary = self._readFile(self.argRemainder[0]) + text, binary = self._readFileTextAndBinary(self.argRemainder[0]) fileType = None try: @@ -64,15 +45,13 @@ def execute(self): return elif decoder.containsEncoded( "CERTIFICATE"): fileType = "Certificate" - sslc = TlsCertificate() - sslc.parsePem(text) - sys.stdout.write(sslc.writeText()) + sslc = TlsCertificate.createFromPem(text) + sys.stdout.write(str(sslc)) return # Is it a certificate? try: - sslc = TlsCertificate() - sslc.parse(binary) - sys.stdout.write(sslc.writeText()) + sslc = TlsCertificate(binary) + sys.stdout.write(str(sslc)) except SyntaxError: self.printError("Unrecognized file type") except SyntaxError as e: diff --git a/tack/crypto/AES.py b/tack/crypto/AES.py index 87215c1..84004c6 100644 --- a/tack/crypto/AES.py +++ b/tack/crypto/AES.py @@ -5,7 +5,7 @@ class AES: @staticmethod - def new(key, IV): + def create(key, IV): if openssl.enabled: return OpenSSL_AES(key, IV) else: diff --git a/tack/crypto/ECPrivateKey.py b/tack/crypto/ECPrivateKey.py index a153568..e5546d7 100644 --- a/tack/crypto/ECPrivateKey.py +++ b/tack/crypto/ECPrivateKey.py @@ -5,7 +5,7 @@ class ECPrivateKey: @staticmethod - def new(rawPrivateKey, rawPublicKey): + def create(rawPrivateKey, rawPublicKey): if o.enabled: return OpenSSL_ECPrivateKey(rawPrivateKey, rawPublicKey) else: diff --git a/tack/crypto/ECPublicKey.py b/tack/crypto/ECPublicKey.py index d8e6f51..e45ebef 100644 --- a/tack/crypto/ECPublicKey.py +++ b/tack/crypto/ECPublicKey.py @@ -5,7 +5,7 @@ class ECPublicKey: @staticmethod - def new(rawPublicKey): + def create(rawPublicKey): if o.enabled: return OpenSSL_ECPublicKey(rawPublicKey) else: diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index eef027c..7be6727 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -10,24 +10,25 @@ class Tack(TlsStructure): LENGTH = 166 def __init__(self, data=None): - TlsStructure.__init__(self, data) + if data is None: + return - if data is not None: - if len(data) != Tack.LENGTH: - raise SyntaxError("TACK is the wrong size. Is %s and should be %s" % (len(data), Tack.LENGTH)) - - self.public_key = ECPublicKey.new(self.getBytes(64)) - self.min_generation = self.getInt(1) - self.generation = self.getInt(1) - self.expiration = self.getInt(4) - self.target_hash = self.getBytes(32) - self.signature = self.getBytes(64) - - if self.generation < self.min_generation: - raise SyntaxError("Generation less than min_generation") - - if not self.verifySignature(): - raise SyntaxError("TACK has bad signature") + TlsStructure.__init__(self, data) + if len(data) != Tack.LENGTH: + raise SyntaxError("TACK is the wrong size. Is %s and should be %s" % (len(data), Tack.LENGTH)) + + self.public_key = ECPublicKey.create(self.getBytes(64)) + self.min_generation = self.getInt(1) + self.generation = self.getInt(1) + self.expiration = self.getInt(4) + self.target_hash = self.getBytes(32) + self.signature = self.getBytes(64) + + if self.generation < self.min_generation: + raise SyntaxError("Generation less than min_generation") + + if not self.verifySignature(): + raise SyntaxError("TACK has bad signature") @classmethod diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index 2ff1935..16891c2 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -8,17 +8,18 @@ class TackBreakSig(TlsStructure): LENGTH = 128 def __init__(self, data=None): - TlsStructure.__init__(self, data) - - if data is not None: - if len(data) != TackBreakSig.LENGTH: - raise SyntaxError("Break signature is the wrong size. Is %s and should be %s." % (len(data), TackBreakSig.LENGTH)) + if data is None: + return - self.public_key = ECPublicKey.new(self.getBytes(64)) - self.signature = self.getBytes(64) - - if not self.verifySignature(): - raise SyntaxError("TACK_Break_Sig has bad signature") + TlsStructure.__init__(self, data) + if len(data) != TackBreakSig.LENGTH: + raise SyntaxError("Break signature is the wrong size. Is %s and should be %s." % (len(data), TackBreakSig.LENGTH)) + + self.public_key = ECPublicKey.create(self.getBytes(64)) + self.signature = self.getBytes(64) + + if not self.verifySignature(): + raise SyntaxError("TACK_Break_Sig has bad signature") @classmethod diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index 11b20b4..e54be10 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -8,17 +8,19 @@ class TackExtension(TlsStructure): def __init__(self, data=None): + if data is None: + return + TlsStructure.__init__(self, data) - if data is not None: - self.tack = self._parseTack() - self.break_sigs = self._parseBreakSigs() - self.pin_activation = self.getInt(1) + self.tack = self._parseTack() + self.break_sigs = self._parseBreakSigs() + self.pin_activation = self.getInt(1) - if self.pin_activation not in TackActivation.ALL: - raise SyntaxError("Bad pin_activation value") + if self.pin_activation not in TackActivation.ALL: + raise SyntaxError("Bad pin_activation value") - if self.index != len(data): - raise SyntaxError("Excess bytes in TACK_Extension") + if self.index != len(data): + raise SyntaxError("Excess bytes in TACK_Extension") @classmethod def create(cls, tack, break_sigs, pin_activation): diff --git a/tack/structures/TackKeyFile.py b/tack/structures/TackKeyFile.py index acc6106..43f3724 100644 --- a/tack/structures/TackKeyFile.py +++ b/tack/structures/TackKeyFile.py @@ -37,22 +37,27 @@ class TackKeyFile(TlsStructure): LENGTH = 149 # length of keyfile in bytes def __init__(self, data=None, password=None): + if data is None: + return + TlsStructure.__init__(self, data) - if data is not None: - self.version = self.getInt(1) + if len(data) != TackKeyFile.LENGTH: + raise SyntaxError("Key File is the wrong size. Is %s and should be %s." % (len(data), TackKeyFile.LENGTH)) + + self.version = self.getInt(1) - if self.version != 1: - raise SyntaxError("Bad version in Key File") + if self.version != 1: + raise SyntaxError("Bad version in Key File") - self.iter_count = self.getInt(4) - self.salt = self.getBytes(16) - self.ciphertext = self.getBytes(32) - self.public_key = ECPublicKey.new(self.getBytes(64)) - self.mac = self.getBytes(32) + self.iter_count = self.getInt(4) + self.salt = self.getBytes(16) + self.ciphertext = self.getBytes(32) + self.public_key = ECPublicKey.create(self.getBytes(64)) + self.mac = self.getBytes(32) - if password is not None: - rawPrivateKey = self._decrypt(password) - self.private_key = ECPrivateKey.new(rawPrivateKey, self.public_key.getRawKey()) + if password is not None: + rawPrivateKey = self._decrypt(password) + self.private_key = ECPrivateKey.create(rawPrivateKey, self.public_key.getRawKey()) @classmethod def create(cls, public_key, private_key, password): @@ -89,7 +94,7 @@ def serializeAsPem(self): def _encrypt(self, password): encKey, authKey = self._deriveKeys(password, self.salt, self.iter_count) - ciphertext = AES.new(encKey, bytearray(16)).encrypt(self.private_key.getRawKey()) + ciphertext = AES.create(encKey, bytearray(16)).encrypt(self.private_key.getRawKey()) macData = ciphertext + self.public_key.getRawKey() mac = Digest.HMAC_SHA256(authKey, macData) self.ciphertext = ciphertext @@ -103,7 +108,7 @@ def _decrypt(self, password): if not Util.constTimeCompare(calcMac, self.mac): raise InvalidPasswordException("Bad password") - return AES.new(encKey, bytearray(16)).decrypt(self.ciphertext) + return AES.create(encKey, bytearray(16)).decrypt(self.ciphertext) # Uses PBKDF2, then HMAC-SHA256 as PRF to derive independent 32-byte keys def _deriveKeys(self, password, salt, iter_count): diff --git a/tack/tls/TlsCertificate.py b/tack/tls/TlsCertificate.py index ca3efdf..fe73cdd 100644 --- a/tack/tls/TlsCertificate.py +++ b/tack/tls/TlsCertificate.py @@ -11,61 +11,22 @@ class TlsCertificate: OID_TACK = bytearray(b"\x2B\x06\x01\x04\x01\x82\xB0\x34\x01") -# def __init__(self, data): -# if data is not None: -# self.serialized = data -# self.certificate = X509.load_cert_string(data) -# self.notAfter = time.mktime(self.certificate.get_not_after().get_datetime().timetuple()) -# self.cert_sha256 = bytearray(base64.b16decode(self.certificate.get_fingerprint(md='sha256'))) -# self.key_sha256 = Util.SHA256(self.certificate.get_pubkey().as_der()) -# self.tackExt = self._parseTackExtension(data) - - def __init__(self): - self.key_sha256 = bytearray(32) - self.cert_sha256 = bytearray(32) + def __init__(self, data=None): + if data is None: + return + #self.key_sha256 = bytearray(32) + #self.cert_sha256 = bytearray(32) self.notAfter = 0 + # Below values are populated for TACK certs self.tackExt = None + # Below values hold cert contents excluding TACK stuff self.preExtBytes = None self.extBytes = None self.postExtBytes = None - def create(self, tackExt = None): - self.tackExt = tackExt - self.preExtBytes = a2b_hex( - "a003020102020100300d06092a864886f70d0101050500300f310d300b06035504031" - "3045441434b301e170d3031303730353138303534385a170d34343037303431383035" - "34385a300f310d300b060355040313045441434b301f300d06092a864886f70d01010" - "10500030e00300b0204010203050203010001") - # Below is BasicConstraints, saving space by omitting - #self.extBytes = binascii.a2b_hex(\ - #"300c0603551d13040530030101ff") - self.extBytes = bytearray() - self.postExtBytes = a2b_hex( - "300d06092a864886f70d01010505000303003993") - - def open(self, fileBytes): - # May raise SyntaxError - try: - fileStr = bytesToStr(fileBytes, "ascii") # UnicodeDecodeError - self.parsePem(fileStr) # SyntaxError - return - except (UnicodeDecodeError, SyntaxError) as e: - # File had non-ASCII chars in it, *OR* - # File did not PEM-decode - pass - self.parse(bytearray(fileBytes)) # SyntaxError - - def matches(self, tack): - return self.key_sha256 == tack.target_hash - - def parsePem(self, s): - b = PEMDecoder(s).decode("CERTIFICATE") - self.parse(b) - - def parse(self, b): - p = ASN1Parser(b) + p = ASN1Parser(data) #Get the tbsCertificate tbsCertificateP = p.getChild(0) @@ -92,7 +53,7 @@ def parse(self, b): raise SyntaxError() # Get the hash values - self.cert_sha256 = Digest.SHA256(b) + self.cert_sha256 = Digest.SHA256(data) self.key_sha256 = Digest.SHA256(spkiP.getTotalBytes()) # Check if this is a TACK certificate: @@ -114,7 +75,7 @@ def parse(self, b): break x += 1 - self.preExtBytes = b[versionP.offset : certFieldP.offset] + self.preExtBytes = data[versionP.offset : certFieldP.offset] self.extBytes = bytearray() # Iterate through extensions @@ -135,14 +96,48 @@ def parse(self, b): self.tackExt = TackExtension(extFieldP.getChild(1).value) else: # Collect all non-TACK extensions: - self.extBytes += b[extFieldP.offset :\ + self.extBytes += data[extFieldP.offset :\ extFieldP.offset + extFieldP.getTotalLength()] x += 1 # Finish copying the tail of the certificate - self.postExtBytes = b[certFieldP.offset + certFieldP.getTotalLength():] + self.postExtBytes = data[certFieldP.offset + certFieldP.getTotalLength():] - def write(self): + @classmethod + def create(cls, tackExt = None): + tlsCert = cls() + tlsCert.tackExt = tackExt + tlsCert.preExtBytes = a2b_hex( + "a003020102020100300d06092a864886f70d0101050500300f310d300b06035504031" + "3045441434b301e170d3031303730353138303534385a170d34343037303431383035" + "34385a300f310d300b060355040313045441434b301f300d06092a864886f70d01010" + "10500030e00300b0204010203050203010001") + # Below is BasicConstraints, saving space by omitting + #self.extBytes = binascii.a2b_hex(\ + #"300c0603551d13040530030101ff") + tlsCert.extBytes = bytearray() + tlsCert.postExtBytes = a2b_hex( + "300d06092a864886f70d01010505000303003993") + + # Parse the cert to populate its key_sha256, cert_sha256, and notAfter + return cls(tlsCert.serialize()) + + @classmethod + def createFromBytes(cls, fileBytes): + # May raise SyntaxError + try: + fileStr = bytesToStr(fileBytes, "ascii") # UnicodeDecodeError + return cls.createFromPem(fileStr) # SyntaxError + except (UnicodeDecodeError, SyntaxError) as e: + # File had non-ASCII chars in it, *OR* + # File did not PEM-decode + return cls(bytearray(fileBytes)) # SyntaxError + + @classmethod + def createFromPem(cls, s): + return cls(PEMDecoder(s).decode("CERTIFICATE")) + + def serialize(self): b = bytearray(0) if self.tackExt: # type=SEQ,len=?,type=6,len=9(for OID), @@ -165,10 +160,10 @@ def write(self): b = bytearray([0x30]) + asn1Length(len(b)) + b return b - def writePem(self): - b = self.write() - return PEMEncoder(b).encode("CERTIFICATE") - def writeText(self): + def serializeAsPem(self): + return PEMEncoder(self.serialize()).encode("CERTIFICATE") + + def __str__(self): s =\ """key_sha256 = %s notAfter = %s @@ -178,3 +173,6 @@ def writeText(self): if self.tackExt: s += "\n" + str(self.tackExt) return s + + def matches(self, tack): + return self.key_sha256 == tack.target_hash From c1780b9a57259d561dab69f4c1d13d0e915fb824 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 17 May 2012 23:28:16 +0300 Subject: [PATCH 55/94] Passing unit tests, now. --- tack/crypto/openssl/OpenSSL.py | 3 +++ tests/Certificate_Test.py | 12 +++++++++--- tests/Crypto_Test.py | 16 +++++++++------- tests/Structures_Test.py | 4 +++- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/tack/crypto/openssl/OpenSSL.py b/tack/crypto/openssl/OpenSSL.py index f675a4c..dbd52b3 100644 --- a/tack/crypto/openssl/OpenSSL.py +++ b/tack/crypto/openssl/OpenSSL.py @@ -13,6 +13,9 @@ def cToBytes(c): class OpenSSL: + def __init__(self): + self.setInitError("uninitialized") + def initialize(self): self.initErrorString = "unknown error loading OpenSSL" diff --git a/tests/Certificate_Test.py b/tests/Certificate_Test.py index 766f2ba..58b4ded 100644 --- a/tests/Certificate_Test.py +++ b/tests/Certificate_Test.py @@ -38,12 +38,18 @@ def test_Certificate(self): 5CvQvlPHaG/TTXXNh3pZFl3d/J5/76ZfeQzQtZ+dCrE4a4601Q4hBBXEq5gQfaof H4yTGzfDv+JLIICAIcCs -----END CERTIFICATE-----""" - sslc = TlsCertificate() - sslc.parsePem(s) + sslc = TlsCertificate.createFromPem(s) assert(sslc.key_sha256 == a2b_hex("ffd30bcb84dbbc211a510875694354c58863d84fb7fc5853dfe36f4be2eb2e50")) assert(sslc.cert_sha256 == a2b_hex("1a50e3de3a153f33b314b67c1aacc2f59fc99c49b8449c33dcc3665663e2bff1")) assert(Time.posixTimeToStr(sslc.notAfter, True) == "2012-07-08T00:19:57Z") - assert(isinstance(sslc.writeText(), str)) + + # Write to binary and re-parse it, then check again + b = sslc.serialize() + sslc2 = TlsCertificate(b) + assert(sslc2.key_sha256 == a2b_hex("ffd30bcb84dbbc211a510875694354c58863d84fb7fc5853dfe36f4be2eb2e50")) + assert(sslc2.cert_sha256 == a2b_hex("1a50e3de3a153f33b314b67c1aacc2f59fc99c49b8449c33dcc3665663e2bff1")) + assert(Time.posixTimeToStr(sslc2.notAfter, True) == "2012-07-08T00:19:57Z") + return 1 if __name__ == '__main__': diff --git a/tests/Crypto_Test.py b/tests/Crypto_Test.py index c45ed8f..59c2af4 100644 --- a/tests/Crypto_Test.py +++ b/tests/Crypto_Test.py @@ -4,23 +4,25 @@ from tack.crypto.ASN1 import asn1Length, toAsn1IntBytes, fromAsn1IntBytes from tack.crypto.ECGenerator import ECGenerator +import binascii + class CryptoTest(unittest.TestCase): def test_AES(self): - key = a2b_hex("c286696d887c9aa0611bbb3e2025a45a") - IV = a2b_hex("562e17996d093d28ddb3ba695a2e6f58") - plaintext = a2b_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") - ciphertext = a2b_hex("d296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1") + key = a2b_hex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") + IV = a2b_hex("000102030405060708090A0B0C0D0E0F") + plaintext = a2b_hex("6bc1bee22e409f96e93d7e117393172a") + ciphertext = a2b_hex("f58c4c04d6e5f1ba779eabfb5f7bfbd6") - assert(AES(key, IV).encrypt(plaintext) == ciphertext) - assert(AES(key, IV).decrypt(ciphertext) == plaintext) + assert(AES.create(key, IV).encrypt(plaintext) == ciphertext) + assert(AES.create(key, IV).decrypt(ciphertext) == plaintext) def test_ECDSA(self): publicKey, privateKey = ECGenerator().generateECKeyPair() data = bytearray([0,1,2,3]) badData = bytearray([0,1,2,4]) - signature = privateKey.getSignature(data) + signature = privateKey.sign(data) assert(publicKey.verify(data, signature)) assert(not publicKey.verify(badData, signature)) diff --git a/tests/Structures_Test.py b/tests/Structures_Test.py index 191825b..7e4cbc8 100644 --- a/tests/Structures_Test.py +++ b/tests/Structures_Test.py @@ -1,5 +1,6 @@ import unittest from tack.compat import a2b_hex +from tack.crypto.ECGenerator import ECGenerator from tack.structures.Tack import Tack from tack.structures.TackBreakSig import TackBreakSig from tack.structures.TackKeyFile import TackKeyFile @@ -89,7 +90,8 @@ def test_KeyFile(self): kf2 = TackKeyFile.createFromPem(kf.serializeAsPem(), "asdf") assert(kf2.getPublicKey().getRawKey() == publicKey) assert(kf2.getPrivateKey().getRawKey() == privateKey) - kf3 = TackKeyFile.createRandom("123") + public_key, private_key = ECGenerator.generateECKeyPair() + kf3 = TackKeyFile.create(public_key, private_key, "123") kf4 = TackKeyFile.createFromPem(kf3.serializeAsPem(), "123") assert(kf3.getPublicKey().getRawKey() == kf4.getPublicKey().getRawKey()) From 306fdbcd0a13f22b70b1b858f9da078134615cb5 Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 18 May 2012 00:03:55 +0300 Subject: [PATCH 56/94] Added license headers to all source files. --- README | 6 +++--- setup.py | 8 ++++++-- tack.py | 6 +++++- tack/InvalidPasswordException.py | 5 +++++ tack/commands/BreakCommand.py | 6 ++++++ tack/commands/CertificateCommand.py | 6 ++++++ tack/commands/Command.py | 6 ++++++ tack/commands/GenerateKeyCommand.py | 6 ++++++ tack/commands/HelpCommand.py | 6 ++++++ tack/commands/SignCommand.py | 6 ++++++ tack/commands/ViewCommand.py | 6 ++++++ tack/compat.py | 6 ++++-- tack/crypto/AES.py | 6 ++++++ tack/crypto/ASN1.py | 6 ++++-- tack/crypto/Digest.py | 6 ++++++ tack/crypto/ECGenerator.py | 6 ++++++ tack/crypto/ECPrivateKey.py | 6 ++++++ tack/crypto/ECPublicKey.py | 6 ++++++ tack/crypto/PBKDF2.py | 6 ++++++ tack/crypto/openssl/OpenSSL.py | 5 +++++ tack/crypto/openssl/OpenSSL_AES.py | 6 ++++++ tack/crypto/openssl/OpenSSL_ECGenerator.py | 6 ++++++ tack/crypto/openssl/OpenSSL_ECPrivateKey.py | 6 ++++++ tack/crypto/openssl/OpenSSL_ECPublicKey.py | 6 ++++++ tack/crypto/python/Python_AES.py | 4 ++++ tack/crypto/python/Python_ECGenerator.py | 5 +++++ tack/crypto/python/Python_ECPrivateKey.py | 5 +++++ tack/crypto/python/Python_ECPublicKey.py | 5 +++++ tack/crypto/python/cryptomath.py | 6 +++--- tack/structures/Tack.py | 6 ++++++ tack/structures/TackActivation.py | 5 +++++ tack/structures/TackBreakSig.py | 6 ++++++ tack/structures/TackExtension.py | 6 ++++++ tack/structures/TackKeyFile.py | 6 ++++++ tack/tls/TlsCertificate.py | 6 ++++++ tack/tls/TlsStructure.py | 5 +++++ tack/tls/TlsStructureWriter.py | 6 ++++++ tack/util/PEMDecoder.py | 6 ++++++ tack/util/PEMEncoder.py | 6 ++++++ tack/util/Time.py | 5 ++++- tack/util/Util.py | 6 ++++++ tests/Certificate_Test.py | 6 ++++++ tests/Compat_Test.py | 6 ++++++ tests/Crypto_Test.py | 6 ++++++ tests/Structures_Test.py | 6 ++++++ tests/Time_Test.py | 6 ++++++ 46 files changed, 254 insertions(+), 14 deletions(-) diff --git a/README b/README index b6b8333..0afb479 100644 --- a/README +++ b/README @@ -25,9 +25,9 @@ OpenSSL Tackpy tries to use OpenSSL for AES and ECDSA operations. If OpenSSL cannot be loaded, Tackpy will fall back to using slower python crypto code. -To use OpenSSL on Windows you will need "libeay32.dll" on your path. On Red -Hat Linux systems you will need to provide your own libcrypto as the system -default does not include elliptic curve functionality. +To use OpenSSL on Windows you need "libeay32.dll" on your path. On Red Hat +systems you need to provide your own libcrypto as the system default does not +include elliptic curve support. Quick start with command-line tool diff --git a/setup.py b/setup.py index 20006ed..adb452f 100755 --- a/setup.py +++ b/setup.py @@ -1,9 +1,13 @@ #!/usr/bin/env python +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + from distutils.core import setup -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. import os import shutil diff --git a/tack.py b/tack.py index 6d10fb4..97c0390 100755 --- a/tack.py +++ b/tack.py @@ -1,7 +1,11 @@ #! /usr/bin/env python -# Author: Trevor Perrin +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# # See the LICENSE file for legal information regarding use of this file. + import sys from tack.commands.BreakCommand import BreakCommand from tack.commands.CertificateCommand import CertificateCommand diff --git a/tack/InvalidPasswordException.py b/tack/InvalidPasswordException.py index 7005c95..2534a10 100644 --- a/tack/InvalidPasswordException.py +++ b/tack/InvalidPasswordException.py @@ -1,3 +1,8 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. class InvalidPasswordException(Exception): def __init__(self, args): diff --git a/tack/commands/BreakCommand.py b/tack/commands/BreakCommand.py index 85d9e58..f0458a8 100644 --- a/tack/commands/BreakCommand.py +++ b/tack/commands/BreakCommand.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import sys from tack.commands.Command import Command from tack.structures.TackBreakSig import TackBreakSig diff --git a/tack/commands/CertificateCommand.py b/tack/commands/CertificateCommand.py index fa69c36..68522ea 100644 --- a/tack/commands/CertificateCommand.py +++ b/tack/commands/CertificateCommand.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import sys from tack.commands.Command import Command from tack.structures.Tack import Tack diff --git a/tack/commands/Command.py b/tack/commands/Command.py index 6f2a0b3..5e8e9fb 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import getopt import getpass import sys diff --git a/tack/commands/GenerateKeyCommand.py b/tack/commands/GenerateKeyCommand.py index 355ed47..1054c7c 100644 --- a/tack/commands/GenerateKeyCommand.py +++ b/tack/commands/GenerateKeyCommand.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import getpass import sys from tack.commands.Command import Command diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index 7a7fded..9d15d2b 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import sys from tack.compat import bytesToStr from tack.commands.CertificateCommand import CertificateCommand diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index 211bbc7..b977b7f 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import sys import time import math diff --git a/tack/commands/ViewCommand.py b/tack/commands/ViewCommand.py index 4c7fdac..e397335 100644 --- a/tack/commands/ViewCommand.py +++ b/tack/commands/ViewCommand.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import sys from tack.compat import bytesToStr from tack.commands.Command import Command diff --git a/tack/compat.py b/tack/compat.py index 64b2fce..0c7a0a2 100644 --- a/tack/compat.py +++ b/tack/compat.py @@ -1,7 +1,9 @@ -# Author: Trevor Perrin +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# # See the LICENSE file for legal information regarding use of this file. -################ COMPAT ### """These functions abstract away the differences between Python 2 and 3. """ diff --git a/tack/crypto/AES.py b/tack/crypto/AES.py index 84004c6..5af2c4c 100644 --- a/tack/crypto/AES.py +++ b/tack/crypto/AES.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + from .openssl.OpenSSL import openssl from .openssl.OpenSSL_AES import OpenSSL_AES from .python.Python_AES import Python_AES diff --git a/tack/crypto/ASN1.py b/tack/crypto/ASN1.py index 1961505..442cb70 100644 --- a/tack/crypto/ASN1.py +++ b/tack/crypto/ASN1.py @@ -1,7 +1,9 @@ -# Author: Trevor Perrin +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# # See the LICENSE file for legal information regarding use of this file. -################ ASN1 ### from tack.tls.TlsStructure import TlsStructure def asn1Length(x): diff --git a/tack/crypto/Digest.py b/tack/crypto/Digest.py index 5b90fda..6da47eb 100644 --- a/tack/crypto/Digest.py +++ b/tack/crypto/Digest.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import hashlib import hmac from tack.compat import compat26Str diff --git a/tack/crypto/ECGenerator.py b/tack/crypto/ECGenerator.py index 7409f5b..db55986 100644 --- a/tack/crypto/ECGenerator.py +++ b/tack/crypto/ECGenerator.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + from .openssl.OpenSSL_ECGenerator import OpenSSL_ECGenerator from .python.Python_ECGenerator import Python_ECGenerator from .openssl.OpenSSL import openssl as o diff --git a/tack/crypto/ECPrivateKey.py b/tack/crypto/ECPrivateKey.py index e5546d7..045ee91 100644 --- a/tack/crypto/ECPrivateKey.py +++ b/tack/crypto/ECPrivateKey.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + from .python.Python_ECPrivateKey import Python_ECPrivateKey from .openssl.OpenSSL_ECPrivateKey import OpenSSL_ECPrivateKey from .openssl.OpenSSL import openssl as o diff --git a/tack/crypto/ECPublicKey.py b/tack/crypto/ECPublicKey.py index e45ebef..f61534a 100644 --- a/tack/crypto/ECPublicKey.py +++ b/tack/crypto/ECPublicKey.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + from .python.Python_ECPublicKey import Python_ECPublicKey from .openssl.OpenSSL import openssl as o from .openssl.OpenSSL_ECPublicKey import OpenSSL_ECPublicKey diff --git a/tack/crypto/PBKDF2.py b/tack/crypto/PBKDF2.py index 9a902f0..9d4d84d 100644 --- a/tack/crypto/PBKDF2.py +++ b/tack/crypto/PBKDF2.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + from tack.crypto.Digest import Digest class PBKDF2: diff --git a/tack/crypto/openssl/OpenSSL.py b/tack/crypto/openssl/OpenSSL.py index dbd52b3..b61a004 100644 --- a/tack/crypto/openssl/OpenSSL.py +++ b/tack/crypto/openssl/OpenSSL.py @@ -1,3 +1,8 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. import sys from ctypes import * diff --git a/tack/crypto/openssl/OpenSSL_AES.py b/tack/crypto/openssl/OpenSSL_AES.py index 68433ef..3532db5 100644 --- a/tack/crypto/openssl/OpenSSL_AES.py +++ b/tack/crypto/openssl/OpenSSL_AES.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import ctypes from .OpenSSL import openssl as o from .OpenSSL import bytesToC, cToBytes diff --git a/tack/crypto/openssl/OpenSSL_ECGenerator.py b/tack/crypto/openssl/OpenSSL_ECGenerator.py index 370b1d3..25ca8ad 100644 --- a/tack/crypto/openssl/OpenSSL_ECGenerator.py +++ b/tack/crypto/openssl/OpenSSL_ECGenerator.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import math, ctypes from .OpenSSL_ECPublicKey import OpenSSL_ECPublicKey from .OpenSSL_ECPrivateKey import OpenSSL_ECPrivateKey diff --git a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py index 7199664..5e7d64a 100644 --- a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import ctypes from tack.crypto.Digest import Digest from .OpenSSL_ECPublicKey import OpenSSL_ECPublicKey diff --git a/tack/crypto/openssl/OpenSSL_ECPublicKey.py b/tack/crypto/openssl/OpenSSL_ECPublicKey.py index fba44a5..5c409fd 100644 --- a/tack/crypto/openssl/OpenSSL_ECPublicKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPublicKey.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import ctypes, sys from tack.compat import b2a_base32 from tack.crypto.Digest import Digest diff --git a/tack/crypto/python/Python_AES.py b/tack/crypto/python/Python_AES.py index 2118bd3..802282e 100644 --- a/tack/crypto/python/Python_AES.py +++ b/tack/crypto/python/Python_AES.py @@ -1,3 +1,7 @@ +# Authors: +# Trevor Perrin +# +# See the LICENSE file for legal information regarding use of this file. from .rijndael import rijndael diff --git a/tack/crypto/python/Python_ECGenerator.py b/tack/crypto/python/Python_ECGenerator.py index 1f3e565..d4932c6 100644 --- a/tack/crypto/python/Python_ECGenerator.py +++ b/tack/crypto/python/Python_ECGenerator.py @@ -1,3 +1,8 @@ +# Authors: +# Trevor Perrin +# +# See the LICENSE file for legal information regarding use of this file. + import os from .cryptomath import numberToBytes, bytesToNumber from .ecdsa import generator_256 diff --git a/tack/crypto/python/Python_ECPrivateKey.py b/tack/crypto/python/Python_ECPrivateKey.py index 9e48b93..95620b2 100644 --- a/tack/crypto/python/Python_ECPrivateKey.py +++ b/tack/crypto/python/Python_ECPrivateKey.py @@ -1,3 +1,8 @@ +# Authors: +# Trevor Perrin +# +# See the LICENSE file for legal information regarding use of this file. + import os from tack.crypto.Digest import Digest from .cryptomath import numberToBytes, bytesToNumber diff --git a/tack/crypto/python/Python_ECPublicKey.py b/tack/crypto/python/Python_ECPublicKey.py index 5ac96ea..bfcba72 100644 --- a/tack/crypto/python/Python_ECPublicKey.py +++ b/tack/crypto/python/Python_ECPublicKey.py @@ -1,3 +1,8 @@ +# Authors: +# Trevor Perrin +# +# See the LICENSE file for legal information regarding use of this file. + import ctypes from tack.compat import b2a_base32 from tack.compat import bytesToStr diff --git a/tack/crypto/python/cryptomath.py b/tack/crypto/python/cryptomath.py index 4d43e6c..fc71052 100644 --- a/tack/crypto/python/cryptomath.py +++ b/tack/crypto/python/cryptomath.py @@ -1,8 +1,8 @@ -# Author: Trevor Perrin +# Authors: +# Trevor Perrin +# # See the LICENSE file for legal information regarding use of this file. -################ CRYPTOMATH ### - import math, hashlib, hmac def bytesToNumber(bytes): diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index 7be6727..569c447 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + from tack.crypto.ECPublicKey import ECPublicKey from tack.tls.TlsStructure import TlsStructure from tack.tls.TlsStructureWriter import TlsStructureWriter diff --git a/tack/structures/TackActivation.py b/tack/structures/TackActivation.py index ba41476..f375424 100644 --- a/tack/structures/TackActivation.py +++ b/tack/structures/TackActivation.py @@ -1,3 +1,8 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. class TackActivation: DISABLED = 0 diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index 16891c2..9fbff3a 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + from tack.crypto.ECPublicKey import ECPublicKey from tack.tls.TlsStructure import TlsStructure from tack.tls.TlsStructureWriter import TlsStructureWriter diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index e54be10..0f28ea9 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + from tack.structures.Tack import Tack from tack.structures.TackActivation import TackActivation from tack.structures.TackBreakSig import TackBreakSig diff --git a/tack/structures/TackKeyFile.py b/tack/structures/TackKeyFile.py index 43f3724..c685e5a 100644 --- a/tack/structures/TackKeyFile.py +++ b/tack/structures/TackKeyFile.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + """ File format: diff --git a/tack/tls/TlsCertificate.py b/tack/tls/TlsCertificate.py index fe73cdd..9ec5bda 100644 --- a/tack/tls/TlsCertificate.py +++ b/tack/tls/TlsCertificate.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + from tack.compat import a2b_hex, bytesToStr from tack.crypto.ASN1 import ASN1Parser, asn1Length from tack.crypto.Digest import Digest diff --git a/tack/tls/TlsStructure.py b/tack/tls/TlsStructure.py index e492319..4c939be 100644 --- a/tack/tls/TlsStructure.py +++ b/tack/tls/TlsStructure.py @@ -1,3 +1,8 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. class TlsStructure: def __init__(self, bytes): diff --git a/tack/tls/TlsStructureWriter.py b/tack/tls/TlsStructureWriter.py index 81a1e8b..0ae286a 100644 --- a/tack/tls/TlsStructureWriter.py +++ b/tack/tls/TlsStructureWriter.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + class TlsStructureWriter: def __init__(self, totalLength): self.index = 0 diff --git a/tack/util/PEMDecoder.py b/tack/util/PEMDecoder.py index 0ade9af..e7318ac 100644 --- a/tack/util/PEMDecoder.py +++ b/tack/util/PEMDecoder.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + from tack.compat import a2b_base64 class PEMDecoder: diff --git a/tack/util/PEMEncoder.py b/tack/util/PEMEncoder.py index c5e8536..5d2c560 100644 --- a/tack/util/PEMEncoder.py +++ b/tack/util/PEMEncoder.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + from tack.compat import b2a_base64 import sys diff --git a/tack/util/Time.py b/tack/util/Time.py index e88a1aa..04ad0d6 100644 --- a/tack/util/Time.py +++ b/tack/util/Time.py @@ -1,4 +1,7 @@ -# Author: Trevor Perrin +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# # See the LICENSE file for legal information regarding use of this file. from tack.compat import * diff --git a/tack/util/Util.py b/tack/util/Util.py index 3a2aaa7..da9ee2e 100644 --- a/tack/util/Util.py +++ b/tack/util/Util.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + from tack.compat import b2a_hex class Util: diff --git a/tests/Certificate_Test.py b/tests/Certificate_Test.py index 58b4ded..5413c5d 100644 --- a/tests/Certificate_Test.py +++ b/tests/Certificate_Test.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import unittest from tack.tls.TlsCertificate import TlsCertificate from tack.compat import a2b_hex diff --git a/tests/Compat_Test.py b/tests/Compat_Test.py index 35acfe1..311ae13 100644 --- a/tests/Compat_Test.py +++ b/tests/Compat_Test.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import unittest from tack.compat import a2b_hex from tack.compat import a2b_base64 diff --git a/tests/Crypto_Test.py b/tests/Crypto_Test.py index 59c2af4..5c97cd8 100644 --- a/tests/Crypto_Test.py +++ b/tests/Crypto_Test.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import unittest from tack.compat import a2b_hex from tack.crypto.AES import AES diff --git a/tests/Structures_Test.py b/tests/Structures_Test.py index 7e4cbc8..c0da159 100644 --- a/tests/Structures_Test.py +++ b/tests/Structures_Test.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import unittest from tack.compat import a2b_hex from tack.crypto.ECGenerator import ECGenerator diff --git a/tests/Time_Test.py b/tests/Time_Test.py index 37a03c3..7ade016 100644 --- a/tests/Time_Test.py +++ b/tests/Time_Test.py @@ -1,3 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + import unittest from tack.util.Time import Time From 93191b2022c407bdf40f2b90dd24f57e08a6cafb Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 18 May 2012 00:38:36 +0300 Subject: [PATCH 57/94] Cosmetic touchups. --- tack/commands/CertificateCommand.py | 2 +- tack/commands/GenerateKeyCommand.py | 4 +- tack/commands/SignCommand.py | 15 +- tack/crypto/python/ecdsa_wrappers.py | 233 --------------------------- 4 files changed, 11 insertions(+), 243 deletions(-) delete mode 100644 tack/crypto/python/ecdsa_wrappers.py diff --git a/tack/commands/CertificateCommand.py b/tack/commands/CertificateCommand.py index 68522ea..a8fbca5 100644 --- a/tack/commands/CertificateCommand.py +++ b/tack/commands/CertificateCommand.py @@ -66,7 +66,7 @@ def _getBreakSignatures(self): def _getInputFile(self): fileName = self._getOptionValue("-i") if fileName is None: - self.printError("-i missing") + self.printError("-i missing (Certificate or TACK)") try: text, binary = self._readFileTextAndBinary(fileName) diff --git a/tack/commands/GenerateKeyCommand.py b/tack/commands/GenerateKeyCommand.py index 1054c7c..6dac86a 100644 --- a/tack/commands/GenerateKeyCommand.py +++ b/tack/commands/GenerateKeyCommand.py @@ -18,7 +18,7 @@ def __init__(self, argv): self.outputFile, self.outputFileName = self.getOutputFile() def execute(self): - password = self._getPassword() + password = self._getPasswordWithPrompt() public_key, private_key = ECGenerator.generateECKeyPair() keyFile = TackKeyFile.create(public_key, private_key, password) self.outputFile.write(self.addPemComments(keyFile.serializeAsPem())) @@ -27,7 +27,7 @@ def execute(self): self.writeCryptoVersion() sys.stderr.write(str(keyFile)) - def _getPassword(self): + def _getPasswordWithPrompt(self): if not self.password: password, password2 = "this", "that" while password != password2: diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index b977b7f..3ad9073 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -36,8 +36,9 @@ def __init__(self, argv): def execute(self): if not self.numArg: #We are only signing a single TACK (this is the typical mode) - tack = Tack.create(self.keyfile.getPublicKey(), self.keyfile.getPrivateKey(), self.min_generation, - self.generation, self.expiration, self.certificate.key_sha256) + tack = Tack.create(self.keyfile.getPublicKey(), self.keyfile.getPrivateKey(), + self.min_generation, self.generation, self.expiration, + self.certificate.key_sha256) self.outputFile.write(self.addPemComments(tack.serializeAsPem())) @@ -52,8 +53,9 @@ def execute(self): self.printError("-o required with -n") for x in range(numTacks): - tack = Tack.create(self.keyfile.getPublicKey(), self.keyfile.getPrivateKey(), self.min_generation, - self.generation, self.expiration, self.certificate.key_sha256) + tack = Tack.create(self.keyfile.getPublicKey(), self.keyfile.getPrivateKey(), + self.min_generation, self.generation, + self.expiration, self.certificate.key_sha256) try: outputFileName = self.outputFileName + "_%04d.pem" % x @@ -82,8 +84,7 @@ def _getCertificate(self): else: certificateBytes = bytearray(open(certificateFile, "rb").read()) - inCert = TlsCertificate.createFromBytes(certificateBytes) - return inCert + return TlsCertificate.createFromBytes(certificateBytes) except SyntaxError: self.printError("Certificate malformed: %s" % certificateFile) except IOError: @@ -114,7 +115,7 @@ def _getNumArg(self): raise ValueError() return numTacks, interval except (ValueError, SyntaxError): - self.printError("Bad -n NUMTACKS: %s:" % numArgRaw) + self.printError("Bad -n NUMTACKS (1 - 10000): %s:" % numArgRaw) def _getGeneration(self): generation = self._getOptionValue("-g") diff --git a/tack/crypto/python/ecdsa_wrappers.py b/tack/crypto/python/ecdsa_wrappers.py deleted file mode 100644 index 932f1a2..0000000 --- a/tack/crypto/python/ecdsa_wrappers.py +++ /dev/null @@ -1,233 +0,0 @@ -# Author: Trevor Perrin -# See the LICENSE file for legal information regarding use of this file. - -from .ecdsa import * -from .cryptomath import * -from .pem import * -from .asn1 import * -from .misc import * -from .m2crypto import * - -################ ECDSA_WRAPPERS ### -"""The following three "wrapper" functions are used for working with ECDSA: - ec256Generate - ecdsa256Sign - ecdsa256Verify - -These wrapper functions operate on bytearrays: - privateKey is a bytearray of length 32 - publicKey is a bytearray of length 64 - signature is a bytearray of length 64 - dataToSign/Verify is an arbitrary-length bytearray - -There are M2Crypto/OpenSSL versions of these functions, as well as -pure Python versions based on Peter Pearson's code (see the -NUMBERTHEORY, ELLIPTICCURVE, and ECDSA sections for pure Python). - -The M2Crypto/OpenSSL versions are loaded and used if present, otherwise -the pure Python versions are used. - -Because M2Crypto operates on ASN.1-encoded signatures, and traditional OpenSSL -PEM-encoded public and private keys, there is a fair bit of data munging to -convert to/from M2Crypto formats. -""" - -import os - -def ec256Generate(): - if m2cryptoLoaded: - return m2crypto_ec256Generate() - else: - return python_ec256Generate() - -def ecdsa256Sign(privateKey, publicKey, dataToSign): - if m2cryptoLoaded: - return m2crypto_ecdsa256Sign(privateKey, publicKey, dataToSign) - else: - return python_ecdsa256Sign(privateKey, publicKey, dataToSign) - -def ecdsa256Verify(publicKey, dataToVerify, signature): - if m2cryptoLoaded: - return m2crypto_ecdsa256Verify(publicKey, dataToVerify, signature) - else: - return python_ecdsa256Verify(publicKey, dataToVerify, signature) - -if m2cryptoLoaded: - - # Marshal/unmarshal PEM-wrapped ECPrivateKey and ASN.1 Signatures - - def _parseECPrivateKey(pemPrivKeyBytes): - """Parse a bytearray containing a PEM-encoded ECPrivatey. - - Return a pair of (32-byte bytearray, 64-byte bytearray) - containing the (privateKey, publicKey) - """ - b = dePem(pemPrivKeyBytes, "EC PRIVATE KEY") - p = ASN1Parser(b) - # The private key is stored as an ASN.1 integer which may - # need to have zero padding removed (if 33 bytes) or added - # (if < 32 bytes): - privateKey = p.getChild(1).value - privateKey = fromAsn1IntBytes(privateKey, 32) - # There is a 00 04 byte prior to the 64-byte public key - # I'm not sure why M2Crypto has the 00 byte there?, - # some ASN1 thing - the 04 byte signals "uncompressed" - # per SECG. Anyways, strip both those bytes off ([2:]) - publicKey = p.getChild(3).getTagged().value[2:] - assert(len(privateKey) == 32) - assert(len(publicKey) == 64) - return (privateKey, publicKey) - - def _writeECPrivateKey(privateKey, publicKey): - assert(len(privateKey) == 32) - assert(len(publicKey) == 64) - bytes1 = a2b_hex("02010104") - bytes2 = a2b_hex("a00a06082a8648ce3d030107a14403420004") - privateKey = toAsn1IntBytes(privateKey) - b = bytes1 + asn1Length(len(privateKey)) + privateKey + \ - bytes2 + publicKey - b = bytearray([0x30]) + asn1Length(len(b)) + b - pemPrivKeyBytes = pem(b, "EC PRIVATE KEY") - return pemPrivKeyBytes - - def _writeECPublicKey(publicKey): - assert(len(publicKey) == 64) - bytes1 = a2b_hex(\ - "3059301306072a8648ce3d020106082a8648ce3d03010703420004") - asn1KeyBytes = bytes1 + publicKey - pemPubKeyBytes = pem(asn1KeyBytes, "PUBLIC KEY") - return pemPubKeyBytes - - def _parseECSignature(asn1SigBytes): - p = ASN1Parser(bytearray(asn1SigBytes)) - r = bytesToNumber(p.getChild(0).value) - s = bytesToNumber(p.getChild(1).value) - return numberToBytes(r, 32) + numberToBytes(s, 32) - - def _writeECSignature(ecSigBytes): - assert(len(ecSigBytes) == 64) - asn1R = toAsn1IntBytes(ecSigBytes[:32]) - asn1S = toAsn1IntBytes(ecSigBytes[32:]) - # Add ASN1 Type=2(int), and Length fields - asn1R = bytearray([2]) + asn1Length(len(asn1R)) + asn1R - asn1S = bytearray([2]) + asn1Length(len(asn1S)) + asn1S - # Add ASN1 Type=0x30(Sequence) and Length fields - asn1ECSigBytes = bytearray([0x30]) + \ - asn1Length(len(asn1R+asn1S)) + asn1R + asn1S - return asn1ECSigBytes - - def m2crypto_ec256Generate(): - # Generate M2Crypto.EC.EC object - m2EC = EC.gen_params(EC.NID_X9_62_prime256v1) - m2EC.gen_key() - # Get the ASN.1 ECPrivateKey for the object - m2Bio = BIO.MemoryBuffer() - m2EC.save_key_bio(m2Bio, cipher=None) - pemPrivKeyBytes = m2Bio.getvalue() - # Parse the ASN.1 ECPrivateKey into byte arrays - # for the 32-byte priv key, and 64-byte pub key - (privateKey, publicKey) = _parseECPrivateKey(pemPrivKeyBytes) - return (privateKey, publicKey) - - def m2crypto_ecdsa256Sign(privateKey, publicKey, dataToSign): - # Write the passed-in private key byte array into PEM form - # Then create M2Crypto EC object from ASN.1 form - pemPrivKeyBytes = _writeECPrivateKey(privateKey, publicKey) - m2EC = EC.load_key_bio(BIO.MemoryBuffer(pemPrivKeyBytes)) - - # Produce ASN.1 signature - hash = SHA256(dataToSign) - asn1SigBytes = m2EC.sign_dsa_asn1(hash) - - # Convert stupid ASN.1 signature into 64-byte signature - # Double-check before returning - sigBytes = _parseECSignature(asn1SigBytes) - assert(ecdsa256Verify(publicKey, dataToSign, sigBytes)) - return sigBytes - - def m2crypto_ecdsa256Verify(publicKey, dataToVerify, signature): - # Write the passed-in public key byte array into PEM form - # Then create M2Crypto EC_pub - pemPubKeyBytes = _writeECPublicKey(publicKey) - m2ECpub = EC.load_pub_key_bio(BIO.MemoryBuffer(pemPubKeyBytes)) - - # Convert 64-byte signature into a stupid ASN.1 signature - asn1SigBytes = _writeECSignature(signature) - hash = SHA256(dataToVerify) - return m2ECpub.verify_dsa_asn1(hash, asn1SigBytes) - - -# Always load the python functions, even if m2crypto loaded: - -def python_ec256Generate(extraRandBytes=None): - # ECDSA key generation per FIPS 186-3 B.4.1 - # (except we use 32 extra random bytes instead of 8 before reduction) - # Random bytes taken from /dev/urandom as well as any extraRandBytes - # REVIEW THIS CAREFULLY! CHANGE AT YOUR PERIL! - randBytes0 = bytearray(os.urandom(64)) - if extraRandBytes: - randBytes0 += bytearray(extraRandBytes) - randBytes = HMAC_SHA256(randBytes0, bytearray([1])) - randBytes+= HMAC_SHA256(randBytes0, bytearray([2])) - c = bytesToNumber(randBytes) - n = generator_256.order() - d = (c % (n-1))+1 - privateKey = numberToBytes(d, 32) - publicKeyPoint = generator_256 * d - publicKey = numberToBytes(publicKeyPoint.x(), 32) + \ - numberToBytes(publicKeyPoint.y(), 32) - return (privateKey, publicKey) - -def python_ecdsa256Sign(privateKey, publicKey, dataToSign): - privateKeyNum = bytesToNumber(privateKey) - hash = SHA256(dataToSign) - g = generator_256 - n = g.order() - x = bytesToNumber(publicKey[:32]) - y = bytesToNumber(publicKey[32:]) - pubkey = Public_key(g, Point(g.curve(), x,y)) - privkey = Private_key(pubkey, privateKeyNum) - - # Generating random nonce k per FIPS 186-3 B.5.1: - # (except we use 32 extra bytes instead of 8 before reduction) - # Random bytes taken from /dev/urandom as well as HMAC(privkey,hash) - # REVIEW THIS CAREFULLY!!! CHANGE AT YOUR PERIL!!! - randBytes0 = bytearray(os.urandom(64)) - randBytes0+= HMAC_SHA256(privateKey, hash) - randBytes = HMAC_SHA256(randBytes0, bytearray([1])) - randBytes+= HMAC_SHA256(randBytes0, bytearray([2])) - c = bytesToNumber(randBytes) - k = (c % (n-1))+1 - hashNum = bytesToNumber(hash) - sig = privkey.sign(hashNum, k) - signature = numberToBytes(sig.r, 32) + numberToBytes(sig.s, 32) - # Double-check value before returning - assert(ecdsa256Verify(publicKey, dataToSign, signature)) - return signature - -def python_ecdsa256Verify(publicKey, dataToVerify, signature): - hashNum = bytesToNumber(SHA256(dataToVerify)) - g = generator_256 - x = bytesToNumber(publicKey[:32]) - y = bytesToNumber(publicKey[32:]) - pubkey = Public_key(g, Point(g.curve(), x,y)) - sig = Signature(bytesToNumber(signature[:32]), - bytesToNumber(signature[32:])) - return pubkey.verifies(hashNum, sig) - -def testECDSAWrappers(): - print("Testing ECDSA WRAPPERS") - privateKey, publicKey = python_ec256Generate() - data = bytearray([0,1,2,3]) - badData = bytearray([0,1,2,4]) - signature = python_ecdsa256Sign(privateKey, publicKey, data) - assert(python_ecdsa256Verify(publicKey, data, signature)) - assert(not python_ecdsa256Verify(publicKey, badData, signature)) - if m2cryptoLoaded: - # See if M2Crypto can verify Python sig - assert(m2crypto_ecdsa256Verify(publicKey, data, signature)) - privateKey, publicKey = m2crypto_ec256Generate() - signature = m2crypto_ecdsa256Sign(privateKey, publicKey, data) - assert(m2crypto_ecdsa256Verify(publicKey, data, signature)) - assert(not m2crypto_ecdsa256Verify(publicKey, badData, signature)) - return 1 From 2fcbddec59f9adbc29fa1361da3627240430a1e8 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sun, 20 May 2012 10:15:22 -0700 Subject: [PATCH 58/94] Update version to 0.9.7, this is getting close to releasable. --- Makefile | 3 +-- README | 2 +- setup.py | 4 ++-- tack/version.py | 2 +- 4 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index fbb5666..662491a 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ clean: # Variables for testing TESTDIR = testoutput -EXEC = python3 ./tack.py +EXEC = ~/Downloads/Python-2.6.7/python.exe ./tack.py CERT1 = ./testdata/serverX509Cert.pem CERT2 = ./testdata/serverX509Cert.der @@ -31,7 +31,6 @@ CERT2 = ./testdata/serverX509Cert.der test: rm -rf $(TESTDIR) mkdir $(TESTDIR) - #$(EXEC) test $(EXEC) genkey -p asdf > $(TESTDIR)/TACK_Key1.pem $(EXEC) genkey -x -p asdf > $(TESTDIR)/TACK_Key2.pem $(EXEC) genkey -p asdf -o $(TESTDIR)/TACK_Key3.pem diff --git a/README b/README index 0afb479..95b2380 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Tackpy version 0.9.6 Feb 23 2012 +Tackpy version 0.9.7 Feb 23 2012 ============================================================================ Licenses/Acknowledgements diff --git a/setup.py b/setup.py index adb452f..b42cd04 100755 --- a/setup.py +++ b/setup.py @@ -14,11 +14,11 @@ shutil.copyfile("tack.py", "tack/tack") setup( name="tackpy", - version="0.9.6", + version="0.9.7", author="Trevor Perrin", author_email="tackpy@trevp.net", url="https://github.com/trevp/TACKpy", - description="TACKpy implements TACK in python", + description="Tackpy implements TACK in python", license="public domain", scripts=["tack/tack"], packages=["tack", "tack/commands", "tack/crypto", "tack/crypto/openssl", diff --git a/tack/version.py b/tack/version.py index 5de8aa0..9b34a8f 100644 --- a/tack/version.py +++ b/tack/version.py @@ -3,5 +3,5 @@ ################ VERSION ### -__version__ = "0.9.6" +__version__ = "0.9.7" From 6689b6a0dd8b8ce0f00432f2df6f41d2292a56e1 Mon Sep 17 00:00:00 2001 From: Trevor Date: Mon, 21 May 2012 15:01:58 -0700 Subject: [PATCH 59/94] README tweaks. --- README | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/README b/README index 95b2380..21b5e35 100644 --- a/README +++ b/README @@ -86,8 +86,8 @@ part of the TLS connection. This break signature must remain published at the hostname until all pins between the hostname and the old TACK key have become inactive (30 days at -most; this is exactly the same as the "waiting period" described in previous -section). +most; this is exactly the same as the "waiting period" described in the +previous section). A break signature from a TACK key causes any client who encounters it to discard all pins involving the TACK key. Thus, once a break signature is @@ -107,34 +107,35 @@ Advanced uses Revoking older generations of a TACK ------------------------------------- -If a server's TLS public key (not its TACK key) has been compromised and you -are switching to a new TLS key, you may revoke the TACK for the old key by "-m +If a server's TLS key (not its TACK key) has been compromised and you are +switching to a new TLS key, you may revoke the TACK for the old key by "-m " in the "sign" command. is a number from 0-255 that is larger than the generation of the TACK you wish to revoke. -Clients who encounter the new TACK will reject older generations from then on. -Prior to publishing a new you should replace all your TACKs -with this generation number (or higher) by signing with "-g ". +Clients who encounter the new TACK will reject older generation TACKs from +then on. Prior to publishing a new you should replace all +your TACKs with this generation number (or higher) by signing with "-g +". -For example: By default TACK signatures have generation=0, so the first time -you use this capability you will want to set "-m1" after pushing out a new set -of TACKs signed with "-g1". If you use it a second time, you will set "-m2", -and so on. +For example: By default TACKs have generation=0, so the first time you use +this capability you will want to set "-m1" after pushing out a new set of +TACKs signed with "-g1". If you use it a second time, you will set "-m2", and +so on. Security Consideration: This only provides protection if clients receive the -new min_generation. For a more robust defense against SSL key compromise, +new min_generation. For a more robust defense against TLS key compromise, consider using short-lived TACKs. Short-lived TACKs ------------------ Every TACK contains a signature covering a TLS public key. The TLS key is -contained in a certificate. By default the TACK signature is set to expire at -the same time as the certificate, and must be replaced by an updated TACK at -that point. +contained in a certificate. By default the TACK is set to expire at the same +time as the certificate, and must be replaced by an updated TACK at that +point. -If you shorten the TACK's expiration time, then a compromised SSL certificate -will become unusable to an attacker once the TACK expires. For example, every -day at midnight you could deploy a new TACK that expires within 48 hours. +If you shorten the TACK's expiration time, then a compromised TLS key will +become unusable to an attacker once the TACK expires. For example, every day +at midnight you could deploy a new TACK that expires within 48 hours. A good way to handle short-lived TACKs is to generate a batch of them and store the TACKs on a secure system that distributes them to servers. This way, From 6b91618b350fdafd5749509fa7e20c6be5756721 Mon Sep 17 00:00:00 2001 From: Trevor Date: Mon, 21 May 2012 15:31:45 -0700 Subject: [PATCH 60/94] Rename duration to delta. --- Makefile | 2 +- tack/commands/SignCommand.py | 2 +- tack/util/Time.py | 22 ++++----------------- tests/Time_Test.py | 37 ++++++++++++++---------------------- 4 files changed, 20 insertions(+), 43 deletions(-) diff --git a/Makefile b/Makefile index 662491a..f8ced52 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ clean: # Variables for testing TESTDIR = testoutput -EXEC = ~/Downloads/Python-2.6.7/python.exe ./tack.py +EXEC = ./tack.py CERT1 = ./testdata/serverX509Cert.pem CERT2 = ./testdata/serverX509Cert.der diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index 3ad9073..af66a43 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -110,7 +110,7 @@ def _getNumArg(self): try: leftArg, rightArg = numArgRaw.split("@") # could raise ValueError numTacks = int(leftArg) # could raise ValueError - interval = Time.parseDurationArg(rightArg) # SyntaxError + interval = Time.parseDeltaArg(rightArg) # SyntaxError if numTacks < 1 or numTacks >= 10000: raise ValueError() return numTacks, interval diff --git a/tack/util/Time.py b/tack/util/Time.py index 04ad0d6..536b048 100644 --- a/tack/util/Time.py +++ b/tack/util/Time.py @@ -21,25 +21,11 @@ def posixTimeToStr(u, includeSeconds=False): s = time.strftime("%Y-%m-%dT%H:%MZ", t) return s - # u in minutes - @staticmethod - def durationToStr(u): - s = "" - if u >= (1440): # 1440 minutes per day - s += "%dd" % (u//1440) - u %= 1440 - if u >= (60): # 60 minutes per hour - s += "%dh" % (u//60) - u %= 60 - if u>0 or not s: - s += "%dm" % u - return s - @staticmethod def parseTimeArg(arg): - # First, see if they specified time as a duration + # First, see if they specified time as a delta try: - mins = Time.parseDurationArg(arg) + mins = Time.parseDeltaArg(arg) return int(math.ceil(time.time() / 60.0)) + mins except SyntaxError: pass @@ -60,7 +46,7 @@ def parseTimeArg(arg): raise SyntaxError( '''Invalid time format, use e.g. "%s" (current time) or some prefix, such as: "%sZ", "%sZ", or "%sZ", - *OR* some duration, such as "5m", "30d", "1d12h5m", etc."''' % + *OR* some delta, such as "5m", "30d", "1d12h5m", etc."''' % (s, s[:13], s[:10], s[:4])) u = int(calendar.timegm(t)//60) if u < 0: @@ -68,7 +54,7 @@ def parseTimeArg(arg): return u @staticmethod - def parseDurationArg(arg): + def parseDeltaArg(arg): arg = arg.upper() foundSomething = False try: diff --git a/tests/Time_Test.py b/tests/Time_Test.py index 7ade016..6edd57a 100644 --- a/tests/Time_Test.py +++ b/tests/Time_Test.py @@ -13,28 +13,17 @@ def test_posix(self): assert(Time.posixTimeToStr(1234567890, True) == "2009-02-13T23:31:30Z") assert(Time.posixTimeToStr(1234567890) == "2009-02-13T23:31Z") - def test_duration(self): - assert(Time.durationToStr(0) == "0m") - assert(Time.durationToStr(59) == "59m") - assert(Time.durationToStr(60) == "1h") - assert(Time.durationToStr(61) == "1h1m") - assert(Time.durationToStr(1439) == "23h59m") - assert(Time.durationToStr(1440) == "1d") - assert(Time.durationToStr(1441) == "1d1m") - assert(Time.durationToStr(1500) == "1d1h") - assert(Time.durationToStr(1501) == "1d1h1m") - assert(Time.durationToStr(1440*37+122) == "37d2h2m") - - assert(0 == Time.parseDurationArg("0m")) - assert(59 == Time.parseDurationArg("59m")) - assert(60 == Time.parseDurationArg("1h")) - assert(61 == Time.parseDurationArg("1h1m")) - assert(1439 == Time.parseDurationArg("23h59m")) - assert(1440 == Time.parseDurationArg("1d")) - assert(1441 == Time.parseDurationArg("1d1m")) - assert(1500 == Time.parseDurationArg("1d1h")) - assert(1501 == Time.parseDurationArg("1d1h1m")) - assert(1440*37+122 == Time.parseDurationArg("37d2h2m")) + def test_delta(self): + assert(0 == Time.parseDeltaArg("0m")) + assert(59 == Time.parseDeltaArg("59m")) + assert(60 == Time.parseDeltaArg("1h")) + assert(61 == Time.parseDeltaArg("1h1m")) + assert(1439 == Time.parseDeltaArg("23h59m")) + assert(1440 == Time.parseDeltaArg("1d")) + assert(1441 == Time.parseDeltaArg("1d1m")) + assert(1500 == Time.parseDeltaArg("1d1h")) + assert(1501 == Time.parseDeltaArg("1d1h1m")) + assert(1440*37+122 == Time.parseDeltaArg("37d2h2m")) def test_string(self): assert(Time.parseTimeArg("2012-07-20T05:40Z")*60 == 1342762800) @@ -42,6 +31,8 @@ def test_string(self): assert(Time.parseTimeArg("2012-07-20Z")*60 == 1342742400) assert(Time.parseTimeArg("2012-07Z")*60 == 1341100800) assert(Time.parseTimeArg("2012Z")*60 == 1325376000) + +# !!! Add tests for ASN1 times if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() From 700a72ac0c023eb8bc21150f4860a887441d34ff Mon Sep 17 00:00:00 2001 From: Trevor Date: Mon, 21 May 2012 16:10:40 -0700 Subject: [PATCH 61/94] Added PythonCrypto_Test (unit tests). --- tack/crypto/python/ecdsa.py | 299 +-------------- tack/crypto/python/ellipticcurve.py | 95 ----- tack/crypto/python/numbertheory.py | 120 ------ tack/crypto/python/rijndael.py | 17 - tests/Crypto_Test.py | 2 - tests/PythonCrypto_Test.py | 544 ++++++++++++++++++++++++++++ 6 files changed, 546 insertions(+), 531 deletions(-) create mode 100644 tests/PythonCrypto_Test.py diff --git a/tack/crypto/python/ecdsa.py b/tack/crypto/python/ecdsa.py index e5d9cbd..b641337 100644 --- a/tack/crypto/python/ecdsa.py +++ b/tack/crypto/python/ecdsa.py @@ -7,7 +7,7 @@ from .numbertheory import * from .ellipticcurve import * -#from .compat import * +from tack.compat import compat26Str ################ ECDSA ### #! /usr/bin/env python @@ -175,6 +175,7 @@ def string_to_int( s ): return result + def digest_integer( m ): """Convert an integer into a string of bytes, compute its SHA-1 hash, and convert the result to an integer.""" @@ -229,299 +230,3 @@ def point_is_valid( generator, x, y ): curve_256 = CurveFp( _p, -3, _b ) generator_256 = Point( curve_256, _Gx, _Gy, _r ) - - -def testECDSA(): - print("Testing ECDSA") - import random - - def test_point_validity( generator, x, y, expected ): - """generator defines the curve; is (x,y) a point on - this curve? "expected" is True if the right answer is Yes.""" - if point_is_valid( generator, x, y ) == expected: - #print("Point validity tested as expected.") - pass - else: - print("*** Point validity test gave wrong result.") - assert() - - def test_signature_validity( Msg, Qx, Qy, R, S, expected ): - """Msg = message, Qx and Qy represent the base point on - elliptic curve c192, R and S are the signature, and - "expected" is True iff the signature is expected to be valid.""" - pubk = Public_key( generator_192, - Point( curve_192, Qx, Qy ) ) - got = pubk.verifies( digest_integer( Msg ), Signature( R, S ) ) - if got == expected: - #print("Signature tested as expected: got %s, expected %s." % \ - # ( got, expected )) - pass - else: - print("*** Signature test failed: got %s, expected %s." % \ - ( got, expected )) - assert() - - #print("NIST Curve P-192:") - p192 = generator_192 - - # From X9.62: - - d = 651056770906015076056810763456358567190100156695615665659 - Q = d * p192 - if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5: - #print("*** p192 * d came out wrong.") - assert() - - k = 6140507067065001063065065565667405560006161556565665656654 - R = k * p192 - if R.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ - or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: - #print("*** k * p192 came out wrong.") - assert() - - u1 = 2563697409189434185194736134579731015366492496392189760599 - u2 = 6266643813348617967186477710235785849136406323338782220568 - temp = u1 * p192 + u2 * Q - if temp.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ - or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: - print("*** u1 * p192 + u2 * Q came out wrong.") - assert() - - e = 968236873715988614170569073515315707566766479517 - pubk = Public_key( generator_192, generator_192 * d ) - privk = Private_key( pubk, d ) - sig = privk.sign( e, k ) - r, s = sig.r, sig.s - if r != 3342403536405981729393488334694600415596881826869351677613 \ - or s != 5735822328888155254683894997897571951568553642892029982342: - print("*** r or s came out wrong.") - assert() - - valid = pubk.verifies( e, sig ) - if valid: pass#print("Signature verified OK.") - else: print("*** Signature failed verification."); assert() - - valid = pubk.verifies( e-1, sig ) - if not valid: pass#print("Forgery was correctly rejected.") - else: print("*** Forgery was erroneously accepted."); assert() - - #print("Testing point validity, as per ECDSAVS.pdf B.2.2:") - - test_point_validity( \ - p192, \ - 0xcd6d0f029a023e9aaca429615b8f577abee685d8257cc83a, \ - 0x00019c410987680e9fb6c0b6ecc01d9a2647c8bae27721bacdfc, \ - False ) - - test_point_validity( - p192, \ - 0x00017f2fce203639e9eaf9fb50b81fc32776b30e3b02af16c73b, \ - 0x95da95c5e72dd48e229d4748d4eee658a9a54111b23b2adb, \ - False ) - - test_point_validity( - p192, \ - 0x4f77f8bc7fccbadd5760f4938746d5f253ee2168c1cf2792, \ - 0x000147156ff824d131629739817edb197717c41aab5c2a70f0f6, \ - False ) - - test_point_validity( - p192, \ - 0xc58d61f88d905293bcd4cd0080bcb1b7f811f2ffa41979f6, \ - 0x8804dc7a7c4c7f8b5d437f5156f3312ca7d6de8a0e11867f, \ - True ) - """ - test_point_validity( - p192, \ - 0xcdf56c1aa3d8afc53c521adf3ffb96734a6a630a4a5b5a70, \ - 0x97c1c44a5fb229007b5ec5d25f7413d170068ffd023caa4e, \ - True ) - - test_point_validity( - p192, \ - 0x89009c0dc361c81e99280c8e91df578df88cdf4b0cdedced, \ - 0x27be44a529b7513e727251f128b34262a0fd4d8ec82377b9, \ - True ) - - test_point_validity( - p192, \ - 0x6a223d00bd22c52833409a163e057e5b5da1def2a197dd15, \ - 0x7b482604199367f1f303f9ef627f922f97023e90eae08abf, \ - True ) - - test_point_validity( - p192, \ - 0x6dccbde75c0948c98dab32ea0bc59fe125cf0fb1a3798eda, \ - 0x0001171a3e0fa60cf3096f4e116b556198de430e1fbd330c8835, \ - False ) - - test_point_validity( - p192, \ - 0xd266b39e1f491fc4acbbbc7d098430931cfa66d55015af12, \ - 0x193782eb909e391a3148b7764e6b234aa94e48d30a16dbb2, \ - False ) - - test_point_validity( - p192, \ - 0x9d6ddbcd439baa0c6b80a654091680e462a7d1d3f1ffeb43, \ - 0x6ad8efc4d133ccf167c44eb4691c80abffb9f82b932b8caa, \ - False ) - - test_point_validity( - p192, \ - 0x146479d944e6bda87e5b35818aa666a4c998a71f4e95edbc, \ - 0xa86d6fe62bc8fbd88139693f842635f687f132255858e7f6, \ - False ) - - test_point_validity( - p192, \ - 0xe594d4a598046f3598243f50fd2c7bd7d380edb055802253, \ - 0x509014c0c4d6b536e3ca750ec09066af39b4c8616a53a923, \ - False ) - """ - - #print("Trying signature-verification tests from ECDSAVS.pdf B.2.4:") - #print("P-192:") - Msg = 0x84ce72aa8699df436059f052ac51b6398d2511e49631bcb7e71f89c499b9ee425dfbc13a5f6d408471b054f2655617cbbaf7937b7c80cd8865cf02c8487d30d2b0fbd8b2c4e102e16d828374bbc47b93852f212d5043c3ea720f086178ff798cc4f63f787b9c2e419efa033e7644ea7936f54462dc21a6c4580725f7f0e7d158 - Qx = 0xd9dbfb332aa8e5ff091e8ce535857c37c73f6250ffb2e7ac - Qy = 0x282102e364feded3ad15ddf968f88d8321aa268dd483ebc4 - R = 0x64dca58a20787c488d11d6dd96313f1b766f2d8efe122916 - S = 0x1ecba28141e84ab4ecad92f56720e2cc83eb3d22dec72479 - test_signature_validity( Msg, Qx, Qy, R, S, True ) - - Msg = 0x94bb5bacd5f8ea765810024db87f4224ad71362a3c28284b2b9f39fab86db12e8beb94aae899768229be8fdb6c4f12f28912bb604703a79ccff769c1607f5a91450f30ba0460d359d9126cbd6296be6d9c4bb96c0ee74cbb44197c207f6db326ab6f5a659113a9034e54be7b041ced9dcf6458d7fb9cbfb2744d999f7dfd63f4 - Qx = 0x3e53ef8d3112af3285c0e74842090712cd324832d4277ae7 - Qy = 0xcc75f8952d30aec2cbb719fc6aa9934590b5d0ff5a83adb7 - R = 0x8285261607283ba18f335026130bab31840dcfd9c3e555af - S = 0x356d89e1b04541afc9704a45e9c535ce4a50929e33d7e06c - test_signature_validity( Msg, Qx, Qy, R, S, True ) - - Msg = 0xf6227a8eeb34afed1621dcc89a91d72ea212cb2f476839d9b4243c66877911b37b4ad6f4448792a7bbba76c63bdd63414b6facab7dc71c3396a73bd7ee14cdd41a659c61c99b779cecf07bc51ab391aa3252386242b9853ea7da67fd768d303f1b9b513d401565b6f1eb722dfdb96b519fe4f9bd5de67ae131e64b40e78c42dd - Qx = 0x16335dbe95f8e8254a4e04575d736befb258b8657f773cb7 - Qy = 0x421b13379c59bc9dce38a1099ca79bbd06d647c7f6242336 - R = 0x4141bd5d64ea36c5b0bd21ef28c02da216ed9d04522b1e91 - S = 0x159a6aa852bcc579e821b7bb0994c0861fb08280c38daa09 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x16b5f93afd0d02246f662761ed8e0dd9504681ed02a253006eb36736b563097ba39f81c8e1bce7a16c1339e345efabbc6baa3efb0612948ae51103382a8ee8bc448e3ef71e9f6f7a9676694831d7f5dd0db5446f179bcb737d4a526367a447bfe2c857521c7f40b6d7d7e01a180d92431fb0bbd29c04a0c420a57b3ed26ccd8a - Qx = 0xfd14cdf1607f5efb7b1793037b15bdf4baa6f7c16341ab0b - Qy = 0x83fa0795cc6c4795b9016dac928fd6bac32f3229a96312c4 - R = 0x8dfdb832951e0167c5d762a473c0416c5c15bc1195667dc1 - S = 0x1720288a2dc13fa1ec78f763f8fe2ff7354a7e6fdde44520 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - """ - Msg = 0x08a2024b61b79d260e3bb43ef15659aec89e5b560199bc82cf7c65c77d39192e03b9a895d766655105edd9188242b91fbde4167f7862d4ddd61e5d4ab55196683d4f13ceb90d87aea6e07eb50a874e33086c4a7cb0273a8e1c4408f4b846bceae1ebaac1b2b2ea851a9b09de322efe34cebe601653efd6ddc876ce8c2f2072fb - Qx = 0x674f941dc1a1f8b763c9334d726172d527b90ca324db8828 - Qy = 0x65adfa32e8b236cb33a3e84cf59bfb9417ae7e8ede57a7ff - R = 0x9508b9fdd7daf0d8126f9e2bc5a35e4c6d800b5b804d7796 - S = 0x36f2bf6b21b987c77b53bb801b3435a577e3d493744bfab0 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x1843aba74b0789d4ac6b0b8923848023a644a7b70afa23b1191829bbe4397ce15b629bf21a8838298653ed0c19222b95fa4f7390d1b4c844d96e645537e0aae98afb5c0ac3bd0e4c37f8daaff25556c64e98c319c52687c904c4de7240a1cc55cd9756b7edaef184e6e23b385726e9ffcba8001b8f574987c1a3fedaaa83ca6d - Qx = 0x10ecca1aad7220b56a62008b35170bfd5e35885c4014a19f - Qy = 0x04eb61984c6c12ade3bc47f3c629ece7aa0a033b9948d686 - R = 0x82bfa4e82c0dfe9274169b86694e76ce993fd83b5c60f325 - S = 0xa97685676c59a65dbde002fe9d613431fb183e8006d05633 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x5a478f4084ddd1a7fea038aa9732a822106385797d02311aeef4d0264f824f698df7a48cfb6b578cf3da416bc0799425bb491be5b5ecc37995b85b03420a98f2c4dc5c31a69a379e9e322fbe706bbcaf0f77175e05cbb4fa162e0da82010a278461e3e974d137bc746d1880d6eb02aa95216014b37480d84b87f717bb13f76e1 - Qx = 0x6636653cb5b894ca65c448277b29da3ad101c4c2300f7c04 - Qy = 0xfdf1cbb3fc3fd6a4f890b59e554544175fa77dbdbeb656c1 - R = 0xeac2ddecddfb79931a9c3d49c08de0645c783a24cb365e1c - S = 0x3549fee3cfa7e5f93bc47d92d8ba100e881a2a93c22f8d50 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0xc598774259a058fa65212ac57eaa4f52240e629ef4c310722088292d1d4af6c39b49ce06ba77e4247b20637174d0bd67c9723feb57b5ead232b47ea452d5d7a089f17c00b8b6767e434a5e16c231ba0efa718a340bf41d67ea2d295812ff1b9277daacb8bc27b50ea5e6443bcf95ef4e9f5468fe78485236313d53d1c68f6ba2 - Qx = 0xa82bd718d01d354001148cd5f69b9ebf38ff6f21898f8aaa - Qy = 0xe67ceede07fc2ebfafd62462a51e4b6c6b3d5b537b7caf3e - R = 0x4d292486c620c3de20856e57d3bb72fcde4a73ad26376955 - S = 0xa85289591a6081d5728825520e62ff1c64f94235c04c7f95 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0xca98ed9db081a07b7557f24ced6c7b9891269a95d2026747add9e9eb80638a961cf9c71a1b9f2c29744180bd4c3d3db60f2243c5c0b7cc8a8d40a3f9a7fc910250f2187136ee6413ffc67f1a25e1c4c204fa9635312252ac0e0481d89b6d53808f0c496ba87631803f6c572c1f61fa049737fdacce4adff757afed4f05beb658 - Qx = 0x7d3b016b57758b160c4fca73d48df07ae3b6b30225126c2f - Qy = 0x4af3790d9775742bde46f8da876711be1b65244b2b39e7ec - R = 0x95f778f5f656511a5ab49a5d69ddd0929563c29cbc3a9e62 - S = 0x75c87fc358c251b4c83d2dd979faad496b539f9f2ee7a289 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x31dd9a54c8338bea06b87eca813d555ad1850fac9742ef0bbe40dad400e10288acc9c11ea7dac79eb16378ebea9490e09536099f1b993e2653cd50240014c90a9c987f64545abc6a536b9bd2435eb5e911fdfde2f13be96ea36ad38df4ae9ea387b29cced599af777338af2794820c9cce43b51d2112380a35802ab7e396c97a - Qx = 0x9362f28c4ef96453d8a2f849f21e881cd7566887da8beb4a - Qy = 0xe64d26d8d74c48a024ae85d982ee74cd16046f4ee5333905 - R = 0xf3923476a296c88287e8de914b0b324ad5a963319a4fe73b - S = 0xf0baeed7624ed00d15244d8ba2aede085517dbdec8ac65f5 - test_signature_validity( Msg, Qx, Qy, R, S, True ) - - Msg = 0xb2b94e4432267c92f9fdb9dc6040c95ffa477652761290d3c7de312283f6450d89cc4aabe748554dfb6056b2d8e99c7aeaad9cdddebdee9dbc099839562d9064e68e7bb5f3a6bba0749ca9a538181fc785553a4000785d73cc207922f63e8ce1112768cb1de7b673aed83a1e4a74592f1268d8e2a4e9e63d414b5d442bd0456d - Qx = 0xcc6fc032a846aaac25533eb033522824f94e670fa997ecef - Qy = 0xe25463ef77a029eccda8b294fd63dd694e38d223d30862f1 - R = 0x066b1d07f3a40e679b620eda7f550842a35c18b80c5ebe06 - S = 0xa0b0fb201e8f2df65e2c4508ef303bdc90d934016f16b2dc - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x4366fcadf10d30d086911de30143da6f579527036937007b337f7282460eae5678b15cccda853193ea5fc4bc0a6b9d7a31128f27e1214988592827520b214eed5052f7775b750b0c6b15f145453ba3fee24a085d65287e10509eb5d5f602c440341376b95c24e5c4727d4b859bfe1483d20538acdd92c7997fa9c614f0f839d7 - Qx = 0x955c908fe900a996f7e2089bee2f6376830f76a19135e753 - Qy = 0xba0c42a91d3847de4a592a46dc3fdaf45a7cc709b90de520 - R = 0x1f58ad77fc04c782815a1405b0925e72095d906cbf52a668 - S = 0xf2e93758b3af75edf784f05a6761c9b9a6043c66b845b599 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x543f8af57d750e33aa8565e0cae92bfa7a1ff78833093421c2942cadf9986670a5ff3244c02a8225e790fbf30ea84c74720abf99cfd10d02d34377c3d3b41269bea763384f372bb786b5846f58932defa68023136cd571863b304886e95e52e7877f445b9364b3f06f3c28da12707673fecb4b8071de06b6e0a3c87da160cef3 - Qx = 0x31f7fa05576d78a949b24812d4383107a9a45bb5fccdd835 - Qy = 0x8dc0eb65994a90f02b5e19bd18b32d61150746c09107e76b - R = 0xbe26d59e4e883dde7c286614a767b31e49ad88789d3a78ff - S = 0x8762ca831c1ce42df77893c9b03119428e7a9b819b619068 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0xd2e8454143ce281e609a9d748014dcebb9d0bc53adb02443a6aac2ffe6cb009f387c346ecb051791404f79e902ee333ad65e5c8cb38dc0d1d39a8dc90add5023572720e5b94b190d43dd0d7873397504c0c7aef2727e628eb6a74411f2e400c65670716cb4a815dc91cbbfeb7cfe8c929e93184c938af2c078584da045e8f8d1 - Qx = 0x66aa8edbbdb5cf8e28ceb51b5bda891cae2df84819fe25c0 - Qy = 0x0c6bc2f69030a7ce58d4a00e3b3349844784a13b8936f8da - R = 0xa4661e69b1734f4a71b788410a464b71e7ffe42334484f23 - S = 0x738421cf5e049159d69c57a915143e226cac8355e149afe9 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - - Msg = 0x6660717144040f3e2f95a4e25b08a7079c702a8b29babad5a19a87654bc5c5afa261512a11b998a4fb36b5d8fe8bd942792ff0324b108120de86d63f65855e5461184fc96a0a8ffd2ce6d5dfb0230cbbdd98f8543e361b3205f5da3d500fdc8bac6db377d75ebef3cb8f4d1ff738071ad0938917889250b41dd1d98896ca06fb - Qx = 0xbcfacf45139b6f5f690a4c35a5fffa498794136a2353fc77 - Qy = 0x6f4a6c906316a6afc6d98fe1f0399d056f128fe0270b0f22 - R = 0x9db679a3dafe48f7ccad122933acfe9da0970b71c94c21c1 - S = 0x984c2db99827576c0a41a5da41e07d8cc768bc82f18c9da9 - test_signature_validity( Msg, Qx, Qy, R, S, False ) - """ - #print("Testing the example code:") - - # Building a public/private key pair from the NIST Curve P-192: - - g = generator_256 - n = g.order() - - # (random.SystemRandom is supposed to provide - # crypto-quality random numbers, but as Debian recently - # illustrated, a systems programmer can accidentally - # demolish this security, so in serious applications - # further precautions are appropriate.) - - randrange = random.SystemRandom().randrange - - secret = randrange( 1, n ) - pubkey = Public_key( g, g * secret ) - privkey = Private_key( pubkey, secret ) - - # Signing a hash value: - - hash = randrange( 1, n ) - signature = privkey.sign( hash, randrange( 1, n ) ) - - # Verifying a signature for a hash value: - - if pubkey.verifies( hash, signature ): - #print("Demo verification succeeded.") - pass - else: - print("*** Demo verification failed.") - assert() - - if pubkey.verifies( hash-1, signature ): - print("**** Demo verification failed to reject tampered hash.") - assert() - return 1 - diff --git a/tack/crypto/python/ellipticcurve.py b/tack/crypto/python/ellipticcurve.py index a8810a8..b669ccb 100644 --- a/tack/crypto/python/ellipticcurve.py +++ b/tack/crypto/python/ellipticcurve.py @@ -183,98 +183,3 @@ def order( self ): # This one point is the Point At Infinity for all purposes: INFINITY = Point( None, None, None ) - -def testEllipticCurve(): - - print("Testing ELLIPTIC CURVE") - - def test_add( c, x1, y1, x2, y2, x3, y3 ): - """We expect that on curve c, (x1,y1) + (x2, y2 ) = (x3, y3).""" - p1 = Point( c, x1, y1 ) - p2 = Point( c, x2, y2 ) - p3 = p1 + p2 - #print "%s + %s = %s" % ( p1, p2, p3 ), - if p3.x() != x3 or p3.y() != y3: - print(" Failure: should give (%d,%d)." % ( x3, y3 )) - assert() - - def test_double( c, x1, y1, x3, y3 ): - """We expect that on curve c, 2*(x1,y1) = (x3, y3).""" - p1 = Point( c, x1, y1 ) - p3 = p1.double() - #print "%s doubled = %s" % ( p1, p3 ), - if p3.x() != x3 or p3.y() != y3: - print(" Failure: should give (%d,%d)." % ( x3, y3 )) - assert() - - def test_multiply( c, x1, y1, m, x3, y3 ): - """We expect that on curve c, m*(x1,y1) = (x3,y3).""" - p1 = Point( c, x1, y1 ) - p3 = p1 * m - #print "%s * %d = %s" % ( p1, m, p3 ), - if p3.x() != x3 or p3.y() != y3: - print(" Failure: should give (%d,%d)." % ( x3, y3 )) - assert() - - # A few tests from X9.62 B.3: - - c = CurveFp( 23, 1, 1 ) - test_add( c, 3, 10, 9, 7, 17, 20 ) - test_double( c, 3, 10, 7, 12 ) - test_add( c, 3, 10, 3, 10, 7, 12 ) # (Should just invoke double.) - test_multiply( c, 3, 10, 2, 7, 12 ) - - # From X9.62 I.1 (p. 96): - - g = Point( c, 13, 7, 7 ) - - check = INFINITY - for i in range( 7 + 1 ): - p = ( i % 7 ) * g - #print("%s * %d = %s, expected %s . . ." % ( g, i, p, check )) - if p == check: - #print(" Good.") - pass - else: - print(p.x(), p.y()) - #print(check.x(), check.y()) - #print(" Bad. %s %s %s %s" % (p, check, type(p), type(check))) - assert() - check = check + g - - # NIST Curve P-192: - p = 6277101735386680763835789423207666416083908700390324961279 - r = 6277101735386680763835789423176059013767194773182842284081 - s = 0x3045ae6fc8422f64ed579528d38120eae12196d5 - c = 0x3099d2bbbfcb2538542dcd5fb078b6ef5f3d6fe2c745de65 - b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1 - Gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012 - Gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811 - - c192 = CurveFp( p, -3, b ) - p192 = Point( c192, Gx, Gy, r ) - - # Checking against some sample computations presented - # in X9.62: - - d = 651056770906015076056810763456358567190100156695615665659 - Q = d * p192 - if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5: - print("p192 * d came out wrong.") - assert() - - k = 6140507067065001063065065565667405560006161556565665656654 - R = k * p192 - if R.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ - or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: - print("k * p192 came out wrong.") - assert() - - u1 = 2563697409189434185194736134579731015366492496392189760599 - u2 = 6266643813348617967186477710235785849136406323338782220568 - temp = u1 * p192 + u2 * Q - if temp.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ - or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: - print("u1 * p192 + u2 * Q came out wrong.") - assert() - return 1 diff --git a/tack/crypto/python/numbertheory.py b/tack/crypto/python/numbertheory.py index 0f6f3dd..3b50985 100644 --- a/tack/crypto/python/numbertheory.py +++ b/tack/crypto/python/numbertheory.py @@ -500,123 +500,3 @@ def next_prime( starting_value ): 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, 1223, 1229] - - - -def testNumberTheory(): - print("Testing NUMBER THEORY") - miller_rabin_test_count = 0 - - # Making sure locally defined exceptions work: - # p = modular_exp( 2, -2, 3 ) - # p = square_root_mod_prime( 2, 3 ) - - #print "Testing gcd..." - assert gcd( 3*5*7, 3*5*11, 3*5*13 ) == 3*5 - assert gcd( [ 3*5*7, 3*5*11, 3*5*13 ] ) == 3*5 - assert gcd( 3 ) == 3 - - #print "Testing lcm..." - assert lcm( 3, 5*3, 7*3 ) == 3*5*7 - assert lcm( [ 3, 5*3, 7*3 ] ) == 3*5*7 - assert lcm( 3 ) == 3 - - #print "Testing next_prime..." - bigprimes = ( 999671, - 999683, - 999721, - 999727, - 999749, - 999763, - 999769, - 999773, - 999809, - 999853, - 999863, - 999883, - 999907, - 999917, - 999931, - 999953, - 999959, - 999961, - 999979, - 999983 ) - - for i in range( len( bigprimes ) - 1 ): - assert next_prime( bigprimes[i] ) == bigprimes[ i+1 ] - - error_tally = 0 - - # Test the square_root_mod_prime function: - - for p in smallprimes[:50]: - #print "Testing square_root_mod_prime for modulus p = %d." % p - squares = [] - - for root in range( 0, 1+p//2 ): - sq = ( root * root ) % p - squares.append( sq ) - calculated = square_root_mod_prime( sq, p ) - if ( calculated * calculated ) % p != sq: - error_tally = error_tally + 1 - print("Failed to find %d as sqrt( %d ) mod %d. Said %d." % \ - ( root, sq, p, calculated )) - - for nonsquare in range( 0, p ): - if nonsquare not in squares: - try: - calculated = square_root_mod_prime( nonsquare, p ) - except SquareRootError: - pass - else: - error_tally = error_tally + 1 - print("Failed to report no root for sqrt( %d ) mod %d." % \ - ( nonsquare, p )) - - # Test the jacobi function: - for m in range( 3, 100, 2 ): - #print "Testing jacobi for modulus m = %d." % m - if is_prime( m ): - squares = [] - for root in range( 1, m ): - if jacobi( root * root, m ) != 1: - error_tally = error_tally + 1 - print("jacobi( %d * %d, %d ) != 1" % ( root, root, m )) - squares.append( root * root % m ) - for i in range( 1, m ): - if not i in squares: - if jacobi( i, m ) != -1: - error_tally = error_tally + 1 - print("jacobi( %d, %d ) != -1" % ( i, m )) - else: # m is not prime. - f = factorization( m ) - for a in range( 1, m ): - c = 1 - for i in f: - c = c * jacobi( a, i[0] ) ** i[1] - if c != jacobi( a, m ): - error_tally = error_tally + 1 - print("%d != jacobi( %d, %d )" % ( c, a, m )) - - -# Test the inverse_mod function: - #print "Testing inverse_mod . . ." - import random - n_tests = 0 - for i in range( 100 ): - m = random.randint( 20, 10000 ) - for j in range( 100 ): - a = random.randint( 1, m-1 ) - if gcd( a, m ) == 1: - n_tests = n_tests + 1 - inv = inverse_mod( a, m ) - if inv <= 0 or inv >= m or ( a * inv ) % m != 1: - error_tally = error_tally + 1 - print("%d = inverse_mod( %d, %d ) is wrong." % ( inv, a, m )) - assert(False) - #assert n_tests > 1000 - #print n_tests, " tests of inverse_mod completed." - #print error_tally, "errors detected." - assert(error_tally == 0) - return 1 diff --git a/tack/crypto/python/rijndael.py b/tack/crypto/python/rijndael.py index cca8faa..9814dbd 100644 --- a/tack/crypto/python/rijndael.py +++ b/tack/crypto/python/rijndael.py @@ -388,20 +388,3 @@ def encrypt(key, block): def decrypt(key, block): return rijndael(key, len(block)).decrypt(block) - -def testRijndael(): - print("Testing RIJNDAEL") - def t(kl, bl): - b = bytearray(b'b') * bl - r = rijndael(bytearray(b'a') * kl, bl) - assert r.decrypt(r.encrypt(b)) == b - t(16, 16) - t(16, 24) - t(16, 32) - t(24, 16) - t(24, 24) - t(24, 32) - t(32, 16) - t(32, 24) - t(32, 32) - return 1 diff --git a/tests/Crypto_Test.py b/tests/Crypto_Test.py index 5c97cd8..82150f8 100644 --- a/tests/Crypto_Test.py +++ b/tests/Crypto_Test.py @@ -10,8 +10,6 @@ from tack.crypto.ASN1 import asn1Length, toAsn1IntBytes, fromAsn1IntBytes from tack.crypto.ECGenerator import ECGenerator -import binascii - class CryptoTest(unittest.TestCase): def test_AES(self): diff --git a/tests/PythonCrypto_Test.py b/tests/PythonCrypto_Test.py new file mode 100644 index 0000000..75e90c8 --- /dev/null +++ b/tests/PythonCrypto_Test.py @@ -0,0 +1,544 @@ +# Authors: +# Trevor Perrin +# +# See the LICENSE file for legal information regarding use of this file. + +import unittest + +from tack.compat import a2b_hex +from tack.crypto.python.Python_AES import Python_AES +from tack.crypto.python.Python_ECGenerator import Python_ECGenerator +from tack.crypto.python.numbertheory import * +from tack.crypto.python.ellipticcurve import * +from tack.crypto.python.ecdsa import * + + + +class PythonCryptoTest(unittest.TestCase): + + def test_PythonAES(self): + key = a2b_hex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") + IV = a2b_hex("000102030405060708090A0B0C0D0E0F") + plaintext = a2b_hex("6bc1bee22e409f96e93d7e117393172a") + ciphertext = a2b_hex("f58c4c04d6e5f1ba779eabfb5f7bfbd6") + + assert(Python_AES(key, IV).encrypt(plaintext) == ciphertext) + assert(Python_AES(key, IV).decrypt(ciphertext) == plaintext) + + def test_PythonECDSA(self): + publicKey, privateKey = Python_ECGenerator().generateECKeyPair() + data = bytearray([0,1,2,3]) + badData = bytearray([0,1,2,4]) + + signature = privateKey.sign(data) + assert(publicKey.verify(data, signature)) + assert(not publicKey.verify(badData, signature)) + + def test_NumberTheory(self): + miller_rabin_test_count = 0 + + # Making sure locally defined exceptions work: + # p = modular_exp( 2, -2, 3 ) + # p = square_root_mod_prime( 2, 3 ) + + #print "Testing gcd..." + assert gcd( 3*5*7, 3*5*11, 3*5*13 ) == 3*5 + assert gcd( [ 3*5*7, 3*5*11, 3*5*13 ] ) == 3*5 + assert gcd( 3 ) == 3 + + #print "Testing lcm..." + assert lcm( 3, 5*3, 7*3 ) == 3*5*7 + assert lcm( [ 3, 5*3, 7*3 ] ) == 3*5*7 + assert lcm( 3 ) == 3 + + #print "Testing next_prime..." + bigprimes = ( 999671, + 999683, + 999721, + 999727, + 999749, + 999763, + 999769, + 999773, + 999809, + 999853, + 999863, + 999883, + 999907, + 999917, + 999931, + 999953, + 999959, + 999961, + 999979, + 999983 ) + + for i in range( len( bigprimes ) - 1 ): + assert next_prime( bigprimes[i] ) == bigprimes[ i+1 ] + + error_tally = 0 + + # Test the square_root_mod_prime function: + + for p in smallprimes[:50]: + #print "Testing square_root_mod_prime for modulus p = %d." % p + squares = [] + + for root in range( 0, 1+p//2 ): + sq = ( root * root ) % p + squares.append( sq ) + calculated = square_root_mod_prime( sq, p ) + if ( calculated * calculated ) % p != sq: + error_tally = error_tally + 1 + print("Failed to find %d as sqrt( %d ) mod %d. Said %d." % \ + ( root, sq, p, calculated )) + + for nonsquare in range( 0, p ): + if nonsquare not in squares: + try: + calculated = square_root_mod_prime( nonsquare, p ) + except SquareRootError: + pass + else: + error_tally = error_tally + 1 + print("Failed to report no root for sqrt( %d ) mod %d." % \ + ( nonsquare, p )) + + # Test the jacobi function: + for m in range( 3, 100, 2 ): + #print "Testing jacobi for modulus m = %d." % m + if is_prime( m ): + squares = [] + for root in range( 1, m ): + if jacobi( root * root, m ) != 1: + error_tally = error_tally + 1 + print("jacobi( %d * %d, %d ) != 1" % ( root, root, m )) + squares.append( root * root % m ) + for i in range( 1, m ): + if not i in squares: + if jacobi( i, m ) != -1: + error_tally = error_tally + 1 + print("jacobi( %d, %d ) != -1" % ( i, m )) + else: # m is not prime. + f = factorization( m ) + for a in range( 1, m ): + c = 1 + for i in f: + c = c * jacobi( a, i[0] ) ** i[1] + if c != jacobi( a, m ): + error_tally = error_tally + 1 + print("%d != jacobi( %d, %d )" % ( c, a, m )) + + + # Test the inverse_mod function: + #print "Testing inverse_mod . . ." + import random + n_tests = 0 + for i in range( 100 ): + m = random.randint( 20, 10000 ) + for j in range( 100 ): + a = random.randint( 1, m-1 ) + if gcd( a, m ) == 1: + n_tests = n_tests + 1 + inv = inverse_mod( a, m ) + if inv <= 0 or inv >= m or ( a * inv ) % m != 1: + error_tally = error_tally + 1 + print("%d = inverse_mod( %d, %d ) is wrong." % ( inv, a, m )) + assert(False) + #assert n_tests > 1000 + #print n_tests, " tests of inverse_mod completed." + #print error_tally, "errors detected." + assert(error_tally == 0) + return 1 + + + def test_EllipticCurve(self): + + def test_add( c, x1, y1, x2, y2, x3, y3 ): + """We expect that on curve c, (x1,y1) + (x2, y2 ) = (x3, y3).""" + p1 = Point( c, x1, y1 ) + p2 = Point( c, x2, y2 ) + p3 = p1 + p2 + #print "%s + %s = %s" % ( p1, p2, p3 ), + if p3.x() != x3 or p3.y() != y3: + print(" Failure: should give (%d,%d)." % ( x3, y3 )) + assert() + + def test_double( c, x1, y1, x3, y3 ): + """We expect that on curve c, 2*(x1,y1) = (x3, y3).""" + p1 = Point( c, x1, y1 ) + p3 = p1.double() + #print "%s doubled = %s" % ( p1, p3 ), + if p3.x() != x3 or p3.y() != y3: + print(" Failure: should give (%d,%d)." % ( x3, y3 )) + assert() + + def test_multiply( c, x1, y1, m, x3, y3 ): + """We expect that on curve c, m*(x1,y1) = (x3,y3).""" + p1 = Point( c, x1, y1 ) + p3 = p1 * m + #print "%s * %d = %s" % ( p1, m, p3 ), + if p3.x() != x3 or p3.y() != y3: + print(" Failure: should give (%d,%d)." % ( x3, y3 )) + assert() + + # A few tests from X9.62 B.3: + + c = CurveFp( 23, 1, 1 ) + test_add( c, 3, 10, 9, 7, 17, 20 ) + test_double( c, 3, 10, 7, 12 ) + test_add( c, 3, 10, 3, 10, 7, 12 ) # (Should just invoke double.) + test_multiply( c, 3, 10, 2, 7, 12 ) + + # From X9.62 I.1 (p. 96): + + g = Point( c, 13, 7, 7 ) + + check = INFINITY + for i in range( 7 + 1 ): + p = ( i % 7 ) * g + #print("%s * %d = %s, expected %s . . ." % ( g, i, p, check )) + if p == check: + #print(" Good.") + pass + else: + print(p.x(), p.y()) + #print(check.x(), check.y()) + #print(" Bad. %s %s %s %s" % (p, check, type(p), type(check))) + assert() + check = check + g + + # NIST Curve P-192: + p = 6277101735386680763835789423207666416083908700390324961279 + r = 6277101735386680763835789423176059013767194773182842284081 + s = 0x3045ae6fc8422f64ed579528d38120eae12196d5 + c = 0x3099d2bbbfcb2538542dcd5fb078b6ef5f3d6fe2c745de65 + b = 0x64210519e59c80e70fa7e9ab72243049feb8deecc146b9b1 + Gx = 0x188da80eb03090f67cbf20eb43a18800f4ff0afd82ff1012 + Gy = 0x07192b95ffc8da78631011ed6b24cdd573f977a11e794811 + + c192 = CurveFp( p, -3, b ) + p192 = Point( c192, Gx, Gy, r ) + + # Checking against some sample computations presented + # in X9.62: + + d = 651056770906015076056810763456358567190100156695615665659 + Q = d * p192 + if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5: + print("p192 * d came out wrong.") + assert() + + k = 6140507067065001063065065565667405560006161556565665656654 + R = k * p192 + if R.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ + or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: + print("k * p192 came out wrong.") + assert() + + u1 = 2563697409189434185194736134579731015366492496392189760599 + u2 = 6266643813348617967186477710235785849136406323338782220568 + temp = u1 * p192 + u2 * Q + if temp.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ + or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: + print("u1 * p192 + u2 * Q came out wrong.") + assert() + return 1 + + + def test_ECDSA(self): + + import random + + def test_point_validity( generator, x, y, expected ): + """generator defines the curve; is (x,y) a point on + this curve? "expected" is True if the right answer is Yes.""" + if point_is_valid( generator, x, y ) == expected: + #print("Point validity tested as expected.") + pass + else: + print("*** Point validity test gave wrong result.") + assert() + + def test_signature_validity( Msg, Qx, Qy, R, S, expected ): + """Msg = message, Qx and Qy represent the base point on + elliptic curve c192, R and S are the signature, and + "expected" is True iff the signature is expected to be valid.""" + pubk = Public_key( generator_192, + Point( curve_192, Qx, Qy ) ) + got = pubk.verifies( digest_integer( Msg ), Signature( R, S ) ) + if got == expected: + #print("Signature tested as expected: got %s, expected %s." % \ + # ( got, expected )) + pass + else: + print("*** Signature test failed: got %s, expected %s." % \ + ( got, expected )) + assert() + + #print("NIST Curve P-192:") + p192 = generator_192 + + # From X9.62: + + d = 651056770906015076056810763456358567190100156695615665659 + Q = d * p192 + if Q.x() != 0x62B12D60690CDCF330BABAB6E69763B471F994DD702D16A5: + #print("*** p192 * d came out wrong.") + assert() + + k = 6140507067065001063065065565667405560006161556565665656654 + R = k * p192 + if R.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ + or R.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: + #print("*** k * p192 came out wrong.") + assert() + + u1 = 2563697409189434185194736134579731015366492496392189760599 + u2 = 6266643813348617967186477710235785849136406323338782220568 + temp = u1 * p192 + u2 * Q + if temp.x() != 0x885052380FF147B734C330C43D39B2C4A89F29B0F749FEAD \ + or temp.y() != 0x9CF9FA1CBEFEFB917747A3BB29C072B9289C2547884FD835: + print("*** u1 * p192 + u2 * Q came out wrong.") + assert() + + e = 968236873715988614170569073515315707566766479517 + pubk = Public_key( generator_192, generator_192 * d ) + privk = Private_key( pubk, d ) + sig = privk.sign( e, k ) + r, s = sig.r, sig.s + if r != 3342403536405981729393488334694600415596881826869351677613 \ + or s != 5735822328888155254683894997897571951568553642892029982342: + print("*** r or s came out wrong.") + assert() + + valid = pubk.verifies( e, sig ) + if valid: pass#print("Signature verified OK.") + else: print("*** Signature failed verification."); assert() + + valid = pubk.verifies( e-1, sig ) + if not valid: pass#print("Forgery was correctly rejected.") + else: print("*** Forgery was erroneously accepted."); assert() + + #print("Testing point validity, as per ECDSAVS.pdf B.2.2:") + + test_point_validity( \ + p192, \ + 0xcd6d0f029a023e9aaca429615b8f577abee685d8257cc83a, \ + 0x00019c410987680e9fb6c0b6ecc01d9a2647c8bae27721bacdfc, \ + False ) + + test_point_validity( + p192, \ + 0x00017f2fce203639e9eaf9fb50b81fc32776b30e3b02af16c73b, \ + 0x95da95c5e72dd48e229d4748d4eee658a9a54111b23b2adb, \ + False ) + + test_point_validity( + p192, \ + 0x4f77f8bc7fccbadd5760f4938746d5f253ee2168c1cf2792, \ + 0x000147156ff824d131629739817edb197717c41aab5c2a70f0f6, \ + False ) + + test_point_validity( + p192, \ + 0xc58d61f88d905293bcd4cd0080bcb1b7f811f2ffa41979f6, \ + 0x8804dc7a7c4c7f8b5d437f5156f3312ca7d6de8a0e11867f, \ + True ) + """ + test_point_validity( + p192, \ + 0xcdf56c1aa3d8afc53c521adf3ffb96734a6a630a4a5b5a70, \ + 0x97c1c44a5fb229007b5ec5d25f7413d170068ffd023caa4e, \ + True ) + + test_point_validity( + p192, \ + 0x89009c0dc361c81e99280c8e91df578df88cdf4b0cdedced, \ + 0x27be44a529b7513e727251f128b34262a0fd4d8ec82377b9, \ + True ) + + test_point_validity( + p192, \ + 0x6a223d00bd22c52833409a163e057e5b5da1def2a197dd15, \ + 0x7b482604199367f1f303f9ef627f922f97023e90eae08abf, \ + True ) + + test_point_validity( + p192, \ + 0x6dccbde75c0948c98dab32ea0bc59fe125cf0fb1a3798eda, \ + 0x0001171a3e0fa60cf3096f4e116b556198de430e1fbd330c8835, \ + False ) + + test_point_validity( + p192, \ + 0xd266b39e1f491fc4acbbbc7d098430931cfa66d55015af12, \ + 0x193782eb909e391a3148b7764e6b234aa94e48d30a16dbb2, \ + False ) + + test_point_validity( + p192, \ + 0x9d6ddbcd439baa0c6b80a654091680e462a7d1d3f1ffeb43, \ + 0x6ad8efc4d133ccf167c44eb4691c80abffb9f82b932b8caa, \ + False ) + + test_point_validity( + p192, \ + 0x146479d944e6bda87e5b35818aa666a4c998a71f4e95edbc, \ + 0xa86d6fe62bc8fbd88139693f842635f687f132255858e7f6, \ + False ) + + test_point_validity( + p192, \ + 0xe594d4a598046f3598243f50fd2c7bd7d380edb055802253, \ + 0x509014c0c4d6b536e3ca750ec09066af39b4c8616a53a923, \ + False ) + """ + + #print("Trying signature-verification tests from ECDSAVS.pdf B.2.4:") + #print("P-192:") + Msg = 0x84ce72aa8699df436059f052ac51b6398d2511e49631bcb7e71f89c499b9ee425dfbc13a5f6d408471b054f2655617cbbaf7937b7c80cd8865cf02c8487d30d2b0fbd8b2c4e102e16d828374bbc47b93852f212d5043c3ea720f086178ff798cc4f63f787b9c2e419efa033e7644ea7936f54462dc21a6c4580725f7f0e7d158 + Qx = 0xd9dbfb332aa8e5ff091e8ce535857c37c73f6250ffb2e7ac + Qy = 0x282102e364feded3ad15ddf968f88d8321aa268dd483ebc4 + R = 0x64dca58a20787c488d11d6dd96313f1b766f2d8efe122916 + S = 0x1ecba28141e84ab4ecad92f56720e2cc83eb3d22dec72479 + test_signature_validity( Msg, Qx, Qy, R, S, True ) + + Msg = 0x94bb5bacd5f8ea765810024db87f4224ad71362a3c28284b2b9f39fab86db12e8beb94aae899768229be8fdb6c4f12f28912bb604703a79ccff769c1607f5a91450f30ba0460d359d9126cbd6296be6d9c4bb96c0ee74cbb44197c207f6db326ab6f5a659113a9034e54be7b041ced9dcf6458d7fb9cbfb2744d999f7dfd63f4 + Qx = 0x3e53ef8d3112af3285c0e74842090712cd324832d4277ae7 + Qy = 0xcc75f8952d30aec2cbb719fc6aa9934590b5d0ff5a83adb7 + R = 0x8285261607283ba18f335026130bab31840dcfd9c3e555af + S = 0x356d89e1b04541afc9704a45e9c535ce4a50929e33d7e06c + test_signature_validity( Msg, Qx, Qy, R, S, True ) + + Msg = 0xf6227a8eeb34afed1621dcc89a91d72ea212cb2f476839d9b4243c66877911b37b4ad6f4448792a7bbba76c63bdd63414b6facab7dc71c3396a73bd7ee14cdd41a659c61c99b779cecf07bc51ab391aa3252386242b9853ea7da67fd768d303f1b9b513d401565b6f1eb722dfdb96b519fe4f9bd5de67ae131e64b40e78c42dd + Qx = 0x16335dbe95f8e8254a4e04575d736befb258b8657f773cb7 + Qy = 0x421b13379c59bc9dce38a1099ca79bbd06d647c7f6242336 + R = 0x4141bd5d64ea36c5b0bd21ef28c02da216ed9d04522b1e91 + S = 0x159a6aa852bcc579e821b7bb0994c0861fb08280c38daa09 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x16b5f93afd0d02246f662761ed8e0dd9504681ed02a253006eb36736b563097ba39f81c8e1bce7a16c1339e345efabbc6baa3efb0612948ae51103382a8ee8bc448e3ef71e9f6f7a9676694831d7f5dd0db5446f179bcb737d4a526367a447bfe2c857521c7f40b6d7d7e01a180d92431fb0bbd29c04a0c420a57b3ed26ccd8a + Qx = 0xfd14cdf1607f5efb7b1793037b15bdf4baa6f7c16341ab0b + Qy = 0x83fa0795cc6c4795b9016dac928fd6bac32f3229a96312c4 + R = 0x8dfdb832951e0167c5d762a473c0416c5c15bc1195667dc1 + S = 0x1720288a2dc13fa1ec78f763f8fe2ff7354a7e6fdde44520 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + """ + Msg = 0x08a2024b61b79d260e3bb43ef15659aec89e5b560199bc82cf7c65c77d39192e03b9a895d766655105edd9188242b91fbde4167f7862d4ddd61e5d4ab55196683d4f13ceb90d87aea6e07eb50a874e33086c4a7cb0273a8e1c4408f4b846bceae1ebaac1b2b2ea851a9b09de322efe34cebe601653efd6ddc876ce8c2f2072fb + Qx = 0x674f941dc1a1f8b763c9334d726172d527b90ca324db8828 + Qy = 0x65adfa32e8b236cb33a3e84cf59bfb9417ae7e8ede57a7ff + R = 0x9508b9fdd7daf0d8126f9e2bc5a35e4c6d800b5b804d7796 + S = 0x36f2bf6b21b987c77b53bb801b3435a577e3d493744bfab0 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x1843aba74b0789d4ac6b0b8923848023a644a7b70afa23b1191829bbe4397ce15b629bf21a8838298653ed0c19222b95fa4f7390d1b4c844d96e645537e0aae98afb5c0ac3bd0e4c37f8daaff25556c64e98c319c52687c904c4de7240a1cc55cd9756b7edaef184e6e23b385726e9ffcba8001b8f574987c1a3fedaaa83ca6d + Qx = 0x10ecca1aad7220b56a62008b35170bfd5e35885c4014a19f + Qy = 0x04eb61984c6c12ade3bc47f3c629ece7aa0a033b9948d686 + R = 0x82bfa4e82c0dfe9274169b86694e76ce993fd83b5c60f325 + S = 0xa97685676c59a65dbde002fe9d613431fb183e8006d05633 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x5a478f4084ddd1a7fea038aa9732a822106385797d02311aeef4d0264f824f698df7a48cfb6b578cf3da416bc0799425bb491be5b5ecc37995b85b03420a98f2c4dc5c31a69a379e9e322fbe706bbcaf0f77175e05cbb4fa162e0da82010a278461e3e974d137bc746d1880d6eb02aa95216014b37480d84b87f717bb13f76e1 + Qx = 0x6636653cb5b894ca65c448277b29da3ad101c4c2300f7c04 + Qy = 0xfdf1cbb3fc3fd6a4f890b59e554544175fa77dbdbeb656c1 + R = 0xeac2ddecddfb79931a9c3d49c08de0645c783a24cb365e1c + S = 0x3549fee3cfa7e5f93bc47d92d8ba100e881a2a93c22f8d50 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0xc598774259a058fa65212ac57eaa4f52240e629ef4c310722088292d1d4af6c39b49ce06ba77e4247b20637174d0bd67c9723feb57b5ead232b47ea452d5d7a089f17c00b8b6767e434a5e16c231ba0efa718a340bf41d67ea2d295812ff1b9277daacb8bc27b50ea5e6443bcf95ef4e9f5468fe78485236313d53d1c68f6ba2 + Qx = 0xa82bd718d01d354001148cd5f69b9ebf38ff6f21898f8aaa + Qy = 0xe67ceede07fc2ebfafd62462a51e4b6c6b3d5b537b7caf3e + R = 0x4d292486c620c3de20856e57d3bb72fcde4a73ad26376955 + S = 0xa85289591a6081d5728825520e62ff1c64f94235c04c7f95 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0xca98ed9db081a07b7557f24ced6c7b9891269a95d2026747add9e9eb80638a961cf9c71a1b9f2c29744180bd4c3d3db60f2243c5c0b7cc8a8d40a3f9a7fc910250f2187136ee6413ffc67f1a25e1c4c204fa9635312252ac0e0481d89b6d53808f0c496ba87631803f6c572c1f61fa049737fdacce4adff757afed4f05beb658 + Qx = 0x7d3b016b57758b160c4fca73d48df07ae3b6b30225126c2f + Qy = 0x4af3790d9775742bde46f8da876711be1b65244b2b39e7ec + R = 0x95f778f5f656511a5ab49a5d69ddd0929563c29cbc3a9e62 + S = 0x75c87fc358c251b4c83d2dd979faad496b539f9f2ee7a289 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x31dd9a54c8338bea06b87eca813d555ad1850fac9742ef0bbe40dad400e10288acc9c11ea7dac79eb16378ebea9490e09536099f1b993e2653cd50240014c90a9c987f64545abc6a536b9bd2435eb5e911fdfde2f13be96ea36ad38df4ae9ea387b29cced599af777338af2794820c9cce43b51d2112380a35802ab7e396c97a + Qx = 0x9362f28c4ef96453d8a2f849f21e881cd7566887da8beb4a + Qy = 0xe64d26d8d74c48a024ae85d982ee74cd16046f4ee5333905 + R = 0xf3923476a296c88287e8de914b0b324ad5a963319a4fe73b + S = 0xf0baeed7624ed00d15244d8ba2aede085517dbdec8ac65f5 + test_signature_validity( Msg, Qx, Qy, R, S, True ) + + Msg = 0xb2b94e4432267c92f9fdb9dc6040c95ffa477652761290d3c7de312283f6450d89cc4aabe748554dfb6056b2d8e99c7aeaad9cdddebdee9dbc099839562d9064e68e7bb5f3a6bba0749ca9a538181fc785553a4000785d73cc207922f63e8ce1112768cb1de7b673aed83a1e4a74592f1268d8e2a4e9e63d414b5d442bd0456d + Qx = 0xcc6fc032a846aaac25533eb033522824f94e670fa997ecef + Qy = 0xe25463ef77a029eccda8b294fd63dd694e38d223d30862f1 + R = 0x066b1d07f3a40e679b620eda7f550842a35c18b80c5ebe06 + S = 0xa0b0fb201e8f2df65e2c4508ef303bdc90d934016f16b2dc + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x4366fcadf10d30d086911de30143da6f579527036937007b337f7282460eae5678b15cccda853193ea5fc4bc0a6b9d7a31128f27e1214988592827520b214eed5052f7775b750b0c6b15f145453ba3fee24a085d65287e10509eb5d5f602c440341376b95c24e5c4727d4b859bfe1483d20538acdd92c7997fa9c614f0f839d7 + Qx = 0x955c908fe900a996f7e2089bee2f6376830f76a19135e753 + Qy = 0xba0c42a91d3847de4a592a46dc3fdaf45a7cc709b90de520 + R = 0x1f58ad77fc04c782815a1405b0925e72095d906cbf52a668 + S = 0xf2e93758b3af75edf784f05a6761c9b9a6043c66b845b599 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x543f8af57d750e33aa8565e0cae92bfa7a1ff78833093421c2942cadf9986670a5ff3244c02a8225e790fbf30ea84c74720abf99cfd10d02d34377c3d3b41269bea763384f372bb786b5846f58932defa68023136cd571863b304886e95e52e7877f445b9364b3f06f3c28da12707673fecb4b8071de06b6e0a3c87da160cef3 + Qx = 0x31f7fa05576d78a949b24812d4383107a9a45bb5fccdd835 + Qy = 0x8dc0eb65994a90f02b5e19bd18b32d61150746c09107e76b + R = 0xbe26d59e4e883dde7c286614a767b31e49ad88789d3a78ff + S = 0x8762ca831c1ce42df77893c9b03119428e7a9b819b619068 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0xd2e8454143ce281e609a9d748014dcebb9d0bc53adb02443a6aac2ffe6cb009f387c346ecb051791404f79e902ee333ad65e5c8cb38dc0d1d39a8dc90add5023572720e5b94b190d43dd0d7873397504c0c7aef2727e628eb6a74411f2e400c65670716cb4a815dc91cbbfeb7cfe8c929e93184c938af2c078584da045e8f8d1 + Qx = 0x66aa8edbbdb5cf8e28ceb51b5bda891cae2df84819fe25c0 + Qy = 0x0c6bc2f69030a7ce58d4a00e3b3349844784a13b8936f8da + R = 0xa4661e69b1734f4a71b788410a464b71e7ffe42334484f23 + S = 0x738421cf5e049159d69c57a915143e226cac8355e149afe9 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + + Msg = 0x6660717144040f3e2f95a4e25b08a7079c702a8b29babad5a19a87654bc5c5afa261512a11b998a4fb36b5d8fe8bd942792ff0324b108120de86d63f65855e5461184fc96a0a8ffd2ce6d5dfb0230cbbdd98f8543e361b3205f5da3d500fdc8bac6db377d75ebef3cb8f4d1ff738071ad0938917889250b41dd1d98896ca06fb + Qx = 0xbcfacf45139b6f5f690a4c35a5fffa498794136a2353fc77 + Qy = 0x6f4a6c906316a6afc6d98fe1f0399d056f128fe0270b0f22 + R = 0x9db679a3dafe48f7ccad122933acfe9da0970b71c94c21c1 + S = 0x984c2db99827576c0a41a5da41e07d8cc768bc82f18c9da9 + test_signature_validity( Msg, Qx, Qy, R, S, False ) + """ + #print("Testing the example code:") + + # Building a public/private key pair from the NIST Curve P-192: + + g = generator_256 + n = g.order() + + # (random.SystemRandom is supposed to provide + # crypto-quality random numbers, but as Debian recently + # illustrated, a systems programmer can accidentally + # demolish this security, so in serious applications + # further precautions are appropriate.) + + randrange = random.SystemRandom().randrange + + secret = randrange( 1, n ) + pubkey = Public_key( g, g * secret ) + privkey = Private_key( pubkey, secret ) + + # Signing a hash value: + + hash = randrange( 1, n ) + signature = privkey.sign( hash, randrange( 1, n ) ) + + # Verifying a signature for a hash value: + + if pubkey.verifies( hash, signature ): + #print("Demo verification succeeded.") + pass + else: + print("*** Demo verification failed.") + assert() + + if pubkey.verifies( hash-1, signature ): + print("**** Demo verification failed to reject tampered hash.") + assert() + return 1 + +if __name__ == '__main__': + unittest.main() \ No newline at end of file From a1250816f49a097734fc056e2fa372b999e64555 Mon Sep 17 00:00:00 2001 From: Trevor Date: Mon, 21 May 2012 16:26:29 -0700 Subject: [PATCH 62/94] Rename "tackcert" command to "cert". --- tack.py | 5 +++-- tack/commands/HelpCommand.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/tack.py b/tack.py index 97c0390..cb8b114 100755 --- a/tack.py +++ b/tack.py @@ -26,11 +26,12 @@ SignCommand(sys.argv[2:]).execute() elif sys.argv[1] == "break"[:len(sys.argv[1])]: BreakCommand(sys.argv[2:]).execute() - elif sys.argv[1] == "tackcert"[:len(sys.argv[1])]: - CertificateCommand(sys.argv[2:]).execute() elif sys.argv[1] == "view"[:len(sys.argv[1])]: ViewCommand(sys.argv[2:]).execute() elif sys.argv[1] == "help"[:len(sys.argv[1])]: HelpCommand(sys.argv[2:]).execute() + # Special hidden command: + elif sys.argv[1] == "cert"[:len(sys.argv[1])]: + CertificateCommand(sys.argv[2:]).execute() else: HelpCommand.printGeneralUsage("Unknown command: %s" % sys.argv[1]) diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index 9d15d2b..0d4df55 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -19,7 +19,7 @@ class HelpCommand(Command): COMMANDS = {"genkey" : GenerateKeyCommand, "sign" : SignCommand, "break" : BreakCommand, "view" : ViewCommand, - "tackcert" : CertificateCommand} + "cert" : CertificateCommand} def __init__(self, argv): Command.__init__(self, argv, "", "", allowArgRemainder=True) From aabaa1f14cc18c3d82db1b00bbc0c6bdb8ccbbfe Mon Sep 17 00:00:00 2001 From: Trevor Date: Mon, 21 May 2012 16:43:17 -0700 Subject: [PATCH 63/94] Oops complete renaming tackcert -> cert. --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index f8ced52..84b643d 100644 --- a/Makefile +++ b/Makefile @@ -44,9 +44,9 @@ test: $(EXEC) break -k $(TESTDIR)/TACK_Key1.pem -p asdf > $(TESTDIR)/TACK_Break_Sig1.pem $(EXEC) b -x -k $(TESTDIR)/TACK_Key2.pem -p asdf -o $(TESTDIR)/TACK_Break_Sig2.pem cat $(TESTDIR)/TACK_Break_Sig1.pem $(TESTDIR)/TACK_Break_Sig2.pem > $(TESTDIR)/TACK_Break_Sigs.pem - $(EXEC) tackcert -i $(TESTDIR)/TACK3.pem > $(TESTDIR)/TACK_Cert3.pem - $(EXEC) tackcert -i $(TESTDIR)/TACK4.pem -b $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_Cert4.pem - $(EXEC) tackcert -i $(TESTDIR)/TACK_Cert3.pem > $(TESTDIR)/TACK3_FromCert.pem + $(EXEC) cert -i $(TESTDIR)/TACK3.pem > $(TESTDIR)/TACK_Cert3.pem + $(EXEC) cert -i $(TESTDIR)/TACK4.pem -b $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_Cert4.pem + $(EXEC) cert -i $(TESTDIR)/TACK_Cert3.pem > $(TESTDIR)/TACK3_FromCert.pem $(EXEC) view $(TESTDIR)/TACK_Key1.pem > $(TESTDIR)/TACK_View_Key1.txt cat $(TESTDIR)/TACK1.pem | $(EXEC) view - > $(TESTDIR)/TACK_View1.txt $(EXEC) v $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_View_Break_Sigs.txt From 27c0d682e52d0baf4bbfe7499c678dad5463575b Mon Sep 17 00:00:00 2001 From: Trevor Date: Mon, 21 May 2012 17:07:33 -0700 Subject: [PATCH 64/94] Use OpenSSL (if available) for unit testing. Note that there is also a Python crypto unit test, so the python code will be exercised no matter what. --- tests/Crypto_Test.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/Crypto_Test.py b/tests/Crypto_Test.py index 82150f8..3ec09a2 100644 --- a/tests/Crypto_Test.py +++ b/tests/Crypto_Test.py @@ -6,11 +6,16 @@ import unittest from tack.compat import a2b_hex +from tack.crypto.openssl.OpenSSL import openssl from tack.crypto.AES import AES from tack.crypto.ASN1 import asn1Length, toAsn1IntBytes, fromAsn1IntBytes from tack.crypto.ECGenerator import ECGenerator class CryptoTest(unittest.TestCase): + + @classmethod + def setUpClass(cls): + openssl.initialize() def test_AES(self): key = a2b_hex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") From 94695cc2a733b743aa70ba5f91de93cac6735306 Mon Sep 17 00:00:00 2001 From: Trevor Date: Mon, 21 May 2012 17:28:56 -0700 Subject: [PATCH 65/94] Touchup cosmetic things w/"cert" command. --- setup.py | 8 +++++--- tack/commands/CertificateCommand.py | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/setup.py b/setup.py index b42cd04..ce846d4 100755 --- a/setup.py +++ b/setup.py @@ -21,10 +21,12 @@ description="Tackpy implements TACK in python", license="public domain", scripts=["tack/tack"], - packages=["tack", "tack/commands", "tack/crypto", "tack/crypto/openssl", - "tack/crypto/python", "tack/structures", "tack/tls", "tack/util"], + packages=["tack", "tack/commands", + "tack/crypto", "tack/crypto/openssl", "tack/crypto/python", + "tack/structures", "tack/tls", "tack/util"], data_files=[("", ["LICENSE", "tack.py", "Makefile"]), - ("testdata", ["testdata/serverX509Cert.pem", "testdata/serverX509Cert.der"])] + ("testdata", ["testdata/serverX509Cert.pem", + "testdata/serverX509Cert.der"])] ) print "Cleaning up..." diff --git a/tack/commands/CertificateCommand.py b/tack/commands/CertificateCommand.py index a8fbca5..8e90932 100644 --- a/tack/commands/CertificateCommand.py +++ b/tack/commands/CertificateCommand.py @@ -31,7 +31,7 @@ def execute(self): self.outputFile.write(tlsCertificate.serializeAsPem()) if self.isVerbose(): - sys.stderr.write(str(tackExtension) + "\n") + sys.stderr.write(str(tackExtension)) elif self.inputCertificate is not None: if self.breakSignatures is not None: @@ -94,7 +94,7 @@ def printHelp(): (Alternatively, if input is a TACK certificate, writes out the TACK and/or Break Signatures as PEM files). -tackcert -i (TACK or CERT) +cert -i (TACK or CERT) Optional arguments: -v : Verbose From ca4349ddcd39239366aaf6fab1ffe647fbd0e698 Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 21 May 2012 17:58:27 -0700 Subject: [PATCH 66/94] Fixed some typos, removed unnecessary imports, added pedantic scoping. --- tack/commands/CertificateCommand.py | 3 +- tack/commands/Command.py | 2 +- tack/commands/HelpCommand.py | 2 -- tack/commands/ViewCommand.py | 2 -- tack/crypto/ECGenerator.py | 6 ++-- tack/crypto/openssl/OpenSSL_ECGenerator.py | 20 +++++------ tack/crypto/openssl/OpenSSL_ECPrivateKey.py | 28 ++++++++------- tack/crypto/openssl/OpenSSL_ECPublicKey.py | 27 ++++++++------ tack/crypto/python/Python_ECPublicKey.py | 6 ++-- tack/crypto/python/cryptomath.py | 2 +- tack/crypto/python/ecdsa.py | 6 ++-- tack/crypto/python/ellipticcurve.py | 6 ++-- tack/crypto/python/rijndael.py | 1 - tack/structures/TackExtension.py | 1 - tack/util/PEMEncoder.py | 1 - tests/PythonCrypto_Test.py | 40 ++++++++++----------- 16 files changed, 75 insertions(+), 78 deletions(-) diff --git a/tack/commands/CertificateCommand.py b/tack/commands/CertificateCommand.py index 8e90932..ace0e0d 100644 --- a/tack/commands/CertificateCommand.py +++ b/tack/commands/CertificateCommand.py @@ -8,7 +8,6 @@ from tack.commands.Command import Command from tack.structures.Tack import Tack from tack.structures.TackActivation import TackActivation -from tack.structures.TackBreakSig import TackBreakSig from tack.structures.TackExtension import TackExtension from tack.tls.TlsCertificate import TlsCertificate from tack.util.PEMDecoder import PEMDecoder @@ -48,7 +47,7 @@ def execute(self): self.outputFile.write(s) if self.isVerbose(): - sys.stderr.write(self.inputCertificate.writeText()) + sys.stderr.write(str(self.inputCertificate)) else: assert(False) diff --git a/tack/commands/Command.py b/tack/commands/Command.py index 5e8e9fb..a9d4600 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -131,7 +131,7 @@ def _readFileTextAndBinary(self, fname): return text, binary except IOError: - self.printError("Error opening file: %s" % argv[0]) + self.printError("Error opening file: %s" % fname) def printError(self, error): """Print error message and exit""" diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index 0d4df55..61816a7 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -5,7 +5,6 @@ # See the LICENSE file for legal information regarding use of this file. import sys -from tack.compat import bytesToStr from tack.commands.CertificateCommand import CertificateCommand from tack.version import __version__ from tack.commands.BreakCommand import BreakCommand @@ -13,7 +12,6 @@ from tack.commands.GenerateKeyCommand import GenerateKeyCommand from tack.commands.SignCommand import SignCommand from tack.commands.ViewCommand import ViewCommand -from tack.crypto.openssl.OpenSSL import openssl as o class HelpCommand(Command): diff --git a/tack/commands/ViewCommand.py b/tack/commands/ViewCommand.py index e397335..6c01952 100644 --- a/tack/commands/ViewCommand.py +++ b/tack/commands/ViewCommand.py @@ -5,9 +5,7 @@ # See the LICENSE file for legal information regarding use of this file. import sys -from tack.compat import bytesToStr from tack.commands.Command import Command -from tack.compat import readStdinBinary from tack.structures.Tack import Tack from tack.structures.TackKeyFile import TackKeyFile from tack.structures.TackBreakSig import TackBreakSig diff --git a/tack/crypto/ECGenerator.py b/tack/crypto/ECGenerator.py index db55986..a4d27c6 100644 --- a/tack/crypto/ECGenerator.py +++ b/tack/crypto/ECGenerator.py @@ -4,9 +4,9 @@ # # See the LICENSE file for legal information regarding use of this file. -from .openssl.OpenSSL_ECGenerator import OpenSSL_ECGenerator -from .python.Python_ECGenerator import Python_ECGenerator -from .openssl.OpenSSL import openssl as o +from tack.crypto.openssl.OpenSSL_ECGenerator import OpenSSL_ECGenerator +from tack.crypto.python.Python_ECGenerator import Python_ECGenerator +from tack.crypto.openssl.OpenSSL import openssl as o class ECGenerator: diff --git a/tack/crypto/openssl/OpenSSL_ECGenerator.py b/tack/crypto/openssl/OpenSSL_ECGenerator.py index 25ca8ad..a1c64fd 100644 --- a/tack/crypto/openssl/OpenSSL_ECGenerator.py +++ b/tack/crypto/openssl/OpenSSL_ECGenerator.py @@ -4,20 +4,18 @@ # # See the LICENSE file for legal information regarding use of this file. -import math, ctypes -from .OpenSSL_ECPublicKey import OpenSSL_ECPublicKey -from .OpenSSL_ECPrivateKey import OpenSSL_ECPrivateKey -from tack.crypto.python.Python_ECPrivateKey import Python_ECPrivateKey -from .OpenSSL import openssl as o -from .OpenSSL import bytesToC, cToBytes +from tack.crypto.openssl.OpenSSL_ECPublicKey import OpenSSL_ECPublicKey +from tack.crypto.openssl.OpenSSL_ECPrivateKey import OpenSSL_ECPrivateKey +from tack.crypto.openssl.OpenSSL import openssl as o +from tack.crypto.openssl.OpenSSL import bytesToC, cToBytes class OpenSSL_ECGenerator: @staticmethod def generateECKeyPair(): - try: - ec_key, ec_group = None, None + ec_key, ec_group = None, None + try: # Generate the new key ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid(b"prime256v1")) o.EC_KEY_generate_key(ec_key) @@ -47,5 +45,7 @@ def generateECKeyPair(): return (pub, priv) finally: - o.EC_KEY_free(ec_key) - o.EC_GROUP_free(ec_group) + if ec_key: + o.EC_KEY_free(ec_key) + if ec_group: + o.EC_GROUP_free(ec_group) diff --git a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py index 5e7d64a..1358545 100644 --- a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py @@ -4,12 +4,10 @@ # # See the LICENSE file for legal information regarding use of this file. -import ctypes from tack.crypto.Digest import Digest -from .OpenSSL_ECPublicKey import OpenSSL_ECPublicKey -from tack.crypto.python.Python_ECPublicKey import Python_ECPublicKey -from .OpenSSL import openssl as o -from .OpenSSL import bytesToC, cToBytes +from tack.crypto.openssl.OpenSSL_ECPublicKey import OpenSSL_ECPublicKey +from tack.crypto.openssl.OpenSSL import openssl as o +from tack.crypto.openssl.OpenSSL import bytesToC, cToBytes class OpenSSL_ECPrivateKey: @@ -29,10 +27,10 @@ def __init__(self, rawPrivateKey, rawPublicKey, ec_key=None): def __del__(self): o.EC_KEY_free(self.ec_key) - def sign(self, data): - try: - ecdsa_sig = None + def sign(self, data): + ecdsa_sig = None + try: # Hash and apply ECDSA hashBuf = bytesToC(Digest.SHA256(data)) ecdsa_sig = o.ECDSA_do_sign(hashBuf, 32, self.ec_key) @@ -49,7 +47,8 @@ def sign(self, data): sigBytes = rBytes + sBytes assert(len(sigBytes) == 64) finally: - o.ECDSA_SIG_free(ecdsa_sig) + if ecdsa_sig: + o.ECDSA_SIG_free(ecdsa_sig) # Double-check the signature before returning assert(OpenSSL_ECPublicKey(self.rawPublicKey).verify(data, sigBytes)) @@ -59,9 +58,9 @@ def getRawKey(self): return self.rawPrivateKey def _constructEcFromRawKey(self, rawPrivateKey): + privBignum, ec_key = None, None + try: - privBignum, ec_key = None, None - ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid(b"prime256v1")) privBuf = bytesToC(rawPrivateKey) privBignum = o.BN_new() @@ -69,8 +68,11 @@ def _constructEcFromRawKey(self, rawPrivateKey): o.EC_KEY_set_private_key(ec_key, privBignum) return o.EC_KEY_dup(ec_key) finally: - o.BN_free(privBignum) - o.EC_KEY_free(ec_key) + if privBignum: + o.BN_free(privBignum) + + if ec_key: + o.EC_KEY_free(ec_key) diff --git a/tack/crypto/openssl/OpenSSL_ECPublicKey.py b/tack/crypto/openssl/OpenSSL_ECPublicKey.py index 5c409fd..5c1cd60 100644 --- a/tack/crypto/openssl/OpenSSL_ECPublicKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPublicKey.py @@ -4,11 +4,10 @@ # # See the LICENSE file for legal information regarding use of this file. -import ctypes, sys from tack.compat import b2a_base32 from tack.crypto.Digest import Digest -from .OpenSSL import openssl as o -from .OpenSSL import bytesToC, cToBytes +from tack.crypto.openssl.OpenSSL import openssl as o +from tack.crypto.openssl.OpenSSL import bytesToC class OpenSSL_ECPublicKey: @@ -29,9 +28,9 @@ def __del__(self): def verify(self, data, signature): assert(len(signature) == 64) - try: - ecdsa_sig = None + ecdsa_sig = None + try: # Create ECDSA_SIG ecdsa_sig = o.ECDSA_SIG_new() rBuf = bytesToC(signature[ : 32]) @@ -49,7 +48,8 @@ def verify(self, data, signature): else: assert(False) finally: - o.ECDSA_SIG_free(ecdsa_sig) + if ecdsa_sig: + o.ECDSA_SIG_free(ecdsa_sig) def getRawKey(self): return self.rawPublicKey @@ -61,9 +61,9 @@ def getFingerprint(self): return "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) def _constructEcFromRawKey(self, rawPublicKey): - try: - ec_key, ec_group, ec_point = None, None, None + ec_key, ec_group, ec_point = None, None, None + try: ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid(b"prime256v1")) ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid(b"prime256v1")) ec_point = o.EC_POINT_new(ec_group) @@ -74,9 +74,14 @@ def _constructEcFromRawKey(self, rawPublicKey): o.EC_KEY_set_public_key(ec_key, ec_point) return o.EC_KEY_dup(ec_key) finally: - o.EC_KEY_free(ec_key) - o.EC_KEY_free(ec_group) - o.EC_POINT_free(ec_point) + if ec_key: + o.EC_KEY_free(ec_key) + + if ec_group: + o.EC_KEY_free(ec_group) + + if ec_point: + o.EC_POINT_free(ec_point) def __str__(self): return self.getFingerprint() \ No newline at end of file diff --git a/tack/crypto/python/Python_ECPublicKey.py b/tack/crypto/python/Python_ECPublicKey.py index bfcba72..44bf187 100644 --- a/tack/crypto/python/Python_ECPublicKey.py +++ b/tack/crypto/python/Python_ECPublicKey.py @@ -3,12 +3,10 @@ # # See the LICENSE file for legal information regarding use of this file. -import ctypes from tack.compat import b2a_base32 -from tack.compat import bytesToStr from tack.crypto.Digest import Digest -from .ecdsa import Public_key, Point, generator_256, Signature -from .cryptomath import bytesToNumber, numberToBytes +from tack.crypto.python.ecdsa import Public_key, Point, generator_256, Signature +from tack.crypto.python.cryptomath import bytesToNumber class Python_ECPublicKey: diff --git a/tack/crypto/python/cryptomath.py b/tack/crypto/python/cryptomath.py index fc71052..7cf8521 100644 --- a/tack/crypto/python/cryptomath.py +++ b/tack/crypto/python/cryptomath.py @@ -3,7 +3,7 @@ # # See the LICENSE file for legal information regarding use of this file. -import math, hashlib, hmac +import math def bytesToNumber(bytes): "Convert a sequence of bytes (eg bytearray) into integer." diff --git a/tack/crypto/python/ecdsa.py b/tack/crypto/python/ecdsa.py index b641337..2366389 100644 --- a/tack/crypto/python/ecdsa.py +++ b/tack/crypto/python/ecdsa.py @@ -5,8 +5,8 @@ # See the LICENSE file for legal information regarding use of this file. # Also see Peter Pearson's statement below -from .numbertheory import * -from .ellipticcurve import * +from tack.crypto.python.numbertheory import * +from tack.crypto.python.ellipticcurve import * from tack.compat import compat26Str ################ ECDSA ### @@ -149,7 +149,7 @@ def sign( self, hash, random_k ): p1 = k * G r = p1.x() if r == 0: raise RuntimeError("amazingly unlucky random number r") - s = ( inverse_mod( k, n ) * \ + s = ( inverse_mod( k, n ) * ( hash + ( self.secret_multiplier * r ) % n ) ) % n if s == 0: raise RuntimeError("amazingly unlucky random number s") return Signature( r, s ) diff --git a/tack/crypto/python/ellipticcurve.py b/tack/crypto/python/ellipticcurve.py index b669ccb..e6db86d 100644 --- a/tack/crypto/python/ellipticcurve.py +++ b/tack/crypto/python/ellipticcurve.py @@ -5,7 +5,7 @@ # See the LICENSE file for legal information regarding use of this file. # Also see Peter Pearson's statement below -from .numbertheory import * +from tack.crypto.python.numbertheory import * ################ ELLIPTIC CURVE ### @@ -104,7 +104,7 @@ def __add__( self, other ): p = self.__curve.p() - l = ( ( other.__y - self.__y ) * \ + l = ( ( other.__y - self.__y ) * inverse_mod( other.__x - self.__x, p ) ) % p x3 = ( l * l - self.__x - other.__x ) % p @@ -160,7 +160,7 @@ def double( self ): p = self.__curve.p() a = self.__curve.a() - l = ( ( 3 * self.__x * self.__x + a ) * \ + l = ( ( 3 * self.__x * self.__x + a ) * inverse_mod( 2 * self.__y, p ) ) % p x3 = ( l * l - 2 * self.__x ) % p diff --git a/tack/crypto/python/rijndael.py b/tack/crypto/python/rijndael.py index 9814dbd..2d89328 100644 --- a/tack/crypto/python/rijndael.py +++ b/tack/crypto/python/rijndael.py @@ -37,7 +37,6 @@ # deleting all the comments and renaming all the variables import copy -import string diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index 0f28ea9..9bc8827 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -7,7 +7,6 @@ from tack.structures.Tack import Tack from tack.structures.TackActivation import TackActivation from tack.structures.TackBreakSig import TackBreakSig -from tack.structures.TackActivation import TackActivation from tack.tls.TlsStructure import TlsStructure from tack.tls.TlsStructureWriter import TlsStructureWriter diff --git a/tack/util/PEMEncoder.py b/tack/util/PEMEncoder.py index 5d2c560..3d8c4a7 100644 --- a/tack/util/PEMEncoder.py +++ b/tack/util/PEMEncoder.py @@ -5,7 +5,6 @@ # See the LICENSE file for legal information regarding use of this file. from tack.compat import b2a_base64 -import sys class PEMEncoder: diff --git a/tests/PythonCrypto_Test.py b/tests/PythonCrypto_Test.py index 75e90c8..9280abe 100644 --- a/tests/PythonCrypto_Test.py +++ b/tests/PythonCrypto_Test.py @@ -90,7 +90,7 @@ def test_NumberTheory(self): calculated = square_root_mod_prime( sq, p ) if ( calculated * calculated ) % p != sq: error_tally = error_tally + 1 - print("Failed to find %d as sqrt( %d ) mod %d. Said %d." % \ + print("Failed to find %d as sqrt( %d ) mod %d. Said %d." % ( root, sq, p, calculated )) for nonsquare in range( 0, p ): @@ -101,7 +101,7 @@ def test_NumberTheory(self): pass else: error_tally = error_tally + 1 - print("Failed to report no root for sqrt( %d ) mod %d." % \ + print("Failed to report no root for sqrt( %d ) mod %d." % ( nonsquare, p )) # Test the jacobi function: @@ -272,7 +272,7 @@ def test_signature_validity( Msg, Qx, Qy, R, S, expected ): # ( got, expected )) pass else: - print("*** Signature test failed: got %s, expected %s." % \ + print("*** Signature test failed: got %s, expected %s." % ( got, expected )) assert() @@ -322,29 +322,29 @@ def test_signature_validity( Msg, Qx, Qy, R, S, expected ): #print("Testing point validity, as per ECDSAVS.pdf B.2.2:") - test_point_validity( \ - p192, \ - 0xcd6d0f029a023e9aaca429615b8f577abee685d8257cc83a, \ - 0x00019c410987680e9fb6c0b6ecc01d9a2647c8bae27721bacdfc, \ - False ) + test_point_validity( + p192, + 0xcd6d0f029a023e9aaca429615b8f577abee685d8257cc83a, + 0x00019c410987680e9fb6c0b6ecc01d9a2647c8bae27721bacdfc, + False ) test_point_validity( - p192, \ - 0x00017f2fce203639e9eaf9fb50b81fc32776b30e3b02af16c73b, \ - 0x95da95c5e72dd48e229d4748d4eee658a9a54111b23b2adb, \ - False ) + p192, + 0x00017f2fce203639e9eaf9fb50b81fc32776b30e3b02af16c73b, + 0x95da95c5e72dd48e229d4748d4eee658a9a54111b23b2adb, + False ) test_point_validity( - p192, \ - 0x4f77f8bc7fccbadd5760f4938746d5f253ee2168c1cf2792, \ - 0x000147156ff824d131629739817edb197717c41aab5c2a70f0f6, \ - False ) + p192, + 0x4f77f8bc7fccbadd5760f4938746d5f253ee2168c1cf2792, + 0x000147156ff824d131629739817edb197717c41aab5c2a70f0f6, + False ) test_point_validity( - p192, \ - 0xc58d61f88d905293bcd4cd0080bcb1b7f811f2ffa41979f6, \ - 0x8804dc7a7c4c7f8b5d437f5156f3312ca7d6de8a0e11867f, \ - True ) + p192, + 0xc58d61f88d905293bcd4cd0080bcb1b7f811f2ffa41979f6, + 0x8804dc7a7c4c7f8b5d437f5156f3312ca7d6de8a0e11867f, + True ) """ test_point_validity( p192, \ From 0b7fde134d9c56c4c9ca72231f60295229f5cf9b Mon Sep 17 00:00:00 2001 From: Moxie Marlinspike Date: Mon, 21 May 2012 21:44:55 -0700 Subject: [PATCH 67/94] Moved draft into separate repository. --- draft-tack.txt | 1288 ------------------------------------------------ draft-tack.xml | 989 ------------------------------------- 2 files changed, 2277 deletions(-) delete mode 100644 draft-tack.txt delete mode 100644 draft-tack.xml diff --git a/draft-tack.txt b/draft-tack.txt deleted file mode 100644 index a79e98d..0000000 --- a/draft-tack.txt +++ /dev/null @@ -1,1288 +0,0 @@ - - - -TLS Working Group M. Marlinspike -Internet-Draft T. Perrin, Ed. -Intended status: Standards Track May 10, 2012 -Expires: November 11, 2012 - - - Trust Assertions for Certificate Keys - draft-tack.txt - -Abstract - - This document defines TACK, a TLS Extension that enables a TLS server - to assert the authenticity of its public key. A TACK contains a - "TACK key" which is used to sign the public key from the TLS server's - certificate. Hostnames can be "pinned" to a TACK key. TLS - connections to a pinned hostname require the server to present a TACK - containing the pinned key and a corresponding signature over the TLS - server's public key. - -Status of this Memo - - This Internet-Draft is submitted in full conformance with the - provisions of BCP 78 and BCP 79. - - Internet-Drafts are working documents of the Internet Engineering - Task Force (IETF). Note that other groups may also distribute - working documents as Internet-Drafts. The list of current Internet- - Drafts is at http://datatracker.ietf.org/drafts/current/. - - Internet-Drafts are draft documents valid for a maximum of six months - and may be updated, replaced, or obsoleted by other documents at any - time. It is inappropriate to use Internet-Drafts as reference - material or to cite them other than as "work in progress." - - This Internet-Draft will expire on November 11, 2012. - -Copyright Notice - - Copyright (c) 2012 IETF Trust and the persons identified as the - document authors. All rights reserved. - - This document is subject to BCP 78 and the IETF Trust's Legal - Provisions Relating to IETF Documents - (http://trustee.ietf.org/license-info) in effect on the date of - publication of this document. Please review these documents - carefully, as they describe your rights and restrictions with respect - to this document. Code Components extracted from this document must - include Simplified BSD License text as described in Section 4.e of - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 1] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - - the Trust Legal Provisions and are provided without warranty as - described in the Simplified BSD License. - - -Table of Contents - - 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3 - 2. Requirements notation . . . . . . . . . . . . . . . . . . . . 4 - 3. Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 - 3.1. TACK life cycle . . . . . . . . . . . . . . . . . . . . . 5 - 3.2. Pin life cycle . . . . . . . . . . . . . . . . . . . . . . 6 - 4. TACK Extension . . . . . . . . . . . . . . . . . . . . . . . . 7 - 4.1. Definition of TACK_Extension . . . . . . . . . . . . . . . 7 - 4.2. Explanation of TACK_Extension fields . . . . . . . . . . . 8 - 4.2.1. TACK fields . . . . . . . . . . . . . . . . . . . . . 8 - 4.2.2. TACK_Break_Sig fields . . . . . . . . . . . . . . . . 8 - 4.2.3. TACK_Extension fields . . . . . . . . . . . . . . . . 9 - 5. Client processing . . . . . . . . . . . . . . . . . . . . . . 10 - 5.1. TACK pins, key records, and name records . . . . . . . . . 10 - 5.2. High-level client processing . . . . . . . . . . . . . . . 10 - 5.3. Client processing details . . . . . . . . . . . . . . . . 11 - 5.3.1. Check whether the TLS handshake is well-formed . . . . 11 - 5.3.2. Check the TACK generation and update min_generation . 12 - 5.3.3. Check whether the TACK is expired . . . . . . . . . . 12 - 5.3.4. Create and activate pins (optional) . . . . . . . . . 12 - 5.3.5. Discard pins based on break signatures . . . . . . . . 13 - 5.3.6. Deleting pins . . . . . . . . . . . . . . . . . . . . 13 - 6. Variations on client processing . . . . . . . . . . . . . . . 14 - 6.1. TACK and certificate verification . . . . . . . . . . . . 14 - 6.2. Application-specific pinning . . . . . . . . . . . . . . . 14 - 7. TACK IDs . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 - 8. Advice . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 - 8.1. For server operators . . . . . . . . . . . . . . . . . . . 16 - 8.2. For client implementers . . . . . . . . . . . . . . . . . 17 - 9. Security considerations . . . . . . . . . . . . . . . . . . . 18 - 9.1. For server operators . . . . . . . . . . . . . . . . . . . 18 - 9.2. For client implementers . . . . . . . . . . . . . . . . . 18 - 9.3. Note on algorithm agility . . . . . . . . . . . . . . . . 19 - 10. IANA considerations . . . . . . . . . . . . . . . . . . . . . 20 - 10.1. New entry for the TLS ExtensionType Registry . . . . . . . 20 - 11. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 21 - 12. Normative references . . . . . . . . . . . . . . . . . . . . . 22 - Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 23 - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 2] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -1. Introduction - - Traditionally, a TLS client verifies a TLS server's public key using - a certificate chain issued by some public CA. "Pinning" is a way for - clients to obtain increased certainty in server public keys. Clients - that employ pinning check for some constant "pinned" element of the - TLS connection when contacting a particular TLS host. - - Unfortunately, a number of problems arise when attempting to pin - certificate chains: the TLS servers at a given hostname may have - different certificate chains simultaneously deployed and may change - their chains at any time, the "more constant" elements of a chain - (the CAs) may not be trustworthy, and the client may be oblivious to - key compromise events which render the pinned data untrustworthy. - - TACK addresses these problems by having the site sign its TLS server - public keys with a "TACK key". This enables clients to "pin" a - hostname to the TACK key without requiring sites to modify their - existing certificate chains, and without limiting a site's - flexibility to deploy different certificate chains on different - servers or change certificate chains at any time. Since TACK pins - are based on TACK keys (instead of CA keys), trust in CAs is not - required. Additionally, the TACK key may be used to revoke previous - TACK signatures (or even itself) in order to handle the compromise of - TLS or TACK private keys. - - If requested, a compliant server will send a TLS Extension containing - its "TACK". Inside the TACK is a public key and signature. Once a - client has seen the same (hostname, TACK public key) pair multiple - times, the client will "activate" a pin between the hostname and TACK - key for a period equal to the length of time the pair has been - observed for. This "pin activation" process limits the impact of bad - pins resulting from transient network attacks or operator error. - - TACK pins are easily shared between clients. For example, a TACK - client may scan the internet to discover TACK pins, then publish - these pins for other clients to rely upon. - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 3] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -2. Requirements notation - - The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", - "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this - document are to be interpreted as described in [RFC2119]. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 4] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -3. Overview - -3.1. TACK life cycle - - A server operator using TACK may perform several processes: - - Selection of a TACK key: The server operator first chooses the ECDSA - signing key to use for a set of hostnames. It is safest to use a - different signing key for each hostname, though a signing key may - be reused for closely-related hostnames (such as aliases for the - same host, or hosts sharing the same TLS key). - - Creating initial TACKs under a TACK key: The TACK private key is - then used to sign the TLS public keys for all servers associated - with those hostnames. The TACK public key and signature are - combined with some metadata into each server's "TACK". - - Deploying initial TACKs: For each hostname, TACKs are deployed to - TLS servers in a two-stage process. First, each TLS server - associated with the hostname is given a TACK. Once this is - completed, pin activation is enabled on the servers. - - Creating new TACKs under a TACK key: A TACK needs to be replaced - whenever a server changes its TLS public key, or when the TACK - expires. TACKs may also need to be replaced with later-generation - TACKs if the TACK key's "min_generation" is updated (see next). - - Revoking old TACKs: If a TLS private key is compromised, the TACKs - signing this key can be revoked by publishing a new TACK - containing a higher "min_generation". - - Revoking TACK keys: If a TACK private key is compromised, or a - server operator wishes to stop using TACK or abruptly change its - TACK key for any reason, a server can revoke an entire TACK key - (including all TACKs and pins referring to it) by publishing a - "break signature". - - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 5] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -3.2. Pin life cycle - - A TACK client maintains a store of pins for verifying TLS - connections. Pins associate a hostname and a TACK key. When a - client sees a new hostname and TACK key combination, an inactive pin - is created. Every subsequent time the client sees the same pin, the - pin is "activated" for a period equal to the timespan between the - first time the pin was seen and the most recent time, up to a maximum - period of 30 days. - - Pin activation prevents an attacker with short-lived control of the - hostname from activating long-lived pins. It also makes it safer for - sites to experiment with TACKs, as a new TACK can be discarded - without causing long-lived problems. The 30 day limit guarantees - that a worst-case pin can be recovered from in reasonable time. - - In addition to creating and activating pins, a TLS connection can - alter the clients's pin store by publishing revocation data: - - Min_generation: Each pin stores the highest "min_generation" value - it has seen from the pinned TACK key, and rejects TACKs from - earlier generations. - - Break signatures: A TLS handshake may send break signatures which - cause all pins for the broken key to be discarded. - - - - - - - - - - - - - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 6] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -4. TACK Extension - -4.1. Definition of TACK_Extension - - A new TLS ExtensionType ("tack") is defined and MAY be included by a - TLS client in the ClientHello message defined in [RFC5246]. - - enum {tack(TBD), (65535)} ExtensionType; - - The "extension_data" field of this ClientHello SHALL be empty. A TLS - server which is not resuming a TLS session MAY respond with an - extension of type "tack" in the ServerHello. The "extension_data" - field of this ServerHello SHALL contain a "TACK_Extension", as - defined below using the TLS presentation language from [RFC5246]. - - enum (disabled(0), enabled(1)} TACK_Activation; - - struct { - opaque public_key[64]; - uint8 min_generation; - uint8 generation; - uint32 expiration; - opaque target_hash[32]; - opaque signature[64]; - } TACK; /* 166 bytes */ - - struct { - opaque public_key[64]; - opaque signature[64]; - } TACK_Break_Sig; /* 128 bytes */ - - struct { - TACK tack<0...166> /* 0 or 1 TACK */ - TACK_Break_Sig break_sigs<0...1024> /* 0...8 Break Sigs */ - TACK_Activation pin_activation; - } TACK_Extension; - - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 7] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -4.2. Explanation of TACK_Extension fields - -4.2.1. TACK fields - - public_key: This field specifies the TACK's public key. The field - contains a pair of integers (x, y) representing a point on the - elliptic curve P-256 defined in [FIPS186-3]. Each integer is - encoded as a 32-byte octet string using the Integer-to-Octet- - String algorithm from [RFC6090], and these strings are - concatenated with the x value first. (NOTE: This is equivalent to - an uncompressed subjectPublicKey from [RFC5480], except that the - initial 0x04 byte is omitted). - - min_generation: This field publishes a min_generation value. - - generation: This field assigns each TACK a generation. Generations - less than a published min_generation are considered revoked. - - expiration: This field specifies a time after which the TACK is - considered expired. The time is encoded as the number of minutes, - excluding leap seconds, after midnight UTC, January 1 1970. - - target_hash: This field is a hash of the TLS server's - SubjectPublicKeyInfo [RFC5280] using the SHA256 algorithm from - [FIPS180-2]. The SubjectPublicKeyInfo is typically conveyed as - part of the server's X.509 certificate. - - signature: This field is an ECDSA signature by the TACK's public key - over the 8 byte ASCII string "tack_sig" followed by the contents - of the TACK prior to the "signature" field (i.e. the preceding 102 - bytes). The field contains a pair of integers (r, s) representing - an ECDSA signature as defined in [FIPS186-3], using curve P-256 - and SHA256. Each integer is encoded as a 32-byte octet string - using the Integer-to-Octet-String algorithm from [RFC6090], and - these strings are concatenated with the r value first. - -4.2.2. TACK_Break_Sig fields - - public_key: This field specifies the TACK key being broken. The key - is encoded as per TACK.public_key. - - signature: This field is an ECDSA signature by the TACK_Break_Sig's - public key over the 14 byte ASCII string "tack_break_sig". The - field contains a pair of integers (r, s) representing an ECDSA - signature as defined in [FIPS186-3], using curve P-256 and SHA256. - It is calculated and encoded as per TACK.signature. - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 8] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -4.2.3. TACK_Extension fields - - tack: This field provides the server's TACK. It MAY be empty, or - MAY contain a TACK. - - break_sigs: This field provides break signatures. It MAY be empty, - or MAY contain up to 8 break signatures. - - pin_activation: If pin activation is enabled, then the - TACK_Extension MAY be used by clients to activate or extend the - activation of TACK pins. This field is typically toggled from a - disabled to an enabled state once TACKs have been deployed to all - TLS servers for a hostname. Enabling pin_activation when there is - no TACK present has no effect. - - Note that both the "tack" and "break_sigs" fields MAY be empty. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 9] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -5. Client processing - -5.1. TACK pins, key records, and name records - - A client supporting TACK SHALL have a local store of pins, consisting - of "key records" and "name records". Each name record is associated - with a key record. Multiple name records MAY be associated with one - key record. A "pin" refers to a (name record, key record) pair. - - A "key record" contains: - - TACK public key (or hash): A public key or a cryptographically- - secure, second preimage-resistant hash of a public key. A client - SHALL NOT store multiple key records referencing the same key. - - Min_generation: A single byte used to detect revoked TACKs. - - A "name record" contains: - - Name: A fully qualified DNS hostname. A client SHALL NOT store - multiple name records with the same name. The TLS server's - hostname is considered the "relevant name", and a pin whose name - exactly matches the relevant name is considered a "relevant pin". - - Initial timestamp: A timestamp noting when this pin was created. - - Active period end: Empty or a timestamp. If empty or set to a - time in the past, the pin is "inactive". If set to a future time, - the pin is "active" until that time. - -5.2. High-level client processing - - A TACK client SHALL send the "tack" extension defined previously, and - SHOULD send the "server_name" extension from [RFC6066]. If not - resuming a session, the server MAY respond with a TACK_Extension. A - TACK client SHALL perform the following steps prior to using a non- - resumed connection: - - 1. Check whether the TLS handshake is "well-formed". - - 2. Check the TACK generation and update min_generation. - - 3. Check whether the TACK is expired. - - 4. Create and activate pins (optional). - - 5. Discard pins based on break signatures. - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 10] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - - These steps SHALL be performed in order. If there is any error, the - client SHALL send a fatal error alert and close the connection, - skipping the remaining steps (see Section 5.3 for details). - - After the above steps, if there is a relevant active pin and a TACK - whose key is referenced by the pin, then the connection is "accepted" - by the pin. If there is a relevant active pin but no such TACK, the - connection is "rejected" by the pin. If there is no relevant active - pin, the connection is "unpinned". - - A rejected connection might indicate a network attack. If the - connection is rejected the client SHOULD send a fatal "access_denied" - error alert and close the connection. - - A client MAY perform additional verification steps before using an - accepted or unpinned connection. See Section 6.1 for an example. - -5.3. Client processing details - -5.3.1. Check whether the TLS handshake is well-formed - - A TLS handshake is "well-formed" if the following are true (the error - alert to be sent on a failure is indicated in parentheses): - - 1. The handshake protocol negotiates a cryptographically secure - ciphersuite and finishes succesfully (else see [RFC5246]). - - 2. The handshake contains either no TACK_Extension or a - syntactically-valid TACK_Extension (else "decode_error"). - - 3. If break signatures are present, the signatures are correct (else - "decrypt_error"). This step is optional, as break signature - verification MAY be deferred till later. - - 4. If a TACK is present, it is "well-formed" by the rules below. - - A TACK is "well-formed" if: - - 1. "public_key" is a valid elliptic curve public key on the curve - P-256 (else "decrypt_error"). - - 2. "generation" is >= "min_generation" (else "decode_error"). - - 3. "target_hash" is equal to the SHA256 hash of the server's - SubjectPublicKeyInfo (else "illegal_parameter"). - - 4. "signature" is a correct ECDSA signature (else "decrypt_error"). - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 11] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -5.3.2. Check the TACK generation and update min_generation - - If there is a TACK and a key record referencing the TACK key, and the - TACK's generation is less than the key record's min_generation, then - the TACK is revoked and the client SHALL send the - "certificate_revoked" alert and close the connection. - - Otherwise, if there is a TACK and a key record referencing the TACK - key, and the TACK's min_generation is greater than the key record's - min_generation, then the key record's min_generation SHALL be set to - the TACK's value. - -5.3.3. Check whether the TACK is expired - - If there is a TACK and the TACK's "expiration" field specifies a time - in the past, the client SHALL send the "certificate_expired" alert - and close the connection. - -5.3.4. Create and activate pins (optional) - - The TLS connection MAY be used to create, delete, and activate pins - as described in this section. Note that this section is optional; a - client MAY rely on an external source of pins, provided the external - pins are produced by a client following the below algorithms. - - If there is a TACK and a relevant pin referencing the TACK key, and - pin activation is enabled, the name record's "active period end" - SHALL be set using the below formula (where "current" is the current - time, and "initial" is the "initial timestamp" from the name record): - - active_period_end = current + MIN(30 days, current - initial) - - If there is a TACK and either no relevant pin or an inactive relevant - pin that does not reference the TACK key, a new pin SHALL be created: - - 1. If the TACK key is referenced by an existing key record, the key - record is reused, otherwise a new key record is created with the - TACK's key and min_generation. - - 2. A new name record is created containing the relevant name, an - "initial timestamp" equal to the current time, and an empty - "active period end". - - 3. If there is an existing relevant pin, the pin SHALL be deleted - (see Section 5.3.6). - - If there is no TACK and the relevant pin is inactive, the pin SHALL - be deleted (see Section 5.3.6). - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 12] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - - The following table summarizes this behavior based on whether the - relevant pin is active and references the TACK key. The "(*)" means - "if pin activation is enabled". - - +------------+---------------------+-------------------------------+ - | Pin status | Pin references TACK | Result | - +------------+---------------------+-------------------------------+ - | Active | Yes | Extend activation period (*) | - | | | | - | Active | No (or no TACK) | Rejected | - | | | | - | Inactive | Yes | Activate pin (*) | - | | | | - | Inactive | No | Replace with new inactive pin | - | | | | - | Inactive | No TACK | Delete pin | - | | | | - | No pin | - | Create new inactive pin | - | | | | - | No pin | No TACK | - | - +------------+---------------------+-------------------------------+ - -5.3.5. Discard pins based on break signatures - - All key records broken by break signatures SHALL be discarded, along - with their associated name records. A key record is broken by a - break signature if the break signature passes the following checks: - - 1. "public_key" is referenced by the key record. - - 2. "signature" is a correct ECDSA signature (else "decrypt_error"). - -5.3.6. Deleting pins - - A client might need to delete a pin from its store as a result of the - algorithms in Section 5.3.4. A client MAY also delete pins from its - store at any time, whether to save space, protect privacy, or for any - other reason. To delete a pin, its name record SHALL be removed. If - this leaves a key record with no associated name records, the key - record MAY be removed as well. Pins MAY be deleted regardless of - whether they are active or inactive, however for security concerns - regarding pin deletion, see Section 9.2. - - Deleting pins unnecessarily will reduce the benefits of TACK, so - SHOULD be avoided. Note that a pin SHOULD NOT be deleted simply - because it has become inactive. Instead, such a pin SHOULD be - retained, so that it can be re-activated in the future by the - algorithms in Section 5.3.4. - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 13] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -6. Variations on client processing - -6.1. TACK and certificate verification - - A TACK client MAY choose to perform some form of certificate - verification in addition to TACK processing. When combining - certificate verification and TACK processing, the TACK processing - described in Section 5 SHALL be followed, with the exception that - TACK processing MAY be terminated early (or skipped) if some fatal - certificate error is discovered. - - If TACK processing and certificate verification both complete without - a fatal error, the client SHALL apply some policy to decide whether - to accept the connection. The policy is up to the client. An - example policy would be to accept the connection only if it passes - certificate verification and is not rejected by a pin, or if the user - elects to "connect anyway" despite certificate and/or pin failures. - -6.2. Application-specific pinning - - In addition to the hostname-based pinning described in Section 5, - some applications may require "application-specific pins", where an - application-layer name is pinned to a TACK key. For example, an SMTP - MTA may wish to authenticate receiving MTAs by pinning email domains - to the receiving MTAs' TACK keys. - - Application-specific pins may require redefinition of the name - record's "name" field, the "relevant name" for the TLS connection, - and the "pin activation" signal. With these items redefined, the - client processing rules in Section 5 may be reused. - - Note that a server using application-specific pins is still subject - to hostname pins, and a client MAY apply either or both forms of - pinning. - - The specification of application-specific pinning for particular - applications is outside the scope of this document. - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 14] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -7. TACK IDs - - A "TACK ID" MAY be used to represent a TACK public key to users in a - form that is relatively easy to compare and transcribe. A TACK ID - consists of the first 25 characters from the base32 encoding of - SHA256(public_key), split into 5 groups of 5 characters separated by - periods. Base32 encoding is as specified in [RFC4648], except - lowercase is used. - - Example TACK IDs: - - quxiz.kpldu.uuedc.j5znm.7mqst - - a334f.bt7ts.ljb3b.y24ij.6zhwm - - ebsx7.z22qt.okobu.ibhut.xzdny - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 15] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -8. Advice - -8.1. For server operators - - Key reuse: All servers that are pinned to a single TACK key are able - to impersonate each other, since clients will perceive their TACKs - as equivalent. Thus, TACK keys SHOULD NOT be reused with - different hostnames unless these hostnames are closely related. - Examples where it would be safe to reuse a TACK key are hostnames - aliased to the same host, hosts sharing the same TLS key, or - hostnames for a group of near-identical servers. - - Aliases: A TLS server may be referenced by multiple hostnames. - Clients may pin any of these hostnames. Server operators should - be careful when using such DNS aliases that hostnames are not - pinned inadvertently. - - Generations: To revoke older generations of TACKs, the server - operator SHOULD first provide all servers with a new generation of - TACKs, and only then provide servers with new TACKs containing the - new min_generation. Otherwise, a client may receive a - min_generation update from one server but then try to contact an - older-generation server which has not yet been updated. - - Signature expiration: It is convenient to set the TACK expiration - equal to the end-entity certificate expiration, so that the TACK - and certificate may both be replaced at the same time. - Alternatively, short-lived TACKs may be used so that a compromised - TLS private key has limited value to an attacker. - - Break signatures: A break signature only needs to be published for a - time interval equal to the maximum active period of any affected - pins. For example, if a TACK has been only been published on a - website for 24 hours, to remove the TACK only requires publishing - the break signature for 24 hours. - - Pin activation: Pin activation SHOULD only be enabled once all TLS - servers sharing the same hostname have a TACK. Otherwise, a - client may activate a pin by contacting one server, then contact a - different server at the same hostname that does not yet have a - TACK. - - Pin deactivation: The pin_activation field can be used to phase out - TACKs for a hostname. If all servers at a hostname disable pin - activation, all existing pins for the hostname will eventually - become inactive, at which point the servers' TACKs can be removed. - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 16] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -8.2. For client implementers - - Sharing pin information: It is possible for a client to maintain a - pin store based entirely on its own TLS connections. However, - such a client runs the risk of creating incorrect pins, failing to - keep its pins active, or failing to receive revocation information - (min_generation updates and break signatures). Clients are - advised to collaborate so that pin data can be aggregated and - shared. This will require additional protocols outside the scope - of this document. - - Clock synchronization: A client SHOULD take measures to prevent - TACKs from being erroneously rejected due to an inaccurate client - clock. Such methods MAY include using time synchronization - protocols such as NTP [RFC5905], or accepting seemingly-expired - TACKs if they expired less than T minutes ago, where T is a - "tolerance bound" set to the client's maximum expected clock - error. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 17] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -9. Security considerations - -9.1. For server operators - - All servers pinned to the same TACK key can impersonate each other - (see Section 8.1). Think carefully about this risk if using the same - TACK key for multiple hostnames. - - Make backup copies of the TACK private key and keep all copies in - secure locations where they can't be compromised. - - A TACK private key MUST NOT be used to perform any non-TACK - cryptographic operations. For example, using a TACK key for email - encryption, code-signing, or any other purpose MUST NOT be done. - - HTTP cookies [RFC6265] set by a pinned host can be stolen by a - network attacker who can forge web and DNS responses so as to cause a - client to send the cookies to a phony subdomain of the pinned host. - To prevent this, TACK HTTPS Servers SHOULD set the "secure" attribute - and omit the "domain" attribute on all security-sensitive cookies, - such as session cookies. These settings tell the browser that the - cookie should only be presented back to the originating host (not its - subdomains), and should only be sent over HTTPS (not HTTP) [RFC6265]. - -9.2. For client implementers - - A TACK pin store may contain private details of the client's - connection history. An attacker may be able to access this - information by hacking or stealing the client. Some information - about the client's connection history could also be gleaned by - observing whether the client accepts or rejects connections to phony - TLS servers without correct TACKs. To mitigate these risks, a TACK - client SHOULD allow the user to edit or clear the pin store. - - Aside from rejecting TLS connections, clients SHOULD NOT take any - actions which would reveal to a network observer the state of the - client's pin store, as this would allow an attacker to know in - advance whether a "man-in-the-middle" attack on a particular TLS - connection will succeed or be detected. - - An attacker may attempt to flood a client with spurious TACKs for - different hostnames, causing the client to delete old pins to make - space for new ones. To defend against this, clients SHOULD NOT - delete active pins to make space for new pins. Clients instead - SHOULD delete inactive pins. If there are no inactive pins to - delete, then the pin store is full and there is no space for new - pins. To select an inactive pin for deletion, the client SHOULD - delete the pin with the oldest "active_period_end". - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 18] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -9.3. Note on algorithm agility - - If the need arises for TACKs using different cryptographic algorithms - (e.g., if SHA256 or ECDSA are shown to be weak), a "v2" version of - TACKs could be defined, requiring assignment of a new TLS Extension - number. TACKs as defined in this document would then be known as - "v1" TACKs. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 19] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -10. IANA considerations - -10.1. New entry for the TLS ExtensionType Registry - - IANA is requested to add an entry to the existing TLS ExtensionType - registry, defined in [RFC5246], for tack(TBD) as defined in this - document. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 20] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -11. Acknowledgements - - Valuable feedback has been provided by Adam Langley, Chris Palmer, - Nate Lawson, and Joseph Bonneau. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 21] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -12. Normative references - - [FIPS180-2] - National Institute of Standards and Technology, "Secure - Hash Standard", FIPS PUB 180-2, August 2002, . - - [FIPS186-3] - National Institute of Standards and Technology, "Digital - Signature Standard", FIPS PUB 186-3, June 2009, . - - [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate - Requirement Levels", BCP 14, RFC 2119, March 1997. - - [RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data - Encodings", RFC 4648, October 2006. - - [RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security - (TLS) Protocol Version 1.2", RFC 5246, August 2008. - - [RFC5280] Cooper, D., Santesson, S., Farrell, S., Boeyen, S., - Housley, R., and W. Polk, "Internet X.509 Public Key - Infrastructure Certificate and Certificate Revocation List - (CRL) Profile", RFC 5280, May 2008. - - [RFC5480] Turner, S., Brown, D., Yiu, K., Housley, R., and T. Polk, - "Elliptic Curve Cryptography Subject Public Key - Information", RFC 5480, March 2009. - - [RFC5905] Mills, D., Martin, J., Burbank, J., and W. Kasch, "Network - Time Protocol Version 4: Protocol and Algorithms - Specification", RFC 5905, June 2010. - - [RFC6066] Eastlake, D., "Transport Layer Security (TLS) Extensions: - Extension Definitions", RFC 6066, January 2011. - - [RFC6090] McGrew, D., Igoe, K., and M. Salter, "Fundamental Elliptic - Curve Cryptography Algorithms", RFC 6090, February 2011. - - [RFC6265] Barth, A., "HTTP State Management Mechanism", RFC 6265, - April 2011. - - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 22] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -Authors' Addresses - - Moxie Marlinspike - - - Trevor Perrin (editor) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 11, 2012 [Page 23] - diff --git a/draft-tack.xml b/draft-tack.xml deleted file mode 100644 index 2dd4143..0000000 --- a/draft-tack.xml +++ /dev/null @@ -1,989 +0,0 @@ - - - - - - - - - - - Trust Assertions for Certificate Keys - - - - - - - - Security - TLS Working Group - - - -This document defines TACK, a TLS Extension that enables a TLS server to -assert the authenticity of its public key. A TACK contains a "TACK key" -which is used to sign the public key from the TLS server's certificate. -Hostnames can be "pinned" to a TACK key. TLS connections to a pinned -hostname require the server to present a TACK containing the pinned key -and a corresponding signature over the TLS server's public key. - - - - - - -
- - -Traditionally, a TLS client verifies a TLS server's public key using a -certificate chain issued by some public CA. "Pinning" is a way for clients to -obtain increased certainty in server public keys. Clients that employ pinning -check for some constant "pinned" element of the TLS connection when -contacting a particular TLS host. - - - -Unfortunately, a number of problems arise when attempting to pin certificate -chains: the TLS servers at a given hostname may have different certificate -chains simultaneously deployed and may change their chains at any time, the -"more constant" elements of a chain (the CAs) may not be trustworthy, and the -client may be oblivious to key compromise events which render the pinned data -untrustworthy. - - - - - -TACK addresses these problems by having the site sign its TLS server public -keys with a "TACK key". This enables clients to "pin" a hostname to the TACK -key without requiring sites to modify their existing certificate chains, and -without limiting a site's flexibility to deploy different certificate chains -on different servers or change certificate chains at any time. Since TACK pins -are based on TACK keys (instead of CA keys), trust in CAs is not required. -Additionally, the TACK key may be used to revoke previous TACK signatures (or -even itself) in order to handle the compromise of TLS or TACK private keys. - - - - -If requested, a compliant server will send a TLS Extension containing its -"TACK". Inside the TACK is a public key and signature. Once a client has seen -the same (hostname, TACK public key) pair multiple times, the client will -"activate" a pin between the hostname and TACK key for a period equal to the -length of time the pair has been observed for. This "pin activation" process -limits the impact of bad pins resulting from transient network attacks or -operator error. - - - - -TACK pins are easily shared between clients. For example, a TACK client -may scan the internet to discover TACK pins, then publish these pins for other -clients to rely upon. - - -
- - -
The key words "MUST", -"MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", -"RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as -described in . -
- -
- -
- -A server operator using TACK may perform several processes: - - - - -The server operator first chooses the ECDSA signing key to use for a set of -hostnames. It is safest to use a different signing key for each hostname, -though a signing key may be reused for closely-related hostnames (such as -aliases for the same host, or hosts sharing the same TLS key). - - - - -The TACK private key is then used to sign the TLS public keys for all servers -associated with those hostnames. The TACK public key and signature are -combined with some metadata into each server's "TACK". - - - - -For each hostname, TACKs are deployed to TLS servers in a two-stage process. -First, each TLS server associated with the hostname is given a TACK. Once this -is completed, pin activation is enabled on the servers. - - - - - -A TACK needs to be replaced whenever a server changes its TLS public key, or -when the TACK expires. TACKs may also need to be replaced with -later-generation TACKs if the TACK key's "min_generation" is updated (see -next). - - - - -If a TLS private key is compromised, the TACKs signing this key can be revoked by -publishing a new TACK containing a higher "min_generation". - - - - -If a TACK private key is compromised, or a server operator wishes to stop using -TACK or abruptly change its TACK key for any reason, a server can revoke an entire -TACK key (including all TACKs and pins referring to it) by publishing a "break -signature". - - - - - - - -
- -
- - - -A TACK client maintains a store of pins for verifying TLS connections. Pins -associate a hostname and a TACK key. When a client sees a new hostname and -TACK key combination, an inactive pin is created. Every subsequent time the -client sees the same pin, the pin is "activated" for a period equal to the -timespan between the first time the pin was seen and the most recent time, up -to a maximum period of 30 days. - - - - -Pin activation prevents an attacker with short-lived control of the hostname -from activating long-lived pins. It also makes it safer for sites to -experiment with TACKs, as a new TACK can be discarded without causing -long-lived problems. The 30 day limit guarantees that a worst-case pin can be -recovered from in reasonable time. - - - - - In addition to creating and activating pins, a TLS connection can alter the - clients's pin store by publishing revocation data: - -Each pin stores the highest "min_generation" value it has seen from the pinned -TACK key, and rejects TACKs from earlier generations. - - - - - -A TLS handshake may send break signatures which cause all pins for the -broken key to be discarded. - - - - - -
-
- -
-
- - -A new TLS ExtensionType ("tack") is defined and MAY be included by a TLS -client in the ClientHello message defined in . - - -
-enum {tack(TBD), (65535)} ExtensionType; -
- - -The "extension_data" field of this ClientHello SHALL be empty. A TLS server -which is not resuming a TLS session MAY respond with an extension of type -"tack" in the ServerHello. The "extension_data" field of this ServerHello -SHALL contain a "TACK_Extension", as defined below using the TLS presentation -language from . - - -
-enum (disabled(0), enabled(1)} TACK_Activation; - -struct { - opaque public_key[64]; - uint8 min_generation; - uint8 generation; - uint32 expiration; - opaque target_hash[32]; - opaque signature[64]; -} TACK; /* 166 bytes */ - -struct { - opaque public_key[64]; - opaque signature[64]; -} TACK_Break_Sig; /* 128 bytes */ - -struct { - TACK tack<0...166> /* 0 or 1 TACK */ - TACK_Break_Sig break_sigs<0...1024> /* 0...8 Break Sigs */ - TACK_Activation pin_activation; -} TACK_Extension; - -
- - - - -
-
- -
- - - - - -This field specifies the TACK's public key. The field contains a pair -of integers (x, y) representing a point on the elliptic curve P-256 defined in -. Each integer is encoded as a 32-byte octet string -using the Integer-to-Octet-String algorithm from , and -these strings are concatenated with the x value first. (NOTE: This is -equivalent to an uncompressed subjectPublicKey from , -except that the initial 0x04 byte is omitted). - - - - - -This field publishes a min_generation value. - - - - - -This field assigns each TACK a generation. Generations less than a published -min_generation are considered revoked. - - - - - -This field specifies a time after which the TACK is considered expired. The -time is encoded as the number of minutes, excluding leap seconds, after -midnight UTC, January 1 1970. - - - -This field is a hash of the TLS server's SubjectPublicKeyInfo using the SHA256 algorithm from . -The SubjectPublicKeyInfo is typically conveyed as part of the server's X.509 -certificate. - - - - - -This field is an ECDSA signature by the TACK's public key over the 8 byte -ASCII string "tack_sig" followed by the contents of the TACK prior to the -"signature" field (i.e. the preceding 102 bytes). The field contains a pair of -integers (r, s) representing an ECDSA signature as defined in , using curve P-256 and SHA256. Each integer is encoded as -a 32-byte octet string using the Integer-to-Octet-String algorithm from , and these strings are concatenated with the r value first. - - - - -
- -
- - - - - -This field specifies the TACK key being broken. The key is encoded as per -TACK.public_key. - - - - - -This field is an ECDSA signature by the TACK_Break_Sig's public key over the -14 byte ASCII string "tack_break_sig". The field contains a pair of integers -(r, s) representing an ECDSA signature as defined in , using curve P-256 and SHA256. It is calculated and -encoded as per TACK.signature. - - - - - - -
- -
- - - - - -This field provides the server's TACK. It MAY be empty, or MAY contain a TACK. - - - - -This field provides break signatures. It MAY be empty, or MAY contain up to 8 -break signatures. - - - - -If pin activation is enabled, then the TACK_Extension MAY be used by clients -to activate or extend the activation of TACK pins. This field is typically -toggled from a disabled to an enabled state once TACKs have been deployed to -all TLS servers for a hostname. Enabling pin_activation when there is no TACK -present has no effect. - - - - Note that both the "tack" and "break_sigs" fields MAY be empty. - -
- -
-
- -
-
- - - -A client supporting TACK SHALL have a local store of pins, consisting of "key -records" and "name records". Each name record is associated with a key record. -Multiple name records MAY be associated with one key record. A "pin" refers to -a (name record, key record) pair. - - - - - A "key record" contains: - - - - -TACK public key (or hash): A public key or a cryptographically-secure, second -preimage-resistant hash of a public key. A client SHALL NOT store multiple key -records referencing the same key. - - - - -Min_generation: A single byte used to detect revoked TACKs. - - - - - - -A "name record" contains: - - - - - -Name: A fully qualified DNS hostname. A client SHALL NOT store multiple name -records with the same name. The TLS server's hostname is considered the -"relevant name", and a pin whose name exactly matches the relevant name is -considered a "relevant pin". - - - - -Initial timestamp: A timestamp noting when this pin was created. - - - - -Active period end: Empty or a timestamp. If empty or set to a time in the -past, the pin is "inactive". If set to a future time, the pin is "active" -until that time. - - - - - - -
- -
- - - -A TACK client SHALL send the "tack" extension defined previously, and SHOULD -send the "server_name" extension from . If not -resuming a session, the server MAY respond with a TACK_Extension. A TACK -client SHALL perform the following steps prior to using a non-resumed -connection: - - - Check whether the TLS handshake is "well-formed". - Check the TACK generation and update min_generation. - Check whether the TACK is expired. - Create and activate pins (optional). - Discard pins based on break signatures. - - -These steps SHALL be performed in order. If there is any error, the client -SHALL send a fatal error alert and close the connection, skipping the -remaining steps (see for details). - - - - - -After the above steps, if there is a relevant active pin and a TACK whose key -is referenced by the pin, then the connection is "accepted" by the pin. If -there is a relevant active pin but no such TACK, the connection is "rejected" -by the pin. If there is no relevant active pin, the connection is "unpinned". - - - A rejected connection might indicate a network attack. If the connection -is rejected the client SHOULD send a fatal "access_denied" error alert and -close the connection. - - A client MAY perform additional verification steps before using an -accepted or unpinned connection. See for an -example. - -
- -
- - - -A TLS handshake is "well-formed" if the following are true (the error alert to -be sent on a failure is indicated in parentheses): - - - -The handshake protocol negotiates a cryptographically secure ciphersuite -and finishes succesfully (else see ). - -The handshake contains either no TACK_Extension or a syntactically-valid -TACK_Extension (else "decode_error"). - -If break signatures are present, the signatures are correct (else -"decrypt_error"). This step is optional, as break signature verification MAY -be deferred till later. - -If a TACK is present, it is "well-formed" by the rules below. - - - - - -A TACK is "well-formed" if: - - - "public_key" is a valid elliptic curve public key on the curve P-256 (else -"decrypt_error"). - - "generation" is >= "min_generation" (else "decode_error"). - - "target_hash" is equal to the SHA256 hash of the server's -SubjectPublicKeyInfo (else "illegal_parameter"). - - "signature" is a correct ECDSA signature (else "decrypt_error"). - - - -
- -
- - -If there is a TACK and a key record referencing the TACK key, and the TACK's -generation is less than the key record's min_generation, then the TACK is -revoked and the client SHALL send the "certificate_revoked" alert and close -the connection. - - - Otherwise, if there is a TACK and a key record referencing the TACK key, -and the TACK's min_generation is greater than the key record's min_generation, -then the key record's min_generation SHALL be set to the TACK's value. - - -
-
- - -If there is a TACK and the TACK's "expiration" field specifies a time in the -past, the client SHALL send the "certificate_expired" alert and close the -connection. - - - - -
- -
- - - -The TLS connection MAY be used to create, delete, and activate pins as -described in this section. Note that this section is optional; a client MAY -rely on an external source of pins, provided the external pins are produced by -a client following the below algorithms. - - - - -If there is a TACK and a relevant pin referencing the TACK key, and pin -activation is enabled, the name record's "active period end" SHALL be set -using the below formula (where "current" is the current time, and "initial" is -the "initial timestamp" from the name record): - -
-active_period_end = current + MIN(30 days, current - initial) -
- - -If there is a TACK and either no relevant pin or an inactive relevant pin that -does not reference the TACK key, a new pin SHALL be created: - - -If the TACK key is referenced by an existing -key record, the key record is reused, otherwise a new key record is created -with the TACK's key and min_generation. -A new name record is created -containing the relevant name, an "initial timestamp" equal to the current -time, and an empty "active period end". - -If there is an existing relevant pin, the pin SHALL be deleted (see ). - - - - - If there is no TACK and the relevant pin is inactive, the pin SHALL be -deleted (see ). - - - -The following table summarizes this behavior based on whether the relevant pin -is active and references the TACK key. The "(*)" means "if pin activation is -enabled". - - - - Pin status - Pin references TACK - Result - Active - Yes - Extend activation period (*) - - Active - No (or no TACK) - Rejected - - Inactive - Yes - Activate pin (*) - - Inactive - No - Replace with new inactive pin - - Inactive - No TACK - Delete pin - - No pin - - - Create new inactive pin - - No pin - No TACK - - - - - -
-
- - -All key records broken by break signatures SHALL be discarded, along with -their associated name records. A key record is broken by a break signature if -the break signature passes the following checks: - - - - "public_key" is referenced by the key record. - - "signature" is a correct ECDSA signature (else "decrypt_error"). - - - -
- -
- - - -A client might need to delete a pin from its store as a result of the -algorithms in . A client MAY also delete pins from -its store at any time, whether to save space, protect privacy, or for any -other reason. To delete a pin, its name record SHALL be removed. If this -leaves a key record with no associated name records, the key record MAY be -removed as well. Pins MAY be deleted regardless of whether they are active or -inactive, however for security concerns regarding pin deletion, see . - - - - - - -Deleting pins unnecessarily will reduce the benefits of TACK, so SHOULD be -avoided. Note that a pin SHOULD NOT be deleted simply because it has become -inactive. Instead, such a pin SHOULD be retained, so that it can be -re-activated in the future by the algorithms in . - - -
-
-
-
-
- - - -A TACK client MAY choose to perform some form of certificate verification in -addition to TACK processing. When combining certificate verification and TACK -processing, the TACK processing described in SHALL -be followed, with the exception that TACK processing MAY be terminated early -(or skipped) if some fatal certificate error is discovered. - - - - -If TACK processing and certificate verification both complete without a fatal -error, the client SHALL apply some policy to decide whether to accept the -connection. The policy is up to the client. An example policy would be to -accept the connection only if it passes certificate verification and is not -rejected by a pin, or if the user elects to "connect anyway" despite -certificate and/or pin failures. - - - -
- -
- - - -In addition to the hostname-based pinning described in , some applications may require "application-specific -pins", where an application-layer name is pinned to a TACK key. For example, -an SMTP MTA may wish to authenticate receiving MTAs by pinning email domains -to the receiving MTAs' TACK keys. - - - - -Application-specific pins may require redefinition of the name record's "name" -field, the "relevant name" for the TLS connection, and the "pin activation" -signal. With these items redefined, the client processing rules in may be reused. - - - -Note that a server using application-specific pins is still subject to -hostname pins, and a client MAY apply either or both forms of pinning. - - -The specification of application-specific pinning for particular applications -is outside the scope of this document. - - - -
-
- - -
- - -A "TACK ID" MAY be used to represent a TACK public key to users in a form that -is relatively easy to compare and transcribe. A TACK ID consists of the first -25 characters from the base32 encoding of SHA256(public_key), split into 5 -groups of 5 characters separated by periods. Base32 encoding is as specified -in , except lowercase is used. - - - -Example TACK IDs: - - - - quxiz.kpldu.uuedc.j5znm.7mqst - a334f.bt7ts.ljb3b.y24ij.6zhwm - ebsx7.z22qt.okobu.ibhut.xzdny - - - -
- -
-
- - - - - -All servers that are pinned to a single TACK key are able to impersonate each -other, since clients will perceive their TACKs as equivalent. Thus, TACK keys -SHOULD NOT be reused with different hostnames unless these hostnames are -closely related. Examples where it would be safe to reuse a TACK key are -hostnames aliased to the same host, hosts sharing the same TLS key, or -hostnames for a group of near-identical servers. - - - - - -A TLS server may be referenced by multiple hostnames. Clients may pin any of -these hostnames. Server operators should be careful when using such DNS -aliases that hostnames are not pinned inadvertently. - - - - - -To revoke older generations of TACKs, the server operator SHOULD first provide -all servers with a new generation of TACKs, and only then provide servers with -new TACKs containing the new min_generation. Otherwise, a client may receive a -min_generation update from one server but then try to contact an -older-generation server which has not yet been updated. - - - -It is convenient to set the TACK expiration equal to the end-entity -certificate expiration, so that the TACK and certificate may both be replaced -at the same time. Alternatively, short-lived TACKs may be used so that a -compromised TLS private key has limited value to an attacker. - - - -A break signature only needs to be published for a time interval equal to the -maximum active period of any affected pins. For example, if a TACK has been only -been published on a website for 24 hours, to remove the TACK only requires -publishing the break signature for 24 hours. - - - - -Pin activation SHOULD only be enabled once all TLS servers sharing the same -hostname have a TACK. Otherwise, a client may activate a pin by contacting one -server, then contact a different server at the same hostname that does not yet -have a TACK. - -The pin_activation field can be used to phase -out TACKs for a hostname. If all servers at a hostname disable pin activation, -all existing pins for the hostname will eventually become inactive, at which -point the servers' TACKs can be removed. - - - - - - - -
-
- - - - -It is possible for a client to maintain a pin store based entirely on its own -TLS connections. However, such a client runs the risk of creating incorrect -pins, failing to keep its pins active, or failing to receive revocation -information (min_generation updates and break signatures). Clients are advised -to collaborate so that pin data can be aggregated and shared. This will -require additional protocols outside the scope of this document. - - - -A client SHOULD take measures to prevent TACKs from being erroneously rejected -due to an inaccurate client clock. Such methods MAY include using time -synchronization protocols such as NTP , or accepting -seemingly-expired TACKs if they expired less than T minutes ago, where T is a -"tolerance bound" set to the client's maximum expected clock error. - - - - - -
-
- -
-
- - -All servers pinned to the same TACK key can impersonate each other (see ). Think carefully about this risk if using the same TACK -key for multiple hostnames. - - - -Make backup copies of the TACK private key and keep all copies in secure -locations where they can't be compromised. - - - - -A TACK private key MUST NOT be used to perform any non-TACK cryptographic -operations. For example, using a TACK key for email encryption, code-signing, -or any other purpose MUST NOT be done. - - - -HTTP cookies set by a pinned host can be stolen by a -network attacker who can forge web and DNS responses so as to cause a client -to send the cookies to a phony subdomain of the pinned host. To prevent this, -TACK HTTPS Servers SHOULD set the "secure" attribute and omit the "domain" -attribute on all security-sensitive cookies, such as session cookies. These -settings tell the browser that the cookie should only be presented back to the -originating host (not its subdomains), and should only be sent over HTTPS (not -HTTP) . - - -
- -
- - - -A TACK pin store may contain private details of the client's connection -history. An attacker may be able to access this information by hacking or -stealing the client. Some information about the client's connection history -could also be gleaned by observing whether the client accepts or rejects -connections to phony TLS servers without correct TACKs. To mitigate these -risks, a TACK client SHOULD allow the user to edit or clear the pin store. - - - - - -Aside from rejecting TLS connections, clients SHOULD NOT take any actions -which would reveal to a network observer the state of the client's pin store, -as this would allow an attacker to know in advance whether a -"man-in-the-middle" attack on a particular TLS connection will succeed or be -detected. - - - - - -An attacker may attempt to flood a client with spurious TACKs for different -hostnames, causing the client to delete old pins to make space for new ones. -To defend against this, clients SHOULD NOT delete active pins to make space -for new pins. Clients instead SHOULD delete inactive pins. If there are no -inactive pins to delete, then the pin store is full and there is no space for -new pins. To select an inactive pin for deletion, the client SHOULD delete the -pin with the oldest "active_period_end". - - -
- -
- - - -If the need arises for TACKs using different cryptographic algorithms (e.g., -if SHA256 or ECDSA are shown to be weak), a "v2" version of TACKs could be -defined, requiring assignment of a new TLS Extension number. TACKs as defined -in this document would then be known as "v1" TACKs. - - -
- -
- -
-
- - -IANA is requested to add an entry to the existing TLS ExtensionType registry, -defined in , for tack(TBD) as defined in this document. - - -
- -
-
- - -Valuable feedback has been provided by Adam Langley, Chris Palmer, Nate -Lawson, and Joseph Bonneau. - -
- -
- - - - - - - Secure Hash Standard - - National Institute of Standards and Technology - - - - - - - - -Digital Signature Standard - -National Institute of Standards and Technology - - - - - - - - - - - - - - - - - - -
From 4bf90a93200f5a037eab28c3f672e6ce8f870669 Mon Sep 17 00:00:00 2001 From: Trevor Date: Tue, 22 May 2012 10:20:09 -0700 Subject: [PATCH 68/94] oops adding missing file. --- tack/crypto/openssl/OpenSSLException.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 tack/crypto/openssl/OpenSSLException.py diff --git a/tack/crypto/openssl/OpenSSLException.py b/tack/crypto/openssl/OpenSSLException.py new file mode 100644 index 0000000..41c408f --- /dev/null +++ b/tack/crypto/openssl/OpenSSLException.py @@ -0,0 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +class OpenSSLException(Exception): + def __init__(self, args): + Exception.__init__(self, args) \ No newline at end of file From fea29c34804f5c11b91de2da63b908e3c417d010 Mon Sep 17 00:00:00 2001 From: Trevor Date: Tue, 22 May 2012 11:56:51 -0700 Subject: [PATCH 69/94] Touchup README and setup.py for release. --- README | 2 +- setup.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/README b/README index 21b5e35..8badc9a 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Tackpy version 0.9.7 Feb 23 2012 +Tackpy version 0.9.7 May 22 2012 ============================================================================ Licenses/Acknowledgements diff --git a/setup.py b/setup.py index ce846d4..617ffd3 100755 --- a/setup.py +++ b/setup.py @@ -15,9 +15,8 @@ setup( name="tackpy", version="0.9.7", - author="Trevor Perrin", - author_email="tackpy@trevp.net", - url="https://github.com/trevp/TACKpy", + author="Trevor Perrin,Moxie Marlinspike", + url="http://tack.io", description="Tackpy implements TACK in python", license="public domain", scripts=["tack/tack"], @@ -29,7 +28,7 @@ "testdata/serverX509Cert.der"])] ) -print "Cleaning up..." +print("Cleaning up...") if os.path.exists("build/"): shutil.rmtree("build/") From 58b996a89054695d9be7abcad8fd8a8154ae59f6 Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 6 Jul 2012 13:25:26 -0700 Subject: [PATCH 70/94] Rename "TACK ID" to "fingerprint". --- tack/structures/Tack.py | 2 +- tack/structures/TackBreakSig.py | 2 +- tack/structures/TackKeyFile.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index 569c447..33d0cb8 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -90,7 +90,7 @@ def __str__(self): Used by the "TACK view" command to display TACK objects.""" s =\ - """TACK ID = %s + """fingerprint = %s min_generation = %d generation = %d expiration = %s diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index 9fbff3a..a076b13 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -72,4 +72,4 @@ def verifySignature(self): def __str__(self): """Return a readable string describing this TACK_Break_Sig. Used by the "TACK view" command to display TACK objects.""" - return "Breaks TACK ID = %s\n" % self.getTackId() + return "Breaks fingerprint = %s\n" % self.getTackId() diff --git a/tack/structures/TackKeyFile.py b/tack/structures/TackKeyFile.py index c685e5a..0ea3e36 100644 --- a/tack/structures/TackKeyFile.py +++ b/tack/structures/TackKeyFile.py @@ -125,5 +125,5 @@ def _deriveKeys(self, password, salt, iter_count): return encKey, authKey def __str__(self): - return """TACK ID = %s\n""" % str(self.public_key) + return """fingerprint = %s\n""" % str(self.public_key) From ad96e4d9c5cc2c5427632830b352079a6d1da288 Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 6 Jul 2012 13:42:30 -0700 Subject: [PATCH 71/94] Rename "fingerprint" to "key fingerprint", "pin_activation" to "activation_flag". --- tack/structures/Tack.py | 10 +++++----- tack/structures/TackBreakSig.py | 2 +- tack/structures/TackExtension.py | 18 +++++++++--------- tack/structures/TackKeyFile.py | 2 +- tack/tls/TlsCertificate.py | 4 ++-- tack/util/Util.py | 2 +- 6 files changed, 19 insertions(+), 19 deletions(-) diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index 33d0cb8..cbddd2e 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -90,11 +90,11 @@ def __str__(self): Used by the "TACK view" command to display TACK objects.""" s =\ - """fingerprint = %s -min_generation = %d -generation = %d -expiration = %s -target_hash = %s\n""" %\ + """key fingerprint = %s +min_generation = %d +generation = %d +expiration = %s +target_hash = %s\n""" %\ (self.getTackId(), self.min_generation, self.generation, diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py index a076b13..2482439 100644 --- a/tack/structures/TackBreakSig.py +++ b/tack/structures/TackBreakSig.py @@ -72,4 +72,4 @@ def verifySignature(self): def __str__(self): """Return a readable string describing this TACK_Break_Sig. Used by the "TACK view" command to display TACK objects.""" - return "Breaks fingerprint = %s\n" % self.getTackId() + return "Breaks key fingerprint = %s\n" % self.getTackId() diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index 9bc8827..d5fd4b8 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -19,23 +19,23 @@ def __init__(self, data=None): TlsStructure.__init__(self, data) self.tack = self._parseTack() self.break_sigs = self._parseBreakSigs() - self.pin_activation = self.getInt(1) + self.activation_flag = self.getInt(1) - if self.pin_activation not in TackActivation.ALL: - raise SyntaxError("Bad pin_activation value") + if self.activation_flag not in TackActivation.ALL: + raise SyntaxError("Bad activation_flag value") if self.index != len(data): raise SyntaxError("Excess bytes in TACK_Extension") @classmethod - def create(cls, tack, break_sigs, pin_activation): + def create(cls, tack, break_sigs, activation_flag): tackExtension = cls() tackExtension.tack = tack tackExtension.break_sigs = break_sigs - if not pin_activation: - tackExtension.pin_activation = TackActivation.DISABLED + if not activation_flag: + tackExtension.activation_flag = TackActivation.DISABLED else: - tackExtension.pin_activation = TackActivation.ENABLED + tackExtension.activation_flag = TackActivation.ENABLED return tackExtension @@ -55,7 +55,7 @@ def serialize(self): else: w.add(0, 2) - w.add(self.pin_activation, 1) + w.add(self.activation_flag, 1) return w.getBytes() @@ -114,6 +114,6 @@ def __str__(self): for break_sig in self.break_sigs: result += str(break_sig) - result += "pin_activation = %s\n" % TackActivation.STRINGS[self.pin_activation] + result += "activation_flag = %s\n" % TackActivation.STRINGS[self.activation_flag] return result diff --git a/tack/structures/TackKeyFile.py b/tack/structures/TackKeyFile.py index 0ea3e36..1e58520 100644 --- a/tack/structures/TackKeyFile.py +++ b/tack/structures/TackKeyFile.py @@ -125,5 +125,5 @@ def _deriveKeys(self, password, salt, iter_count): return encKey, authKey def __str__(self): - return """fingerprint = %s\n""" % str(self.public_key) + return """key fingerprint = %s\n""" % str(self.public_key) diff --git a/tack/tls/TlsCertificate.py b/tack/tls/TlsCertificate.py index 9ec5bda..8c7dc97 100644 --- a/tack/tls/TlsCertificate.py +++ b/tack/tls/TlsCertificate.py @@ -171,8 +171,8 @@ def serializeAsPem(self): def __str__(self): s =\ - """key_sha256 = %s -notAfter = %s + """key_sha256 = %s +notAfter = %s """ % (\ Util.writeBytes(self.key_sha256), Time.posixTimeToStr(self.notAfter, True)) diff --git a/tack/util/Util.py b/tack/util/Util.py index da9ee2e..3376dd3 100644 --- a/tack/util/Util.py +++ b/tack/util/Util.py @@ -16,7 +16,7 @@ def writeBytes(b): retVal += s[:32] s = s[32:] if len(s): - retVal += "\n " + retVal += "\n " return retVal @staticmethod From 978a0631d74b4593c68940ab49d53ccdc7bfacfd Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 6 Jul 2012 13:58:39 -0700 Subject: [PATCH 72/94] Updating tackpy README. --- README | 93 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 47 insertions(+), 46 deletions(-) diff --git a/README b/README index 8badc9a..ed90c0c 100644 --- a/README +++ b/README @@ -42,44 +42,44 @@ Create a TACK key: 1) Run "tack genkey > KEY.pem" (replace "KEY" with a specific name) 2) Back up the key file where it won't be lost or stolen. -If a hostname is using TACK, each server at that hostname must have a TACK +If a hostname is using TACK, each server at that hostname must have a tack that signs the public key in the server's certificate. To create and deploy -these TACKs, do the following: +these tacks, do the following: -Create a TACK for a certificate's public key: +Create a tack for a certificate's public key: 1) Run "tack sign -k KEY.pem -c CERT > TACK.pem". -Deploy TACKs to a hostname - 1) Deploy TACKs to each server at the hostname. - - Apache: Set "SSLTackFile" to a TACK file. - 2) Once all servers are serving a TACK, activate pinning on each server. - - Apache: Set "SSLTackPinActivation On". - 3) Test the site (if there are problems, see "Removing TACKs"). - 4) Whenever you change a server's certificate, you must replace its TACK. +Deploy tacks to a hostname + 1) Deploy tacks to each server at the hostname. + - Apache: Set "SSLTackFile" to a tack file. + 2) Set the activation flag on each server. + - Apache: Set "SSLTackActivationFlag On". + 3) Test the site (if there are problems, see "Removing tacks"). + 4) Whenever you change a server's certificate, you must replace its tack. -Removing TACKs +Removing tacks =============== -Disabling pin activation -------------------------- +Disabling the activation flag +------------------------------ If you wish to stop using TACK for a hostname but can tolerate a "waiting -period" before the TACKs are removed, simply disable pin activation at all -servers for that hostname (Apache: "SSLTackPinActivation Off"). Then wait +period" before the tacks are removed, simply disable the activation flag at +all servers for that hostname (Apache: "SSLTackActivationFlag Off"). Then wait for all existing client pins to become inactive. -The waiting period required is equal to the length of time that pin activation -has been enabled for any servers at the hostname, or a maximum of 30 days. -Once the waiting period is elapsed, all TACKs for the hostname can be safely -removed. +The waiting period required is equal to the length of time that the activation +flag has been enabled for any servers at the hostname, or a maximum of 30 +days. Once the waiting period is elapsed, all tacks for the hostname can be +safely removed. -(For example: If you start using a TACK for "example.com", then decide to -disable pin activation after one day, you can remove the TACK at the end of -the second day.) +(For example: If you start using a tack for "example.com", then decide to +disable the activation flag after one day, you can remove the tack at the end +of the second day.) Break signatures ----------------- -If you wish to abruptly stop publishing a TACK for a hostname, or abruptly +If you wish to abruptly stop publishing a tack for a hostname, or abruptly change the hostname's TACK key, or signal that a TACK key has been compromised, then you may publish a "break signature" from the TACK key as part of the TLS connection. @@ -91,64 +91,65 @@ previous section). A break signature from a TACK key causes any client who encounters it to discard all pins involving the TACK key. Thus, once a break signature is -published for a TACK key, all existing pins and TACKS for the TACK key cease +published for a TACK key, all existing pins and tacks for the TACK key cease providing security. A server can have up to eight break signatures. However, keep in mind that break signatures add to TLS handshake overhead, so are best avoided. -Create a break signature for a TACK: +Create a break signature for a TACK key: 1) Run "tack break -k KEY.pem > TACK_Break_Sig.pem" 2) Add the break signature to your web server. - Apache: Set "SSLTACKBreakSigsFile" to a file with break signatures. + Advanced uses ============== -Revoking older generations of a TACK +Revoking older generations of a tack ------------------------------------- If a server's TLS key (not its TACK key) has been compromised and you are -switching to a new TLS key, you may revoke the TACK for the old key by "-m +switching to a new TLS key, you may revoke the tack for the old key by "-m " in the "sign" command. is a number from -0-255 that is larger than the generation of the TACK you wish to revoke. +0-255 that is larger than the generation of the tack you wish to revoke. -Clients who encounter the new TACK will reject older generation TACKs from +Clients who encounter the new tack will reject older generation tacks from then on. Prior to publishing a new you should replace all -your TACKs with this generation number (or higher) by signing with "-g +your tacks with this generation number (or higher) by signing with "-g ". -For example: By default TACKs have generation=0, so the first time you use +For example: By default tacks have generation=0, so the first time you use this capability you will want to set "-m1" after pushing out a new set of -TACKs signed with "-g1". If you use it a second time, you will set "-m2", and +tacks signed with "-g1". If you use it a second time, you will set "-m2", and so on. Security Consideration: This only provides protection if clients receive the new min_generation. For a more robust defense against TLS key compromise, -consider using short-lived TACKs. +consider using short-lived tacks. -Short-lived TACKs +Short-lived tacks ------------------ -Every TACK contains a signature covering a TLS public key. The TLS key is -contained in a certificate. By default the TACK is set to expire at the same -time as the certificate, and must be replaced by an updated TACK at that +Every tack contains a signature covering a TLS public key. The TLS key is +contained in a certificate. By default the tack is set to expire at the same +time as the certificate, and must be replaced by an updated tack at that point. -If you shorten the TACK's expiration time, then a compromised TLS key will -become unusable to an attacker once the TACK expires. For example, every day -at midnight you could deploy a new TACK that expires within 48 hours. +If you shorten the tack's expiration time, then a compromised TLS key will +become unusable to an attacker once the tack expires. For example, every day +at midnight you could deploy a new tack that expires within 48 hours. -A good way to handle short-lived TACKs is to generate a batch of them and -store the TACKs on a secure system that distributes them to servers. This way, -you do not have to use your TACK key to sign new TACKs frequently. +A good way to handle short-lived tacks is to generate a batch of them and +store the tacks on a secure system that distributes them to servers. This way, +you do not have to use your TACK key to sign new tacks frequently. -You can generate a batch of TACKs with the "-n NUM@INTERVAL" argument to -"sign", specifying the number of TACKs and the interval between their +You can generate a batch of tacks with the "-n NUM@INTERVAL" argument to +"sign", specifying the number of tacks and the interval between their expiration times. The "-o" argument is taken as a filename prefix, and the "-e" time is used as the first expiration time. Example: tack sign -k KEY.pem -c CERT -n 365@1d -e 2013-01-02Z -o T1 -produces 365 TACKs, one expiring at midnight (UTC) each day of 2013: +produces 365 tacks, one expiring at midnight (UTC) each day of 2013: T1_0000.pem T1_0001.pem T1_0002.pem From 8c903320b9210f8c61f33052b25889c6002c6d66 Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 6 Jul 2012 14:23:06 -0700 Subject: [PATCH 73/94] Renaming Readme title to "Pin deactivation". --- README | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README b/README index ed90c0c..4ebf3a7 100644 --- a/README +++ b/README @@ -61,8 +61,8 @@ Deploy tacks to a hostname Removing tacks =============== -Disabling the activation flag ------------------------------- +Pin deactivation +----------------- If you wish to stop using TACK for a hostname but can tolerate a "waiting period" before the tacks are removed, simply disable the activation flag at all servers for that hostname (Apache: "SSLTackActivationFlag Off"). Then wait From 2a5b193d5132591918c26eeda0a30587b7e9306f Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 6 Jul 2012 15:12:17 -0700 Subject: [PATCH 74/94] Set tack expiration to cert expiration plus 30 days. --- tack/commands/SignCommand.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index af66a43..7b4d500 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -94,7 +94,8 @@ def _getExpiration(self, certificate): expiration = self._getOptionValue("-e") if expiration is None and self._getNumArg() is None: - return int(math.ceil(certificate.notAfter / 60.0)) + # Set expiration based on cert + 30 days (per spec's advice) + return int(math.ceil(certificate.notAfter / 60.0)) + (30*24*60) else: try: return Time.parseTimeArg(expiration) From c35353eaf946d249e5a082f59f096ae3883f9564 Mon Sep 17 00:00:00 2001 From: Trevor Date: Wed, 11 Jul 2012 17:23:33 -0700 Subject: [PATCH 75/94] Adding pack/unpack commands, bit of cleanup on commands. --- Makefile | 8 +-- tack.py | 10 +-- tack/commands/BreakCommand.py | 7 +- tack/commands/CertificateCommand.py | 102 ---------------------------- tack/commands/Command.py | 86 +++++++++++++++++++++-- tack/commands/GenerateKeyCommand.py | 5 +- tack/commands/HelpCommand.py | 8 ++- tack/commands/PackCommand.py | 58 ++++++++++++++++ tack/commands/SignCommand.py | 38 ++--------- tack/commands/UnpackCommand.py | 38 +++++++++++ tack/commands/ViewCommand.py | 9 ++- tack/structures/TackExtension.py | 9 +++ 12 files changed, 216 insertions(+), 162 deletions(-) delete mode 100644 tack/commands/CertificateCommand.py create mode 100644 tack/commands/PackCommand.py create mode 100644 tack/commands/UnpackCommand.py diff --git a/Makefile b/Makefile index 84b643d..236302a 100644 --- a/Makefile +++ b/Makefile @@ -44,13 +44,13 @@ test: $(EXEC) break -k $(TESTDIR)/TACK_Key1.pem -p asdf > $(TESTDIR)/TACK_Break_Sig1.pem $(EXEC) b -x -k $(TESTDIR)/TACK_Key2.pem -p asdf -o $(TESTDIR)/TACK_Break_Sig2.pem cat $(TESTDIR)/TACK_Break_Sig1.pem $(TESTDIR)/TACK_Break_Sig2.pem > $(TESTDIR)/TACK_Break_Sigs.pem - $(EXEC) cert -i $(TESTDIR)/TACK3.pem > $(TESTDIR)/TACK_Cert3.pem - $(EXEC) cert -i $(TESTDIR)/TACK4.pem -b $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_Cert4.pem - $(EXEC) cert -i $(TESTDIR)/TACK_Cert3.pem > $(TESTDIR)/TACK3_FromCert.pem + $(EXEC) pack -t $(TESTDIR)/TACK3.pem > $(TESTDIR)/TACK_Ext3.pem + $(EXEC) pack -t $(TESTDIR)/TACK4.pem -b $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_Ext4.pem + $(EXEC) unpack -E $(TESTDIR)/TACK_Ext4.pem -o $(TESTDIR)/TACK_Ext4_Unpack.txt $(EXEC) view $(TESTDIR)/TACK_Key1.pem > $(TESTDIR)/TACK_View_Key1.txt cat $(TESTDIR)/TACK1.pem | $(EXEC) view - > $(TESTDIR)/TACK_View1.txt $(EXEC) v $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_View_Break_Sigs.txt $(EXEC) v $(CERT1) > $(TESTDIR)/TACK_View_Cert1.txt cat $(CERT2) | $(EXEC) v - > $(TESTDIR)/TACK_View_Cert2.txt - $(EXEC) v $(TESTDIR)/TACK_Cert3.pem > $(TESTDIR)/TACK_View_TACK_Cert3.txt + $(EXEC) v $(TESTDIR)/TACK_Ext3.pem > $(TESTDIR)/TACK_View_TACK_Ext3.txt @echo OK diff --git a/tack.py b/tack.py index cb8b114..b4b592a 100755 --- a/tack.py +++ b/tack.py @@ -8,11 +8,12 @@ import sys from tack.commands.BreakCommand import BreakCommand -from tack.commands.CertificateCommand import CertificateCommand from tack.commands.GenerateKeyCommand import GenerateKeyCommand from tack.commands.HelpCommand import HelpCommand from tack.commands.SignCommand import SignCommand from tack.commands.ViewCommand import ViewCommand +from tack.commands.PackCommand import PackCommand +from tack.commands.UnpackCommand import UnpackCommand from tack.crypto.openssl.OpenSSL import openssl openssl.initialize() @@ -30,8 +31,9 @@ ViewCommand(sys.argv[2:]).execute() elif sys.argv[1] == "help"[:len(sys.argv[1])]: HelpCommand(sys.argv[2:]).execute() - # Special hidden command: - elif sys.argv[1] == "cert"[:len(sys.argv[1])]: - CertificateCommand(sys.argv[2:]).execute() + elif sys.argv[1] == "pack"[:len(sys.argv[1])]: + PackCommand(sys.argv[2:]).execute() + elif sys.argv[1] == "unpack"[:len(sys.argv[1])]: + UnpackCommand(sys.argv[2:]).execute() else: HelpCommand.printGeneralUsage("Unknown command: %s" % sys.argv[1]) diff --git a/tack/commands/BreakCommand.py b/tack/commands/BreakCommand.py index f0458a8..61c3c5e 100644 --- a/tack/commands/BreakCommand.py +++ b/tack/commands/BreakCommand.py @@ -14,15 +14,12 @@ def __init__(self, argv): Command.__init__(self, argv, "pok", "vx") self.password = self.getPassword() self.outputFile, self.outputFileName = self.getOutputFile() - self.keyfile = self.getKeyFile(self.getPassword()) + self.keyfile = self.getKeyFile(self.getPassword(), mandatory=True) def execute(self): breakSig = TackBreakSig.create(self.keyfile.getPublicKey(), self.keyfile.getPrivateKey()) self.outputFile.write(self.addPemComments(breakSig.serializeAsPem())) - - if self.isVerbose(): - self.writeCryptoVersion() - sys.stderr.write(str(breakSig)) + self.printVerbose(str(breakSig)) @staticmethod def printHelp(): diff --git a/tack/commands/CertificateCommand.py b/tack/commands/CertificateCommand.py deleted file mode 100644 index ace0e0d..0000000 --- a/tack/commands/CertificateCommand.py +++ /dev/null @@ -1,102 +0,0 @@ -# Authors: -# Trevor Perrin -# Moxie Marlinspike -# -# See the LICENSE file for legal information regarding use of this file. - -import sys -from tack.commands.Command import Command -from tack.structures.Tack import Tack -from tack.structures.TackActivation import TackActivation -from tack.structures.TackExtension import TackExtension -from tack.tls.TlsCertificate import TlsCertificate -from tack.util.PEMDecoder import PEMDecoder - -class CertificateCommand(Command): - - def __init__(self, argv): - Command.__init__(self, argv, "oib", "v") - - self.outputFile, self.outputFileName = self.getOutputFile() - (self.inputTack, self.inputCertificate) = self._getInputFile() - self.breakSignatures = self._getBreakSignatures() - - def execute(self): - if self.inputTack is not None: - tackExtension = TackExtension.create(self.inputTack, self.breakSignatures, - TackActivation.DISABLED) - - tlsCertificate = TlsCertificate.create(tackExtension) - self.outputFile.write(tlsCertificate.serializeAsPem()) - - if self.isVerbose(): - sys.stderr.write(str(tackExtension)) - - elif self.inputCertificate is not None: - if self.breakSignatures is not None: - self.printError("Invalid arguments: break sigs with TACK cert.") - - s = "" - if self.inputCertificate.tackExt: - if self.inputCertificate.tackExt.tack: - s += self.inputCertificate.tackExt.tack.serializeAsPem() - if self.inputCertificate.tackExt.break_sigs: - for bs in self.inputCertificate.tackExt.break_sigs: - s += bs.serializeAsPem() - - self.outputFile.write(s) - - if self.isVerbose(): - sys.stderr.write(str(self.inputCertificate)) - else: - assert(False) - - def _getBreakSignatures(self): - fileName = self._getOptionValue("-b") - - if fileName is None: - return None - - try: - contents = open(fileName, "r").read() - except IOError: - self.printError("Error opening break signature: %s" % fileName) - - def _getInputFile(self): - fileName = self._getOptionValue("-i") - if fileName is None: - self.printError("-i missing (Certificate or TACK)") - try: - - text, binary = self._readFileTextAndBinary(fileName) - if text: - pem = PEMDecoder(text) - if pem.containsEncoded("TACK"): - return (Tack.createFromPem(text), None) - elif pem.containsEncoded("CERTIFICATE"): - return (None, TlsCertificate.createFromPem(text)) - else: - self.printError("Unrecognized input file: %s" % fileName) - else: - return (None, TlsCertificate(binary)) - - except IOError: - self.printError("Error opening input file: %s" % fileName) - except SyntaxError: - self.printError("Error parsing input file: %s" % fileName) - - @staticmethod - def printHelp(): - print( -"""Creates a TACK certificate with the input TACK and optional Break Sigs. - -(Alternatively, if input is a TACK certificate, writes out the TACK and/or -Break Signatures as PEM files). - -cert -i (TACK or CERT) - -Optional arguments: - -v : Verbose - -b BREAKSIGS : Include Break Signatures from this file. - -o FILE : Write the output to this file (instead of stdout) -""") diff --git a/tack/commands/Command.py b/tack/commands/Command.py index a9d4600..16bd32d 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -9,6 +9,10 @@ import sys import time from tack.structures.TackKeyFile import TackKeyFile +from tack.structures.TackExtension import TackExtension +from tack.structures.Tack import Tack +from tack.structures.TackBreakSig import TackBreakSig +from tack.tls.TlsCertificate import TlsCertificate from tack.util.Time import Time from tack.crypto.openssl.OpenSSL import openssl as o from tack.compat import bytesToStr, readStdinBinary @@ -24,9 +28,16 @@ def __init__(self, argv, options, flags, allowArgRemainder=False): self.values, self.argRemainder = getopt.getopt(argv, self.options + self.flags) if not allowArgRemainder and self.argRemainder: self.printError("Too many arguments: %s" % self.argRemainder) + + # Handle flags if self._containsOption("-x"): o.setInitError("requested from command line") o.enabled = False + if self._containsOption("-v"): + self.verbose = True + else: + self.verbose = False + except getopt.GetoptError as e: self.printError(e) @@ -39,19 +50,19 @@ def getCryptoVersion(): return cryptoVersion def writeCryptoVersion(self): - sys.stderr.write("Crypto = %s\n" % Command.getCryptoVersion()) - - def isVerbose(self): - return self._containsOption("-v") + sys.stderr.write("Crypto = %s\n" % Command.getCryptoVersion()) def getPassword(self): return self._getOptionValue("-p") - def getKeyFile(self, password): + def getKeyFile(self, password, mandatory): keyPemFile = self._getOptionValue("-k") if not keyPemFile: - self.printError("-k missing (TACK Key)") + if mandatory: + self.printError("-k missing (TACK Key)") + else: + return None if not password: password = self._promptPassword() @@ -75,6 +86,65 @@ def getKeyFile(self, password): except IOError: self.printError("Error opening TACK Key File: %s" % keyPemFile) + def getBreakSignatures(self): + fileName = self._getOptionValue("-b") + if fileName is None: + return None + try: + contents = open(fileName, "r").read() + return TackBreakSig.createFromPemList(contents) + except IOError: + self.printError("Error opening break signatures: %s" % fileName) + except SyntaxError: + self.printError("Error parsing break signatures: %s" % fileName) + + def getTack(self): + fileName = self._getOptionValue("-t") + if fileName is None: + return None + try: + contents = open(fileName, "r").read() + return Tack.createFromPem(contents) + except IOError: + self.printError("Error opening tack: %s" % fileName) + except SyntaxError: + self.printError("Error parsing tack: %s" % fileName) + + def getTackExtension(self, mandatory): + fileName = self._getOptionValue("-E") + if fileName is None: + if mandatory: + self.printError("-E missing (TACK Extension)") + else: + return None + try: + contents = open(fileName, "r").read() + return TackExtension.createFromPem(contents) + except IOError: + self.printError("Error opening extension: %s" % fileName) + except SyntaxError: + self.printError("Error parsing extension: %s" % fileName) + + def getCertificate(self, mandatory): + certificateFile = self._getOptionValue("-c") + + if not certificateFile: + if mandatory: + self.printError("-c missing (Certificate)") + else: + return None + try: + if certificateFile == "-": + # Read as binary + certificateBytes = readStdinBinary() + else: + certificateBytes = bytearray(open(certificateFile, "rb").read()) + + return TlsCertificate.createFromBytes(certificateBytes) + except SyntaxError: + self.printError("Certificate malformed: %s" % certificateFile) + except IOError: + self.printError("Error opening certificate: %s" % certificateFile) def getOutputFile(self): output = None @@ -138,3 +208,7 @@ def printError(self, error): sys.stderr.write("ERROR: %s\n" % error) sys.exit(-1) + def printVerbose(self, s): + if self.verbose: + self.writeCryptoVersion() + sys.stderr.write(s) diff --git a/tack/commands/GenerateKeyCommand.py b/tack/commands/GenerateKeyCommand.py index 6dac86a..3938ac7 100644 --- a/tack/commands/GenerateKeyCommand.py +++ b/tack/commands/GenerateKeyCommand.py @@ -22,10 +22,7 @@ def execute(self): public_key, private_key = ECGenerator.generateECKeyPair() keyFile = TackKeyFile.create(public_key, private_key, password) self.outputFile.write(self.addPemComments(keyFile.serializeAsPem())) - - if self.isVerbose(): - self.writeCryptoVersion() - sys.stderr.write(str(keyFile)) + self.printVerbose(str(keyFile)) def _getPasswordWithPrompt(self): if not self.password: diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index 61816a7..2d2d413 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -5,19 +5,20 @@ # See the LICENSE file for legal information regarding use of this file. import sys -from tack.commands.CertificateCommand import CertificateCommand from tack.version import __version__ -from tack.commands.BreakCommand import BreakCommand from tack.commands.Command import Command from tack.commands.GenerateKeyCommand import GenerateKeyCommand from tack.commands.SignCommand import SignCommand +from tack.commands.BreakCommand import BreakCommand from tack.commands.ViewCommand import ViewCommand +from tack.commands.PackCommand import PackCommand +from tack.commands.UnpackCommand import UnpackCommand class HelpCommand(Command): COMMANDS = {"genkey" : GenerateKeyCommand, "sign" : SignCommand, "break" : BreakCommand, "view" : ViewCommand, - "cert" : CertificateCommand} + "pack" : PackCommand, "unpack" : UnpackCommand} def __init__(self, argv): Command.__init__(self, argv, "", "", allowArgRemainder=True) @@ -54,5 +55,6 @@ def printGeneralUsage(message=None): break -k KEY view FILE help COMMAND +("pack" and "unpack" are advanced commands for debugging) """ % (__version__, Command.getCryptoVersion())) sys.exit(-1) diff --git a/tack/commands/PackCommand.py b/tack/commands/PackCommand.py new file mode 100644 index 0000000..b09c22f --- /dev/null +++ b/tack/commands/PackCommand.py @@ -0,0 +1,58 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +import sys +from tack.commands.Command import Command +from tack.structures.TackActivation import TackActivation +from tack.structures.TackExtension import TackExtension +from tack.tls.TlsCertificate import TlsCertificate + +class PackCommand(Command): + + def __init__(self, argv): + Command.__init__(self, argv, "otba", "vx") + self.outputFile, self.outputFileName = self.getOutputFile() + self.tack = self.getTack() + self.breakSignatures = self.getBreakSignatures() + self.activationFlag = self._getActivationFlag() + + def execute(self): + tackExtension = TackExtension.create(self.tack, self.breakSignatures, + self.activationFlag) + + #tlsCertificate = TlsCertificate.create(tackExtension) + self.outputFile.write(tackExtension.serializeAsPem()) + self.printVerbose(str(tackExtension)) + + def _getActivationFlag(self): + activation_flag = self._getOptionValue("-a") + + if activation_flag is None: + return 0 + + try: + activation_flag = int(activation_flag) # Could raise ValueError + if activation_flag < 0 or activation_flag > 1: + raise ValueError() + except ValueError: + self.printError("Bad activation_flag: %s" % activation_flag) + + return activation_flag + + + @staticmethod + def printHelp(): + print( +"""Takes the input Tack, Break Sigs, and Activation Flag, and produces a +TACK_Extension from them. + +Optional arguments: + -v : Verbose + -t TACK : Include Tack from this file. + -b BREAKSIGS : Include Break Signatures from this file. + -a FLAG : Activation flag (0 or 1) + -o FILE : Write the output to this file (instead of stdout) +""") diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index 7b4d500..023c558 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -10,18 +10,16 @@ from tack.compat import readStdinBinary from tack.commands.Command import Command from tack.structures.Tack import Tack -from tack.tls.TlsCertificate import TlsCertificate from tack.util.Time import Time class SignCommand(Command): def __init__(self, argv): Command.__init__(self, argv, "kcopmgen", "vx") - self.password = self.getPassword() - self.keyfile = self.getKeyFile(self.password) + self.keyfile = self.getKeyFile(self.password, mandatory=True) - self.certificate = self._getCertificate() + self.certificate = self.getCertificate(mandatory=True) self.generation = self._getGeneration() self.min_generation = self._getMinGeneration() self.expiration = self._getExpiration(self.certificate) @@ -41,10 +39,7 @@ def execute(self): self.certificate.key_sha256) self.outputFile.write(self.addPemComments(tack.serializeAsPem())) - - if self.isVerbose(): - self.writeCryptoVersion() - sys.stderr.write(str(tack)) + self.printVerbose(str(tack)) else: # We are signing multiple TACKs, since "-n" was specified (numTacks, interval) = self.numArg @@ -65,30 +60,9 @@ def execute(self): except IOError: self.printError("Error opening output file: %s" % outputFileName) - if self.isVerbose(): - self.writeCryptoVersion() - sys.stderr.write(str(tack)) - - self.expiration += interval + self.expiration += interval + self.printVerbose(str(tack)) - def _getCertificate(self): - certificateFile = self._getOptionValue("-c") - - if not certificateFile: - self.printError("-c missing (Certificate)") - - try: - if certificateFile == "-": - # Read as binary - certificateBytes = readStdinBinary() - else: - certificateBytes = bytearray(open(certificateFile, "rb").read()) - - return TlsCertificate.createFromBytes(certificateBytes) - except SyntaxError: - self.printError("Certificate malformed: %s" % certificateFile) - except IOError: - self.printError("Error opening certificate: %s" % certificateFile) def _getExpiration(self, certificate): expiration = self._getOptionValue("-e") @@ -175,7 +149,7 @@ def printHelp(): Or, specify a delta from current time: ("5m", "30d", "1d12h5m", "0m", etc.) If not specified, the certificate's notAfter is used. - - n NUM@INTERVAL : Generate NUM TACKs, with expiration times spaced + -n NUM@INTERVAL : Generate NUM TACKs, with expiration times spaced out by INTERVAL (see -e for delta syntax). The -o argument is used as a filename prefix, and the -e argument is used as the first expiration time. diff --git a/tack/commands/UnpackCommand.py b/tack/commands/UnpackCommand.py new file mode 100644 index 0000000..9565841 --- /dev/null +++ b/tack/commands/UnpackCommand.py @@ -0,0 +1,38 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +import sys +from tack.commands.Command import Command +from tack.structures.TackActivation import TackActivation +from tack.structures.TackExtension import TackExtension +from tack.tls.TlsCertificate import TlsCertificate + +class UnpackCommand(Command): + + def __init__(self, argv): + Command.__init__(self, argv, "oE", "vx") + self.outputFile, self.outputFileName = self.getOutputFile() + self.tackExtension = self.getTackExtension(mandatory=True) + + def execute(self): + if self.tackExtension.tack: + self.outputFile.write(self.tackExtension.tack.serializeAsPem()) + for break_sig in self.tackExtension.break_sigs: + self.outputFile.write(break_sig.serializeAsPem()) + self.printVerbose(str(self.tackExtension)) + + @staticmethod + def printHelp(): + print( +"""Takes the input TACK Extension, and writes out PEM encodings for its Tack +and Break Signatures. + + unpack -e EXTENSION + +Optional arguments: + -v : Verbose + -o FILE : Write the output to this file (instead of stdout) +""") diff --git a/tack/commands/ViewCommand.py b/tack/commands/ViewCommand.py index 6c01952..009537e 100644 --- a/tack/commands/ViewCommand.py +++ b/tack/commands/ViewCommand.py @@ -6,6 +6,7 @@ import sys from tack.commands.Command import Command +from tack.structures.TackExtension import TackExtension from tack.structures.Tack import Tack from tack.structures.TackKeyFile import TackKeyFile from tack.structures.TackBreakSig import TackBreakSig @@ -16,7 +17,6 @@ class ViewCommand(Command): def __init__(self, argv): Command.__init__(self, argv, "", "x", allowArgRemainder=True) - if len(self.argRemainder) < 1: self.printError("Missing argument: file to view") if len(self.argRemainder) > 1: @@ -35,7 +35,7 @@ def execute(self): sys.stdout.write(str(kf)) return elif decoder.containsEncoded("TACK"): - fileType = "TACK" + fileType = "Tack" tack = Tack.createFromPem(text) sys.stdout.write(str(tack)) return @@ -47,6 +47,11 @@ def execute(self): s += str(tbs) sys.stdout.write(s) return + elif decoder.containsEncoded("TACK EXTENSION"): + fileType = "TACK Extension" + tackExt = TackExtension.createFromPem(text) + sys.stdout.write(str(tackExt)) + return elif decoder.containsEncoded( "CERTIFICATE"): fileType = "Certificate" sslc = TlsCertificate.createFromPem(text) diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index d5fd4b8..9150781 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -9,6 +9,8 @@ from tack.structures.TackBreakSig import TackBreakSig from tack.tls.TlsStructure import TlsStructure from tack.tls.TlsStructureWriter import TlsStructureWriter +from tack.util.PEMDecoder import PEMDecoder +from tack.util.PEMEncoder import PEMEncoder class TackExtension(TlsStructure): @@ -27,6 +29,10 @@ def __init__(self, data=None): if self.index != len(data): raise SyntaxError("Excess bytes in TACK_Extension") + @classmethod + def createFromPem(cls, data): + return cls(PEMDecoder(data).decode("TACK EXTENSION")) + @classmethod def create(cls, tack, break_sigs, activation_flag): tackExtension = cls() @@ -59,6 +65,9 @@ def serialize(self): return w.getBytes() + def serializeAsPem(self): + return PEMEncoder(self.serialize()).encode("TACK EXTENSION") + def isEmpty(self): return not self.tack and not self.break_sigs From 9e0f5adb1ac47708d59c72e46b1220f10a5c94a2 Mon Sep 17 00:00:00 2001 From: Trevor Date: Wed, 25 Jul 2012 13:56:17 -0700 Subject: [PATCH 76/94] Add PEM comments to packed TACK Extensions. --- tack/commands/PackCommand.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tack/commands/PackCommand.py b/tack/commands/PackCommand.py index b09c22f..d7856b4 100644 --- a/tack/commands/PackCommand.py +++ b/tack/commands/PackCommand.py @@ -24,7 +24,7 @@ def execute(self): self.activationFlag) #tlsCertificate = TlsCertificate.create(tackExtension) - self.outputFile.write(tackExtension.serializeAsPem()) + self.outputFile.write(self.addPemComments(tackExtension.serializeAsPem())) self.printVerbose(str(tackExtension)) def _getActivationFlag(self): From 7b4c603c25c6abb5787435cc90cec945df365b9e Mon Sep 17 00:00:00 2001 From: Trevor Date: Mon, 13 Aug 2012 16:20:43 -0700 Subject: [PATCH 77/94] Supporting tack pairs in an Extension, needs more tests. --- tack/commands/Command.py | 8 +-- tack/commands/PackCommand.py | 29 +++++----- tack/commands/UnpackCommand.py | 6 +- tack/crypto/openssl/OpenSSL_ECPublicKey.py | 2 +- tack/crypto/python/Python_ECPublicKey.py | 2 +- tack/structures/Tack.py | 13 +++++ tack/structures/TackActivation.py | 10 ++-- tack/structures/TackExtension.py | 66 ++++++++++++---------- 8 files changed, 79 insertions(+), 57 deletions(-) diff --git a/tack/commands/Command.py b/tack/commands/Command.py index 16bd32d..5592eb1 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -98,17 +98,17 @@ def getBreakSignatures(self): except SyntaxError: self.printError("Error parsing break signatures: %s" % fileName) - def getTack(self): + def getTacks(self): fileName = self._getOptionValue("-t") if fileName is None: return None try: contents = open(fileName, "r").read() - return Tack.createFromPem(contents) + return Tack.createFromPemList(contents) except IOError: - self.printError("Error opening tack: %s" % fileName) + self.printError("Error opening tacks: %s" % fileName) except SyntaxError: - self.printError("Error parsing tack: %s" % fileName) + self.printError("Error parsing tacks: %s" % fileName) def getTackExtension(self, mandatory): fileName = self._getOptionValue("-E") diff --git a/tack/commands/PackCommand.py b/tack/commands/PackCommand.py index d7856b4..39e61ec 100644 --- a/tack/commands/PackCommand.py +++ b/tack/commands/PackCommand.py @@ -15,43 +15,42 @@ class PackCommand(Command): def __init__(self, argv): Command.__init__(self, argv, "otba", "vx") self.outputFile, self.outputFileName = self.getOutputFile() - self.tack = self.getTack() + self.tacks = self.getTacks() self.breakSignatures = self.getBreakSignatures() - self.activationFlag = self._getActivationFlag() + self.activationFlags = self._getActivationFlags() def execute(self): - tackExtension = TackExtension.create(self.tack, self.breakSignatures, - self.activationFlag) + tackExtension = TackExtension.create(self.tacks, self.breakSignatures, + self.activationFlags) - #tlsCertificate = TlsCertificate.create(tackExtension) self.outputFile.write(self.addPemComments(tackExtension.serializeAsPem())) self.printVerbose(str(tackExtension)) - def _getActivationFlag(self): - activation_flag = self._getOptionValue("-a") + def _getActivationFlags(self): + activation_flags = self._getOptionValue("-a") - if activation_flag is None: - return 0 + if activation_flags is None: + return TackActivation.NONE try: - activation_flag = int(activation_flag) # Could raise ValueError - if activation_flag < 0 or activation_flag > 1: + activation_flags = int(activation_flags) # Could raise ValueError + if activation_flags < 0 or activation_flags > TackActivation.BOTH_ACTIVE: raise ValueError() except ValueError: - self.printError("Bad activation_flag: %s" % activation_flag) + self.printError("Bad activation_flags: %s" % activation_flags) - return activation_flag + return activation_flags @staticmethod def printHelp(): print( -"""Takes the input Tack, Break Sigs, and Activation Flag, and produces a +"""Takes the input Tacks, Break Sigs, and Activation Flag, and produces a TACK_Extension from them. Optional arguments: -v : Verbose - -t TACK : Include Tack from this file. + -t TACKS : Include Tacks from this file. -b BREAKSIGS : Include Break Signatures from this file. -a FLAG : Activation flag (0 or 1) -o FILE : Write the output to this file (instead of stdout) diff --git a/tack/commands/UnpackCommand.py b/tack/commands/UnpackCommand.py index 9565841..a27ce8d 100644 --- a/tack/commands/UnpackCommand.py +++ b/tack/commands/UnpackCommand.py @@ -18,8 +18,8 @@ def __init__(self, argv): self.tackExtension = self.getTackExtension(mandatory=True) def execute(self): - if self.tackExtension.tack: - self.outputFile.write(self.tackExtension.tack.serializeAsPem()) + for tack in self.tackExtension.tacks: + self.outputFile.write(tack.serializeAsPem()) for break_sig in self.tackExtension.break_sigs: self.outputFile.write(break_sig.serializeAsPem()) self.printVerbose(str(self.tackExtension)) @@ -27,7 +27,7 @@ def execute(self): @staticmethod def printHelp(): print( -"""Takes the input TACK Extension, and writes out PEM encodings for its Tack +"""Takes the input TACK Extension, and writes out PEM encodings for its Tacks and Break Signatures. unpack -e EXTENSION diff --git a/tack/crypto/openssl/OpenSSL_ECPublicKey.py b/tack/crypto/openssl/OpenSSL_ECPublicKey.py index 5c1cd60..3773bec 100644 --- a/tack/crypto/openssl/OpenSSL_ECPublicKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPublicKey.py @@ -57,7 +57,7 @@ def getRawKey(self): def getFingerprint(self): digest = Digest.SHA256(self.rawPublicKey) assert(len(digest) == 32) - s = b2a_base32(digest).lower()[:25] + s = b2a_base32(digest).lower()[:25].replace('l', '8') return "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) def _constructEcFromRawKey(self, rawPublicKey): diff --git a/tack/crypto/python/Python_ECPublicKey.py b/tack/crypto/python/Python_ECPublicKey.py index 44bf187..4541e7e 100644 --- a/tack/crypto/python/Python_ECPublicKey.py +++ b/tack/crypto/python/Python_ECPublicKey.py @@ -33,7 +33,7 @@ def getRawKey(self): def getFingerprint(self): digest = Digest.SHA256(self.rawPublicKey) assert(len(digest) == 32) - s = b2a_base32(digest).lower()[:25] + s = b2a_base32(digest).lower()[:25].replace('l', '8') return "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) def __str__(self): diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index cbddd2e..dbc31e8 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -41,6 +41,19 @@ def __init__(self, data=None): def createFromPem(cls, pem): return cls(PEMDecoder(pem).decode("TACK")) + @classmethod + def createFromPemList(cls, data): + """Parse a string containing a sequence of PEM Tacks. + + Raise a SyntaxError if input is malformed. + """ + tacks = [] + bList = PEMDecoder(data).decodeList("TACK") + for b in bList: + tacks.append(Tack(b)) + + return tacks + @classmethod def create(cls, public_key, private_key, min_generation, generation, expiration, target_hash): assert(len(public_key.getRawKey()) == 64) diff --git a/tack/structures/TackActivation.py b/tack/structures/TackActivation.py index f375424..05e2fa8 100644 --- a/tack/structures/TackActivation.py +++ b/tack/structures/TackActivation.py @@ -5,7 +5,9 @@ # See the LICENSE file for legal information regarding use of this file. class TackActivation: - DISABLED = 0 - ENABLED = 1 - ALL = (DISABLED, ENABLED) - STRINGS = ["disabled", "enabled"] \ No newline at end of file + NONE = 0 + FIRST_ACTIVE = 1 + SECOND_ACTIVE = 2 + BOTH_ACTIVE = 3 + ALL = (NONE, FIRST_ACTIVE, SECOND_ACTIVE, BOTH_ACTIVE) + STRINGS = ["none", "first_active", "second_active", "both_active"] \ No newline at end of file diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index 9150781..2fb9fd0 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -19,11 +19,11 @@ def __init__(self, data=None): return TlsStructure.__init__(self, data) - self.tack = self._parseTack() - self.break_sigs = self._parseBreakSigs() - self.activation_flag = self.getInt(1) + self.tacks = self._parseTacks() + self.break_sigs = self._parseBreakSigs() + self.activation_flags = self.getInt(1) - if self.activation_flag not in TackActivation.ALL: + if self.activation_flags not in TackActivation.ALL: raise SyntaxError("Bad activation_flag value") if self.index != len(data): @@ -34,25 +34,23 @@ def createFromPem(cls, data): return cls(PEMDecoder(data).decode("TACK EXTENSION")) @classmethod - def create(cls, tack, break_sigs, activation_flag): + def create(cls, tacks, break_sigs, activation_flags): tackExtension = cls() - tackExtension.tack = tack + tackExtension.tacks = tacks tackExtension.break_sigs = break_sigs - if not activation_flag: - tackExtension.activation_flag = TackActivation.DISABLED - else: - tackExtension.activation_flag = TackActivation.ENABLED + tackExtension.activation_flags = activation_flags return tackExtension def serialize(self): w = TlsStructureWriter(self._getSerializedLength()) - if self.tack: - w.add(Tack.LENGTH, 1) - w.add(self.tack.serialize(), Tack.LENGTH) + if self.tacks: + w.add(len(self.tacks) * Tack.LENGTH, 2) + for tack in self.tacks: + w.add(tack.serialize(), Tack.LENGTH) else: - w.add(0, 1) + w.add(0, 2) if self.break_sigs: w.add(len(self.break_sigs) * TackBreakSig.LENGTH, 2) @@ -61,7 +59,7 @@ def serialize(self): else: w.add(0, 2) - w.add(self.activation_flag, 1) + w.add(self.activation_flags, 1) return w.getBytes() @@ -72,8 +70,8 @@ def isEmpty(self): return not self.tack and not self.break_sigs def verifySignatures(self): - if self.tack: - if not self.tack.verifySignature(): + for tack in self.tacks: + if not tack.verifySignature(): return False for break_sig in self.break_sigs: if not break_sig.verifySignature(): @@ -82,20 +80,29 @@ def verifySignatures(self): def _getSerializedLength(self): length = 0 - if self.tack: - length += Tack.LENGTH + if self.tacks: + length += len(self.tacks) * Tack.LENGTH if self.break_sigs: length += len(self.break_sigs) * TackBreakSig.LENGTH - return length + 4 + return length + 5 # 2x2 byes length fields, 1 byte flags - def _parseTack(self): - tackLen = self.getInt(1) - if tackLen: - if tackLen != Tack.LENGTH: - raise SyntaxError("TACK wrong size: %d" % tackLen) - return Tack(self.getBytes(tackLen)) + def _parseTacks(self): + tacksLen = self.getInt(2) + if tacksLen: + if tacksLen > 2 * Tack.LENGTH: + raise SyntaxError("tacks too large: %d" % tacksLen) + elif tacksLen % Tack.LENGTH != 0: + raise SyntaxError("tacks wrong size: %d" % tacksLen) + + tacks = [] + b2 = self.getBytes(tacksLen) + while b2: + tacks.append(Tack(b2[:Tack.LENGTH])) + b2 = b2[Tack.LENGTH:] + + return tacks def _parseBreakSigs(self): sigsLen = self.getInt(2) @@ -116,13 +123,14 @@ def _parseBreakSigs(self): def __str__(self): result = "" - if self.tack: - result += str(self.tack) + if self.tacks: + for tack in self.tacks: + result += str(tack) if self.break_sigs: for break_sig in self.break_sigs: result += str(break_sig) - result += "activation_flag = %s\n" % TackActivation.STRINGS[self.activation_flag] + result += "activation_flags = %s\n" % TackActivation.STRINGS[self.activation_flags] return result From 71fbdf64b9b13cba0bb03a9e1c38663800892bce Mon Sep 17 00:00:00 2001 From: Trevor Date: Mon, 13 Aug 2012 22:13:43 -0700 Subject: [PATCH 78/94] Add Makefile test for paired tacks. --- Makefile | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 236302a..0f01678 100644 --- a/Makefile +++ b/Makefile @@ -45,8 +45,9 @@ test: $(EXEC) b -x -k $(TESTDIR)/TACK_Key2.pem -p asdf -o $(TESTDIR)/TACK_Break_Sig2.pem cat $(TESTDIR)/TACK_Break_Sig1.pem $(TESTDIR)/TACK_Break_Sig2.pem > $(TESTDIR)/TACK_Break_Sigs.pem $(EXEC) pack -t $(TESTDIR)/TACK3.pem > $(TESTDIR)/TACK_Ext3.pem - $(EXEC) pack -t $(TESTDIR)/TACK4.pem -b $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_Ext4.pem - $(EXEC) unpack -E $(TESTDIR)/TACK_Ext4.pem -o $(TESTDIR)/TACK_Ext4_Unpack.txt + cat $(TESTDIR)/TACK3.pem $(TESTDIR)/TACK4.pem > $(TESTDIR)/TACK3_4.pem + $(EXEC) pack -t $(TESTDIR)/TACK3_4.pem -b $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_Ext3_4.pem + $(EXEC) unpack -E $(TESTDIR)/TACK_Ext3_4.pem -o $(TESTDIR)/TACK_Ext3_4_Unpack.txt $(EXEC) view $(TESTDIR)/TACK_Key1.pem > $(TESTDIR)/TACK_View_Key1.txt cat $(TESTDIR)/TACK1.pem | $(EXEC) view - > $(TESTDIR)/TACK_View1.txt $(EXEC) v $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_View_Break_Sigs.txt From ab51b8923e8c9864cc1b300afb72ac4ea7e6a4ee Mon Sep 17 00:00:00 2001 From: Trevor Date: Mon, 20 Aug 2012 21:26:25 -0700 Subject: [PATCH 79/94] Change activation_flags to integer, remove TackActivation. --- tack/commands/PackCommand.py | 5 ++--- tack/commands/UnpackCommand.py | 1 - tack/structures/TackActivation.py | 13 ------------- tack/structures/TackExtension.py | 7 +++---- 4 files changed, 5 insertions(+), 21 deletions(-) delete mode 100644 tack/structures/TackActivation.py diff --git a/tack/commands/PackCommand.py b/tack/commands/PackCommand.py index 39e61ec..0f872e3 100644 --- a/tack/commands/PackCommand.py +++ b/tack/commands/PackCommand.py @@ -6,7 +6,6 @@ import sys from tack.commands.Command import Command -from tack.structures.TackActivation import TackActivation from tack.structures.TackExtension import TackExtension from tack.tls.TlsCertificate import TlsCertificate @@ -30,11 +29,11 @@ def _getActivationFlags(self): activation_flags = self._getOptionValue("-a") if activation_flags is None: - return TackActivation.NONE + return 0 try: activation_flags = int(activation_flags) # Could raise ValueError - if activation_flags < 0 or activation_flags > TackActivation.BOTH_ACTIVE: + if activation_flags < 0 or activation_flags > 3: raise ValueError() except ValueError: self.printError("Bad activation_flags: %s" % activation_flags) diff --git a/tack/commands/UnpackCommand.py b/tack/commands/UnpackCommand.py index a27ce8d..662601c 100644 --- a/tack/commands/UnpackCommand.py +++ b/tack/commands/UnpackCommand.py @@ -6,7 +6,6 @@ import sys from tack.commands.Command import Command -from tack.structures.TackActivation import TackActivation from tack.structures.TackExtension import TackExtension from tack.tls.TlsCertificate import TlsCertificate diff --git a/tack/structures/TackActivation.py b/tack/structures/TackActivation.py deleted file mode 100644 index 05e2fa8..0000000 --- a/tack/structures/TackActivation.py +++ /dev/null @@ -1,13 +0,0 @@ -# Authors: -# Trevor Perrin -# Moxie Marlinspike -# -# See the LICENSE file for legal information regarding use of this file. - -class TackActivation: - NONE = 0 - FIRST_ACTIVE = 1 - SECOND_ACTIVE = 2 - BOTH_ACTIVE = 3 - ALL = (NONE, FIRST_ACTIVE, SECOND_ACTIVE, BOTH_ACTIVE) - STRINGS = ["none", "first_active", "second_active", "both_active"] \ No newline at end of file diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index 2fb9fd0..fb28e0a 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -5,7 +5,6 @@ # See the LICENSE file for legal information regarding use of this file. from tack.structures.Tack import Tack -from tack.structures.TackActivation import TackActivation from tack.structures.TackBreakSig import TackBreakSig from tack.tls.TlsStructure import TlsStructure from tack.tls.TlsStructureWriter import TlsStructureWriter @@ -23,7 +22,7 @@ def __init__(self, data=None): self.break_sigs = self._parseBreakSigs() self.activation_flags = self.getInt(1) - if self.activation_flags not in TackActivation.ALL: + if self.activation_flags > 3: raise SyntaxError("Bad activation_flag value") if self.index != len(data): @@ -67,7 +66,7 @@ def serializeAsPem(self): return PEMEncoder(self.serialize()).encode("TACK EXTENSION") def isEmpty(self): - return not self.tack and not self.break_sigs + return not self.tacks and not self.break_sigs def verifySignatures(self): for tack in self.tacks: @@ -131,6 +130,6 @@ def __str__(self): for break_sig in self.break_sigs: result += str(break_sig) - result += "activation_flags = %s\n" % TackActivation.STRINGS[self.activation_flags] + result += "activation_flags = %d\n" % self.activation_flags return result From 0471e7bee71ac30484374d7aaf7ac03ce7a8b816 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 30 Aug 2012 18:51:04 -0400 Subject: [PATCH 80/94] Roll back base32 l->8 --- tack/crypto/openssl/OpenSSL_ECPublicKey.py | 2 +- tack/crypto/python/Python_ECPublicKey.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tack/crypto/openssl/OpenSSL_ECPublicKey.py b/tack/crypto/openssl/OpenSSL_ECPublicKey.py index 3773bec..5c1cd60 100644 --- a/tack/crypto/openssl/OpenSSL_ECPublicKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPublicKey.py @@ -57,7 +57,7 @@ def getRawKey(self): def getFingerprint(self): digest = Digest.SHA256(self.rawPublicKey) assert(len(digest) == 32) - s = b2a_base32(digest).lower()[:25].replace('l', '8') + s = b2a_base32(digest).lower()[:25] return "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) def _constructEcFromRawKey(self, rawPublicKey): diff --git a/tack/crypto/python/Python_ECPublicKey.py b/tack/crypto/python/Python_ECPublicKey.py index 4541e7e..44bf187 100644 --- a/tack/crypto/python/Python_ECPublicKey.py +++ b/tack/crypto/python/Python_ECPublicKey.py @@ -33,7 +33,7 @@ def getRawKey(self): def getFingerprint(self): digest = Digest.SHA256(self.rawPublicKey) assert(len(digest) == 32) - s = b2a_base32(digest).lower()[:25].replace('l', '8') + s = b2a_base32(digest).lower()[:25] return "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) def __str__(self): From 05ed9964250d23bac68aff45e77c75e829ae7848 Mon Sep 17 00:00:00 2001 From: Trevor Date: Wed, 5 Sep 2012 13:53:04 -0400 Subject: [PATCH 81/94] Update README for rollover. --- README | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README b/README index 4ebf3a7..957d75c 100644 --- a/README +++ b/README @@ -51,9 +51,9 @@ Create a tack for a certificate's public key: Deploy tacks to a hostname 1) Deploy tacks to each server at the hostname. - - Apache: Set "SSLTackFile" to a tack file. + - Apache: Set "SSLTACKTackFile" to a tack file. 2) Set the activation flag on each server. - - Apache: Set "SSLTackActivationFlag On". + - Apache: Set "SSLTACKActivationFlags 1". 3) Test the site (if there are problems, see "Removing tacks"). 4) Whenever you change a server's certificate, you must replace its tack. @@ -65,7 +65,7 @@ Pin deactivation ----------------- If you wish to stop using TACK for a hostname but can tolerate a "waiting period" before the tacks are removed, simply disable the activation flag at -all servers for that hostname (Apache: "SSLTackActivationFlag Off"). Then wait +all servers for that hostname (Apache: "SSLTACKActivationFlags 0"). Then wait for all existing client pins to become inactive. The waiting period required is equal to the length of time that the activation @@ -100,7 +100,7 @@ break signatures add to TLS handshake overhead, so are best avoided. Create a break signature for a TACK key: 1) Run "tack break -k KEY.pem > TACK_Break_Sig.pem" 2) Add the break signature to your web server. - - Apache: Set "SSLTACKBreakSigsFile" to a file with break signatures. + - Apache: Set "SSLTACKBreakSigFile" to a file with break signatures. Advanced uses @@ -155,3 +155,9 @@ produces 365 tacks, one expiring at midnight (UTC) each day of 2013: T1_0002.pem ... T1_0364.pem + +TACK Key rollover +------------------ +You may "rollover" a hostname from one TACK key to another without an interruption in security by publishing two tacks simultaneously. This allows clients to form pins based on the second tack prior to the first tack being removed. + +To perform a rollover, simply append the new tack to the SSLTACKTackFile, and set the SSLTACKActivationFlags to 3 (1 activates the first tack, 2 activates the second tack, and 3 activates both). Allow at least 30 days, then deactivate the first tack by setting SSLTACKActivationFlags to 2. Allow at least another 30 days, then delete the first tack and set SSLTACKActivationFlags to 1. The rollover is now complete. From 769b403bfa338f09fc3dacd18415534a552bd76c Mon Sep 17 00:00:00 2001 From: Trevor Date: Wed, 5 Sep 2012 17:06:25 -0400 Subject: [PATCH 82/94] Changing version strings for 0.9.8. --- README | 2 +- setup.py | 2 +- tack/version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README b/README index 957d75c..fd6e432 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Tackpy version 0.9.7 May 22 2012 +Tackpy version 0.9.8 Sep 5 2012 ============================================================================ Licenses/Acknowledgements diff --git a/setup.py b/setup.py index 617ffd3..8bad630 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ shutil.copyfile("tack.py", "tack/tack") setup( name="tackpy", - version="0.9.7", + version="0.9.8", author="Trevor Perrin,Moxie Marlinspike", url="http://tack.io", description="Tackpy implements TACK in python", diff --git a/tack/version.py b/tack/version.py index 9b34a8f..998bade 100644 --- a/tack/version.py +++ b/tack/version.py @@ -3,5 +3,5 @@ ################ VERSION ### -__version__ = "0.9.7" +__version__ = "0.9.8" From f58d3117821e30c7179d23a7b2c8346d8bcb63b5 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sat, 8 Sep 2012 11:52:17 -0400 Subject: [PATCH 83/94] Remove break sigs. --- Makefile | 6 +-- README | 53 +++++++--------------- tack.py | 3 -- tack/commands/BreakCommand.py | 38 ---------------- tack/commands/Command.py | 13 ------ tack/commands/HelpCommand.py | 5 +-- tack/commands/PackCommand.py | 11 ++--- tack/commands/UnpackCommand.py | 5 +-- tack/commands/ViewCommand.py | 9 ---- tack/structures/TackBreakSig.py | 75 -------------------------------- tack/structures/TackExtension.py | 43 +----------------- 11 files changed, 25 insertions(+), 236 deletions(-) delete mode 100644 tack/commands/BreakCommand.py delete mode 100644 tack/structures/TackBreakSig.py diff --git a/Makefile b/Makefile index 0f01678..68e91e4 100644 --- a/Makefile +++ b/Makefile @@ -41,16 +41,12 @@ test: $(EXEC) sign -x -k $(TESTDIR)/TACK_Key1.pem -p asdf -e 2030-06-06Z -c $(CERT2) -o $(TESTDIR)/TACK5.pem cat $(CERT1) | $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -g2 -m2 -c- -o $(TESTDIR)/TACK6.pem cat $(CERT2) | $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -m250 -g251 -c - -o $(TESTDIR)/T6 -e 2013-01-02Z -n 3@1d - $(EXEC) break -k $(TESTDIR)/TACK_Key1.pem -p asdf > $(TESTDIR)/TACK_Break_Sig1.pem - $(EXEC) b -x -k $(TESTDIR)/TACK_Key2.pem -p asdf -o $(TESTDIR)/TACK_Break_Sig2.pem - cat $(TESTDIR)/TACK_Break_Sig1.pem $(TESTDIR)/TACK_Break_Sig2.pem > $(TESTDIR)/TACK_Break_Sigs.pem $(EXEC) pack -t $(TESTDIR)/TACK3.pem > $(TESTDIR)/TACK_Ext3.pem cat $(TESTDIR)/TACK3.pem $(TESTDIR)/TACK4.pem > $(TESTDIR)/TACK3_4.pem - $(EXEC) pack -t $(TESTDIR)/TACK3_4.pem -b $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_Ext3_4.pem + $(EXEC) pack -t $(TESTDIR)/TACK3_4.pem > $(TESTDIR)/TACK_Ext3_4.pem $(EXEC) unpack -E $(TESTDIR)/TACK_Ext3_4.pem -o $(TESTDIR)/TACK_Ext3_4_Unpack.txt $(EXEC) view $(TESTDIR)/TACK_Key1.pem > $(TESTDIR)/TACK_View_Key1.txt cat $(TESTDIR)/TACK1.pem | $(EXEC) view - > $(TESTDIR)/TACK_View1.txt - $(EXEC) v $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_View_Break_Sigs.txt $(EXEC) v $(CERT1) > $(TESTDIR)/TACK_View_Cert1.txt cat $(CERT2) | $(EXEC) v - > $(TESTDIR)/TACK_View_Cert2.txt $(EXEC) v $(TESTDIR)/TACK_Ext3.pem > $(TESTDIR)/TACK_View_TACK_Ext3.txt diff --git a/README b/README index fd6e432..9391659 100644 --- a/README +++ b/README @@ -54,19 +54,15 @@ Deploy tacks to a hostname - Apache: Set "SSLTACKTackFile" to a tack file. 2) Set the activation flag on each server. - Apache: Set "SSLTACKActivationFlags 1". - 3) Test the site (if there are problems, see "Removing tacks"). + 3) Test the site (if there are problems, see "Pin deactivation"). 4) Whenever you change a server's certificate, you must replace its tack. -Removing tacks -=============== - Pin deactivation ------------------ -If you wish to stop using TACK for a hostname but can tolerate a "waiting -period" before the tacks are removed, simply disable the activation flag at -all servers for that hostname (Apache: "SSLTACKActivationFlags 0"). Then wait -for all existing client pins to become inactive. +================= +If you wish to stop using TACK for a hostname, simply disable the activation +flag at all servers for that hostname (Apache: "SSLTACKActivationFlags 0"). +Then wait for all existing client pins to become inactive. The waiting period required is equal to the length of time that the activation flag has been enabled for any servers at the hostname, or a maximum of 30 @@ -77,31 +73,6 @@ safely removed. disable the activation flag after one day, you can remove the tack at the end of the second day.) -Break signatures ------------------ -If you wish to abruptly stop publishing a tack for a hostname, or abruptly -change the hostname's TACK key, or signal that a TACK key has been -compromised, then you may publish a "break signature" from the TACK key as -part of the TLS connection. - -This break signature must remain published at the hostname until all pins -between the hostname and the old TACK key have become inactive (30 days at -most; this is exactly the same as the "waiting period" described in the -previous section). - -A break signature from a TACK key causes any client who encounters it to -discard all pins involving the TACK key. Thus, once a break signature is -published for a TACK key, all existing pins and tacks for the TACK key cease -providing security. - -A server can have up to eight break signatures. However, keep in mind that -break signatures add to TLS handshake overhead, so are best avoided. - -Create a break signature for a TACK key: - 1) Run "tack break -k KEY.pem > TACK_Break_Sig.pem" - 2) Add the break signature to your web server. - - Apache: Set "SSLTACKBreakSigFile" to a file with break signatures. - Advanced uses ============== @@ -158,6 +129,14 @@ produces 365 tacks, one expiring at midnight (UTC) each day of 2013: TACK Key rollover ------------------ -You may "rollover" a hostname from one TACK key to another without an interruption in security by publishing two tacks simultaneously. This allows clients to form pins based on the second tack prior to the first tack being removed. - -To perform a rollover, simply append the new tack to the SSLTACKTackFile, and set the SSLTACKActivationFlags to 3 (1 activates the first tack, 2 activates the second tack, and 3 activates both). Allow at least 30 days, then deactivate the first tack by setting SSLTACKActivationFlags to 2. Allow at least another 30 days, then delete the first tack and set SSLTACKActivationFlags to 1. The rollover is now complete. +You may "rollover" a hostname from one TACK key to another without an +interruption in security by publishing two tacks simultaneously. This allows +clients to form pins based on the second tack prior to the first tack being +removed. + +To perform a rollover, simply append the new tack to the SSLTACKTackFile, and +set the SSLTACKActivationFlags to 3 (1 activates the first tack, 2 activates +the second tack, and 3 activates both). Allow at least 30 days, then +deactivate the first tack by setting SSLTACKActivationFlags to 2. Allow at +least another 30 days, then delete the first tack and set +SSLTACKActivationFlags to 1. The rollover is now complete. diff --git a/tack.py b/tack.py index b4b592a..17480d7 100755 --- a/tack.py +++ b/tack.py @@ -7,7 +7,6 @@ # See the LICENSE file for legal information regarding use of this file. import sys -from tack.commands.BreakCommand import BreakCommand from tack.commands.GenerateKeyCommand import GenerateKeyCommand from tack.commands.HelpCommand import HelpCommand from tack.commands.SignCommand import SignCommand @@ -25,8 +24,6 @@ GenerateKeyCommand(sys.argv[2:]).execute() elif sys.argv[1] == "sign"[:len(sys.argv[1])]: SignCommand(sys.argv[2:]).execute() - elif sys.argv[1] == "break"[:len(sys.argv[1])]: - BreakCommand(sys.argv[2:]).execute() elif sys.argv[1] == "view"[:len(sys.argv[1])]: ViewCommand(sys.argv[2:]).execute() elif sys.argv[1] == "help"[:len(sys.argv[1])]: diff --git a/tack/commands/BreakCommand.py b/tack/commands/BreakCommand.py deleted file mode 100644 index 61c3c5e..0000000 --- a/tack/commands/BreakCommand.py +++ /dev/null @@ -1,38 +0,0 @@ -# Authors: -# Trevor Perrin -# Moxie Marlinspike -# -# See the LICENSE file for legal information regarding use of this file. - -import sys -from tack.commands.Command import Command -from tack.structures.TackBreakSig import TackBreakSig - -class BreakCommand(Command): - - def __init__(self, argv): - Command.__init__(self, argv, "pok", "vx") - self.password = self.getPassword() - self.outputFile, self.outputFileName = self.getOutputFile() - self.keyfile = self.getKeyFile(self.getPassword(), mandatory=True) - - def execute(self): - breakSig = TackBreakSig.create(self.keyfile.getPublicKey(), self.keyfile.getPrivateKey()) - self.outputFile.write(self.addPemComments(breakSig.serializeAsPem())) - self.printVerbose(str(breakSig)) - - @staticmethod - def printHelp(): - print( -"""Creates a break signature based on an input TACK key file. - - break -k KEY - - -k KEY : Use this TACK key file ("-" for stdin) - -Optional arguments: - -v : Verbose - -x : Use python crypto (not OpenSSL) - -o FILE : Write the output to this file (instead of stdout) - -p PASSWORD : Use this TACK key password instead of prompting -""") diff --git a/tack/commands/Command.py b/tack/commands/Command.py index 5592eb1..ffbbcc7 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -11,7 +11,6 @@ from tack.structures.TackKeyFile import TackKeyFile from tack.structures.TackExtension import TackExtension from tack.structures.Tack import Tack -from tack.structures.TackBreakSig import TackBreakSig from tack.tls.TlsCertificate import TlsCertificate from tack.util.Time import Time from tack.crypto.openssl.OpenSSL import openssl as o @@ -86,18 +85,6 @@ def getKeyFile(self, password, mandatory): except IOError: self.printError("Error opening TACK Key File: %s" % keyPemFile) - def getBreakSignatures(self): - fileName = self._getOptionValue("-b") - if fileName is None: - return None - try: - contents = open(fileName, "r").read() - return TackBreakSig.createFromPemList(contents) - except IOError: - self.printError("Error opening break signatures: %s" % fileName) - except SyntaxError: - self.printError("Error parsing break signatures: %s" % fileName) - def getTacks(self): fileName = self._getOptionValue("-t") if fileName is None: diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index 2d2d413..8bb37fe 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -9,7 +9,6 @@ from tack.commands.Command import Command from tack.commands.GenerateKeyCommand import GenerateKeyCommand from tack.commands.SignCommand import SignCommand -from tack.commands.BreakCommand import BreakCommand from tack.commands.ViewCommand import ViewCommand from tack.commands.PackCommand import PackCommand from tack.commands.UnpackCommand import UnpackCommand @@ -17,8 +16,7 @@ class HelpCommand(Command): COMMANDS = {"genkey" : GenerateKeyCommand, "sign" : SignCommand, - "break" : BreakCommand, "view" : ViewCommand, - "pack" : PackCommand, "unpack" : UnpackCommand} + "view" : ViewCommand, "pack" : PackCommand, "unpack" : UnpackCommand} def __init__(self, argv): Command.__init__(self, argv, "", "", allowArgRemainder=True) @@ -52,7 +50,6 @@ def printGeneralUsage(message=None): Commands (use "help " to see optional args): genkey sign -k KEY -c CERT - break -k KEY view FILE help COMMAND ("pack" and "unpack" are advanced commands for debugging) diff --git a/tack/commands/PackCommand.py b/tack/commands/PackCommand.py index 0f872e3..399047b 100644 --- a/tack/commands/PackCommand.py +++ b/tack/commands/PackCommand.py @@ -12,15 +12,13 @@ class PackCommand(Command): def __init__(self, argv): - Command.__init__(self, argv, "otba", "vx") + Command.__init__(self, argv, "ota", "vx") self.outputFile, self.outputFileName = self.getOutputFile() self.tacks = self.getTacks() - self.breakSignatures = self.getBreakSignatures() self.activationFlags = self._getActivationFlags() def execute(self): - tackExtension = TackExtension.create(self.tacks, self.breakSignatures, - self.activationFlags) + tackExtension = TackExtension.create(self.tacks, self.activationFlags) self.outputFile.write(self.addPemComments(tackExtension.serializeAsPem())) self.printVerbose(str(tackExtension)) @@ -44,13 +42,12 @@ def _getActivationFlags(self): @staticmethod def printHelp(): print( -"""Takes the input Tacks, Break Sigs, and Activation Flag, and produces a +"""Takes the input Tacks, and Activation Flags, and produces a TACK_Extension from them. Optional arguments: -v : Verbose -t TACKS : Include Tacks from this file. - -b BREAKSIGS : Include Break Signatures from this file. - -a FLAG : Activation flag (0 or 1) + -a FLAG : Activation flag (0...3) -o FILE : Write the output to this file (instead of stdout) """) diff --git a/tack/commands/UnpackCommand.py b/tack/commands/UnpackCommand.py index 662601c..383ce4e 100644 --- a/tack/commands/UnpackCommand.py +++ b/tack/commands/UnpackCommand.py @@ -19,15 +19,12 @@ def __init__(self, argv): def execute(self): for tack in self.tackExtension.tacks: self.outputFile.write(tack.serializeAsPem()) - for break_sig in self.tackExtension.break_sigs: - self.outputFile.write(break_sig.serializeAsPem()) self.printVerbose(str(self.tackExtension)) @staticmethod def printHelp(): print( -"""Takes the input TACK Extension, and writes out PEM encodings for its Tacks -and Break Signatures. +"""Takes the input TACK Extension, and writes out PEM encodings for its Tacks. unpack -e EXTENSION diff --git a/tack/commands/ViewCommand.py b/tack/commands/ViewCommand.py index 009537e..fa73694 100644 --- a/tack/commands/ViewCommand.py +++ b/tack/commands/ViewCommand.py @@ -9,7 +9,6 @@ from tack.structures.TackExtension import TackExtension from tack.structures.Tack import Tack from tack.structures.TackKeyFile import TackKeyFile -from tack.structures.TackBreakSig import TackBreakSig from tack.tls.TlsCertificate import TlsCertificate from tack.util.PEMDecoder import PEMDecoder @@ -39,14 +38,6 @@ def execute(self): tack = Tack.createFromPem(text) sys.stdout.write(str(tack)) return - elif decoder.containsEncoded("TACK BREAK SIG"): - fileType = "Break Sig" - tbsList = TackBreakSig.createFromPemList(text) - s = "" - for tbs in tbsList: - s += str(tbs) - sys.stdout.write(s) - return elif decoder.containsEncoded("TACK EXTENSION"): fileType = "TACK Extension" tackExt = TackExtension.createFromPem(text) diff --git a/tack/structures/TackBreakSig.py b/tack/structures/TackBreakSig.py deleted file mode 100644 index 2482439..0000000 --- a/tack/structures/TackBreakSig.py +++ /dev/null @@ -1,75 +0,0 @@ -# Authors: -# Trevor Perrin -# Moxie Marlinspike -# -# See the LICENSE file for legal information regarding use of this file. - -from tack.crypto.ECPublicKey import ECPublicKey -from tack.tls.TlsStructure import TlsStructure -from tack.tls.TlsStructureWriter import TlsStructureWriter -from tack.util.PEMDecoder import PEMDecoder -from tack.util.PEMEncoder import PEMEncoder - -class TackBreakSig(TlsStructure): - LENGTH = 128 - - def __init__(self, data=None): - if data is None: - return - - TlsStructure.__init__(self, data) - if len(data) != TackBreakSig.LENGTH: - raise SyntaxError("Break signature is the wrong size. Is %s and should be %s." % (len(data), TackBreakSig.LENGTH)) - - self.public_key = ECPublicKey.create(self.getBytes(64)) - self.signature = self.getBytes(64) - - if not self.verifySignature(): - raise SyntaxError("TACK_Break_Sig has bad signature") - - - @classmethod - def createFromPem(cls, data): - return cls(PEMDecoder(data).decode("TACK BREAK SIG")) - - @classmethod - def createFromPemList(cls, data): - """Parse a string containing a sequence of PEM Break Sigs. - - Raise a SyntaxError if input is malformed. - """ - breakSigs = [] - bList = PEMDecoder(data).decodeList("TACK BREAK SIG") - for b in bList: - breakSigs.append(TackBreakSig(b)) - - return breakSigs - - @classmethod - def create(cls, public_key, private_key): - tackBreakSig = cls() - tackBreakSig.public_key = public_key - tackBreakSig.signature = private_key.sign(bytearray("tack_break_sig", "ascii")) - - return tackBreakSig - - def serialize(self): - """Return a bytearray containing the TACK_Break_Sig.""" - w = TlsStructureWriter(TackBreakSig.LENGTH) - w.add(self.public_key.getRawKey(), 64) - w.add(self.signature, 64) - return w.getBytes() - - def serializeAsPem(self): - return PEMEncoder(self.serialize()).encode("TACK BREAK SIG") - - def getTackId(self): - return str(self.public_key) - - def verifySignature(self): - return self.public_key.verify(bytearray("tack_break_sig", "ascii"), self.signature) - - def __str__(self): - """Return a readable string describing this TACK_Break_Sig. - Used by the "TACK view" command to display TACK objects.""" - return "Breaks key fingerprint = %s\n" % self.getTackId() diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index fb28e0a..ea6604e 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -5,7 +5,6 @@ # See the LICENSE file for legal information regarding use of this file. from tack.structures.Tack import Tack -from tack.structures.TackBreakSig import TackBreakSig from tack.tls.TlsStructure import TlsStructure from tack.tls.TlsStructureWriter import TlsStructureWriter from tack.util.PEMDecoder import PEMDecoder @@ -19,7 +18,6 @@ def __init__(self, data=None): TlsStructure.__init__(self, data) self.tacks = self._parseTacks() - self.break_sigs = self._parseBreakSigs() self.activation_flags = self.getInt(1) if self.activation_flags > 3: @@ -33,10 +31,9 @@ def createFromPem(cls, data): return cls(PEMDecoder(data).decode("TACK EXTENSION")) @classmethod - def create(cls, tacks, break_sigs, activation_flags): + def create(cls, tacks, activation_flags): tackExtension = cls() tackExtension.tacks = tacks - tackExtension.break_sigs = break_sigs tackExtension.activation_flags = activation_flags return tackExtension @@ -51,13 +48,6 @@ def serialize(self): else: w.add(0, 2) - if self.break_sigs: - w.add(len(self.break_sigs) * TackBreakSig.LENGTH, 2) - for break_sig in self.break_sigs: - w.add(break_sig.serialize(), TackBreakSig.LENGTH) - else: - w.add(0, 2) - w.add(self.activation_flags, 1) return w.getBytes() @@ -65,16 +55,10 @@ def serialize(self): def serializeAsPem(self): return PEMEncoder(self.serialize()).encode("TACK EXTENSION") - def isEmpty(self): - return not self.tacks and not self.break_sigs - def verifySignatures(self): for tack in self.tacks: if not tack.verifySignature(): return False - for break_sig in self.break_sigs: - if not break_sig.verifySignature(): - return False return True def _getSerializedLength(self): @@ -82,10 +66,7 @@ def _getSerializedLength(self): if self.tacks: length += len(self.tacks) * Tack.LENGTH - if self.break_sigs: - length += len(self.break_sigs) * TackBreakSig.LENGTH - - return length + 5 # 2x2 byes length fields, 1 byte flags + return length + 3 # 2 byes length field, 1 byte flags def _parseTacks(self): tacksLen = self.getInt(2) @@ -103,22 +84,6 @@ def _parseTacks(self): return tacks - def _parseBreakSigs(self): - sigsLen = self.getInt(2) - - if sigsLen > 1024: - raise SyntaxError("break_sigs too large: %d" % sigsLen) - elif sigsLen % TackBreakSig.LENGTH != 0: - raise SyntaxError("break_sigs wrong size: %d" % sigsLen) - - break_sigs = [] - b2 = self.getBytes(sigsLen) - while b2: - break_sigs.append(TackBreakSig(b2[:TackBreakSig.LENGTH])) - b2 = b2[TackBreakSig.LENGTH:] - - return break_sigs - def __str__(self): result = "" @@ -126,10 +91,6 @@ def __str__(self): for tack in self.tacks: result += str(tack) - if self.break_sigs: - for break_sig in self.break_sigs: - result += str(break_sig) - result += "activation_flags = %d\n" % self.activation_flags return result From d131187d5d3fd2b3a687bb47fef8ddda9c7ec448 Mon Sep 17 00:00:00 2001 From: Trevor Date: Sat, 8 Sep 2012 18:59:46 -0400 Subject: [PATCH 84/94] Disallow empty TACK_Extensions. --- tack/structures/TackExtension.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index ea6604e..ce4d8cb 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -71,8 +71,8 @@ def _getSerializedLength(self): def _parseTacks(self): tacksLen = self.getInt(2) if tacksLen: - if tacksLen > 2 * Tack.LENGTH: - raise SyntaxError("tacks too large: %d" % tacksLen) + if tacksLen > 2 * Tack.LENGTH or tacksLen < Tack.LENGTH: + raise SyntaxError("tacks wrong number: %d" % tacksLen) elif tacksLen % Tack.LENGTH != 0: raise SyntaxError("tacks wrong size: %d" % tacksLen) From e6c08a174ada7ecdb6d453fabcb05e998ea1db76 Mon Sep 17 00:00:00 2001 From: Eldar Zaitov Date: Fri, 21 Sep 2012 15:51:32 +0400 Subject: [PATCH 85/94] EVP_CIPHER_CTX_init definition fixed --- tack/crypto/openssl/OpenSSL.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tack/crypto/openssl/OpenSSL.py b/tack/crypto/openssl/OpenSSL.py index b61a004..491ca18 100644 --- a/tack/crypto/openssl/OpenSSL.py +++ b/tack/crypto/openssl/OpenSSL.py @@ -89,7 +89,7 @@ class ECDSA_SIG(Structure): self._add("BN_bin2bn", args=[c_void_p, c_int, c_void_p]) self._add("EVP_CIPHER_CTX_new", ret=c_void_p) - self._add("EVP_CIPHER_CTX_init", args=[c_void_p]) + self._add("EVP_CIPHER_CTX_init", args=[c_void_p], skipWrap=True) self._add("EVP_CIPHER_CTX_cleanup", args=[c_void_p]) self._add("EVP_CIPHER_CTX_free", args=[c_void_p], skipWrap=True) self._add("EVP_CIPHER_CTX_set_padding", args=[c_void_p, c_int]) From de9e64285c9e9fe5444e0d5eb306a1ffc5d7a866 Mon Sep 17 00:00:00 2001 From: Trevor Date: Mon, 24 Sep 2012 13:31:45 -0400 Subject: [PATCH 86/94] Tweaks. --- tack/structures/Tack.py | 4 ++-- tack/util/PEMDecoder.py | 4 +++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index dbc31e8..3890e05 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -21,7 +21,7 @@ def __init__(self, data=None): TlsStructure.__init__(self, data) if len(data) != Tack.LENGTH: - raise SyntaxError("TACK is the wrong size. Is %s and should be %s" % (len(data), Tack.LENGTH)) + raise SyntaxError("Tack is the wrong size. Is %s and should be %s" % (len(data), Tack.LENGTH)) self.public_key = ECPublicKey.create(self.getBytes(64)) self.min_generation = self.getInt(1) @@ -34,7 +34,7 @@ def __init__(self, data=None): raise SyntaxError("Generation less than min_generation") if not self.verifySignature(): - raise SyntaxError("TACK has bad signature") + raise SyntaxError("Tack has bad signature") @classmethod diff --git a/tack/util/PEMDecoder.py b/tack/util/PEMDecoder.py index e7318ac..d2ba7b8 100644 --- a/tack/util/PEMDecoder.py +++ b/tack/util/PEMDecoder.py @@ -38,7 +38,9 @@ def decode(self, name): end = self.data.find(postfix, start+len(prefix)) if end == -1: raise SyntaxError("Missing PEM postfix") - s = self.data[start+len("-----BEGIN %s-----" % name) : end] + if end < start: + raise SyntaxError("PEM postfix before prefix") + s = self.data[start+len(prefix) : end] retBytes = a2b_base64(s) return retBytes From 72f89a4e8b4d1da9c05ef0b8491eaf8addbc2413 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 27 Sep 2012 16:32:51 -0400 Subject: [PATCH 87/94] Oops 0.9.9 checkin, but *ALSO* fixed leftover TackBreakSig reference in unit tests. (Which was not distributed as 0.9.9). --- README | 2 +- setup.py | 2 +- tack/version.py | 2 +- tests/Structures_Test.py | 40 +--------------------------------------- 4 files changed, 4 insertions(+), 42 deletions(-) diff --git a/README b/README index 9391659..7b516fd 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Tackpy version 0.9.8 Sep 5 2012 +Tackpy version 0.9.9 Sep 25 2012 ============================================================================ Licenses/Acknowledgements diff --git a/setup.py b/setup.py index 8bad630..ae0c8bb 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ shutil.copyfile("tack.py", "tack/tack") setup( name="tackpy", - version="0.9.8", + version="0.9.9", author="Trevor Perrin,Moxie Marlinspike", url="http://tack.io", description="Tackpy implements TACK in python", diff --git a/tack/version.py b/tack/version.py index 998bade..cdc73cd 100644 --- a/tack/version.py +++ b/tack/version.py @@ -3,5 +3,5 @@ ################ VERSION ### -__version__ = "0.9.8" +__version__ = "0.9.9" diff --git a/tests/Structures_Test.py b/tests/Structures_Test.py index c0da159..6ca8432 100644 --- a/tests/Structures_Test.py +++ b/tests/Structures_Test.py @@ -8,7 +8,6 @@ from tack.compat import a2b_hex from tack.crypto.ECGenerator import ECGenerator from tack.structures.Tack import Tack -from tack.structures.TackBreakSig import TackBreakSig from tack.structures.TackKeyFile import TackKeyFile from tack.util.Time import Time @@ -39,43 +38,6 @@ def test_Tack(self): "515d2bcc82188d73f9f13e69a6118" "a4602e52b703")) - def test_BreakSig(self): - s = """ ------BEGIN TACK BREAK SIG----- -TAmsAZIpzR+MYwQrsujLhesvpu3dRc5ROhfgySqUVkU1p1hdXo+PwQrmaQo9B9+o -hecRrWElh3yThwgYQRgbS0HynTQCmrY48oJsQtarSMoxnRNYHaaYOXwu9+4ur8mX -wjKhIA9fXWNxuP73ZoicU+qC4bZjMN+WKuy7k8bSQZY= ------END TACK BREAK SIG-----""" - - tbs = TackBreakSig.createFromPem(s) - assert(tbs.getTackId() == "nkufh.czttd.5cmlw.7cxtv.k6srn") - assert(tbs.signature == a2b_hex("41f29d34029ab638f2826c42d6a" - "b48ca319d13581da698397c2ef7" - "ee2eafc997c232a1200f5f5d637" - "1b8fef766889c53ea82e1b66330" - "df962aecbb93c6d24196")) - - def test_BreakSigList(self): - s = """ ------BEGIN TACK BREAK SIG----- -TAmsAZIpzR+MYwQrsujLhesvpu3dRc5ROhfgySqUVkU1p1hdXo+PwQrmaQo9B9+o -hecRrWElh3yThwgYQRgbS0HynTQCmrY48oJsQtarSMoxnRNYHaaYOXwu9+4ur8mX -wjKhIA9fXWNxuP73ZoicU+qC4bZjMN+WKuy7k8bSQZY= ------END TACK BREAK SIG----- -Created by TACK.py 0.9.6 -Created at 2012-05-10T00:54:10Z ------BEGIN TACK BREAK SIG----- -73nkbxCcvFnrCIlcgtZx4iPevqxUFd9RFUNU18xfqzTCU8hV0jwYerdCwt8+VbkQ -OvHEbbRHmGAX8yseGrYX1dNuoFfSN1fCLY08u/0NU+x8fmJ6tEewegVAHguw67eR -PgegVlKuDULIASht9fvs6xTfxcFJDUgNaenZfcqAgAI= ------END TACK BREAK SIG----- -""" - tbsList = TackBreakSig.createFromPemList(s) - assert(tbsList[0].getTackId() == "nkufh.czttd.5cmlw.7cxtv.k6srn") - assert(tbsList[1].getTackId() == "6xwgu.ydz7m.7cki3.kizmd.pt2f2") - assert(len(tbsList) == 2) - return 1 - def test_KeyFile(self): s = """ -----BEGIN TACK PRIVATE KEY----- @@ -102,4 +64,4 @@ def test_KeyFile(self): assert(kf3.getPublicKey().getRawKey() == kf4.getPublicKey().getRawKey()) if __name__ == '__main__': - unittest.main() \ No newline at end of file + unittest.main() From e101d435d597e6d5fa8b3da0d18572d377e0b639 Mon Sep 17 00:00:00 2001 From: Trevor Date: Thu, 27 Sep 2012 16:44:01 -0400 Subject: [PATCH 88/94] Change version to 0.9.9a --- README | 2 +- setup.py | 2 +- tack/version.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README b/README index 7b516fd..7032bab 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Tackpy version 0.9.9 Sep 25 2012 +Tackpy version 0.9.9a Sep 25 2012 ============================================================================ Licenses/Acknowledgements diff --git a/setup.py b/setup.py index ae0c8bb..829efcc 100755 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ shutil.copyfile("tack.py", "tack/tack") setup( name="tackpy", - version="0.9.9", + version="0.9.9a", author="Trevor Perrin,Moxie Marlinspike", url="http://tack.io", description="Tackpy implements TACK in python", diff --git a/tack/version.py b/tack/version.py index cdc73cd..ad6dcc1 100644 --- a/tack/version.py +++ b/tack/version.py @@ -3,5 +3,5 @@ ################ VERSION ### -__version__ = "0.9.9" +__version__ = "0.9.9a" From 666879748e707fa95f17c4efa3f7d2f9ec2e25b5 Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 1 Feb 2013 23:25:28 -0800 Subject: [PATCH 89/94] Process extension files in "extender" format. --- tack/commands/Command.py | 36 +++++++++--------- tack/commands/PackCommand.py | 2 +- tack/commands/UnpackCommand.py | 2 +- tack/commands/ViewCommand.py | 2 +- tack/structures/TackExtension.py | 64 +++++++++++++++++--------------- 5 files changed, 55 insertions(+), 51 deletions(-) diff --git a/tack/commands/Command.py b/tack/commands/Command.py index ffbbcc7..4a574b5 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -79,11 +79,11 @@ def getKeyFile(self, password, mandatory): except InvalidPasswordException as ipe: sys.stderr.write("Password incorrect!\n") password = self._promptPassword() - except SyntaxError: - self.printError("Error processing TACK Key File") + except SyntaxError as e: + self.printError("Error processing TACK Key File: %s\n%s" % (keyPemFile, e)) - except IOError: - self.printError("Error opening TACK Key File: %s" % keyPemFile) + except IOError as e: + self.printError("Error opening TACK Key File: %s\n%s" % (keyPemFile, e)) def getTacks(self): fileName = self._getOptionValue("-t") @@ -92,12 +92,12 @@ def getTacks(self): try: contents = open(fileName, "r").read() return Tack.createFromPemList(contents) - except IOError: - self.printError("Error opening tacks: %s" % fileName) - except SyntaxError: - self.printError("Error parsing tacks: %s" % fileName) + except IOError as e: + self.printError("Error opening tacks: %s\n%s" % (fileName, e)) + except SyntaxError as e: + self.printError("Error parsing tacks: %s\n%s" % (fileName, e)) - def getTackExtension(self, mandatory): + def getTackExtension(self, mandatory, extenderFormat=False): fileName = self._getOptionValue("-E") if fileName is None: if mandatory: @@ -106,11 +106,11 @@ def getTackExtension(self, mandatory): return None try: contents = open(fileName, "r").read() - return TackExtension.createFromPem(contents) - except IOError: - self.printError("Error opening extension: %s" % fileName) - except SyntaxError: - self.printError("Error parsing extension: %s" % fileName) + return TackExtension.createFromPem(contents, extenderFormat) + except IOError as e: + self.printError("Error opening extension: %s\n%s" % (fileName, e)) + except SyntaxError as e: + self.printError("Error parsing extension: %s\n%s" % (fileName, e)) def getCertificate(self, mandatory): certificateFile = self._getOptionValue("-c") @@ -128,10 +128,10 @@ def getCertificate(self, mandatory): certificateBytes = bytearray(open(certificateFile, "rb").read()) return TlsCertificate.createFromBytes(certificateBytes) - except SyntaxError: - self.printError("Certificate malformed: %s" % certificateFile) - except IOError: - self.printError("Error opening certificate: %s" % certificateFile) + except SyntaxError as e: + self.printError("Certificate malformed: %s\n%s" % (certificateFile, e)) + except IOError as e: + self.printError("Error opening certificate: %s\n%s" % (certificateFile, e)) def getOutputFile(self): output = None diff --git a/tack/commands/PackCommand.py b/tack/commands/PackCommand.py index 399047b..cf98ae6 100644 --- a/tack/commands/PackCommand.py +++ b/tack/commands/PackCommand.py @@ -20,7 +20,7 @@ def __init__(self, argv): def execute(self): tackExtension = TackExtension.create(self.tacks, self.activationFlags) - self.outputFile.write(self.addPemComments(tackExtension.serializeAsPem())) + self.outputFile.write(self.addPemComments(tackExtension.serializeAsPem(True))) self.printVerbose(str(tackExtension)) def _getActivationFlags(self): diff --git a/tack/commands/UnpackCommand.py b/tack/commands/UnpackCommand.py index 383ce4e..38af1ba 100644 --- a/tack/commands/UnpackCommand.py +++ b/tack/commands/UnpackCommand.py @@ -14,7 +14,7 @@ class UnpackCommand(Command): def __init__(self, argv): Command.__init__(self, argv, "oE", "vx") self.outputFile, self.outputFileName = self.getOutputFile() - self.tackExtension = self.getTackExtension(mandatory=True) + self.tackExtension = self.getTackExtension(mandatory=True, extenderFormat=True) def execute(self): for tack in self.tackExtension.tacks: diff --git a/tack/commands/ViewCommand.py b/tack/commands/ViewCommand.py index fa73694..28363d1 100644 --- a/tack/commands/ViewCommand.py +++ b/tack/commands/ViewCommand.py @@ -40,7 +40,7 @@ def execute(self): return elif decoder.containsEncoded("TACK EXTENSION"): fileType = "TACK Extension" - tackExt = TackExtension.createFromPem(text) + tackExt = TackExtension.createFromPem(text, True) sys.stdout.write(str(tackExt)) return elif decoder.containsEncoded( "CERTIFICATE"): diff --git a/tack/structures/TackExtension.py b/tack/structures/TackExtension.py index ce4d8cb..b918665 100644 --- a/tack/structures/TackExtension.py +++ b/tack/structures/TackExtension.py @@ -12,11 +12,17 @@ class TackExtension(TlsStructure): - def __init__(self, data=None): + def __init__(self, data=None, extenderFormat=False): if data is None: return TlsStructure.__init__(self, data) + if extenderFormat: + extensionType = self.getInt(2) + if extensionType != 62208: + raise SyntaxError("Bad TLS Extension type") + extensionLen = self.getInt(2) + self.tacks = self._parseTacks() self.activation_flags = self.getInt(1) @@ -25,10 +31,12 @@ def __init__(self, data=None): if self.index != len(data): raise SyntaxError("Excess bytes in TACK_Extension") + if extenderFormat and self.index != 4 + extensionLen: + raise SyntaxError("Bad TLS Extension length: %d %d") @classmethod - def createFromPem(cls, data): - return cls(PEMDecoder(data).decode("TACK EXTENSION")) + def createFromPem(cls, data, extenderFormat=False): + return cls(PEMDecoder(data).decode("TACK EXTENSION"), extenderFormat) @classmethod def create(cls, tacks, activation_flags): @@ -38,22 +46,20 @@ def create(cls, tacks, activation_flags): return tackExtension - def serialize(self): - w = TlsStructureWriter(self._getSerializedLength()) - - if self.tacks: - w.add(len(self.tacks) * Tack.LENGTH, 2) - for tack in self.tacks: - w.add(tack.serialize(), Tack.LENGTH) - else: - w.add(0, 2) - + def serialize(self, extenderFormat=False): + assert(self.tacks) + w = TlsStructureWriter(self._getSerializedLength(extenderFormat)) + if extenderFormat: + w.add(62208, 2) + w.add(len(self.tacks) * Tack.LENGTH + 3, 2) + w.add(len(self.tacks) * Tack.LENGTH, 2) + for tack in self.tacks: + w.add(tack.serialize(), Tack.LENGTH) w.add(self.activation_flags, 1) - return w.getBytes() - def serializeAsPem(self): - return PEMEncoder(self.serialize()).encode("TACK EXTENSION") + def serializeAsPem(self, extenderFormat=False): + return PEMEncoder(self.serialize(extenderFormat)).encode("TACK EXTENSION") def verifySignatures(self): for tack in self.tacks: @@ -61,20 +67,19 @@ def verifySignatures(self): return False return True - def _getSerializedLength(self): - length = 0 - if self.tacks: - length += len(self.tacks) * Tack.LENGTH - + def _getSerializedLength(self, extenderFormat=False): + assert(self.tacks) + length = len(self.tacks) * Tack.LENGTH + if extenderFormat: + length += 4 return length + 3 # 2 byes length field, 1 byte flags def _parseTacks(self): tacksLen = self.getInt(2) - if tacksLen: - if tacksLen > 2 * Tack.LENGTH or tacksLen < Tack.LENGTH: - raise SyntaxError("tacks wrong number: %d" % tacksLen) - elif tacksLen % Tack.LENGTH != 0: - raise SyntaxError("tacks wrong size: %d" % tacksLen) + if tacksLen > 2 * Tack.LENGTH or tacksLen < Tack.LENGTH or tacksLen == 0: + raise SyntaxError("tacks wrong number: %d" % tacksLen) + elif tacksLen % Tack.LENGTH != 0: + raise SyntaxError("tacks wrong size: %d" % tacksLen) tacks = [] b2 = self.getBytes(tacksLen) @@ -86,10 +91,9 @@ def _parseTacks(self): def __str__(self): result = "" - - if self.tacks: - for tack in self.tacks: - result += str(tack) + assert(self.tacks) + for tack in self.tacks: + result += str(tack) result += "activation_flags = %d\n" % self.activation_flags From 1d1fee175088acb609b06cc03c352715aa6aebea Mon Sep 17 00:00:00 2001 From: Trevor Date: Tue, 19 Feb 2013 20:50:32 -0800 Subject: [PATCH 90/94] Change sign to take positional args for KEY and CERT. --- Makefile | 14 +++++++------- README | 4 ++-- tack/commands/Command.py | 28 +++++++++------------------- tack/commands/SignCommand.py | 12 ++++++------ 4 files changed, 24 insertions(+), 34 deletions(-) diff --git a/Makefile b/Makefile index 68e91e4..7f7ba5c 100644 --- a/Makefile +++ b/Makefile @@ -34,13 +34,13 @@ test: $(EXEC) genkey -p asdf > $(TESTDIR)/TACK_Key1.pem $(EXEC) genkey -x -p asdf > $(TESTDIR)/TACK_Key2.pem $(EXEC) genkey -p asdf -o $(TESTDIR)/TACK_Key3.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) > $(TESTDIR)/TACK1.pem - cat $(TESTDIR)/TACK_Key1.pem | $(EXEC) sign -k- -p asdf -c $(CERT2) -o $(TESTDIR)/TACK2.pem - $(EXEC) sign -x -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) -m2 -o $(TESTDIR)/TACK3.pem - $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -c $(CERT1) -o $(TESTDIR)/TACK4.pem - $(EXEC) sign -x -k $(TESTDIR)/TACK_Key1.pem -p asdf -e 2030-06-06Z -c $(CERT2) -o $(TESTDIR)/TACK5.pem - cat $(CERT1) | $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -g2 -m2 -c- -o $(TESTDIR)/TACK6.pem - cat $(CERT2) | $(EXEC) sign -k $(TESTDIR)/TACK_Key1.pem -p asdf -m250 -g251 -c - -o $(TESTDIR)/T6 -e 2013-01-02Z -n 3@1d + $(EXEC) sign $(TESTDIR)/TACK_Key1.pem -p asdf $(CERT1) > $(TESTDIR)/TACK1.pem + cat $(TESTDIR)/TACK_Key1.pem | $(EXEC) sign - -p asdf $(CERT2) -o $(TESTDIR)/TACK2.pem + $(EXEC) sign -x $(TESTDIR)/TACK_Key1.pem -p asdf $(CERT1) -m2 -o $(TESTDIR)/TACK3.pem + $(EXEC) sign $(TESTDIR)/TACK_Key1.pem -p asdf $(CERT1) -o $(TESTDIR)/TACK4.pem + $(EXEC) sign -x $(TESTDIR)/TACK_Key1.pem -p asdf -e 2030-06-06Z $(CERT2) -o $(TESTDIR)/TACK5.pem + cat $(CERT1) | $(EXEC) sign $(TESTDIR)/TACK_Key1.pem -p asdf -g2 -m2 - -o $(TESTDIR)/TACK6.pem + cat $(CERT2) | $(EXEC) sign $(TESTDIR)/TACK_Key1.pem -p asdf -m250 -g251 - -o $(TESTDIR)/T6 -e 2013-01-02Z -n 3@1d $(EXEC) pack -t $(TESTDIR)/TACK3.pem > $(TESTDIR)/TACK_Ext3.pem cat $(TESTDIR)/TACK3.pem $(TESTDIR)/TACK4.pem > $(TESTDIR)/TACK3_4.pem $(EXEC) pack -t $(TESTDIR)/TACK3_4.pem > $(TESTDIR)/TACK_Ext3_4.pem diff --git a/README b/README index 7032bab..590be7f 100644 --- a/README +++ b/README @@ -47,7 +47,7 @@ that signs the public key in the server's certificate. To create and deploy these tacks, do the following: Create a tack for a certificate's public key: - 1) Run "tack sign -k KEY.pem -c CERT > TACK.pem". + 1) Run "tack sign KEY.pem CERT > TACK.pem". Deploy tacks to a hostname 1) Deploy tacks to each server at the hostname. @@ -118,7 +118,7 @@ You can generate a batch of tacks with the "-n NUM@INTERVAL" argument to expiration times. The "-o" argument is taken as a filename prefix, and the "-e" time is used as the first expiration time. Example: -tack sign -k KEY.pem -c CERT -n 365@1d -e 2013-01-02Z -o T1 +tack sign KEY.pem CERT -n 365@1d -e 2013-01-02Z -o T1 produces 365 tacks, one expiring at midnight (UTC) each day of 2013: T1_0000.pem diff --git a/tack/commands/Command.py b/tack/commands/Command.py index 4a574b5..fdaf32b 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -20,13 +20,16 @@ class Command: - def __init__(self, argv, options, flags, allowArgRemainder=False): + def __init__(self, argv, options, flags, allowArgRemainder=0): try: self.flags = flags self.options = ":".join(options) + ":" - self.values, self.argRemainder = getopt.getopt(argv, self.options + self.flags) + self.values, self.argRemainder = getopt.gnu_getopt(argv, self.options + self.flags) if not allowArgRemainder and self.argRemainder: self.printError("Too many arguments: %s" % self.argRemainder) + if allowArgRemainder != len(self.argRemainder): + self.printError("Wrong number of arguments: %d should be %d" % \ + (len(self.argRemainder), allowArgRemainder)) # Handle flags if self._containsOption("-x"): @@ -54,15 +57,8 @@ def writeCryptoVersion(self): def getPassword(self): return self._getOptionValue("-p") - def getKeyFile(self, password, mandatory): - keyPemFile = self._getOptionValue("-k") - - if not keyPemFile: - if mandatory: - self.printError("-k missing (TACK Key)") - else: - return None - + def getKeyFile(self, password): + keyPemFile = self.argRemainder[0] if not password: password = self._promptPassword() @@ -112,14 +108,8 @@ def getTackExtension(self, mandatory, extenderFormat=False): except SyntaxError as e: self.printError("Error parsing extension: %s\n%s" % (fileName, e)) - def getCertificate(self, mandatory): - certificateFile = self._getOptionValue("-c") - - if not certificateFile: - if mandatory: - self.printError("-c missing (Certificate)") - else: - return None + def getCertificate(self): + certificateFile = self.argRemainder[1] try: if certificateFile == "-": # Read as binary diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py index 023c558..34582e5 100644 --- a/tack/commands/SignCommand.py +++ b/tack/commands/SignCommand.py @@ -15,11 +15,11 @@ class SignCommand(Command): def __init__(self, argv): - Command.__init__(self, argv, "kcopmgen", "vx") + Command.__init__(self, argv, "opmgen", "vx", 2) self.password = self.getPassword() - self.keyfile = self.getKeyFile(self.password, mandatory=True) + self.keyfile = self.getKeyFile(self.password) - self.certificate = self.getCertificate(mandatory=True) + self.certificate = self.getCertificate() self.generation = self._getGeneration() self.min_generation = self._getMinGeneration() self.expiration = self._getExpiration(self.certificate) @@ -131,10 +131,10 @@ def printHelp(): print( """Creates a TACK based on a target certificate. - sign -k KEY -c CERT + sign KEY CERT - -k KEY : Use this TACK key file ("-" for stdin) - -c CERT : Sign this certificate's public key ("-" for stdin) + KEY : Use this TACK key file ("-" for stdin) + CERT : Sign this certificate's public key ("-" for stdin) Optional arguments: -v : Verbose From b485b56d7b9649b416718d7fec827fc3caff49e1 Mon Sep 17 00:00:00 2001 From: Trevor Date: Tue, 19 Feb 2013 20:50:58 -0800 Subject: [PATCH 91/94] Tweak README for new version. --- README | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README b/README index 590be7f..979a2d1 100644 --- a/README +++ b/README @@ -1,4 +1,4 @@ -Tackpy version 0.9.9a Sep 25 2012 +Tackpy version 0.9.9b Sep 25 2012 ============================================================================ Licenses/Acknowledgements From 60e32140c24b3527fe0edd34c76f6d4fd228fbdd Mon Sep 17 00:00:00 2001 From: Trevor Date: Tue, 19 Feb 2013 21:01:08 -0800 Subject: [PATCH 92/94] Change pack and unpack to take positional args. --- Makefile | 6 +++--- tack/commands/Command.py | 13 +++---------- tack/commands/PackCommand.py | 12 ++++++++---- tack/commands/UnpackCommand.py | 10 ++++++---- 4 files changed, 20 insertions(+), 21 deletions(-) diff --git a/Makefile b/Makefile index 7f7ba5c..cb7d36d 100644 --- a/Makefile +++ b/Makefile @@ -41,10 +41,10 @@ test: $(EXEC) sign -x $(TESTDIR)/TACK_Key1.pem -p asdf -e 2030-06-06Z $(CERT2) -o $(TESTDIR)/TACK5.pem cat $(CERT1) | $(EXEC) sign $(TESTDIR)/TACK_Key1.pem -p asdf -g2 -m2 - -o $(TESTDIR)/TACK6.pem cat $(CERT2) | $(EXEC) sign $(TESTDIR)/TACK_Key1.pem -p asdf -m250 -g251 - -o $(TESTDIR)/T6 -e 2013-01-02Z -n 3@1d - $(EXEC) pack -t $(TESTDIR)/TACK3.pem > $(TESTDIR)/TACK_Ext3.pem + $(EXEC) pack $(TESTDIR)/TACK3.pem > $(TESTDIR)/TACK_Ext3.pem cat $(TESTDIR)/TACK3.pem $(TESTDIR)/TACK4.pem > $(TESTDIR)/TACK3_4.pem - $(EXEC) pack -t $(TESTDIR)/TACK3_4.pem > $(TESTDIR)/TACK_Ext3_4.pem - $(EXEC) unpack -E $(TESTDIR)/TACK_Ext3_4.pem -o $(TESTDIR)/TACK_Ext3_4_Unpack.txt + $(EXEC) pack $(TESTDIR)/TACK3_4.pem > $(TESTDIR)/TACK_Ext3_4.pem + $(EXEC) unpack $(TESTDIR)/TACK_Ext3_4.pem -o $(TESTDIR)/TACK_Ext3_4_Unpack.txt $(EXEC) view $(TESTDIR)/TACK_Key1.pem > $(TESTDIR)/TACK_View_Key1.txt cat $(TESTDIR)/TACK1.pem | $(EXEC) view - > $(TESTDIR)/TACK_View1.txt $(EXEC) v $(CERT1) > $(TESTDIR)/TACK_View_Cert1.txt diff --git a/tack/commands/Command.py b/tack/commands/Command.py index fdaf32b..7a5ca3f 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -82,9 +82,7 @@ def getKeyFile(self, password): self.printError("Error opening TACK Key File: %s\n%s" % (keyPemFile, e)) def getTacks(self): - fileName = self._getOptionValue("-t") - if fileName is None: - return None + fileName = self.argRemainder[0] try: contents = open(fileName, "r").read() return Tack.createFromPemList(contents) @@ -93,13 +91,8 @@ def getTacks(self): except SyntaxError as e: self.printError("Error parsing tacks: %s\n%s" % (fileName, e)) - def getTackExtension(self, mandatory, extenderFormat=False): - fileName = self._getOptionValue("-E") - if fileName is None: - if mandatory: - self.printError("-E missing (TACK Extension)") - else: - return None + def getTackExtension(self, extenderFormat=False): + fileName = self.argRemainder[0] try: contents = open(fileName, "r").read() return TackExtension.createFromPem(contents, extenderFormat) diff --git a/tack/commands/PackCommand.py b/tack/commands/PackCommand.py index cf98ae6..0d7ac6a 100644 --- a/tack/commands/PackCommand.py +++ b/tack/commands/PackCommand.py @@ -12,7 +12,7 @@ class PackCommand(Command): def __init__(self, argv): - Command.__init__(self, argv, "ota", "vx") + Command.__init__(self, argv, "oa", "vx", 1) self.outputFile, self.outputFileName = self.getOutputFile() self.tacks = self.getTacks() self.activationFlags = self._getActivationFlags() @@ -42,12 +42,16 @@ def _getActivationFlags(self): @staticmethod def printHelp(): print( -"""Takes the input Tacks, and Activation Flags, and produces a -TACK_Extension from them. +"""Takes a file containing 1 or more TACK PEM files, and write out a TACK +Extension containing them. + + pack TACKS + + TACKS : Use this Tacks file (PEM format, "-" for stdin) + Optional arguments: -v : Verbose - -t TACKS : Include Tacks from this file. -a FLAG : Activation flag (0...3) -o FILE : Write the output to this file (instead of stdout) """) diff --git a/tack/commands/UnpackCommand.py b/tack/commands/UnpackCommand.py index 38af1ba..679ab3d 100644 --- a/tack/commands/UnpackCommand.py +++ b/tack/commands/UnpackCommand.py @@ -12,9 +12,9 @@ class UnpackCommand(Command): def __init__(self, argv): - Command.__init__(self, argv, "oE", "vx") + Command.__init__(self, argv, "o", "vx", 1) self.outputFile, self.outputFileName = self.getOutputFile() - self.tackExtension = self.getTackExtension(mandatory=True, extenderFormat=True) + self.tackExtension = self.getTackExtension(extenderFormat=True) def execute(self): for tack in self.tackExtension.tacks: @@ -24,9 +24,11 @@ def execute(self): @staticmethod def printHelp(): print( -"""Takes the input TACK Extension, and writes out PEM encodings for its Tacks. +"""Takes the input TACK Extension, and writes out its tacks. - unpack -e EXTENSION + unpack EXTENSION + + EXTENSION : Use this Extension file (PEM format, "-" for stdin) Optional arguments: -v : Verbose From e290d7a9c936dce3800f8d29696d0a80092977f0 Mon Sep 17 00:00:00 2001 From: Trevor Date: Tue, 19 Feb 2013 21:04:03 -0800 Subject: [PATCH 93/94] Oops fix help command. --- tack/commands/Command.py | 11 ++++++----- tack/commands/HelpCommand.py | 2 +- tack/commands/PackCommand.py | 1 - 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tack/commands/Command.py b/tack/commands/Command.py index 7a5ca3f..d98e8ab 100644 --- a/tack/commands/Command.py +++ b/tack/commands/Command.py @@ -25,11 +25,12 @@ def __init__(self, argv, options, flags, allowArgRemainder=0): self.flags = flags self.options = ":".join(options) + ":" self.values, self.argRemainder = getopt.gnu_getopt(argv, self.options + self.flags) - if not allowArgRemainder and self.argRemainder: - self.printError("Too many arguments: %s" % self.argRemainder) - if allowArgRemainder != len(self.argRemainder): - self.printError("Wrong number of arguments: %d should be %d" % \ - (len(self.argRemainder), allowArgRemainder)) + if not allowArgRemainder == -1: + if not allowArgRemainder and self.argRemainder: + self.printError("Too many arguments: %s" % self.argRemainder) + if allowArgRemainder != len(self.argRemainder): + self.printError("Wrong number of arguments: %d should be %d" % \ + (len(self.argRemainder), allowArgRemainder)) # Handle flags if self._containsOption("-x"): diff --git a/tack/commands/HelpCommand.py b/tack/commands/HelpCommand.py index 8bb37fe..7224812 100644 --- a/tack/commands/HelpCommand.py +++ b/tack/commands/HelpCommand.py @@ -19,7 +19,7 @@ class HelpCommand(Command): "view" : ViewCommand, "pack" : PackCommand, "unpack" : UnpackCommand} def __init__(self, argv): - Command.__init__(self, argv, "", "", allowArgRemainder=True) + Command.__init__(self, argv, "", "", allowArgRemainder=-1) if len(self.argRemainder) < 1 or len(self.argRemainder)>1: HelpCommand.printGeneralUsage() diff --git a/tack/commands/PackCommand.py b/tack/commands/PackCommand.py index 0d7ac6a..5dbac8f 100644 --- a/tack/commands/PackCommand.py +++ b/tack/commands/PackCommand.py @@ -49,7 +49,6 @@ def printHelp(): TACKS : Use this Tacks file (PEM format, "-" for stdin) - Optional arguments: -v : Verbose -a FLAG : Activation flag (0...3) From cc34364d7da4591758dff0bf72db4a9b3063e843 Mon Sep 17 00:00:00 2001 From: Trevor Date: Fri, 10 May 2013 12:02:07 -0700 Subject: [PATCH 94/94] Fix OpenSSL free bug. --- tack/crypto/openssl/OpenSSL_ECPrivateKey.py | 1 - tack/crypto/openssl/OpenSSL_ECPublicKey.py | 3 +-- tack/structures/Tack.py | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py index 1358545..5d4262d 100644 --- a/tack/crypto/openssl/OpenSSL_ECPrivateKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py @@ -29,7 +29,6 @@ def __del__(self): def sign(self, data): ecdsa_sig = None - try: # Hash and apply ECDSA hashBuf = bytesToC(Digest.SHA256(data)) diff --git a/tack/crypto/openssl/OpenSSL_ECPublicKey.py b/tack/crypto/openssl/OpenSSL_ECPublicKey.py index 5c1cd60..b51d760 100644 --- a/tack/crypto/openssl/OpenSSL_ECPublicKey.py +++ b/tack/crypto/openssl/OpenSSL_ECPublicKey.py @@ -62,7 +62,6 @@ def getFingerprint(self): def _constructEcFromRawKey(self, rawPublicKey): ec_key, ec_group, ec_point = None, None, None - try: ec_key = o.EC_KEY_new_by_curve_name(o.OBJ_txt2nid(b"prime256v1")) ec_group = o.EC_GROUP_new_by_curve_name(o.OBJ_txt2nid(b"prime256v1")) @@ -78,7 +77,7 @@ def _constructEcFromRawKey(self, rawPublicKey): o.EC_KEY_free(ec_key) if ec_group: - o.EC_KEY_free(ec_group) + o.EC_GROUP_free(ec_group) if ec_point: o.EC_POINT_free(ec_point) diff --git a/tack/structures/Tack.py b/tack/structures/Tack.py index 3890e05..c46f03f 100644 --- a/tack/structures/Tack.py +++ b/tack/structures/Tack.py @@ -70,7 +70,6 @@ def create(cls, public_key, private_key, min_generation, generation, expiration, tack.expiration = expiration tack.target_hash = target_hash tack.signature = private_key.sign(tack._getDataToSign()) - return tack def getTackId(self):