From 38b93b3525d10a499b9a868266b9d01d281286ed Mon Sep 17 00:00:00 2001 From: Ilya Lobkov Date: Thu, 27 Feb 2020 13:59:25 +0100 Subject: [PATCH 1/5] Add functions for token generation and verification Signed-off-by: Ilya Lobkov --- go.mod | 1 + go.sum | 1 + pkg/tools/security/provider.go | 1 + pkg/tools/security/spire.go | 8 ++ pkg/tools/security/token.go | 44 ++++++ pkg/tools/security/token_test.go | 226 +++++++++++++++++++++++++++++++ 6 files changed, 281 insertions(+) create mode 100644 pkg/tools/security/token.go create mode 100644 pkg/tools/security/token_test.go diff --git a/go.mod b/go.mod index d0c9738ea..b85aa705f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.13 require ( github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd // indirect + github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/golang/protobuf v1.3.3 github.com/google/uuid v1.1.1 github.com/grpc-ecosystem/grpc-opentracing v0.0.0-20180507213350-8e809c8a8645 diff --git a/go.sum b/go.sum index 8b33191cc..b6991a428 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,7 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= diff --git a/pkg/tools/security/provider.go b/pkg/tools/security/provider.go index 98479ca2b..4aa1b9d77 100644 --- a/pkg/tools/security/provider.go +++ b/pkg/tools/security/provider.go @@ -24,4 +24,5 @@ import ( // Provider - interface for tls.Config provider type Provider interface { GetTLSConfig(ctx context.Context) (*tls.Config, error) + GetCertificate(ctx context.Context) (*tls.Certificate, error) } diff --git a/pkg/tools/security/spire.go b/pkg/tools/security/spire.go index 30ef84354..68b1f89b9 100644 --- a/pkg/tools/security/spire.go +++ b/pkg/tools/security/spire.go @@ -75,3 +75,11 @@ func NewSpireProvider(addr string) (Provider, error) { func (p *spireProvider) GetTLSConfig(ctx context.Context) (*tls.Config, error) { return p.peer.GetConfig(ctx, spiffe.ExpectAnyPeer()) } + +func (p *spireProvider) GetCertificate(ctx context.Context) (*tls.Certificate, error) { + if err := p.peer.WaitUntilReady(ctx); err != nil { + return nil, err + } + + return p.peer.GetCertificate() +} diff --git a/pkg/tools/security/token.go b/pkg/tools/security/token.go new file mode 100644 index 000000000..1a051cc34 --- /dev/null +++ b/pkg/tools/security/token.go @@ -0,0 +1,44 @@ +// Copyright (c) 2020 Doc.ai and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// 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 security + +import ( + "context" + "crypto/x509" + "github.com/dgrijalva/jwt-go" + "time" +) + +func GenerateToken(ctx context.Context, p Provider, expiresAt time.Duration) (string, error) { + crt, err := p.GetCertificate(ctx) + if err != nil { + return "", err + } + + claims := jwt.StandardClaims{ + ExpiresAt: time.Now().Add(expiresAt).Unix(), + } + + return jwt.NewWithClaims(jwt.SigningMethodES256, claims).SignedString(crt.PrivateKey) +} + +func VerifyToken(token string, x509crt *x509.Certificate) error { + _, err := new(jwt.Parser).Parse(token, func(token *jwt.Token) (interface{}, error) { + return x509crt.PublicKey, nil + }) + return err +} diff --git a/pkg/tools/security/token_test.go b/pkg/tools/security/token_test.go new file mode 100644 index 000000000..35d01e67f --- /dev/null +++ b/pkg/tools/security/token_test.go @@ -0,0 +1,226 @@ +// Copyright (c) 2020 Doc.ai and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// 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 security_test + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/pem" + "github.com/dgrijalva/jwt-go" + "github.com/networkservicemesh/sdk/pkg/tools/security" + "github.com/stretchr/testify/require" + "math/big" + "testing" + "time" +) + +const ( + spiffeID = "spiffe://test.com/workload" + nameTypeURI = 6 +) + +var ( + testCA tls.Certificate + testTLSCertificate tls.Certificate +) + +func init() { + var err error + testCA, err = generateCA() + if err != nil { + panic(err) + } + + testTLSCertificate, err = generateKeyPair(spiffeID, "test.com", &testCA) + if err != nil { + panic(err) + } +} + +type testProvider struct { + GetCertificateFunc func(ctx context.Context) (*tls.Certificate, error) +} + +func (t *testProvider) GetTLSConfig(ctx context.Context) (*tls.Config, error) { + panic("implement me") +} + +func (t *testProvider) GetCertificate(ctx context.Context) (*tls.Certificate, error) { + return t.GetCertificateFunc(ctx) +} + +func generateCA() (tls.Certificate, error) { + ca := &x509.Certificate{ + SerialNumber: big.NewInt(1653), + Subject: pkix.Name{ + Organization: []string{"ORGANIZATION_NAME"}, + Country: []string{"COUNTRY_CODE"}, + Province: []string{"PROVINCE"}, + Locality: []string{"CITY"}, + StreetAddress: []string{"ADDRESS"}, + PostalCode: []string{"POSTAL_CODE"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + SignatureAlgorithm: x509.ECDSAWithSHA256, + BasicConstraintsValid: true, + } + + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return tls.Certificate{}, err + } + pub := &priv.PublicKey + + certBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv) + if err != nil { + return tls.Certificate{}, err + } + + certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) + keyBytes, err := x509.MarshalECPrivateKey(priv) + if err != nil { + return tls.Certificate{}, err + } + keyPem := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}) + return tls.X509KeyPair(certPem, keyPem) +} + +func marshalSAN(spiffeID string) ([]byte, error) { + return asn1.Marshal([]asn1.RawValue{{Tag: nameTypeURI, Class: 2, Bytes: []byte(spiffeID)}}) +} + +func generateKeyPair(spiffeID, domain string, caTLS *tls.Certificate) (tls.Certificate, error) { + san, err := marshalSAN(spiffeID) + if err != nil { + return tls.Certificate{}, nil + } + + oidSanExtension := []int{2, 5, 29, 17} + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1658), + Subject: pkix.Name{ + Organization: []string{"ORGANIZATION_NAME"}, + Country: []string{"COUNTRY_CODE"}, + Province: []string{"PROVINCE"}, + Locality: []string{"CITY"}, + StreetAddress: []string{"ADDRESS"}, + PostalCode: []string{"POSTAL_CODE"}, + CommonName: domain, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + ExtraExtensions: []pkix.Extension{ + { + Id: oidSanExtension, + Value: san, + }, + }, + SignatureAlgorithm: x509.ECDSAWithSHA256, + KeyUsage: x509.KeyUsageDigitalSignature, + } + + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return tls.Certificate{}, nil + } + pub := &priv.PublicKey + + ca, err := x509.ParseCertificate(caTLS.Certificate[0]) + if err != nil { + return tls.Certificate{}, nil + } + + certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, pub, caTLS.PrivateKey) + if err != nil { + return tls.Certificate{}, nil + } + + certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) + keyBytes, err := x509.MarshalECPrivateKey(priv) + if err != nil { + return tls.Certificate{}, err + } + keyPem := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes}) + return tls.X509KeyPair(certPem, keyPem) +} + +func TestGenerateToken(t *testing.T) { + p := &testProvider{ + GetCertificateFunc: func(ctx context.Context) (certificate *tls.Certificate, e error) { + return &testTLSCertificate, nil + }, + } + + token, err := security.GenerateToken(context.Background(), p, 0) + require.Nil(t, err) + + _, err = new(jwt.Parser).Parse(token, func(token *jwt.Token) (interface{}, error) { + x509crt, err := x509.ParseCertificate(testTLSCertificate.Certificate[0]) + require.Nil(t, err) + return x509crt.PublicKey, nil + }) + require.Nil(t, err) +} + +func TestGenerateToken_Expire(t *testing.T) { + p := &testProvider{ + GetCertificateFunc: func(ctx context.Context) (certificate *tls.Certificate, e error) { + return &testTLSCertificate, nil + }, + } + + token, err := security.GenerateToken(context.Background(), p, 3*time.Second) + require.Nil(t, err) + + <-time.After(5 * time.Second) + + _, err = new(jwt.Parser).Parse(token, func(token *jwt.Token) (interface{}, error) { + x509crt, err := x509.ParseCertificate(testTLSCertificate.Certificate[0]) + require.Nil(t, err) + return x509crt.PublicKey, nil + }) + require.NotNil(t, err) +} + +func TestVerifyToken(t *testing.T) { + token, err := jwt.New(jwt.SigningMethodES256).SignedString(testTLSCertificate.PrivateKey) + require.Nil(t, err) + + x509crt, err := x509.ParseCertificate(testTLSCertificate.Certificate[0]) + require.Nil(t, err) + + err = security.VerifyToken(token, x509crt) + require.Nil(t, err) + + invalidX509crt, err := x509.ParseCertificate(testCA.Certificate[0]) + require.Nil(t, err) + + err = security.VerifyToken(token, invalidX509crt) + require.NotNil(t, err) +} From 1650e09fc36977ad94e568efe50da8ed7c903347 Mon Sep 17 00:00:00 2001 From: Ilya Lobkov Date: Thu, 27 Feb 2020 14:07:49 +0100 Subject: [PATCH 2/5] Add comments Signed-off-by: Ilya Lobkov --- pkg/tools/security/token.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/tools/security/token.go b/pkg/tools/security/token.go index 1a051cc34..707e754c3 100644 --- a/pkg/tools/security/token.go +++ b/pkg/tools/security/token.go @@ -23,6 +23,7 @@ import ( "time" ) +// GenerateToken generates JWT token based on tls.Certificate from security.Provider func GenerateToken(ctx context.Context, p Provider, expiresAt time.Duration) (string, error) { crt, err := p.GetCertificate(ctx) if err != nil { @@ -36,6 +37,7 @@ func GenerateToken(ctx context.Context, p Provider, expiresAt time.Duration) (st return jwt.NewWithClaims(jwt.SigningMethodES256, claims).SignedString(crt.PrivateKey) } +// VerifyToken verifies JWT 'token' using x509.Certificate func VerifyToken(token string, x509crt *x509.Certificate) error { _, err := new(jwt.Parser).Parse(token, func(token *jwt.Token) (interface{}, error) { return x509crt.PublicKey, nil From 676326264ff99c452008416f5f0c11fdac733959 Mon Sep 17 00:00:00 2001 From: Ilya Lobkov Date: Thu, 27 Feb 2020 14:21:57 +0100 Subject: [PATCH 3/5] go lint Signed-off-by: Ilya Lobkov --- .golangci.yml | 3 +++ pkg/tools/security/token.go | 3 ++- pkg/tools/security/token_test.go | 18 +++++++++++------- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index c4c08385c..a994c7183 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -201,3 +201,6 @@ issues: linters: - dupl text: "lines are duplicate of" + - path: pkg/tools/security/token_test.go + linters: + - gochecknoinits diff --git a/pkg/tools/security/token.go b/pkg/tools/security/token.go index 707e754c3..f0e9c4a33 100644 --- a/pkg/tools/security/token.go +++ b/pkg/tools/security/token.go @@ -19,8 +19,9 @@ package security import ( "context" "crypto/x509" - "github.com/dgrijalva/jwt-go" "time" + + "github.com/dgrijalva/jwt-go" ) // GenerateToken generates JWT token based on tls.Certificate from security.Provider diff --git a/pkg/tools/security/token_test.go b/pkg/tools/security/token_test.go index 35d01e67f..cee949831 100644 --- a/pkg/tools/security/token_test.go +++ b/pkg/tools/security/token_test.go @@ -26,12 +26,14 @@ import ( "crypto/x509/pkix" "encoding/asn1" "encoding/pem" - "github.com/dgrijalva/jwt-go" - "github.com/networkservicemesh/sdk/pkg/tools/security" - "github.com/stretchr/testify/require" "math/big" "testing" "time" + + "github.com/dgrijalva/jwt-go" + "github.com/stretchr/testify/require" + + "github.com/networkservicemesh/sdk/pkg/tools/security" ) const ( @@ -180,9 +182,10 @@ func TestGenerateToken(t *testing.T) { token, err := security.GenerateToken(context.Background(), p, 0) require.Nil(t, err) + x509crt, err := x509.ParseCertificate(testTLSCertificate.Certificate[0]) + require.Nil(t, err) + _, err = new(jwt.Parser).Parse(token, func(token *jwt.Token) (interface{}, error) { - x509crt, err := x509.ParseCertificate(testTLSCertificate.Certificate[0]) - require.Nil(t, err) return x509crt.PublicKey, nil }) require.Nil(t, err) @@ -200,9 +203,10 @@ func TestGenerateToken_Expire(t *testing.T) { <-time.After(5 * time.Second) + x509crt, err := x509.ParseCertificate(testTLSCertificate.Certificate[0]) + require.Nil(t, err) + _, err = new(jwt.Parser).Parse(token, func(token *jwt.Token) (interface{}, error) { - x509crt, err := x509.ParseCertificate(testTLSCertificate.Certificate[0]) - require.Nil(t, err) return x509crt.PublicKey, nil }) require.NotNil(t, err) From 94f7e22e9ced1d016a290bcf3360b3017dd27f7d Mon Sep 17 00:00:00 2001 From: Ilya Lobkov Date: Sat, 29 Feb 2020 18:50:49 +0100 Subject: [PATCH 4/5] Get rid of 'init', add suites Signed-off-by: Ilya Lobkov --- .golangci.yml | 3 - pkg/tools/security/test/provider.go | 35 ++++++ pkg/tools/security/test/utils.go | 135 +++++++++++++++++++++ pkg/tools/security/token_test.go | 176 ++++++---------------------- 4 files changed, 206 insertions(+), 143 deletions(-) create mode 100644 pkg/tools/security/test/provider.go create mode 100644 pkg/tools/security/test/utils.go diff --git a/.golangci.yml b/.golangci.yml index a994c7183..c4c08385c 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -201,6 +201,3 @@ issues: linters: - dupl text: "lines are duplicate of" - - path: pkg/tools/security/token_test.go - linters: - - gochecknoinits diff --git a/pkg/tools/security/test/provider.go b/pkg/tools/security/test/provider.go new file mode 100644 index 000000000..bd31d6c89 --- /dev/null +++ b/pkg/tools/security/test/provider.go @@ -0,0 +1,35 @@ +// Copyright (c) 2020 Doc.ai and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// 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 securitytest + +import ( + "context" + "crypto/tls" +) + +type Provider struct { + GetTLSConfigFunc func(ctx context.Context) (*tls.Config, error) + GetCertificateFunc func(ctx context.Context) (*tls.Certificate, error) +} + +func (t *Provider) GetTLSConfig(ctx context.Context) (*tls.Config, error) { + return t.GetTLSConfigFunc(ctx) +} + +func (t *Provider) GetCertificate(ctx context.Context) (*tls.Certificate, error) { + return t.GetCertificateFunc(ctx) +} diff --git a/pkg/tools/security/test/utils.go b/pkg/tools/security/test/utils.go new file mode 100644 index 000000000..fa9b94d60 --- /dev/null +++ b/pkg/tools/security/test/utils.go @@ -0,0 +1,135 @@ +// Copyright (c) 2020 Doc.ai and/or its affiliates. +// +// SPDX-License-Identifier: Apache-2.0 +// +// 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 securitytest + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/tls" + "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" + "encoding/pem" + "math/big" + "time" +) + +const ( + nameTypeURI = 6 +) + +func GenerateCA() (tls.Certificate, error) { + ca := &x509.Certificate{ + SerialNumber: big.NewInt(1653), + Subject: pkix.Name{ + Organization: []string{"ORGANIZATION_NAME"}, + Country: []string{"COUNTRY_CODE"}, + Province: []string{"PROVINCE"}, + Locality: []string{"CITY"}, + StreetAddress: []string{"ADDRESS"}, + PostalCode: []string{"POSTAL_CODE"}, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + IsCA: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + SignatureAlgorithm: x509.ECDSAWithSHA256, + BasicConstraintsValid: true, + } + + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return tls.Certificate{}, err + } + pub := &priv.PublicKey + + certBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv) + if err != nil { + return tls.Certificate{}, err + } + + certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) + keyBytes, err := x509.MarshalECPrivateKey(priv) + if err != nil { + return tls.Certificate{}, err + } + keyPem := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}) + return tls.X509KeyPair(certPem, keyPem) +} + +func marshalSAN(spiffeID string) ([]byte, error) { + return asn1.Marshal([]asn1.RawValue{{Tag: nameTypeURI, Class: 2, Bytes: []byte(spiffeID)}}) +} + +func GenerateKeyPair(spiffeID, domain string, caTLS *tls.Certificate) (tls.Certificate, error) { + san, err := marshalSAN(spiffeID) + if err != nil { + return tls.Certificate{}, nil + } + + oidSanExtension := []int{2, 5, 29, 17} + cert := &x509.Certificate{ + SerialNumber: big.NewInt(1658), + Subject: pkix.Name{ + Organization: []string{"ORGANIZATION_NAME"}, + Country: []string{"COUNTRY_CODE"}, + Province: []string{"PROVINCE"}, + Locality: []string{"CITY"}, + StreetAddress: []string{"ADDRESS"}, + PostalCode: []string{"POSTAL_CODE"}, + CommonName: domain, + }, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + SubjectKeyId: []byte{1, 2, 3, 4, 6}, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, + ExtraExtensions: []pkix.Extension{ + { + Id: oidSanExtension, + Value: san, + }, + }, + SignatureAlgorithm: x509.ECDSAWithSHA256, + KeyUsage: x509.KeyUsageDigitalSignature, + } + + priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return tls.Certificate{}, nil + } + pub := &priv.PublicKey + + ca, err := x509.ParseCertificate(caTLS.Certificate[0]) + if err != nil { + return tls.Certificate{}, nil + } + + certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, pub, caTLS.PrivateKey) + if err != nil { + return tls.Certificate{}, nil + } + + certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) + keyBytes, err := x509.MarshalECPrivateKey(priv) + if err != nil { + return tls.Certificate{}, err + } + keyPem := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes}) + return tls.X509KeyPair(certPem, keyPem) +} diff --git a/pkg/tools/security/token_test.go b/pkg/tools/security/token_test.go index cee949831..8fd55e357 100644 --- a/pkg/tools/security/token_test.go +++ b/pkg/tools/security/token_test.go @@ -18,47 +18,44 @@ package security_test import ( "context" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" "crypto/tls" "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" - "encoding/pem" - "math/big" + securitytest "github.com/networkservicemesh/sdk/pkg/tools/security/test" + "github.com/stretchr/testify/suite" "testing" "time" "github.com/dgrijalva/jwt-go" - "github.com/stretchr/testify/require" - "github.com/networkservicemesh/sdk/pkg/tools/security" ) const ( - spiffeID = "spiffe://test.com/workload" - nameTypeURI = 6 + spiffeID = "spiffe://test.com/workload" ) -var ( - testCA tls.Certificate - testTLSCertificate tls.Certificate -) +type TokenTestSuite struct { + suite.Suite + TestCA tls.Certificate + TestTLSCertificate tls.Certificate +} -func init() { +func (suite *TokenTestSuite) SetupSuite() { var err error - testCA, err = generateCA() + suite.TestCA, err = securitytest.GenerateCA() if err != nil { panic(err) } - testTLSCertificate, err = generateKeyPair(spiffeID, "test.com", &testCA) + suite.TestTLSCertificate, err = securitytest.GenerateKeyPair(spiffeID, "test.com", &suite.TestCA) if err != nil { panic(err) } } +func TestTokenTestSuite(t *testing.T) { + suite.Run(t, new(TokenTestSuite)) +} + type testProvider struct { GetCertificateFunc func(ctx context.Context) (*tls.Certificate, error) } @@ -71,160 +68,59 @@ func (t *testProvider) GetCertificate(ctx context.Context) (*tls.Certificate, er return t.GetCertificateFunc(ctx) } -func generateCA() (tls.Certificate, error) { - ca := &x509.Certificate{ - SerialNumber: big.NewInt(1653), - Subject: pkix.Name{ - Organization: []string{"ORGANIZATION_NAME"}, - Country: []string{"COUNTRY_CODE"}, - Province: []string{"PROVINCE"}, - Locality: []string{"CITY"}, - StreetAddress: []string{"ADDRESS"}, - PostalCode: []string{"POSTAL_CODE"}, - }, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(10, 0, 0), - IsCA: true, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - SignatureAlgorithm: x509.ECDSAWithSHA256, - BasicConstraintsValid: true, - } - - priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return tls.Certificate{}, err - } - pub := &priv.PublicKey - - certBytes, err := x509.CreateCertificate(rand.Reader, ca, ca, pub, priv) - if err != nil { - return tls.Certificate{}, err - } - - certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) - keyBytes, err := x509.MarshalECPrivateKey(priv) - if err != nil { - return tls.Certificate{}, err - } - keyPem := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: keyBytes}) - return tls.X509KeyPair(certPem, keyPem) -} - -func marshalSAN(spiffeID string) ([]byte, error) { - return asn1.Marshal([]asn1.RawValue{{Tag: nameTypeURI, Class: 2, Bytes: []byte(spiffeID)}}) -} - -func generateKeyPair(spiffeID, domain string, caTLS *tls.Certificate) (tls.Certificate, error) { - san, err := marshalSAN(spiffeID) - if err != nil { - return tls.Certificate{}, nil - } - - oidSanExtension := []int{2, 5, 29, 17} - cert := &x509.Certificate{ - SerialNumber: big.NewInt(1658), - Subject: pkix.Name{ - Organization: []string{"ORGANIZATION_NAME"}, - Country: []string{"COUNTRY_CODE"}, - Province: []string{"PROVINCE"}, - Locality: []string{"CITY"}, - StreetAddress: []string{"ADDRESS"}, - PostalCode: []string{"POSTAL_CODE"}, - CommonName: domain, - }, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(10, 0, 0), - SubjectKeyId: []byte{1, 2, 3, 4, 6}, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth}, - ExtraExtensions: []pkix.Extension{ - { - Id: oidSanExtension, - Value: san, - }, - }, - SignatureAlgorithm: x509.ECDSAWithSHA256, - KeyUsage: x509.KeyUsageDigitalSignature, - } - - priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return tls.Certificate{}, nil - } - pub := &priv.PublicKey - - ca, err := x509.ParseCertificate(caTLS.Certificate[0]) - if err != nil { - return tls.Certificate{}, nil - } - - certBytes, err := x509.CreateCertificate(rand.Reader, cert, ca, pub, caTLS.PrivateKey) - if err != nil { - return tls.Certificate{}, nil - } - - certPem := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certBytes}) - keyBytes, err := x509.MarshalECPrivateKey(priv) - if err != nil { - return tls.Certificate{}, err - } - keyPem := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: keyBytes}) - return tls.X509KeyPair(certPem, keyPem) -} - -func TestGenerateToken(t *testing.T) { +func (suite *TokenTestSuite) TestGenerateToken() { p := &testProvider{ GetCertificateFunc: func(ctx context.Context) (certificate *tls.Certificate, e error) { - return &testTLSCertificate, nil + return &suite.TestTLSCertificate, nil }, } token, err := security.GenerateToken(context.Background(), p, 0) - require.Nil(t, err) + suite.Nil(err) - x509crt, err := x509.ParseCertificate(testTLSCertificate.Certificate[0]) - require.Nil(t, err) + x509crt, err := x509.ParseCertificate(suite.TestTLSCertificate.Certificate[0]) + suite.Nil(err) _, err = new(jwt.Parser).Parse(token, func(token *jwt.Token) (interface{}, error) { return x509crt.PublicKey, nil }) - require.Nil(t, err) + suite.Nil(err) } -func TestGenerateToken_Expire(t *testing.T) { +func (suite *TokenTestSuite) TestGenerateToken_Expire() { p := &testProvider{ GetCertificateFunc: func(ctx context.Context) (certificate *tls.Certificate, e error) { - return &testTLSCertificate, nil + return &suite.TestTLSCertificate, nil }, } token, err := security.GenerateToken(context.Background(), p, 3*time.Second) - require.Nil(t, err) + suite.Nil(err) <-time.After(5 * time.Second) - x509crt, err := x509.ParseCertificate(testTLSCertificate.Certificate[0]) - require.Nil(t, err) + x509crt, err := x509.ParseCertificate(suite.TestTLSCertificate.Certificate[0]) + suite.Nil(err) _, err = new(jwt.Parser).Parse(token, func(token *jwt.Token) (interface{}, error) { return x509crt.PublicKey, nil }) - require.NotNil(t, err) + suite.NotNil(err) } -func TestVerifyToken(t *testing.T) { - token, err := jwt.New(jwt.SigningMethodES256).SignedString(testTLSCertificate.PrivateKey) - require.Nil(t, err) +func (suite *TokenTestSuite) TestVerifyToken() { + token, err := jwt.New(jwt.SigningMethodES256).SignedString(suite.TestTLSCertificate.PrivateKey) + suite.Nil(err) - x509crt, err := x509.ParseCertificate(testTLSCertificate.Certificate[0]) - require.Nil(t, err) + x509crt, err := x509.ParseCertificate(suite.TestTLSCertificate.Certificate[0]) + suite.Nil(err) err = security.VerifyToken(token, x509crt) - require.Nil(t, err) + suite.Nil(err) - invalidX509crt, err := x509.ParseCertificate(testCA.Certificate[0]) - require.Nil(t, err) + invalidX509crt, err := x509.ParseCertificate(suite.TestCA.Certificate[0]) + suite.Nil(err) err = security.VerifyToken(token, invalidX509crt) - require.NotNil(t, err) + suite.NotNil(err) } From e45484aa4e62d8df7737b4a3bf94990e3fd6b658 Mon Sep 17 00:00:00 2001 From: Ilya Lobkov Date: Sat, 29 Feb 2020 18:59:33 +0100 Subject: [PATCH 5/5] go lint Signed-off-by: Ilya Lobkov --- pkg/tools/security/test/provider.go | 3 ++ pkg/tools/security/test/utils.go | 3 ++ pkg/tools/security/token_test.go | 55 +++++++++++++++-------------- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/pkg/tools/security/test/provider.go b/pkg/tools/security/test/provider.go index bd31d6c89..3f1197d31 100644 --- a/pkg/tools/security/test/provider.go +++ b/pkg/tools/security/test/provider.go @@ -21,15 +21,18 @@ import ( "crypto/tls" ) +// Provider is a struct for testing purposes allows to pass method implementation from tests type Provider struct { GetTLSConfigFunc func(ctx context.Context) (*tls.Config, error) GetCertificateFunc func(ctx context.Context) (*tls.Certificate, error) } +// GetTLSConfig implements security.Provider interface func (t *Provider) GetTLSConfig(ctx context.Context) (*tls.Config, error) { return t.GetTLSConfigFunc(ctx) } +// GetCertificate implements security.Provider interface func (t *Provider) GetCertificate(ctx context.Context) (*tls.Certificate, error) { return t.GetCertificateFunc(ctx) } diff --git a/pkg/tools/security/test/utils.go b/pkg/tools/security/test/utils.go index fa9b94d60..9fee6cee4 100644 --- a/pkg/tools/security/test/utils.go +++ b/pkg/tools/security/test/utils.go @@ -14,6 +14,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +// Package securitytest contains auxiliary methods for testing security package securitytest import ( @@ -33,6 +34,7 @@ const ( nameTypeURI = 6 ) +// GenerateCA generates Certificate Authority func GenerateCA() (tls.Certificate, error) { ca := &x509.Certificate{ SerialNumber: big.NewInt(1653), @@ -77,6 +79,7 @@ func marshalSAN(spiffeID string) ([]byte, error) { return asn1.Marshal([]asn1.RawValue{{Tag: nameTypeURI, Class: 2, Bytes: []byte(spiffeID)}}) } +// GenerateKeyPair generates tls.Certificate for the passed 'spiffeID' and signed by 'caTLS' func GenerateKeyPair(spiffeID, domain string, caTLS *tls.Certificate) (tls.Certificate, error) { san, err := marshalSAN(spiffeID) if err != nil { diff --git a/pkg/tools/security/token_test.go b/pkg/tools/security/token_test.go index 8fd55e357..cba0ff647 100644 --- a/pkg/tools/security/token_test.go +++ b/pkg/tools/security/token_test.go @@ -20,12 +20,15 @@ import ( "context" "crypto/tls" "crypto/x509" - securitytest "github.com/networkservicemesh/sdk/pkg/tools/security/test" - "github.com/stretchr/testify/suite" "testing" "time" + "github.com/stretchr/testify/suite" + + securitytest "github.com/networkservicemesh/sdk/pkg/tools/security/test" + "github.com/dgrijalva/jwt-go" + "github.com/networkservicemesh/sdk/pkg/tools/security" ) @@ -39,14 +42,14 @@ type TokenTestSuite struct { TestTLSCertificate tls.Certificate } -func (suite *TokenTestSuite) SetupSuite() { +func (s *TokenTestSuite) SetupSuite() { var err error - suite.TestCA, err = securitytest.GenerateCA() + s.TestCA, err = securitytest.GenerateCA() if err != nil { panic(err) } - suite.TestTLSCertificate, err = securitytest.GenerateKeyPair(spiffeID, "test.com", &suite.TestCA) + s.TestTLSCertificate, err = securitytest.GenerateKeyPair(spiffeID, "test.com", &s.TestCA) if err != nil { panic(err) } @@ -68,59 +71,59 @@ func (t *testProvider) GetCertificate(ctx context.Context) (*tls.Certificate, er return t.GetCertificateFunc(ctx) } -func (suite *TokenTestSuite) TestGenerateToken() { +func (s *TokenTestSuite) TestGenerateToken() { p := &testProvider{ GetCertificateFunc: func(ctx context.Context) (certificate *tls.Certificate, e error) { - return &suite.TestTLSCertificate, nil + return &s.TestTLSCertificate, nil }, } token, err := security.GenerateToken(context.Background(), p, 0) - suite.Nil(err) + s.Nil(err) - x509crt, err := x509.ParseCertificate(suite.TestTLSCertificate.Certificate[0]) - suite.Nil(err) + x509crt, err := x509.ParseCertificate(s.TestTLSCertificate.Certificate[0]) + s.Nil(err) _, err = new(jwt.Parser).Parse(token, func(token *jwt.Token) (interface{}, error) { return x509crt.PublicKey, nil }) - suite.Nil(err) + s.Nil(err) } -func (suite *TokenTestSuite) TestGenerateToken_Expire() { +func (s *TokenTestSuite) TestGenerateToken_Expire() { p := &testProvider{ GetCertificateFunc: func(ctx context.Context) (certificate *tls.Certificate, e error) { - return &suite.TestTLSCertificate, nil + return &s.TestTLSCertificate, nil }, } token, err := security.GenerateToken(context.Background(), p, 3*time.Second) - suite.Nil(err) + s.Nil(err) <-time.After(5 * time.Second) - x509crt, err := x509.ParseCertificate(suite.TestTLSCertificate.Certificate[0]) - suite.Nil(err) + x509crt, err := x509.ParseCertificate(s.TestTLSCertificate.Certificate[0]) + s.Nil(err) _, err = new(jwt.Parser).Parse(token, func(token *jwt.Token) (interface{}, error) { return x509crt.PublicKey, nil }) - suite.NotNil(err) + s.NotNil(err) } -func (suite *TokenTestSuite) TestVerifyToken() { - token, err := jwt.New(jwt.SigningMethodES256).SignedString(suite.TestTLSCertificate.PrivateKey) - suite.Nil(err) +func (s *TokenTestSuite) TestVerifyToken() { + token, err := jwt.New(jwt.SigningMethodES256).SignedString(s.TestTLSCertificate.PrivateKey) + s.Nil(err) - x509crt, err := x509.ParseCertificate(suite.TestTLSCertificate.Certificate[0]) - suite.Nil(err) + x509crt, err := x509.ParseCertificate(s.TestTLSCertificate.Certificate[0]) + s.Nil(err) err = security.VerifyToken(token, x509crt) - suite.Nil(err) + s.Nil(err) - invalidX509crt, err := x509.ParseCertificate(suite.TestCA.Certificate[0]) - suite.Nil(err) + invalidX509crt, err := x509.ParseCertificate(s.TestCA.Certificate[0]) + s.Nil(err) err = security.VerifyToken(token, invalidX509crt) - suite.NotNil(err) + s.NotNil(err) }