o1egl/paseto Go Library

repository·master·Indexed 21 days ago

https://github.com/o1egl/paseto

A pure Go implementation of PASETO (Platform-Agnostic SEcurity TOkens) providing secure stateless tokens. It supports versioned protocols (V1 and V2) to avoid algorithm agility, offering 'local' mode for symmetric-key authenticated encryption and 'public' mode for asymmetric-key digital signatures. The library includes tools for managing JSONToken claims, temporal validation via ValidAt, and high-level functions for encrypting, decrypting, signing, and verifying tokens.

Tokens
7.6K
Snippets
32
Records
35
Agent score
75%

What's inside o1egl/paseto

  1. How PASETO works: local vs public modes

    master

    PASETO (Platform-Agnostic SEcurity TOkens) uses versioned protocols instead of algorithm agility to prevent insecure configurations. There are two primary purposes for tokens:

    • local: Uses shared-key authenticated encryption (symmetric-key, AEAD). Use this when both the issuer and the consumer share the same secret.
    • public: Uses public-key digital signatures (asymmetric-key). Use this when the issuer signs with a private key and the consumer verifies with a public key.

    Regardless of the purpose, the header and an optional footer (which is cleartext but base64url-encoded) are included in the signature or authentication tag.

  2. Create and decrypt tokens in local mode (symmetric key)

    master

    In local mode, you use a shared symmetric key to encrypt and decrypt data. The key must be exactly 32 bytes. You can use the paseto.JSONToken struct for the payload, which supports standard claims like Audience, Issuer, Subject, Expiration, etc., and allows custom claims via the .Set() method.

    To encrypt, use paseto.Encrypt. To decrypt, use paseto.Decrypt.

    symmetricKey := []byte("YELLOW SUBMARINE, BLACK WIZARDRY") // Must be 32 bytes
    now := time.Now()
    exp := now.Add(24 * time.Hour)
    nbt := now
    
    jsonToken := paseto.JSONToken{
            Audience:   "test",
            Issuer:     "test_service",
            Jti:        "123",
            Subject:    "test_subject",
            IssuedAt:   now,
            Expiration: exp,
            NotBefore:  nbt,
            }
    // Add custom claim
    jsonToken.Set("data", "this is a signed message")
    footer := "some footer"
    
    // Encrypt data
    token, err := paseto.Encrypt(symmetricKey, jsonToken, footer)
    
    // Decrypt data
    var newJsonToken paseto.JSONToken
    var newFooter string
    err := paseto.Decrypt(token, symmetricKey, &newJsonToken, &newFooter)
  3. Sign and verify tokens in public mode (asymmetric key)

    master

    In public mode, you use asymmetric keys (e.g., Ed25519). The issuer signs the token using a private key, and the consumer verifies it using the corresponding public key.

    To sign, use paseto.Sign. To verify, use paseto.Verify.

    b, _ := hex.DecodeString("b4cbfb43df4ce210727d953e4a713307fa19bb7d9f85041438d9e11b942a37741eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")
    privateKey := ed25519.PrivateKey(b)
    
    b, _ = hex.DecodeString("1eb9dbbbbc047c03fd70604e0071f0987e16b28b757225c11f00415d0e20b1a2")
    publicKey := ed25519.PublicKey(b)
    
    jsonToken := paseto.JSONToken{
            Expiration: time.Now().Add(24 * time.Hour),
            }
            
    // Add custom claim
    jsonToken.Set("data", "this is a signed message")
    footer := "some footer"
    
    // Sign data
    token, err := paseto.Sign(privateKey, jsonToken, footer)
    
    // Verify data
    var newJsonToken paseto.JSONToken
    var newFooter string
    err := paseto.Verify(token, publicKey, &newJsonToken, &newFooter)
  4. Parse all supported token versions with Parse()

    master

    The paseto.Parse function allows you to parse tokens regardless of their version (v1 or v2) by providing a map of public keys for each supported version.

    Note: Version 1 of the protocol is deprecated.

    To use Parse, you must provide:

    1. The token string.
    2. A pointer to a JSONToken to hold the payload.
    3. A pointer to a string to hold the footer.
    4. The symmetric key (if parsing a local token).
    5. A map of paseto.Version to crypto.PublicKey for asymmetric verification.
    // ... setup keys ...
    var payload JSONToken
    var footer string
    version, err := paseto.Parse(token, &payload, &footer, symmetricKey, map[paseto.Version]crypto.PublicKey{paseto.V1: v1PublicKey, paseto.V2: v2PublicKey})
  5. Sign a payload using asymmetric keys (Public Mode)

    master

    Use the Sign method to create a public PASETO token using Ed25519 digital signatures.

    • privateKey: Must be an ed25519.PrivateKey.
    • payload: The data to be signed.
    • footer: Optional metadata.

    Returns the signed token string or an error. If the provided key is not an ed25519.PrivateKey, it returns ErrIncorrectPrivateKeyType.

    // Example: Signing a payload
    // privateKey is an ed25519.PrivateKey
    payload := map[string]string{"role": "admin"}
    footer := map[string]string{"alg": "ed25519"}
    
    token, err := pasetoV2.Sign(privateKey, payload, footer)
    if err != nil {
        // handle error
    }
  6. Parse only the footer with ParseFooter()

    master

    Use ParseFooter(token string, footer interface{}) to extract only the footer data from a token. This is useful if you only need metadata stored in the footer and do not want to process the main payload.

    err := paseto.ParseFooter(token, &footer)
  7. Verify a token using asymmetric keys with Verify()

    master

    Use Verify(token string, publicKey crypto.PublicKey, value, footer interface{}) to verify a PASETO token created in 'public' mode. This function uses the V2 protocol by default. The publicKey should be an ed25519.PublicKey. The value argument is a pointer to the structure where the decoded payload will be stored.

    // publicKey should be an ed25519.PublicKey
    err := paseto.Verify(token, publicKey, &payload, &footer)
  8. Sign data using PASETO V1 (Public Mode)

    master

    Use the Sign method on a *V1 instance to create an asymmetric signed token. This requires an RSA private key.

    • privateKey: Must be of type *rsa.PrivateKey.
    • payload: The data to sign.
    • footer: Optional metadata.

    Returns the encoded token string or an error. If the provided key is not an *rsa.PrivateKey, it returns ErrIncorrectPrivateKeyType.

    import "crypto/rsa"
    
    // Example asymmetric signing
    // privateKey should be an *rsa.PrivateKey
    token, err := v1.Sign(privateKey, payload, footer)
    if err != nil {
        // handle error
    }
  9. Parse any PASETO token with Parse()

    master

    The Parse function is a high-level helper that automatically determines whether to decrypt (local) or verify (public) a token based on its content.

    • For local (symmetric) tokens: Provide the symmetricKey (32 bytes). Pass nil for publicKeys.
    • For public (asymmetric) tokens: Provide a map of publicKeys indexed by Version. Pass nil for symmetricKey.

    Returns the Version of the token and an error if parsing fails.

    // For local tokens
    version, err := paseto.Parse(token, &payload, &footer, symmetricKey, nil)
    
    // For public tokens
    publicKeys := map[paseto.Version]crypto.PublicKey{
        paseto.VersionV2: publicKeyV2,
    }
    version, err := paseto.Parse(token, &payload, &footer, nil, publicKeys)
  10. Verify a signature using PASETO V1 (Public Mode)

    master

    Use the Verify method on a *V1 instance to validate an asymmetric token created via Sign.

    • token: The encoded token string.
    • publicKey: Must be of type *rsa.PublicKey.
    • payload: A pointer to the variable where the payload should be stored if verification succeeds.
    • footer: A pointer to the variable where the footer should be stored if verification succeeds.

    Returns an error if verification fails (e.g., ErrInvalidSignature) or if the key type is incorrect (ErrIncorrectPublicKeyType).

    import "crypto/rsa"
    
    // Example asymmetric verification
    var decryptedPayload map[string]string
    var decryptedFooter map[string]string
    
    err := v1.Verify(token, publicKey, &decryptedPayload, &decryptedFooter)
    if err != nil {
        // handle error (e.g., ErrInvalidSignature)
    }