Skip to content

Commit

Permalink
Create pkg/certificate
Browse files Browse the repository at this point in the history
Refactors common certificate behavior into a distinct package.

Adds ability to go to/from Extension type <-> []pkix.Extensions.

Signed-off-by: Billy Lynch <billy@chainguard.dev>
  • Loading branch information
wlynch committed Aug 24, 2022
1 parent fbd79b2 commit 880eb15
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 120 deletions.
3 changes: 2 additions & 1 deletion pkg/ca/baseca/baseca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

ct "github.com/google/certificate-transparency-go"
"github.com/sigstore/fulcio/pkg/ca"
"github.com/sigstore/fulcio/pkg/certificate"
"github.com/sigstore/fulcio/pkg/test"
"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/sigstore/pkg/signature"
Expand Down Expand Up @@ -92,7 +93,7 @@ func (tp testPrincipal) Name(context.Context) string {

func (tp testPrincipal) Embed(ctx context.Context, cert *x509.Certificate) (err error) {
cert.EmailAddresses = []string{"alice@example.com"}
cert.ExtraExtensions, err = ca.Extensions{
cert.ExtraExtensions, err = certificate.Extensions{
Issuer: "example.com",
}.Render()
return
Expand Down
89 changes: 3 additions & 86 deletions pkg/ca/extensions.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,92 +15,9 @@
package ca

import (
"crypto/x509/pkix"
"encoding/asn1"
"errors"
)

var (
OIDIssuer = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}
OIDGitHubWorkflowTrigger = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 2}
OIDGitHubWorkflowSHA = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 3}
OIDGitHubWorkflowName = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 4}
OIDGithubWorkflowRepository = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 5}
OIDGithubWorkflowRef = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 6}
"github.com/sigstore/fulcio/pkg/certificate"
)

// Extensions contains all custom x509 extensions defined by Fulcio
type Extensions struct {
// NB: New extensions must be added here and documented
// at docs/oidc-info.md

// The OIDC issuer. Should match `iss` claim of ID token or, in the case of
// a federated login like Dex it should match the issuer URL of the
// upstream issuer. The issuer is not set the extensions are invalid and
// will fail to render.
Issuer string // OID 1.3.6.1.4.1.57264.1.1

// Triggering event of the Github Workflow. Matches the `event_name` claim of ID
// tokens from Github Actions
GithubWorkflowTrigger string // OID 1.3.6.1.4.1.57264.1.2

// SHA of git commit being built in Github Actions. Matches the `sha` claim of ID
// tokens from Github Actions
GithubWorkflowSHA string // OID 1.3.6.1.4.1.57264.1.3

// Name of Github Actions Workflow. Matches the `workflow` claim of the ID
// tokens from Github Actions
GithubWorkflowName string // OID 1.3.6.1.4.1.57264.1.4

// Repository of the Github Actions Workflow. Matches the `repository` claim of the ID
// tokens from Github Actions
GithubWorkflowRepository string // OID 1.3.6.1.4.1.57264.1.5

// Git Ref of the Github Actions Workflow. Matches the `ref` claim of the ID tokens
// from Github Actions
GithubWorkflowRef string // 1.3.6.1.4.1.57264.1.6
}

func (e Extensions) Render() ([]pkix.Extension, error) {
var exts []pkix.Extension

if e.Issuer != "" {
exts = append(exts, pkix.Extension{
Id: OIDIssuer,
Value: []byte(e.Issuer),
})
} else {
return nil, errors.New("extensions must have a non-empty issuer url")
}
if e.GithubWorkflowTrigger != "" {
exts = append(exts, pkix.Extension{
Id: OIDGitHubWorkflowTrigger,
Value: []byte(e.GithubWorkflowTrigger),
})
}
if e.GithubWorkflowSHA != "" {
exts = append(exts, pkix.Extension{
Id: OIDGitHubWorkflowSHA,
Value: []byte(e.GithubWorkflowSHA),
})
}
if e.GithubWorkflowName != "" {
exts = append(exts, pkix.Extension{
Id: OIDGitHubWorkflowName,
Value: []byte(e.GithubWorkflowName),
})
}
if e.GithubWorkflowRepository != "" {
exts = append(exts, pkix.Extension{
Id: OIDGithubWorkflowRepository,
Value: []byte(e.GithubWorkflowRepository),
})
}
if e.GithubWorkflowRef != "" {
exts = append(exts, pkix.Extension{
Id: OIDGithubWorkflowRef,
Value: []byte(e.GithubWorkflowRef),
})
}
return exts, nil
}
// Deprecated: use certificate.Extensions instead.
type Extensions = certificate.Extensions
17 changes: 17 additions & 0 deletions pkg/certificate/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// 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 certificate contains helpers for getting data from Fulcio issued
// x509 certificates.
package certificate
131 changes: 131 additions & 0 deletions pkg/certificate/extensions.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
// 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 certificate

import (
"crypto/x509/pkix"
"encoding/asn1"
"errors"
)

var (
OIDIssuer = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1}
OIDGitHubWorkflowTrigger = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 2}
OIDGitHubWorkflowSHA = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 3}
OIDGitHubWorkflowName = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 4}
OIDGitHubWorkflowRepository = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 5}
OIDGitHubWorkflowRef = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 6}
)

// Extensions contains all custom x509 extensions defined by Fulcio
type Extensions struct {
// NB: New extensions must be added here and documented
// at docs/oidc-info.md

// The OIDC issuer. Should match `iss` claim of ID token or, in the case of
// a federated login like Dex it should match the issuer URL of the
// upstream issuer. The issuer is not set the extensions are invalid and
// will fail to render.
Issuer string // OID 1.3.6.1.4.1.57264.1.1

// Triggering event of the Github Workflow. Matches the `event_name` claim of ID
// tokens from Github Actions
GithubWorkflowTrigger string // OID 1.3.6.1.4.1.57264.1.2

// SHA of git commit being built in Github Actions. Matches the `sha` claim of ID
// tokens from Github Actions
GithubWorkflowSHA string // OID 1.3.6.1.4.1.57264.1.3

// Name of Github Actions Workflow. Matches the `workflow` claim of the ID
// tokens from Github Actions
GithubWorkflowName string // OID 1.3.6.1.4.1.57264.1.4

// Repository of the Github Actions Workflow. Matches the `repository` claim of the ID
// tokens from Github Actions
GithubWorkflowRepository string // OID 1.3.6.1.4.1.57264.1.5

// Git Ref of the Github Actions Workflow. Matches the `ref` claim of the ID tokens
// from Github Actions
GithubWorkflowRef string // 1.3.6.1.4.1.57264.1.6
}

func (e Extensions) Render() ([]pkix.Extension, error) {
var exts []pkix.Extension

if e.Issuer != "" {
exts = append(exts, pkix.Extension{
Id: OIDIssuer,
Value: []byte(e.Issuer),
})
} else {
return nil, errors.New("extensions must have a non-empty issuer url")
}
if e.GithubWorkflowTrigger != "" {
exts = append(exts, pkix.Extension{
Id: OIDGitHubWorkflowTrigger,
Value: []byte(e.GithubWorkflowTrigger),
})
}
if e.GithubWorkflowSHA != "" {
exts = append(exts, pkix.Extension{
Id: OIDGitHubWorkflowSHA,
Value: []byte(e.GithubWorkflowSHA),
})
}
if e.GithubWorkflowName != "" {
exts = append(exts, pkix.Extension{
Id: OIDGitHubWorkflowName,
Value: []byte(e.GithubWorkflowName),
})
}
if e.GithubWorkflowRepository != "" {
exts = append(exts, pkix.Extension{
Id: OIDGitHubWorkflowRepository,
Value: []byte(e.GithubWorkflowRepository),
})
}
if e.GithubWorkflowRef != "" {
exts = append(exts, pkix.Extension{
Id: OIDGitHubWorkflowRef,
Value: []byte(e.GithubWorkflowRef),
})
}
return exts, nil
}

func ParseExtensions(ext []pkix.Extension) (Extensions, error) {
out := Extensions{}

for _, e := range ext {
switch {
case e.Id.Equal(OIDIssuer):
out.Issuer = string(e.Value)
case e.Id.Equal(OIDGitHubWorkflowTrigger):
out.GithubWorkflowTrigger = string(e.Value)
case e.Id.Equal(OIDGitHubWorkflowSHA):
out.GithubWorkflowSHA = string(e.Value)
case e.Id.Equal(OIDGitHubWorkflowName):
out.GithubWorkflowName = string(e.Value)
case e.Id.Equal(OIDGitHubWorkflowRepository):
out.GithubWorkflowRepository = string(e.Value)
case e.Id.Equal(OIDGitHubWorkflowRef):
out.GithubWorkflowRef = string(e.Value)
}
}

// We only ever return nil, but leaving error in place so that we can add
// more complex parsing of fields in a backwards compatible way if needed.
return out, nil
}
41 changes: 20 additions & 21 deletions pkg/ca/extensions_test.go → pkg/certificate/extensions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package ca
package certificate

import (
"bytes"
"crypto/x509/pkix"
"encoding/asn1"

"testing"

"github.com/google/go-cmp/cmp"
)

func TestRenderExtensions(t *testing.T) {
func TestExtensions(t *testing.T) {
tests := map[string]struct {
Extensions Extensions
Expect []pkix.Extension
Expand All @@ -45,27 +45,27 @@ func TestRenderExtensions(t *testing.T) {
},
Expect: []pkix.Extension{
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 1},
Id: OIDIssuer,
Value: []byte(`1`),
},
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 2},
Id: OIDGitHubWorkflowTrigger,
Value: []byte(`2`),
},
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 3},
Id: OIDGitHubWorkflowSHA,
Value: []byte(`3`),
},
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 4},
Id: OIDGitHubWorkflowName,
Value: []byte(`4`),
},
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 5},
Id: OIDGitHubWorkflowRepository,
Value: []byte(`5`),
},
{
Id: asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 1, 6},
Id: OIDGitHubWorkflowRef,
Value: []byte(`6`),
},
},
Expand All @@ -75,25 +75,24 @@ func TestRenderExtensions(t *testing.T) {

for name, test := range tests {
t.Run(name, func(t *testing.T) {
got, err := test.Extensions.Render()
render, err := test.Extensions.Render()
if err != nil {
if !test.WantErr {
t.Error("Failed to render with unexpected error", err)
} else {
return
}
}
if len(got) != len(test.Expect) {
t.Errorf("Got %d extensions when rendered and expected %d", len(got), len(test.Expect))
return
if diff := cmp.Diff(test.Expect, render); diff != "" {
t.Errorf("Render: %s", diff)
}
for i, ext := range got {
if !ext.Id.Equal(test.Expect[i].Id) {
t.Errorf("Got OID %v in extension %d and expected %v", ext.Id, i, test.Expect[i].Id)
}
if !bytes.Equal(ext.Value, test.Expect[i].Value) {
t.Errorf("Expected extension value to be %s but got %s", test.Expect[i].Value, ext.Value)
}

parse, err := ParseExtensions(render)
if err != nil {
t.Fatalf("ParseExtensions: err = %v", err)
}
if diff := cmp.Diff(test.Extensions, parse); diff != "" {
t.Errorf("ParseExtensions: %s", diff)
}
})
}
Expand Down
4 changes: 2 additions & 2 deletions pkg/identity/email/principal.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import (
"errors"

"github.com/coreos/go-oidc/v3/oidc"
"github.com/sigstore/fulcio/pkg/ca"
"github.com/sigstore/fulcio/pkg/certificate"
"github.com/sigstore/fulcio/pkg/config"
"github.com/sigstore/fulcio/pkg/identity"
"github.com/sigstore/fulcio/pkg/oauthflow"
Expand Down Expand Up @@ -64,7 +64,7 @@ func (p principal) Embed(ctx context.Context, cert *x509.Certificate) error {
cert.EmailAddresses = []string{p.address}

var err error
cert.ExtraExtensions, err = ca.Extensions{
cert.ExtraExtensions, err = certificate.Extensions{
Issuer: p.issuer,
}.Render()
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions pkg/identity/github/principal.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (
"net/url"

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

Expand Down Expand Up @@ -111,7 +111,7 @@ func (w workflowPrincipal) Embed(ctx context.Context, cert *x509.Certificate) er
cert.URIs = []*url.URL{parsed}

// Embed additional information into custom extensions
cert.ExtraExtensions, err = ca.Extensions{
cert.ExtraExtensions, err = certificate.Extensions{
Issuer: w.issuer,
GithubWorkflowTrigger: w.trigger,
GithubWorkflowSHA: w.sha,
Expand Down

0 comments on commit 880eb15

Please sign in to comment.