Created
June 12, 2024 01:46
-
-
Save bnewbold/bc9b97c9b281295da1fa47c03b0b3c69 to your computer and use it in GitHub Desktop.
example of doing atproto JWT service auth validation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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