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 some tests for challenges #583

Merged
merged 3 commits into from
May 14, 2022
Merged
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
205 changes: 205 additions & 0 deletions pkg/challenges/challenges_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@ import (
"errors"
"fmt"
"net/url"
"reflect"
"testing"
"unsafe"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/google/go-cmp/cmp"
"github.com/sigstore/fulcio/pkg/config"
"github.com/sigstore/sigstore/pkg/cryptoutils"
)
Expand Down Expand Up @@ -67,6 +70,21 @@ func TestEmbedChallengeResult(t *testing.T) {
`Certificate has correct ref extension`: factExtensionIs(asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 6}, "ref"),
},
},
`Github workflow value with bad URL fails`: {
Challenge: ChallengeResult{
Issuer: `https://token.actions.githubusercontent.com`,
TypeVal: GithubWorkflowValue,
Value: "\nbadurl",
AdditionalInfo: map[AdditionalInfo]string{
GithubWorkflowSha: "sha",
GithubWorkflowTrigger: "trigger",
GithubWorkflowName: "workflowname",
GithubWorkflowRepository: "repository",
GithubWorkflowRef: "ref",
},
},
WantErr: true,
},
`Email challenges should set issuer extension and email subject`: {
Challenge: ChallengeResult{
Issuer: `example.com`,
Expand All @@ -87,6 +105,130 @@ func TestEmbedChallengeResult(t *testing.T) {
`Certificate should have issuer extension set`: factIssuerIs("example.com"),
},
},
`Good spiffe challenge`: {
Challenge: ChallengeResult{
Issuer: `example.com`,
TypeVal: SpiffeValue,
Value: `spiffe://example.com/foo/bar`,
},
WantErr: false,
WantFacts: map[string]func(x509.Certificate) error{
`Issuer is example.com`: factIssuerIs(`example.com`),
`SAN is spiffe://example.com/foo/bar`: func(cert x509.Certificate) error {
WantURI, err := url.Parse("spiffe://example.com/foo/bar")
if err != nil {
return err
}
if len(cert.URIs) != 1 {
return errors.New("no URI SAN set")
}
if diff := cmp.Diff(cert.URIs[0], WantURI); diff != "" {
return errors.New(diff)
}
return nil
},
},
},
`Spiffe value with bad URL fails`: {
Challenge: ChallengeResult{
Issuer: `example.com`,
TypeVal: SpiffeValue,
Value: "\nbadurl",
},
WantErr: true,
},
`Good Kubernetes value`: {
Challenge: ChallengeResult{
Issuer: `k8s.example.com`,
TypeVal: KubernetesValue,
Value: "https://k8s.example.com",
},
WantErr: false,
WantFacts: map[string]func(x509.Certificate) error{
`Issuer is k8s.example.com`: factIssuerIs(`k8s.example.com`),
`SAN is https://k8s.example.com`: func(cert x509.Certificate) error {
WantURI, err := url.Parse("https://k8s.example.com")
if err != nil {
return err
}
if len(cert.URIs) != 1 {
return errors.New("no URI SAN set")
}
if diff := cmp.Diff(cert.URIs[0], WantURI); diff != "" {
return errors.New(diff)
}
return nil
},
},
},
`Kubernetes value with bad URL fails`: {
Challenge: ChallengeResult{
Issuer: `example.com`,
TypeVal: KubernetesValue,
Value: "\nbadurl",
},
WantErr: true,
},
`Good URI value`: {
Challenge: ChallengeResult{
Issuer: `foo.example.com`,
TypeVal: URIValue,
Value: "https://foo.example.com",
},
WantErr: false,
WantFacts: map[string]func(x509.Certificate) error{
`Issuer is foo.example.com`: factIssuerIs(`foo.example.com`),
`SAN is https://foo.example.com`: func(cert x509.Certificate) error {
WantURI, err := url.Parse("https://foo.example.com")
if err != nil {
return err
}
if len(cert.URIs) != 1 {
return errors.New("no URI SAN set")
}
if diff := cmp.Diff(cert.URIs[0], WantURI); diff != "" {
return errors.New(diff)
}
return nil
},
},
},
`Bad URI value fails`: {
Challenge: ChallengeResult{
Issuer: `foo.example.com`,
TypeVal: URIValue,
Value: "\nnoooooo",
},
WantErr: true,
},
`Good username value`: {
Challenge: ChallengeResult{
Issuer: `foo.example.com`,
TypeVal: UsernameValue,
Value: "name@foo.example.com",
},
WantErr: false,
WantFacts: map[string]func(x509.Certificate) error{
`Issuer is foo.example.com`: factIssuerIs(`foo.example.com`),
`SAN is name@foo.example.com`: func(cert x509.Certificate) error {
if len(cert.EmailAddresses) != 1 {
return errors.New("no email SAN set")
}
if cert.EmailAddresses[0] != "name@foo.example.com" {
return errors.New("wrong email")
}
return nil
},
},
},
`No issuer should fail to render extensions`: {
Challenge: ChallengeResult{
Issuer: ``,
TypeVal: SpiffeValue,
Value: "spiffe://foo.example.com/foo/bar",
},
WantErr: true,
},
}

for name, test := range tests {
Expand All @@ -98,6 +240,8 @@ func TestEmbedChallengeResult(t *testing.T) {
t.Error(err)
}
return
} else if test.WantErr {
t.Error("expected error")
}
for factName, fact := range test.WantFacts {
t.Run(factName, func(t *testing.T) {
Expand Down Expand Up @@ -346,6 +490,62 @@ func Test_isURISubjectAllowed(t *testing.T) {
}
}

// reflect hack because "claims" field is unexported by oidc IDToken
// https://github.com/coreos/go-oidc/pull/329
func updateIDToken(idToken *oidc.IDToken, fieldName string, data []byte) {
val := reflect.Indirect(reflect.ValueOf(idToken))
member := val.FieldByName(fieldName)
pointer := unsafe.Pointer(member.UnsafeAddr())
realPointer := (*[]byte)(pointer)
*realPointer = data
}

func TestEmailWithClaims(t *testing.T) {
tests := map[string]struct {
InputClaims []byte
WantErr bool
}{
"Good": {
InputClaims: []byte(`{"email":"John.Doe@email.com", "email_verified":true}`),
WantErr: false,
},
"Email not verified": {
InputClaims: []byte(`{"email":"John.Doe@email.com", "email_verified":false}`),
WantErr: true,
},
"Email missing": {
InputClaims: []byte(`{"email_verified":true}`),
WantErr: true,
},
}

ctx := context.Background()
cfg := &config.FulcioConfig{
OIDCIssuers: map[string]config.OIDCIssuer{
"email.com": {IssuerURL: "email.com"},
},
}
ctx = config.With(ctx, cfg)

for name, test := range tests {
t.Run(name, func(t *testing.T) {
idToken := &oidc.IDToken{
Issuer: `email.com`,
}
updateIDToken(idToken, "claims", test.InputClaims)
_, err := email(ctx, idToken)
if err != nil {
if !test.WantErr {
t.Error(err)
}
return
} else if test.WantErr {
t.Error("expected error")
}
})
}
}

func Test_validateAllowedDomain(t *testing.T) {
tests := []struct {
name string
Expand Down Expand Up @@ -436,6 +636,11 @@ func TestCheckSignatureECDSA(t *testing.T) {
t.Fatal(err)
}

// Nil key should fail
if err := CheckSignature(nil, signature, email); err == nil {
t.Error("nil public key should raise error")
}

// Try a bad email but "good" signature
if err := CheckSignature(&priv.PublicKey, signature, "bad@email.com"); err == nil {
t.Fatal("check should have failed")
Expand Down
4 changes: 4 additions & 0 deletions pkg/oauthflow/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package oauthflow

import (
"errors"
"fmt"

"github.com/PaesslerAG/jsonpath"
Expand All @@ -31,6 +32,9 @@ func EmailFromIDToken(token *oidc.IDToken) (string, bool, error) {
if err := token.Claims(&claims); err != nil {
return "", false, err
}
if claims.Email == "" {
return "", false, errors.New("token missing email claim")
}

return claims.Email, claims.Verified, nil
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/oauthflow/oidc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,13 +53,17 @@ func TestTokenWithClaims(t *testing.T) {
inputClaims: []byte(`{}`),
expectedEmail: "",
expectedVerified: false,
expectedErr: nil,
expectedErr: errors.New("token missing email claim"),
}, {
name: "token with non-verified claims set",
inputClaims: []byte(`{"email":"John.Doe@email.com"}`),
expectedEmail: "John.Doe@email.com",
expectedVerified: false,
expectedErr: nil,
}, {
name: "token missing email claim",
inputClaims: []byte(`{"email_verified": true}`),
expectedErr: errors.New("token missing email claim"),
}, {
name: "token with claims set",
inputClaims: []byte(`{"email":"John.Doe@email.com", "email_verified":true}`),
Expand Down