Skip to content

Commit

Permalink
Move email principal to package (#620)
Browse files Browse the repository at this point in the history
Signed-off-by: Nathan Smith <[email protected]>
  • Loading branch information
nsmith5 authored May 31, 2022
1 parent 3bff454 commit fa41bd9
Show file tree
Hide file tree
Showing 5 changed files with 429 additions and 116 deletions.
23 changes: 17 additions & 6 deletions pkg/ca/intermediateca/intermediateca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import (
"testing"

ct "github.com/google/certificate-transparency-go"
"github.com/sigstore/fulcio/pkg/challenges"
"github.com/sigstore/fulcio/pkg/ca/x509ca"
"github.com/sigstore/fulcio/pkg/test"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
Expand Down Expand Up @@ -156,6 +156,20 @@ func TestIntermediateCAVerifyCertChain(t *testing.T) {
}
}

type testPrincipal struct{}

func (tp testPrincipal) Name(context.Context) string {
return "doesntmatter"
}

func (tp testPrincipal) Embed(ctx context.Context, cert *x509.Certificate) (err error) {
cert.EmailAddresses = []string{"[email protected]"}
cert.ExtraExtensions, err = x509ca.Extensions{
Issuer: "example.com",
}.Render()
return
}

func TestCreatePrecertificateAndIssueFinalCertificate(t *testing.T) {
rootCert, rootKey, _ := test.GenerateRootCA()
subCert, subKey, _ := test.GenerateSubordinateCA(rootCert, rootKey)
Expand All @@ -164,11 +178,8 @@ func TestCreatePrecertificateAndIssueFinalCertificate(t *testing.T) {
certChain := []*x509.Certificate{subCert, rootCert}

ica := IntermediateCA{Certs: certChain, Signer: subKey}
precsc, err := ica.CreatePrecertificate(context.TODO(), &challenges.ChallengeResult{
Issuer: "iss",
TypeVal: challenges.EmailValue,
Value: "[email protected]",
}, priv.Public())

precsc, err := ica.CreatePrecertificate(context.TODO(), testPrincipal{}, priv.Public())

if err != nil {
t.Fatalf("error generating precertificate: %v", err)
Expand Down
35 changes: 3 additions & 32 deletions pkg/challenges/challenges.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,20 @@ import (
"github.com/sigstore/fulcio/pkg/ca/x509ca"
"github.com/sigstore/fulcio/pkg/config"
"github.com/sigstore/fulcio/pkg/identity"
"github.com/sigstore/fulcio/pkg/identity/email"
"github.com/sigstore/fulcio/pkg/identity/github"
"github.com/sigstore/fulcio/pkg/identity/kubernetes"
"github.com/sigstore/fulcio/pkg/identity/spiffe"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/sigstore/fulcio/pkg/oauthflow"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
)

type ChallengeType int

const (
EmailValue ChallengeType = iota
URIValue
URIValue ChallengeType = iota
UsernameValue
)

Expand All @@ -65,8 +64,6 @@ func (cr *ChallengeResult) Name(context.Context) string {

func (cr *ChallengeResult) Embed(ctx context.Context, cert *x509.Certificate) error {
switch cr.TypeVal {
case EmailValue:
cert.EmailAddresses = []string{cr.Value}
case URIValue:
subjectURI, err := url.Parse(cr.Value)
if err != nil {
Expand Down Expand Up @@ -101,32 +98,6 @@ func CheckSignature(pub crypto.PublicKey, proof []byte, subject string) error {
return verifier.VerifySignature(bytes.NewReader(proof), strings.NewReader(subject))
}

func email(ctx context.Context, principal *oidc.IDToken) (identity.Principal, error) {
emailAddress, emailVerified, err := oauthflow.EmailFromIDToken(principal)
if !emailVerified {
return nil, errors.New("email_verified claim was false")
} else if err != nil {
return nil, err
}

cfg, ok := config.FromContext(ctx).GetIssuer(principal.Issuer)
if !ok {
return nil, errors.New("invalid configuration for OIDC ID Token issuer")
}

issuer, err := oauthflow.IssuerFromIDToken(principal, cfg.IssuerClaim)
if err != nil {
return nil, err
}

return &ChallengeResult{
Issuer: issuer,
TypeVal: EmailValue,
Value: emailAddress,
subject: emailAddress,
}, nil
}

func uri(ctx context.Context, principal *oidc.IDToken) (identity.Principal, error) {
uriWithSubject := principal.Subject

Expand Down Expand Up @@ -190,7 +161,7 @@ func PrincipalFromIDToken(ctx context.Context, tok *oidc.IDToken) (identity.Prin
var err error
switch iss.Type {
case config.IssuerTypeEmail:
principal, err = email(ctx, tok)
principal, err = email.PrincipalFromIDToken(ctx, tok)
case config.IssuerTypeSpiffe:
principal, err = spiffe.PrincipalFromIDToken(ctx, tok)
case config.IssuerTypeGithubWorkflow:
Expand Down
78 changes: 0 additions & 78 deletions pkg/challenges/challenges_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ import (
"errors"
"fmt"
"net/url"
"reflect"
"testing"
"unsafe"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/google/go-cmp/cmp"
Expand All @@ -45,26 +43,6 @@ func TestEmbedChallengeResult(t *testing.T) {
WantErr bool
WantFacts map[string]func(x509.Certificate) error
}{
`Email challenges should set issuer extension and email subject`: {
Challenge: ChallengeResult{
Issuer: `example.com`,
TypeVal: EmailValue,
Value: `[email protected]`,
},
WantErr: false,
WantFacts: map[string]func(x509.Certificate) error{
`Certificate should have [email protected] email subject`: func(cert x509.Certificate) error {
if len(cert.EmailAddresses) != 1 {
return errors.New("no email SAN set for email challenge")
}
if cert.EmailAddresses[0] != `[email protected]` {
return errors.New("bad email. expected [email protected]")
}
return nil
},
`Certificate should have issuer extension set`: factIssuerIs("example.com"),
},
},
`Good URI value`: {
Challenge: ChallengeResult{
Issuer: `foo.example.com`,
Expand Down Expand Up @@ -278,62 +256,6 @@ func TestUsernameInvalidChar(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":"[email protected]", "email_verified":true}`),
WantErr: false,
},
"Email not verified": {
InputClaims: []byte(`{"email":"[email protected]", "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.Errorf("%s: %v", name, err)
}
return
} else if test.WantErr {
t.Errorf("%s: expected error", name)
}
})
}
}

func failErr(t *testing.T, err error) {
if err != nil {
t.Fatal(err)
Expand Down
75 changes: 75 additions & 0 deletions pkg/identity/email/principal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright 2022 The Sigstore 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 email

import (
"context"
"crypto/x509"
"errors"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/sigstore/fulcio/pkg/ca/x509ca"
"github.com/sigstore/fulcio/pkg/config"
"github.com/sigstore/fulcio/pkg/identity"
"github.com/sigstore/fulcio/pkg/oauthflow"
)

type principal struct {
address string
issuer string
}

func PrincipalFromIDToken(ctx context.Context, token *oidc.IDToken) (identity.Principal, error) {
emailAddress, emailVerified, err := oauthflow.EmailFromIDToken(token)
if err != nil {
return nil, err
}
if !emailVerified {
return nil, errors.New("email_verified claim was false")
}

cfg, ok := config.FromContext(ctx).GetIssuer(token.Issuer)
if !ok {
return nil, errors.New("invalid configuration for OIDC ID Token issuer")
}

issuer, err := oauthflow.IssuerFromIDToken(token, cfg.IssuerClaim)
if err != nil {
return nil, err
}

return principal{
issuer: issuer,
address: emailAddress,
}, nil
}

func (p principal) Name(context.Context) string {
return p.address
}

func (p principal) Embed(ctx context.Context, cert *x509.Certificate) error {
cert.EmailAddresses = []string{p.address}

var err error
cert.ExtraExtensions, err = x509ca.Extensions{
Issuer: p.issuer,
}.Render()
if err != nil {
return err
}

return nil
}
Loading

0 comments on commit fa41bd9

Please sign in to comment.