The Copenhagen Book

repository·main·Indexed 24 days ago

https://github.com/pilcrowonpaper/copenhagen

An open-source, community-maintained guide providing general guidelines for implementing authentication in web applications. It covers topics including ECDSA signature and public key formats, CSRF prevention via anti-CSRF tokens and signed double-submit cookies, email validation and verification processes, and the implementation of Time-based One-time Passwords (TOTP). Note: This project is archived; current guidelines are available at auth.pilcrowonpaper.com.

Tokens
12.1K
Snippets
25
Records
55
Agent score
85%

What's inside Copenhagen Book

  1. Understand the Open Redirect vulnerability

    main

    An Open Redirect vulnerability occurs when an application accepts a user-controlled URL (often via a query parameter like redirect_to) and redirects the user to that location without proper validation.

    Attackers can exploit this by crafting links that appear to belong to your trusted domain but actually redirect users to malicious sites (e.g., https://example.com/login?redirect_to=https%3A%2F%2Fscam.com). This is frequently used in phishing attacks to trick users into entering credentials on a fraudulent site that mimics the original.

  2. Handle email sub-addressing

    main

    Some providers (like Google) allow sub-addressing using the + symbol (e.g., user+foo@example.com).

    • You can block emails containing + to prevent users from creating multiple accounts with the same primary address.
    • Warning: Never silently remove the tag portion from user input, as an email address with + can be a valid, intentional address.
  3. ECDSA Public Key Formats: SEC1 vs PKIX

    main

    ECDSA public keys are represented as a pair of positive integers, (x, y). They can be encoded using the following standards:

    SEC1 Encoding

    SEC1 allows for two types of encoding:

    • Uncompressed: A leading 0x04 byte followed by the big-endian x and y coordinates. Format: 0x04 || x || y.
    • Compressed: A leading 0x02 byte (if x is even) or 0x03 byte (if x is odd), followed by the x coordinate. The y coordinate is derived from x and the curve. Formats: 0x02 || x or 0x03 || x.

    PKIX (RFC 5480) Encoding

    In PKIX, the public key is wrapped in a SubjectPublicKeyInfo ASN.1 sequence. This includes an AlgorithmIdentifier and the subjectPublicKey (which is the SEC1 encoded key as a BIT STRING).

    // SEC1 Uncompressed
    0x04 || x || y
    
    // SEC1 Compressed
    0x02 || x
    0x03 || x
    
    // PKIX
    SubjectPublicKeyInfo := SEQUENCE {
        algorithm           AlgorithmIdentifier,
        subjectPublicKey    BIT STRING
    }
    
    AlgorithmIdentifier := SEQUENCE {
        algorithm   OBJECT IDENTIFIER
        namedCurve  OBJECT IDENTIFIER
    }
  4. WebAuthn Vocabulary and Concepts

    main

    WebAuthn (Web Authentication) allows users to authenticate using device-bound credentials (PINs or biometrics).

    Key Terms

    • Relying Party (RP): Your application.
    • Authenticator: The device holding the credential (e.g., a phone or security key).
    • Challenge: A unique, single-use token (minimum 16 bytes entropy) generated by the server to prevent replay attacks.
    • User Presence: Confirmation that the user has physical access to the device.
    • User Verification: Confirmation that the user has verified their identity (e.g., via biometrics or PIN).
    • Resident Keys (Discoverable Credentials): Credentials stored directly on the authenticator. These allow for passwordless login.
    • Non-resident Keys: Credentials that are encrypted and stored on your application's server (database).
  5. Avoid using Web Storage API for session IDs

    main
    Storing session IDs in localStorage or sessionStorage (Web Storage API) makes them vulnerable to theft via XSS or supply chain attacks, as attackers can read the entire storage content directly. If you must use Web Storage, send the token via the Authorization header rather than relying on automatic browser behavior. Never include session tokens in URLs as query parameters or in form data.
  6. Use WebAuthn for MFA

    main

    WebAuthn allows applications to use user devices (like security keys or biometrics) for authentication via public key cryptography.

    Capabilities:

    • Verify identity using a device's PIN or biometrics.
    • Verify the device itself (more user-friendly as it avoids password/fingerprint prompts).

    Both methods serve as a second factor. Refer to the specific WebAuthn guide for implementation details.

  7. ECDSA Signature Formats: IEEE P1363 vs PKIX

    main

    ECDSA signatures consist of a pair of positive integers, (r, s). There are two primary ways to encode these values:

    1. IEEE P1363: The signature is the direct concatenation of r and s. Both are encoded as big-endian bytes with a size equal to the curve size (e.g., 32 bytes for P-256). Format: r || s

    2. PKIX (RFC 5480): The signature is an ASN.1 DER encoded sequence containing r and s.

    // IEEE P1363
    r || s;
    // PKIX
    SEQUENCE {
        r     INTEGER,
        s     INTEGER
    }
  8. Understanding and calculating biases

    main

    Randomness generation can introduce biases that make certain values more likely than others.

    Modulo Bias

    Occurs when using RANDOM_INT % MAX. If the range of the random source is not a multiple of MAX, some numbers will appear more frequently. Approximate bias formula: 1 / ( RANDOM_BITS - LOG2(MAX) ).

    Floating-point Bias

    Occurs when multiplying a maximum by a random float (e.g., FLOOR( RANDOM_FLOAT * 5 )). If the precision of the RANDOM_FLOAT is low relative to the multiplier, certain outcomes will have higher probabilities.

  9. Implement Sudo Mode for security-critical actions

    main
    Instead of forcing users to use short-lived sessions for everything, implement 'Sudo Mode'. This allows users to maintain long-lived sessions for general browsing, but requires them to re-authenticate (via password, WebAuthn, or TOTP) before accessing security-critical components. This mitigates the impact of session hijacking while maintaining a good user experience.
  10. Secure password storage and hashing

    main

    Passwords must be salted and hashed before storage.

    Salting and Peppering

    • Salting: Add a random value to each password before hashing. The salt must be generated using a cryptographically-secure random generator and should have at least 120 bits of entropy. Store the salt alongside the hash.
    • Peppering: Use a secret key (pepper) during hashing. Unlike salts, the pepper is stored in a separate location from the hashes.

    Comparison

    When comparing a provided password hash against a stored hash, always use constant-time comparison to prevent timing-based attacks. Do not use standard equality operators like ==.

    1. Argon2id (Default choice)
    2. Scrypt
    3. Bcrypt (Legacy systems only)
    import (
    	"crypto/subtle"
    	"golang.org/x/crypto/argon2"
    )
    
    var storedHash []byte
    var password []byte
    hash := argon2.IDKey(password, salt, 2, 19*1024, 1, 32)
    
    if (subtle.ConstantTimeCompare(hash, storedHash)) {
    	// Valid password.
    }
  11. Generate random strings using encoding

    main

    The safest way to generate random strings is to generate random bytes using crypto/rand and then encode them using standard schemes like base16 (hex), base32, or base64. This avoids the need for manual character selection and ensures high entropy.

    import (
    	"crypto/rand"
    	"encoding/base32"
    )
    
    func generateRandomString() string {
    	bytes := make([]byte, 12)
    	rand.Read(bytes)
    	return base32.StdEncoding.EncodeToString(bytes)
    }
  12. Verify the Origin header for request validation

    main

    A simple defense against CSRF is to check the Origin header for all non-GET requests. Since the Origin header cannot be spoofed via client-side JavaScript in a browser, it is a reliable way to ensure the request started from a trusted source.

    Implementation Details:

    • If the Origin header is missing, do not allow the request.
    • You can use the Referer header as a fallback if Origin is not defined.
    • Critical Requirement: If you rely on this method, your application must not use GET requests to modify resources.
    func handleRequest(w http.ResponseWriter, request *http.Request) {
      	if request.Method != "GET" {
    		originHeader := request.Header.Get("Origin")
    		// You can also compare it against the Host or X-Forwarded-Host header.
    		if originHeader != "https://example.com" {
    			// Invalid request origin
    			w.WriteHeader(403)
    			return
    		}
    	}
      	// ...
    }