-
Notifications
You must be signed in to change notification settings - Fork 137
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adding a flag to select the google private ca api version at runtime
Signed-off-by: Scott Nichols <n3wscott@chainguard.dev>
- Loading branch information
Showing
9 changed files
with
340 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// Copyright 2021 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 api | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/spf13/viper" | ||
privatecapb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1" | ||
|
||
googleca "github.com/sigstore/fulcio/pkg/ca/googleca/v1" | ||
"github.com/sigstore/fulcio/pkg/challenges" | ||
"github.com/sigstore/fulcio/pkg/log" | ||
) | ||
|
||
func GoogleCASigningCertHandlerV1(ctx context.Context, subj *challenges.ChallengeResult, publicKey []byte) (string, []string, error) { | ||
logger := log.ContextLogger(ctx) | ||
|
||
parent := viper.GetString("gcp_private_ca_parent") | ||
|
||
// call a new function here to set the type, we may need to pass back the issuer? | ||
var privca *privatecapb.CertificateConfig_SubjectConfig | ||
switch subj.TypeVal { | ||
case challenges.EmailValue: | ||
privca = googleca.EmailSubject(subj.Value) | ||
case challenges.SpiffeValue: | ||
privca = googleca.SpiffeSubject(subj.Value) | ||
case challenges.GithubWorkflowValue: | ||
privca = googleca.GithubWorkflowSubject(subj.Value) | ||
} | ||
|
||
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) | ||
if err != nil { | ||
return "", nil, err | ||
} | ||
return resp.PemCertificate, resp.PemCertificateChain, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// Copyright 2021 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 api | ||
|
||
import ( | ||
"context" | ||
|
||
"github.com/spf13/viper" | ||
privatecapb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" | ||
|
||
googleca "github.com/sigstore/fulcio/pkg/ca/googleca/v1beta1" | ||
"github.com/sigstore/fulcio/pkg/challenges" | ||
"github.com/sigstore/fulcio/pkg/log" | ||
) | ||
|
||
func GoogleCASigningCertHandlerV1Beta1(ctx context.Context, subj *challenges.ChallengeResult, publicKey []byte) (string, []string, error) { | ||
logger := log.ContextLogger(ctx) | ||
|
||
parent := viper.GetString("gcp_private_ca_parent") | ||
|
||
// call a new function here to set the type, we may need to pass back the issuer? | ||
var privca *privatecapb.CertificateConfig_SubjectConfig | ||
switch subj.TypeVal { | ||
case challenges.EmailValue: | ||
privca = googleca.EmailSubject(subj.Value) | ||
case challenges.SpiffeValue: | ||
privca = googleca.SpiffeSubject(subj.Value) | ||
case challenges.GithubWorkflowValue: | ||
privca = googleca.GithubWorkflowSubject(subj.Value) | ||
} | ||
|
||
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) | ||
if err != nil { | ||
return "", nil, err | ||
} | ||
return resp.PemCertificate, resp.PemCertificateChain, nil | ||
} |
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
// Copyright 2021 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 googleca | ||
|
||
import ( | ||
"context" | ||
"crypto/ecdsa" | ||
"crypto/rsa" | ||
"crypto/x509" | ||
"encoding/pem" | ||
"fmt" | ||
"sync" | ||
|
||
privateca "cloud.google.com/go/security/privateca/apiv1beta1" | ||
privatecapb "google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1" | ||
"google.golang.org/protobuf/types/known/durationpb" | ||
) | ||
|
||
var ( | ||
once sync.Once | ||
c *privateca.CertificateAuthorityClient | ||
) | ||
|
||
func Client() *privateca.CertificateAuthorityClient { | ||
// Use a once block to avoid creating a new client every time. | ||
once.Do(func() { | ||
var err error | ||
c, err = privateca.NewCertificateAuthorityClient(context.Background()) | ||
if err != nil { | ||
panic(err) | ||
} | ||
}) | ||
|
||
return c | ||
} | ||
|
||
// Returns the PublicKey type required by gcp privateca (to handle both PEM_RSA_KEY / PEM_EC_KEY) | ||
// https://pkg.go.dev/google.golang.org/genproto/googleapis/cloud/security/privateca/v1beta1#PublicKey_KeyType | ||
func getPubKeyType(pemBytes []byte) interface{} { | ||
block, _ := pem.Decode(pemBytes) | ||
pub, err := x509.ParsePKIXPublicKey(block.Bytes) | ||
if err != nil { | ||
panic("failed to parse public key: " + err.Error()) | ||
} | ||
switch pub := pub.(type) { | ||
case *rsa.PublicKey: | ||
return privatecapb.PublicKey_KeyType(1) | ||
case *ecdsa.PublicKey: | ||
return privatecapb.PublicKey_KeyType(2) | ||
default: | ||
panic(fmt.Errorf("unknown public key type: %v", pub)) | ||
} | ||
} | ||
|
||
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{ | ||
Parent: parent, | ||
Certificate: &privatecapb.Certificate{ | ||
Lifetime: &durationpb.Duration{Seconds: 20 * 60}, | ||
CertificateConfig: &privatecapb.Certificate_Config{ | ||
Config: &privatecapb.CertificateConfig{ | ||
PublicKey: &privatecapb.PublicKey{ | ||
Type: pubkeyType.(privatecapb.PublicKey_KeyType), | ||
Key: pemBytes, | ||
}, | ||
ReusableConfig: &privatecapb.ReusableConfigWrapper{ | ||
ConfigValues: &privatecapb.ReusableConfigWrapper_ReusableConfigValues{ | ||
ReusableConfigValues: &privatecapb.ReusableConfigValues{ | ||
KeyUsage: &privatecapb.KeyUsage{ | ||
BaseKeyUsage: &privatecapb.KeyUsage_KeyUsageOptions{ | ||
DigitalSignature: true, | ||
}, | ||
ExtendedKeyUsage: &privatecapb.KeyUsage_ExtendedKeyUsageOptions{ | ||
CodeSigning: true, | ||
}, | ||
}, | ||
AdditionalExtensions: extensions, | ||
}, | ||
}, | ||
}, | ||
SubjectConfig: subject, | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func EmailSubject(email string) *privatecapb.CertificateConfig_SubjectConfig { | ||
return &privatecapb.CertificateConfig_SubjectConfig{ | ||
SubjectAltName: &privatecapb.SubjectAltNames{ | ||
EmailAddresses: []string{email}, | ||
}} | ||
} | ||
|
||
// SPIFFE IDs go as "Uris" according to the spec: https://github.com/spiffe/spiffe/blob/main/standards/X509-SVID.md | ||
func SpiffeSubject(id string) *privatecapb.CertificateConfig_SubjectConfig { | ||
return &privatecapb.CertificateConfig_SubjectConfig{ | ||
SubjectAltName: &privatecapb.SubjectAltNames{ | ||
Uris: []string{id}, | ||
}, | ||
} | ||
} | ||
|
||
func GithubWorkflowSubject(id string) *privatecapb.CertificateConfig_SubjectConfig { | ||
return &privatecapb.CertificateConfig_SubjectConfig{ | ||
SubjectAltName: &privatecapb.SubjectAltNames{ | ||
Uris: []string{id}, | ||
}, | ||
} | ||
} | ||
|
||
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), | ||
}} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
// Copyright 2021 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 googleca | ||
|
||
import ( | ||
"crypto" | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"crypto/rand" | ||
"crypto/rsa" | ||
"crypto/sha256" | ||
"testing" | ||
|
||
"github.com/sigstore/fulcio/pkg/challenges" | ||
) | ||
|
||
func failErr(t *testing.T, err error) { | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
} | ||
|
||
func TestCheckSignatureECDSA(t *testing.T) { | ||
|
||
priv, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
failErr(t, err) | ||
|
||
email := "test@gmail.com" | ||
if err := challenges.CheckSignature(&priv.PublicKey, []byte("foo"), email); err == nil { | ||
t.Fatal("check should have failed") | ||
} | ||
|
||
h := sha256.Sum256([]byte(email)) | ||
signature, err := priv.Sign(rand.Reader, h[:], crypto.SHA256) | ||
failErr(t, err) | ||
|
||
if err := challenges.CheckSignature(&priv.PublicKey, signature, email); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Try a bad email but "good" signature | ||
if err := challenges.CheckSignature(&priv.PublicKey, signature, "bad@email.com"); err == nil { | ||
t.Fatal("check should have failed") | ||
} | ||
} | ||
|
||
func TestCheckSignatureRSA(t *testing.T) { | ||
priv, err := rsa.GenerateKey(rand.Reader, 2048) | ||
failErr(t, err) | ||
|
||
email := "test@gmail.com" | ||
if err := challenges.CheckSignature(&priv.PublicKey, []byte("foo"), email); err == nil { | ||
t.Fatal("check should have failed") | ||
} | ||
|
||
h := sha256.Sum256([]byte(email)) | ||
signature, err := priv.Sign(rand.Reader, h[:], crypto.SHA256) | ||
failErr(t, err) | ||
|
||
if err := challenges.CheckSignature(&priv.PublicKey, signature, email); err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
// Try a bad email but "good" signature | ||
if err := challenges.CheckSignature(&priv.PublicKey, signature, "bad@email.com"); err == nil { | ||
t.Fatal("check should have failed") | ||
} | ||
} |