pomerium/internal/cryptutil/sign.go
2019-02-18 18:48:14 -08:00

84 lines
3.1 KiB
Go

package cryptutil // import "github.com/pomerium/pomerium/internal/cryptutil"
import (
"fmt"
"time"
jose "gopkg.in/square/go-jose.v2"
"gopkg.in/square/go-jose.v2/jwt"
)
// JWTSigner implements JWT signing according to JSON Web Token (JWT) RFC7519
// https://tools.ietf.org/html/rfc7519
type JWTSigner interface {
SignJWT(string, string) (string, error)
}
// ES256Signer is struct containing the required fields to create a ES256 signed JSON Web Tokens
type ES256Signer struct {
// User (sub) is unique, stable identifier for the user.
// Use in place of the x-pomerium-authenticated-user-id header.
User string `json:"sub,omitempty"`
// Email (sub) is a **private** claim name identifier for the user email address.
// Use in place of the x-pomerium-authenticated-user-email header.
Email string `json:"email,omitempty"`
// Audience (aud) must be the destination of the upstream proxy locations.
// e.g. `helloworld.corp.example.com`
Audience jwt.Audience `json:"aud,omitempty"`
// Issuer (iss) is the URL of the proxy.
// e.g. `proxy.corp.example.com`
Issuer string `json:"iss,omitempty"`
// Expiry (exp) is the expiration time in seconds since the UNIX epoch.
// Allow 1 minute for skew. The maximum lifetime of a token is 10 minutes + 2 * skew.
Expiry jwt.NumericDate `json:"exp,omitempty"`
// IssuedAt (iat) is the time is measured in seconds since the UNIX epoch.
// Allow 1 minute for skew.
IssuedAt jwt.NumericDate `json:"iat,omitempty"`
// IssuedAt (nbf) is the time is measured in seconds since the UNIX epoch.
// Allow 1 minute for skew.
NotBefore jwt.NumericDate `json:"nbf,omitempty"`
signer jose.Signer
}
// NewES256Signer creates an Elliptic Curve, NIST P-256 (aka secp256r1 aka prime256v1) JWT signer.
//
// RSA is not supported due to performance considerations of needing to sign each request.
// Go's P-256 is constant-time and SHA-256 is faster on 64-bit machines and immune
// to length extension attacks.
// See also:
// - https://cloud.google.com/iot/docs/how-tos/credentials/keys
func NewES256Signer(privKey []byte, audience string) (*ES256Signer, error) {
key, err := DecodePrivateKey(privKey)
if err != nil {
return nil, fmt.Errorf("internal/cryptutil parsing key failed %v", err)
}
signer, err := jose.NewSigner(
jose.SigningKey{
Algorithm: jose.ES256, // ECDSA using P-256 and SHA-256
Key: key,
},
(&jose.SignerOptions{}).WithType("JWT"))
if err != nil {
return nil, fmt.Errorf("internal/cryptutil new signer failed %v", err)
}
return &ES256Signer{
Issuer: "pomerium-proxy",
Audience: jwt.Audience{audience},
signer: signer,
}, nil
}
// SignJWT creates a signed JWT containing claims for the logged in user id (`sub`) and email (`email`).
func (s *ES256Signer) SignJWT(user, email string) (string, error) {
s.User = user
s.Email = email
now := time.Now()
s.IssuedAt = jwt.NewNumericDate(now)
s.Expiry = jwt.NewNumericDate(now.Add(jwt.DefaultLeeway))
s.NotBefore = jwt.NewNumericDate(now.Add(-1 * jwt.DefaultLeeway))
rawJWT, err := jwt.Signed(s.signer).Claims(s).CompactSerialize()
if err != nil {
return "", err
}
return rawJWT, nil
}