Skip to content

Commit

Permalink
Add issuer information to code signing certificates (#204)
Browse files Browse the repository at this point in the history
* Add issuer from OIDC token into code signing cert

This extracts a claim from the OIDC token (defaults to `iss`) and
inserts it into the final code signing certificate issued by Fulcio.

This provides an explicit link between the identity (i.e. the email or
the URI that is the subject of the certificate) to the identity
provider who is vouching for an entity holding valid credentials for
said identity.

The value is stored in an X509 extension with OID:
1.3.6.1.4.1.57264.1.1

Signed-off-by: Bob Callaway <bob.callaway@gmail.com>

* fix lint errors

Signed-off-by: Bob Callaway <bob.callaway@gmail.com>

* fix federation config

Signed-off-by: Bob Callaway <bob.callaway@gmail.com>
  • Loading branch information
bobcallaway authored Oct 26, 2021
1 parent 85e35f0 commit 9179910
Show file tree
Hide file tree
Showing 16 changed files with 125 additions and 21 deletions.
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ ENTRYPOINT ["/usr/local/bin/fulcio-server", "serve"]

# debug compile options & debugger
FROM deploy as debug
RUN go get github.com/go-delve/delve/cmd/dlv
RUN go install github.com/go-delve/delve/cmd/dlv

# overwrite server and include debugger
COPY --from=builder /opt/app-root/src/server_debug /usr/local/bin/fulcio-server
2 changes: 1 addition & 1 deletion Dockerfile.ctfe_init
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ FROM golang:1.17.2 AS builder

WORKDIR /root/

RUN go get github.com/google/trillian/cmd/createtree@v1.3.10
RUN go install github.com/google/trillian/cmd/createtree@v1.3.10
ADD ./config/logid.sh /root/
ADD ./config/ctfe /root/ctfe
RUN chmod +x /root/logid.sh
Expand Down
6 changes: 6 additions & 0 deletions config/config.jsn
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@
"ClientID": "sigstore",
"Type": "email"
},
"http://dex-idp:8888/auth": {
"IssuerURL": "http://dex-idp:8888/auth",
"ClientID": "fulcio",
"IssuerClaim": "$.federated_claims.connector_id",
"Type": "email"
},
"https://oidc.dlorenc.dev": {
"IssuerURL": "https://oidc.dlorenc.dev",
"ClientID": "sigstore",
Expand Down
2 changes: 1 addition & 1 deletion config/dex/docker-compose-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ oauth2:

connectors:
- type: mockCallback
id: mock
id: https://any.valid.url/
name: AlwaysApprovesOIDCProvider

staticClients:
Expand Down
3 changes: 2 additions & 1 deletion config/fulcio-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ data:
"https://oauth2.sigstore.dev/auth": {
"IssuerURL": "https://oauth2.sigstore.dev/auth",
"ClientID": "sigstore",
"Type": "email"
"Type": "email",
"IssuerClaim": "$.federated_claims.connector_id"
},
"https://oidc.dlorenc.dev": {
"IssuerURL": "https://oidc.dlorenc.dev",
Expand Down
2 changes: 1 addition & 1 deletion docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ services:
- dex-idp
- ct_server
dex-idp:
image: dexidp/dex:v2.28.0
image: dexidp/dex:v2.30.0
command: [
"dex",
"serve",
Expand Down
12 changes: 7 additions & 5 deletions federation/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,9 @@ var boilerPlate = `#
`

type federationConfig struct {
URL string
Type string
URL string
Type string
IssuerClaim string
}

func main() {
Expand All @@ -70,9 +71,10 @@ func main() {
}

fulcioCfg := config.OIDCIssuer{
IssuerURL: cfg.URL,
ClientID: "sigstore",
Type: config.IssuerType(cfg.Type),
IssuerURL: cfg.URL,
ClientID: "sigstore",
Type: config.IssuerType(cfg.Type),
IssuerClaim: cfg.IssuerClaim,
}
fulcioConfig.OIDCIssuers[cfg.URL] = fulcioCfg
}
Expand Down
1 change: 1 addition & 0 deletions federation/oauth2.sigstore.dev/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
# limitations under the License.

url: https://oauth2.sigstore.dev/auth
issuerclaim: $.federated_claims.connector_id
contact: lorenc.d@gmail.com
description: "dex address for fulcio"
type: "email"
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ go 1.16

require (
cloud.google.com/go/security v0.1.0
github.com/PaesslerAG/jsonpath v0.1.1
github.com/ThalesIgnite/crypto11 v1.2.5
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/coreos/go-oidc/v3 v3.1.0
Expand Down
5 changes: 5 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/PaesslerAG/gval v1.0.0 h1:GEKnRwkWDdf9dOmKcNrar9EA1bz1z9DqPIO1+iLzhd8=
github.com/PaesslerAG/gval v1.0.0/go.mod h1:y/nm5yEyTeX6av0OfKJNp9rBNj2XrGhAf5+v24IBN1I=
github.com/PaesslerAG/jsonpath v0.1.0/go.mod h1:4BzmtoM/PI8fPO4aQGIusjGxGir2BzcV0grWtFzq1Y8=
github.com/PaesslerAG/jsonpath v0.1.1 h1:c1/AToHQMVsduPAa4Vh6xp2U0evy4t8SWp8imEsylIk=
github.com/PaesslerAG/jsonpath v0.1.1/go.mod h1:lVboNxFGal/VwW6d9JzIy56bUsYAP6tH/x80vjnCseY=
github.com/PuerkitoBio/purell v1.1.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
Expand Down
4 changes: 3 additions & 1 deletion pkg/api/googleca_signing_cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ func GoogleCASigningCertHandler(ctx context.Context, subj *challenges.ChallengeR
case challenges.GithubWorkflowValue:
privca = googleca.GithubWorkflowSubject(subj.Value)
}
req := googleca.Req(parent, privca, publicKey)

extensions := googleca.IssuerExtension(subj.Issuer)
req := googleca.Req(parent, privca, publicKey, extensions)
logger.Infof("requesting cert from %s for %v", parent, Subject)

resp, err := googleca.Client().CreateCertificate(ctx, req)
Expand Down
16 changes: 15 additions & 1 deletion pkg/ca/googleca/googleca.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func getPubKeyType(pemBytes []byte) interface{} {
}
}

func Req(parent string, subject *privatecapb.CertificateConfig_SubjectConfig, pemBytes []byte) *privatecapb.CreateCertificateRequest {
func Req(parent string, subject *privatecapb.CertificateConfig_SubjectConfig, pemBytes []byte, extensions []*privatecapb.X509Extension) *privatecapb.CreateCertificateRequest {
// TODO, use the right fields :)
pubkeyType := getPubKeyType(pemBytes)
return &privatecapb.CreateCertificateRequest{
Expand All @@ -89,6 +89,7 @@ func Req(parent string, subject *privatecapb.CertificateConfig_SubjectConfig, pe
CodeSigning: true,
},
},
AdditionalExtensions: extensions,
},
},
},
Expand Down Expand Up @@ -122,3 +123,16 @@ func GithubWorkflowSubject(id string) *privatecapb.CertificateConfig_SubjectConf
},
}
}

func IssuerExtension(issuer string) []*privatecapb.X509Extension {
if issuer == "" {
return nil
}

return []*privatecapb.X509Extension{{
ObjectId: &privatecapb.ObjectId{
ObjectIdPath: []int32{1, 3, 6, 1, 4, 1, 57264, 1, 1},
},
Value: []byte(issuer),
}}
}
15 changes: 15 additions & 0 deletions pkg/ca/pkcs11ca/pkcs11ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ package pkcs11ca
import (
"crypto/rand"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"encoding/pem"
"math/big"
"net/url"
Expand Down Expand Up @@ -58,6 +60,8 @@ func CreateClientCertificate(rootCA *x509.Certificate, subject *challenges.Chall
}
cert.URIs = []*url.URL{jobWorkflowURI}
}
cert.ExtraExtensions = IssuerExtension(subject.Issuer)

certBytes, err := x509.CreateCertificate(rand.Reader, cert, rootCA, publicKeyPEM, privKey)
if err != nil {
return "", nil, err
Expand All @@ -69,3 +73,14 @@ func CreateClientCertificate(rootCA *x509.Certificate, subject *challenges.Chall

return string(certPEM), nil, nil
}

func IssuerExtension(issuer string) []pkix.Extension {
if issuer == "" {
return nil
}

return []pkix.Extension{{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1},
Value: []byte(issuer),
}}
}
39 changes: 37 additions & 2 deletions pkg/challenges/challenges.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ const (
)

type ChallengeResult struct {
Issuer string
TypeVal ChallengeType
Value string
}
Expand Down Expand Up @@ -81,8 +82,20 @@ func Email(ctx context.Context, principal *oidc.IDToken, pubKey, challenge []byt
return nil, err
}

globalCfg := config.Config()
cfg, ok := globalCfg.OIDCIssuers[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
}

// Now issue cert!
return &ChallengeResult{
Issuer: issuer,
TypeVal: EmailValue,
Value: emailAddress,
}, nil
Expand All @@ -96,10 +109,14 @@ func Spiffe(ctx context.Context, principal *oidc.IDToken, pubKey, challenge []by
if err != nil {
return nil, err
}
cfg := config.Config()
globalCfg := config.Config()
cfg, ok := globalCfg.OIDCIssuers[principal.Issuer]
if !ok {
return nil, errors.New("invalid configuration for OIDC ID Token issuer")
}

// The Spiffe ID must be a subdomain of the issuer (spiffe://foo.example.com -> example.com/...)
u, err := url.Parse(cfg.OIDCIssuers[principal.Issuer].IssuerURL)
u, err := url.Parse(cfg.IssuerURL)
if err != nil {
return nil, err
}
Expand All @@ -114,8 +131,14 @@ func Spiffe(ctx context.Context, principal *oidc.IDToken, pubKey, challenge []by
return nil, err
}

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

// Now issue cert!
return &ChallengeResult{
Issuer: issuer,
TypeVal: SpiffeValue,
Value: spiffeID,
}, nil
Expand All @@ -137,8 +160,20 @@ func GithubWorkflow(ctx context.Context, principal *oidc.IDToken, pubKey, challe
return nil, err
}

globalCfg := config.Config()
cfg, ok := globalCfg.OIDCIssuers[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
}

// Now issue cert!
return &ChallengeResult{
Issuer: issuer,
TypeVal: GithubWorkflowValue,
Value: workflowRef,
}, nil
Expand Down
14 changes: 8 additions & 6 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ type FulcioConfig struct {
}

type OIDCIssuer struct {
IssuerURL string
ClientID string
Type IssuerType
IssuerURL string
ClientID string
Type IssuerType
IssuerClaim string `json:"IssuerClaim,omitempty"`
}

type IssuerType string
Expand All @@ -52,9 +53,10 @@ func ParseConfig(b []byte) (FulcioConfig, error) {
var DefaultConfig = FulcioConfig{
OIDCIssuers: map[string]OIDCIssuer{
"https://oauth2.sigstore.dev/auth": {
IssuerURL: "https://oauth2.sigstore.dev/auth",
ClientID: "sigstore",
Type: IssuerTypeEmail,
IssuerURL: "https://oauth2.sigstore.dev/auth",
ClientID: "sigstore",
IssuerClaim: "$.federated_claims.connector_id",
Type: IssuerTypeEmail,
},
"https://accounts.google.com": {
IssuerURL: "https://accounts.google.com",
Expand Down
22 changes: 21 additions & 1 deletion pkg/oauthflow/oidc.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@

package oauthflow

import "github.com/coreos/go-oidc/v3/oidc"
import (
"fmt"

"github.com/PaesslerAG/jsonpath"
"github.com/coreos/go-oidc/v3/oidc"
)

func EmailFromIDToken(token *oidc.IDToken) (string, bool, error) {
// Extract custom claims
Expand All @@ -29,3 +34,18 @@ func EmailFromIDToken(token *oidc.IDToken) (string, bool, error) {

return claims.Email, claims.Verified, nil
}

func IssuerFromIDToken(token *oidc.IDToken, claimJSONPath string) (string, error) {
if claimJSONPath == "" {
return token.Issuer, nil
}
v := interface{}(nil)
if err := token.Claims(&v); err != nil {
return "", err
}
result, err := jsonpath.Get(claimJSONPath, v)
if err != nil {
return "", err
}
return fmt.Sprintf("%v", result), nil
}

0 comments on commit 9179910

Please sign in to comment.