edit README.md

add NOTICE file
Naming of repair methods
This commit is contained in:
xsl
2023-06-05 11:16:23 +08:00
parent 1a14757e21
commit d6cf6be6d4
16 changed files with 106 additions and 190 deletions

10
NOTICE Normal file
View File

@@ -0,0 +1,10 @@
xslasd/x-oidc
Copyright 2023 xslasd
This product includes software developed by the Apache Software Foundation (http://www.apache.org/).
This product includes software developed by go-jose (github.com/go-jose/go-jose/v3).
This product includes software developed by google/uuid (github.com/google/uuid).
This project referred to the redesign and implementation of interface functions for zitadel/oidc.
The above code files or parts of them are licensed under the Apache 2.0 License and are subject to the terms and conditions of the Apache 2.0 License.

View File

@@ -21,12 +21,14 @@ op.go definition and implementation of an OIDC OpenID Provider (server)
## Third-party Library
The library primarily depends on the third-party library "go-jose/v3".
The HTTP processing section uses an interface-based approach (with net/http being the default), which can be extended as needed.
The HTTP processing section uses an interface-based approach , which can be extended as needed.
When starting OP, implement Config.OpenIDWrapper. By default, github. com/xslass/x-oidc/example/server/httpwrapper can be used. Implementation based on net/HTTP.
```
github.com/go-jose/go-jose/v3 v3.0.0
github.com/google/uuid v1.3.0
golang.org/x/text v0.9.0
```
Special thanks to [zitadel/oidc](https://github.com/zitadel/oidc). This project referred to the redesign and implementation of interface functions for zitadel/oidc.
## Contributors
<a href="https://github.com/xslasd/x-oidc/graphs/contributors">

View File

@@ -1,13 +1,13 @@
package oidc
import (
"github.com/xslasd/x-oidc/crypto"
"github.com/xslasd/x-oidc/storage"
"github.com/xslasd/x-oidc/util"
)
type Config struct {
Issuer string
Crypto crypto.JWTCertifier
Handler OpenIDHandler
Crypto util.JWTCertifier
OpenIDWrapper OpenIDWrapper
Storage storage.IStorage
}

View File

@@ -1,69 +0,0 @@
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
}

View File

@@ -1,44 +0,0 @@
package crypto
import (
"crypto/sha256"
"crypto/sha512"
"encoding/base64"
"errors"
"fmt"
"github.com/go-jose/go-jose/v3"
"hash"
)
var ErrUnsupportedAlgorithm = errors.New("unsupported signing algorithm")
func GetHashAlgorithm(sigAlgorithm jose.SignatureAlgorithm) (hash.Hash, error) {
switch sigAlgorithm {
case jose.RS256, jose.ES256, jose.PS256, jose.HS256:
return sha256.New(), nil
case jose.RS384, jose.ES384, jose.PS384, jose.HS384:
return sha512.New384(), nil
case jose.RS512, jose.ES512, jose.PS512, jose.HS512:
return sha512.New(), nil
default:
return nil, fmt.Errorf("%w: %q", ErrUnsupportedAlgorithm, sigAlgorithm)
}
}
func ClaimHash(claim string, sigAlgorithm jose.SignatureAlgorithm) (string, error) {
hash, err := GetHashAlgorithm(sigAlgorithm)
if err != nil {
return "", err
}
return HashString(hash, claim, true), nil
}
func HashString(hash hash.Hash, s string, firstHalf bool) string {
hash.Write([]byte(s))
size := hash.Size()
if firstHalf {
size = size / 2
}
sum := hash.Sum(nil)[:size]
return base64.RawURLEncoding.EncodeToString(sum)
}

View File

@@ -55,4 +55,5 @@ var (
PublicKeyInvalid = New(1050, ServerErrorErrorType, "failed to decode PEM block containing public key", "")
PrivateKeyInvalid = New(1051, ServerErrorErrorType, "failed to decode PEM block containing private key", "")
AlgorithmUnsupported = New(1052, ServerErrorErrorType, "unsupported jose signing algorithm: %s", "")
)

View File

@@ -1,4 +1,4 @@
package handler
package httpwrapper
import (
"context"
@@ -15,20 +15,20 @@ import (
"time"
)
type HttpHandler struct {
type HttpWrapper struct {
handler *http.ServeMux
addr string
logger log.Logger
}
func NewHttpHandler(addr string) *HttpHandler {
return &HttpHandler{handler: http.DefaultServeMux, addr: addr}
func NewHttpHandler(addr string) *HttpWrapper {
return &HttpWrapper{handler: http.DefaultServeMux, addr: addr}
}
func (h *HttpHandler) SetLogger(logger log.Logger) {
func (h *HttpWrapper) SetLogger(logger log.Logger) {
h.logger = logger
}
func (h *HttpHandler) ListenAndServe() error {
func (h *HttpWrapper) ListenAndServe() error {
h.login()
var err error
srv := &http.Server{
@@ -60,7 +60,7 @@ func (h *HttpHandler) ListenAndServe() error {
}
}
func (h *HttpHandler) DiscoveryJWKs(jwksEndpoint string, handler func() (*jose.JSONWebKeySet, error)) {
func (h *HttpWrapper) DiscoveryJWKs(jwksEndpoint string, handler func() (*jose.JSONWebKeySet, error)) {
h.handler.HandleFunc(jwksEndpoint, func(w http.ResponseWriter, r *http.Request) {
data, err := handler()
if err != nil {
@@ -74,7 +74,7 @@ func (h *HttpHandler) DiscoveryJWKs(jwksEndpoint string, handler func() (*jose.J
})
}
func (h *HttpHandler) DiscoveryConfig(discoveryEndpoint string, handler func(req *x_oidc.DiscoveryConfigReq) *model.DiscoveryConfiguration) {
func (h *HttpWrapper) DiscoveryConfig(discoveryEndpoint string, handler func(req *x_oidc.DiscoveryConfigReq) *model.DiscoveryConfiguration) {
h.handler.HandleFunc(discoveryEndpoint, func(w http.ResponseWriter, r *http.Request) {
data := handler(&x_oidc.DiscoveryConfigReq{
RegistrationEndpoint: "",
@@ -88,7 +88,7 @@ func (h *HttpHandler) DiscoveryConfig(discoveryEndpoint string, handler func(req
})
}
func (h *HttpHandler) Authorize(authorizationEndpoint string, handler func(ctx context.Context, req *x_oidc.AuthRequestReq) (string, error)) {
func (h *HttpWrapper) Authorize(authorizationEndpoint string, handler func(ctx context.Context, req *x_oidc.AuthRequestReq) (string, error)) {
h.handler.HandleFunc(authorizationEndpoint, func(w http.ResponseWriter, r *http.Request) {
var authRequestReq x_oidc.AuthRequestReq
if r.Method == "GET" {
@@ -136,7 +136,7 @@ func (h *HttpHandler) Authorize(authorizationEndpoint string, handler func(ctx c
})
}
func (h *HttpHandler) EndSession(endSessionEndpoint string, handler func(ctx context.Context, req *x_oidc.EndSessionReq) (string, error)) {
func (h *HttpWrapper) EndSession(endSessionEndpoint string, handler func(ctx context.Context, req *x_oidc.EndSessionReq) (string, error)) {
h.handler.HandleFunc(endSessionEndpoint, func(w http.ResponseWriter, r *http.Request) {
var endSessionReq x_oidc.EndSessionReq
r.ParseForm()
@@ -168,7 +168,7 @@ func (h *HttpHandler) EndSession(endSessionEndpoint string, handler func(ctx con
})
}
func (h *HttpHandler) Introspect(introspectionEndpoint string, handler func(ctx context.Context, req *x_oidc.IntrospectionReq, r *http.Request) (*model.IntrospectionModel, error)) {
func (h *HttpWrapper) Introspect(introspectionEndpoint string, handler func(ctx context.Context, req *x_oidc.IntrospectionReq, r *http.Request) (*model.IntrospectionModel, error)) {
h.handler.HandleFunc(introspectionEndpoint, func(w http.ResponseWriter, r *http.Request) {
var introspectionReq x_oidc.IntrospectionReq
r.ParseForm()
@@ -207,7 +207,7 @@ func (h *HttpHandler) Introspect(introspectionEndpoint string, handler func(ctx
})
}
func (h *HttpHandler) RevokeToken(revocationEndpoint string, handler func(ctx context.Context, req *x_oidc.RevokeTokenReq, r *http.Request) error) {
func (h *HttpWrapper) RevokeToken(revocationEndpoint string, handler func(ctx context.Context, req *x_oidc.RevokeTokenReq, r *http.Request) error) {
h.handler.HandleFunc(revocationEndpoint, func(w http.ResponseWriter, r *http.Request) {
var revokeTokenReq x_oidc.RevokeTokenReq
r.ParseForm()
@@ -246,7 +246,7 @@ func (h *HttpHandler) RevokeToken(revocationEndpoint string, handler func(ctx co
})
}
func (h *HttpHandler) TokenExchange(tokenExchangeEndpoint string, handler func(ctx context.Context, req *x_oidc.TokenExchangeReq, r *http.Request) (interface{}, error)) {
func (h *HttpWrapper) TokenExchange(tokenExchangeEndpoint string, handler func(ctx context.Context, req *x_oidc.TokenExchangeReq, r *http.Request) (interface{}, error)) {
h.handler.HandleFunc(tokenExchangeEndpoint, func(w http.ResponseWriter, r *http.Request) {
var tokenExchangeReq x_oidc.TokenExchangeReq
r.ParseForm()
@@ -315,7 +315,7 @@ func (h *HttpHandler) TokenExchange(tokenExchangeEndpoint string, handler func(c
})
}
func (h *HttpHandler) Userinfo(userinfoEndpoint string, handler func(ctx context.Context, req *x_oidc.UserinfoReq, r *http.Request) (*model.UserInfo, error)) {
func (h *HttpWrapper) Userinfo(userinfoEndpoint string, handler func(ctx context.Context, req *x_oidc.UserinfoReq, r *http.Request) (*model.UserInfo, error)) {
h.handler.HandleFunc(userinfoEndpoint, func(w http.ResponseWriter, r *http.Request) {
var userinfoReq x_oidc.UserinfoReq
r.ParseForm()
@@ -340,7 +340,7 @@ func (h *HttpHandler) Userinfo(userinfoEndpoint string, handler func(ctx context
})
}
func (h *HttpHandler) AuthorizeCallback(authorizeCallbackEndpoint string, handler func(ctx context.Context, req *x_oidc.AuthorizeCallbackReq) (callbackUrl string, err error)) {
func (h *HttpWrapper) AuthorizeCallback(authorizeCallbackEndpoint string, handler func(ctx context.Context, req *x_oidc.AuthorizeCallbackReq) (callbackUrl string, err error)) {
h.handler.HandleFunc(authorizeCallbackEndpoint, func(w http.ResponseWriter, r *http.Request) {
var authorizeCallbackReq x_oidc.AuthorizeCallbackReq

View File

@@ -1,4 +1,4 @@
package handler
package httpwrapper
import (
"embed"
@@ -13,7 +13,7 @@ var (
templates = template.Must(template.ParseFS(templateFS, "templates/*.html"))
)
func (h *HttpHandler) login() {
func (h *HttpWrapper) login() {
h.handler.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
@@ -23,7 +23,6 @@ func (h *HttpHandler) login() {
if r.Method == "GET" {
templates.ExecuteTemplate(w, "login", map[string]string{
"ID": r.Form.Get("request_id"),
"Error": "",
})
}
})

View File

@@ -0,0 +1,23 @@
{{ define "login" -}}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body style="display: flex; align-items: center; justify-content: center; height: 100vh;">
<form method="POST" action="/callback" style="height: 200px; width: 400px;">
<input type="hidden" name="id" value="{{.ID}}">
<div>
<label for="username">Username:</label>
<input id="username" name="username" style="width: 100%;height: 40px">
</div>
<div>
<label for="password">Password:(Correct password:test)</label>
<input id="password" type="password" name="password" style="width: 100%;height: 40px">
</div>
<button type="submit">Login</button>
</form>
</body>
</html>`
{{- end }}

View File

@@ -3,21 +3,21 @@ package main
import (
"github.com/go-jose/go-jose/v3"
x_oidc "github.com/xslasd/x-oidc"
"github.com/xslasd/x-oidc/crypto"
"github.com/xslasd/x-oidc/example/server/handler"
"github.com/xslasd/x-oidc/example/server/httpwrapper"
"github.com/xslasd/x-oidc/example/server/storage"
"github.com/xslasd/x-oidc/util"
)
func main() {
httpHandler := handler.NewHttpHandler(":8080")
cr, err := crypto.NewJoseRSAJWT("private.pem", jose.RS256)
httpHandler := httpwrapper.NewHttpHandler(":8080")
cr, err := util.NewJoseRSAJWT("private.pem", jose.RS256)
if err != nil {
panic(err)
}
_, err = x_oidc.NewOpenIDProvider(
&x_oidc.Config{
Issuer: "http://localhost:8080",
Handler: httpHandler,
OpenIDWrapper: httpHandler,
Storage: storage.NewStorage(),
Crypto: cr,
},

View File

@@ -79,7 +79,7 @@ func (t *TokenClaims) CheckAuthorizationContextClassReference(acr string) error
type JWTClientTokenClaims struct {
Issuer string `json:"iss"`
Subject string `json:"sub"`
Audience []string `json:"aud"` //todo array or string
Audience Audience `json:"aud"`
IssuedAt int64 `json:"iat"`
ExpiresAt int64 `json:"exp"`

8
op.go
View File

@@ -17,7 +17,7 @@ type OpenIDProvider struct {
opt *OpenIDOption
}
type OpenIDHandler interface {
type OpenIDWrapper interface {
SetLogger(logger log.Logger)
DiscoveryJWKs(jwksEndpoint string, handler func() (*jose.JSONWebKeySet, error))
@@ -38,7 +38,7 @@ func NewOpenIDProvider(cfg *Config, opts ...Option) (*OpenIDProvider, error) {
if err != nil {
return nil, err
}
if cfg.Handler == nil {
if cfg.OpenIDWrapper == nil {
return nil, ecode.HandlerIsNull
}
if cfg.Storage == nil {
@@ -52,10 +52,10 @@ func NewOpenIDProvider(cfg *Config, opts ...Option) (*OpenIDProvider, error) {
if !opt.allowInsecure && !util.IsHttpsPrefix(cfg.Issuer) {
return nil, ecode.IssuerHTTPSInvalid
}
handler := srv.cfg.Handler
handler := srv.cfg.OpenIDWrapper
srv.printBanner()
cfg.Storage.SetLogger(opt.logger)
cfg.Handler.SetLogger(opt.logger)
cfg.OpenIDWrapper.SetLogger(opt.logger)
handler.DiscoveryJWKs(opt.jwksPath, srv.discoveryJWKs)
opt.logger.Infof("JWKsEndpoint -> %s", opt.jwksEndpoint)
handler.DiscoveryConfig(opt.discoveryPath, srv.discoveryConfig)

View File

@@ -8,7 +8,6 @@ import (
"github.com/go-jose/go-jose/v3"
"github.com/go-jose/go-jose/v3/jwt"
"github.com/xslasd/x-oidc/constant"
"github.com/xslasd/x-oidc/crypto"
"github.com/xslasd/x-oidc/ecode"
"github.com/xslasd/x-oidc/model"
"github.com/xslasd/x-oidc/util"
@@ -66,7 +65,7 @@ func (o OIDCClient) GenerateCodeChallenge(CodeChallengeMethod string) string {
return util.RandomString(43)
case constant.CodeChallengeMethodS256:
codeChallenge := util.RandomString(43)
return crypto.HashString(sha256.New(), codeChallenge, false)
return util.HashString(sha256.New(), codeChallenge, false)
}
return ""
}

View File

@@ -6,9 +6,9 @@ import (
"fmt"
"github.com/go-jose/go-jose/v3"
"github.com/xslasd/x-oidc/constant"
"github.com/xslasd/x-oidc/crypto"
"github.com/xslasd/x-oidc/ecode"
"github.com/xslasd/x-oidc/storage"
"github.com/xslasd/x-oidc/util"
"net/http"
"net/url"
"strings"
@@ -136,8 +136,7 @@ func authorizeCodeChallenge(req *TokenExchangeReq, authReq *storage.AuthRequest)
}
switch authReq.CodeChallengeMethod {
case constant.CodeChallengeMethodS256:
req.CodeVerifier = crypto.HashString(sha256.New(), req.CodeVerifier, false)
default:
req.CodeVerifier = util.HashString(sha256.New(), req.CodeVerifier, false)
}
if req.CodeVerifier != authReq.CodeChallenge {
fmt.Println("debug: CodeChallengeInvalid")

View File

@@ -24,17 +24,3 @@ func ValidateIssuer(issuer string) error {
func IsHttpsPrefix(issuer string) bool {
return strings.HasPrefix(issuer, constant.HttpsPrefix)
}
func GetQueryString(queryMap map[string]string) string {
if queryMap == nil || len(queryMap) == 0 {
return ""
}
queryValue := url.Values{}
for key, value := range queryMap {
if value == "" {
continue
}
queryValue.Add(key, value)
}
return queryValue.Encode()
}

View File

@@ -1,9 +1,12 @@
package crypto
package util
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/sha512"
"crypto/x509"
"encoding/base64"
"encoding/hex"
"encoding/pem"
"fmt"
@@ -11,6 +14,7 @@ import (
"github.com/go-jose/go-jose/v3/jwt"
"github.com/google/uuid"
"github.com/xslasd/x-oidc/ecode"
"hash"
"os"
)
@@ -132,7 +136,6 @@ func (j JoseRSAJWT) ParseJWT(token string, payload interface{}) error {
//}
//
//func (j JoseHMACJWT) GenerateJWT(claims interface{}) (string, error) {
// // 创建一个 JWT 签名者
// signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.HS256, Key: j.signingKey}, nil)
// if err != nil {
// fmt.Println("Error creating signer:", err)
@@ -161,19 +164,26 @@ func (j JoseRSAJWT) ParseJWT(token string, payload interface{}) error {
// err = json.Unmarshal(b, payload)
// return err
//}
//
//func Sign(object interface{}, signingKey jose.SigningKey) (string, error) {
// signer, err := jose.NewSigner(signingKey, &jose.SignerOptions{})
// if err != nil {
// return "", err
// }
// payload, err := json.Marshal(object)
// if err != nil {
// return "", err
// }
// result, err := signer.Sign(payload)
// if err != nil {
// return "", err
// }
// return result.CompactSerialize()
//}
func GetHashAlgorithm(sigAlgorithm jose.SignatureAlgorithm) (hash.Hash, error) {
switch sigAlgorithm {
case jose.RS256, jose.ES256, jose.PS256, jose.HS256:
return sha256.New(), nil
case jose.RS384, jose.ES384, jose.PS384, jose.HS384:
return sha512.New384(), nil
case jose.RS512, jose.ES512, jose.PS512, jose.HS512:
return sha512.New(), nil
default:
return nil, ecode.AlgorithmUnsupported.SetDescriptionf(string(sigAlgorithm))
}
}
func HashString(hash hash.Hash, s string, firstHalf bool) string {
hash.Write([]byte(s))
size := hash.Size()
if firstHalf {
size = size / 2
}
sum := hash.Sum(nil)[:size]
return base64.RawURLEncoding.EncodeToString(sum)
}