feat: Token Revocation, Request Object and OP Certification (#130)

FEATURES (and FIXES):
- support OAuth 2.0 Token Revocation [RFC 7009](https://datatracker.ietf.org/doc/html/rfc7009)
- handle request object using `request` parameter [OIDC Core 1.0 Request Object](https://openid.net/specs/openid-connect-core-1_0.html#RequestObject)
- handle response mode
- added some information to the discovery endpoint:
  - revocation_endpoint (added with token revocation) 
  - revocation_endpoint_auth_methods_supported (added with token revocation)
  - revocation_endpoint_auth_signing_alg_values_supported (added with token revocation)
  - token_endpoint_auth_signing_alg_values_supported (was missing)
  - introspection_endpoint_auth_signing_alg_values_supported (was missing)
  - request_object_signing_alg_values_supported (added with request object)
  - request_parameter_supported (added with request object)
 - fixed `removeUserinfoScopes ` now returns the scopes without "userinfo" scopes (profile, email, phone, addedd) [source diff](https://github.com/caos/oidc/pull/130/files#diff-fad50c8c0f065d4dbc49d6c6a38f09c992c8f5d651a479ba00e31b500543559eL170-R171)
- improved error handling (pkg/oidc/error.go) and fixed some wrong OAuth errors (e.g. `invalid_grant` instead of `invalid_request`)
- improved MarshalJSON and added MarshalJSONWithStatus
- removed deprecated PEM decryption from `BytesToPrivateKey`  [source diff](https://github.com/caos/oidc/pull/130/files#diff-fe246e428e399ccff599627c71764de51387b60b4df84c67de3febd0954e859bL11-L19)
- NewAccessTokenVerifier now uses correct (internal) `accessTokenVerifier` [source diff](https://github.com/caos/oidc/pull/130/files#diff-3a01c7500ead8f35448456ef231c7c22f8d291710936cac91de5edeef52ffc72L52-R52)

BREAKING CHANGE:
- move functions from `utils` package into separate packages
- added various methods to the (OP) `Configuration` interface [source diff](https://github.com/caos/oidc/pull/130/files#diff-2538e0dfc772fdc37f057aecd6fcc2943f516c24e8be794cce0e368a26d20a82R19-R32)
- added revocationEndpoint to `WithCustomEndpoints ` [source diff](https://github.com/caos/oidc/pull/130/files#diff-19ae13a743eb7cebbb96492798b1bec556673eb6236b1387e38d722900bae1c3L355-R391)
- remove unnecessary context parameter from JWTProfileExchange [source diff](https://github.com/caos/oidc/pull/130/files#diff-4ed8f6affa4a9631fa8a034b3d5752fbb6a819107141aae00029014e950f7b4cL14)
This commit is contained in:
Livio Amstutz 2021-11-02 13:21:35 +01:00 committed by GitHub
parent 763d3334e7
commit eb10752e48
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 1738 additions and 624 deletions

71
pkg/crypto/crypto.go Normal file
View file

@ -0,0 +1,71 @@
package crypto
import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"encoding/base64"
"errors"
"io"
)
var (
ErrCipherTextBlockSize = errors.New("ciphertext block size is too short")
)
func EncryptAES(data string, key string) (string, error) {
encrypted, err := EncryptBytesAES([]byte(data), key)
if err != nil {
return "", err
}
return base64.RawURLEncoding.EncodeToString(encrypted), nil
}
func EncryptBytesAES(plainText []byte, key string) ([]byte, error) {
block, err := aes.NewCipher([]byte(key))
if err != nil {
return nil, err
}
cipherText := make([]byte, aes.BlockSize+len(plainText))
iv := cipherText[:aes.BlockSize]
if _, err = io.ReadFull(rand.Reader, iv); err != nil {
return nil, err
}
stream := cipher.NewCFBEncrypter(block, iv)
stream.XORKeyStream(cipherText[aes.BlockSize:], plainText)
return cipherText, nil
}
func DecryptAES(data string, key string) (string, error) {
text, err := base64.RawURLEncoding.DecodeString(data)
if err != nil {
return "", err
}
decrypted, err := DecryptBytesAES(text, key)
if err != nil {
return "", err
}
return string(decrypted), nil
}
func DecryptBytesAES(cipherText []byte, key string) ([]byte, error) {
block, err := aes.NewCipher([]byte(key))
if err != nil {
return nil, err
}
if len(cipherText) < aes.BlockSize {
return nil, ErrCipherTextBlockSize
}
iv := cipherText[:aes.BlockSize]
cipherText = cipherText[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(cipherText, cipherText)
return cipherText, err
}

43
pkg/crypto/hash.go Normal file
View file

@ -0,0 +1,43 @@
package crypto
import (
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"errors"
"fmt"
"hash"
"gopkg.in/square/go-jose.v2"
)
var (
ErrUnsupportedAlgorithm = errors.New("unsupported signing algorithm")
)
func GetHashAlgorithm(sigAlgorithm jose.SignatureAlgorithm) (hash.Hash, error) {
switch sigAlgorithm {
case jose.RS256, jose.ES256, jose.PS256:
return sha256.New(), nil
case jose.RS384, jose.ES384, jose.PS384:
return sha512.New384(), nil
case jose.RS512, jose.ES512, jose.PS512:
return sha512.New(), nil
default:
return nil, fmt.Errorf("%w: %q", ErrUnsupportedAlgorithm, sigAlgorithm)
}
}
func HashString(hash hash.Hash, s string, firstHalf bool) string {
if hash == nil {
return s
}
//nolint:errcheck
hash.Write([]byte(s))
size := hash.Size()
if firstHalf {
size = size / 2
}
sum := hash.Sum(nil)[:size]
return base64.RawURLEncoding.EncodeToString(sum)
}

17
pkg/crypto/key.go Normal file
View file

@ -0,0 +1,17 @@
package crypto
import (
"crypto/rsa"
"crypto/x509"
"encoding/pem"
)
func BytesToPrivateKey(priv []byte) (*rsa.PrivateKey, error) {
block, _ := pem.Decode(priv)
b := block.Bytes
key, err := x509.ParsePKCS1PrivateKey(b)
if err != nil {
return nil, err
}
return key, nil
}

27
pkg/crypto/sign.go Normal file
View file

@ -0,0 +1,27 @@
package crypto
import (
"encoding/json"
"errors"
"gopkg.in/square/go-jose.v2"
)
func Sign(object interface{}, signer jose.Signer) (string, error) {
payload, err := json.Marshal(object)
if err != nil {
return "", err
}
return SignPayload(payload, signer)
}
func SignPayload(payload []byte, signer jose.Signer) (string, error) {
if signer == nil {
return "", errors.New("missing signer")
}
result, err := signer.Sign(payload)
if err != nil {
return "", err
}
return result.CompactSerialize()
}