From 0c51cb72c33bb059bc061693bdff5c379eddbec8 Mon Sep 17 00:00:00 2001 From: Evan Date: Wed, 4 Jun 2025 07:53:44 +0200 Subject: [PATCH] fix: parse public key as fallback for certificate for bearer authentication (#3180) * fix: parse public key as fallback for bearer auth Signed-off-by: evanebb * fix: use correct error message Signed-off-by: evanebb --------- Signed-off-by: evanebb --- errors/errors.go | 2 +- pkg/api/authn.go | 36 ++++++++++++++++++++++++------------ pkg/api/controller_test.go | 34 +++++++++++++++++++++++++++++++--- test/scripts/gen_certs.sh | 20 ++++++++++++++++++++ 4 files changed, 76 insertions(+), 16 deletions(-) diff --git a/errors/errors.go b/errors/errors.go index 37312244..307d8a78 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -179,7 +179,7 @@ var ( ErrNoBearerToken = errors.New("no bearer token given") ErrInvalidBearerToken = errors.New("invalid bearer token given") ErrInsufficientScope = errors.New("bearer token does not have sufficient scope") - ErrCouldNotLoadCertificate = errors.New("failed to load certificate") + ErrCouldNotLoadPublicKey = errors.New("failed to load public key") ErrEventTypeEmpty = errors.New("event type empty") ErrEventSinkIsNil = errors.New("event sink is nil") ErrUnsupportedEventSink = errors.New("event sink is not supported") diff --git a/pkg/api/authn.go b/pkg/api/authn.go index 7435c88e..ab0aebb0 100644 --- a/pkg/api/authn.go +++ b/pkg/api/authn.go @@ -2,6 +2,7 @@ package api import ( "context" + "crypto" "crypto/sha256" "crypto/x509" "encoding/base64" @@ -402,15 +403,17 @@ func (amw *AuthnMiddleware) tryAuthnHandlers(ctlr *Controller) mux.MiddlewareFun } func bearerAuthHandler(ctlr *Controller) mux.MiddlewareFunc { - certificate, err := loadCertificateFromFile(ctlr.Config.HTTP.Auth.Bearer.Cert) + // although the configuration option is called 'cert', this function will also parse a public key directly + // see https://github.com/project-zot/zot/issues/3173 for info + publicKey, err := loadPublicKeyFromFile(ctlr.Config.HTTP.Auth.Bearer.Cert) if err != nil { - ctlr.Log.Panic().Err(err).Msg("failed to load certificate for bearer authentication") + ctlr.Log.Panic().Err(err).Msg("failed to load public key for bearer authentication") } authorizer := NewBearerAuthorizer( ctlr.Config.HTTP.Auth.Bearer.Realm, ctlr.Config.HTTP.Auth.Bearer.Service, - certificate.PublicKey, + publicKey, ) return func(next http.Handler) http.Handler { @@ -902,21 +905,30 @@ func GenerateAPIKey(uuidGenerator guuid.Generator, log log.Logger, return apiKey, apiKeyID.String(), err } -func loadCertificateFromFile(path string) (*x509.Certificate, error) { - rawCert, err := os.ReadFile(path) +func loadPublicKeyFromFile(path string) (crypto.PublicKey, error) { + raw, err := os.ReadFile(path) if err != nil { - return nil, fmt.Errorf("%w: %w, path %s", zerr.ErrCouldNotLoadCertificate, err, path) + return nil, fmt.Errorf("%w: %w, path %s", zerr.ErrCouldNotLoadPublicKey, err, path) } - block, _ := pem.Decode(rawCert) + block, _ := pem.Decode(raw) if block == nil { - return nil, fmt.Errorf("%w: no valid PEM data found", zerr.ErrCouldNotLoadCertificate) + return nil, fmt.Errorf("%w: no valid PEM data found", zerr.ErrCouldNotLoadPublicKey) } - cert, err := x509.ParseCertificate(block.Bytes) - if err != nil { - return nil, fmt.Errorf("%w: %w", zerr.ErrCouldNotLoadCertificate, err) + pemBytes := block.Bytes + + if cert, err := x509.ParseCertificate(pemBytes); err == nil { + return cert.PublicKey, nil } - return cert, nil + if key, err := x509.ParsePKIXPublicKey(pemBytes); err == nil { + return key, nil + } + + if key, err := x509.ParsePKCS1PublicKey(pemBytes); err == nil { + return key, nil + } + + return nil, fmt.Errorf("%w: no valid x509 certificate or public key found", zerr.ErrCouldNotLoadPublicKey) } diff --git a/pkg/api/controller_test.go b/pkg/api/controller_test.go index 752233a0..f0ded5b0 100644 --- a/pkg/api/controller_test.go +++ b/pkg/api/controller_test.go @@ -77,11 +77,15 @@ import ( const ( ServerCert = "../../test/data/server.cert" ServerKey = "../../test/data/server.key" + ServerPublicKey = "../../test/data/server-public.key" + ServerPublicKeyPKCS1 = "../../test/data/server-public-pkcs1.key" CACert = "../../test/data/ca.crt" ServerCertECDSA = "../../test/data/server-ecdsa.cert" ServerKeyECDSA = "../../test/data/server-ecdsa.key" + ServerPublicKeyECDSA = "../../test/data/server-public-ecdsa.key" ServerCertED25519 = "../../test/data/server-ed25519.cert" ServerKeyED25519 = "../../test/data/server-ed25519.key" + ServerPublicKeyED25519 = "../../test/data/server-public-ed25519.key" UnauthorizedNamespace = "fortknox/notallowed" AuthorizationNamespace = "authz/image" LDAPAddress = "127.0.0.1" @@ -3921,23 +3925,47 @@ func TestBearerAuthMultipleAlgorithms(t *testing.T) { alg string }{ { - "RSA signing key", + "RSA signing key using certificate", ServerKey, ServerCert, "RS256", }, { - "ECDSA signing key", + "RSA signing key using public key", + ServerKey, + ServerPublicKey, + "RS256", + }, + { + "RSA signing key using public key in PKCS1 format", + ServerKey, + ServerPublicKeyPKCS1, + "RS256", + }, + { + "ECDSA signing key using certificate", ServerKeyECDSA, ServerCertECDSA, "ES256", }, { - "ED25519 signing key", + "ECDSA signing key using public key", + ServerKeyECDSA, + ServerPublicKeyECDSA, + "ES256", + }, + { + "ED25519 signing key using certificate", ServerKeyED25519, ServerCertED25519, "EdDSA", }, + { + "ED25519 signing key using public key", + ServerKeyED25519, + ServerPublicKeyED25519, + "EdDSA", + }, } for _, testCase := range testCases { diff --git a/test/scripts/gen_certs.sh b/test/scripts/gen_certs.sh index 2351dd90..f24bbbae 100755 --- a/test/scripts/gen_certs.sh +++ b/test/scripts/gen_certs.sh @@ -19,6 +19,16 @@ openssl req \ -out server.csr \ -subj "/OU=TestServer/CN=*" +openssl rsa \ + -in server.key \ + -pubout \ + -out server-public.key + +openssl rsa \ + -in server.key \ + -RSAPublicKey_out \ + -out server-public-pkcs1.key + openssl x509 \ -req \ -days 3650 \ @@ -76,6 +86,11 @@ openssl req \ -out server-ecdsa.csr \ -subj "/OU=TestServer/CN=*" +openssl ec \ + -in server-ecdsa.key \ + -pubout \ + -out server-public-ecdsa.key + openssl x509 \ -req \ -days 3650 \ @@ -112,6 +127,11 @@ openssl req \ -out server-ed25519.csr \ -subj "/OU=TestServer/CN=*" +openssl pkey \ + -in server-ed25519.key \ + -pubout \ + -out server-public-ed25519.key + openssl x509 \ -req \ -days 3650 \