From a6ddb51c8a3a470368069319fcdba8f10a1a4ced Mon Sep 17 00:00:00 2001 From: Joe LeGasse Date: Fri, 22 Jun 2018 12:21:40 -0400 Subject: [PATCH] auth: Support all JWT algorithms This change adds support to etcd for all of the JWT algorithms included in the underlying JWT library. --- Documentation/op-guide/configuration.md | 1 + auth/jwt.go | 192 +++++++---------- auth/jwt_test.go | 194 +++++++++++++----- auth/options.go | 192 +++++++++++++++++ auth/store.go | 4 + integration/fixtures/gencerts.sh | 9 + integration/fixtures/server-ca-csr-ecdsa.json | 20 ++ integration/fixtures/server-ecdsa.crt | 20 ++ .../fixtures/server-ecdsa.key.insecure | 5 + 9 files changed, 472 insertions(+), 165 deletions(-) create mode 100644 auth/options.go create mode 100644 integration/fixtures/server-ca-csr-ecdsa.json create mode 100644 integration/fixtures/server-ecdsa.crt create mode 100644 integration/fixtures/server-ecdsa.key.insecure diff --git a/Documentation/op-guide/configuration.md b/Documentation/op-guide/configuration.md index 837a84e01..d3d81f010 100644 --- a/Documentation/op-guide/configuration.md +++ b/Documentation/op-guide/configuration.md @@ -372,6 +372,7 @@ Follow the instructions when using these flags. ### --auth-token + Specify a token type and token specific options, especially for JWT. Its format is "type,var1=val1,var2=val2,...". Possible type is 'simple' or 'jwt'. Possible variables are 'sign-method' for specifying a sign method of jwt (its possible values are 'ES256', 'ES384', 'ES512', 'HS256', 'HS384', 'HS512', 'RS256', 'RS384', 'RS512', 'PS256', 'PS384', or 'PS512'), 'pub-key' for specifying a path to a public key for verifying jwt, 'priv-key' for specifying a path to a private key for signing jwt, and 'ttl' for specifying TTL of jwt tokens. ++ For asymmetric algorithms ('RS', 'PS', 'ES'), the public key is optional, as the private key contains enough information to both sign and verify tokens. + Example option of JWT: '--auth-token jwt,pub-key=app.rsa.pub,priv-key=app.rsa,sign-method=RS512,ttl=10m' + default: "simple" diff --git a/auth/jwt.go b/auth/jwt.go index 6e4c835e6..c22ef898a 100644 --- a/auth/jwt.go +++ b/auth/jwt.go @@ -16,8 +16,9 @@ package auth import ( "context" + "crypto/ecdsa" "crypto/rsa" - "io/ioutil" + "errors" "time" jwt "github.com/dgrijalva/jwt-go" @@ -26,10 +27,10 @@ import ( type tokenJWT struct { lg *zap.Logger - signMethod string - signKey *rsa.PrivateKey - verifyKey *rsa.PublicKey + signMethod jwt.SigningMethod + key interface{} ttl time.Duration + verifyOnly bool } func (t *tokenJWT) enable() {} @@ -45,25 +46,20 @@ func (t *tokenJWT) info(ctx context.Context, token string, rev uint64) (*AuthInf ) parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) { - return t.verifyKey, nil + if token.Method.Alg() != t.signMethod.Alg() { + return nil, errors.New("invalid signing method") + } + switch k := t.key.(type) { + case *rsa.PrivateKey: + return &k.PublicKey, nil + case *ecdsa.PrivateKey: + return &k.PublicKey, nil + default: + return t.key, nil + } }) - switch err.(type) { - case nil: - if !parsed.Valid { - if t.lg != nil { - t.lg.Warn("invalid JWT token", zap.String("token", token)) - } else { - plog.Warningf("invalid jwt token: %s", token) - } - return nil, false - } - - claims := parsed.Claims.(jwt.MapClaims) - - username = claims["username"].(string) - revision = uint64(claims["revision"].(float64)) - default: + if err != nil { if t.lg != nil { t.lg.Warn( "failed to parse a JWT token", @@ -76,20 +72,37 @@ func (t *tokenJWT) info(ctx context.Context, token string, rev uint64) (*AuthInf return nil, false } + claims, ok := parsed.Claims.(jwt.MapClaims) + if !parsed.Valid || !ok { + if t.lg != nil { + t.lg.Warn("invalid JWT token", zap.String("token", token)) + } else { + plog.Warningf("invalid jwt token: %s", token) + } + return nil, false + } + + username = claims["username"].(string) + revision = uint64(claims["revision"].(float64)) + return &AuthInfo{Username: username, Revision: revision}, true } func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64) (string, error) { + if t.verifyOnly { + return "", ErrVerifyOnly + } + // Future work: let a jwt token include permission information would be useful for // permission checking in proxy side. - tk := jwt.NewWithClaims(jwt.GetSigningMethod(t.signMethod), + tk := jwt.NewWithClaims(t.signMethod, jwt.MapClaims{ "username": username, "revision": revision, "exp": time.Now().Add(t.ttl).Unix(), }) - token, err := tk.SignedString(t.signKey) + token, err := tk.SignedString(t.key) if err != nil { if t.lg != nil { t.lg.Warn( @@ -117,113 +130,54 @@ func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64) return token, err } -func prepareOpts(lg *zap.Logger, opts map[string]string) (jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath string, ttl time.Duration, err error) { - for k, v := range opts { - switch k { - case "sign-method": - jwtSignMethod = v - case "pub-key": - jwtPubKeyPath = v - case "priv-key": - jwtPrivKeyPath = v - case "ttl": - ttl, err = time.ParseDuration(v) - if err != nil { - if lg != nil { - lg.Warn( - "failed to parse JWT TTL option", - zap.String("ttl-value", v), - zap.Error(err), - ) - } else { - plog.Errorf("failed to parse ttl option (%s)", err) - } - return "", "", "", 0, ErrInvalidAuthOpts - } - default: - if lg != nil { - lg.Warn("unknown JWT token option", zap.String("option", k)) - } else { - plog.Errorf("unknown token specific option: %s", k) - } - return "", "", "", 0, ErrInvalidAuthOpts - } - } - if len(jwtSignMethod) == 0 { - return "", "", "", 0, ErrInvalidAuthOpts - } - return jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, ttl, nil -} - -func newTokenProviderJWT(lg *zap.Logger, opts map[string]string) (*tokenJWT, error) { - jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, ttl, err := prepareOpts(lg, opts) +func newTokenProviderJWT(lg *zap.Logger, optMap map[string]string) (*tokenJWT, error) { + var err error + var opts jwtOptions + err = opts.ParseWithDefaults(optMap) if err != nil { + if lg != nil { + lg.Warn("problem loading JWT options", zap.Error(err)) + } else { + plog.Errorf("problem loading JWT options: %s", err) + } return nil, ErrInvalidAuthOpts } - if ttl == 0 { - ttl = 5 * time.Minute + var keys = make([]string, 0, len(optMap)) + for k := range optMap { + if !knownOptions[k] { + keys = append(keys, k) + } + } + if len(keys) > 0 { + if lg != nil { + lg.Warn("unknown JWT options", zap.Strings("keys", keys)) + } else { + plog.Warningf("unknown JWT options: %v", keys) + } + } + + key, err := opts.Key() + if err != nil { + return nil, err } t := &tokenJWT{ - lg: lg, - ttl: ttl, + lg: lg, + ttl: opts.TTL, + signMethod: opts.SignMethod, + key: key, } - t.signMethod = jwtSignMethod - - verifyBytes, err := ioutil.ReadFile(jwtPubKeyPath) - if err != nil { - if lg != nil { - lg.Warn( - "failed to read JWT public key", - zap.String("public-key-path", jwtPubKeyPath), - zap.Error(err), - ) - } else { - plog.Errorf("failed to read public key (%s) for jwt: %s", jwtPubKeyPath, err) + switch t.signMethod.(type) { + case *jwt.SigningMethodECDSA: + if _, ok := t.key.(*ecdsa.PublicKey); ok { + t.verifyOnly = true } - return nil, err - } - t.verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes) - if err != nil { - if lg != nil { - lg.Warn( - "failed to parse JWT public key", - zap.String("public-key-path", jwtPubKeyPath), - zap.Error(err), - ) - } else { - plog.Errorf("failed to parse public key (%s): %s", jwtPubKeyPath, err) + case *jwt.SigningMethodRSA, *jwt.SigningMethodRSAPSS: + if _, ok := t.key.(*rsa.PublicKey); ok { + t.verifyOnly = true } - return nil, err - } - - signBytes, err := ioutil.ReadFile(jwtPrivKeyPath) - if err != nil { - if lg != nil { - lg.Warn( - "failed to read JWT private key", - zap.String("private-key-path", jwtPrivKeyPath), - zap.Error(err), - ) - } else { - plog.Errorf("failed to read private key (%s) for jwt: %s", jwtPrivKeyPath, err) - } - return nil, err - } - t.signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes) - if err != nil { - if lg != nil { - lg.Warn( - "failed to parse JWT private key", - zap.String("private-key-path", jwtPrivKeyPath), - zap.Error(err), - ) - } else { - plog.Errorf("failed to parse private key (%s): %s", jwtPrivKeyPath, err) - } - return nil, err } return t, nil diff --git a/auth/jwt_test.go b/auth/jwt_test.go index 926651057..24d13ca01 100644 --- a/auth/jwt_test.go +++ b/auth/jwt_test.go @@ -23,80 +23,182 @@ import ( ) const ( - jwtPubKey = "../integration/fixtures/server.crt" - jwtPrivKey = "../integration/fixtures/server.key.insecure" + jwtRSAPubKey = "../integration/fixtures/server.crt" + jwtRSAPrivKey = "../integration/fixtures/server.key.insecure" + + jwtECPubKey = "../integration/fixtures/server-ecdsa.crt" + jwtECPrivKey = "../integration/fixtures/server-ecdsa.key.insecure" ) func TestJWTInfo(t *testing.T) { - opts := map[string]string{ - "pub-key": jwtPubKey, - "priv-key": jwtPrivKey, - "sign-method": "RS256", + optsMap := map[string]map[string]string{ + "RSA-priv": { + "priv-key": jwtRSAPrivKey, + "sign-method": "RS256", + "ttl": "1h", + }, + "RSA": { + "pub-key": jwtRSAPubKey, + "priv-key": jwtRSAPrivKey, + "sign-method": "RS256", + }, + "RSAPSS-priv": { + "priv-key": jwtRSAPrivKey, + "sign-method": "PS256", + }, + "RSAPSS": { + "pub-key": jwtRSAPubKey, + "priv-key": jwtRSAPrivKey, + "sign-method": "PS256", + }, + "ECDSA-priv": { + "priv-key": jwtECPrivKey, + "sign-method": "ES256", + }, + "ECDSA": { + "pub-key": jwtECPubKey, + "priv-key": jwtECPrivKey, + "sign-method": "ES256", + }, + "HMAC": { + "priv-key": jwtECPrivKey, // any file, raw bytes used as shared secret + "sign-method": "HS256", + }, } - jwt, err := newTokenProviderJWT(zap.NewExample(), opts) + + for k, opts := range optsMap { + t.Run(k, func(tt *testing.T) { + testJWTInfo(tt, opts) + }) + } +} + +func testJWTInfo(t *testing.T, opts map[string]string) { + lg := zap.NewNop() + jwt, err := newTokenProviderJWT(lg, opts) if err != nil { t.Fatal(err) } - token, aerr := jwt.assign(context.TODO(), "abc", 123) + + ctx := context.TODO() + + token, aerr := jwt.assign(ctx, "abc", 123) if aerr != nil { - t.Fatal(err) + t.Fatalf("%#v", aerr) } - ai, ok := jwt.info(context.TODO(), token, 123) + ai, ok := jwt.info(ctx, token, 123) if !ok { t.Fatalf("failed to authenticate with token %s", token) } if ai.Revision != 123 { t.Fatalf("expected revision 123, got %d", ai.Revision) } - ai, ok = jwt.info(context.TODO(), "aaa", 120) + ai, ok = jwt.info(ctx, "aaa", 120) if ok || ai != nil { t.Fatalf("expected aaa to fail to authenticate, got %+v", ai) } + + // test verify-only provider + if opts["pub-key"] != "" && opts["priv-key"] != "" { + t.Run("verify-only", func(t *testing.T) { + newOpts := make(map[string]string, len(opts)) + for k, v := range opts { + newOpts[k] = v + } + delete(newOpts, "priv-key") + verify, err := newTokenProviderJWT(lg, newOpts) + if err != nil { + t.Fatal(err) + } + + ai, ok := verify.info(ctx, token, 123) + if !ok { + t.Fatalf("failed to authenticate with token %s", token) + } + if ai.Revision != 123 { + t.Fatalf("expected revision 123, got %d", ai.Revision) + } + ai, ok = verify.info(ctx, "aaa", 120) + if ok || ai != nil { + t.Fatalf("expected aaa to fail to authenticate, got %+v", ai) + } + + _, aerr := verify.assign(ctx, "abc", 123) + if aerr != ErrVerifyOnly { + t.Fatalf("unexpected error when attempting to sign with public key: %v", aerr) + } + + }) + } } func TestJWTBad(t *testing.T) { - opts := map[string]string{ - "pub-key": jwtPubKey, - "priv-key": jwtPrivKey, - "sign-method": "RS256", - } - // private key instead of public key - opts["pub-key"] = jwtPrivKey - if _, err := newTokenProviderJWT(zap.NewExample(), opts); err == nil { - t.Fatalf("expected failure on missing public key") - } - opts["pub-key"] = jwtPubKey - // public key instead of private key - opts["priv-key"] = jwtPubKey - if _, err := newTokenProviderJWT(zap.NewExample(), opts); err == nil { - t.Fatalf("expected failure on missing public key") + var badCases = map[string]map[string]string{ + "no options": {}, + "invalid method": { + "sign-method": "invalid", + }, + "rsa no key": { + "sign-method": "RS256", + }, + "invalid ttl": { + "sign-method": "RS256", + "ttl": "forever", + }, + "rsa invalid public key": { + "sign-method": "RS256", + "pub-key": jwtRSAPrivKey, + "priv-key": jwtRSAPrivKey, + }, + "rsa invalid private key": { + "sign-method": "RS256", + "pub-key": jwtRSAPubKey, + "priv-key": jwtRSAPubKey, + }, + "hmac no key": { + "sign-method": "HS256", + }, + "hmac pub key": { + "sign-method": "HS256", + "pub-key": jwtRSAPubKey, + }, + "missing public key file": { + "sign-method": "HS256", + "pub-key": "missing-file", + }, + "missing private key file": { + "sign-method": "HS256", + "priv-key": "missing-file", + }, + "ecdsa no key": { + "sign-method": "ES256", + }, + "ecdsa invalid public key": { + "sign-method": "ES256", + "pub-key": jwtECPrivKey, + "priv-key": jwtECPrivKey, + }, + "ecdsa invalid private key": { + "sign-method": "ES256", + "pub-key": jwtECPubKey, + "priv-key": jwtECPubKey, + }, } - opts["priv-key"] = jwtPrivKey - // missing signing option - delete(opts, "sign-method") - if _, err := newTokenProviderJWT(zap.NewExample(), opts); err == nil { - t.Fatal("expected error on missing option") - } - opts["sign-method"] = "RS256" + lg := zap.NewNop() - // bad file for pubkey - opts["pub-key"] = "whatever" - if _, err := newTokenProviderJWT(zap.NewExample(), opts); err == nil { - t.Fatalf("expected failure on missing public key") + for k, v := range badCases { + t.Run(k, func(t *testing.T) { + _, err := newTokenProviderJWT(lg, v) + if err == nil { + t.Errorf("expected error for options %v", v) + } + }) } - opts["pub-key"] = jwtPubKey - - // bad file for private key - opts["priv-key"] = "whatever" - if _, err := newTokenProviderJWT(zap.NewExample(), opts); err == nil { - t.Fatalf("expeceted failure on missing private key") - } - opts["priv-key"] = jwtPrivKey } // testJWTOpts is useful for passing to NewTokenProvider which requires a string. func testJWTOpts() string { - return fmt.Sprintf("%s,pub-key=%s,priv-key=%s,sign-method=RS256", tokenTypeJWT, jwtPubKey, jwtPrivKey) + return fmt.Sprintf("%s,pub-key=%s,priv-key=%s,sign-method=RS256", tokenTypeJWT, jwtRSAPubKey, jwtRSAPrivKey) } diff --git a/auth/options.go b/auth/options.go new file mode 100644 index 000000000..f40b92de6 --- /dev/null +++ b/auth/options.go @@ -0,0 +1,192 @@ +// Copyright 2018 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package auth + +import ( + "crypto/ecdsa" + "crypto/rsa" + "fmt" + "io/ioutil" + "time" + + jwt "github.com/dgrijalva/jwt-go" +) + +const ( + optSignMethod = "sign-method" + optPublicKey = "pub-key" + optPrivateKey = "priv-key" + optTTL = "ttl" +) + +var knownOptions = map[string]bool{ + optSignMethod: true, + optPublicKey: true, + optPrivateKey: true, + optTTL: true, +} + +var ( + // DefaultTTL will be used when a 'ttl' is not specified + DefaultTTL = 5 * time.Minute +) + +type jwtOptions struct { + SignMethod jwt.SigningMethod + PublicKey []byte + PrivateKey []byte + TTL time.Duration +} + +// ParseWithDefaults will load options from the specified map or set defaults where appropriate +func (opts *jwtOptions) ParseWithDefaults(optMap map[string]string) error { + if opts.TTL == 0 && optMap[optTTL] == "" { + opts.TTL = DefaultTTL + } + + return opts.Parse(optMap) +} + +// Parse will load options from the specified map +func (opts *jwtOptions) Parse(optMap map[string]string) error { + var err error + if ttl := optMap[optTTL]; ttl != "" { + opts.TTL, err = time.ParseDuration(ttl) + if err != nil { + return err + } + } + + if file := optMap[optPublicKey]; file != "" { + opts.PublicKey, err = ioutil.ReadFile(file) + if err != nil { + return err + } + } + + if file := optMap[optPrivateKey]; file != "" { + opts.PrivateKey, err = ioutil.ReadFile(file) + if err != nil { + return err + } + } + + // signing method is a required field + method := optMap[optSignMethod] + opts.SignMethod = jwt.GetSigningMethod(method) + if opts.SignMethod == nil { + return ErrInvalidAuthMethod + } + + return nil +} + +// Key will parse and return the appropriately typed key for the selected signature method +func (opts *jwtOptions) Key() (interface{}, error) { + switch opts.SignMethod.(type) { + case *jwt.SigningMethodRSA, *jwt.SigningMethodRSAPSS: + return opts.rsaKey() + case *jwt.SigningMethodECDSA: + return opts.ecKey() + case *jwt.SigningMethodHMAC: + return opts.hmacKey() + default: + return nil, fmt.Errorf("unsupported signing method: %T", opts.SignMethod) + } +} + +func (opts *jwtOptions) hmacKey() (interface{}, error) { + if len(opts.PrivateKey) == 0 { + return nil, ErrMissingKey + } + return opts.PrivateKey, nil +} + +func (opts *jwtOptions) rsaKey() (interface{}, error) { + var ( + priv *rsa.PrivateKey + pub *rsa.PublicKey + err error + ) + + if len(opts.PrivateKey) > 0 { + priv, err = jwt.ParseRSAPrivateKeyFromPEM(opts.PrivateKey) + if err != nil { + return nil, err + } + } + + if len(opts.PublicKey) > 0 { + pub, err = jwt.ParseRSAPublicKeyFromPEM(opts.PublicKey) + if err != nil { + return nil, err + } + } + + if priv == nil { + if pub == nil { + // Neither key given + return nil, ErrMissingKey + } + // Public key only, can verify tokens + return pub, nil + } + + // both keys provided, make sure they match + if pub != nil && pub.E != priv.E && pub.N.Cmp(priv.N) != 0 { + return nil, ErrKeyMismatch + } + + return priv, nil +} + +func (opts *jwtOptions) ecKey() (interface{}, error) { + var ( + priv *ecdsa.PrivateKey + pub *ecdsa.PublicKey + err error + ) + + if len(opts.PrivateKey) > 0 { + priv, err = jwt.ParseECPrivateKeyFromPEM(opts.PrivateKey) + if err != nil { + return nil, err + } + } + + if len(opts.PublicKey) > 0 { + pub, err = jwt.ParseECPublicKeyFromPEM(opts.PublicKey) + if err != nil { + return nil, err + } + } + + if priv == nil { + if pub == nil { + // Neither key given + return nil, ErrMissingKey + } + // Public key only, can verify tokens + return pub, nil + } + + // both keys provided, make sure they match + if pub != nil && pub.Curve != priv.Curve && + pub.X.Cmp(priv.X) != 0 && pub.Y.Cmp(priv.Y) != 0 { + return nil, ErrKeyMismatch + } + + return priv, nil +} diff --git a/auth/store.go b/auth/store.go index 44df87873..eda884f9f 100644 --- a/auth/store.go +++ b/auth/store.go @@ -66,6 +66,10 @@ var ( ErrInvalidAuthToken = errors.New("auth: invalid auth token") ErrInvalidAuthOpts = errors.New("auth: invalid auth options") ErrInvalidAuthMgmt = errors.New("auth: invalid auth management") + ErrInvalidAuthMethod = errors.New("auth: invalid auth signature method") + ErrMissingKey = errors.New("auth: missing key data") + ErrKeyMismatch = errors.New("auth: public and private keys don't match") + ErrVerifyOnly = errors.New("auth: token signing attempted with verify-only key") ) const ( diff --git a/integration/fixtures/gencerts.sh b/integration/fixtures/gencerts.sh index 67d610b9e..068106a01 100755 --- a/integration/fixtures/gencerts.sh +++ b/integration/fixtures/gencerts.sh @@ -25,6 +25,15 @@ cfssl gencert \ mv server.pem server.crt mv server-key.pem server.key.insecure +# generate DNS: localhost, IP: 127.0.0.1, CN: example.com certificates (ECDSA) +cfssl gencert \ + --ca ./ca.crt \ + --ca-key ./ca-key.pem \ + --config ./gencert.json \ + ./server-ca-csr-ecdsa.json | cfssljson --bare ./server-ecdsa +mv server-ecdsa.pem server-ecdsa.crt +mv server-ecdsa-key.pem server-ecdsa.key.insecure + # generate IP: 127.0.0.1, CN: example.com certificates cfssl gencert \ --ca ./ca.crt \ diff --git a/integration/fixtures/server-ca-csr-ecdsa.json b/integration/fixtures/server-ca-csr-ecdsa.json new file mode 100644 index 000000000..c9c71f00a --- /dev/null +++ b/integration/fixtures/server-ca-csr-ecdsa.json @@ -0,0 +1,20 @@ +{ + "key": { + "algo": "ecdsa", + "size": 256 + }, + "names": [ + { + "O": "etcd", + "OU": "etcd Security", + "L": "San Francisco", + "ST": "California", + "C": "USA" + } + ], + "CN": "example.com", + "hosts": [ + "127.0.0.1", + "localhost" + ] +} diff --git a/integration/fixtures/server-ecdsa.crt b/integration/fixtures/server-ecdsa.crt new file mode 100644 index 000000000..bf1af2456 --- /dev/null +++ b/integration/fixtures/server-ecdsa.crt @@ -0,0 +1,20 @@ +-----BEGIN CERTIFICATE----- +MIIDRzCCAi+gAwIBAgIUK5XUt/HZQ3IpLbDFI1EIU4jiAxIwDQYJKoZIhvcNAQEL +BQAwbzEMMAoGA1UEBhMDVVNBMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQH +Ew1TYW4gRnJhbmNpc2NvMQ0wCwYDVQQKEwRldGNkMRYwFAYDVQQLEw1ldGNkIFNl +Y3VyaXR5MQswCQYDVQQDEwJjYTAeFw0xODA2MTkxNjIwMDBaFw0yODA2MTYxNjIw +MDBaMHgxDDAKBgNVBAYTA1VTQTETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UE +BxMNU2FuIEZyYW5jaXNjbzENMAsGA1UEChMEZXRjZDEWMBQGA1UECxMNZXRjZCBT +ZWN1cml0eTEUMBIGA1UEAxMLZXhhbXBsZS5jb20wWTATBgcqhkjOPQIBBggqhkjO +PQMBBwNCAARDiiEQNXiH6eYz5Tws31IeU/OZ0sf7gHIJNvbST/cpXtjo4oFGcu0t +TY4+FAMk0ku07s/kX9r55TgKr1VljG31o4GcMIGZMA4GA1UdDwEB/wQEAwIFoDAd +BgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUHAwIwDAYDVR0TAQH/BAIwADAdBgNV +HQ4EFgQUzo0YV8GX/aN/WRsyygA8QVZaMQQwHwYDVR0jBBgwFoAURt/EV2KWh7I1 +N8NXXowk6J1QtvgwGgYDVR0RBBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3 +DQEBCwUAA4IBAQCbUYjMwKuHQjNEFTvx4jQB/LZTr1Mn53C1etR0qLd50v9TXVzb +FeZoo0g4mXln0BrLVMLatw0CTlGBCw+yJQ+5iJB5z3bKEl4ADwzRFDxwCMXXG8lV +wQOS/eaTBcAkzf/BWITLB1mIIp3kKZwXM6IW53yDkPFDpnExPY+ycoNp58U1JxOJ +ySM3/zyr0Ac8qCNqAakT2WacJ+AdB7pgoupbVF2WKT6qYbF1yvYY8x/zr8ePHznS +fvuO+80wYPbyw13s6rpNv4d0L1k7GDcXVs3lHC47hSNn7OBhf4Xkku101MtP3DhO +gFqW7p7vigK20tZKy4NYF6+nW3xJmOlw3gJF +-----END CERTIFICATE----- diff --git a/integration/fixtures/server-ecdsa.key.insecure b/integration/fixtures/server-ecdsa.key.insecure new file mode 100644 index 000000000..753426863 --- /dev/null +++ b/integration/fixtures/server-ecdsa.key.insecure @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIIZcM3NsBY+ZjW2t+AqdvW1lqYhD5l4zT6xr/eBIoh1aoAoGCCqGSM49 +AwEHoUQDQgAEQ4ohEDV4h+nmM+U8LN9SHlPzmdLH+4ByCTb20k/3KV7Y6OKBRnLt +LU2OPhQDJNJLtO7P5F/a+eU4Cq9VZYxt9Q== +-----END EC PRIVATE KEY-----