-
Notifications
You must be signed in to change notification settings - Fork 128
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add file backed certificate authority
Loads private key and certificate from disk. Optionally watches for file changes and loads updated key pair. Signed-off-by: Nathan Smith <nathan@nfsmith.ca>
- Loading branch information
Showing
18 changed files
with
589 additions
and
1 deletion.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// 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 fileca | ||
|
||
import ( | ||
"context" | ||
"crypto" | ||
"crypto/rand" | ||
"crypto/x509" | ||
"sync" | ||
|
||
"github.com/fsnotify/fsnotify" | ||
"github.com/sigstore/fulcio/pkg/ca" | ||
"github.com/sigstore/fulcio/pkg/ca/x509ca" | ||
"github.com/sigstore/fulcio/pkg/challenges" | ||
) | ||
|
||
type fileCA struct { | ||
sync.RWMutex | ||
|
||
cert *x509.Certificate | ||
key crypto.Signer | ||
} | ||
|
||
// NewFileCA returns a file backed certificate authority. Expects paths to a | ||
// certificate and key that are PEM encoded and not password protected. | ||
func NewFileCA(certPath, keyPath string, watch bool) (ca.CertificateAuthority, error) { | ||
var fca fileCA | ||
|
||
var err error | ||
fca.cert, fca.key, err = loadKeyPair(certPath, keyPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if watch { | ||
watcher, err := fsnotify.NewWatcher() | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = watcher.Add(certPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
err = watcher.Add(keyPath) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
go ioWatch(certPath, keyPath, watcher, fca.updateX509KeyPair) | ||
} | ||
|
||
return &fca, err | ||
} | ||
|
||
func (fca *fileCA) updateX509KeyPair(cert *x509.Certificate, key crypto.Signer) { | ||
fca.Lock() | ||
defer fca.Unlock() | ||
|
||
// NB: We use the RWLock to unsure a reading thread can't get a mismatching | ||
// cert / key pair by reading the attributes halfway through the update | ||
// below. | ||
fca.cert = cert | ||
fca.key = key | ||
} | ||
|
||
func (fca *fileCA) getX509KeyPair() (*x509.Certificate, crypto.Signer) { | ||
fca.RLock() | ||
defer fca.RUnlock() | ||
return fca.cert, fca.key | ||
} | ||
|
||
// CreateCertificate issues code signing certificates | ||
func (fca *fileCA) CreateCertificate(_ context.Context, subject *challenges.ChallengeResult) (*ca.CodeSigningCertificate, error) { | ||
cert, err := x509ca.MakeX509(subject) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
rootCA, privateKey := fca.getX509KeyPair() | ||
|
||
finalCertBytes, err := x509.CreateCertificate(rand.Reader, cert, rootCA, subject.PublicKey, privateKey) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return ca.CreateCSCFromDER(subject, finalCertBytes, 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,64 @@ | ||
// 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 fileca | ||
|
||
import ( | ||
"crypto/ecdsa" | ||
"crypto/ed25519" | ||
"testing" | ||
) | ||
|
||
func TestNewFileCA(t *testing.T) { | ||
_, err := NewFileCA(`testdata/ed25519-cert.pem`, `testdata/ed25519-key.pem`, false) | ||
if err != nil { | ||
t.Error(`Failed to load file CA from disk`) | ||
} | ||
} | ||
|
||
func TestCertUpdate(t *testing.T) { | ||
oldCert := `testdata/ed25519-cert.pem` | ||
oldKey := `testdata/ed25519-key.pem` | ||
newCert := `testdata/ecdsa-cert.pem` | ||
newKey := `testdata/ecdsa-key.pem` | ||
watch := false | ||
|
||
ca, err := NewFileCA(oldCert, oldKey, watch) | ||
if err != nil { | ||
t.Fatal(`Failed to load file CA from disk`) | ||
} | ||
|
||
fca, ok := ca.(*fileCA) | ||
if !ok { | ||
t.Fatal(`Bad CA type`) | ||
} | ||
|
||
_, key := fca.getX509KeyPair() | ||
if _, ok = key.(ed25519.PrivateKey); !ok { | ||
t.Error(`first key should have been an ed25519 key`) | ||
} | ||
|
||
cert, key, err := loadKeyPair(newCert, newKey) | ||
if err != nil { | ||
t.Fatal(`Failed to load new keypair`) | ||
} | ||
|
||
fca.updateX509KeyPair(cert, key) | ||
_, key = fca.getX509KeyPair() | ||
|
||
if _, ok = key.(*ecdsa.PrivateKey); !ok { | ||
t.Fatal(`file CA should have been updated with ecdsa key`) | ||
} | ||
} |
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,61 @@ | ||
// 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 fileca | ||
|
||
import ( | ||
"crypto" | ||
"crypto/tls" | ||
"crypto/x509" | ||
"errors" | ||
) | ||
|
||
func loadKeyPair(certPath, keyPath string) (*x509.Certificate, crypto.Signer, error) { | ||
tlsCert, err := tls.LoadX509KeyPair(certPath, keyPath) | ||
if err != nil { | ||
return nil, nil, err | ||
} | ||
|
||
var ( | ||
key crypto.Signer | ||
cert *x509.Certificate | ||
) | ||
{ | ||
var ok bool | ||
key, ok = tlsCert.PrivateKey.(crypto.Signer) | ||
if !ok { | ||
// Shouldn't be reachable because of the data validation that | ||
// tls.LoadX509KeyPair already did | ||
return nil, nil, errors.New(`fileca: loaded private key can't be used to sign`) | ||
} | ||
|
||
if len(tlsCert.Certificate) != 1 { | ||
return nil, nil, errors.New(`fileca: expected certificate chain with exactly one cert`) | ||
} | ||
|
||
cert, err = x509.ParseCertificate(tlsCert.Certificate[0]) | ||
if err != nil { | ||
// Also shouldn't be reachable because tls.LoadX509KeyPair | ||
// already parsed this cert to validate it. | ||
return nil, nil, err | ||
} | ||
} | ||
|
||
if !cert.IsCA { | ||
return nil, nil, errors.New(`fileca: certificate is not a CA`) | ||
} | ||
|
||
return cert, key, 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,56 @@ | ||
// 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 fileca | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
) | ||
|
||
func TestValidLoadKeyPair(t *testing.T) { | ||
keypairs := []string{ | ||
"ecdsa", | ||
"ed25519", | ||
"rsa4096", | ||
} | ||
|
||
for _, keypair := range keypairs { | ||
keyPath := fmt.Sprintf("testdata/%s-key.pem", keypair) | ||
certPath := fmt.Sprintf("testdata/%s-cert.pem", keypair) | ||
|
||
_, _, err := loadKeyPair(certPath, keyPath) | ||
if err != nil { | ||
t.Errorf("Failed to load key pair of type %s", keypair) | ||
} | ||
} | ||
} | ||
|
||
func TestInvalidLoadKeyPair(t *testing.T) { | ||
keypairs := []string{ | ||
"notca", | ||
"mismatch", | ||
} | ||
|
||
for _, keypair := range keypairs { | ||
keyPath := fmt.Sprintf("testdata/%s-key.pem", keypair) | ||
certPath := fmt.Sprintf("testdata/%s-cert.pem", keypair) | ||
|
||
_, _, err := loadKeyPair(certPath, keyPath) | ||
if err == nil { | ||
t.Errorf("Expected invalid key pair of type %s to fail to load", keypair) | ||
} | ||
} | ||
} |
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,12 @@ | ||
-----BEGIN CERTIFICATE----- | ||
MIIByDCCAU6gAwIBAgIUQGIeaoDYeu1gbfZDZCbA4tRfcBgwCgYIKoZIzj0EAwIw | ||
EDEOMAwGA1UEAwwFZWNkc2EwIBcNMjExMjE2MDI0NjAyWhgPMjEyMTExMjIwMjQ2 | ||
MDJaMBAxDjAMBgNVBAMMBWVjZHNhMHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEDBCL | ||
v9Z3ESXJK5aJ1z2iJ2pMuSByDuTGDCdJZ7uZw9DCrZvuwpo69CHUSf7a1Hm1Ly0G | ||
q0Vanuhm5gxHly7LoGWU95wWpgs+uLnXlpEIsEiO7JOUWruBb1IfFYet5jPEo2cw | ||
ZTAdBgNVHQ4EFgQUrhwW+5E6odLZfdJjkE0T4BQYIaQwHwYDVR0jBBgwFoAUrhwW | ||
+5E6odLZfdJjkE0T4BQYIaQwDwYDVR0TAQH/BAUwAwEB/zASBgNVHRMBAf8ECDAG | ||
AQH/AgEBMAoGCCqGSM49BAMCA2gAMGUCMGwGmkqlYRYaUTsSAITMwjdfqd1QfCg/ | ||
M9OY6NJd8MMAc0FgLT9mliTcnlP6NB0+uQIxAM86EGJkdqTf0fs4TNJut9dEu+Sb | ||
tvwHCNsV8/WeNbcX7WI/6TEO1ln9EpVEhFKT/A== | ||
-----END CERTIFICATE----- |
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,6 @@ | ||
-----BEGIN PRIVATE KEY----- | ||
MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAoEEUZMXem58C10Mjq | ||
NQtV5AoAHIbUBHlHofzlkB85XoQ/YuGSUJUGm9VUSX9sCaShZANiAAQMEIu/1ncR | ||
JckrlonXPaInaky5IHIO5MYMJ0lnu5nD0MKtm+7Cmjr0IdRJ/trUebUvLQarRVqe | ||
6GbmDEeXLsugZZT3nBamCz64udeWkQiwSI7sk5Rau4FvUh8Vh63mM8Q= | ||
-----END PRIVATE KEY----- |
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,10 @@ | ||
-----BEGIN CERTIFICATE----- | ||
MIIBTzCCAQGgAwIBAgIUEvRW73iZMiv+Uj9/TeIlBjMXjqUwBQYDK2VwMBIxEDAO | ||
BgNVBAMMB2VkMjU1MTkwIBcNMjExMjE2MDI0MTA4WhgPMjEyMTExMjIwMjQxMDha | ||
MBIxEDAOBgNVBAMMB2VkMjU1MTkwKjAFBgMrZXADIQASlHPcRjDzpeZzxulCYLBr | ||
vNUpIY+U/VRQWX2bVOGt5aNnMGUwHQYDVR0OBBYEFLZsJymHlUHELUFEqQ/Ct+oW | ||
hhF5MB8GA1UdIwQYMBaAFLZsJymHlUHELUFEqQ/Ct+oWhhF5MA8GA1UdEwEB/wQF | ||
MAMBAf8wEgYDVR0TAQH/BAgwBgEB/wIBATAFBgMrZXADQQDRWFn9bb6lnov5MF02 | ||
Agu9UyyPHq9icGBrEzgo+XKD5IbWI47WkaMkwb5teKX/soUuvBdpx01A2mjLIldT | ||
EG8P | ||
-----END CERTIFICATE----- |
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,3 @@ | ||
-----BEGIN PRIVATE KEY----- | ||
MC4CAQAwBQYDK2VwBCIEIJupNKg2i5A9z95BaXw6O9DAii4Jw9dqV1qs3i4ojPsb | ||
-----END PRIVATE KEY----- |
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,10 @@ | ||
-----BEGIN CERTIFICATE----- | ||
MIIBUTCCAQOgAwIBAgIUaw66azMJaY94U7BtBZ7c/XYdl0YwBQYDK2VwMBMxETAP | ||
BgNVBAMMCG1pc21hdGNoMCAXDTIxMTIxNjAyNDczM1oYDzIxMjExMTIyMDI0NzMz | ||
WjATMREwDwYDVQQDDAhtaXNtYXRjaDAqMAUGAytlcAMhAOqTJbZpBETmbyPdx2oF | ||
Yux9k0okt6wyOVykT9k3SBzPo2cwZTAdBgNVHQ4EFgQU+KMr0c2O0ONyvYV8wTDw | ||
HD/yBywwHwYDVR0jBBgwFoAU+KMr0c2O0ONyvYV8wTDwHD/yBywwDwYDVR0TAQH/ | ||
BAUwAwEB/zASBgNVHRMBAf8ECDAGAQH/AgEBMAUGAytlcANBAEIfbtgklrBjV6H5 | ||
/UyHlegf9Dms4W34qPkv7UrTwGkMay13And04fQUhgQ/M0B/WzV2LEoaL9Vk/08N | ||
f4zT3w0= | ||
-----END CERTIFICATE----- |
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,3 @@ | ||
-----BEGIN PRIVATE KEY----- | ||
MC4CAQAwBQYDK2VwBCIEIJupNKg2i5A9z95BaXw6O9DAii4Jw9dqV1qs3i4ojPsb | ||
-----END PRIVATE KEY----- |
Oops, something went wrong.