Files
probo/pkg/crypto/jose/jose.go
Émile Ré 9156d6a16a Add wsl linter and fix
Signed-off-by: Émile Ré <emile@probo.com>
2026-05-20 09:27:28 +04:00

100 lines
2.9 KiB
Go

// Copyright (c) 2026 Probo Inc <hello@getprobo.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
}