idnits 2.17.00 (12 Aug 2021) /tmp/idnits50740/draft-ietf-privacypass-auth-scheme-02.txt: Checking boilerplate required by RFC 5378 and the IETF Trust (see https://trustee.ietf.org/license-info): ---------------------------------------------------------------------------- No issues found here. Checking nits according to https://www.ietf.org/id-info/1id-guidelines.txt: ---------------------------------------------------------------------------- No issues found here. Checking nits according to https://www.ietf.org/id-info/checklist : ---------------------------------------------------------------------------- No issues found here. Miscellaneous warnings: ---------------------------------------------------------------------------- -- The document date (4 April 2022) is 40 days in the past. Is this intentional? Checking references for intended status: Proposed Standard ---------------------------------------------------------------------------- (See RFCs 3967 and 4897 for information about using normative references to lower-maturity documents in RFCs) -- Looks like a reference, but probably isn't: '32' on line 326 == Missing Reference: 'Nid' is mentioned on line 327, but not defined == Missing Reference: 'Nk' is mentioned on line 328, but not defined == Outdated reference: A later version (-10) exists of draft-ietf-httpbis-rfc6265bis-09 Summary: 0 errors (**), 0 flaws (~~), 3 warnings (==), 2 comments (--). Run idnits with the --verbose option for more detailed information about the items above. -------------------------------------------------------------------------------- 2 Network Working Group T. Pauly 3 Internet-Draft Apple Inc. 4 Intended status: Standards Track S. Valdez 5 Expires: 6 October 2022 Google LLC 6 C. A. Wood 7 Cloudflare 8 4 April 2022 10 The Privacy Pass HTTP Authentication Scheme 11 draft-ietf-privacypass-auth-scheme-02 13 Abstract 15 This document defines an HTTP authentication scheme that can be used 16 by clients to redeem Privacy Pass tokens with an origin. It can also 17 be used by origins to challenge clients to present an acceptable 18 Privacy Pass token. 20 Status of This Memo 22 This Internet-Draft is submitted in full conformance with the 23 provisions of BCP 78 and BCP 79. 25 Internet-Drafts are working documents of the Internet Engineering 26 Task Force (IETF). Note that other groups may also distribute 27 working documents as Internet-Drafts. The list of current Internet- 28 Drafts is at https://datatracker.ietf.org/drafts/current/. 30 Internet-Drafts are draft documents valid for a maximum of six months 31 and may be updated, replaced, or obsoleted by other documents at any 32 time. It is inappropriate to use Internet-Drafts as reference 33 material or to cite them other than as "work in progress." 35 This Internet-Draft will expire on 6 October 2022. 37 Copyright Notice 39 Copyright (c) 2022 IETF Trust and the persons identified as the 40 document authors. All rights reserved. 42 This document is subject to BCP 78 and the IETF Trust's Legal 43 Provisions Relating to IETF Documents (https://trustee.ietf.org/ 44 license-info) in effect on the date of publication of this document. 45 Please review these documents carefully, as they describe your rights 46 and restrictions with respect to this document. Code Components 47 extracted from this document must include Revised BSD License text as 48 described in Section 4.e of the Trust Legal Provisions and are 49 provided without warranty as described in the Revised BSD License. 51 Table of Contents 53 1. Introduction . . . . . . . . . . . . . . . . . . . . . . . . 2 54 1.1. Terminology . . . . . . . . . . . . . . . . . . . . . . . 3 55 2. HTTP Authentication Scheme . . . . . . . . . . . . . . . . . 4 56 2.1. Token Challenge . . . . . . . . . . . . . . . . . . . . . 4 57 2.1.1. Redemption Context Construction . . . . . . . . . . . 6 58 2.1.2. Token Caching . . . . . . . . . . . . . . . . . . . . 7 59 2.2. Token Redemption . . . . . . . . . . . . . . . . . . . . 7 60 3. Issuance Protocol Requirements . . . . . . . . . . . . . . . 9 61 4. User Interaction . . . . . . . . . . . . . . . . . . . . . . 9 62 5. Security Considerations . . . . . . . . . . . . . . . . . . . 10 63 6. IANA Considerations . . . . . . . . . . . . . . . . . . . . . 11 64 6.1. Authentication Scheme . . . . . . . . . . . . . . . . . . 11 65 6.2. Token Type Registry . . . . . . . . . . . . . . . . . . . 12 66 7. References . . . . . . . . . . . . . . . . . . . . . . . . . 12 67 7.1. Normative References . . . . . . . . . . . . . . . . . . 12 68 7.2. Informative References . . . . . . . . . . . . . . . . . 13 69 Authors' Addresses . . . . . . . . . . . . . . . . . . . . . . . 13 71 1. Introduction 73 Privacy Pass tokens are unlinkable authenticators that can be used to 74 anonymously authorize a client (see 75 [I-D.ietf-privacypass-architecture]). A client possessing such a 76 token is able to prove that it was able to get a token issued by a 77 token issuer -- based on some check from a token issuer, such as 78 authentication or solving a CAPTCHA -- without allowing the relying 79 party redeeming the client's token (the origin) to link it with 80 issuance flow. 82 Different types of authenticators, using different token issuance 83 protocols, can be used as Privacy Pass tokens. 85 This document defines a common HTTP authentication scheme 86 ([RFC7235]), PrivateToken, that allows clients to redeem various 87 kinds of Privacy Pass tokens. 89 Clients and relying parties interact using this scheme to perform the 90 token challenge and token redemption flow. Clients use a token 91 issuance protocol to actually fetch tokens to redeem. 93 Client Relying Party (Origin) 95 <------------------------------ Challenge \ 96 | 97 +----------------------------------\ | 98 | | | 99 | Issuance Protocol | | 100 | | | 101 +----------------------------------/ | 102 | 103 Redemption -------------------------- > / 105 Figure 1: Token Architectural Components 107 In addition to working with different token issuance protocols, this 108 scheme supports optionally associating tokens with origin-chosen 109 contexts and specific origin names. Relying parties that request and 110 redeem tokens can choose a specific kind of token, as appropriate for 111 its use case. These options allow for different deployment models to 112 prevent double-spending, and allow for both interactive (online 113 challenges) and non-interactive (pre-fetched) tokens. 115 1.1. Terminology 117 The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", 118 "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and 119 "OPTIONAL" in this document are to be interpreted as described in 120 BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all 121 capitals, as shown here. 123 Unless otherwise specified, this document encodes protocol messages 124 in TLS notation from [TLS13], Section 3. 126 This document uses the terms "Client", "Origin", "Issuer", "Issuance 127 Protocol", and "Token" as defined in 128 [I-D.ietf-privacypass-architecture]. It additionally uses the 129 following terms in more specific ways: 131 * Issuer key: Keying material that can be used with an issuance 132 protocol to create a signed token. 134 * Token challenge: A requirement for tokens sent from an origin to a 135 client, using the "WWW-Authenticate" HTTP header. This challenge 136 is bound to a specific token issuer and issuance protocol, and may 137 be additionally bound to a specific context or origin name. 139 * Token redemption: An action by which a client presents a token to 140 an origin, using the "Authorization" HTTP header. 142 2. HTTP Authentication Scheme 144 Token redemption is performed using HTTP Authentication ([RFC7235]), 145 with the scheme "PrivateToken". Origins challenge clients to present 146 a token from a specific issuer (Section 2.1). Once a client has 147 received a token from that issuer, or already has a valid token 148 available, it presents the token to the origin (Section 2.2). 150 2.1. Token Challenge 152 Origins send a token challenge to clients in an "WWW-Authenticate" 153 header with the "PrivateToken" scheme. This challenge includes a 154 TokenChallenge message, along with information about what keys to use 155 when requesting a token from the issuer. 157 Origins that support this authentication scheme need to handle the 158 following tasks: 160 1. Select which issuer to use, and configure the issuer name and 161 token-key to include in WWW-Authenticate challenges. 163 2. Determine a redemption context construction to include in the 164 TokenChallenge, as discussed in Section 2.1.1. 166 3. Select the origin information to include in the TokenChallenge. 167 This can be empty to allow fully cross-origin tokens, a single 168 origin name that matches the origin itself, or a list of origin 169 names containing the origin. 171 The TokenChallenge message has the following structure: 173 struct { 174 uint16_t token_type; 175 opaque issuer_name<1..2^16-1>; 176 opaque redemption_context<0..32>; 177 opaque origin_info<0..2^16-1>; 178 } TokenChallenge; 180 The structure fields are defined as follows: 182 * "token_type" is a 2-octet integer, in network byte order. This 183 type indicates the issuance protocol used to generate the token. 184 Values are registered in an IANA registry, Section 6.2. 185 Challenges with unsupported token_type values MUST be ignored. 187 * "issuer_name" is a string containing the name of the issuer. This 188 is a hostname that is used to identify the issuer that is allowed 189 to issue tokens that can be redeemed by this origin. The string 190 is prefixed with a 2-octet integer indicating the length, in 191 network byte order. 193 * "redemption_context" is an optional field. If present, it allows 194 the origin to require that clients fetch tokens bound to a 195 specific context, as opposed to reusing tokens that were fetched 196 for other contexts. See Section 2.1.1 for example contexts that 197 might be useful in practice. When present, this value is a 198 32-byte context generated by the origin. Valid lengths for this 199 field are either 0 or 32 bytes. The field is prefixed with a 200 single octet indicating the length. Challenges with 201 redemption_context values of invalid lengths MUST be ignored. 203 * "origin_info" is an optional string containing one or more origin 204 names, which allows a token to be scoped to a specific set of 205 origins. The string is prefixed with a 2-octet integer indicating 206 the length, in network byte order. If empty, any non-origin- 207 specific token can be redeemed. If the string contains multiple 208 origin names, they are delimited with commas "," without any 209 whitespace. 211 When used in an authentication challenge, the "PrivateToken" scheme 212 uses the following attributes: 214 * "challenge", which contains a base64url-encoded [RFC4648] 215 TokenChallenge value. Since the length of the challenge is not 216 fixed, the base64url data MUST include padding. This MUST be 217 unique for every 401 HTTP response to prevent replay attacks. 218 This attribute is required for all challenges. 220 * "token-key", which contains a base64url encoding of the public key 221 for use with the issuance protocol indicated by the challenge. 222 Since the length of the key is not fixed, the base64url data MUST 223 include padding. This attribute MAY be omitted in deployments 224 where clients are able to retrieve the issuer key using an out-of- 225 band mechanism. 227 * "max-age", an optional attribute that consists of the number of 228 seconds for which the challenge will be accepted by the origin. 230 Clients can ignore the challenge if the token-key is invalid or 231 otherwise untrusted. 233 The header MAY also include the standard "realm" attribute, if 234 desired. Issuance protocols MAY require other attributes. 236 As an example, the WWW-Authenticate header could look like this: 238 WWW-Authenticate: PrivateToken challenge=abc..., token-key=123... 240 Upon receipt of this challenge, a client uses the message and keys in 241 the issuance protocol indicated by the token_type. If the 242 TokenChallenge has a token_type the client does not recognize or 243 support, it MUST NOT parse or respond to the challenge. If the 244 TokenChallenge contains a non-empty origin_info field, the client 245 MUST validate that the name of the origin that issued the 246 authentication challenge is included in the list of origin names. 247 Clients MAY have further restrictions and requirements around 248 validating when a challenge is considered acceptable or valid. For 249 example, clients can choose to reject challenges that list origin 250 names for which current connection is not authoritative (according to 251 the TLS certificate). 253 Caching and pre-fetching of tokens is discussed in Section 2.1.2. 255 Note that it is possible for the WWW-Authenticate header to include 256 multiple challenges. This allows the origin to indicate support for 257 different token types, issuers, or to include multiple redemption 258 contexts. For example, the WWW-Authenticate header could look like 259 this: 261 WWW-Authenticate: PrivateToken challenge=abc..., token-key=123..., 262 PrivateToken challenge=def..., token-key=234... 264 2.1.1. Redemption Context Construction 266 The TokenChallenge redemption context allows the origin to determine 267 the context in which a given token can be redeemed. This value can 268 be a unique per-request nonce, constructed from 32 freshly generated 269 random bytes. It can also represent state or properties of the 270 client session. Some example properties and methods for constructing 271 the corresponding context are below. This list is not exhaustive. 273 * Context bound to a given time window: Construct redemption context 274 as SHA256(current time window). 276 * Context bound to a client location: Construct redemption context 277 as SHA256(client IP address prefix). 279 * Context bound to a given time window and location: Construct 280 redemption context as SHA256(current time window, client IP 281 address prefix). 283 An empty redemption context is not bound to any property of the 284 client session. Preventing double spending on tokens requires the 285 origin to keep state associated with the redemption context. The 286 size of this state varies based on the size of the redemption 287 context. For example, double spend state for unique, per-request 288 redemption contexts does only needs to exist within the scope of the 289 request connection or session. In contrast, double spend state for 290 empty redemption contexts must be stored and shared across all 291 requests until token-key expiration or rotation. 293 2.1.2. Token Caching 295 Clients can generate multiple tokens from a single TokenChallenge, 296 and cache them for future use. This improves privacy by separating 297 the time of token issuance from the time of token redemption, and 298 also allows clients to avoid any overhead of receiving new tokens via 299 the issuance protocol. 301 Cached tokens can only be redeemed when they match all of the fields 302 in the TokenChallenge: token_type, issuer_name, redemption_context, 303 and origin_info. Clients ought to store cached tokens based on all 304 of these fields, to avoid trying to redeem a token that does not 305 match. Note that each token has a unique client nonce, which is sent 306 in token redemption (Section 2.2). 308 If a client fetches a batch of multiple tokens for future use that 309 are bound to a specific redemption context (the redemption_context in 310 the TokenChallenge was not empty), clients SHOULD discard these 311 tokens upon flushing state such as HTTP cookies [COOKIES], or 312 changing networks. Using these tokens in a context that otherwise 313 would not be linkable to the original context could allow the origin 314 to recognize a client. 316 2.2. Token Redemption 318 The output of the issuance protocol is a token that corresponds to 319 the origin's challenge (see Section 2.1). A token is a structure 320 that begins with a two-octet field that indicates a token type, which 321 MUST match the token_type in the TokenChallenge structure. 323 struct { 324 uint16_t token_type; 325 uint8_t nonce[32]; 326 uint8_t challenge_digest[32]; 327 uint8_t token_key_id[Nid]; 328 uint8_t authenticator[Nk]; 329 } Token; 331 The structure fields are defined as follows: 333 * "token_type" is a 2-octet integer, in network byte order. This 334 value must match the value in the challenge (Section 2.1). 336 * "nonce" is a 32-octet message containing a client-generated random 337 nonce. 339 * "challenge_digest" is a 32-octet message containing the hash of 340 the original TokenChallenge, SHA256(TokenChallenge). 342 * "token_key_id" is an Nid-octet identifier for the the token 343 authentication key. The value of this field is defined by the 344 token_type and corresponding issuance protocol. 346 * "authenticator" is a Nk-octet authenticator that covers the 347 preceding fields in the token. The value of this field is defined 348 by the token_type and corresponding issuance protocol. 350 The authenticator value in the Token structure is computed over the 351 token_type, nonce, context, and token_key_id fields. 353 When used for client authorization, the "PrivateToken" authentication 354 scheme defines one parameter, "token", which contains the base64url- 355 encoded Token struct. Since the length of the Token struct is not 356 fixed, the base64url data MUST include padding. All unknown or 357 unsupported parameters to "PrivateToken" authentication credentials 358 MUST be ignored. 360 Clients present this Token structure to origins in a new HTTP request 361 using the Authorization header as follows: 363 Authorization: PrivateToken token=abc... 365 For token types that support public verifiability, origins verify the 366 token authenticator using the public key of the issuer, and validate 367 that the signed message matches the concatenation of the client nonce 368 and the hash of a valid TokenChallenge. For context-bound tokens, 369 origins store or reconstruct the contexts of previous TokenChallenge 370 structures in order to validate the token. A TokenChallenge MAY be 371 bound to a specific HTTP session with client, but origins can also 372 accept tokens for valid challenges in new sessions. Origins SHOULD 373 implement some form of double-spend prevention that prevents a token 374 with the same nonce from being redeemed twice. This prevents clients 375 from "replaying" tokens for previous challenges. For context-bound 376 tokens, this double-spend prevention can require no state or minimal 377 state, since the context can be used to verify token uniqueness. 379 If a client is unable to fetch a token, it MUST react to the 380 challenge as if it could not produce a valid Authorization response. 382 3. Issuance Protocol Requirements 384 Clients initiate the issuance protocol using a challenge, a randomly 385 generated nonce, and a public key for the issuer. The issuance 386 protocol itself can be any interactive protocol between client, 387 issuer, or other parties that produces a valid authenticator over the 388 client's input, subject to the following security requirements. 390 1. Unconditional input secrecy. The issuance protocol MUST NOT 391 reveal anything about the client's private input, including the 392 challenge and nonce. The issuance protocol can reveal the issuer 393 public key for the purposes of determining which private key to 394 use in producing the issuance protocol. A result of this 395 property is that the redemption flow is unlinkable from the 396 issuance flow. 398 2. One-more forgery security. The issuance protocol MUST NOT allow 399 malicious clients to forge tokens without interacting with the 400 issuer directly. 402 3. Concurrent security. The issuance protocol MUST be safe to run 403 concurrently with arbitrarily many clients. 405 4. User Interaction 407 When used in contexts like websites, origins that challenge clients 408 for tokens need to consider how to optimize their interaction model 409 to ensure a good user experience. 411 Tokens challenges can be performed without explicit user involvement, 412 depending on the issuance protocol. If tokens are scoped to a 413 specific origin, there is no need for per-challenge user interaction. 414 Note that the issuance protocol may separately involve user 415 interaction if the client needs to be newly validated. 417 If a client cannot use cached tokens to respond to a challenge 418 (either because it has run out of cached tokens or the associated 419 context is unique), the token issuance process can add user- 420 perceivable latency. Origins need not block useful work on token 421 authentication. Instead, token authentication can be used in similar 422 ways to CAPTCHA validation today, but without the need for user 423 interaction. If issuance is taking a long time, a website could show 424 an indicator that it is waiting, or fall back to another method of 425 user validation. 427 An origin MUST NOT use more than one redemption context value for a 428 given token type and issuer per client request. If an origin issues 429 a large number of challenges with unique contexts, such as more than 430 once for each request, this can indicate that the origin is either 431 not functioning correctly or is trying to attack or overload the 432 client or issuance server. In such cases, a client MUST ignore 433 redundant token challenges for the same request and SHOULD alert the 434 user if possible. 436 Origins MAY include multiple challenges, where each challenge refers 437 to a different issuer or a different token type, to allow clients to 438 choose a preferred issuer or type. 440 5. Security Considerations 442 The security properties of token challenges vary depending on whether 443 the challenge contains a redemption context or not, as well as 444 whether the challenge is per-origin or not. For example, cross- 445 origin tokens with empty contexts can be replayed from one party by 446 another, as shown below. 448 Client Attacker Origin 450 <----------- Challenge \ 451 | 452 <--------- Challenge | 453 | 454 Redemption ----> | 455 | 456 Redemption ----------> / 458 Figure 2: Token Architectural Components 460 Token challenges that include non-empty origin_info bind tokens to 461 one or more specific origins. As described in Section 2.1, clients 462 only accept such challenges from origin names listed in the 463 origin_info string. Even if multiple origins are listed, a token can 464 only be redeemed for an origin if the challenge has an exact match 465 for the origin_info. For example, if "a.example.com" issues a 466 challenge with an origin_info string of 467 "a.example.com,b.example.com", a client could redeem a token fetched 468 for this challenge if and only if "b.example.com" also included an 469 origin_info string of "a.example.com,b.example.com". On the other 470 hand, if "b.example.com" had an origin_info string of "b.example.com" 471 or "b.example.com,a.example.com" or 472 "a.example.com,b.example.com,c.example.com", the string would not 473 match and the client would need to use a different token. 475 Context-bound token challenges require clients to obtain matching 476 tokens when challenged, rather than presenting a token that was 477 obtained from a different context in the past. This can make it more 478 likely that issuance and redemption events will occur at 479 approximately the same time. For example, if a client is challenged 480 for a token with a unique context at time T1 and then subsequently 481 obtains a token at time T2, a colluding issuer and origin can link 482 this to the same client if T2 is unique to the client. This 483 linkability is less feasible as the number of issuance events at time 484 T2 increases. Depending on the "max-age" token challenge attribute, 485 clients MAY try to augment the time between getting challenged then 486 redeeming a token so as to make this sort of linkability more 487 difficult. For more discussion on correlation risks between token 488 issuance and redemption, see [I-D.ietf-privacypass-architecture]. 490 As discussed in Section 2.1, clients SHOULD discard any context-bound 491 tokens upon flushing cookies or changing networks, to prevent an 492 origin using the redemption context state as a cookie to recognize 493 clients. 495 Applications SHOULD constrain tokens to a single origin unless the 496 use case can accommodate such replay attacks. 498 All random values in the challenge and token MUST be generated using 499 a cryptographically secure source of randomness. 501 6. IANA Considerations 503 6.1. Authentication Scheme 505 This document registers the "PrivateToken" authentication scheme in 506 the "Hypertext Transfer Protocol (HTTP) Authentication Scheme 507 Registry" established by [RFC7235]. 509 Authentication Scheme Name: PrivateToken 511 Pointer to specification text: Section 2 of this document 513 6.2. Token Type Registry 515 The "Token Type" registry lists identifiers for issuance protocols 516 defined for use with the Privacy Pass token authentication scheme. 517 These identifiers are two-byte values, so the maximum possible value 518 is 0xFFFF = 65535. 520 Template: 522 * Value: The two-byte identifier for the algorithm 524 * Name: Name of the issuance protocol 526 * Publicly Verifiable: A Y/N value indicating if the output tokens 527 are publicly verifiable 529 * Public Metadata: A Y/N value indicating if the output tokens can 530 contain public metadata. 532 * Private Metadata: A Y/N value indicating if the output tokens can 533 contain private metadata. 535 * Nk: The length in bytes of an output authenticator 537 * Nid: The length of the token key identifier 539 * Reference: Where this algorithm is defined 541 The initial contents for this registry are defined in the table 542 below. 544 +======+============+============+========+========+==+===+=========+ 545 |Value | Name | Publicly |Public |Private |Nk|Nid|Reference| 546 | | | Verifiable |Metadata|Metadata| | | | 547 +======+============+============+========+========+==+===+=========+ 548 |0x0000| (reserved) | N/A |N/A |N/A |N/|N/A|N/A | 549 | | | | | |A | | | 550 +------+------------+------------+--------+--------+--+---+---------+ 552 Table 1: Token Types 554 7. References 556 7.1. Normative References 558 [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate 559 Requirement Levels", BCP 14, RFC 2119, 560 DOI 10.17487/RFC2119, March 1997, 561 . 563 [RFC4648] Josefsson, S., "The Base16, Base32, and Base64 Data 564 Encodings", RFC 4648, DOI 10.17487/RFC4648, October 2006, 565 . 567 [RFC7235] Fielding, R., Ed. and J. Reschke, Ed., "Hypertext Transfer 568 Protocol (HTTP/1.1): Authentication", RFC 7235, 569 DOI 10.17487/RFC7235, June 2014, 570 . 572 [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 573 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, 574 May 2017, . 576 [TLS13] Rescorla, E., "The Transport Layer Security (TLS) Protocol 577 Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018, 578 . 580 7.2. Informative References 582 [COOKIES] Chen, L., Englehardt, S., West, M., and J. Wilander, 583 "Cookies: HTTP State Management Mechanism", Work in 584 Progress, Internet-Draft, draft-ietf-httpbis-rfc6265bis- 585 09, 19 October 2021, 586 . 589 [I-D.ietf-privacypass-architecture] 590 Davidson, A., Iyengar, J., and C. A. Wood, "Privacy Pass 591 Architectural Framework", Work in Progress, Internet- 592 Draft, draft-ietf-privacypass-architecture-03, 7 March 593 2022, . 596 Authors' Addresses 598 Tommy Pauly 599 Apple Inc. 600 One Apple Park Way 601 Cupertino, California 95014, 602 United States of America 603 Email: tpauly@apple.com 604 Steven Valdez 605 Google LLC 606 Email: svaldez@chromium.org 608 Christopher A. Wood 609 Cloudflare 610 Email: caw@heapingbits.net