diff --git a/.gitignore b/.gitignore index 0d20b64..99ec428 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ *.pyc +.idea 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/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 index 134f979..cb7d36d 100644 --- a/Makefile +++ b/Makefile @@ -1,64 +1,53 @@ +TESTDIR = testoutput + .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 + @echo To install tackpy run \"./setup.py install\" or \"make install\" + @echo .PHONY: install install: ./setup.py install +.PHONY: dist +dist: + ./setup.py sdist + .PHONY : clean clean: - rm -f src/*.pyc - rm -rf $(SCDIR) - rm -rf $(TESTDIR) + rm -f `find . -name *.pyc` rm -rf build rm -rf dist + rm -rf $(TESTDIR) -.PHONY: selfcontained -selfcontained: - rm -rf $(SCDIR) - mkdir $(SCDIR) - ./make_selfcontained.py > $(SCDIR)/TACK.py - chmod +x $(SCDIR)/TACK.py - -dist: selfcontained - ./setup.py sdist +# 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 > $(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 -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) 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 $(TESTDIR)/TACK3.pem > $(TESTDIR)/TACK_Ext3.pem + cat $(TESTDIR)/TACK3.pem $(TESTDIR)/TACK4.pem > $(TESTDIR)/TACK3_4.pem + $(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 - $(EXEC) view $(TESTDIR)/TACK1.pem > $(TESTDIR)/TACK_View1.txt - $(EXEC) v $(TESTDIR)/TACK_Break_Sigs.pem > $(TESTDIR)/TACK_View_Break_Sigs.txt + cat $(TESTDIR)/TACK1.pem | $(EXEC) view - > $(TESTDIR)/TACK_View1.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 + cat $(CERT2) | $(EXEC) v - > $(TESTDIR)/TACK_View_Cert2.txt + $(EXEC) v $(TESTDIR)/TACK_Ext3.pem > $(TESTDIR)/TACK_View_TACK_Ext3.txt @echo OK diff --git a/README b/README index a513374..979a2d1 100644 --- a/README +++ b/README @@ -1,32 +1,37 @@ -TACKpy version 0.9.6 Feb 23 2012 -Trevor Perrin +Tackpy version 0.9.9b Sep 25 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. +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-line tool. -To use TACK.py without installation you can run "selfcontained/TACK.py". +To use the command-line tool without installation run "./tack.py". -If you have M2Crypto installed, TACKpy will use it for elliptic curve and AES -operations. +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 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. -TACK.py 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 @@ -34,117 +39,104 @@ 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 +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: - -Create a TACK for a certificate's public key: - 1) Run "TACK.py 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. - - -Removing TACKs -=============== - -Disabling pin activation -------------------------- -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 -for all existing client pins to become inactive. +these tacks, do the following: -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. - -(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.) +Create a tack for a certificate's public key: + 1) Run "tack sign KEY.pem CERT > TACK.pem". -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. +Deploy tacks to a hostname + 1) Deploy tacks to each server at the 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 "Pin deactivation"). + 4) Whenever you change a server's certificate, you must replace its tack. -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). -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. +Pin deactivation +================= +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. -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. +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. -Create a break signature for a TACK: - 1) Run "TACK.py break -k KEY.pem > TACK_Break_Sig.pem" - 2) Add the break signature to your web server. +(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.) Advanced uses ============== -Revoking older generations of a TACK +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. +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, -consider using short-lived TACKs. +new min_generation. For a more robust defense against TLS key compromise, +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 signature is set to expire at -the same time as the certificate, and must be replaced by an updated TACK at -that point. +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 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, -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.py 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: +produces 365 tacks, one expiring at midnight (UTC) each day of 2013: T1_0000.pem T1_0001.pem 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. 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/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/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/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/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/draft-tack.txt b/draft-tack.txt deleted file mode 100644 index ea02135..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 5, 2012 -Expires: November 6, 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 6, 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 6, 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 6, 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 6, 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 6, 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 6, 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 6, 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 6, 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 6, 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. - - Note that both the "tack" and "break_sigs" fields MAY be empty. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 6, 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, 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: - - 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 6, 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 6, 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 6, 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 6, 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 6, 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 6, 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 6, 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 6, 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 6, 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 6, 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 6, 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 6, 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 6, 2012 [Page 22] - -Internet-Draft Trust Assertions for Certificate Keys May 2012 - - -Authors' Addresses - - Moxie Marlinspike - - - Trevor Perrin (editor) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -Marlinspike & Perrin Expires November 6, 2012 [Page 23] - diff --git a/draft-tack.xml b/draft-tack.xml deleted file mode 100644 index 971eab8..0000000 --- a/draft-tack.xml +++ /dev/null @@ -1,988 +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. - - - - 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, 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: - - - 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 - - - - - - - - - - - - - - - - - - -
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..829efcc 100755 --- a/setup.py +++ b/setup.py @@ -1,16 +1,38 @@ #!/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 + +shutil.copyfile("tack.py", "tack/tack") + +setup( name="tackpy", + version="0.9.9a", + author="Trevor Perrin,Moxie Marlinspike", + url="http://tack.io", + 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/"): + shutil.rmtree("build/") -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=["scripts/TACK.py"], - packages=["TACKpy"]) +try: + os.remove("tack/tack") +except: + pass diff --git a/tack.py b/tack.py new file mode 100755 index 0000000..17480d7 --- /dev/null +++ b/tack.py @@ -0,0 +1,36 @@ +#! /usr/bin/env python + +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +import sys +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() + +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] == "view"[:len(sys.argv[1])]: + ViewCommand(sys.argv[2:]).execute() + elif sys.argv[1] == "help"[:len(sys.argv[1])]: + HelpCommand(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/InvalidPasswordException.py b/tack/InvalidPasswordException.py new file mode 100644 index 0000000..2534a10 --- /dev/null +++ b/tack/InvalidPasswordException.py @@ -0,0 +1,9 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +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/Command.py b/tack/commands/Command.py new file mode 100644 index 0000000..d98e8ab --- /dev/null +++ b/tack/commands/Command.py @@ -0,0 +1,185 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +import getopt +import getpass +import sys +import time +from tack.structures.TackKeyFile import TackKeyFile +from tack.structures.TackExtension import TackExtension +from tack.structures.Tack import Tack +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 +from tack.version import __version__ +from tack.InvalidPasswordException import InvalidPasswordException + +class Command: + + def __init__(self, argv, options, flags, allowArgRemainder=0): + try: + self.flags = flags + self.options = ":".join(options) + ":" + self.values, self.argRemainder = getopt.gnu_getopt(argv, self.options + self.flags) + 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"): + 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) + + @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 getPassword(self): + return self._getOptionValue("-p") + + def getKeyFile(self, password): + keyPemFile = self.argRemainder[0] + if not password: + password = self._promptPassword() + + try: + if keyPemFile == "-": + keyPemData = sys.stdin.read() + else: + keyPemData = open(keyPemFile, "rU").read() + + while True: + try: + inKey = TackKeyFile.createFromPem(keyPemData, password) + return inKey + except InvalidPasswordException as ipe: + sys.stderr.write("Password incorrect!\n") + password = self._promptPassword() + except SyntaxError as e: + self.printError("Error processing TACK Key File: %s\n%s" % (keyPemFile, e)) + + except IOError as e: + self.printError("Error opening TACK Key File: %s\n%s" % (keyPemFile, e)) + + def getTacks(self): + fileName = self.argRemainder[0] + try: + contents = open(fileName, "r").read() + return Tack.createFromPemList(contents) + 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, extenderFormat=False): + fileName = self.argRemainder[0] + try: + contents = open(fileName, "r").read() + 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): + certificateFile = self.argRemainder[1] + try: + if certificateFile == "-": + # Read as binary + certificateBytes = readStdinBinary() + else: + certificateBytes = bytearray(open(certificateFile, "rb").read()) + + return TlsCertificate.createFromBytes(certificateBytes) + 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 + + 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 getOutputFileName(self): + return self._getOptionValue("-o") + + 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 _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" % fname) + + def printError(self, error): + """Print error message and exit""" + 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 new file mode 100644 index 0000000..3938ac7 --- /dev/null +++ b/tack/commands/GenerateKeyCommand.py @@ -0,0 +1,53 @@ +# 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 +from tack.crypto.ECGenerator import ECGenerator +from tack.structures.TackKeyFile import TackKeyFile + +class GenerateKeyCommand(Command): + + def __init__(self, argv): + Command.__init__(self, argv, "po", "vx") + self.password = self.getPassword() + self.outputFile, self.outputFileName = self.getOutputFile() + + def execute(self): + password = self._getPasswordWithPrompt() + public_key, private_key = ECGenerator.generateECKeyPair() + keyFile = TackKeyFile.create(public_key, private_key, password) + self.outputFile.write(self.addPemComments(keyFile.serializeAsPem())) + self.printVerbose(str(keyFile)) + + def _getPasswordWithPrompt(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 + -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 new file mode 100644 index 0000000..7224812 --- /dev/null +++ b/tack/commands/HelpCommand.py @@ -0,0 +1,57 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +import sys +from tack.version import __version__ +from tack.commands.Command import Command +from tack.commands.GenerateKeyCommand import GenerateKeyCommand +from tack.commands.SignCommand import SignCommand +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, + "view" : ViewCommand, "pack" : PackCommand, "unpack" : UnpackCommand} + + def __init__(self, argv): + Command.__init__(self, argv, "", "", allowArgRemainder=-1) + + if len(self.argRemainder) < 1 or len(self.argRemainder)>1: + HelpCommand.printGeneralUsage() + + self.command = self.argRemainder[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): + if message: + print ("Error: %s\n" % message) + sys.stdout.write( +"""tack.py version %s (%s) + +Commands (use "help " to see optional args): + genkey + sign -k KEY -c CERT + 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..5dbac8f --- /dev/null +++ b/tack/commands/PackCommand.py @@ -0,0 +1,56 @@ +# 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.TackExtension import TackExtension +from tack.tls.TlsCertificate import TlsCertificate + +class PackCommand(Command): + + def __init__(self, argv): + Command.__init__(self, argv, "oa", "vx", 1) + self.outputFile, self.outputFileName = self.getOutputFile() + self.tacks = self.getTacks() + self.activationFlags = self._getActivationFlags() + + def execute(self): + tackExtension = TackExtension.create(self.tacks, self.activationFlags) + + self.outputFile.write(self.addPemComments(tackExtension.serializeAsPem(True))) + self.printVerbose(str(tackExtension)) + + def _getActivationFlags(self): + activation_flags = self._getOptionValue("-a") + + if activation_flags is None: + return 0 + + try: + activation_flags = int(activation_flags) # Could raise ValueError + if activation_flags < 0 or activation_flags > 3: + raise ValueError() + except ValueError: + self.printError("Bad activation_flags: %s" % activation_flags) + + return activation_flags + + + @staticmethod + def printHelp(): + print( +"""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 + -a FLAG : Activation flag (0...3) + -o FILE : Write the output to this file (instead of stdout) +""") diff --git a/tack/commands/SignCommand.py b/tack/commands/SignCommand.py new file mode 100644 index 0000000..34582e5 --- /dev/null +++ b/tack/commands/SignCommand.py @@ -0,0 +1,157 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +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.util.Time import Time + +class SignCommand(Command): + + def __init__(self, argv): + Command.__init__(self, argv, "opmgen", "vx", 2) + self.password = self.getPassword() + self.keyfile = self.getKeyFile(self.password) + + self.certificate = self.getCertificate() + self.generation = self._getGeneration() + self.min_generation = self._getMinGeneration() + 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: + self.outputFileName = self.getOutputFileName() + return + self.outputFile, self.outputFileName = self.getOutputFile() + + + 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) + + self.outputFile.write(self.addPemComments(tack.serializeAsPem())) + self.printVerbose(str(tack)) + else: + # We are signing multiple TACKs, since "-n" was specified + (numTacks, interval) = self.numArg + + if not self.outputFileName: + 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) + + 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) + + self.expiration += interval + self.printVerbose(str(tack)) + + + def _getExpiration(self, certificate): + expiration = self._getOptionValue("-e") + + if expiration is None and self._getNumArg() is None: + # 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) + except SyntaxError as e: + self.printError(e) + + def _getNumArg(self): + numArgRaw = self._getOptionValue("-n") + + if numArgRaw is None: + return None + + try: + leftArg, rightArg = numArgRaw.split("@") # could raise ValueError + numTacks = int(leftArg) # could raise ValueError + interval = Time.parseDeltaArg(rightArg) # SyntaxError + if numTacks < 1 or numTacks >= 10000: + raise ValueError() + return numTacks, interval + except (ValueError, SyntaxError): + self.printError("Bad -n NUMTACKS (1 - 10000): %s:" % numArgRaw) + + 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 certificate. + + sign KEY CERT + + KEY : Use this TACK key file ("-" for stdin) + CERT : Sign this certificate's public key ("-" 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 + -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/UnpackCommand.py b/tack/commands/UnpackCommand.py new file mode 100644 index 0000000..679ab3d --- /dev/null +++ b/tack/commands/UnpackCommand.py @@ -0,0 +1,36 @@ +# 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.TackExtension import TackExtension +from tack.tls.TlsCertificate import TlsCertificate + +class UnpackCommand(Command): + + def __init__(self, argv): + Command.__init__(self, argv, "o", "vx", 1) + self.outputFile, self.outputFileName = self.getOutputFile() + self.tackExtension = self.getTackExtension(extenderFormat=True) + + def execute(self): + for tack in self.tackExtension.tacks: + self.outputFile.write(tack.serializeAsPem()) + self.printVerbose(str(self.tackExtension)) + + @staticmethod + def printHelp(): + print( +"""Takes the input TACK Extension, and writes out its tacks. + + unpack EXTENSION + + EXTENSION : Use this Extension file (PEM format, "-" for stdin) + +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 new file mode 100644 index 0000000..28363d1 --- /dev/null +++ b/tack/commands/ViewCommand.py @@ -0,0 +1,69 @@ +# 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.TackExtension import TackExtension +from tack.structures.Tack import Tack +from tack.structures.TackKeyFile import TackKeyFile +from tack.tls.TlsCertificate import TlsCertificate +from tack.util.PEMDecoder import PEMDecoder + +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: + self.printError("Can only view one file") + + def execute(self): + text, binary = self._readFileTextAndBinary(self.argRemainder[0]) + fileType = None + + try: + if text: + decoder = PEMDecoder(text) + if decoder.containsEncoded("TACK PRIVATE KEY"): + fileType = "Private Key" + kf = TackKeyFile.createFromPem(text, None) + sys.stdout.write(str(kf)) + return + elif decoder.containsEncoded("TACK"): + fileType = "Tack" + tack = Tack.createFromPem(text) + sys.stdout.write(str(tack)) + return + elif decoder.containsEncoded("TACK EXTENSION"): + fileType = "TACK Extension" + tackExt = TackExtension.createFromPem(text, True) + sys.stdout.write(str(tackExt)) + return + elif decoder.containsEncoded( "CERTIFICATE"): + fileType = "Certificate" + sslc = TlsCertificate.createFromPem(text) + sys.stdout.write(str(sslc)) + return + # Is it a certificate? + try: + sslc = TlsCertificate(binary) + sys.stdout.write(str(sslc)) + 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 certificate. + +view [-x] ("-" for stdin) + +Optional arguments: + -x : Use python crypto (not OpenSSL) to verify signatures +""") 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 61% rename from TACKpy/compat.py rename to tack/compat.py index 2a993eb..0c7a0a2 100644 --- a/TACKpy/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. """ @@ -38,8 +40,12 @@ def b2a_base64(b): def b2a_base32(b): return base64.b32encode(b).decode("ascii") - def bytesToStrAscii(b): - return str(b, "ascii") + # 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 +56,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,39 +79,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)) - 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 + # 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()) + + # Decodes all 256 byte values, use "ascii" for first 128 + def bytesToStr(b, encoding="latin-1"): + return b.decode(encoding) diff --git a/tack/crypto/AES.py b/tack/crypto/AES.py new file mode 100644 index 0000000..5af2c4c --- /dev/null +++ b/tack/crypto/AES.py @@ -0,0 +1,18 @@ +# 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 + +class AES: + + @staticmethod + def create(key, IV): + if openssl.enabled: + return OpenSSL_AES(key, IV) + else: + return Python_AES(key, IV) diff --git a/TACKpy/asn1.py b/tack/crypto/ASN1.py similarity index 64% rename from TACKpy/asn1.py rename to tack/crypto/ASN1.py index 458d2e7..442cb70 100644 --- a/TACKpy/asn1.py +++ b/tack/crypto/ASN1.py @@ -1,10 +1,11 @@ -# Author: Trevor Perrin +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# # See the LICENSE file for legal information regarding use of this file. -from .struct_parser import * -from .cryptomath import * +from tack.tls.TlsStructure import TlsStructure -################ ASN1 ### def asn1Length(x): """Return a bytearray encoding an ASN1 length field based on input length. @@ -24,7 +25,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 +57,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 +69,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 +88,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 +96,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 +119,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..6da47eb --- /dev/null +++ b/tack/crypto/Digest.py @@ -0,0 +1,20 @@ +# 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 + +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/ECGenerator.py b/tack/crypto/ECGenerator.py new file mode 100644 index 0000000..a4d27c6 --- /dev/null +++ b/tack/crypto/ECGenerator.py @@ -0,0 +1,18 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +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: + + @staticmethod + def generateECKeyPair(): + if o.enabled: + return OpenSSL_ECGenerator.generateECKeyPair() + else: + return Python_ECGenerator.generateECKeyPair() diff --git a/tack/crypto/ECPrivateKey.py b/tack/crypto/ECPrivateKey.py new file mode 100644 index 0000000..045ee91 --- /dev/null +++ b/tack/crypto/ECPrivateKey.py @@ -0,0 +1,18 @@ +# 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 + +class ECPrivateKey: + + @staticmethod + def create(rawPrivateKey, rawPublicKey): + if o.enabled: + return OpenSSL_ECPrivateKey(rawPrivateKey, rawPublicKey) + else: + return Python_ECPrivateKey(rawPrivateKey, rawPublicKey) diff --git a/tack/crypto/ECPublicKey.py b/tack/crypto/ECPublicKey.py new file mode 100644 index 0000000..f61534a --- /dev/null +++ b/tack/crypto/ECPublicKey.py @@ -0,0 +1,18 @@ +# 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 + +class ECPublicKey: + + @staticmethod + def create(rawPublicKey): + if o.enabled: + return OpenSSL_ECPublicKey(rawPublicKey) + else: + return Python_ECPublicKey(rawPublicKey) \ No newline at end of file diff --git a/tack/crypto/PBKDF2.py b/tack/crypto/PBKDF2.py new file mode 100644 index 0000000..9d4d84d --- /dev/null +++ b/tack/crypto/PBKDF2.py @@ -0,0 +1,24 @@ +# 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: + + @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/crypto/openssl/OpenSSL.py b/tack/crypto/openssl/OpenSSL.py new file mode 100644 index 0000000..491ca18 --- /dev/null +++ b/tack/crypto/openssl/OpenSSL.py @@ -0,0 +1,143 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +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: + + def __init__(self): + self.setInitError("uninitialized") + + def initialize(self): + self.initErrorString = "unknown error loading OpenSSL" + + 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") + raise + + try: + if sys.platform == "win32": + self._lib = CDLL(libraryName) + else: + 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") + 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) + + # 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]) + 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_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) + 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], 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]) + 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.ERR_load_crypto_strings() + self.enabled = True + except: + self.enabled = False + # raise + + + def _add(self, name, ret=None, args=None, skipWrap=False): + try: + func = getattr(self._lib, name) + except: + self.setInitError("error loading OpenSSL:%s" % name) + raise + + if ret: + func.restype = ret + if args: + func.argtypes = args + + if skipWrap: + setattr(self, name, func) + else: + def wrappedFunc(*a): + retval = func(*a) + 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) + + def setInitError(self, s): + self.enabled = False + self.initErrorString = s + +# Singleton, initialize() this once then use it +openssl = OpenSSL() + + + + \ No newline at end of file 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 diff --git a/tack/crypto/openssl/OpenSSL_AES.py b/tack/crypto/openssl/OpenSSL_AES.py new file mode 100644 index 0000000..3532db5 --- /dev/null +++ b/tack/crypto/openssl/OpenSSL_AES.py @@ -0,0 +1,58 @@ +# 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 + +BLOCKSIZE = 16 + +class OpenSSL_AES: + + def __init__(self, key, IV): + if len(key) != 32: + raise AssertionError() + if len(IV) != 16: + raise AssertionError() + self.cipherType = o.EVP_aes_256_cbc() + self.key = key + self.IV = IV + + 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) % BLOCKSIZE == 0) + ctx = None + 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, 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 = cToBytes(outBuf)[:len(inBytes)] + finally: + o.EVP_CIPHER_CTX_cleanup(ctx) + o.EVP_CIPHER_CTX_free(ctx) + + # Update the CBC chaining + if encrypt: + self.IV = outBytes[-BLOCKSIZE:] + else: + self.IV = inBytes[-BLOCKSIZE:] + return outBytes diff --git a/tack/crypto/openssl/OpenSSL_ECGenerator.py b/tack/crypto/openssl/OpenSSL_ECGenerator.py new file mode 100644 index 0000000..a1c64fd --- /dev/null +++ b/tack/crypto/openssl/OpenSSL_ECGenerator.py @@ -0,0 +1,51 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +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(): + 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) + + # Extract the key's public and private values as strings + # into pubBuf and privBuf + 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(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) + 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 = cToBytes(pubBuf)[1:65] + rawPrivateKey = bytearray(32-privLen) + (cToBytes(privBuf)[:privLen]) + + # 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: + 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 new file mode 100644 index 0000000..5d4262d --- /dev/null +++ b/tack/crypto/openssl/OpenSSL_ECPrivateKey.py @@ -0,0 +1,77 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +from tack.crypto.Digest import Digest +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: + + 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): + ecdsa_sig = None + try: + # Hash and apply ECDSA + hashBuf = bytesToC(Digest.SHA256(data)) + ecdsa_sig = o.ECDSA_do_sign(hashBuf, 32, self.ec_key) + + # Encode the signature into 64 bytes + 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) + cToBytes(rBuf)[:rLen] + sBytes = bytearray(32-sLen) + cToBytes(sBuf)[:sLen] + sigBytes = rBytes + sBytes + assert(len(sigBytes) == 64) + finally: + if ecdsa_sig: + 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): + privBignum, ec_key = None, None + + try: + 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) + return o.EC_KEY_dup(ec_key) + finally: + 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 new file mode 100644 index 0000000..b51d760 --- /dev/null +++ b/tack/crypto/openssl/OpenSSL_ECPublicKey.py @@ -0,0 +1,86 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +from tack.compat import b2a_base32 +from tack.crypto.Digest import Digest +from tack.crypto.openssl.OpenSSL import openssl as o +from tack.crypto.openssl.OpenSSL import bytesToC + + +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) + ecdsa_sig = None + + try: + # Create ECDSA_SIG + ecdsa_sig = o.ECDSA_SIG_new() + 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 = bytesToC(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: + if ecdsa_sig: + 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): + 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) + + # Add 0x04 byte to signal "uncompressed" public key + 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) + finally: + if ec_key: + o.EC_KEY_free(ec_key) + + if ec_group: + o.EC_GROUP_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/openssl/__init__.py b/tack/crypto/openssl/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tack/crypto/python/Python_AES.py b/tack/crypto/python/Python_AES.py new file mode 100644 index 0000000..802282e --- /dev/null +++ b/tack/crypto/python/Python_AES.py @@ -0,0 +1,67 @@ +# Authors: +# Trevor Perrin +# +# See the LICENSE file for legal information regarding use of this file. + +from .rijndael import rijndael + +BLOCKSIZE = 16 + +class Python_AES: + + def __init__(self, key, IV): + 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) % BLOCKSIZE == 0) + ciphertextBytes = plaintextBytes[:] + chainBytes = self.IV + + #CBC Mode: For each block... + for x in range(len(ciphertextBytes)//BLOCKSIZE): + + #XOR with the chaining block + 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(BLOCKSIZE): + ciphertextBytes[(x*BLOCKSIZE)+y] = encryptedBytes[y] + + #Set the next chaining block + chainBytes = encryptedBytes + + self.IV = chainBytes + return ciphertextBytes + + def decrypt(self, ciphertextBytes): + assert(len(ciphertextBytes) % BLOCKSIZE == 0) + plaintextBytes = ciphertextBytes[:] + chainBytes = self.IV + + #CBC Mode: For each block... + for x in range(len(plaintextBytes)//BLOCKSIZE): + + #Decrypt it + 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(BLOCKSIZE): + decryptedBytes[y] ^= chainBytes[y] + plaintextBytes[(x*BLOCKSIZE)+y] = decryptedBytes[y] + + #Set the next chaining block + chainBytes = blockBytes + + self.IV = chainBytes + return plaintextBytes diff --git a/tack/crypto/python/Python_ECGenerator.py b/tack/crypto/python/Python_ECGenerator.py new file mode 100644 index 0000000..d4932c6 --- /dev/null +++ b/tack/crypto/python/Python_ECGenerator.py @@ -0,0 +1,37 @@ +# 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 +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)) + + # 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/crypto/python/Python_ECPrivateKey.py b/tack/crypto/python/Python_ECPrivateKey.py new file mode 100644 index 0000000..95620b2 --- /dev/null +++ b/tack/crypto/python/Python_ECPrivateKey.py @@ -0,0 +1,58 @@ +# 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 +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..44bf187 --- /dev/null +++ b/tack/crypto/python/Python_ECPublicKey.py @@ -0,0 +1,40 @@ +# Authors: +# Trevor Perrin +# +# See the LICENSE file for legal information regarding use of this file. + +from tack.compat import b2a_base32 +from tack.crypto.Digest import Digest +from tack.crypto.python.ecdsa import Public_key, Point, generator_256, Signature +from tack.crypto.python.cryptomath import bytesToNumber + +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/__init__.py b/tack/crypto/python/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/TACKpy/cryptomath.py b/tack/crypto/python/cryptomath.py similarity index 57% rename from TACKpy/cryptomath.py rename to tack/crypto/python/cryptomath.py index 4395611..7cf8521 100644 --- a/TACKpy/cryptomath.py +++ b/tack/crypto/python/cryptomath.py @@ -1,11 +1,9 @@ -# Author: Trevor Perrin +# Authors: +# Trevor Perrin +# # See the LICENSE file for legal information regarding use of this file. -from .compat import * - -################ CRYPTOMATH ### - -import math, hashlib, hmac +import math def bytesToNumber(bytes): "Convert a sequence of bytes (eg bytearray) into integer." @@ -54,36 +52,3 @@ def numBytes(n): 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/tack/crypto/python/ecdsa.py b/tack/crypto/python/ecdsa.py new file mode 100644 index 0000000..2366389 --- /dev/null +++ b/tack/crypto/python/ecdsa.py @@ -0,0 +1,232 @@ +# 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 tack.crypto.python.numbertheory import * +from tack.crypto.python.ellipticcurve import * +from tack.compat import compat26Str + +################ 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 ) diff --git a/TACKpy/ellipticcurve.py b/tack/crypto/python/ellipticcurve.py similarity index 60% rename from TACKpy/ellipticcurve.py rename to tack/crypto/python/ellipticcurve.py index a8810a8..e6db86d 100644 --- a/TACKpy/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 @@ -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/TACKpy/numbertheory.py b/tack/crypto/python/numbertheory.py similarity index 78% rename from TACKpy/numbertheory.py rename to tack/crypto/python/numbertheory.py index 0f6f3dd..3b50985 100644 --- a/TACKpy/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/TACKpy/rijndael.py b/tack/crypto/python/rijndael.py similarity index 96% rename from TACKpy/rijndael.py rename to tack/crypto/python/rijndael.py index cca8faa..2d89328 100644 --- a/TACKpy/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 @@ -388,20 +387,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/tack/structures/Tack.py b/tack/structures/Tack.py new file mode 100644 index 0000000..c46f03f --- /dev/null +++ b/tack/structures/Tack.py @@ -0,0 +1,115 @@ +# 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.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): + if data is None: + return + + 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 + 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) + 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.sign(tack._getDataToSign()) + return tack + + def getTackId(self): + return str(self.public_key) + + def serialize(self): + return self._serializePreSig() + self.signature + + def serializeAsPem(self): + return PEMEncoder(self.serialize()).encode("TACK") + + 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. + + Used by the "TACK view" command to display TACK objects.""" + s =\ + """key fingerprint = %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/TackExtension.py b/tack/structures/TackExtension.py new file mode 100644 index 0000000..b918665 --- /dev/null +++ b/tack/structures/TackExtension.py @@ -0,0 +1,100 @@ +# 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.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): + + 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) + + if self.activation_flags > 3: + raise SyntaxError("Bad activation_flag value") + + 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, extenderFormat=False): + return cls(PEMDecoder(data).decode("TACK EXTENSION"), extenderFormat) + + @classmethod + def create(cls, tacks, activation_flags): + tackExtension = cls() + tackExtension.tacks = tacks + tackExtension.activation_flags = activation_flags + + return tackExtension + + 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, extenderFormat=False): + return PEMEncoder(self.serialize(extenderFormat)).encode("TACK EXTENSION") + + def verifySignatures(self): + for tack in self.tacks: + if not tack.verifySignature(): + return False + return True + + 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 > 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) + while b2: + tacks.append(Tack(b2[:Tack.LENGTH])) + b2 = b2[Tack.LENGTH:] + + return tacks + + def __str__(self): + result = "" + assert(self.tacks) + for tack in self.tacks: + result += str(tack) + + result += "activation_flags = %d\n" % self.activation_flags + + return result diff --git a/tack/structures/TackKeyFile.py b/tack/structures/TackKeyFile.py new file mode 100644 index 0000000..1e58520 --- /dev/null +++ b/tack/structures/TackKeyFile.py @@ -0,0 +1,129 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +""" +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.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.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): + if data is None: + return + + TlsStructure.__init__(self, data) + 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") + + 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.create(rawPrivateKey, self.public_key.getRawKey()) + + @classmethod + def create(cls, public_key, private_key, password): + tackKeyFile = cls() + tackKeyFile.version = 1 + tackKeyFile.iter_count = 8192 + tackKeyFile.salt = bytearray(os.urandom(16)) + tackKeyFile.public_key, tackKeyFile.private_key = public_key, private_key + tackKeyFile._encrypt(password) + return tackKeyFile + + @classmethod + def createFromPem(cls, pem, password): + return cls(PEMDecoder(pem).decode("TACK PRIVATE KEY"), password) + + def getPublicKey(self): + return self.public_key + + def getPrivateKey(self): + return self.private_key + + 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.getRawKey(), 64) + w.add(self.mac, 32) + return w.getBytes() + + def serializeAsPem(self): + return PEMEncoder(self.serialize()).encode("TACK PRIVATE KEY") + + def _encrypt(self, password): + encKey, authKey = self._deriveKeys(password, self.salt, self.iter_count) + 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 + self.mac = mac + + 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, self.mac): + raise InvalidPasswordException("Bad password") + + 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): + 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 """key fingerprint = %s\n""" % str(self.public_key) + 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..8c7dc97 --- /dev/null +++ b/tack/tls/TlsCertificate.py @@ -0,0 +1,184 @@ +# 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 +from tack.structures.TackExtension import TackExtension +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=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 + + p = ASN1Parser(data) + + #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(data) + 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 = data[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 += data[extFieldP.offset :\ + extFieldP.offset + extFieldP.getTotalLength()] + x += 1 + + # Finish copying the tail of the certificate + self.postExtBytes = data[certFieldP.offset + certFieldP.getTotalLength():] + + @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), + # 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 serializeAsPem(self): + return PEMEncoder(self.serialize()).encode("CERTIFICATE") + + def __str__(self): + s =\ + """key_sha256 = %s +notAfter = %s +""" % (\ + Util.writeBytes(self.key_sha256), + Time.posixTimeToStr(self.notAfter, True)) + if self.tackExt: + s += "\n" + str(self.tackExt) + return s + + def matches(self, tack): + return self.key_sha256 == tack.target_hash diff --git a/tack/tls/TlsStructure.py b/tack/tls/TlsStructure.py new file mode 100644 index 0000000..4c939be --- /dev/null +++ b/tack/tls/TlsStructure.py @@ -0,0 +1,35 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +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..0ae286a --- /dev/null +++ b/tack/tls/TlsStructureWriter.py @@ -0,0 +1,37 @@ +# 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 + 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): + assert(self.index == len(self.bytes)) + 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..d2ba7b8 --- /dev/null +++ b/tack/util/PEMDecoder.py @@ -0,0 +1,87 @@ +# 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: + + def __init__(self, data): + self.data = data + + def containsEncoded(self, name): + searchStr = "-----BEGIN %s-----" % name + return searchStr in self.data + + def decode(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") + if end < start: + raise SyntaxError("PEM postfix before prefix") + s = self.data[start+len(prefix) : end] + retBytes = a2b_base64(s) + return retBytes + + + 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 + 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..3d8c4a7 --- /dev/null +++ b/tack/util/PEMEncoder.py @@ -0,0 +1,35 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# See the LICENSE file for legal information regarding use of this file. + +from tack.compat import b2a_base64 + +class PEMEncoder: + + def __init__(self, data): + self.data = data + + 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 + 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..536b048 --- /dev/null +++ b/tack/util/Time.py @@ -0,0 +1,111 @@ +# Authors: +# Trevor Perrin +# Moxie Marlinspike +# +# 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 + + @staticmethod + def parseTimeArg(arg): + # First, see if they specified time as a delta + try: + mins = Time.parseDeltaArg(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 delta, 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 parseDeltaArg(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(bytesToStr(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..3376dd3 --- /dev/null +++ b/tack/util/Util.py @@ -0,0 +1,37 @@ +# 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: + @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 84% rename from TACKpy/version.py rename to tack/version.py index 5de8aa0..ad6dcc1 100644 --- a/TACKpy/version.py +++ b/tack/version.py @@ -3,5 +3,5 @@ ################ VERSION ### -__version__ = "0.9.6" +__version__ = "0.9.9a" diff --git a/tests/Certificate_Test.py b/tests/Certificate_Test.py new file mode 100644 index 0000000..5413c5d --- /dev/null +++ b/tests/Certificate_Test.py @@ -0,0 +1,62 @@ +# 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 +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.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") + + # 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__': + unittest.main() \ No newline at end of file diff --git a/tests/Compat_Test.py b/tests/Compat_Test.py new file mode 100644 index 0000000..311ae13 --- /dev/null +++ b/tests/Compat_Test.py @@ -0,0 +1,45 @@ +# 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 +from tack.compat import b2a_hex +from tack.compat import b2a_base64 +from tack.compat import bytesToStr + +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(bytesToStr(bytearray(b"abcd123")) == "abcd123") + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/tests/Crypto_Test.py b/tests/Crypto_Test.py new file mode 100644 index 0000000..3ec09a2 --- /dev/null +++ b/tests/Crypto_Test.py @@ -0,0 +1,67 @@ +# 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.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") + IV = a2b_hex("000102030405060708090A0B0C0D0E0F") + plaintext = a2b_hex("6bc1bee22e409f96e93d7e117393172a") + ciphertext = a2b_hex("f58c4c04d6e5f1ba779eabfb5f7bfbd6") + + 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.sign(data) + assert(publicKey.verify(data, signature)) + assert(not publicKey.verify(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/PythonCrypto_Test.py b/tests/PythonCrypto_Test.py new file mode 100644 index 0000000..9280abe --- /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 diff --git a/tests/Structures_Test.py b/tests/Structures_Test.py new file mode 100644 index 0000000..6ca8432 --- /dev/null +++ b/tests/Structures_Test.py @@ -0,0 +1,67 @@ +# 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 +from tack.structures.Tack import Tack +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.getRawKey() == 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_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.getPublicKey().getRawKey() == publicKey) + assert(kf.getPrivateKey().getRawKey() == privateKey) + kf2 = TackKeyFile.createFromPem(kf.serializeAsPem(), "asdf") + assert(kf2.getPublicKey().getRawKey() == publicKey) + assert(kf2.getPrivateKey().getRawKey() == privateKey) + 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()) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/Time_Test.py b/tests/Time_Test.py new file mode 100644 index 0000000..6edd57a --- /dev/null +++ b/tests/Time_Test.py @@ -0,0 +1,38 @@ +# 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 + +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_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) + 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) + +# !!! Add tests for ASN1 times + +if __name__ == '__main__': + unittest.main() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29