Skip to content

Instantly share code, notes, and snippets.

@bnewbold
Created June 12, 2024 01:46
Show Gist options
  • Save bnewbold/bc9b97c9b281295da1fa47c03b0b3c69 to your computer and use it in GitHub Desktop.
Save bnewbold/bc9b97c9b281295da1fa47c03b0b3c69 to your computer and use it in GitHub Desktop.
example of doing atproto JWT service auth validation
package main
// copied from Jaz's https://github.com/ericvolp12/jwt-go-secp256k1
import (
"crypto"
"errors"
atcrypto "github.com/bluesky-social/indigo/atproto/crypto"
"github.com/golang-jwt/jwt/v5"
)
var (
SigningMethodES256K *SigningMethodAtproto
SigningMethodES256 *SigningMethodAtproto
)
// implementation of jwt.SigningMethod.
type SigningMethodAtproto struct {
alg string
hash crypto.Hash
toOutSig toOutSig
sigLen int
}
type toOutSig func(sig []byte) []byte
func init() {
SigningMethodES256K = &SigningMethodAtproto{
alg: "ES256K",
hash: crypto.SHA256,
toOutSig: toES256K,
sigLen: 64,
}
jwt.RegisterSigningMethod(SigningMethodES256K.Alg(), func() jwt.SigningMethod {
return SigningMethodES256K
})
SigningMethodES256 = &SigningMethodAtproto{
alg: "ES256",
hash: crypto.SHA256,
toOutSig: toES256,
sigLen: 64,
}
jwt.RegisterSigningMethod(SigningMethodES256.Alg(), func() jwt.SigningMethod {
return SigningMethodES256
})
}
// Errors returned on different problems.
var (
ErrWrongKeyFormat = errors.New("wrong key type")
ErrBadSignature = errors.New("bad signature")
ErrVerification = errors.New("signature verification failed")
ErrFailedSigning = errors.New("failed generating signature")
ErrHashUnavailable = errors.New("hasher unavailable")
)
func (sm *SigningMethodAtproto) Verify(signingString string, sig []byte, key interface{}) error {
pub, ok := key.(atcrypto.PublicKey)
if !ok {
return ErrWrongKeyFormat
}
if !sm.hash.Available() {
return ErrHashUnavailable
}
if len(sig) != sm.sigLen {
return ErrBadSignature
}
return pub.HashAndVerifyLenient([]byte(signingString), sig)
}
func (sm *SigningMethodAtproto) Sign(signingString string, key interface{}) ([]byte, error) {
// TODO: implement signatures
return nil, ErrFailedSigning
}
func (sm *SigningMethodAtproto) Alg() string {
return sm.alg
}
func toES256K(sig []byte) []byte {
return sig[:64]
}
func toES256(sig []byte) []byte {
return sig[:64]
}
package main
import (
"context"
"testing"
"time"
"github.com/bluesky-social/indigo/atproto/crypto"
"github.com/bluesky-social/indigo/atproto/syntax"
"github.com/golang-jwt/jwt/v5"
"github.com/hashicorp/golang-lru/v2/expirable"
"github.com/stretchr/testify/assert"
)
// Returns an early-2024 timestamp as a point in time for validating known JWTs (which contain expires-at)
func testTime() time.Time {
return time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
}
func TestValidateJWT(t *testing.T) {
assert := assert.New(t)
ctx := context.Background()
jwtTestVectors := []struct {
name string
pubkey string
iss string
aud string
jwt string
}{
{
name: "secp256k1 (K-256)",
pubkey: "did:key:zQ3shscXNYZQZSPwegiv7uQZZV5kzATLBRtgJhs7uRY7pfSk4",
iss: "did:example:iss",
aud: "did:example:aud",
jwt: "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJpc3MiOiJkaWQ6ZXhhbXBsZTppc3MiLCJhdWQiOiJkaWQ6ZXhhbXBsZTphdWQiLCJleHAiOjE3MTM1NzEwMTJ9.J_In_PQCMjygeeoIKyjybORD89ZnEy1bZTd--sdq_78qv3KCO9181ZAh-2Pl0qlXZjfUlxgIa6wiak2NtsT98g",
},
{
name: "secp256k1 (K-256)",
pubkey: "did:key:zQ3shqKrpHzQ5HDfhgcYMWaFcpBK3SS39wZLdTjA5GeakX8G5",
iss: "did:example:iss",
aud: "did:example:aud",
jwt: "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NksifQ.eyJhdWQiOiJkaWQ6ZXhhbXBsZTphdWQiLCJpc3MiOiJkaWQ6ZXhhbXBsZTppc3MiLCJleHAiOjE3MTM1NzExMzJ9.itNeYcF5oFMZIGxtnbJhE4McSniv_aR-Yk1Wj8uWk1K8YjlS2fzuJMo0-fILV3payETxn6r45f0FfpTaqY0EZQ",
},
{
name: "P-256",
pubkey: "did:key:zDnaeXRDKRCEUoYxi8ZJS2pDsgfxUh3pZiu3SES9nbY4DoART",
iss: "did:example:iss",
aud: "did:example:aud",
jwt: "eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiJ9.eyJpc3MiOiJkaWQ6ZXhhbXBsZTppc3MiLCJhdWQiOiJkaWQ6ZXhhbXBsZTphdWQiLCJleHAiOjE3MTM1NzE1NTR9.FFRLm7SGbDUp6cL0WoCs0L5oqNkjCXB963TqbgI-KxIjbiqMQATVCalcMJx17JGTjMmfVHJP6Op_V4Z0TTjqog",
},
}
kcache := expirable.NewLRU[syntax.DID, crypto.PublicKey](500, nil, 10*time.Minute)
srv := Server{
keyCache: kcache,
}
for _, fix := range jwtTestVectors {
pubk, err := crypto.ParsePublicDIDKey(fix.pubkey)
if err != nil {
t.Fatal(err)
}
srv.keyCache.Add(syntax.DID(fix.iss), pubk)
did, err := srv.checkJwtConfig(ctx, fix.jwt, jwt.WithTimeFunc(testTime))
assert.NoError(err)
assert.Equal(fix.iss, did)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment