100 lines
2.9 KiB
Go
100 lines
2.9 KiB
Go
// Copyright (c) 2026 Probo Inc <hello@probo.com>.
|
|
//
|
|
// Permission to use, copy, modify, and/or distribute this software for any
|
|
// purpose with or without fee is hereby granted, provided that the above
|
|
// copyright notice and this permission notice appear in all copies.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
// PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
package jose
|
|
|
|
import (
|
|
"crypto"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/big"
|
|
)
|
|
|
|
type (
|
|
// JWK represents a JSON Web Key (RFC 7517).
|
|
JWK struct {
|
|
KeyType string `json:"kty"`
|
|
Use string `json:"use"`
|
|
Algorithm string `json:"alg"`
|
|
KeyID string `json:"kid"`
|
|
N string `json:"n"`
|
|
E string `json:"e"`
|
|
}
|
|
|
|
// JWKS represents a JSON Web Key Set (RFC 7517).
|
|
JWKS struct {
|
|
Keys []JWK `json:"keys"`
|
|
}
|
|
|
|
// JWTHeader represents a JWT header (RFC 7519).
|
|
JWTHeader struct {
|
|
Algorithm string `json:"alg"`
|
|
Type string `json:"typ"`
|
|
KeyID string `json:"kid"`
|
|
}
|
|
)
|
|
|
|
// RSAPublicKeyToJWK converts an RSA public key to a JWK with the given
|
|
// key ID, marked for RS256 signature use.
|
|
func RSAPublicKeyToJWK(pub *rsa.PublicKey, kid string) JWK {
|
|
return JWK{
|
|
KeyType: "RSA",
|
|
Use: "sig",
|
|
Algorithm: "RS256",
|
|
KeyID: kid,
|
|
N: base64.RawURLEncoding.EncodeToString(pub.N.Bytes()),
|
|
E: base64.RawURLEncoding.EncodeToString(big.NewInt(int64(pub.E)).Bytes()),
|
|
}
|
|
}
|
|
|
|
// SignJWT signs arbitrary claims as a JWT using RS256 with the given RSA
|
|
// private key and key ID. The claims value is JSON-marshaled as the payload.
|
|
func SignJWT(privateKey *rsa.PrivateKey, kid string, claims any) (string, error) {
|
|
header := JWTHeader{
|
|
Algorithm: "RS256",
|
|
Type: "JWT",
|
|
KeyID: kid,
|
|
}
|
|
|
|
headerJSON, err := json.Marshal(header)
|
|
if err != nil {
|
|
return "", fmt.Errorf("cannot marshal jwt header: %w", err)
|
|
}
|
|
|
|
claimsJSON, err := json.Marshal(claims)
|
|
if err != nil {
|
|
return "", fmt.Errorf("cannot marshal jwt claims: %w", err)
|
|
}
|
|
|
|
headerB64 := base64.RawURLEncoding.EncodeToString(headerJSON)
|
|
claimsB64 := base64.RawURLEncoding.EncodeToString(claimsJSON)
|
|
|
|
signingInput := headerB64 + "." + claimsB64
|
|
|
|
h := sha256.Sum256([]byte(signingInput))
|
|
|
|
signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, h[:])
|
|
if err != nil {
|
|
return "", fmt.Errorf("cannot sign jwt: %w", err)
|
|
}
|
|
|
|
signatureB64 := base64.RawURLEncoding.EncodeToString(signature)
|
|
|
|
return signingInput + "." + signatureB64, nil
|
|
}
|