├── .gitignore ├── LICENSE ├── README ├── draft-tack.html ├── draft-tack.txt ├── draft-tack.xml ├── setup.py ├── tack.py ├── tack ├── InvalidPasswordException.py ├── __init__.py ├── commands │ ├── BreakCommand.py │ ├── CertificateCommand.py │ ├── Command.py │ ├── GenerateKeyCommand.py │ ├── HelpCommand.py │ ├── SignCommand.py │ ├── ViewCommand.py │ └── __init__.py ├── compat.py ├── crypto │ ├── AES.py │ ├── ASN1.py │ ├── Digest.py │ ├── ECGenerator.py │ ├── ECPrivateKey.py │ ├── ECPublicKey.py │ ├── PBKDF2.py │ └── __init__.py ├── structures │ ├── Tack.py │ ├── TackActivation.py │ ├── TackBreakSig.py │ ├── TackExtension.py │ ├── TackKeyFile.py │ ├── TackVersion.py │ └── __init__.py ├── tls │ ├── TlsCertificate.py │ ├── TlsStructure.py │ ├── TlsStructureWriter.py │ └── __init__.py ├── util │ ├── PEMDecoder.py │ ├── PEMEncoder.py │ ├── Time.py │ ├── Util.py │ └── __init__.py └── version.py └── tests ├── CertificateTest.py ├── CompatTest.py ├── CryptoTest.py ├── StructuresTest.py ├── TimeTest.py └── __init__.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | TACKpy includes code from different sources. All code is dedicated to the 3 | public domain by its authors. In particular: 4 | 5 | - 6 | 7 | Code written by Trevor Perrin is available under the following terms: 8 | 9 | This is free and unencumbered software released into the public domain. 10 | 11 | Anyone is free to copy, modify, publish, use, compile, sell, or distribute 12 | this software, either in source code form or as a compiled binary, for any 13 | purpose, commercial or non-commercial, and by any means. 14 | 15 | In jurisdictions that recognize copyright laws, the author or authors of this 16 | software dedicate any and all copyright interest in the software to the public 17 | domain. We make this dedication for the benefit of the public at large and to 18 | the detriment of our heirs and successors. We intend this dedication to be an 19 | overt act of relinquishment in perpetuity of all present and future rights to 20 | this software under copyright law. 21 | 22 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 23 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 24 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 25 | AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 26 | ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 27 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 28 | 29 | - 30 | 31 | Code written by Bram Cohen (rijndael.py) was dedicated to the public domain by 32 | its author. See rijndael.py for details. 33 | 34 | - 35 | 36 | Code written by Peter Pearson was dedicated to the public domain by its 37 | author. See numbertheory.py, ellipticcurve.py, and ecdsa.py for details. 38 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | TACKpy version 0.9.6 Feb 23 2012 2 | Trevor Perrin 3 | ============================================================================ 4 | 5 | Licenses/Acknowledgements 6 | ========================== 7 | TACKpy is written (mostly) by Trevor Perrin. It includes crypto code from 8 | Peter Pearson (ECDSA) and Bram Cohen (AES). 9 | 10 | All code in TACKpy has been dedicated to the public domain by its authors. See 11 | the LICENSE file for details. 12 | 13 | 14 | Installation 15 | ============= 16 | TACKpy requires Python 2.6 or greater, or Python 3. 17 | 18 | Run "python setup.py install" or "make install". This installs: 19 | - The "TACKpy" library for use by other Python programs (such as TLS Lite). 20 | - The "TACK.py" command for working with TACKs. 21 | 22 | To use TACK.py without installation you can run "selfcontained/TACK.py". 23 | 24 | If you have M2Crypto installed, TACKpy will use it for elliptic curve and AES 25 | operations. 26 | 27 | 28 | TACK.py quick start 29 | ==================== 30 | You will need to create one or more TACK keys to "pin" your hostnames to. You 31 | should use a different key for each hostname, unless those hostnames are 32 | closely related (such as aliases for the same host, or hosts sharing a TLS 33 | private key). Once you decide how many TACK keys you need, and the assignment 34 | of hostnames to keys, do the following: 35 | 36 | Create a TACK key: 37 | 1) Run "TACK.py genkey > KEY.pem" (replace "KEY" with a specific name) 38 | 2) Back up the key file where it won't be lost or stolen. 39 | 40 | If a hostname is using TACK, each server at that hostname must have a TACK 41 | that signs the public key in the server's certificate. To create and deploy 42 | these TACKs, do the following: 43 | 44 | Create a TACK for a certificate's public key: 45 | 1) Run "TACK.py sign -k KEY.pem -c CERT > TACK.pem". 46 | 47 | Deploy TACKs to a hostname 48 | 1) Deploy TACKs to each server at the hostname. 49 | - Apache: Set "SSLTackFile" to a TACK file. 50 | 2) Once all servers are serving a TACK, activate pinning on each server. 51 | - Apache: Set "SSLTackPinActivation On". 52 | 3) Test the site (if there are problems, see "Removing TACKs"). 53 | 4) Whenever you change a server's certificate, you must replace its TACK. 54 | 55 | 56 | Removing TACKs 57 | =============== 58 | 59 | Disabling pin activation 60 | ------------------------- 61 | If you wish to stop using TACK for a hostname but can tolerate a "waiting 62 | period" before the TACKs are removed, simply disable pin activation at all 63 | servers for that hostname (Apache: "SSLTackPinActivation Off"). Then wait 64 | for all existing client pins to become inactive. 65 | 66 | The waiting period required is equal to the length of time that pin activation 67 | has been enabled for any servers at the hostname, or a maximum of 30 days. 68 | Once the waiting period is elapsed, all TACKs for the hostname can be safely 69 | removed. 70 | 71 | (For example: If you start using a TACK for "example.com", then decide to 72 | disable pin activation after one day, you can remove the TACK at the end of 73 | the second day.) 74 | 75 | Break signatures 76 | ----------------- 77 | If you wish to abruptly stop publishing a TACK for a hostname, or abruptly 78 | change the hostname's TACK key, or signal that a TACK key has been 79 | compromised, then you may publish a "break signature" from the TACK key as 80 | part of the TLS connection. 81 | 82 | This break signature must remain published at the hostname until all pins 83 | between the hostname and the old TACK key have become inactive (30 days at 84 | most; this is exactly the same as the "waiting period" described in previous 85 | section). 86 | 87 | A break signature from a TACK key causes any client who encounters it to 88 | discard all pins involving the TACK key. Thus, once a break signature is 89 | published for a TACK key, all existing pins and TACKS for the TACK key cease 90 | providing security. 91 | 92 | A server can have up to eight break signatures. However, keep in mind that 93 | break signatures add to TLS handshake overhead, so are best avoided. 94 | 95 | Create a break signature for a TACK: 96 | 1) Run "TACK.py break -k KEY.pem > TACK_Break_Sig.pem" 97 | 2) Add the break signature to your web server. 98 | 99 | 100 | Advanced uses 101 | ============== 102 | 103 | Revoking older generations of a TACK 104 | ------------------------------------- 105 | If a server's TLS public key (not its TACK key) has been compromised and you 106 | are switching to a new TLS key, you may revoke the TACK for the old key by "-m 107 | " in the "sign" command. is a number from 108 | 0-255 that is larger than the generation of the TACK you wish to revoke. 109 | 110 | Clients who encounter the new TACK will reject older generations from then on. 111 | Prior to publishing a new you should replace all your TACKs 112 | with this generation number (or higher) by signing with "-g ". 113 | 114 | For example: By default TACK signatures have generation=0, so the first time 115 | you use this capability you will want to set "-m1" after pushing out a new set 116 | of TACKs signed with "-g1". If you use it a second time, you will set "-m2", 117 | and so on. 118 | 119 | Security Consideration: This only provides protection if clients receive the 120 | new min_generation. For a more robust defense against SSL key compromise, 121 | consider using short-lived TACKs. 122 | 123 | Short-lived TACKs 124 | ------------------ 125 | Every TACK contains a signature covering a TLS public key. The TLS key is 126 | contained in a certificate. By default the TACK signature is set to expire at 127 | the same time as the certificate, and must be replaced by an updated TACK at 128 | that point. 129 | 130 | If you shorten the TACK's expiration time, then a compromised SSL certificate 131 | will become unusable to an attacker once the TACK expires. For example, every 132 | day at midnight you could deploy a new TACK that expires within 48 hours. 133 | 134 | A good way to handle short-lived TACKs is to generate a batch of them and 135 | store the TACKs on a secure system that distributes them to servers. This way, 136 | you do not have to use your TACK key to sign new TACKs frequently. 137 | 138 | You can generate a batch of TACKs with the "-n NUM@INTERVAL" argument to 139 | "sign", specifying the number of TACKs and the interval between their 140 | expiration times. The "-o" argument is taken as a filename prefix, and the 141 | "-e" time is used as the first expiration time. Example: 142 | 143 | TACK.py sign -k KEY.pem -c CERT -n 365@1d -e 2013-01-02Z -o T1 144 | 145 | produces 365 TACKs, one expiring at midnight (UTC) each day of 2013: 146 | T1_0000.pem 147 | T1_0001.pem 148 | T1_0002.pem 149 | ... 150 | T1_0364.pem 151 | -------------------------------------------------------------------------------- /draft-tack.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | TLS Working Group M. Marlinspike 5 | Internet-Draft T. Perrin, Ed. 6 | Intended status: Standards Track May 5, 2012 7 | Expires: November 6, 2012 8 | 9 | 10 | Trust Assertions for Certificate Keys 11 | draft-tack.txt 12 | 13 | Abstract 14 | 15 | This document defines TACK, a TLS Extension that enables a TLS server 16 | to assert the authenticity of its public key. A TACK contains a 17 | "TACK key" which is used to sign the public key from the TLS server's 18 | certificate. Hostnames can be "pinned" to a TACK key. TLS 19 | connections to a pinned hostname require the server to present a TACK 20 | containing the pinned key and a corresponding signature over the TLS 21 | server's public key. 22 | 23 | Status of this Memo 24 | 25 | This Internet-Draft is submitted in full conformance with the 26 | provisions of BCP 78 and BCP 79. 27 | 28 | Internet-Drafts are working documents of the Internet Engineering 29 | Task Force (IETF). Note that other groups may also distribute 30 | working documents as Internet-Drafts. The list of current Internet- 31 | Drafts is at http://datatracker.ietf.org/drafts/current/. 32 | 33 | Internet-Drafts are draft documents valid for a maximum of six months 34 | and may be updated, replaced, or obsoleted by other documents at any 35 | time. It is inappropriate to use Internet-Drafts as reference 36 | material or to cite them other than as "work in progress." 37 | 38 | This Internet-Draft will expire on November 6, 2012. 39 | 40 | Copyright Notice 41 | 42 | Copyright (c) 2012 IETF Trust and the persons identified as the 43 | document authors. All rights reserved. 44 | 45 | This document is subject to BCP 78 and the IETF Trust's Legal 46 | Provisions Relating to IETF Documents 47 | (http://trustee.ietf.org/license-info) in effect on the date of 48 | publication of this document. Please review these documents 49 | carefully, as they describe your rights and restrictions with respect 50 | to this document. Code Components extracted from this document must 51 | include Simplified BSD License text as described in Section 4.e of 52 | 53 | 54 | 55 | Marlinspike & Perrin Expires November 6, 2012 [Page 1] 56 | 57 | Internet-Draft Trust Assertions for Certificate Keys May 2012 58 | 59 | 60 | the Trust Legal Provisions and are provided without warranty as 61 | described in the Simplified BSD License. 62 | 63 | 64 | Table of Contents 65 | 66 | 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . . 3 67 | 2. Requirements notation . . . . . . . . . . . . . . . . . . . . 4 68 | 3. Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 69 | 3.1. TACK life cycle . . . . . . . . . . . . . . . . . . . . . 5 70 | 3.2. Pin life cycle . . . . . . . . . . . . . . . . . . . . . . 6 71 | 4. TACK Extension . . . . . . . . . . . . . . . . . . . . . . . . 7 72 | 4.1. Definition of TACK_Extension . . . . . . . . . . . . . . . 7 73 | 4.2. Explanation of TACK_Extension fields . . . . . . . . . . . 8 74 | 4.2.1. TACK fields . . . . . . . . . . . . . . . . . . . . . 8 75 | 4.2.2. TACK_Break_Sig fields . . . . . . . . . . . . . . . . 8 76 | 4.2.3. TACK_Extension fields . . . . . . . . . . . . . . . . 9 77 | 5. Client processing . . . . . . . . . . . . . . . . . . . . . . 10 78 | 5.1. TACK pins, key records, and name records . . . . . . . . . 10 79 | 5.2. High-level client processing . . . . . . . . . . . . . . . 10 80 | 5.3. Client processing details . . . . . . . . . . . . . . . . 11 81 | 5.3.1. Check whether the TLS handshake is well-formed . . . . 11 82 | 5.3.2. Check the TACK generation and update min_generation . 12 83 | 5.3.3. Check whether the TACK is expired . . . . . . . . . . 12 84 | 5.3.4. Create and activate pins (optional) . . . . . . . . . 12 85 | 5.3.5. Discard pins based on break signatures . . . . . . . . 13 86 | 5.3.6. Deleting pins . . . . . . . . . . . . . . . . . . . . 13 87 | 6. Variations on client processing . . . . . . . . . . . . . . . 14 88 | 6.1. TACK and certificate verification . . . . . . . . . . . . 14 89 | 6.2. Application-specific pinning . . . . . . . . . . . . . . . 14 90 | 7. TACK IDs . . . . . . . . . . . . . . . . . . . . . . . . . . . 15 91 | 8. Advice . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 92 | 8.1. For server operators . . . . . . . . . . . . . . . . . . . 16 93 | 8.2. For client implementers . . . . . . . . . . . . . . . . . 17 94 | 9. Security considerations . . . . . . . . . . . . . . . . . . . 18 95 | 9.1. For server operators . . . . . . . . . . . . . . . . . . . 18 96 | 9.2. For client implementers . . . . . . . . . . . . . . . . . 18 97 | 9.3. Note on algorithm agility . . . . . . . . . . . . . . . . 19 98 | 10. IANA considerations . . . . . . . . . . . . . . . . . . . . . 20 99 | 10.1. New entry for the TLS ExtensionType Registry . . . . . . . 20 100 | 11. Acknowledgements . . . . . . . . . . . . . . . . . . . . . . . 21 101 | 12. Normative references . . . . . . . . . . . . . . . . . . . . . 22 102 | Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . . 23 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | Marlinspike & Perrin Expires November 6, 2012 [Page 2] 112 | 113 | Internet-Draft Trust Assertions for Certificate Keys May 2012 114 | 115 | 116 | 1. Introduction 117 | 118 | Traditionally, a TLS client verifies a TLS server's public key using 119 | a certificate chain issued by some public CA. "Pinning" is a way for 120 | clients to obtain increased certainty in server public keys. Clients 121 | that employ pinning check for some constant "pinned" element of the 122 | TLS connection when contacting a particular TLS host. 123 | 124 | Unfortunately, a number of problems arise when attempting to pin 125 | certificate chains: the TLS servers at a given hostname may have 126 | different certificate chains simultaneously deployed and may change 127 | their chains at any time, the "more constant" elements of a chain 128 | (the CAs) may not be trustworthy, and the client may be oblivious to 129 | key compromise events which render the pinned data untrustworthy. 130 | 131 | TACK addresses these problems by having the site sign its TLS server 132 | public keys with a "TACK key". This enables clients to "pin" a 133 | hostname to the TACK key without requiring sites to modify their 134 | existing certificate chains, and without limiting a site's 135 | flexibility to deploy different certificate chains on different 136 | servers or change certificate chains at any time. Since TACK pins 137 | are based on TACK keys (instead of CA keys), trust in CAs is not 138 | required. Additionally, the TACK key may be used to revoke previous 139 | TACK signatures (or even itself) in order to handle the compromise of 140 | TLS or TACK private keys. 141 | 142 | If requested, a compliant server will send a TLS Extension containing 143 | its "TACK". Inside the TACK is a public key and signature. Once a 144 | client has seen the same (hostname, TACK public key) pair multiple 145 | times, the client will "activate" a pin between the hostname and TACK 146 | key for a period equal to the length of time the pair has been 147 | observed for. This "pin activation" process limits the impact of bad 148 | pins resulting from transient network attacks or operator error. 149 | 150 | TACK pins are easily shared between clients. For example, a TACK 151 | client may scan the internet to discover TACK pins, then publish 152 | these pins for other clients to rely upon. 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | Marlinspike & Perrin Expires November 6, 2012 [Page 3] 168 | 169 | Internet-Draft Trust Assertions for Certificate Keys May 2012 170 | 171 | 172 | 2. Requirements notation 173 | 174 | The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 175 | "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this 176 | document are to be interpreted as described in [RFC2119]. 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | Marlinspike & Perrin Expires November 6, 2012 [Page 4] 224 | 225 | Internet-Draft Trust Assertions for Certificate Keys May 2012 226 | 227 | 228 | 3. Overview 229 | 230 | 3.1. TACK life cycle 231 | 232 | A server operator using TACK may perform several processes: 233 | 234 | Selection of a TACK key: The server operator first chooses the ECDSA 235 | signing key to use for a set of hostnames. It is safest to use a 236 | different signing key for each hostname, though a signing key may 237 | be reused for closely-related hostnames (such as aliases for the 238 | same host, or hosts sharing the same TLS key). 239 | 240 | Creating initial TACKs under a TACK key: The TACK private key is 241 | then used to sign the TLS public keys for all servers associated 242 | with those hostnames. The TACK public key and signature are 243 | combined with some metadata into each server's "TACK". 244 | 245 | Deploying initial TACKs: For each hostname, TACKs are deployed to 246 | TLS servers in a two-stage process. First, each TLS server 247 | associated with the hostname is given a TACK. Once this is 248 | completed, pin activation is enabled on the servers. 249 | 250 | Creating new TACKs under a TACK key: A TACK needs to be replaced 251 | whenever a server changes its TLS public key, or when the TACK 252 | expires. TACKs may also need to be replaced with later-generation 253 | TACKs if the TACK key's "min_generation" is updated (see next). 254 | 255 | Revoking old TACKs: If a TLS private key is compromised, the TACKs 256 | signing this key can be revoked by publishing a new TACK 257 | containing a higher "min_generation". 258 | 259 | Revoking TACK keys: If a TACK private key is compromised, or a 260 | server operator wishes to stop using TACK or abruptly change its 261 | TACK key for any reason, a server can revoke an entire TACK key 262 | (including all TACKs and pins referring to it) by publishing a 263 | "break signature". 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | Marlinspike & Perrin Expires November 6, 2012 [Page 5] 280 | 281 | Internet-Draft Trust Assertions for Certificate Keys May 2012 282 | 283 | 284 | 3.2. Pin life cycle 285 | 286 | A TACK client maintains a store of pins for verifying TLS 287 | connections. Pins associate a hostname and a TACK key. When a 288 | client sees a new hostname and TACK key combination, an inactive pin 289 | is created. Every subsequent time the client sees the same pin, the 290 | pin is "activated" for a period equal to the timespan between the 291 | first time the pin was seen and the most recent time, up to a maximum 292 | period of 30 days. 293 | 294 | Pin activation prevents an attacker with short-lived control of the 295 | hostname from activating long-lived pins. It also makes it safer for 296 | sites to experiment with TACKs, as a new TACK can be discarded 297 | without causing long-lived problems. The 30 day limit guarantees 298 | that a worst-case pin can be recovered from in reasonable time. 299 | 300 | In addition to creating and activating pins, a TLS connection can 301 | alter the clients's pin store by publishing revocation data: 302 | 303 | Min_generation: Each pin stores the highest "min_generation" value 304 | it has seen from the pinned TACK key, and rejects TACKs from 305 | earlier generations. 306 | 307 | Break signatures: A TLS handshake may send break signatures which 308 | cause all pins for the broken key to be discarded. 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | Marlinspike & Perrin Expires November 6, 2012 [Page 6] 336 | 337 | Internet-Draft Trust Assertions for Certificate Keys May 2012 338 | 339 | 340 | 4. TACK Extension 341 | 342 | 4.1. Definition of TACK_Extension 343 | 344 | A new TLS ExtensionType ("tack") is defined and MAY be included by a 345 | TLS client in the ClientHello message defined in [RFC5246]. 346 | 347 | enum {tack(TBD), (65535)} ExtensionType; 348 | 349 | The "extension_data" field of this ClientHello SHALL be empty. A TLS 350 | server which is not resuming a TLS session MAY respond with an 351 | extension of type "tack" in the ServerHello. The "extension_data" 352 | field of this ServerHello SHALL contain a "TACK_Extension", as 353 | defined below using the TLS presentation language from [RFC5246]. 354 | 355 | enum (disabled(0), enabled(1)} TACK_Activation; 356 | 357 | struct { 358 | opaque public_key[64]; 359 | uint8 min_generation; 360 | uint8 generation; 361 | uint32 expiration; 362 | opaque target_hash[32]; 363 | opaque signature[64]; 364 | } TACK; /* 166 bytes */ 365 | 366 | struct { 367 | opaque public_key[64]; 368 | opaque signature[64]; 369 | } TACK_Break_Sig; /* 128 bytes */ 370 | 371 | struct { 372 | TACK tack<0...166> /* 0 or 1 TACK */ 373 | TACK_Break_Sig break_sigs<0...1024> /* 0...8 Break Sigs */ 374 | TACK_Activation pin_activation; 375 | } TACK_Extension; 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | Marlinspike & Perrin Expires November 6, 2012 [Page 7] 392 | 393 | Internet-Draft Trust Assertions for Certificate Keys May 2012 394 | 395 | 396 | 4.2. Explanation of TACK_Extension fields 397 | 398 | 4.2.1. TACK fields 399 | 400 | public_key: This field specifies the TACK's public key. The field 401 | contains a pair of integers (x, y) representing a point on the 402 | elliptic curve P-256 defined in [FIPS186-3]. Each integer is 403 | encoded as a 32-byte octet string using the Integer-to-Octet- 404 | String algorithm from [RFC6090], and these strings are 405 | concatenated with the x value first. (NOTE: This is equivalent to 406 | an uncompressed subjectPublicKey from [RFC5480], except that the 407 | initial 0x04 byte is omitted). 408 | 409 | min_generation: This field publishes a min_generation value. 410 | 411 | generation: This field assigns each TACK a generation. Generations 412 | less than a published min_generation are considered revoked. 413 | 414 | expiration: This field specifies a time after which the TACK is 415 | considered expired. The time is encoded as the number of minutes, 416 | excluding leap seconds, after midnight UTC, January 1 1970. 417 | 418 | target_hash: This field is a hash of the TLS server's 419 | SubjectPublicKeyInfo [RFC5280] using the SHA256 algorithm from 420 | [FIPS180-2]. The SubjectPublicKeyInfo is typically conveyed as 421 | part of the server's X.509 certificate. 422 | 423 | signature: This field is an ECDSA signature by the TACK's public key 424 | over the 8 byte ASCII string "tack_sig" followed by the contents 425 | of the TACK prior to the "signature" field (i.e. the preceding 102 426 | bytes). The field contains a pair of integers (r, s) representing 427 | an ECDSA signature as defined in [FIPS186-3], using curve P-256 428 | and SHA256. Each integer is encoded as a 32-byte octet string 429 | using the Integer-to-Octet-String algorithm from [RFC6090], and 430 | these strings are concatenated with the r value first. 431 | 432 | 4.2.2. TACK_Break_Sig fields 433 | 434 | public_key: This field specifies the TACK key being broken. The key 435 | is encoded as per TACK.public_key. 436 | 437 | signature: This field is an ECDSA signature by the TACK_Break_Sig's 438 | public key over the 14 byte ASCII string "tack_break_sig". The 439 | field contains a pair of integers (r, s) representing an ECDSA 440 | signature as defined in [FIPS186-3], using curve P-256 and SHA256. 441 | It is calculated and encoded as per TACK.signature. 442 | 443 | 444 | 445 | 446 | 447 | Marlinspike & Perrin Expires November 6, 2012 [Page 8] 448 | 449 | Internet-Draft Trust Assertions for Certificate Keys May 2012 450 | 451 | 452 | 4.2.3. TACK_Extension fields 453 | 454 | tack: This field provides the server's TACK. It MAY be empty, or 455 | MAY contain a TACK. 456 | 457 | break_sigs: This field provides break signatures. It MAY be empty, 458 | or MAY contain up to 8 break signatures. 459 | 460 | pin_activation: If pin activation is enabled, then the 461 | TACK_Extension MAY be used by clients to activate or extend the 462 | activation of TACK pins. This field is typically toggled from a 463 | disabled to an enabled state once TACKs have been deployed to all 464 | TLS servers for a hostname. 465 | 466 | Note that both the "tack" and "break_sigs" fields MAY be empty. 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | Marlinspike & Perrin Expires November 6, 2012 [Page 9] 504 | 505 | Internet-Draft Trust Assertions for Certificate Keys May 2012 506 | 507 | 508 | 5. Client processing 509 | 510 | 5.1. TACK pins, key records, and name records 511 | 512 | A client supporting TACK SHALL have a local store of pins, consisting 513 | of "key records" and "name records". Each name record is associated 514 | with a key record. Multiple name records MAY be associated with one 515 | key record. A "pin" refers to a (name record, key record) pair. 516 | 517 | A "key record" contains: 518 | 519 | TACK public key (or hash): A public key or a cryptographically- 520 | secure, second preimage-resistant hash of a public key. A client 521 | SHALL NOT store multiple key records referencing the same key. 522 | 523 | Min_generation: A single byte used to detect revoked TACKs. 524 | 525 | A "name record" contains: 526 | 527 | Name: A fully qualified DNS hostname. A client SHALL NOT store 528 | multiple name records with the same name. The TLS server's 529 | hostname is considered the "relevant name", and a pin whose name 530 | exactly matches the relevant name is considered a "relevant pin". 531 | 532 | Initial timestamp: A timestamp noting when this pin was created. 533 | 534 | Active period end: Empty or a timestamp. If empty or set to a 535 | time in the past, the pin is "inactive". If set to a future time, 536 | the pin is "active" until that time. 537 | 538 | 5.2. High-level client processing 539 | 540 | A TACK client SHALL send the "tack" extension defined previously, as 541 | well as the "server_name" extension from [RFC6066] indicating the 542 | hostname the client is contacting. If not resuming a session, the 543 | server MAY respond with a TACK_Extension. A TACK client SHALL 544 | perform the following steps prior to using a non-resumed connection: 545 | 546 | 1. Check whether the TLS handshake is "well-formed". 547 | 548 | 2. Check the TACK generation and update min_generation. 549 | 550 | 3. Check whether the TACK is expired. 551 | 552 | 4. Create and activate pins (optional). 553 | 554 | 5. Discard pins based on break signatures. 555 | 556 | 557 | 558 | 559 | Marlinspike & Perrin Expires November 6, 2012 [Page 10] 560 | 561 | Internet-Draft Trust Assertions for Certificate Keys May 2012 562 | 563 | 564 | These steps SHALL be performed in order. If there is any error, the 565 | client SHALL send a fatal error alert and close the connection, 566 | skipping the remaining steps (see Section 5.3 for details). 567 | 568 | After the above steps, if there is a relevant active pin and a TACK 569 | whose key is referenced by the pin, then the connection is "accepted" 570 | by the pin. If there is a relevant active pin but no such TACK, the 571 | connection is "rejected" by the pin. If there is no relevant active 572 | pin, the connection is "unpinned". 573 | 574 | A rejected connection might indicate a network attack. If the 575 | connection is rejected the client SHOULD send a fatal "access_denied" 576 | error alert and close the connection. 577 | 578 | A client MAY perform additional verification steps before using an 579 | accepted or unpinned connection. See Section 6.1 for an example. 580 | 581 | 5.3. Client processing details 582 | 583 | 5.3.1. Check whether the TLS handshake is well-formed 584 | 585 | A TLS handshake is "well-formed" if the following are true (the error 586 | alert to be sent on a failure is indicated in parentheses): 587 | 588 | 1. The handshake protocol negotiates a cryptographically secure 589 | ciphersuite and finishes succesfully (else see [RFC5246]). 590 | 591 | 2. The handshake contains either no TACK_Extension or a 592 | syntactically-valid TACK_Extension (else "decode_error"). 593 | 594 | 3. If break signatures are present, the signatures are correct (else 595 | "decrypt_error"). This step is optional, as break signature 596 | verification MAY be deferred till later. 597 | 598 | 4. If a TACK is present, it is "well-formed" by the rules below. 599 | 600 | A TACK is "well-formed" if: 601 | 602 | 1. "public_key" is a valid elliptic curve public key on the curve 603 | P-256 (else "decrypt_error"). 604 | 605 | 2. "generation" is >= "min_generation" (else "decode_error"). 606 | 607 | 3. "target_hash" is equal to the SHA256 hash of the server's 608 | SubjectPublicKeyInfo (else "illegal_parameter"). 609 | 610 | 4. "signature" is a correct ECDSA signature (else "decrypt_error"). 611 | 612 | 613 | 614 | 615 | Marlinspike & Perrin Expires November 6, 2012 [Page 11] 616 | 617 | Internet-Draft Trust Assertions for Certificate Keys May 2012 618 | 619 | 620 | 5.3.2. Check the TACK generation and update min_generation 621 | 622 | If there is a TACK and a key record referencing the TACK key, and the 623 | TACK's generation is less than the key record's min_generation, then 624 | the TACK is revoked and the client SHALL send the 625 | "certificate_revoked" alert and close the connection. 626 | 627 | Otherwise, if there is a TACK and a key record referencing the TACK 628 | key, and the TACK's min_generation is greater than the key record's 629 | min_generation, then the key record's min_generation SHALL be set to 630 | the TACK's value. 631 | 632 | 5.3.3. Check whether the TACK is expired 633 | 634 | If there is a TACK and the TACK's "expiration" field specifies a time 635 | in the past, the client SHALL send the "certificate_expired" alert 636 | and close the connection. 637 | 638 | 5.3.4. Create and activate pins (optional) 639 | 640 | The TLS connection MAY be used to create, delete, and activate pins 641 | as described in this section. Note that this section is optional; a 642 | client MAY rely on an external source of pins, provided the external 643 | pins are produced by a client following the below algorithms. 644 | 645 | If there is a TACK and a relevant pin referencing the TACK key, and 646 | pin activation is enabled, the name record's "active period end" 647 | SHALL be set using the below formula (where "current" is the current 648 | time, and "initial" is the "initial timestamp" from the name record): 649 | 650 | active_period_end = current + MIN(30 days, current - initial) 651 | 652 | If there is a TACK and either no relevant pin or an inactive relevant 653 | pin that does not reference the TACK key, a new pin SHALL be created: 654 | 655 | 1. If the TACK key is referenced by an existing key record, the key 656 | record is reused, otherwise a new key record is created with the 657 | TACK's key and min_generation. 658 | 659 | 2. A new name record is created containing the relevant name, an 660 | "initial timestamp" equal to the current time, and an empty 661 | "active period end". 662 | 663 | 3. If there is an existing relevant pin, the pin SHALL be deleted 664 | (see Section 5.3.6). 665 | 666 | If there is no TACK and the relevant pin is inactive, the pin SHALL 667 | be deleted (see Section 5.3.6). 668 | 669 | 670 | 671 | Marlinspike & Perrin Expires November 6, 2012 [Page 12] 672 | 673 | Internet-Draft Trust Assertions for Certificate Keys May 2012 674 | 675 | 676 | The following table summarizes this behavior based on whether the 677 | relevant pin is active and references the TACK key. The "(*)" means 678 | "if pin activation is enabled". 679 | 680 | +------------+---------------------+-------------------------------+ 681 | | Pin status | Pin references TACK | Result | 682 | +------------+---------------------+-------------------------------+ 683 | | Active | Yes | Extend activation period (*) | 684 | | | | | 685 | | Active | No (or no TACK) | Rejected | 686 | | | | | 687 | | Inactive | Yes | Activate pin (*) | 688 | | | | | 689 | | Inactive | No | Replace with new inactive pin | 690 | | | | | 691 | | Inactive | No TACK | Delete pin | 692 | | | | | 693 | | No pin | - | Create new inactive pin | 694 | | | | | 695 | | No pin | No TACK | - | 696 | +------------+---------------------+-------------------------------+ 697 | 698 | 5.3.5. Discard pins based on break signatures 699 | 700 | All key records broken by break signatures SHALL be discarded, along 701 | with their associated name records. A key record is broken by a 702 | break signature if the break signature passes the following checks: 703 | 704 | 1. "public_key" is referenced by the key record. 705 | 706 | 2. "signature" is a correct ECDSA signature (else "decrypt_error"). 707 | 708 | 5.3.6. Deleting pins 709 | 710 | A client might need to delete a pin from its store as a result of the 711 | algorithms in Section 5.3.4. A client MAY also delete pins from its 712 | store at any time, whether to save space, protect privacy, or for any 713 | other reason. To delete a pin, its name record SHALL be removed. If 714 | this leaves a key record with no associated name records, the key 715 | record MAY be removed as well. Pins MAY be deleted regardless of 716 | whether they are active or inactive, however for security concerns 717 | regarding pin deletion, see Section 9.2. 718 | 719 | Deleting pins unnecessarily will reduce the benefits of TACK, so 720 | SHOULD be avoided. Note that a pin SHOULD NOT be deleted simply 721 | because it has become inactive. Instead, such a pin SHOULD be 722 | retained, so that it can be re-activated in the future by the 723 | algorithms in Section 5.3.4. 724 | 725 | 726 | 727 | Marlinspike & Perrin Expires November 6, 2012 [Page 13] 728 | 729 | Internet-Draft Trust Assertions for Certificate Keys May 2012 730 | 731 | 732 | 6. Variations on client processing 733 | 734 | 6.1. TACK and certificate verification 735 | 736 | A TACK client MAY choose to perform some form of certificate 737 | verification in addition to TACK processing. When combining 738 | certificate verification and TACK processing, the TACK processing 739 | described in Section 5 SHALL be followed, with the exception that 740 | TACK processing MAY be terminated early (or skipped) if some fatal 741 | certificate error is discovered. 742 | 743 | If TACK processing and certificate verification both complete without 744 | a fatal error, the client SHALL apply some policy to decide whether 745 | to accept the connection. The policy is up to the client. An 746 | example policy would be to accept the connection only if it passes 747 | certificate verification and is not rejected by a pin, or if the user 748 | elects to "connect anyway" despite certificate and/or pin failures. 749 | 750 | 6.2. Application-specific pinning 751 | 752 | In addition to the hostname-based pinning described in Section 5, 753 | some applications may require "application-specific pins", where an 754 | application-layer name is pinned to a TACK key. For example, an SMTP 755 | MTA may wish to authenticate receiving MTAs by pinning email domains 756 | to the receiving MTAs' TACK keys. 757 | 758 | Application-specific pins may require redefinition of the name 759 | record's "name" field, the "relevant name" for the TLS connection, 760 | and the "pin activation" signal. With these items redefined, the 761 | client processing rules in Section 5 may be reused. 762 | 763 | Note that a server using application-specific pins is still subject 764 | to hostname pins, and a client MAY apply either or both forms of 765 | pinning. 766 | 767 | The specification of application-specific pinning for particular 768 | applications is outside the scope of this document. 769 | 770 | 771 | 772 | 773 | 774 | 775 | 776 | 777 | 778 | 779 | 780 | 781 | 782 | 783 | Marlinspike & Perrin Expires November 6, 2012 [Page 14] 784 | 785 | Internet-Draft Trust Assertions for Certificate Keys May 2012 786 | 787 | 788 | 7. TACK IDs 789 | 790 | A "TACK ID" MAY be used to represent a TACK public key to users in a 791 | form that is relatively easy to compare and transcribe. A TACK ID 792 | consists of the first 25 characters from the base32 encoding of 793 | SHA256(public_key), split into 5 groups of 5 characters separated by 794 | periods. Base32 encoding is as specified in [RFC4648], except 795 | lowercase is used. 796 | 797 | Example TACK IDs: 798 | 799 | quxiz.kpldu.uuedc.j5znm.7mqst 800 | 801 | a334f.bt7ts.ljb3b.y24ij.6zhwm 802 | 803 | ebsx7.z22qt.okobu.ibhut.xzdny 804 | 805 | 806 | 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 | 815 | 816 | 817 | 818 | 819 | 820 | 821 | 822 | 823 | 824 | 825 | 826 | 827 | 828 | 829 | 830 | 831 | 832 | 833 | 834 | 835 | 836 | 837 | 838 | 839 | Marlinspike & Perrin Expires November 6, 2012 [Page 15] 840 | 841 | Internet-Draft Trust Assertions for Certificate Keys May 2012 842 | 843 | 844 | 8. Advice 845 | 846 | 8.1. For server operators 847 | 848 | Key reuse: All servers that are pinned to a single TACK key are able 849 | to impersonate each other, since clients will perceive their TACKs 850 | as equivalent. Thus, TACK keys SHOULD NOT be reused with 851 | different hostnames unless these hostnames are closely related. 852 | Examples where it would be safe to reuse a TACK key are hostnames 853 | aliased to the same host, hosts sharing the same TLS key, or 854 | hostnames for a group of near-identical servers. 855 | 856 | Aliases: A TLS server may be referenced by multiple hostnames. 857 | Clients may pin any of these hostnames. Server operators should 858 | be careful when using such DNS aliases that hostnames are not 859 | pinned inadvertently. 860 | 861 | Generations: To revoke older generations of TACKs, the server 862 | operator SHOULD first provide all servers with a new generation of 863 | TACKs, and only then provide servers with new TACKs containing the 864 | new min_generation. Otherwise, a client may receive a 865 | min_generation update from one server but then try to contact an 866 | older-generation server which has not yet been updated. 867 | 868 | Signature expiration: It is convenient to set the TACK expiration 869 | equal to the end-entity certificate expiration, so that the TACK 870 | and certificate may both be replaced at the same time. 871 | Alternatively, short-lived TACKs may be used so that a compromised 872 | TLS private key has limited value to an attacker. 873 | 874 | Break signatures: A break signature only needs to be published for a 875 | time interval equal to the maximum active period of any affected 876 | pins. For example, if a TACK has been only been published on a 877 | website for 24 hours, to remove the TACK only requires publishing 878 | the break signature for 24 hours. 879 | 880 | Pin activation: Pin activation SHOULD only be enabled once all TLS 881 | servers sharing the same hostname have a TACK. Otherwise, a 882 | client may activate a pin by contacting one server, then contact a 883 | different server at the same hostname that does not yet have a 884 | TACK. 885 | 886 | Pin deactivation: The pin_activation field can be used to phase out 887 | TACKs for a hostname. If all servers at a hostname disable pin 888 | activation, all existing pins for the hostname will eventually 889 | become inactive, at which point the servers' TACKs can be removed. 890 | 891 | 892 | 893 | 894 | 895 | Marlinspike & Perrin Expires November 6, 2012 [Page 16] 896 | 897 | Internet-Draft Trust Assertions for Certificate Keys May 2012 898 | 899 | 900 | 8.2. For client implementers 901 | 902 | Sharing pin information: It is possible for a client to maintain a 903 | pin store based entirely on its own TLS connections. However, 904 | such a client runs the risk of creating incorrect pins, failing to 905 | keep its pins active, or failing to receive revocation information 906 | (min_generation updates and break signatures). Clients are 907 | advised to collaborate so that pin data can be aggregated and 908 | shared. This will require additional protocols outside the scope 909 | of this document. 910 | 911 | Clock synchronization: A client SHOULD take measures to prevent 912 | TACKs from being erroneously rejected due to an inaccurate client 913 | clock. Such methods MAY include using time synchronization 914 | protocols such as NTP [RFC5905], or accepting seemingly-expired 915 | TACKs if they expired less than T minutes ago, where T is a 916 | "tolerance bound" set to the client's maximum expected clock 917 | error. 918 | 919 | 920 | 921 | 922 | 923 | 924 | 925 | 926 | 927 | 928 | 929 | 930 | 931 | 932 | 933 | 934 | 935 | 936 | 937 | 938 | 939 | 940 | 941 | 942 | 943 | 944 | 945 | 946 | 947 | 948 | 949 | 950 | 951 | Marlinspike & Perrin Expires November 6, 2012 [Page 17] 952 | 953 | Internet-Draft Trust Assertions for Certificate Keys May 2012 954 | 955 | 956 | 9. Security considerations 957 | 958 | 9.1. For server operators 959 | 960 | All servers pinned to the same TACK key can impersonate each other 961 | (see Section 8.1). Think carefully about this risk if using the same 962 | TACK key for multiple hostnames. 963 | 964 | Make backup copies of the TACK private key and keep all copies in 965 | secure locations where they can't be compromised. 966 | 967 | A TACK private key MUST NOT be used to perform any non-TACK 968 | cryptographic operations. For example, using a TACK key for email 969 | encryption, code-signing, or any other purpose MUST NOT be done. 970 | 971 | HTTP cookies [RFC6265] set by a pinned host can be stolen by a 972 | network attacker who can forge web and DNS responses so as to cause a 973 | client to send the cookies to a phony subdomain of the pinned host. 974 | To prevent this, TACK HTTPS Servers SHOULD set the "secure" attribute 975 | and omit the "domain" attribute on all security-sensitive cookies, 976 | such as session cookies. These settings tell the browser that the 977 | cookie should only be presented back to the originating host (not its 978 | subdomains), and should only be sent over HTTPS (not HTTP) [RFC6265]. 979 | 980 | 9.2. For client implementers 981 | 982 | A TACK pin store may contain private details of the client's 983 | connection history. An attacker may be able to access this 984 | information by hacking or stealing the client. Some information 985 | about the client's connection history could also be gleaned by 986 | observing whether the client accepts or rejects connections to phony 987 | TLS servers without correct TACKs. To mitigate these risks, a TACK 988 | client SHOULD allow the user to edit or clear the pin store. 989 | 990 | Aside from rejecting TLS connections, clients SHOULD NOT take any 991 | actions which would reveal to a network observer the state of the 992 | client's pin store, as this would allow an attacker to know in 993 | advance whether a "man-in-the-middle" attack on a particular TLS 994 | connection will succeed or be detected. 995 | 996 | An attacker may attempt to flood a client with spurious TACKs for 997 | different hostnames, causing the client to delete old pins to make 998 | space for new ones. To defend against this, clients SHOULD NOT 999 | delete active pins to make space for new pins. Clients instead 1000 | SHOULD delete inactive pins. If there are no inactive pins to 1001 | delete, then the pin store is full and there is no space for new 1002 | pins. To select an inactive pin for deletion, the client SHOULD 1003 | delete the pin with the oldest "active_period_end". 1004 | 1005 | 1006 | 1007 | Marlinspike & Perrin Expires November 6, 2012 [Page 18] 1008 | 1009 | Internet-Draft Trust Assertions for Certificate Keys May 2012 1010 | 1011 | 1012 | 9.3. Note on algorithm agility 1013 | 1014 | If the need arises for TACKs using different cryptographic algorithms 1015 | (e.g., if SHA256 or ECDSA are shown to be weak), a "v2" version of 1016 | TACKs could be defined, requiring assignment of a new TLS Extension 1017 | number. TACKs as defined in this document would then be known as 1018 | "v1" TACKs. 1019 | 1020 | 1021 | 1022 | 1023 | 1024 | 1025 | 1026 | 1027 | 1028 | 1029 | 1030 | 1031 | 1032 | 1033 | 1034 | 1035 | 1036 | 1037 | 1038 | 1039 | 1040 | 1041 | 1042 | 1043 | 1044 | 1045 | 1046 | 1047 | 1048 | 1049 | 1050 | 1051 | 1052 | 1053 | 1054 | 1055 | 1056 | 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | Marlinspike & Perrin Expires November 6, 2012 [Page 19] 1064 | 1065 | Internet-Draft Trust Assertions for Certificate Keys May 2012 1066 | 1067 | 1068 | 10. IANA considerations 1069 | 1070 | 10.1. New entry for the TLS ExtensionType Registry 1071 | 1072 | IANA is requested to add an entry to the existing TLS ExtensionType 1073 | registry, defined in [RFC5246], for tack(TBD) as defined in this 1074 | document. 1075 | 1076 | 1077 | 1078 | 1079 | 1080 | 1081 | 1082 | 1083 | 1084 | 1085 | 1086 | 1087 | 1088 | 1089 | 1090 | 1091 | 1092 | 1093 | 1094 | 1095 | 1096 | 1097 | 1098 | 1099 | 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 | 1109 | 1110 | 1111 | 1112 | 1113 | 1114 | 1115 | 1116 | 1117 | 1118 | 1119 | Marlinspike & Perrin Expires November 6, 2012 [Page 20] 1120 | 1121 | Internet-Draft Trust Assertions for Certificate Keys May 2012 1122 | 1123 | 1124 | 11. Acknowledgements 1125 | 1126 | Valuable feedback has been provided by Adam Langley, Chris Palmer, 1127 | Nate Lawson, and Joseph Bonneau. 1128 | 1129 | 1130 | 1131 | 1132 | 1133 | 1134 | 1135 | 1136 | 1137 | 1138 | 1139 | 1140 | 1141 | 1142 | 1143 | 1144 | 1145 | 1146 | 1147 | 1148 | 1149 | 1150 | 1151 | 1152 | 1153 | 1154 | 1155 | 1156 | 1157 | 1158 | 1159 | 1160 | 1161 | 1162 | 1163 | 1164 | 1165 | 1166 | 1167 | 1168 | 1169 | 1170 | 1171 | 1172 | 1173 | 1174 | 1175 | Marlinspike & Perrin Expires November 6, 2012 [Page 21] 1176 | 1177 | Internet-Draft Trust Assertions for Certificate Keys May 2012 1178 | 1179 | 1180 | 12. Normative references 1181 | 1182 | [FIPS180-2] 1183 | National Institute of Standards and Technology, "Secure 1184 | Hash Standard", FIPS PUB 180-2, August 2002, . 1186 | 1187 | [FIPS186-3] 1188 | National Institute of Standards and Technology, "Digital 1189 | Signature Standard", FIPS PUB 186-3, June 2009, . 1191 | 1192 | [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate 1193 | Requirement Levels", BCP 14, RFC 2119, March 1997. 1194 | 1195 | [RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data 1196 | Encodings", RFC 4648, October 2006. 1197 | 1198 | [RFC5246] Dierks, T. and E. Rescorla, "The Transport Layer Security 1199 | (TLS) Protocol Version 1.2", RFC 5246, August 2008. 1200 | 1201 | [RFC5280] Cooper, D., Santesson, S., Farrell, S., Boeyen, S., 1202 | Housley, R., and W. Polk, "Internet X.509 Public Key 1203 | Infrastructure Certificate and Certificate Revocation List 1204 | (CRL) Profile", RFC 5280, May 2008. 1205 | 1206 | [RFC5480] Turner, S., Brown, D., Yiu, K., Housley, R., and T. Polk, 1207 | "Elliptic Curve Cryptography Subject Public Key 1208 | Information", RFC 5480, March 2009. 1209 | 1210 | [RFC5905] Mills, D., Martin, J., Burbank, J., and W. Kasch, "Network 1211 | Time Protocol Version 4: Protocol and Algorithms 1212 | Specification", RFC 5905, June 2010. 1213 | 1214 | [RFC6066] Eastlake, D., "Transport Layer Security (TLS) Extensions: 1215 | Extension Definitions", RFC 6066, January 2011. 1216 | 1217 | [RFC6090] McGrew, D., Igoe, K., and M. Salter, "Fundamental Elliptic 1218 | Curve Cryptography Algorithms", RFC 6090, February 2011. 1219 | 1220 | [RFC6265] Barth, A., "HTTP State Management Mechanism", RFC 6265, 1221 | April 2011. 1222 | 1223 | 1224 | 1225 | 1226 | 1227 | 1228 | 1229 | 1230 | 1231 | Marlinspike & Perrin Expires November 6, 2012 [Page 22] 1232 | 1233 | Internet-Draft Trust Assertions for Certificate Keys May 2012 1234 | 1235 | 1236 | Authors' Addresses 1237 | 1238 | Moxie Marlinspike 1239 | 1240 | 1241 | Trevor Perrin (editor) 1242 | 1243 | 1244 | 1245 | 1246 | 1247 | 1248 | 1249 | 1250 | 1251 | 1252 | 1253 | 1254 | 1255 | 1256 | 1257 | 1258 | 1259 | 1260 | 1261 | 1262 | 1263 | 1264 | 1265 | 1266 | 1267 | 1268 | 1269 | 1270 | 1271 | 1272 | 1273 | 1274 | 1275 | 1276 | 1277 | 1278 | 1279 | 1280 | 1281 | 1282 | 1283 | 1284 | 1285 | 1286 | 1287 | Marlinspike & Perrin Expires November 6, 2012 [Page 23] 1288 | 1289 | -------------------------------------------------------------------------------- /draft-tack.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | Trust Assertions for Certificate Keys 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | Security 21 | TLS Working Group 22 | 23 | 24 | 25 | This document defines TACK, a TLS Extension that enables a TLS server to 26 | assert the authenticity of its public key. A TACK contains a "TACK key" 27 | which is used to sign the public key from the TLS server's certificate. 28 | Hostnames can be "pinned" to a TACK key. TLS connections to a pinned 29 | hostname require the server to present a TACK containing the pinned key 30 | and a corresponding signature over the TLS server's public key. 31 | 32 | 33 | 34 | 35 | 36 | 37 |
38 | 39 | 40 | Traditionally, a TLS client verifies a TLS server's public key using a 41 | certificate chain issued by some public CA. "Pinning" is a way for clients to 42 | obtain increased certainty in server public keys. Clients that employ pinning 43 | check for some constant "pinned" element of the TLS connection when 44 | contacting a particular TLS host. 45 | 46 | 47 | 48 | Unfortunately, a number of problems arise when attempting to pin certificate 49 | chains: the TLS servers at a given hostname may have different certificate 50 | chains simultaneously deployed and may change their chains at any time, the 51 | "more constant" elements of a chain (the CAs) may not be trustworthy, and the 52 | client may be oblivious to key compromise events which render the pinned data 53 | untrustworthy. 54 | 55 | 56 | 57 | 58 | 59 | TACK addresses these problems by having the site sign its TLS server public 60 | keys with a "TACK key". This enables clients to "pin" a hostname to the TACK 61 | key without requiring sites to modify their existing certificate chains, and 62 | without limiting a site's flexibility to deploy different certificate chains 63 | on different servers or change certificate chains at any time. Since TACK pins 64 | are based on TACK keys (instead of CA keys), trust in CAs is not required. 65 | Additionally, the TACK key may be used to revoke previous TACK signatures (or 66 | even itself) in order to handle the compromise of TLS or TACK private keys. 67 | 68 | 69 | 70 | 71 | If requested, a compliant server will send a TLS Extension containing its 72 | "TACK". Inside the TACK is a public key and signature. Once a client has seen 73 | the same (hostname, TACK public key) pair multiple times, the client will 74 | "activate" a pin between the hostname and TACK key for a period equal to the 75 | length of time the pair has been observed for. This "pin activation" process 76 | limits the impact of bad pins resulting from transient network attacks or 77 | operator error. 78 | 79 | 80 | 81 | 82 | TACK pins are easily shared between clients. For example, a TACK client 83 | may scan the internet to discover TACK pins, then publish these pins for other 84 | clients to rely upon. 85 | 86 | 87 |
88 | 89 | 90 |
The key words "MUST", 91 | "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", 92 | "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as 93 | described in . 94 |
95 | 96 |
97 | 98 |
99 | 100 | A server operator using TACK may perform several processes: 101 | 102 | 103 | 104 | 105 | The server operator first chooses the ECDSA signing key to use for a set of 106 | hostnames. It is safest to use a different signing key for each hostname, 107 | though a signing key may be reused for closely-related hostnames (such as 108 | aliases for the same host, or hosts sharing the same TLS key). 109 | 110 | 111 | 112 | 113 | The TACK private key is then used to sign the TLS public keys for all servers 114 | associated with those hostnames. The TACK public key and signature are 115 | combined with some metadata into each server's "TACK". 116 | 117 | 118 | 119 | 120 | For each hostname, TACKs are deployed to TLS servers in a two-stage process. 121 | First, each TLS server associated with the hostname is given a TACK. Once this 122 | is completed, pin activation is enabled on the servers. 123 | 124 | 125 | 126 | 127 | 128 | A TACK needs to be replaced whenever a server changes its TLS public key, or 129 | when the TACK expires. TACKs may also need to be replaced with 130 | later-generation TACKs if the TACK key's "min_generation" is updated (see 131 | next). 132 | 133 | 134 | 135 | 136 | If a TLS private key is compromised, the TACKs signing this key can be revoked by 137 | publishing a new TACK containing a higher "min_generation". 138 | 139 | 140 | 141 | 142 | If a TACK private key is compromised, or a server operator wishes to stop using 143 | TACK or abruptly change its TACK key for any reason, a server can revoke an entire 144 | TACK key (including all TACKs and pins referring to it) by publishing a "break 145 | signature". 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 |
154 | 155 |
156 | 157 | 158 | 159 | A TACK client maintains a store of pins for verifying TLS connections. Pins 160 | associate a hostname and a TACK key. When a client sees a new hostname and 161 | TACK key combination, an inactive pin is created. Every subsequent time the 162 | client sees the same pin, the pin is "activated" for a period equal to the 163 | timespan between the first time the pin was seen and the most recent time, up 164 | to a maximum period of 30 days. 165 | 166 | 167 | 168 | 169 | Pin activation prevents an attacker with short-lived control of the hostname 170 | from activating long-lived pins. It also makes it safer for sites to 171 | experiment with TACKs, as a new TACK can be discarded without causing 172 | long-lived problems. The 30 day limit guarantees that a worst-case pin can be 173 | recovered from in reasonable time. 174 | 175 | 176 | 177 | 178 | In addition to creating and activating pins, a TLS connection can alter the 179 | clients's pin store by publishing revocation data: 181 | 182 | Each pin stores the highest "min_generation" value it has seen from the pinned 183 | TACK key, and rejects TACKs from earlier generations. 184 | 185 | 186 | 187 | 188 | 189 | A TLS handshake may send break signatures which cause all pins for the 190 | broken key to be discarded. 191 | 192 | 193 | 194 | 195 | 196 |
197 |
198 | 199 |
200 |
201 | 202 | 203 | A new TLS ExtensionType ("tack") is defined and MAY be included by a TLS 204 | client in the ClientHello message defined in . 205 | 206 | 207 |
208 | enum {tack(TBD), (65535)} ExtensionType; 209 |
210 | 211 | 212 | The "extension_data" field of this ClientHello SHALL be empty. A TLS server 213 | which is not resuming a TLS session MAY respond with an extension of type 214 | "tack" in the ServerHello. The "extension_data" field of this ServerHello 215 | SHALL contain a "TACK_Extension", as defined below using the TLS presentation 216 | language from . 217 | 218 | 219 |
220 | enum (disabled(0), enabled(1)} TACK_Activation; 221 | 222 | struct { 223 | opaque public_key[64]; 224 | uint8 min_generation; 225 | uint8 generation; 226 | uint32 expiration; 227 | opaque target_hash[32]; 228 | opaque signature[64]; 229 | } TACK; /* 166 bytes */ 230 | 231 | struct { 232 | opaque public_key[64]; 233 | opaque signature[64]; 234 | } TACK_Break_Sig; /* 128 bytes */ 235 | 236 | struct { 237 | TACK tack<0...166> /* 0 or 1 TACK */ 238 | TACK_Break_Sig break_sigs<0...1024> /* 0...8 Break Sigs */ 239 | TACK_Activation pin_activation; 240 | } TACK_Extension; 241 | 242 |
243 | 244 | 245 | 246 | 247 |
248 |
249 | 250 |
251 | 252 | 253 | 254 | 255 | 256 | This field specifies the TACK's public key. The field contains a pair 257 | of integers (x, y) representing a point on the elliptic curve P-256 defined in 258 | . Each integer is encoded as a 32-byte octet string 259 | using the Integer-to-Octet-String algorithm from , and 260 | these strings are concatenated with the x value first. (NOTE: This is 261 | equivalent to an uncompressed subjectPublicKey from , 262 | except that the initial 0x04 byte is omitted). 263 | 264 | 265 | 266 | 267 | 268 | This field publishes a min_generation value. 269 | 270 | 271 | 272 | 273 | 274 | This field assigns each TACK a generation. Generations less than a published 275 | min_generation are considered revoked. 276 | 277 | 278 | 279 | 280 | 281 | This field specifies a time after which the TACK is considered expired. The 282 | time is encoded as the number of minutes, excluding leap seconds, after 283 | midnight UTC, January 1 1970. 284 | 285 | 286 | 287 | This field is a hash of the TLS server's SubjectPublicKeyInfo using the SHA256 algorithm from . 289 | The SubjectPublicKeyInfo is typically conveyed as part of the server's X.509 290 | certificate. 291 | 292 | 293 | 294 | 295 | 296 | This field is an ECDSA signature by the TACK's public key over the 8 byte 297 | ASCII string "tack_sig" followed by the contents of the TACK prior to the 298 | "signature" field (i.e. the preceding 102 bytes). The field contains a pair of 299 | integers (r, s) representing an ECDSA signature as defined in , using curve P-256 and SHA256. Each integer is encoded as 301 | a 32-byte octet string using the Integer-to-Octet-String algorithm from , and these strings are concatenated with the r value first. 303 | 304 | 305 | 306 | 307 |
308 | 309 |
310 | 311 | 312 | 313 | 314 | 315 | This field specifies the TACK key being broken. The key is encoded as per 316 | TACK.public_key. 317 | 318 | 319 | 320 | 321 | 322 | This field is an ECDSA signature by the TACK_Break_Sig's public key over the 323 | 14 byte ASCII string "tack_break_sig". The field contains a pair of integers 324 | (r, s) representing an ECDSA signature as defined in , using curve P-256 and SHA256. It is calculated and 326 | encoded as per TACK.signature. 327 | 328 | 329 | 330 | 331 | 332 | 333 |
334 | 335 |
336 | 337 | 338 | 339 | 340 | 341 | This field provides the server's TACK. It MAY be empty, or MAY contain a TACK. 342 | 343 | 344 | 345 | 346 | This field provides break signatures. It MAY be empty, or MAY contain up to 8 347 | break signatures. 348 | 349 | 350 | 351 | 352 | If pin activation is enabled, then the TACK_Extension MAY be used by clients 353 | to activate or extend the activation of TACK pins. This field is typically 354 | toggled from a disabled to an enabled state once TACKs have been deployed to 355 | all TLS servers for a hostname. 356 | 357 | 358 | 359 | Note that both the "tack" and "break_sigs" fields MAY be empty. 360 | 361 |
362 | 363 |
364 |
365 | 366 |
367 |
368 | 369 | 370 | 371 | A client supporting TACK SHALL have a local store of pins, consisting of "key 372 | records" and "name records". Each name record is associated with a key record. 373 | Multiple name records MAY be associated with one key record. A "pin" refers to 374 | a (name record, key record) pair. 375 | 376 | 377 | 378 | 379 | A "key record" contains: 380 | 381 | 382 | 383 | 384 | TACK public key (or hash): A public key or a cryptographically-secure, second 385 | preimage-resistant hash of a public key. A client SHALL NOT store multiple key 386 | records referencing the same key. 387 | 388 | 389 | 390 | 391 | Min_generation: A single byte used to detect revoked TACKs. 392 | 393 | 394 | 395 | 396 | 397 | 398 | A "name record" contains: 399 | 400 | 401 | 402 | 403 | 404 | Name: A fully qualified DNS hostname. A client SHALL NOT store multiple name 405 | records with the same name. The TLS server's hostname is considered the 406 | "relevant name", and a pin whose name exactly matches the relevant name is 407 | considered a "relevant pin". 408 | 409 | 410 | 411 | 412 | Initial timestamp: A timestamp noting when this pin was created. 413 | 414 | 415 | 416 | 417 | Active period end: Empty or a timestamp. If empty or set to a time in the 418 | past, the pin is "inactive". If set to a future time, the pin is "active" 419 | until that time. 420 | 421 | 422 | 423 | 424 | 425 | 426 |
427 | 428 |
429 | 430 | 431 | 432 | A TACK client SHALL send the "tack" extension defined previously, as well as 433 | the "server_name" extension from indicating the 434 | hostname the client is contacting. If not resuming a session, the server MAY 435 | respond with a TACK_Extension. A TACK client SHALL perform the following steps 436 | prior to using a non-resumed connection: 437 | 438 | 439 | Check whether the TLS handshake is "well-formed". 440 | Check the TACK generation and update min_generation. 441 | Check whether the TACK is expired. 442 | Create and activate pins (optional). 443 | Discard pins based on break signatures. 444 | 445 | 446 | These steps SHALL be performed in order. If there is any error, the client 447 | SHALL send a fatal error alert and close the connection, skipping the 448 | remaining steps (see for details). 449 | 450 | 451 | 452 | 453 | 454 | After the above steps, if there is a relevant active pin and a TACK whose key 455 | is referenced by the pin, then the connection is "accepted" by the pin. If 456 | there is a relevant active pin but no such TACK, the connection is "rejected" 457 | by the pin. If there is no relevant active pin, the connection is "unpinned". 458 | 459 | 460 | A rejected connection might indicate a network attack. If the connection 461 | is rejected the client SHOULD send a fatal "access_denied" error alert and 462 | close the connection. 463 | 464 | A client MAY perform additional verification steps before using an 465 | accepted or unpinned connection. See for an 466 | example. 467 | 468 |
469 | 470 |
472 | 473 | 474 | 475 | A TLS handshake is "well-formed" if the following are true (the error alert to 476 | be sent on a failure is indicated in parentheses): 477 | 478 | 479 | 480 | The handshake protocol negotiates a cryptographically secure ciphersuite 481 | and finishes succesfully (else see ). 482 | 483 | The handshake contains either no TACK_Extension or a syntactically-valid 484 | TACK_Extension (else "decode_error"). 485 | 486 | If break signatures are present, the signatures are correct (else 487 | "decrypt_error"). This step is optional, as break signature verification MAY 488 | be deferred till later. 489 | 490 | If a TACK is present, it is "well-formed" by the rules below. 491 | 492 | 493 | 494 | 495 | 496 | A TACK is "well-formed" if: 497 | 498 | 499 | "public_key" is a valid elliptic curve public key on the curve P-256 (else 500 | "decrypt_error"). 501 | 502 | "generation" is >= "min_generation" (else "decode_error"). 503 | 504 | "target_hash" is equal to the SHA256 hash of the server's 505 | SubjectPublicKeyInfo (else "illegal_parameter"). 506 | 507 | "signature" is a correct ECDSA signature (else "decrypt_error"). 508 | 509 | 510 | 511 |
512 | 513 |
514 | 515 | 516 | If there is a TACK and a key record referencing the TACK key, and the TACK's 517 | generation is less than the key record's min_generation, then the TACK is 518 | revoked and the client SHALL send the "certificate_revoked" alert and close 519 | the connection. 520 | 521 | 522 | Otherwise, if there is a TACK and a key record referencing the TACK key, 523 | and the TACK's min_generation is greater than the key record's min_generation, 524 | then the key record's min_generation SHALL be set to the TACK's value. 525 | 526 | 527 |
528 |
529 | 530 | 531 | If there is a TACK and the TACK's "expiration" field specifies a time in the 532 | past, the client SHALL send the "certificate_expired" alert and close the 533 | connection. 534 | 535 | 536 | 537 | 538 |
539 | 540 |
541 | 542 | 543 | 544 | The TLS connection MAY be used to create, delete, and activate pins as 545 | described in this section. Note that this section is optional; a client MAY 546 | rely on an external source of pins, provided the external pins are produced by 547 | a client following the below algorithms. 548 | 549 | 550 | 551 | 552 | If there is a TACK and a relevant pin referencing the TACK key, and pin 553 | activation is enabled, the name record's "active period end" SHALL be set 554 | using the below formula (where "current" is the current time, and "initial" is 555 | the "initial timestamp" from the name record): 556 | 557 |
558 | active_period_end = current + MIN(30 days, current - initial) 559 |
560 | 561 | 562 | If there is a TACK and either no relevant pin or an inactive relevant pin that 563 | does not reference the TACK key, a new pin SHALL be created: 564 | 565 | 566 | If the TACK key is referenced by an existing 567 | key record, the key record is reused, otherwise a new key record is created 568 | with the TACK's key and min_generation. 569 | A new name record is created 570 | containing the relevant name, an "initial timestamp" equal to the current 571 | time, and an empty "active period end". 572 | 573 | If there is an existing relevant pin, the pin SHALL be deleted (see ). 575 | 576 | 577 | 578 | 579 | If there is no TACK and the relevant pin is inactive, the pin SHALL be 580 | deleted (see ). 581 | 582 | 583 | 584 | The following table summarizes this behavior based on whether the relevant pin 585 | is active and references the TACK key. The "(*)" means "if pin activation is 586 | enabled". 587 | 588 | 589 | 590 | Pin status 591 | Pin references TACK 592 | Result 593 | Active 594 | Yes 595 | Extend activation period (*) 596 | 597 | Active 598 | No (or no TACK) 599 | Rejected 600 | 601 | Inactive 602 | Yes 603 | Activate pin (*) 604 | 605 | Inactive 606 | No 607 | Replace with new inactive pin 608 | 609 | Inactive 610 | No TACK 611 | Delete pin 612 | 613 | No pin 614 | - 615 | Create new inactive pin 616 | 617 | No pin 618 | No TACK 619 | - 620 | 621 | 622 | 623 |
624 |
625 | 626 | 627 | All key records broken by break signatures SHALL be discarded, along with 628 | their associated name records. A key record is broken by a break signature if 629 | the break signature passes the following checks: 630 | 631 | 632 | 633 | "public_key" is referenced by the key record. 634 | 635 | "signature" is a correct ECDSA signature (else "decrypt_error"). 636 | 637 | 638 | 639 |
640 | 641 |
642 | 643 | 644 | 645 | A client might need to delete a pin from its store as a result of the 646 | algorithms in . A client MAY also delete pins from 647 | its store at any time, whether to save space, protect privacy, or for any 648 | other reason. To delete a pin, its name record SHALL be removed. If this 649 | leaves a key record with no associated name records, the key record MAY be 650 | removed as well. Pins MAY be deleted regardless of whether they are active or 651 | inactive, however for security concerns regarding pin deletion, see . 653 | 654 | 655 | 656 | 657 | 658 | 659 | Deleting pins unnecessarily will reduce the benefits of TACK, so SHOULD be 660 | avoided. Note that a pin SHOULD NOT be deleted simply because it has become 661 | inactive. Instead, such a pin SHOULD be retained, so that it can be 662 | re-activated in the future by the algorithms in . 663 | 664 | 665 |
666 |
667 |
668 |
669 |
670 | 671 | 672 | 673 | A TACK client MAY choose to perform some form of certificate verification in 674 | addition to TACK processing. When combining certificate verification and TACK 675 | processing, the TACK processing described in SHALL 676 | be followed, with the exception that TACK processing MAY be terminated early 677 | (or skipped) if some fatal certificate error is discovered. 678 | 679 | 680 | 681 | 682 | If TACK processing and certificate verification both complete without a fatal 683 | error, the client SHALL apply some policy to decide whether to accept the 684 | connection. The policy is up to the client. An example policy would be to 685 | accept the connection only if it passes certificate verification and is not 686 | rejected by a pin, or if the user elects to "connect anyway" despite 687 | certificate and/or pin failures. 688 | 689 | 690 | 691 |
692 | 693 |
694 | 695 | 696 | 697 | In addition to the hostname-based pinning described in , some applications may require "application-specific 699 | pins", where an application-layer name is pinned to a TACK key. For example, 700 | an SMTP MTA may wish to authenticate receiving MTAs by pinning email domains 701 | to the receiving MTAs' TACK keys. 702 | 703 | 704 | 705 | 706 | Application-specific pins may require redefinition of the name record's "name" 707 | field, the "relevant name" for the TLS connection, and the "pin activation" 708 | signal. With these items redefined, the client processing rules in may be reused. 710 | 711 | 712 | 713 | Note that a server using application-specific pins is still subject to 714 | hostname pins, and a client MAY apply either or both forms of pinning. 715 | 716 | 717 | The specification of application-specific pinning for particular applications 718 | is outside the scope of this document. 719 | 720 | 721 | 722 |
723 |
724 | 725 | 726 |
727 | 728 | 729 | A "TACK ID" MAY be used to represent a TACK public key to users in a form that 730 | is relatively easy to compare and transcribe. A TACK ID consists of the first 731 | 25 characters from the base32 encoding of SHA256(public_key), split into 5 732 | groups of 5 characters separated by periods. Base32 encoding is as specified 733 | in , except lowercase is used. 734 | 735 | 736 | 737 | Example TACK IDs: 738 | 739 | 740 | 741 | quxiz.kpldu.uuedc.j5znm.7mqst 742 | a334f.bt7ts.ljb3b.y24ij.6zhwm 743 | ebsx7.z22qt.okobu.ibhut.xzdny 744 | 745 | 746 | 747 |
748 | 749 |
750 |
751 | 752 | 753 | 754 | 755 | 756 | All servers that are pinned to a single TACK key are able to impersonate each 757 | other, since clients will perceive their TACKs as equivalent. Thus, TACK keys 758 | SHOULD NOT be reused with different hostnames unless these hostnames are 759 | closely related. Examples where it would be safe to reuse a TACK key are 760 | hostnames aliased to the same host, hosts sharing the same TLS key, or 761 | hostnames for a group of near-identical servers. 762 | 763 | 764 | 765 | 766 | 767 | A TLS server may be referenced by multiple hostnames. Clients may pin any of 768 | these hostnames. Server operators should be careful when using such DNS 769 | aliases that hostnames are not pinned inadvertently. 770 | 771 | 772 | 773 | 774 | 775 | To revoke older generations of TACKs, the server operator SHOULD first provide 776 | all servers with a new generation of TACKs, and only then provide servers with 777 | new TACKs containing the new min_generation. Otherwise, a client may receive a 778 | min_generation update from one server but then try to contact an 779 | older-generation server which has not yet been updated. 780 | 781 | 782 | 783 | It is convenient to set the TACK expiration equal to the end-entity 784 | certificate expiration, so that the TACK and certificate may both be replaced 785 | at the same time. Alternatively, short-lived TACKs may be used so that a 786 | compromised TLS private key has limited value to an attacker. 787 | 788 | 789 | 790 | A break signature only needs to be published for a time interval equal to the 791 | maximum active period of any affected pins. For example, if a TACK has been only 792 | been published on a website for 24 hours, to remove the TACK only requires 793 | publishing the break signature for 24 hours. 794 | 795 | 796 | 797 | 798 | Pin activation SHOULD only be enabled once all TLS servers sharing the same 799 | hostname have a TACK. Otherwise, a client may activate a pin by contacting one 800 | server, then contact a different server at the same hostname that does not yet 801 | have a TACK. 802 | 803 | The pin_activation field can be used to phase 804 | out TACKs for a hostname. If all servers at a hostname disable pin activation, 805 | all existing pins for the hostname will eventually become inactive, at which 806 | point the servers' TACKs can be removed. 807 | 808 | 809 | 810 | 811 | 812 | 813 | 814 |
815 |
816 | 817 | 818 | 819 | 820 | It is possible for a client to maintain a pin store based entirely on its own 821 | TLS connections. However, such a client runs the risk of creating incorrect 822 | pins, failing to keep its pins active, or failing to receive revocation 823 | information (min_generation updates and break signatures). Clients are advised 824 | to collaborate so that pin data can be aggregated and shared. This will 825 | require additional protocols outside the scope of this document. 826 | 827 | 828 | 829 | A client SHOULD take measures to prevent TACKs from being erroneously rejected 830 | due to an inaccurate client clock. Such methods MAY include using time 831 | synchronization protocols such as NTP , or accepting 832 | seemingly-expired TACKs if they expired less than T minutes ago, where T is a 833 | "tolerance bound" set to the client's maximum expected clock error. 834 | 835 | 836 | 837 | 838 | 839 |
840 |
841 | 842 |
843 |
844 | 845 | 846 | All servers pinned to the same TACK key can impersonate each other (see ). Think carefully about this risk if using the same TACK 848 | key for multiple hostnames. 849 | 850 | 851 | 852 | Make backup copies of the TACK private key and keep all copies in secure 853 | locations where they can't be compromised. 854 | 855 | 856 | 857 | 858 | A TACK private key MUST NOT be used to perform any non-TACK cryptographic 859 | operations. For example, using a TACK key for email encryption, code-signing, 860 | or any other purpose MUST NOT be done. 861 | 862 | 863 | 864 | HTTP cookies set by a pinned host can be stolen by a 865 | network attacker who can forge web and DNS responses so as to cause a client 866 | to send the cookies to a phony subdomain of the pinned host. To prevent this, 867 | TACK HTTPS Servers SHOULD set the "secure" attribute and omit the "domain" 868 | attribute on all security-sensitive cookies, such as session cookies. These 869 | settings tell the browser that the cookie should only be presented back to the 870 | originating host (not its subdomains), and should only be sent over HTTPS (not 871 | HTTP) . 872 | 873 | 874 |
875 | 876 |
877 | 878 | 879 | 880 | A TACK pin store may contain private details of the client's connection 881 | history. An attacker may be able to access this information by hacking or 882 | stealing the client. Some information about the client's connection history 883 | could also be gleaned by observing whether the client accepts or rejects 884 | connections to phony TLS servers without correct TACKs. To mitigate these 885 | risks, a TACK client SHOULD allow the user to edit or clear the pin store. 886 | 887 | 888 | 889 | 890 | 891 | Aside from rejecting TLS connections, clients SHOULD NOT take any actions 892 | which would reveal to a network observer the state of the client's pin store, 893 | as this would allow an attacker to know in advance whether a 894 | "man-in-the-middle" attack on a particular TLS connection will succeed or be 895 | detected. 896 | 897 | 898 | 899 | 900 | 901 | An attacker may attempt to flood a client with spurious TACKs for different 902 | hostnames, causing the client to delete old pins to make space for new ones. 903 | To defend against this, clients SHOULD NOT delete active pins to make space 904 | for new pins. Clients instead SHOULD delete inactive pins. If there are no 905 | inactive pins to delete, then the pin store is full and there is no space for 906 | new pins. To select an inactive pin for deletion, the client SHOULD delete the 907 | pin with the oldest "active_period_end". 908 | 909 | 910 |
911 | 912 |
913 | 914 | 915 | 916 | If the need arises for TACKs using different cryptographic algorithms (e.g., 917 | if SHA256 or ECDSA are shown to be weak), a "v2" version of TACKs could be 918 | defined, requiring assignment of a new TLS Extension number. TACKs as defined 919 | in this document would then be known as "v1" TACKs. 920 | 921 | 922 |
923 | 924 |
925 | 926 |
927 |
928 | 929 | 930 | IANA is requested to add an entry to the existing TLS ExtensionType registry, 931 | defined in , for tack(TBD) as defined in this document. 932 | 933 | 934 |
935 | 936 |
937 |
938 | 939 | 940 | Valuable feedback has been provided by Adam Langley, Chris Palmer, Nate 941 | Lawson, and Joseph Bonneau. 942 | 943 |
944 | 945 |
946 | 947 | 948 | 949 | 950 | 953 | 954 | Secure Hash Standard 955 | 956 | National Institute of Standards and Technology 957 | 958 | 959 | 960 | 961 | 962 | 963 | 966 | 967 | Digital Signature Standard 968 | 969 | National Institute of Standards and Technology 970 | 971 | 972 | 973 | 974 | 975 | 976 | 977 | 978 | 979 | 980 | 981 | 982 | 983 | 984 | 985 | 986 | 987 | 988 |
989 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | # Author: Trevor Perrin 6 | # See the LICENSE file for legal information regarding use of this file. 7 | import os 8 | import shutil 9 | 10 | shutil.copyfile("tack.py", "tack/tack") 11 | 12 | setup(name="TACKpy", 13 | version="0.9.6", 14 | author="Trevor Perrin", 15 | author_email="tackpy@trevp.net", 16 | url="https://github.com/trevp/TACKpy", 17 | description="TACKpy implements TACK in python", 18 | license="public domain", 19 | scripts=["tack/tack"], 20 | packages=["tack"], 21 | install_requires=['M2Crypto']) 22 | 23 | print "Cleaning up..." 24 | if os.path.exists("build/"): 25 | shutil.rmtree("build/") 26 | 27 | try: 28 | os.remove("tack/tack") 29 | except: 30 | pass 31 | -------------------------------------------------------------------------------- /tack.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | 3 | # Author: Trevor Perrin 4 | # See the LICENSE file for legal information regarding use of this file. 5 | import sys 6 | from tack.commands.BreakCommand import BreakCommand 7 | from tack.commands.CertificateCommand import CertificateCommand 8 | from tack.commands.GenerateKeyCommand import GenerateKeyCommand 9 | from tack.commands.HelpCommand import HelpCommand 10 | from tack.commands.SignCommand import SignCommand 11 | from tack.commands.ViewCommand import ViewCommand 12 | 13 | if __name__ == '__main__': 14 | if len(sys.argv) < 2: 15 | HelpCommand.printGeneralUsage("Missing command") 16 | elif sys.argv[1] == "genkey"[:len(sys.argv[1])]: 17 | GenerateKeyCommand(sys.argv[2:]).execute() 18 | elif sys.argv[1] == "sign"[:len(sys.argv[1])]: 19 | SignCommand(sys.argv[2:]).execute() 20 | elif sys.argv[1] == "break"[:len(sys.argv[1])]: 21 | BreakCommand(sys.argv[2:]).execute() 22 | elif sys.argv[1] == "tackcert"[:len(sys.argv[1])]: 23 | CertificateCommand(sys.argv[2:]).execute() 24 | elif sys.argv[1] == "view"[:len(sys.argv[1])]: 25 | ViewCommand(sys.argv[2:]).execute() 26 | elif sys.argv[1] == "help"[:len(sys.argv[1])]: 27 | HelpCommand(sys.argv[2:]).execute() 28 | else: 29 | HelpCommand.printGeneralUsage("Unknown command: %s" % sys.argv[1]) 30 | -------------------------------------------------------------------------------- /tack/InvalidPasswordException.py: -------------------------------------------------------------------------------- 1 | 2 | class InvalidPasswordException(Exception): 3 | def __init__(self, args): 4 | Exception.__init__(self, args) -------------------------------------------------------------------------------- /tack/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moxie0/TACKpy/5cd8ee33b4a6eba4ae9fed3582682e2658e4fee2/tack/__init__.py -------------------------------------------------------------------------------- /tack/commands/BreakCommand.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from tack.commands.Command import Command 3 | from tack.structures.TackBreakSig import TackBreakSig 4 | 5 | class BreakCommand(Command): 6 | 7 | def __init__(self, argv): 8 | Command.__init__(self, argv, "pok", "v") 9 | self.password = self.getPassword() 10 | self.outputFile, self.outputFileName = self.getOutputFile() 11 | self.key = self.getKey(self.getPassword()) 12 | 13 | def execute(self): 14 | breakSig = TackBreakSig.createFromParameters(self.key.getPublicKey(), self.key.getPrivateKey()) 15 | self.outputFile.write(self.addPemComments(breakSig.serializeAsPem())) 16 | 17 | if self.isVerbose(): 18 | sys.stderr.write(str(breakSig) + "\n") 19 | 20 | @staticmethod 21 | def printHelp(): 22 | print(\ 23 | """Creates a break signature based on an input TACK key file. 24 | 25 | break -k KEY 26 | 27 | -k KEY : Use this TACK key file 28 | 29 | Optional arguments: 30 | -v : Verbose 31 | -o FILE : Write the output to this file (instead of stdout) 32 | -p PASSWORD : Use this TACK key password instead of prompting 33 | """) 34 | -------------------------------------------------------------------------------- /tack/commands/CertificateCommand.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from tack.commands.Command import Command 3 | from tack.structures.Tack import Tack 4 | from tack.structures.TackActivation import TackActivation 5 | from tack.structures.TackBreakSig import TackBreakSig 6 | from tack.structures.TackExtension import TackExtension 7 | from tack.tls.TlsCertificate import TlsCertificate 8 | from tack.util.PEMDecoder import PEMDecoder 9 | 10 | class CertificateCommand(Command): 11 | 12 | def __init__(self, argv): 13 | Command.__init__(self, argv, "oib", "v") 14 | 15 | self.outputFile, self.outputFileName = self.getOutputFile() 16 | self.inputTack = self._getInputTack() 17 | self.inputCertificate = self._getInputCertificate() 18 | self.breakSignatures = self._getBreakSignatures() 19 | 20 | if self.inputTack is None and self.inputCertificate is None: 21 | self.printError("-i missing") 22 | 23 | def execute(self): 24 | if self.inputTack is not None: 25 | tackExtension = TackExtension.createFromParameters(self.inputTack, self.breakSignatures, 26 | TackActivation.DISABLED) 27 | tlsCertificate = TlsCertificate() 28 | tlsCertificate.create(tackExtension) 29 | 30 | self.outputFile.write(tlsCertificate.writePem()) 31 | 32 | if self.isVerbose(): 33 | sys.stderr.write(str(tackExtension) + "\n") 34 | 35 | elif self.inputCertificate is not None: 36 | if self.breakSignatures is not None: 37 | self.printError("Invalid arguments: break sigs with TACK cert.") 38 | 39 | s = "" 40 | if self.inputCertificate.tackExt: 41 | if self.inputCertificate.tackExt.tack: 42 | s += self.inputCertificate.tackExt.tack.serializeAsPem() 43 | if self.inputCertificate.tackExt.break_sigs: 44 | for bs in self.inputCertificate.tackExt.break_sigs: 45 | s += bs.serializeAsPem() 46 | 47 | self.outputFile.write(s) 48 | 49 | if self.isVerbose(): 50 | sys.stderr.write(self.inputCertificate.writeText() + "\n") 51 | 52 | def _getBreakSignatures(self): 53 | fileName = self._getOptionValue("-b") 54 | 55 | if fileName is None: 56 | return None 57 | 58 | contents = open(fileName, "r").read() 59 | 60 | return TackBreakSig.createFromPem(contents) 61 | 62 | def _getInputTack(self): 63 | contents = self._getInputFileContents() 64 | 65 | if contents is None: 66 | return None 67 | 68 | if PEMDecoder(contents).containsEncoded("TACK"): 69 | return Tack.createFromPem(contents) 70 | 71 | return None 72 | 73 | def _getInputCertificate(self): 74 | contents = self._getInputFileContents() 75 | 76 | if contents is None: 77 | return None 78 | 79 | if PEMDecoder(contents).containsEncoded("CERTIFICATE"): 80 | certificate = TlsCertificate() 81 | certificate.open(self._getOptionValue("-i")) 82 | return certificate 83 | 84 | def _getInputFileContents(self): 85 | fileName = self._getOptionValue("-i") 86 | 87 | if fileName is None: 88 | return None 89 | 90 | return open(fileName, "r").read() 91 | 92 | @staticmethod 93 | def printHelp(): 94 | print(\ 95 | """Creates a TACK certificate with the input TACK and optional Break Sigs. 96 | 97 | (Alternatively, if input is a TACK certificate, writes out the TACK and/or 98 | Break Signatures as PEM files). 99 | 100 | tackcert -i (TACK or CERT) 101 | 102 | Optional arguments: 103 | -v : Verbose 104 | -b BREAKSIGS : Include Break Signatures from this file. 105 | -o FILE : Write the output to this file (instead of stdout) 106 | """) 107 | -------------------------------------------------------------------------------- /tack/commands/Command.py: -------------------------------------------------------------------------------- 1 | import getopt 2 | import getpass 3 | import sys 4 | import time 5 | from tack.structures.TackKeyFile import TackKeyFile 6 | from tack.util.Time import Time 7 | from tack.version import __version__ 8 | from tack.InvalidPasswordException import InvalidPasswordException 9 | 10 | class Command: 11 | 12 | def __init__(self, argv, options, flags): 13 | try: 14 | self.argv = argv 15 | self.flags = flags 16 | self.options = ":".join(options) + ":" 17 | self.values, self.remainder = getopt.getopt(argv, self.options + self.flags) 18 | except getopt.GetoptError as e: 19 | self.printError(e) 20 | 21 | def isVerbose(self): 22 | return self._containsOption("-v") 23 | 24 | def getPassword(self): 25 | return self._getOptionValue("-p") 26 | 27 | def getKey(self, password): 28 | keyPemFile = self._getOptionValue("-k") 29 | 30 | if not keyPemFile: 31 | self.printError("-k missing (TACK Key)") 32 | 33 | if not password: 34 | password = self._promptPassword() 35 | 36 | try: 37 | keyPemData = open(keyPemFile, "rU").read() 38 | 39 | while True: 40 | try: 41 | inKey = TackKeyFile.createFromPem(keyPemData, password) 42 | return inKey 43 | except InvalidPasswordException, ipe: 44 | sys.stderr.write("Password incorrect!\n") 45 | password = self._promptPassword() 46 | except SyntaxError: 47 | self.printError("Error processing TACK Key File") 48 | 49 | except IOError: 50 | self.printError("Error opening TACK Key File: %s" % keyPemFile) 51 | 52 | 53 | def getOutputFile(self): 54 | output = None 55 | 56 | try: 57 | output = self._getOptionValue("-o") 58 | 59 | if output is None: 60 | return sys.stdout, None 61 | else: 62 | return open(output, "w"), output 63 | except IOError: 64 | self.printError("Error opening output file: %s" % output) 65 | 66 | def addPemComments(self, inStr): 67 | """Add pre-PEM metadata/comments to PEM strings.""" 68 | versionStr = __version__ 69 | timeStr = Time.posixTimeToStr(time.time(), True) 70 | outStr = "Created by tack.py %s\nCreated at %s\n%s" %\ 71 | (versionStr, timeStr, inStr) 72 | return outStr 73 | 74 | def _promptPassword(self): 75 | return getpass.getpass("Enter password for key file: ") 76 | 77 | def _getOptionValue(self, flag): 78 | for option, value in self.values: 79 | if option == flag: 80 | return value 81 | 82 | return None 83 | 84 | def _containsOption(self, flag): 85 | for option, value in self.values: 86 | if option == flag: 87 | return True 88 | 89 | def printError(self, error): 90 | """Print error message and exit""" 91 | sys.stderr.write("ERROR: %s\n" % error) 92 | sys.exit(-1) 93 | 94 | -------------------------------------------------------------------------------- /tack/commands/GenerateKeyCommand.py: -------------------------------------------------------------------------------- 1 | import getpass 2 | import sys 3 | from tack.commands.Command import Command 4 | from tack.structures.TackKeyFile import TackKeyFile 5 | 6 | class GenerateKeyCommand(Command): 7 | 8 | def __init__(self, argv): 9 | Command.__init__(self, argv, "po", "v") 10 | self.password = self.getPassword() 11 | self.outputFile, self.outputFileName = self.getOutputFile() 12 | 13 | def execute(self): 14 | password = self._getPassword() 15 | keyFile = TackKeyFile.createRandom(password) 16 | self.outputFile.write(self.addPemComments(keyFile.serializeAsPem())) 17 | 18 | if self.isVerbose(): 19 | sys.stderr.write(str(keyFile) + "\n") 20 | 21 | def _getPassword(self): 22 | if not self.password: 23 | password, password2 = "this", "that" 24 | while password != password2: 25 | password = getpass.getpass("Choose password for key file: ") 26 | password2 = getpass.getpass("Re-enter password for key file: ") 27 | 28 | if password != password2: 29 | sys.stderr.write("PASSWORDS DON'T MATCH!\n") 30 | 31 | self.password = password 32 | 33 | return self.password 34 | 35 | @staticmethod 36 | def printHelp(): 37 | print(\ 38 | """Creates a new TACK key file. 39 | 40 | genkey 41 | 42 | Optional arguments: 43 | -v : Verbose 44 | -o FILE : Write the output to this file (instead of stdout) 45 | -p PASSWORD : Use this TACK key password instead of prompting 46 | """) 47 | -------------------------------------------------------------------------------- /tack/commands/HelpCommand.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from tack.commands.CertificateCommand import CertificateCommand 3 | from tack.version import __version__ 4 | from tack.commands.BreakCommand import BreakCommand 5 | from tack.commands.Command import Command 6 | from tack.commands.GenerateKeyCommand import GenerateKeyCommand 7 | from tack.commands.SignCommand import SignCommand 8 | from tack.commands.ViewCommand import ViewCommand 9 | 10 | class HelpCommand(Command): 11 | 12 | COMMANDS = {"genkey" : GenerateKeyCommand, "sign" : SignCommand, 13 | "break" : BreakCommand, "view" : ViewCommand, 14 | "tackcert" : CertificateCommand} 15 | 16 | def __init__(self, argv): 17 | Command.__init__(self, argv, "", "") 18 | 19 | if len(argv) < 1: 20 | HelpCommand.printGeneralUsage() 21 | 22 | self.command = argv[0] 23 | 24 | if not self.command in HelpCommand.COMMANDS: 25 | self.printError("%s not a valid command." % self.command) 26 | 27 | def execute(self): 28 | HelpCommand.COMMANDS[self.command].printHelp() 29 | 30 | @staticmethod 31 | def printHelp(): 32 | print(\ 33 | """Provides help for individual commands. 34 | 35 | help 36 | """) 37 | 38 | @staticmethod 39 | def printGeneralUsage(message=None): 40 | print "Error: %s" % message 41 | print(\ 42 | """\ntack.py version %s 43 | 44 | Commands (use "help " to see optional args): 45 | genkey 46 | sign -k KEY -c CERT 47 | break -k KEY 48 | tackcert -i TACK 49 | view FILE 50 | help COMMAND 51 | """ % __version__) 52 | sys.exit(-1) 53 | -------------------------------------------------------------------------------- /tack/commands/SignCommand.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import time 3 | import math 4 | from tack.commands.Command import Command 5 | from tack.structures.Tack import Tack 6 | from tack.tls.TlsCertificate import TlsCertificate 7 | from tack.util.Time import Time 8 | 9 | class SignCommand(Command): 10 | 11 | def __init__(self, argv): 12 | Command.__init__(self, argv, "kcopmgen", "v") 13 | 14 | self.password = self.getPassword() 15 | self.outputFile, self.outputFileName = self.getOutputFile() 16 | self.key = self.getKey(self.password) 17 | 18 | self.certificate = self._getCertificate() 19 | self.generation = self._getGeneration() 20 | self.min_generation = self._getMinGeneration() 21 | self.expiration = self._getExpiration() 22 | self.numArg = self._getTackCount() 23 | 24 | 25 | def execute(self): 26 | if not self.numArg: 27 | tack = Tack.createFromParameters(self.key.getPublicKey(), self.key.getPrivateKey(), self.min_generation, 28 | self.generation, self.expiration, self.certificate.key_sha256) 29 | 30 | self.outputFile.write(self.addPemComments(tack.serializeAsPem())) 31 | 32 | if self.isVerbose(): 33 | sys.stderr.write(str(tack) + "\n") 34 | else: 35 | (numTacks, interval) = self.numArg 36 | 37 | if not self.outputFileName: 38 | self.printError("-o required with -n") 39 | 40 | for x in range(numTacks): 41 | tack = Tack.createFromParameters(self.key.getPublicKey(), self.key, self.min_generation, 42 | self.generation, self.expiration, self.certificate.key_sha256) 43 | 44 | outputFile = open(self.outputFileName + "_%04d.pem" % x, "w") 45 | outputFile.write(self.addPemComments(tack.serializeAsPem())) 46 | outputFile.close() 47 | 48 | if self.isVerbose(): 49 | sys.stderr.write(str(tack) + "\n") 50 | 51 | self.expiration += interval 52 | 53 | def _getCertificate(self): 54 | certificateFile = self._getOptionValue("-c") 55 | 56 | if not certificateFile: 57 | self.printError("-c missing (Certificate)") 58 | 59 | try: 60 | inCert = TlsCertificate() 61 | inCert.open(certificateFile) 62 | return inCert 63 | except SyntaxError: 64 | self.printError("SSL certificate malformed: %s" % certificateFile) 65 | except IOError: 66 | self.printError("Error opening SSL certificate: %s" % certificateFile) 67 | 68 | def _getExpiration(self): 69 | expiration = self._getOptionValue("-e") 70 | 71 | if expiration is None and self._getTackCount() is None: 72 | return int(math.ceil(self._getCertificate().notAfter / 60.0)) 73 | else: 74 | try: 75 | return Time.parseTimeArg(expiration) 76 | except SyntaxError as e: 77 | self.printError(e) 78 | 79 | def _getTackCount(self): 80 | tackCount = self._getOptionValue("-n") 81 | 82 | if tackCount is None: 83 | return None 84 | 85 | try: 86 | leftArg, rightArg = tackCount.split("@") # could raise ValueError 87 | numTacks = int(leftArg) # could raise ValueError 88 | interval = Time.parseDurationArg(rightArg) # SyntaxError 89 | if numTacks < 1 or numTacks >= 10000: 90 | raise ValueError() 91 | return numTacks, interval 92 | except (ValueError, SyntaxError): 93 | self.printError("Bad -n NUMTACKS: %s:" % tackCount) 94 | 95 | def _getGeneration(self): 96 | generation = self._getOptionValue("-g") 97 | 98 | if generation is None: 99 | generation = self._getMinGeneration() 100 | 101 | try: 102 | generation = int(generation) # Could raise ValueError 103 | if generation < 0 or generation>255: 104 | raise ValueError() 105 | except ValueError: 106 | self.printError("Bad generation: %s" % generation) 107 | 108 | if generation < self._getMinGeneration(): 109 | self.printError("generation must be >= min_generation") 110 | 111 | return generation 112 | 113 | def _getMinGeneration(self): 114 | min_generation = self._getOptionValue("-m") 115 | 116 | if min_generation is None: 117 | min_generation = 0 118 | 119 | try: 120 | min_generation = int(min_generation) # Could raise ValueError 121 | if min_generation < 0 or min_generation>255: 122 | raise ValueError() 123 | except ValueError: 124 | self.printError("Bad min_generation: %s" % min_generation) 125 | 126 | return min_generation 127 | 128 | @staticmethod 129 | def printHelp(): 130 | s = Time.posixTimeToStr(time.time()) 131 | print(\ 132 | """Creates a TACK based on a target SSL certificate. 133 | 134 | sign -k KEY -c CERT 135 | 136 | -k KEY : Use this TACK key file 137 | -c CERT : Sign this SSL certificate's public key 138 | 139 | Optional arguments: 140 | -v : Verbose 141 | -o FILE : Write the output to this file (instead of stdout) 142 | -p PASSWORD : Use this TACK key password instead of prompting 143 | -m MIN_GENERATION : Use this min_generation number (0-255) 144 | -g GENERATION : Use this generation number (0-255) 145 | -e EXPIRATION : Use this UTC time for expiration 146 | ("%s", "%sZ", 147 | "%sZ", "%sZ" etc.) 148 | Or, specify a duration from current time: 149 | ("5m", "30d", "1d12h5m", "0m", etc.) 150 | - n NUM@INTERVAL : Generate NUM TACKs, with expiration times spaced 151 | out by INTERVAL (see -d for INTERVAL syntax). The 152 | -o argument is used as a filename prefix, and the 153 | -e argument is used as the first expiration time. 154 | """ % (s, s[:13], s[:10], s[:4])) 155 | 156 | -------------------------------------------------------------------------------- /tack/commands/ViewCommand.py: -------------------------------------------------------------------------------- 1 | from tack.commands.Command import Command 2 | from tack.structures.Tack import Tack 3 | from tack.structures.TackKeyFile import TackKeyFile 4 | from tack.structures.TackBreakSig import TackBreakSig 5 | from tack.tls.TlsCertificate import TlsCertificate 6 | from tack.util.PEMDecoder import PEMDecoder 7 | 8 | class ViewCommand(Command): 9 | 10 | def __init__(self, argv): 11 | Command.__init__(self, argv, "", "") 12 | 13 | if len(argv) < 1: 14 | self.printError("Missing argument: file to view") 15 | if len(argv) > 1: 16 | self.printError("Can only view one file") 17 | 18 | 19 | def _readFile(self, argv): 20 | try: 21 | # Read both binary (bytearray) and text (str) versions of the input 22 | b = bytearray(open(argv[0], "rb").read()) 23 | try: 24 | s = open(argv[0], "rU").read() 25 | except UnicodeDecodeError: 26 | # Python3 error, so it must be a binary file; not text 27 | s = None 28 | 29 | return s, b 30 | except IOError: 31 | self.printError("Error opening file: %s" % argv[0]) 32 | 33 | def execute(self): 34 | text, binary = self._readFile(self.argv) 35 | fileType = None 36 | 37 | try: 38 | if text: 39 | decoder = PEMDecoder(text) 40 | if decoder.containsEncoded("TACK PRIVATE KEY"): 41 | fileType = "Private Key" 42 | kf = TackKeyFile.createFromPem(text, None) 43 | print(str(kf)) 44 | return 45 | elif decoder.containsEncoded("TACK"): 46 | fileType = "TACK" 47 | tack = Tack.createFromPem(text) 48 | print(str(tack)) 49 | return 50 | elif decoder.containsEncoded("TACK BREAK SIG"): 51 | fileType = "Break Sig" 52 | tbsList = TackBreakSig.createFromPemList(text) 53 | s = "" 54 | for tbs in tbsList: 55 | s += str(tbs) 56 | print(s) 57 | return 58 | elif decoder.containsEncoded( "CERTIFICATE"): 59 | fileType = "Certificate" 60 | sslc = TlsCertificate() 61 | sslc.parsePem(text) 62 | print(sslc.writeText()) 63 | return 64 | # Is it an SSL certificate? 65 | try: 66 | sslc = TlsCertificate() 67 | sslc.parse(binary) 68 | print(sslc.writeText()) 69 | except SyntaxError: 70 | self.printError("Unrecognized file type") 71 | except SyntaxError as e: 72 | self.printError("Error parsing %s: %s" % (fileType, e)) 73 | 74 | @staticmethod 75 | def printHelp(): 76 | print(\ 77 | """Views a TACK, TACK Key, TACK Break Sig, or SSL certificate. 78 | 79 | view 80 | """) 81 | -------------------------------------------------------------------------------- /tack/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moxie0/TACKpy/5cd8ee33b4a6eba4ae9fed3582682e2658e4fee2/tack/commands/__init__.py -------------------------------------------------------------------------------- /tack/compat.py: -------------------------------------------------------------------------------- 1 | # Author: Trevor Perrin 2 | # See the LICENSE file for legal information regarding use of this file. 3 | 4 | ################ COMPAT ### 5 | """These functions abstract away the differences between Python 2 and 3. 6 | """ 7 | 8 | import sys, binascii, base64 9 | 10 | if sys.version_info >= (3,0): 11 | def raw_input(s): 12 | return input(s) 13 | 14 | # So, the python3 binascii module deals with bytearrays, and python2 15 | # deals with strings... I would rather deal with the "a" part as 16 | # strings, and the "b" part as bytearrays, regardless of python version, 17 | # so... 18 | def a2b_hex(s): 19 | try: 20 | b = bytearray(binascii.a2b_hex(bytearray(s, "ascii"))) 21 | except Exception as e: 22 | raise SyntaxError("base16 error: %s" % e) 23 | return b 24 | 25 | def a2b_base64(s): 26 | try: 27 | b = bytearray(binascii.a2b_base64(bytearray(s, "ascii"))) 28 | except Exception as e: 29 | raise SyntaxError("base64 error: %s" % e) 30 | return b 31 | 32 | def b2a_hex(b): 33 | return binascii.b2a_hex(b).decode("ascii") 34 | 35 | def b2a_base64(b): 36 | return binascii.b2a_base64(b).decode("ascii") 37 | 38 | def b2a_base32(b): 39 | return base64.b32encode(b).decode("ascii") 40 | 41 | def bytesToStrAscii(b): 42 | return str(b, "ascii") 43 | 44 | def compat26Str(x): return x 45 | 46 | else: 47 | # Python 2.6 requires strings instead of bytearrays in a couple places, 48 | # so we define this function so it does the conversion if needed. 49 | if sys.version_info < (2,7): 50 | def compat26Str(x): return str(x) 51 | else: 52 | def compat26Str(x): return x 53 | 54 | 55 | def a2b_hex(s): 56 | try: 57 | b = bytearray(binascii.a2b_hex(s)) 58 | except Exception as e: 59 | raise SyntaxError("base16 error: %s" % e) 60 | return b 61 | 62 | def a2b_base64(s): 63 | try: 64 | b = bytearray(binascii.a2b_base64(s)) 65 | except Exception as e: 66 | raise SyntaxError("base64 error: %s" % e) 67 | return b 68 | 69 | def b2a_hex(b): 70 | return binascii.b2a_hex(compat26Str(b)) 71 | 72 | def b2a_base64(b): 73 | return binascii.b2a_base64(compat26Str(b)) 74 | 75 | def b2a_base32(b): 76 | return base64.b32encode(str(b)) 77 | 78 | def bytesToStrAscii(b): 79 | return str(b) 80 | -------------------------------------------------------------------------------- /tack/crypto/AES.py: -------------------------------------------------------------------------------- 1 | from M2Crypto import m2 2 | 3 | class AES: 4 | def __init__(self, key, IV): 5 | if len(key) not in (16, 24, 32): 6 | raise AssertionError() 7 | 8 | if len(IV) != 16: 9 | raise AssertionError() 10 | 11 | self.isBlockCipher = True 12 | self.block_size = 16 13 | self.key = key 14 | self.IV = IV 15 | 16 | if len(key)==16: 17 | self.name = "aes128" 18 | elif len(key)==24: 19 | self.name = "aes192" 20 | elif len(key)==32: 21 | self.name = "aes256" 22 | else: 23 | raise AssertionError() 24 | 25 | def encrypt(self, plaintext): 26 | assert(len(plaintext) % 16 == 0) 27 | context = self._createContext(1) 28 | ciphertext = m2.cipher_update(context, plaintext) 29 | m2.cipher_ctx_free(context) 30 | self.IV = ciphertext[-self.block_size:] 31 | return bytearray(ciphertext) 32 | 33 | def decrypt(self, ciphertext): 34 | assert(len(ciphertext) % 16 == 0) 35 | context = self._createContext(0) 36 | #I think M2Crypto has a bug - it fails to decrypt and return the last block passed in. 37 | #To work around this, we append sixteen zeros to the string, below: 38 | plaintext = m2.cipher_update(context, ciphertext+('\0'*16)) 39 | 40 | #If this bug is ever fixed, then plaintext will end up having a garbage 41 | #plaintext block on the end. That's okay - the below code will discard it. 42 | plaintext = plaintext[:len(ciphertext)] 43 | m2.cipher_ctx_free(context) 44 | self.IV = ciphertext[-self.block_size:] 45 | return bytearray(plaintext) 46 | 47 | def _createContext(self, encrypt): 48 | context = m2.cipher_ctx_new() 49 | if len(self.key)==16: 50 | cipherType = m2.aes_128_cbc() 51 | elif len(self.key)==24: 52 | cipherType = m2.aes_192_cbc() 53 | elif len(self.key)==32: 54 | cipherType = m2.aes_256_cbc() 55 | else: 56 | raise AssertionError("Key is bad size: %s" % len(self.key)) 57 | 58 | m2.cipher_init(context, cipherType, self.key, self.IV, encrypt) 59 | return context -------------------------------------------------------------------------------- /tack/crypto/ASN1.py: -------------------------------------------------------------------------------- 1 | # Author: Trevor Perrin 2 | # See the LICENSE file for legal information regarding use of this file. 3 | 4 | ################ ASN1 ### 5 | from tack.tls.TlsStructure import TlsStructure 6 | 7 | def asn1Length(x): 8 | """Return a bytearray encoding an ASN1 length field based on input length. 9 | 10 | The ASN.1 length field is itself variable-length, depending on whether 11 | the length can be encoded in 7 bits. If so, the length is encoded in a 12 | single byte with the high bit cleared. Otherwise, the first byte's 13 | high bit is set, and the remaining 7 bits encode the number of following 14 | bytes needed to encode the length as a big-endian integer. 15 | 16 | The function is currently limited to input lengths < 65536. 17 | 18 | This function is useful when kludging together ASN.1 data structures. 19 | """ 20 | if x < 128: 21 | return bytearray([x]) 22 | if x < 256: 23 | return bytearray([0x81,x]) 24 | if x < 65536: 25 | return bytearray([0x82, int(x//256), x % 256]) 26 | assert False 27 | 28 | def toAsn1IntBytes(b): 29 | """Return a bytearray containing ASN.1 integer based on input bytearray. 30 | 31 | An ASN.1 integer is a big-endian sequence of bytes, with excess zero bytes 32 | at the beginning removed. However, if the high bit of the first byte 33 | would be set, a zero byte is prepended. Note that the ASN.1 type/len 34 | fields are NOT added by this function. 35 | """ 36 | # Strip leading zeros 37 | while b[0] == 0 and len(b)>1: 38 | b = b[1:] 39 | # Add a leading zero if high bit is set 40 | if b[0] & 0x80: 41 | b = bytearray([0]) + b 42 | return b 43 | 44 | def fromAsn1IntBytes(b, size): 45 | """Return a bytearray of "size" bytes representing a big-endian integer 46 | by converting the input bytearray's ASN.1 integer. 47 | 48 | An ASN.1 integer is a big-endian sequence of bytes, with excess zero bytes 49 | at the beginning removed. However, if the high bit of the first byte 50 | would be set, a zero byte is prepended. Note that the ASN.1 type/len 51 | fields are NOT removed by this function. 52 | 53 | Raises SyntaxError. 54 | """ 55 | if len(b) > size+1: 56 | raise SyntaxError("ASN.1 integer is too big") 57 | if len(b)==size+1: # This can occur if the integer's high bit was set 58 | if b[0]: 59 | raise SyntaxError("ASN.1 integer too big") 60 | if not (b[1] & 0x80): 61 | raise SyntaxError("ASN.1 integer has excess zero padding") 62 | return b[1:] 63 | else: 64 | # Prepend zero bytes if needed to reach "size" bytes 65 | return bytearray([0]*(size-len(b))) + b 66 | 67 | #Takes a byte array which has a DER TLV field at its head 68 | class ASN1Parser: 69 | def __init__(self, bytes, offset = 0): 70 | p = TlsStructure(bytes) 71 | self.type = p.getInt(1) #skip Type 72 | 73 | #Get Length 74 | self.length = self._getASN1Length(p) 75 | 76 | # Header length is however many bytes read so far 77 | self.headerLength = p.index 78 | 79 | #Get Value 80 | self.value = p.getBytes(self.length) 81 | 82 | # This value tracks the offset of this TLV field 83 | # in some enclosing structure (ie an X.509 cert) 84 | self.offset = offset 85 | 86 | 87 | #Assuming this is a sequence... 88 | def getChild(self, which): 89 | p = TlsStructure(self.value) 90 | for x in range(which+1): 91 | if p.index == len(p.bytes): 92 | return None 93 | markIndex = p.index 94 | p.getInt(1) #skip Type 95 | length = self._getASN1Length(p) 96 | p.getBytes(length) 97 | return ASN1Parser(p.bytes[markIndex : p.index], 98 | self.offset + self.headerLength + markIndex) 99 | 100 | #Assuming this is a tagged element... 101 | def getTagged(self): 102 | return ASN1Parser(self.value, self.offset + self.headerLength) 103 | 104 | def getTotalLength(self): 105 | return self.headerLength + self.length 106 | 107 | def getTotalBytes(self): 108 | return bytearray([self.type]) + asn1Length(self.length) + self.value 109 | 110 | #Decode the ASN.1 DER length field 111 | def _getASN1Length(self, p): 112 | firstLength = p.getInt(1) 113 | if firstLength<=127: 114 | lengthLength = 1 115 | return firstLength 116 | else: 117 | lengthLength = firstLength & 0x7F 118 | return p.getInt(lengthLength) 119 | 120 | 121 | -------------------------------------------------------------------------------- /tack/crypto/Digest.py: -------------------------------------------------------------------------------- 1 | import hashlib 2 | import hmac 3 | from tack.compat import compat26Str 4 | 5 | class Digest: 6 | @staticmethod 7 | def SHA256(b): 8 | "Return a 32-byte bytearray which is the SHA256 of input bytearray." 9 | return bytearray(hashlib.sha256(compat26Str(b)).digest()) 10 | 11 | @staticmethod 12 | def HMAC_SHA256(k, b): 13 | """Return a 32-byte bytearray which is HMAC-SHA256 of key and input.""" 14 | return bytearray(hmac.new(bytes(k), bytes(b), hashlib.sha256).digest()) 15 | -------------------------------------------------------------------------------- /tack/crypto/ECGenerator.py: -------------------------------------------------------------------------------- 1 | from M2Crypto import EC, BIO 2 | from tack.crypto.ASN1 import ASN1Parser, fromAsn1IntBytes 3 | from tack.crypto.ECPrivateKey import ECPrivateKey 4 | from tack.crypto.ECPublicKey import ECPublicKey 5 | from tack.util.PEMDecoder import PEMDecoder 6 | 7 | class ECGenerator: 8 | 9 | def generateECKeyPair(self): 10 | # Generate M2Crypto.EC.EC object 11 | ec = EC.gen_params(EC.NID_X9_62_prime256v1) 12 | ec.gen_key() 13 | 14 | rawPrivateKey, rawPublicKey = self._constructRawKeysFromEc(ec) 15 | 16 | return ECPublicKey(rawPublicKey, ec), ECPrivateKey(rawPrivateKey, rawPublicKey, ec) 17 | 18 | def _constructRawKeysFromEc(self, ec): 19 | derEncodedKeys = self._getDerEncodedKeysFromEc(ec) 20 | parser = ASN1Parser(derEncodedKeys) 21 | 22 | # The private key is stored as an ASN.1 integer which may 23 | # need to have zero padding removed (if 33 bytes) or added 24 | # (if < 32 bytes): 25 | rawPrivateKey = parser.getChild(1).value 26 | rawPrivateKey = fromAsn1IntBytes(rawPrivateKey, 32) 27 | 28 | # There is a 00 04 byte prior to the 64-byte public key 29 | # I'm not sure why M2Crypto has the 00 byte there?, 30 | # some ASN1 thing - the 04 byte signals "uncompressed" 31 | # per SECG. Anyways, strip both those bytes off ([2:]) 32 | rawPublicKey = parser.getChild(3).getTagged().value[2:] 33 | 34 | assert(len(rawPrivateKey) == 32) 35 | assert(len(rawPublicKey) == 64) 36 | 37 | return rawPrivateKey, rawPublicKey 38 | 39 | def _getDerEncodedKeysFromEc(self, ec): 40 | # Get the ASN.1 ECPrivateKey for the object 41 | bio = BIO.MemoryBuffer() 42 | ec.save_key_bio(bio, cipher=None) 43 | pemEncodedKeys = bio.getvalue() 44 | 45 | return PEMDecoder(pemEncodedKeys).getDecoded("EC PRIVATE KEY") 46 | 47 | -------------------------------------------------------------------------------- /tack/crypto/ECPrivateKey.py: -------------------------------------------------------------------------------- 1 | from M2Crypto import EC, BIO 2 | import math 3 | from tack.compat import a2b_hex 4 | from tack.crypto.ASN1 import toAsn1IntBytes, asn1Length, ASN1Parser 5 | from tack.crypto.Digest import Digest 6 | from tack.crypto.ECPublicKey import ECPublicKey 7 | from tack.util.PEMEncoder import PEMEncoder 8 | 9 | class ECPrivateKey: 10 | 11 | def __init__(self, rawPrivateKey, rawPublicKey, ec=None): 12 | assert(rawPrivateKey is not None and rawPublicKey is not None) 13 | self.ec = ec 14 | self.rawPrivateKey = rawPrivateKey 15 | self.rawPublicKey = rawPublicKey 16 | 17 | if not self.ec: 18 | self.ec = self._constructEcFromRawKeys(rawPrivateKey, rawPublicKey) 19 | 20 | def getSignature(self, data): 21 | # Produce ASN.1 signature 22 | hash = Digest.SHA256(data) 23 | asn1SigBytes = self.ec.sign_dsa_asn1(hash) 24 | 25 | # Convert stupid ASN.1 signature into 64-byte signature 26 | # Double-check before returning 27 | sigBytes = self._convertToRawSignature(asn1SigBytes) 28 | 29 | assert(ECPublicKey(self.rawPublicKey, self.ec).verify(data, sigBytes)) 30 | return sigBytes 31 | 32 | def getRawKey(self): 33 | return self.rawPrivateKey 34 | 35 | def _constructEcFromRawKeys(self, rawPrivateKey, rawPublicKey): 36 | assert(len(rawPrivateKey) == 32) 37 | assert(len(rawPublicKey) == 64) 38 | bytes1 = a2b_hex("02010104") 39 | bytes2 = a2b_hex("a00a06082a8648ce3d030107a14403420004") 40 | rawPrivateKey = toAsn1IntBytes(rawPrivateKey) 41 | b = bytes1 + asn1Length(len(rawPrivateKey)) + rawPrivateKey +\ 42 | bytes2 + rawPublicKey 43 | b = bytearray([0x30]) + asn1Length(len(b)) + b 44 | pemPrivKeyBytes = PEMEncoder(b).getEncoded("EC PRIVATE KEY") 45 | 46 | return EC.load_key_bio(BIO.MemoryBuffer(pemPrivKeyBytes)) 47 | 48 | 49 | def _convertToRawSignature(self, signature): 50 | parser = ASN1Parser(bytearray(signature)) 51 | r = self._bytesToNumber(parser.getChild(0).value) 52 | s = self._bytesToNumber(parser.getChild(1).value) 53 | return self._numberToBytes(r, 32) + self._numberToBytes(s, 32) 54 | 55 | 56 | def _bytesToNumber(self, bytes): 57 | "Convert a sequence of bytes (eg bytearray) into integer." 58 | total = 0 59 | multiplier = 1 60 | for count in range(len(bytes)-1, -1, -1): 61 | byte = bytes[count] 62 | total += multiplier * byte 63 | multiplier *= 256 64 | return total 65 | 66 | def _numberToBytes(self, n, howManyBytes=None): 67 | """Convert an integer into a bytearray, zero-pad to howManyBytes. 68 | 69 | The returned bytearray may be smaller than howManyBytes, but will 70 | not be larger. The returned bytearray will contain a big-endian 71 | encoding of the input integer (n). 72 | """ 73 | if not howManyBytes: 74 | howManyBytes = self._numBytes(n) 75 | bytes = bytearray(howManyBytes) 76 | for count in range(howManyBytes-1, -1, -1): 77 | bytes[count] = int(n % 256) 78 | n >>= 8 79 | return bytes 80 | 81 | def _numBytes(self, n): 82 | "Return the number of bytes needed to represent the integer n." 83 | if not n: 84 | return 0 85 | bits = self._numBits(n) 86 | return int(math.ceil(bits / 8.0)) 87 | 88 | def _numBits(self, n): 89 | "Return the number of bits needed to represent the integer n." 90 | if not n: 91 | return 0 92 | s = "%x" % n 93 | return ((len(s)-1)*4) +\ 94 | {'0':0, '1':1, '2':2, '3':2, 95 | '4':3, '5':3, '6':3, '7':3, 96 | '8':4, '9':4, 'a':4, 'b':4, 97 | 'c':4, 'd':4, 'e':4, 'f':4, 98 | }[s[0]] 99 | -------------------------------------------------------------------------------- /tack/crypto/ECPublicKey.py: -------------------------------------------------------------------------------- 1 | from M2Crypto import EC, BIO 2 | from tack.compat import a2b_hex 3 | from tack.compat import b2a_base32 4 | from tack.crypto.ASN1 import toAsn1IntBytes, asn1Length 5 | from tack.crypto.Digest import Digest 6 | from tack.util.PEMEncoder import PEMEncoder 7 | 8 | class ECPublicKey: 9 | 10 | def __init__(self, rawPublicKey, ec=None): 11 | assert(rawPublicKey is not None) 12 | self.ec = ec 13 | self.rawPublicKey = rawPublicKey 14 | 15 | if not self.ec: 16 | self.ec = self._constructEcFromRawKey(self.rawPublicKey) 17 | 18 | def verify(self, data, signature): 19 | # Convert 64-byte signature into a stupid ASN.1 signature 20 | asn1SigBytes = self._convertToAsn1Signature(signature) 21 | hash = Digest.SHA256(data) 22 | 23 | return self.ec.verify_dsa_asn1(hash, asn1SigBytes) 24 | 25 | def getRawKey(self): 26 | return self.rawPublicKey 27 | 28 | def getFingerprint(self): 29 | digest = Digest.SHA256(self.rawPublicKey) 30 | assert(len(digest) == 32) 31 | s = b2a_base32(digest).lower()[:25] 32 | return "%s.%s.%s.%s.%s" % (s[:5],s[5:10],s[10:15],s[15:20],s[20:25]) 33 | 34 | def _constructEcFromRawKey(self, rawPublicKey): 35 | assert(len(rawPublicKey) == 64) 36 | bytes1 = a2b_hex("3059301306072a8648ce3d020106082a8648ce3d03010703420004") 37 | asn1KeyBytes = bytes1 + rawPublicKey 38 | pemPubKeyBytes = PEMEncoder(asn1KeyBytes).getEncoded("PUBLIC KEY") 39 | 40 | return EC.load_pub_key_bio(BIO.MemoryBuffer(pemPubKeyBytes)) 41 | 42 | def _convertToAsn1Signature(self, signature): 43 | assert(len(signature) == 64) 44 | asn1R = toAsn1IntBytes(signature[:32]) 45 | asn1S = toAsn1IntBytes(signature[32:]) 46 | # Add ASN1 Type=2(int), and Length fields 47 | asn1R = bytearray([2]) + asn1Length(len(asn1R)) + asn1R 48 | asn1S = bytearray([2]) + asn1Length(len(asn1S)) + asn1S 49 | # Add ASN1 Type=0x30(Sequence) and Length fields 50 | asn1ECSigBytes = bytearray([0x30]) +\ 51 | asn1Length(len(asn1R+asn1S)) + asn1R + asn1S 52 | return asn1ECSigBytes 53 | 54 | def __str__(self): 55 | return self.getFingerprint() -------------------------------------------------------------------------------- /tack/crypto/PBKDF2.py: -------------------------------------------------------------------------------- 1 | from tack.crypto.Digest import Digest 2 | 3 | class PBKDF2: 4 | 5 | @staticmethod 6 | def _xorbytes(s1, s2): 7 | return bytearray([a^b for a,b in zip(s1,s2)]) 8 | 9 | # Uses PBKDF2-HMAC-SHA256 to produce a 32-byte key 10 | @staticmethod 11 | def hmac_sha256(password, salt, iter_count): 12 | m = salt + bytearray([0,0,0,1]) 13 | result = bytearray(32) 14 | for c in range(iter_count): 15 | m = Digest.HMAC_SHA256(bytearray(password, "ascii"), m) 16 | result = PBKDF2._xorbytes(m, result) 17 | 18 | return result 19 | -------------------------------------------------------------------------------- /tack/crypto/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moxie0/TACKpy/5cd8ee33b4a6eba4ae9fed3582682e2658e4fee2/tack/crypto/__init__.py -------------------------------------------------------------------------------- /tack/structures/Tack.py: -------------------------------------------------------------------------------- 1 | from tack.crypto.ECPublicKey import ECPublicKey 2 | from tack.tls.TlsStructure import TlsStructure 3 | from tack.tls.TlsStructureWriter import TlsStructureWriter 4 | from tack.util.Util import Util 5 | from tack.util.PEMDecoder import PEMDecoder 6 | from tack.util.PEMEncoder import PEMEncoder 7 | from tack.util.Time import Time 8 | 9 | class Tack(TlsStructure): 10 | LENGTH = 166 11 | 12 | def __init__(self, data=None): 13 | TlsStructure.__init__(self, data) 14 | if data is not None: 15 | self.public_key = ECPublicKey(self.getBytes(64)) 16 | self.min_generation = self.getInt(1) 17 | self.generation = self.getInt(1) 18 | self.expiration = self.getInt(4) 19 | self.target_hash = self.getBytes(32) 20 | self.signature = self.getBytes(64) 21 | 22 | if not self._verifySignature(): 23 | raise SyntaxError("Signature verification failure") 24 | if self.index != len(data): 25 | raise SyntaxError("Excess bytes in TACK") 26 | 27 | @classmethod 28 | def createFromPem(cls, pem): 29 | data = PEMDecoder(pem).getDecoded("TACK") 30 | 31 | if len(data) != Tack.LENGTH: 32 | raise SyntaxError("TACK is the wrong size. %s, should be %s" % (len(data), Tack.LENGTH)) 33 | 34 | return cls(data) 35 | 36 | @classmethod 37 | def createFromParameters(cls, public_key, private_key, min_generation, generation, expiration, target_hash): 38 | assert(len(public_key.getRawKey()) == 64) 39 | assert(0 <= min_generation <= 255) 40 | assert(0 <= generation <= 255 and 41 | generation >= min_generation) 42 | assert(0 <= expiration <= 2**32-1) 43 | assert(len(target_hash) == 32) 44 | 45 | tack = cls() 46 | tack.public_key = public_key 47 | tack.min_generation = min_generation 48 | tack.generation = generation 49 | tack.expiration = expiration 50 | tack.target_hash = target_hash 51 | tack.signature = private_key.getSignature(tack._getDataToSign()) 52 | 53 | return tack 54 | 55 | def getTackId(self): 56 | return str(self.public_key) 57 | 58 | def serialize(self): 59 | writer = TlsStructureWriter(64) 60 | writer.add(self.signature, 64) 61 | return self._serializePrelude() + writer.getBytes() 62 | 63 | def serializeAsPem(self): 64 | return PEMEncoder(self.serialize()).getEncoded("TACK") 65 | 66 | def _serializePrelude(self): 67 | writer = TlsStructureWriter(Tack.LENGTH - 64) 68 | writer.add(self.public_key.getRawKey(), 64) 69 | writer.add(self.min_generation, 1) 70 | writer.add(self.generation, 1) 71 | writer.add(self.expiration, 4) 72 | writer.add(self.target_hash, 32) 73 | return writer.getBytes() 74 | 75 | def _getDataToSign(self): 76 | return bytearray("tack_sig", "ascii") + self._serializePrelude() 77 | 78 | def _verifySignature(self): 79 | bytesToVerify = self._getDataToSign() 80 | return self.public_key.verify(bytesToVerify, self.signature) 81 | 82 | def __str__(self): 83 | """Return a readable string describing this TACK. 84 | 85 | Used by the "TACK view" command to display TACK objects.""" 86 | s =\ 87 | """TACK ID = %s 88 | min_generation = %d 89 | generation = %d 90 | expiration = %s 91 | target_hash = %s\n""" %\ 92 | (self.getTackId(), 93 | self.min_generation, 94 | self.generation, 95 | Time.posixTimeToStr(self.expiration*60), 96 | Util.writeBytes(self.target_hash)) 97 | return s 98 | -------------------------------------------------------------------------------- /tack/structures/TackActivation.py: -------------------------------------------------------------------------------- 1 | 2 | class TackActivation: 3 | DISABLED = 0 4 | ENABLED = 1 5 | ALL = (DISABLED, ENABLED) 6 | STRINGS = ["disabled", "enabled"] -------------------------------------------------------------------------------- /tack/structures/TackBreakSig.py: -------------------------------------------------------------------------------- 1 | from tack.crypto.ECPublicKey import ECPublicKey 2 | from tack.tls.TlsStructure import TlsStructure 3 | from tack.tls.TlsStructureWriter import TlsStructureWriter 4 | from tack.util.PEMDecoder import PEMDecoder 5 | from tack.util.PEMEncoder import PEMEncoder 6 | 7 | class TackBreakSig(TlsStructure): 8 | LENGTH = 128 9 | 10 | def __init__(self, data=None): 11 | TlsStructure.__init__(self, data) 12 | 13 | if data is not None and len(data) != TackBreakSig.LENGTH: 14 | raise SyntaxError("Break signature is the wrong size. Is %s and should be %s." % (len(data), TackBreakSig.LENGTH)) 15 | 16 | if data is not None: 17 | self.public_key = ECPublicKey(self.getBytes(64)) 18 | self.signature = self.getBytes(64) 19 | 20 | if not self._verifySignature(): 21 | raise SyntaxError("Signature verification failure") 22 | 23 | if self.index != len(data): 24 | raise SyntaxError("Excess bytes in TACK_Break_Sig") 25 | 26 | @classmethod 27 | def createFromPem(cls, data): 28 | return cls(PEMDecoder(data).getDecoded("TACK BREAK SIG")) 29 | 30 | @classmethod 31 | def createFromPemList(cls, data): 32 | """Parse a string containing a sequence of PEM Break Sigs. 33 | 34 | Raise a SyntaxError if input is malformed. 35 | """ 36 | breakSigs = [] 37 | bList = PEMDecoder(data).getDecodedList("TACK BREAK SIG") 38 | for b in bList: 39 | breakSigs.append(TackBreakSig(b)) 40 | 41 | return breakSigs 42 | 43 | @classmethod 44 | def createFromParameters(cls, public_key, private_key): 45 | assert(len(public_key) == 64) 46 | tackBreakSig = cls() 47 | tackBreakSig.public_key = public_key 48 | tackBreakSig.signature = private_key.getSignature(bytearray("tack_break_sig", "ascii")) 49 | 50 | return tackBreakSig 51 | 52 | def serialize(self): 53 | """Return a bytearray containing the TACK_Break_Sig.""" 54 | w = TlsStructureWriter(TackBreakSig.LENGTH) 55 | w.add(self.public_key.getRawKey(), 64) 56 | w.add(self.signature, 64) 57 | assert(w.index == len(w.bytes)) 58 | return w.getBytes() 59 | 60 | def serializeAsPem(self): 61 | return PEMEncoder(self.serialize()).getEncoded("TACK BREAK SIG") 62 | 63 | def getTackId(self): 64 | return str(self.public_key) 65 | 66 | def _verifySignature(self): 67 | return self.public_key.verify(bytearray("tack_break_sig"), self.signature) 68 | 69 | def __str__(self): 70 | """Return a readable string describing this TACK_Break_Sig. 71 | Used by the "TACK view" command to display TACK objects.""" 72 | return "Breaks TACK ID = %s\n" % self.getTackId() 73 | -------------------------------------------------------------------------------- /tack/structures/TackExtension.py: -------------------------------------------------------------------------------- 1 | from tack.structures.Tack import Tack 2 | from tack.structures.TackActivation import TackActivation 3 | from tack.structures.TackBreakSig import TackBreakSig 4 | from tack.tls.TlsStructure import TlsStructure 5 | from tack.tls.TlsStructureWriter import TlsStructureWriter 6 | 7 | class TackExtension(TlsStructure): 8 | 9 | def __init__(self, data=None): 10 | TlsStructure.__init__(self, data) 11 | if data is not None: 12 | self.tack = self._parseTack() 13 | self.break_sigs = self._parseBreakSigs() 14 | self.pin_activation = self.getInt(1) 15 | 16 | if self.pin_activation not in TackActivation.ALL: 17 | raise SyntaxError("Bad pin_activation value") 18 | 19 | if self.index != len(data): 20 | raise SyntaxError("Excess bytes in TACK_Extension") 21 | 22 | @classmethod 23 | def createFromParameters(cls, tack, break_sigs, pin_activation): 24 | tackExtension = cls() 25 | tackExtension.tack = tack 26 | tackExtension.break_sigs = break_sigs 27 | tackExtension.pin_activation = pin_activation 28 | 29 | return tackExtension 30 | 31 | def serialize(self): 32 | w = TlsStructureWriter(self._getSerializedLength()) 33 | 34 | if self.tack: 35 | w.add(Tack.LENGTH, 1) 36 | w.add(self.tack.serialize(), Tack.LENGTH) 37 | else: 38 | w.add(0, 1) 39 | 40 | if self.break_sigs: 41 | w.add(len(self.break_sigs) * TackBreakSig.LENGTH, 2) 42 | for break_sig in self.break_sigs: 43 | w.add(break_sig.serialize(), TackBreakSig.LENGTH) 44 | else: 45 | w.add(0, 2) 46 | 47 | w.add(self.pin_activation, 1) 48 | 49 | assert(w.index == len(w.bytes)) # did we fill entire bytearray? 50 | return w.getBytes() 51 | 52 | def isEmpty(self): 53 | return not self.tack and not self.break_sigs 54 | 55 | def _getSerializedLength(self): 56 | length = 0 57 | if self.tack: 58 | length += Tack.LENGTH 59 | 60 | if self.break_sigs: 61 | length += len(self.break_sigs) * TackBreakSig.LENGTH 62 | 63 | return length + 4 64 | 65 | def _parseTack(self): 66 | tackLen = self.getInt(1) 67 | 68 | if tackLen != Tack.LENGTH: 69 | raise SyntaxError("TACK wrong size") 70 | 71 | return Tack(self.getBytes(tackLen)) 72 | 73 | def _parseBreakSigs(self): 74 | sigsLen = self.getInt(2) 75 | 76 | if sigsLen > 1024: 77 | raise SyntaxError("break_sigs too large") 78 | elif sigsLen % TackBreakSig.LENGTH != 0: 79 | raise SyntaxError("break_sigs wrong size") 80 | 81 | break_sigs = [] 82 | b2 = self.getBytes(sigsLen) 83 | while b2: 84 | break_sigs.append(TackBreakSig(b2[:TackBreakSig.LENGTH])) 85 | b2 = b2[TackBreakSig.LENGTH:] 86 | 87 | return break_sigs 88 | 89 | def __str__(self): 90 | result = "" 91 | 92 | if self.tack: 93 | result += str(self.tack) 94 | 95 | if self.break_sigs: 96 | for break_sig in self.break_sigs: 97 | result += str(break_sig) 98 | 99 | result += "pin_activation = %s\n" % TackActivation.STRINGS[self.pin_activation] 100 | 101 | return result 102 | -------------------------------------------------------------------------------- /tack/structures/TackKeyFile.py: -------------------------------------------------------------------------------- 1 | """ 2 | File format: 3 | 4 | version 1 byte = 0x01 5 | iter_count 4 bytes = uint32, bigendian 6 | salt 16 bytes 7 | EC privkey 32 bytes } aes256-cbc(IV=0) } hmac-sha256 8 | EC pubkey 64 bytes } hmac-sha256 9 | HMAC 32 bytes 10 | 11 | Total 149 12 | 13 | The AES256-CBC and HMAC-SHA256 steps require independent 14 | 32-byte keys (encKey and authKey, respectively). 15 | 16 | These keys are derived from a 32-byte masterKey. The masterKey is 17 | derived from a password via PBKDF2-HMAC-SHA26: 18 | 19 | masterKey = PBKDF2-HMAC-SHA256(password, salt, iter_count) 20 | encKey = HMAC-SHA256(masterKey, 0x01) 21 | authKey = HMAC-SHA256(masterKey, 0x02) 22 | """ 23 | import os 24 | from tack.InvalidPasswordException import InvalidPasswordException 25 | from tack.crypto.ECGenerator import ECGenerator 26 | from tack.crypto.ECPrivateKey import ECPrivateKey 27 | from tack.crypto.ECPublicKey import ECPublicKey 28 | from tack.crypto.PBKDF2 import PBKDF2 29 | from tack.crypto.AES import AES 30 | from tack.crypto.Digest import Digest 31 | from tack.tls.TlsStructure import TlsStructure 32 | from tack.tls.TlsStructureWriter import TlsStructureWriter 33 | from tack.util.Util import Util 34 | from tack.util.PEMDecoder import PEMDecoder 35 | from tack.util.PEMEncoder import PEMEncoder 36 | 37 | class TackKeyFile(TlsStructure): 38 | LENGTH = 149 # length of keyfile in bytes 39 | 40 | def __init__(self, data=None, password=None): 41 | TlsStructure.__init__(self, data) 42 | if data is not None: 43 | self.version = self.getInt(1) 44 | 45 | if self.version != 1: 46 | raise SyntaxError("Bad version in Secret File") 47 | 48 | self.password = password 49 | self.iter_count = self.getInt(4) 50 | self.salt = self.getBytes(16) 51 | self.ciphertext = self.getBytes(32) 52 | self.public_key = ECPublicKey(self.getBytes(64)) 53 | self.mac = bytearray(self.getBytes(32)) 54 | 55 | if self.password is not None: 56 | rawPrivateKey = self._decryptKey(password, self.salt, self.ciphertext, 57 | self.iter_count, self.public_key, self.mac) 58 | self.private_key = ECPrivateKey(rawPrivateKey, self.public_key.getRawKey()) 59 | 60 | @classmethod 61 | def createRandom(cls, password): 62 | tackKeyFile = cls() 63 | tackKeyFile.password = password 64 | tackKeyFile.version = 1 65 | tackKeyFile.iter_count = 8192 66 | tackKeyFile.salt = bytearray(os.urandom(16)) 67 | tackKeyFile.public_key, tackKeyFile.private_key = ECGenerator().generateECKeyPair() 68 | tackKeyFile.ciphertext, tackKeyFile.mac = tackKeyFile._encryptKey(password, tackKeyFile.salt, 69 | tackKeyFile.iter_count, 70 | tackKeyFile.public_key, 71 | tackKeyFile.private_key) 72 | return tackKeyFile 73 | 74 | @classmethod 75 | def createFromPem(cls, pem, password): 76 | return cls(PEMDecoder(pem).getDecoded("TACK PRIVATE KEY"), password) 77 | 78 | def getPublicKey(self): 79 | return self.public_key 80 | 81 | def getPrivateKey(self): 82 | return self.private_key 83 | 84 | def serialize(self): 85 | w = TlsStructureWriter(TackKeyFile.LENGTH) 86 | w.add(self.version, 1) 87 | w.add(self.iter_count, 4) 88 | w.add(self.salt, 16) 89 | w.add(self.ciphertext, 32) 90 | w.add(self.public_key.getRawKey(), 64) 91 | w.add(self.mac, 32) 92 | assert(w.index == len(w.bytes)) # did we fill entire bytearray? 93 | 94 | return w.getBytes() 95 | 96 | def serializeAsPem(self): 97 | return PEMEncoder(self.serialize()).getEncoded("TACK PRIVATE KEY") 98 | 99 | def _encryptKey(self, password, salt, iter_count, public_key, private_key): 100 | encKey, authKey = self._deriveKeys(password, salt, iter_count) 101 | ciphertext = AES(encKey, bytearray(16)).encrypt(private_key.getRawKey()) 102 | macData = ciphertext + public_key.getRawKey() 103 | mac = Digest.HMAC_SHA256(authKey, macData) 104 | return ciphertext, mac 105 | 106 | 107 | def _decryptKey(self, password, salt, ciphertext, iter_count, public_key, mac): 108 | encKey, authKey = self._deriveKeys(password, salt, iter_count) 109 | macData = ciphertext + public_key.getRawKey() 110 | calcMac = Digest.HMAC_SHA256(authKey, macData) 111 | 112 | if not Util.constTimeCompare(calcMac, mac): 113 | raise InvalidPasswordException("Bad password") 114 | 115 | return AES(encKey, bytearray(16)).decrypt(ciphertext) 116 | 117 | # Uses PBKDF2, then HMAC-SHA256 as PRF to derive independent 32-byte keys 118 | def _deriveKeys(self, password, salt, iter_count): 119 | assert(iter_count>0) 120 | masterKey = PBKDF2.hmac_sha256(password, salt, iter_count) 121 | encKey = Digest.HMAC_SHA256(masterKey, bytearray([1])) 122 | authKey = Digest.HMAC_SHA256(masterKey, bytearray([2])) 123 | return encKey, authKey 124 | 125 | def __str__(self): 126 | return """TACK ID = %s\n""" % str(self.public_key) 127 | 128 | -------------------------------------------------------------------------------- /tack/structures/TackVersion.py: -------------------------------------------------------------------------------- 1 | 2 | class TackVersion: 3 | V1 = 0 -------------------------------------------------------------------------------- /tack/structures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moxie0/TACKpy/5cd8ee33b4a6eba4ae9fed3582682e2658e4fee2/tack/structures/__init__.py -------------------------------------------------------------------------------- /tack/tls/TlsCertificate.py: -------------------------------------------------------------------------------- 1 | from tack.compat import a2b_hex 2 | from tack.crypto.ASN1 import ASN1Parser, asn1Length 3 | from tack.crypto.Digest import Digest 4 | from tack.structures.TackExtension import TackExtension 5 | from tack.structures.TackVersion import TackVersion 6 | from tack.util.PEMDecoder import PEMDecoder 7 | from tack.util.PEMEncoder import PEMEncoder 8 | from tack.util.Time import Time 9 | from tack.util.Util import Util 10 | 11 | class TlsCertificate: 12 | 13 | OID_TACK = bytearray(b"\x2B\x06\x01\x04\x01\x82\xB0\x34\x01") 14 | 15 | # def __init__(self, data): 16 | # if data is not None: 17 | # self.serialized = data 18 | # self.certificate = X509.load_cert_string(data) 19 | # self.notAfter = time.mktime(self.certificate.get_not_after().get_datetime().timetuple()) 20 | # self.cert_sha256 = bytearray(base64.b16decode(self.certificate.get_fingerprint(md='sha256'))) 21 | # self.key_sha256 = Util.SHA256(self.certificate.get_pubkey().as_der()) 22 | # self.tackExt = self._parseTackExtension(data) 23 | 24 | def __init__(self): 25 | self.key_sha256 = bytearray(32) 26 | self.cert_sha256 = bytearray(32) 27 | self.notAfter = 0 28 | # Below values are populated for TACK certs 29 | self.tackExt = None 30 | # Below values hold cert contents excluding TACK stuff 31 | self.preExtBytes = None 32 | self.extBytes = None 33 | self.postExtBytes = None 34 | 35 | def create(self, tackExt = None): 36 | self.tackExt = tackExt 37 | self.preExtBytes = a2b_hex( 38 | "a003020102020100300d06092a864886f70d0101050500300f310d300b06035504031" 39 | "3045441434b301e170d3031303730353138303534385a170d34343037303431383035" 40 | "34385a300f310d300b060355040313045441434b301f300d06092a864886f70d01010" 41 | "10500030e00300b0204010203050203010001") 42 | # Below is BasicConstraints, saving space by omitting 43 | #self.extBytes = binascii.a2b_hex(\ 44 | #"300c0603551d13040530030101ff") 45 | self.extBytes = bytearray() 46 | self.postExtBytes = a2b_hex( 47 | "300d06092a864886f70d01010505000303003993") 48 | 49 | def open(self, filename): 50 | # May raise IOError or SyntaxError 51 | try: 52 | sslStr = open(filename, "rU").read() # IOError, UnicodeDecodeError 53 | self.parsePem(sslStr) # SyntaxError 54 | return 55 | except (UnicodeDecodeError, SyntaxError): 56 | # File had non-text chars in it (python3), *OR* 57 | # File did not PEM-decode 58 | pass 59 | sslBytes = bytearray(open(filename, "rb").read()) # IOError 60 | self.parse(sslBytes) # SyntaxError 61 | 62 | def matches(self, tack): 63 | if tack.version == TackVersion.V1: 64 | return self.key_sha256 == tack.sig.target_sha256 65 | return False 66 | 67 | def parsePem(self, s): 68 | b = PEMDecoder(s).getDecoded("CERTIFICATE") 69 | self.parse(b) 70 | 71 | def parse(self, b): 72 | p = ASN1Parser(b) 73 | 74 | #Get the tbsCertificate 75 | tbsCertificateP = p.getChild(0) 76 | 77 | #Is the optional version field present? 78 | #This determines which index the key is at 79 | if tbsCertificateP.value[0]==0xA0: 80 | subjectPublicKeyInfoIndex = 6 81 | validityIndex = 4 82 | else: 83 | subjectPublicKeyInfoIndex = 5 84 | validityIndex = 3 85 | #Get the subjectPublicKeyInfo 86 | spkiP = tbsCertificateP.getChild(subjectPublicKeyInfoIndex) 87 | 88 | #Parse the notAfter time 89 | validityP = tbsCertificateP.getChild(validityIndex) 90 | notAfterP = validityP.getChild(1) 91 | if notAfterP.type == 0x17: # UTCTime 92 | self.notAfter = Time.parseASN1UTCTime(notAfterP.value) 93 | elif notAfterP.type == 0x18: # GeneralizedTime 94 | self.notAfter = Time.parseASN1GeneralizedTime(notAfterP.value) 95 | else: 96 | raise SyntaxError() 97 | 98 | # Get the hash values 99 | self.cert_sha256 = Digest.SHA256(b) 100 | self.key_sha256 = Digest.SHA256(spkiP.getTotalBytes()) 101 | 102 | # Check if this is a TACK certificate: 103 | #Get the tbsCertificate 104 | versionP = tbsCertificateP.getChild(0) 105 | if versionP.type != 0xA0: # i.e. tag of [0], version 106 | return # X.509 version field not present 107 | versionPP = versionP.getTagged() 108 | if versionPP.value != bytearray([0x02]): 109 | return # X.509 version field does not equal v3 110 | 111 | # Find extensions element 112 | x = 0 113 | while 1: 114 | certFieldP = tbsCertificateP.getChild(x) 115 | if not certFieldP: 116 | raise SyntaxError("X.509 extensions not present") 117 | if certFieldP.type == 0xA3: # i.e. tag of [3], extensions 118 | break 119 | x += 1 120 | 121 | self.preExtBytes = b[versionP.offset : certFieldP.offset] 122 | self.extBytes = bytearray() 123 | 124 | # Iterate through extensions 125 | x = 0 126 | certFieldPP = certFieldP.getTagged() 127 | while 1: 128 | extFieldP = certFieldPP.getChild(x) 129 | if not extFieldP: 130 | break 131 | 132 | # Check the extnID and parse out TACK if present 133 | extnIDP = extFieldP.getChild(0) 134 | if extnIDP.value == TlsCertificate.OID_TACK: 135 | if self.tackExt: 136 | raise SyntaxError("More than one TACK Extension") 137 | 138 | # OK! We found a TACK, parse it.. 139 | self.tackExt = TackExtension(extFieldP.getChild(1).value) 140 | else: 141 | # Collect all non-TACK extensions: 142 | self.extBytes += b[extFieldP.offset :\ 143 | extFieldP.offset + extFieldP.getTotalLength()] 144 | x += 1 145 | 146 | # Finish copying the tail of the certificate 147 | self.postExtBytes = b[certFieldP.offset + certFieldP.getTotalLength():] 148 | 149 | def write(self): 150 | b = bytearray(0) 151 | if self.tackExt: 152 | # type=SEQ,len=?,type=6,len=9(for OID), 153 | # type=4,len=?,TACK 154 | TACKBytes = self.tackExt.serialize() 155 | b = bytearray([4]) + asn1Length(len(TACKBytes)) + TACKBytes 156 | b = bytearray([6,9]) + TlsCertificate.OID_TACK + b 157 | b = bytearray([0x30]) + asn1Length(len(b)) + b 158 | 159 | b = b + self.extBytes # add non-TACK extensions after TACK 160 | # Add length fields for extensions and its enclosing tag 161 | b = bytearray([0x30]) + asn1Length(len(b)) + b 162 | b = bytearray([0xA3]) + asn1Length(len(b)) + b 163 | # Add prefix of tbsCertificate, then its type/length fields 164 | b = self.preExtBytes + b 165 | b = bytearray([0x30]) + asn1Length(len(b)) + b 166 | # Add postfix of Certificate (ie SignatureAlgorithm, SignatureValue) 167 | # then its prefix'd type/length fields 168 | b = b + self.postExtBytes 169 | b = bytearray([0x30]) + asn1Length(len(b)) + b 170 | return b 171 | 172 | def writePem(self): 173 | b = self.write() 174 | return PEMEncoder(b).getEncoded("CERTIFICATE") 175 | def writeText(self): 176 | s =\ 177 | """key_sha256 = 0x%s 178 | notAfter = %s 179 | """ % (\ 180 | Util.writeBytes(self.key_sha256), 181 | Time.posixTimeToStr(self.notAfter, True)) 182 | if self.tackExt: 183 | s += "\n" + str(self.tackExt) 184 | return s 185 | -------------------------------------------------------------------------------- /tack/tls/TlsStructure.py: -------------------------------------------------------------------------------- 1 | 2 | class TlsStructure: 3 | def __init__(self, bytes): 4 | self.bytes = bytes 5 | self.index = 0 6 | 7 | def getInt(self, elementLength): 8 | """Reads an integer of 'length' bytes""" 9 | if self.index + elementLength > len(self.bytes): 10 | raise SyntaxError("Reading %s at index %s but only %s bytes remaining." \ 11 | % (elementLength, self.index, len(self.bytes))) 12 | x = 0 13 | for count in range(elementLength): 14 | x <<= 8 15 | x |= self.bytes[self.index] 16 | self.index += 1 17 | return x 18 | 19 | def getBytes(self, elementLength): 20 | """Reads some number of bytes as determined by 'elementLength'""" 21 | bytes = self.bytes[self.index : self.index + elementLength] 22 | self.index += elementLength 23 | return bytes 24 | 25 | def getVarSeqBytes(self, elementLength, lengthLength): 26 | dataLength = self.getInt(lengthLength) 27 | if dataLength % elementLength != 0: 28 | raise SyntaxError() 29 | return [self.getBytes(elementLength) for x in\ 30 | range(dataLength//elementLength)] 31 | -------------------------------------------------------------------------------- /tack/tls/TlsStructureWriter.py: -------------------------------------------------------------------------------- 1 | class TlsStructureWriter: 2 | def __init__(self, totalLength): 3 | self.index = 0 4 | self.bytes = bytearray(totalLength) 5 | 6 | def add(self, x, elementLength): 7 | """Writes 'elementLength' bytes, input is either an integer 8 | (written as big-endian) or a sequence of bytes""" 9 | if isinstance(x, int): 10 | assert(0 <= x < 2**(8*elementLength)) 11 | newIndex = self.index + elementLength-1 12 | while newIndex >= self.index: 13 | self.bytes[newIndex] = x & 0xFF 14 | x >>= 8 15 | newIndex -= 1 16 | else: 17 | assert(len(x) == elementLength) 18 | for i in range(elementLength): 19 | self.bytes[self.index + i] = x[i] 20 | self.index += elementLength 21 | 22 | def addVarSeq(self, seq, elementLength, lengthLength): 23 | """Writes a sequence of elements prefixed by a 24 | total-length field of lengthLength bytes""" 25 | self.add(len(seq)*elementLength, lengthLength) 26 | for e in seq: 27 | self.add(e, elementLength) 28 | 29 | def getBytes(self): 30 | return self.bytes -------------------------------------------------------------------------------- /tack/tls/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moxie0/TACKpy/5cd8ee33b4a6eba4ae9fed3582682e2658e4fee2/tack/tls/__init__.py -------------------------------------------------------------------------------- /tack/util/PEMDecoder.py: -------------------------------------------------------------------------------- 1 | from tack.compat import a2b_base64 2 | 3 | class PEMDecoder: 4 | 5 | def __init__(self, data): 6 | self.data = data 7 | 8 | def containsEncoded(self, name): 9 | searchStr = "-----BEGIN %s-----" % name 10 | return searchStr in self.data 11 | 12 | def getDecoded(self, name): 13 | """Decode a PEM string into a bytearray of its payload. 14 | 15 | The input must contain an appropriate PEM prefix and postfix 16 | based on the input name string, e.g. for name="CERTIFICATE": 17 | 18 | -----BEGIN CERTIFICATE----- 19 | MIIBXDCCAUSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRUQUNL 20 | ... 21 | KoZIhvcNAQEFBQADAwA5kw== 22 | -----END CERTIFICATE----- 23 | 24 | The first such PEM block in the input will be found, and its 25 | payload will be base64 decoded and returned. 26 | """ 27 | prefix = "-----BEGIN %s-----" % name 28 | postfix = "-----END %s-----" % name 29 | start = self.data.find(prefix) 30 | if start == -1: 31 | raise SyntaxError("Missing PEM prefix") 32 | end = self.data.find(postfix, start+len(prefix)) 33 | if end == -1: 34 | raise SyntaxError("Missing PEM postfix") 35 | s = self.data[start+len("-----BEGIN %s-----" % name) : end] 36 | retBytes = a2b_base64(s) 37 | return retBytes 38 | 39 | 40 | def getDecodedList(self, name): 41 | """Decode a sequence of PEM blocks into a list of bytearrays. 42 | 43 | The input must contain any number of PEM blocks, each with the appropriate 44 | PEM prefix and postfix based on the input name string, e.g. for 45 | name="TACK BREAK SIG". Arbitrary text can appear between and before and 46 | after the PEM blocks. For example: 47 | 48 | " Created by tack.py 0.9.3 Created at 2012-02-01T00:30:10Z -----BEGIN TACK 49 | BREAK SIG----- 50 | ATKhrz5C6JHJW8BF5fLVrnQss6JnWVyEaC0p89LNhKPswvcC9/s6+vWLd9snYTUv 51 | YMEBdw69PUP8JB4AdqA3K6Ap0Fgd9SSTOECeAKOUAym8zcYaXUwpk0+WuPYa7Zmm 52 | SkbOlK4ywqt+amhWbg9txSGUwFO5tWUHT3QrnRlE/e3PeNFXLx5Bckg= -----END TACK 53 | BREAK SIG----- Created by tack.py 0.9.3 Created at 2012-02-01T00:30:11Z 54 | -----BEGIN TACK BREAK SIG----- 55 | ATKhrz5C6JHJW8BF5fLVrnQss6JnWVyEaC0p89LNhKPswvcC9/s6+vWLd9snYTUv 56 | YMEBdw69PUP8JB4AdqA3K6BVCWfcjN36lx6JwxmZQncS6sww7DecFO/qjSePCxwM 57 | +kdDqX/9/183nmjx6bf0ewhPXkA0nVXsDYZaydN8rJU1GaMlnjcIYxY= -----END TACK 58 | BREAK SIG----- " 59 | 60 | All such PEM blocks will be found, decoded, and return in an ordered list 61 | of bytearrays, which may have zero elements if not PEM blocks are found. 62 | """ 63 | bList = [] 64 | prefix = "-----BEGIN %s-----" % name 65 | postfix = "-----END %s-----" % name 66 | 67 | s = self.data 68 | 69 | while 1: 70 | start = s.find(prefix) 71 | if start == -1: 72 | return bList 73 | end = s.find(postfix, start+len(prefix)) 74 | if end == -1: 75 | raise SyntaxError("Missing PEM postfix") 76 | s2 = s[start+len(prefix) : end] 77 | retBytes = a2b_base64(s2) # May raise SyntaxError 78 | bList.append(retBytes) 79 | s = s[end+len(postfix) : ] 80 | -------------------------------------------------------------------------------- /tack/util/PEMEncoder.py: -------------------------------------------------------------------------------- 1 | from binascii import b2a_base64 2 | 3 | class PEMEncoder: 4 | 5 | def __init__(self, data): 6 | self.data = data 7 | 8 | def getEncoded(self, name): 9 | """Encode a payload bytearray into a PEM string. 10 | 11 | The input will be base64 encoded, then wrapped in a PEM prefix/postfix 12 | based on the name string, e.g. for name="CERTIFICATE": 13 | 14 | -----BEGIN CERTIFICATE----- 15 | MIIBXDCCAUSgAwIBAgIBADANBgkqhkiG9w0BAQUFADAPMQ0wCwYDVQQDEwRUQUNL 16 | ... 17 | KoZIhvcNAQEFBQADAwA5kw== 18 | -----END CERTIFICATE----- 19 | """ 20 | s1 = b2a_base64(self.data)[:-1] # remove terminating \n 21 | s2 = "" 22 | while s1: 23 | s2 += s1[:64] + "\n" 24 | s1 = s1[64:] 25 | 26 | s = ("-----BEGIN %s-----\n" % name) + s2 +\ 27 | ("-----END %s-----\n" % name) 28 | 29 | return s 30 | -------------------------------------------------------------------------------- /tack/util/Time.py: -------------------------------------------------------------------------------- 1 | # Author: Trevor Perrin 2 | # See the LICENSE file for legal information regarding use of this file. 3 | 4 | from tack.compat import * 5 | 6 | ################ TIME FUNCS ### 7 | 8 | import time, calendar, math 9 | 10 | class Time: 11 | # u in seconds 12 | @staticmethod 13 | def posixTimeToStr(u, includeSeconds=False): 14 | t = time.gmtime(u) 15 | if includeSeconds: 16 | s = time.strftime("%Y-%m-%dT%H:%M:%SZ", t) 17 | else: 18 | s = time.strftime("%Y-%m-%dT%H:%MZ", t) 19 | return s 20 | 21 | # u in minutes 22 | @staticmethod 23 | def durationToStr(u): 24 | s = "" 25 | if u >= (1440): # 1440 minutes per day 26 | s += "%dd" % (u//1440) 27 | u %= 1440 28 | if u >= (60): # 60 minutes per hour 29 | s += "%dh" % (u//60) 30 | u %= 60 31 | if u>0 or not s: 32 | s += "%dm" % u 33 | return s 34 | 35 | @staticmethod 36 | def parseTimeArg(arg): 37 | # First, see if they specified time as a duration 38 | try: 39 | mins = Time.parseDurationArg(arg) 40 | return int(math.ceil(time.time() / 60.0)) + mins 41 | except SyntaxError: 42 | pass 43 | 44 | # Otherwise, allow them to specify as much or as little of 45 | # ISO8601 as they want, but must end with "Z" 46 | patterns = ["%Y-%m-%dT%H:%MZ", "%Y-%m-%dT%HZ", 47 | "%Y-%m-%dZ", "%Y-%mZ", "%YZ"] 48 | t = None 49 | for p in patterns: 50 | try: 51 | t = time.strptime(arg, p) 52 | break 53 | except ValueError: 54 | pass 55 | if not t: 56 | s = Time.posixTimeToStr(time.time()) 57 | raise SyntaxError( 58 | '''Invalid time format, use e.g. "%s" (current time) 59 | or some prefix, such as: "%sZ", "%sZ", or "%sZ", 60 | *OR* some duration, such as "5m", "30d", "1d12h5m", etc."''' % 61 | (s, s[:13], s[:10], s[:4])) 62 | u = int(calendar.timegm(t)//60) 63 | if u < 0: 64 | raise SyntaxError("Time too early, epoch starts at 1970.") 65 | return u 66 | 67 | @staticmethod 68 | def parseDurationArg(arg): 69 | arg = arg.upper() 70 | foundSomething = False 71 | try: 72 | mins = 0 73 | while 1: 74 | i = arg.find("D") 75 | if i != -1: 76 | mins += 1440 * int(arg[:i]) 77 | arg = arg[i+1:] 78 | foundSomething = True 79 | i = arg.find("H") 80 | if i != -1: 81 | mins += 60 * int(arg[:i]) 82 | arg = arg[i+1:] 83 | foundSomething = True 84 | i = arg.find("M") 85 | if i != -1: 86 | mins += int(arg[:i]) 87 | arg = arg[i+1:] 88 | foundSomething = True 89 | if arg: 90 | raise SyntaxError() 91 | if not foundSomething: 92 | raise SyntaxError() 93 | return mins 94 | except: 95 | raise SyntaxError() 96 | 97 | # Return UNIX time int 98 | @staticmethod 99 | def parseASN1UTCTime(b): 100 | try: 101 | if b[-1] != ord("Z"): 102 | raise SyntaxError() 103 | if len(b) == len("YYMHDDHHMMSSZ"): 104 | pass 105 | elif len(b) == len("YYHHDDHHMMZ"): 106 | b = b[:-1] + b"00Z" 107 | else: 108 | raise SyntaxError() 109 | year = int(b[:2]) 110 | if year < 50: 111 | b = b"20" + b 112 | else: 113 | b = b"19" + b 114 | except: 115 | raise SyntaxError() 116 | return Time.parseASN1GeneralizedTime(b) 117 | 118 | @staticmethod 119 | def parseASN1GeneralizedTime(b): 120 | t = time.strptime(bytesToStrAscii(b), "%Y%m%d%H%M%SZ") 121 | return int(calendar.timegm(t)) 122 | 123 | -------------------------------------------------------------------------------- /tack/util/Util.py: -------------------------------------------------------------------------------- 1 | from tack.compat import b2a_hex 2 | 3 | class Util: 4 | @staticmethod 5 | def writeBytes(b): 6 | """Write hex-encoded byte array with 16 bytes (32 chars) per line""" 7 | s = b2a_hex(b) 8 | retVal = "" 9 | while s: 10 | retVal += s[:32] 11 | s = s[32:] 12 | if len(s): 13 | retVal += "\n " 14 | return retVal 15 | 16 | @staticmethod 17 | def constTimeCompare(a, b): 18 | """Compare two sequences of integer (eg bytearrays) without a timing leak. 19 | 20 | This function is secure when comparing against secret values, such as 21 | passwords, MACs, etc., where a more naive, early-exit comparison loop 22 | would leak information that could be used to extract the secret. 23 | """ 24 | if len(a) != len(b): 25 | return False 26 | result = 0 27 | for x in range(len(a)): 28 | result |= a[x]^b[x] 29 | if result: 30 | return False 31 | return True 32 | -------------------------------------------------------------------------------- /tack/util/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moxie0/TACKpy/5cd8ee33b4a6eba4ae9fed3582682e2658e4fee2/tack/util/__init__.py -------------------------------------------------------------------------------- /tack/version.py: -------------------------------------------------------------------------------- 1 | # Author: Trevor Perrin 2 | # See the LICENSE file for legal information regarding use of this file. 3 | 4 | ################ VERSION ### 5 | 6 | __version__ = "0.9.6" 7 | 8 | -------------------------------------------------------------------------------- /tests/CertificateTest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tack.tls.TlsCertificate import TlsCertificate 3 | from tack.compat import a2b_hex 4 | from tack.util.Time import Time 5 | 6 | class CertificateTest(unittest.TestCase): 7 | 8 | def test_Certificate(self): 9 | s = """ 10 | -----BEGIN CERTIFICATE----- 11 | MIIFSzCCBDOgAwIBAgIHJ6JvWHUrOTANBgkqhkiG9w0BAQUFADCByjELMAkGA1UE 12 | BhMCVVMxEDAOBgNVBAgTB0FyaXpvbmExEzARBgNVBAcTClNjb3R0c2RhbGUxGjAY 13 | BgNVBAoTEUdvRGFkZHkuY29tLCBJbmMuMTMwMQYDVQQLEypodHRwOi8vY2VydGlm 14 | aWNhdGVzLmdvZGFkZHkuY29tL3JlcG9zaXRvcnkxMDAuBgNVBAMTJ0dvIERhZGR5 15 | IFNlY3VyZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTERMA8GA1UEBRMIMDc5Njky 16 | ODcwHhcNMTEwNzA4MDAxOTU3WhcNMTIwNzA4MDAxOTU3WjBPMRQwEgYDVQQKFAsq 17 | LnRyZXZwLm5ldDEhMB8GA1UECxMYRG9tYWluIENvbnRyb2wgVmFsaWRhdGVkMRQw 18 | EgYDVQQDFAsqLnRyZXZwLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 19 | ggEBAMgawQKi4zY4TTz1RNL7klt/ibvjG+jGqBYlc6qjUiTQORD3fUrdAF83Alav 20 | JiC3rrwfvarL8KpPn7zQQOOk+APwzFxn0sVphDvAN8E7xI/cC7es08EYA9/DDN7r 21 | VTe/wvbs77CL5AniRSJyAP5puvSUHgixingTgYmnkIgC+3ZFqyfz2uenxvkPkoUT 22 | QEBkm2uEcBOwBMXAih1fdsuhEiJ9qpmejpIEvxLIDoMnCWTPs897zhwr3epQkn5g 23 | lKQ9H+FnEo5Jf8YBM4YhAzwG/8pyfc8NtOHafKUb5PhSIC7Vy7N2EBQ4y9kDOZc+ 24 | r0Vguq4p+Nncc32JI/i1Cdj/lO0CAwEAAaOCAa4wggGqMA8GA1UdEwEB/wQFMAMB 25 | AQAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA4GA1UdDwEB/wQEAwIF 26 | oDAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3JsLmdvZGFkZHkuY29tL2dkczEt 27 | NTIuY3JsME0GA1UdIARGMEQwQgYLYIZIAYb9bQEHFwEwMzAxBggrBgEFBQcCARYl 28 | aHR0cHM6Ly9jZXJ0cy5nb2RhZGR5LmNvbS9yZXBvc2l0b3J5LzCBgAYIKwYBBQUH 29 | AQEEdDByMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5nb2RhZGR5LmNvbS8wSgYI 30 | KwYBBQUHMAKGPmh0dHA6Ly9jZXJ0aWZpY2F0ZXMuZ29kYWRkeS5jb20vcmVwb3Np 31 | dG9yeS9nZF9pbnRlcm1lZGlhdGUuY3J0MB8GA1UdIwQYMBaAFP2sYTKTbEXW4u6F 32 | X5q653aZaMznMCEGA1UdEQQaMBiCCyoudHJldnAubmV0ggl0cmV2cC5uZXQwHQYD 33 | VR0OBBYEFCYv4a9+enZGS27wqAv+TPfJOOb7MA0GCSqGSIb3DQEBBQUAA4IBAQA+ 34 | 2OKO77vpwKtoKddDtamBokiVhHrfw0c7ALGysOXtss1CKV2WgH4FdNuh9pFkVZB2 35 | mKZ7keS7EMW11OzgBR3pRRk0AkNYtDsOJEXA2+1NLFgrtdujHrDX4WIoi9MGbqB5 36 | TfK08XufM7OP3yXDLtMxyUtyjprFhdxPE+9p/GJ0IVdZrMmzYTjyCOO8+okY9zAQ 37 | RVUKuxd+eEaH3BpPAau4MP2n24gy6WEsJ2auB81ee9fDnx/tfKPqvyuc4r4/Z4aL 38 | 5CvQvlPHaG/TTXXNh3pZFl3d/J5/76ZfeQzQtZ+dCrE4a4601Q4hBBXEq5gQfaof 39 | H4yTGzfDv+JLIICAIcCs 40 | -----END CERTIFICATE-----""" 41 | sslc = TlsCertificate() 42 | sslc.parsePem(s) 43 | assert(sslc.key_sha256 == a2b_hex("ffd30bcb84dbbc211a510875694354c58863d84fb7fc5853dfe36f4be2eb2e50")) 44 | assert(sslc.cert_sha256 == a2b_hex("1a50e3de3a153f33b314b67c1aacc2f59fc99c49b8449c33dcc3665663e2bff1")) 45 | assert(Time.posixTimeToStr(sslc.notAfter, True) == "2012-07-08T00:19:57Z") 46 | assert(isinstance(sslc.writeText(), str)) 47 | return 1 48 | 49 | if __name__ == '__main__': 50 | unittest.main() -------------------------------------------------------------------------------- /tests/CompatTest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tack.compat import a2b_hex 3 | from tack.compat import a2b_base64 4 | from tack.compat import b2a_hex 5 | from tack.compat import b2a_base64 6 | from tack.compat import bytesToStrAscii 7 | 8 | class CompatTest(unittest.TestCase): 9 | 10 | def test_Compat(self): 11 | assert(isinstance(a2b_hex("00"), bytearray)) 12 | assert(a2b_hex("0102cdef") == bytearray([0x01,0x02,0xcd,0xef])) 13 | assert(a2b_hex("0102CDEF") == bytearray([0x01,0x02,0xcd,0xef])) 14 | try: 15 | assert(a2b_hex("c0102cdef") == bytearray([0x01,0x02,0xcd,0xef])) 16 | assert False 17 | except SyntaxError: 18 | pass 19 | try: 20 | assert(a2b_hex("xx0102cdef") == bytearray([0x01,0x02,0xcd,0xef])) 21 | assert False 22 | except SyntaxError: 23 | pass 24 | 25 | assert(isinstance(a2b_base64("0000"), bytearray)) 26 | assert(a2b_base64("Zm9vYg==") == bytearray(b"foob")) 27 | 28 | assert(b2a_hex(bytearray([0x01,0x02,0xcd,0xef])) == "0102cdef") 29 | assert(b2a_hex(bytearray([0x00])) == "00") 30 | assert(b2a_hex(bytearray([0xFF])) == "ff") 31 | 32 | assert(b2a_base64(bytearray(b"foob")) == "Zm9vYg==\n") 33 | assert(b2a_base64(bytearray(b"fooba")) == "Zm9vYmE=\n") 34 | assert(b2a_base64(bytearray(b"foobar")) == "Zm9vYmFy\n") 35 | 36 | assert(bytesToStrAscii(bytearray(b"abcd123")) == "abcd123") 37 | 38 | if __name__ == '__main__': 39 | unittest.main() -------------------------------------------------------------------------------- /tests/CryptoTest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tack.compat import a2b_hex 3 | from tack.crypto.AES import AES 4 | from tack.crypto.ASN1 import asn1Length, toAsn1IntBytes, fromAsn1IntBytes 5 | from tack.crypto.ECGenerator import ECGenerator 6 | 7 | class CryptoTest(unittest.TestCase): 8 | 9 | def test_AES(self): 10 | key = a2b_hex("c286696d887c9aa0611bbb3e2025a45a") 11 | IV = a2b_hex("562e17996d093d28ddb3ba695a2e6f58") 12 | plaintext = a2b_hex("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f") 13 | ciphertext = a2b_hex("d296cd94c2cccf8a3a863028b5e1dc0a7586602d253cfff91b8266bea6d61ab1") 14 | 15 | assert(AES(key, IV).encrypt(plaintext) == ciphertext) 16 | assert(AES(key, IV).decrypt(ciphertext) == plaintext) 17 | 18 | def test_ECDSA(self): 19 | publicKey, privateKey = ECGenerator().generateECKeyPair() 20 | data = bytearray([0,1,2,3]) 21 | badData = bytearray([0,1,2,4]) 22 | 23 | signature = privateKey.getSignature(data) 24 | assert(publicKey.verify(data, signature)) 25 | assert(not publicKey.verify(badData, signature)) 26 | 27 | def test_ASN1(self): 28 | assert(asn1Length(7) == bytearray([7])) 29 | assert(asn1Length(0x7F) == bytearray([0x7F])) 30 | assert(asn1Length(0x80) == bytearray([0x81,0x80])) 31 | assert(asn1Length(0x81) == bytearray([0x81,0x81])) 32 | assert(asn1Length(0xFF) == bytearray([0x81,0xFF])) 33 | assert(asn1Length(0x0100) == bytearray([0x82,0x01,0x00])) 34 | assert(asn1Length(0x0101) == bytearray([0x82,0x01,0x01])) 35 | assert(asn1Length(0xFFFF) == bytearray([0x82,0xFF,0xFF])) 36 | 37 | assert(toAsn1IntBytes(bytearray([0xFF])) == bytearray([0x00,0xFF])) 38 | assert(toAsn1IntBytes(bytearray([0x7F])) == bytearray([0x7F])) 39 | assert(toAsn1IntBytes(bytearray([0x00])) == bytearray([0x00])) 40 | assert(toAsn1IntBytes(bytearray([0x00,0x00])) == bytearray([0x00])) 41 | assert(toAsn1IntBytes(bytearray([0x00,0x01])) == bytearray([0x01])) 42 | assert(toAsn1IntBytes(bytearray([0,0xFF])) == bytearray([0,0xFF])) 43 | assert(toAsn1IntBytes(bytearray([0,0,0,0xFF])) == bytearray([0,0xFF])) 44 | assert(toAsn1IntBytes(bytearray([0,0,0,1,1])) == bytearray([1,1])) 45 | 46 | assert(bytearray([0xFF]) == fromAsn1IntBytes(bytearray([0x00,0xFF]),1)) 47 | assert(bytearray([0x7F]) == fromAsn1IntBytes(bytearray([0x7F]),1)) 48 | assert(bytearray([0x00]) == fromAsn1IntBytes(bytearray([0x00]),1)) 49 | assert(bytearray([0x00,0x00]) == fromAsn1IntBytes(bytearray([0x00]),2)) 50 | assert(bytearray([0x00,0x01]) == fromAsn1IntBytes(bytearray([0x01]),2)) 51 | assert(bytearray([0,0xFF]) == fromAsn1IntBytes(bytearray([0,0xFF]),2)) 52 | assert(bytearray([0,0,0,0xFF]) == fromAsn1IntBytes(bytearray([0,0xFF]),4)) 53 | assert(bytearray([0,0,0,1,1]) == fromAsn1IntBytes(bytearray([1,1]),5)) 54 | 55 | if __name__ == '__main__': 56 | unittest.main() -------------------------------------------------------------------------------- /tests/StructuresTest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tack.compat import a2b_hex 3 | from tack.structures.Tack import Tack 4 | from tack.structures.TackBreakSig import TackBreakSig 5 | from tack.structures.TackKeyFile import TackKeyFile 6 | from tack.util.Time import Time 7 | 8 | class StructuresTest(unittest.TestCase): 9 | 10 | def test_Tack(self): 11 | s = """ 12 | -----BEGIN TACK----- 13 | TAmsAZIpzR+MYwQrsujLhesvpu3dRc5ROhfgySqUVkU1p1hdXo+PwQrmaQo9B9+o 14 | hecRrWElh3yThwgYQRgbSwAAAY0cQDHeDLGfKtuw0c17GzHvjuPrWbdEWa75S0gL 15 | 7u64XGTJQUtzAwXIWOkQEQ0BRUlbzcGEa9a1PBhjmmWFNF+kGAswhLnXc5qL4y/Z 16 | PDUV0rzIIYjXP58T5pphGKRgLlK3Aw== 17 | -----END TACK-----""" 18 | 19 | t = Tack().createFromPem(s) 20 | 21 | assert(t.public_key.getRawKey() == a2b_hex("4c09ac019229cd1f8c63042bb2e8" 22 | "cb85eb2fa6eddd45ce513a17e0c9" 23 | "2a94564535a7585d5e8f8fc10ae6" 24 | "690a3d07dfa885e711ad6125877c" 25 | "9387081841181b4b")) 26 | assert(Time.posixTimeToStr(t.expiration*60) == "2019-06-25T22:24Z") 27 | assert(t.generation == 0) 28 | assert(t.target_hash == a2b_hex("31de0cb19f2adbb0d1cd7b1b31ef8ee3eb59b74459aef94b480beeeeb85c64c9")) 29 | assert(t.signature == a2b_hex("414b730305c858e910110d0145495" 30 | "bcdc1846bd6b53c18639a6585345f" 31 | "a4180b3084b9d7739a8be32fd93c3" 32 | "515d2bcc82188d73f9f13e69a6118" 33 | "a4602e52b703")) 34 | 35 | def test_BreakSig(self): 36 | s = """ 37 | -----BEGIN TACK BREAK SIG----- 38 | TAmsAZIpzR+MYwQrsujLhesvpu3dRc5ROhfgySqUVkU1p1hdXo+PwQrmaQo9B9+o 39 | hecRrWElh3yThwgYQRgbS0HynTQCmrY48oJsQtarSMoxnRNYHaaYOXwu9+4ur8mX 40 | wjKhIA9fXWNxuP73ZoicU+qC4bZjMN+WKuy7k8bSQZY= 41 | -----END TACK BREAK SIG-----""" 42 | 43 | tbs = TackBreakSig.createFromPem(s) 44 | assert(tbs.getTackId() == "nkufh.czttd.5cmlw.7cxtv.k6srn") 45 | assert(tbs.signature == a2b_hex("41f29d34029ab638f2826c42d6a" 46 | "b48ca319d13581da698397c2ef7" 47 | "ee2eafc997c232a1200f5f5d637" 48 | "1b8fef766889c53ea82e1b66330" 49 | "df962aecbb93c6d24196")) 50 | 51 | def test_BreakSigList(self): 52 | s = """ 53 | -----BEGIN TACK BREAK SIG----- 54 | TAmsAZIpzR+MYwQrsujLhesvpu3dRc5ROhfgySqUVkU1p1hdXo+PwQrmaQo9B9+o 55 | hecRrWElh3yThwgYQRgbS0HynTQCmrY48oJsQtarSMoxnRNYHaaYOXwu9+4ur8mX 56 | wjKhIA9fXWNxuP73ZoicU+qC4bZjMN+WKuy7k8bSQZY= 57 | -----END TACK BREAK SIG----- 58 | Created by TACK.py 0.9.6 59 | Created at 2012-05-10T00:54:10Z 60 | -----BEGIN TACK BREAK SIG----- 61 | 73nkbxCcvFnrCIlcgtZx4iPevqxUFd9RFUNU18xfqzTCU8hV0jwYerdCwt8+VbkQ 62 | OvHEbbRHmGAX8yseGrYX1dNuoFfSN1fCLY08u/0NU+x8fmJ6tEewegVAHguw67eR 63 | PgegVlKuDULIASht9fvs6xTfxcFJDUgNaenZfcqAgAI= 64 | -----END TACK BREAK SIG----- 65 | """ 66 | tbsList = TackBreakSig.createFromPemList(s) 67 | assert(tbsList[0].getTackId() == "nkufh.czttd.5cmlw.7cxtv.k6srn") 68 | assert(tbsList[1].getTackId() == "6xwgu.ydz7m.7cki3.kizmd.pt2f2") 69 | assert(len(tbsList) == 2) 70 | return 1 71 | 72 | def test_KeyFile(self): 73 | s = """ 74 | -----BEGIN TACK PRIVATE KEY----- 75 | AQAAIAAjOxiOdpiMo5qWidXwBTqJHxW5X1zRDBOA4ldqqFuKOSh6JJdrbXk1WsMN 76 | X/gyaVuHMBhC/g/rjtu/EnmIHoUuT9348iXeeROaLVRPdNqwr+5KEfjtTY7uXA6Q 77 | mhRUn+XmDePKRucRHYkcQaFPnzglrQ120Dh6aXD4PbtJMWajJtzTMvtEo9pNZhoM 78 | QTNZNoM= 79 | -----END TACK PRIVATE KEY-----""" 80 | publicKey = a2b_hex("87301842fe0feb8edbbf1279881e852e" 81 | "4fddf8f225de79139a2d544f74dab0af" 82 | "ee4a11f8ed4d8eee5c0e909a14549fe5" 83 | "e60de3ca46e7111d891c41a14f9f3825") 84 | privateKey = a2b_hex("fc815de8b1de13a436e9cd69742cbf2c" 85 | "d4c1c9bb33e023401d9291cf2781b754") 86 | kf = TackKeyFile.createFromPem(s, "asdf") 87 | assert(kf.getPublicKey().getRawKey() == publicKey) 88 | assert(kf.getPrivateKey().getRawKey() == privateKey) 89 | kf2 = TackKeyFile.createFromPem(kf.serializeAsPem(), "asdf") 90 | assert(kf2.getPublicKey().getRawKey() == publicKey) 91 | assert(kf2.getPrivateKey().getRawKey() == privateKey) 92 | kf3 = TackKeyFile.createRandom("123") 93 | kf4 = TackKeyFile.createFromPem(kf3.serializeAsPem(), "123") 94 | assert(kf3.getPublicKey().getRawKey() == kf4.getPublicKey().getRawKey()) 95 | 96 | if __name__ == '__main__': 97 | unittest.main() -------------------------------------------------------------------------------- /tests/TimeTest.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | from tack.util.Time import Time 3 | 4 | class TimeTest(unittest.TestCase): 5 | 6 | def test_posix(self): 7 | assert(Time.posixTimeToStr(1234567890, True) == "2009-02-13T23:31:30Z") 8 | assert(Time.posixTimeToStr(1234567890) == "2009-02-13T23:31Z") 9 | 10 | def test_duration(self): 11 | assert(Time.durationToStr(0) == "0m") 12 | assert(Time.durationToStr(59) == "59m") 13 | assert(Time.durationToStr(60) == "1h") 14 | assert(Time.durationToStr(61) == "1h1m") 15 | assert(Time.durationToStr(1439) == "23h59m") 16 | assert(Time.durationToStr(1440) == "1d") 17 | assert(Time.durationToStr(1441) == "1d1m") 18 | assert(Time.durationToStr(1500) == "1d1h") 19 | assert(Time.durationToStr(1501) == "1d1h1m") 20 | assert(Time.durationToStr(1440*37+122) == "37d2h2m") 21 | 22 | assert(0 == Time.parseDurationArg("0m")) 23 | assert(59 == Time.parseDurationArg("59m")) 24 | assert(60 == Time.parseDurationArg("1h")) 25 | assert(61 == Time.parseDurationArg("1h1m")) 26 | assert(1439 == Time.parseDurationArg("23h59m")) 27 | assert(1440 == Time.parseDurationArg("1d")) 28 | assert(1441 == Time.parseDurationArg("1d1m")) 29 | assert(1500 == Time.parseDurationArg("1d1h")) 30 | assert(1501 == Time.parseDurationArg("1d1h1m")) 31 | assert(1440*37+122 == Time.parseDurationArg("37d2h2m")) 32 | 33 | def test_string(self): 34 | assert(Time.parseTimeArg("2012-07-20T05:40Z")*60 == 1342762800) 35 | assert(Time.parseTimeArg("2012-07-20T05Z")*60 == 1342760400) 36 | assert(Time.parseTimeArg("2012-07-20Z")*60 == 1342742400) 37 | assert(Time.parseTimeArg("2012-07Z")*60 == 1341100800) 38 | assert(Time.parseTimeArg("2012Z")*60 == 1325376000) 39 | 40 | if __name__ == '__main__': 41 | unittest.main() -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/moxie0/TACKpy/5cd8ee33b4a6eba4ae9fed3582682e2658e4fee2/tests/__init__.py --------------------------------------------------------------------------------