Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for registry v3 JWK thumbprint key ID format #401

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 47 additions & 4 deletions auth_server/server/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,17 @@
package server

import (
"crypto"
"crypto/ecdsa"
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"math/big"
"os"
"strings"
"time"
Expand Down Expand Up @@ -80,14 +86,16 @@ type LetsEncryptConfig struct {
}

type TokenConfig struct {
Issuer string `yaml:"issuer,omitempty"`
CertFile string `yaml:"certificate,omitempty"`
KeyFile string `yaml:"key,omitempty"`
Expiration int64 `yaml:"expiration,omitempty"`
Issuer string `yaml:"issuer,omitempty"`
CertFile string `yaml:"certificate,omitempty"`
KeyFile string `yaml:"key,omitempty"`
Expiration int64 `yaml:"expiration,omitempty"`
DisableLegacyKeyID bool `yaml:"disable_legacy_key_id,omitempty"`

publicKey libtrust.PublicKey
privateKey libtrust.PrivateKey
sigAlg string
keyID string
}

// TLSCipherSuitesValues maps CipherSuite names as strings to the actual values
Expand Down Expand Up @@ -405,6 +413,12 @@ func LoadConfig(fileName string) (*Config, error) {
return nil, fmt.Errorf("failed to load token cert and key: none provided")
}

if c.Token.DisableLegacyKeyID {
c.Token.keyID = getRFC7638Thumbprint(c.Token.publicKey.CryptoPublicKey())
} else {
c.Token.keyID = c.Token.publicKey.KeyID()
}

if !serverConfigured && c.Server.LetsEncrypt.Email != "" {
if c.Server.LetsEncrypt.CacheDir == "" {
return nil, fmt.Errorf("server.letsencrypt.cache_dir is required")
Expand All @@ -419,3 +433,32 @@ func LoadConfig(fileName string) (*Config, error) {

return c, nil
}

// getRFC7638Thumbprint will generate the JWK thumbprint (https://www.rfc-editor.org/rfc/rfc7638.html) for a crypto.PublicKey.
//
// Copied from https://github.com/distribution/distribution/blob/51bdcb7bac069f263ce238db6bd0610759c2635f/registry/auth/token/util.go#L63
func getRFC7638Thumbprint(publickey crypto.PublicKey) string {
var payload string

switch pubkey := publickey.(type) {
case *rsa.PublicKey:
e_big := big.NewInt(int64(pubkey.E)).Bytes()

e := base64.RawURLEncoding.EncodeToString(e_big)
n := base64.RawURLEncoding.EncodeToString(pubkey.N.Bytes())

payload = fmt.Sprintf(`{"e":"%s","kty":"RSA","n":"%s"}`, e, n)
case *ecdsa.PublicKey:
params := pubkey.Params()
crv := params.Name
x := base64.RawURLEncoding.EncodeToString(params.Gx.Bytes())
y := base64.RawURLEncoding.EncodeToString(params.Gy.Bytes())

payload = fmt.Sprintf(`{"crv":"%s","kty":"EC","x":"%s","y":"%s"}`, crv, x, y)
default:
return ""
}

shasum := sha256.Sum256([]byte(payload))
return base64.RawURLEncoding.EncodeToString(shasum[:])
}
2 changes: 1 addition & 1 deletion auth_server/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,7 @@ func (as *AuthServer) CreateToken(ar *authRequest, ares []authzResult) (string,
header := token.Header{
Type: "JWT",
SigningAlg: tc.sigAlg,
KeyID: tc.publicKey.KeyID(),
KeyID: tc.keyID,
}
headerJSON, err := json.Marshal(header)
if err != nil {
Expand Down
5 changes: 5 additions & 0 deletions examples/reference.yml
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ token: # Settings for the tokens.
# If not specified, server's TLS certificate and key are used.
# certificate: "..."
# key: "..."
# Whether the legacy libtrust key ID should be embedded in the `kid` header of the token.
# Set this to true if you are using registry v3, and to false if you are using registry v2.
# If set to true, the JWK thumbprint (see https://www.rfc-editor.org/rfc/rfc7638.html) of the certificate will be embedded instead.
# Defaults to false.
# disable_legacy_key_id: false

# Authentication methods. All are tried, any one returning success is sufficient.
# At least one must be configured. If you want an unauthenticated public setup,
Expand Down
2 changes: 2 additions & 0 deletions examples/simple.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ server:
token:
issuer: "Acme auth server" # Must match issuer in the Registry config.
expiration: 900
# Uncomment the following line if you are using registry v3, leave it commented if you are using registry v2
# disable_legacy_key_id: true

users:
# Password is specified as a BCrypt hash. Use `htpasswd -nB USERNAME` to generate.
Expand Down
Loading