diff --git a/auth/bearer_test.go b/auth/bearer_test.go new file mode 100644 index 0000000000000000000000000000000000000000..0ded53c73ddcf04c6ea0379b6b7872286d6a01e0 --- /dev/null +++ b/auth/bearer_test.go @@ -0,0 +1,101 @@ +package auth + +import ( + "encoding/base64" + "strings" + "testing" + "time" + + "git.sr.ht/~sircmpwn/go-bare" + "github.com/stretchr/testify/assert" + "github.com/vaughan0/go-ini" + + "git.sr.ht/~sircmpwn/core-go/crypto" +) + +func init() { + config, err := ini.Load(strings.NewReader(` +[webhooks] +private-key=ebzsjPaN6E13ln/FeNWly1C92q6bVMVdOnDo1HPl5fc= + +[sr.ht] +network-key=tbuG-7Vh44vrDq1L_HKWkHnWrDOtJhEkPKPiauaLeuk=`)) + if err != nil { + panic(err) + } + crypto.InitCrypto(config) +} + +func TestEncode(t *testing.T) { + bt := &BearerToken{ + Version: TokenVersion, + Expires: ToTimestamp(time.Now().Add(30 * time.Minute)), + Grants: "", + ClientID: "", + Username: "jdoe", + } + token := bt.Encode() + bytes, err := base64.RawStdEncoding.DecodeString(token) + assert.Nil(t, err) + + mac := bytes[len(bytes)-32:] + payload := bytes[:len(bytes)-32] + assert.True(t, crypto.BearerVerify(payload, mac)) + + var bt2 BearerToken + err = bare.Unmarshal(payload, &bt2) + assert.Nil(t, err) + assert.Equal(t, bt.Version, bt2.Version) + assert.Equal(t, bt.Expires, bt2.Expires) + assert.Equal(t, bt.Grants, bt2.Grants) + assert.Equal(t, bt.ClientID, bt2.ClientID) + assert.Equal(t, bt.Username, bt2.Username) +} + +func TestDecode(t *testing.T) { + bt := &BearerToken{ + Version: TokenVersion, + Expires: ToTimestamp(time.Now().Add(30 * time.Minute)), + Grants: "", + ClientID: "", + Username: "jdoe", + } + token := bt.Encode() + bt2 := DecodeBearerToken(token) + assert.NotNil(t, bt2) + assert.Equal(t, bt.Version, bt2.Version) + assert.Equal(t, bt.Expires, bt2.Expires) + assert.Equal(t, bt.Grants, bt2.Grants) + assert.Equal(t, bt.ClientID, bt2.ClientID) + assert.Equal(t, bt.Username, bt2.Username) + + // Expired token: + bt = &BearerToken{ + Version: TokenVersion, + Expires: ToTimestamp(time.Now().Add(-30 * time.Minute)), + Grants: "", + ClientID: "", + Username: "jdoe", + } + token = bt.Encode() + bt2 = DecodeBearerToken(token) + assert.Nil(t, bt2) + + // Invalid MAC: + bt = &BearerToken{ + Version: TokenVersion, + Expires: ToTimestamp(time.Now().Add(30 * time.Minute)), + Grants: "", + ClientID: "", + Username: "jdoe", + } + plain, err := bare.Marshal(bt) + assert.Nil(t, err) + mac := crypto.BearerHMAC(plain) + bt.Username = "rdoe" + plain, err = bare.Marshal(bt) + assert.Nil(t, err) + token = base64.RawStdEncoding.EncodeToString(append(plain, mac...)) + bt2 = DecodeBearerToken(token) + assert.Nil(t, bt2) +} diff --git a/auth/middleware.go b/auth/middleware.go index b04d0a548bdbef21f94820a5c46f31b91033e2a3..038a00ea6e11decbdb36d2685d6bdd715bd2943c 100644 --- a/auth/middleware.go +++ b/auth/middleware.go @@ -75,7 +75,7 @@ // Only filled out if AuthMethod == AUTH_INTERNAL InternalAuth InternalAuth // Only filled out if AuthMethod == AUTH_OAUTH2 - OAuth2Token *OAuth2Token + BearerToken *BearerToken Access map[string]string } @@ -503,15 +503,15 @@ wg sync.WaitGroup ) wg.Add(2) - ot := DecodeToken(token) - if ot == nil { + bt := DecodeBearerToken(token) + if bt == nil { authError(w, `Invalid or expired OAuth 2.0 bearer token`, http.StatusForbidden) return } go func() { defer wg.Done() - err = LookupUser(r.Context(), ot.Username, &auth) + err = LookupUser(r.Context(), bt.Username, &auth) if err != nil { log.Printf("LookupUser: %e", err) atomic.AddInt32(&tempErr, 1) @@ -523,7 +523,7 @@ go func() { defer wg.Done() isRevoked, err := LookupTokenRevocation(r.Context(), - ot.Username, hash, ot.ClientID) + bt.Username, hash, bt.ClientID) if err != nil { log.Printf("LookupTokenRevocation: %e", err) atomic.AddInt32(&tempErr, 1) @@ -550,11 +550,11 @@ return } auth.AuthMethod = AUTH_OAUTH2 - auth.OAuth2Token = ot + auth.BearerToken = bt - if ot.Grants != "" { + if bt.Grants != "" { auth.Access = make(map[string]string) - for _, grant := range strings.Split(ot.Grants, " ") { + for _, grant := range strings.Split(bt.Grants, " ") { var ( service string scope string diff --git a/auth/token.go b/auth/bearer.go rename from auth/token.go rename to auth/bearer.go index 6076e020ec9c998d92893fe68837f33c56359287..8ea49df04b0a35f937ed0dd3d8c22e21eb27cb43 100644 --- a/auth/token.go +++ b/auth/bearer.go @@ -23,7 +23,7 @@ func ToTimestamp(t time.Time) Timestamp { return Timestamp(t.UTC().Unix()) } -type OAuth2Token struct { +type BearerToken struct { Version uint Expires Timestamp Grants string @@ -31,16 +31,16 @@ ClientID string Username string } -func (ot *OAuth2Token) Encode() string { - plain, err := bare.Marshal(ot) +func (bt *BearerToken) Encode() string { + plain, err := bare.Marshal(bt) if err != nil { panic(err) } - mac := crypto.HMAC(plain) + mac := crypto.BearerHMAC(plain) return base64.RawStdEncoding.EncodeToString(append(plain, mac...)) } -func DecodeToken(token string) *OAuth2Token { +func DecodeBearerToken(token string) *BearerToken { payload, err := base64.RawStdEncoding.DecodeString(token) if err != nil { log.Printf("Invalid bearer token: invalid base64 %e", err) @@ -53,25 +53,25 @@ } mac := payload[len(payload)-32:] payload = payload[:len(payload)-32] - if crypto.HMACVerify(payload, mac) == false { + if crypto.BearerVerify(payload, mac) == false { log.Printf("Invalid bearer token: HMAC verification failed (MAC: [%d]%s; payload: [%d]%s", len(mac), hex.EncodeToString(mac), len(payload), hex.EncodeToString(payload)) return nil } - var ot OAuth2Token - err = bare.Unmarshal(payload, &ot) + var bt BearerToken + err = bare.Unmarshal(payload, &bt) if err != nil { log.Printf("Invalid bearer token: BARE unmarshal failed: %e", err) return nil } - if ot.Version != TokenVersion { + if bt.Version != TokenVersion { log.Printf("Invalid bearer token: invalid token version") return nil } - if time.Now().UTC().After(ot.Expires.Time()) { + if time.Now().UTC().After(bt.Expires.Time()) { log.Printf("Invalid bearer token: token expired") return nil } - return &ot + return &bt } diff --git a/auth/token_test.go b/auth/token_test.go deleted file mode 100644 index e33ae096229bed177c6fea74f15258c0e01d0055..0000000000000000000000000000000000000000 --- a/auth/token_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package auth - -import ( - "encoding/base64" - "strings" - "testing" - "time" - - "git.sr.ht/~sircmpwn/go-bare" - "github.com/stretchr/testify/assert" - "github.com/vaughan0/go-ini" - - "git.sr.ht/~sircmpwn/core-go/crypto" -) - -func init() { - config, err := ini.Load(strings.NewReader(` -[webhooks] -private-key=ebzsjPaN6E13ln/FeNWly1C92q6bVMVdOnDo1HPl5fc= - -[sr.ht] -network-key=tbuG-7Vh44vrDq1L_HKWkHnWrDOtJhEkPKPiauaLeuk=`)) - if err != nil { - panic(err) - } - crypto.InitCrypto(config) -} - -func TestEncode(t *testing.T) { - ot := &OAuth2Token{ - Version: TokenVersion, - Expires: ToTimestamp(time.Now().Add(30 * time.Minute)), - Grants: "", - ClientID: "", - Username: "jdoe", - } - token := ot.Encode() - bytes, err := base64.RawStdEncoding.DecodeString(token) - assert.Nil(t, err) - - mac := bytes[len(bytes)-32:] - payload := bytes[:len(bytes)-32] - assert.True(t, crypto.HMACVerify(payload, mac)) - - var ot2 OAuth2Token - err = bare.Unmarshal(payload, &ot2) - assert.Nil(t, err) - assert.Equal(t, ot.Version, ot2.Version) - assert.Equal(t, ot.Expires, ot2.Expires) - assert.Equal(t, ot.Grants, ot2.Grants) - assert.Equal(t, ot.ClientID, ot2.ClientID) - assert.Equal(t, ot.Username, ot2.Username) -} - -func TestDecode(t *testing.T) { - ot := &OAuth2Token{ - Version: TokenVersion, - Expires: ToTimestamp(time.Now().Add(30 * time.Minute)), - Grants: "", - ClientID: "", - Username: "jdoe", - } - token := ot.Encode() - ot2 := DecodeToken(token) - assert.NotNil(t, ot2) - assert.Equal(t, ot.Version, ot2.Version) - assert.Equal(t, ot.Expires, ot2.Expires) - assert.Equal(t, ot.Grants, ot2.Grants) - assert.Equal(t, ot.ClientID, ot2.ClientID) - assert.Equal(t, ot.Username, ot2.Username) - - // Expired token: - ot = &OAuth2Token{ - Version: TokenVersion, - Expires: ToTimestamp(time.Now().Add(-30 * time.Minute)), - Grants: "", - ClientID: "", - Username: "jdoe", - } - token = ot.Encode() - ot2 = DecodeToken(token) - assert.Nil(t, ot2) - - // Invalid MAC: - ot = &OAuth2Token{ - Version: TokenVersion, - Expires: ToTimestamp(time.Now().Add(30 * time.Minute)), - Grants: "", - ClientID: "", - Username: "jdoe", - } - plain, err := bare.Marshal(ot) - assert.Nil(t, err) - mac := crypto.HMAC(plain) - ot.Username = "rdoe" - plain, err = bare.Marshal(ot) - assert.Nil(t, err) - token = base64.RawStdEncoding.EncodeToString(append(plain, mac...)) - ot2 = DecodeToken(token) - assert.Nil(t, ot2) -} diff --git a/cmd/token/main.go b/cmd/token/main.go index a5c7f472bea5a5a25e7747dcabc0164c367c3204..de31da45163db1c6326fabfc97278f7a17b31802 100644 --- a/cmd/token/main.go +++ b/cmd/token/main.go @@ -12,6 +12,6 @@ func main() { conf := config.LoadConfig(":1111") crypto.InitCrypto(conf) - tok := auth.DecodeToken(os.Args[1]) + tok := auth.DecodeBearerToken(os.Args[1]) fmt.Printf("%+v\n", tok) } diff --git a/crypto/crypto.go b/crypto/crypto.go index babfabd68eb55ca09543e27f96b1c120b5ef8b65..bd6dc78f9cc7d922e8fb383cc01d83ee217bad8f 100644 --- a/crypto/crypto.go +++ b/crypto/crypto.go @@ -16,10 +16,10 @@ "github.com/vaughan0/go-ini" ) var ( - privateKey ed25519.PrivateKey - publicKey ed25519.PublicKey - macKey []byte - fernetKey *fernet.Key + webhookSk ed25519.PrivateKey + webhookPk ed25519.PublicKey + bearerKey []byte + fernetKey *fernet.Key ) func InitCrypto(config ini.File) { @@ -31,8 +31,8 @@ seed, err := base64.StdEncoding.DecodeString(b64key) if err != nil { log.Fatalf("base64 decode webhooks private key: %v", err) } - privateKey = ed25519.NewKeyFromSeed(seed) - publicKey, _ = privateKey.Public().(ed25519.PublicKey) + webhookSk = ed25519.NewKeyFromSeed(seed) + webhookPk, _ = webhookSk.Public().(ed25519.PublicKey) b64fernet, ok := config.Get("sr.ht", "network-key") if !ok { @@ -42,17 +42,17 @@ fernetKey, err = fernet.DecodeKey(b64fernet) if err != nil { log.Fatalf("Load Fernet network encryption key: %v", err) } - mac := hmac.New(sha256.New, privateKey) + mac := hmac.New(sha256.New, webhookSk) mac.Write([]byte("sr.ht HMAC key")) - macKey = mac.Sum(nil) + bearerKey = mac.Sum(nil) } func Sign(payload []byte) []byte { - return ed25519.Sign(privateKey, payload) + return ed25519.Sign(webhookSk, payload) } func Verify(payload, signature []byte) bool { - return ed25519.Verify(publicKey, payload, signature) + return ed25519.Verify(webhookPk, payload, signature) } func Encrypt(payload []byte) []byte { @@ -75,14 +75,14 @@ } return fernet.VerifyAndDecrypt(payload, expiry, []*fernet.Key{fernetKey}) } -func HMAC(payload []byte) []byte { - mac := hmac.New(sha256.New, macKey) +func BearerHMAC(payload []byte) []byte { + mac := hmac.New(sha256.New, bearerKey) mac.Write(payload) return mac.Sum(nil) } -func HMACVerify(payload []byte, signature []byte) bool { - mac := hmac.New(sha256.New, macKey) +func BearerVerify(payload []byte, signature []byte) bool { + mac := hmac.New(sha256.New, bearerKey) mac.Write(payload) expected := mac.Sum(nil) return hmac.Equal(expected, signature) diff --git a/crypto/crypto_test.go b/crypto/crypto_test.go index 7cc93b5a970ff5c81a9d478dd66f948abd23c00a..120e762c16c5f5d8215a19be3d1a2387f9598b0d 100644 --- a/crypto/crypto_test.go +++ b/crypto/crypto_test.go @@ -79,13 +79,13 @@ dec = DecryptWithExpiration(enc, time.Duration(2)) assert.Nil(t, dec) } -func TestHMAC(t *testing.T) { +func TestBearerHMAC(t *testing.T) { payload := []byte("Hello, world!") - mac := HMAC(payload) + mac := BearerHMAC(payload) - valid := HMACVerify(payload, mac) + valid := BearerVerify(payload, mac) assert.True(t, valid) - valid = HMACVerify([]byte("Something else"), mac) + valid = BearerVerify([]byte("Something else"), mac) assert.False(t, valid) }