mirror of
https://github.com/hanzoai/base.git
synced 2026-10-21 03:41:50 +00:00
# Conflicts: # apis/record_auth_with_otp.go # core/base_backup.go # go.mod # go.sum # plugins/jsvm/internal/types/generated/types.d.ts # tools/router/rereadable_read_closer_test.go # ui/dist/assets/AuthMethodsDocs-C2aSYmNv.js # ui/dist/assets/AuthRefreshDocs-BapWJLRK.js # ui/dist/assets/AuthWithOAuth2Docs-Ck_pnbL8.js # ui/dist/assets/AuthWithOtpDocs-D8uWq_C5.js # ui/dist/assets/AuthWithPasswordDocs-BQyMhAeO.js # ui/dist/assets/BatchApiDocs-rvUuWLZE.js # ui/dist/assets/CodeEditor-DrMyw8z_.js # ui/dist/assets/CreateApiDocs-XWzcGuMB.js # ui/dist/assets/DeleteApiDocs-CScE0aw3.js # ui/dist/assets/EmailChangeDocs-CxOtrB4s.js # ui/dist/assets/FieldsQueryParam-BzNUQIb5.js # ui/dist/assets/FilterAutocompleteInput-Dm2G2coC.js # ui/dist/assets/Leaflet-DyumpnOc.js # ui/dist/assets/ListApiDocs-CdOzMZnr.js # ui/dist/assets/PageInstaller-vIXdffAJ.js # ui/dist/assets/PageOAuth2RedirectFailure-ClpyaW_B.js # ui/dist/assets/PageOAuth2RedirectSuccess-BLZml1eK.js # ui/dist/assets/PageRecordConfirmEmailChange-Cm3O2YD1.js # ui/dist/assets/PageRecordConfirmPasswordReset-D0XfuL45.js # ui/dist/assets/PageRecordConfirmVerification-CYyNYH_v.js # ui/dist/assets/PageSuperuserConfirmPasswordReset-BvlQ0_9t.js # ui/dist/assets/PageSuperuserRequestPasswordReset-gUPMclJZ.js # ui/dist/assets/PasswordResetDocs-DMF1vqE0.js # ui/dist/assets/RealtimeApiDocs-CNVij_C2.js # ui/dist/assets/UpdateApiDocs-CwQ3ZhFQ.js # ui/dist/assets/VerificationDocs-DUeWWjrS.js # ui/dist/assets/ViewApiDocs-gocWxbTE.js # ui/dist/assets/index-CWGqJiyp.js # ui/dist/index.html
315 lines
6.0 KiB
Go
315 lines
6.0 KiB
Go
package jwk_test
|
|
|
|
import (
|
|
"context"
|
|
"crypto"
|
|
"crypto/ed25519"
|
|
"crypto/rand"
|
|
"crypto/rsa"
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/big"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/golang-jwt/jwt/v5"
|
|
"github.com/hanzoai/base/tools/auth/internal/jwk"
|
|
)
|
|
|
|
type publicKey interface {
|
|
Equal(x crypto.PublicKey) bool
|
|
}
|
|
|
|
func TestJWK_PublicKey(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rsaPrivate, err := rsa.GenerateKey(rand.Reader, 1024)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
scenarios := []struct {
|
|
name string
|
|
key *jwk.JWK
|
|
expectError bool
|
|
expectKey crypto.PublicKey
|
|
}{
|
|
{
|
|
"empty",
|
|
&jwk.JWK{},
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"invalid kty",
|
|
&jwk.JWK{
|
|
Kty: "invalid",
|
|
Alg: "RS256",
|
|
E: base64.RawURLEncoding.EncodeToString(big.NewInt(int64(rsaPrivate.E)).Bytes()),
|
|
N: base64.RawURLEncoding.EncodeToString(rsaPrivate.N.Bytes()),
|
|
},
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"RSA",
|
|
&jwk.JWK{
|
|
Kty: "RSA",
|
|
Alg: "RS256",
|
|
E: base64.RawURLEncoding.EncodeToString(big.NewInt(int64(rsaPrivate.E)).Bytes()),
|
|
N: base64.RawURLEncoding.EncodeToString(rsaPrivate.N.Bytes()),
|
|
},
|
|
false,
|
|
&rsaPrivate.PublicKey,
|
|
},
|
|
{
|
|
"OKP with unsupported curve",
|
|
&jwk.JWK{
|
|
Kty: "OKP",
|
|
Crv: "invalid",
|
|
X: base64.RawURLEncoding.EncodeToString([]byte(strings.Repeat("a", ed25519.PublicKeySize))),
|
|
},
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"OKP with invalid public key length",
|
|
&jwk.JWK{
|
|
Kty: "OKP",
|
|
Crv: "Ed25519",
|
|
X: base64.RawURLEncoding.EncodeToString([]byte(strings.Repeat("a", ed25519.PublicKeySize-1))),
|
|
},
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"valid OKP",
|
|
&jwk.JWK{
|
|
Kty: "OKP",
|
|
Crv: "Ed25519",
|
|
X: base64.RawURLEncoding.EncodeToString([]byte(strings.Repeat("a", ed25519.PublicKeySize))),
|
|
},
|
|
false,
|
|
ed25519.PublicKey([]byte(strings.Repeat("a", ed25519.PublicKeySize))),
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
result, err := s.key.PublicKey()
|
|
|
|
hasErr := err != nil
|
|
if hasErr != s.expectError {
|
|
t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err)
|
|
}
|
|
|
|
if hasErr && result == nil {
|
|
return
|
|
}
|
|
|
|
k, ok := result.(publicKey)
|
|
if !ok {
|
|
t.Fatalf("The returned public key %T doesn't satisfy the expected common interface", k)
|
|
}
|
|
|
|
if !k.Equal(s.expectKey) {
|
|
t.Fatalf("The returned public key doesn't match with the expected one:\n%v\n%v", k, s.expectKey)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestFetch(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
if req.URL.Query().Has("error") {
|
|
res.WriteHeader(http.StatusBadRequest)
|
|
}
|
|
|
|
fmt.Fprintf(res, `{
|
|
"keys": [
|
|
{
|
|
"kid": "abc",
|
|
"kty": "OKP",
|
|
"crv": "Ed25519",
|
|
"x": "test_x"
|
|
},
|
|
{
|
|
"kid": "def",
|
|
"kty": "RSA",
|
|
"alg": "RS256",
|
|
"n": "test_n",
|
|
"e": "test_e"
|
|
}
|
|
]
|
|
}`)
|
|
}))
|
|
defer server.Close()
|
|
|
|
scenarios := []struct {
|
|
name string
|
|
kid string
|
|
expectError bool
|
|
contains []string
|
|
}{
|
|
{
|
|
"error response",
|
|
"def",
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"non-matching kid",
|
|
"missing",
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
"matching kid",
|
|
"def",
|
|
false,
|
|
[]string{
|
|
`"kid":"def"`,
|
|
`"kty":"RSA"`,
|
|
`"alg":"RS256"`,
|
|
`"n":"test_n"`,
|
|
`"e":"test_e"`,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
url := server.URL
|
|
if s.expectError {
|
|
url += "?error"
|
|
}
|
|
|
|
key, err := jwk.Fetch(context.Background(), url, s.kid)
|
|
|
|
hasErr := err != nil
|
|
if hasErr != s.expectError {
|
|
t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err)
|
|
}
|
|
|
|
raw, err := json.Marshal(key)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
rawStr := string(raw)
|
|
|
|
for _, substr := range s.contains {
|
|
if !strings.Contains(rawStr, substr) {
|
|
t.Fatalf("Missing expected substring\n%s\nin\n%s", substr, rawStr)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestValidateTokenSignature(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
rsaPrivate, err := rsa.GenerateKey(rand.Reader, 1024)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
ed25519Public, ed25519Private, err := ed25519.GenerateKey(rand.Reader)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
nonmatchingKidToken := jwt.New(&jwt.SigningMethodEd25519{})
|
|
nonmatchingKidToken.Header["kid"] = "missing"
|
|
nonmatchingKidTokenStr, err := nonmatchingKidToken.SignedString(ed25519Private)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
key1Token := jwt.New(&jwt.SigningMethodEd25519{})
|
|
key1Token.Header["kid"] = "key1"
|
|
key1TokenStr, err := key1Token.SignedString(ed25519Private)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
key2Token := jwt.New(jwt.SigningMethodRS256)
|
|
key2Token.Header["kid"] = "key2"
|
|
key2TokenStr, err := key2Token.SignedString(rsaPrivate)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
|
|
_ = json.NewEncoder(res).Encode(map[string]any{"keys": []*jwk.JWK{
|
|
{
|
|
Kid: "key1",
|
|
Kty: "OKP",
|
|
Alg: "EdDSA",
|
|
Crv: "Ed25519",
|
|
X: base64.RawURLEncoding.EncodeToString(ed25519Public),
|
|
},
|
|
{
|
|
Kid: "key2",
|
|
Kty: "RSA",
|
|
Alg: "RS256",
|
|
E: base64.RawURLEncoding.EncodeToString(big.NewInt(int64(rsaPrivate.E)).Bytes()),
|
|
N: base64.RawURLEncoding.EncodeToString(rsaPrivate.N.Bytes()),
|
|
},
|
|
}})
|
|
}))
|
|
defer server.Close()
|
|
|
|
scenarios := []struct {
|
|
name string
|
|
token string
|
|
expectError bool
|
|
}{
|
|
{
|
|
"empty token",
|
|
"",
|
|
true,
|
|
},
|
|
{
|
|
"invalid token",
|
|
"abc",
|
|
true,
|
|
},
|
|
{
|
|
"no matching kid",
|
|
nonmatchingKidTokenStr,
|
|
true,
|
|
},
|
|
{
|
|
"valid Ed25519 token",
|
|
key1TokenStr,
|
|
false,
|
|
},
|
|
{
|
|
"valid RSA token",
|
|
key2TokenStr,
|
|
false,
|
|
},
|
|
}
|
|
|
|
for _, s := range scenarios {
|
|
t.Run(s.name, func(t *testing.T) {
|
|
err := jwk.ValidateTokenSignature(
|
|
context.Background(),
|
|
s.token,
|
|
server.URL,
|
|
)
|
|
|
|
hasErr := err != nil
|
|
if hasErr != s.expectError {
|
|
t.Fatalf("Expected hasErr %v, got %v (%v)", s.expectError, hasErr, err)
|
|
}
|
|
})
|
|
}
|
|
}
|