generated from sigstore/sigstore-project-template
-
Notifications
You must be signed in to change notification settings - Fork 63
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This fixes signing commits with a timestamp authority. To do this we forked the ietf-cms library from smimesign in order to do the following: - Fixed timestamp before/after checks when the code signing cert issue time matches the signed timestamp. This check should be inclusive. Keyless signing is particularly susceptible to this since we issue certs on the fly. - Allowed for configurable cert pools for TSA and commit verification. Previously smimesign assumed these would be one in the same, based on the system pool. This allows for different verification options to be configued in order to prevent letting the TSA certs verify the commit signature cert. - Adds GITSIGN_TIMESTAMP_CERT option to allow loading in of TSA certs. - Renames GITSIGN_TIMESTAMP_AUTHORITY to GITSIGN_TIMESTAMP_URL. We're choosing to fork here since upstream hasn't been responsive to PRs. Signed-off-by: Billy Lynch <billy@chainguard.dev> Signed-off-by: Billy Lynch <billy@chainguard.dev>
- Loading branch information
Showing
21 changed files
with
3,596 additions
and
8 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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -34,3 +34,5 @@ issues: | |
run: | ||
issues-exit-code: 1 | ||
timeout: 10m | ||
skip-dirs: | ||
- internal/fork |
Large diffs are not rendered by default.
Oops, something went wrong.
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
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,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2017 GitHub, Inc. | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
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 @@ | ||
# CMS | ||
|
||
This package is forked from [github/smimesign](https://github.com/github/smimesign) with the following changes: | ||
|
||
- Adds inclusive checking for cert timestamps in timestamp authorities (https://github.com/github/smimesign/pull/121) | ||
- Fixes tests for MacOS due to regressions in return types in Go 1.18 crypto libraries (https://github.com/golang/go/issues/52010) | ||
- Adds support for separate cert pools for cert validation and TSA validation. | ||
|
||
[CMS (Cryptographic Message Syntax)](https://tools.ietf.org/html/rfc5652) is a syntax for signing, digesting, and encrypting arbitrary messages. It evolved from PKCS#7 and is the basis for higher level protocols such as S/MIME. This package implements the SignedData CMS content-type, allowing users to digitally sign data as well as verify data signed by others. | ||
|
||
## Signing and Verifying Data | ||
|
||
High level APIs are provided for signing a message with a certificate and key: | ||
|
||
```go | ||
msg := []byte("some data") | ||
cert, _ := x509.ParseCertificate(someCertificateData) | ||
key, _ := x509.ParseECPrivateKey(somePrivateKeyData) | ||
|
||
der, _ := cms.Sign(msg, []*x509.Certificate{cert}, key) | ||
|
||
//// | ||
/// At another time, in another place... | ||
// | ||
|
||
sd, _ := ParseSignedData(der) | ||
if err, _ := sd.Verify(x509.VerifyOptions{}); err != nil { | ||
panic(err) | ||
} | ||
``` | ||
|
||
By default, CMS SignedData includes the original message. High level APIs are also available for creating and verifying detached signatures: | ||
|
||
```go | ||
msg := []byte("some data") | ||
cert, _ := x509.ParseCertificate(someCertificateData) | ||
key, _ := x509.ParseECPrivateKey(somePrivateKeyData) | ||
|
||
der, _ := cms.SignDetached(msg, cert, key) | ||
|
||
//// | ||
/// At another time, in another place... | ||
// | ||
|
||
sd, _ := ParseSignedData(der) | ||
if err, _ := sd.VerifyDetached(msg, x509.VerifyOptions{}); err != nil { | ||
panic(err) | ||
} | ||
``` | ||
|
||
## Timestamping | ||
|
||
Because certificates expire and can be revoked, it is may be helpful to attach certified timestamps to signatures, proving that they existed at a given time. RFC3161 timestamps can be added to signatures like so: | ||
|
||
```go | ||
signedData, _ := NewSignedData([]byte("Hello, world!")) | ||
signedData.Sign(identity.Chain(), identity.PrivateKey) | ||
signedData.AddTimestamps("http://timestamp.digicert.com") | ||
|
||
derEncoded, _ := signedData.ToDER() | ||
io.Copy(os.Stdout, bytes.NewReader(derEncoded)) | ||
``` | ||
|
||
Verification functions implicitly verify timestamps as well. Without a timestamp, verification will fail if the certificate is no longer valid. |
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,166 @@ | ||
package cms | ||
|
||
import ( | ||
"bytes" | ||
"crypto/ecdsa" | ||
"crypto/elliptic" | ||
"crypto/rand" | ||
"crypto/x509" | ||
"encoding/asn1" | ||
"io" | ||
"io/ioutil" | ||
"math/big" | ||
"net/http" | ||
"time" | ||
|
||
"github.com/github/smimesign/fakeca" | ||
"github.com/github/smimesign/ietf-cms/oid" | ||
"github.com/github/smimesign/ietf-cms/protocol" | ||
"github.com/sigstore/gitsign/internal/fork/ietf-cms/timestamp" | ||
) | ||
|
||
var ( | ||
// fake PKI setup | ||
root = fakeca.New(fakeca.IsCA) | ||
otherRoot = fakeca.New(fakeca.IsCA) | ||
|
||
intermediateKey, _ = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) | ||
intermediate = root.Issue(fakeca.IsCA, fakeca.PrivateKey(intermediateKey)) | ||
|
||
leaf = intermediate.Issue( | ||
fakeca.NotBefore(time.Now().Add(-time.Hour)), | ||
fakeca.NotAfter(time.Now().Add(time.Hour)), | ||
) | ||
|
||
rootOpts = x509.VerifyOptions{Roots: root.ChainPool()} | ||
otherRootOpts = x509.VerifyOptions{Roots: otherRoot.ChainPool()} | ||
intermediateOpts = x509.VerifyOptions{Roots: intermediate.ChainPool()} | ||
|
||
// fake timestamp authority setup | ||
tsa = &testTSA{ident: intermediate.Issue()} | ||
thc = &testHTTPClient{tsa} | ||
) | ||
|
||
func init() { | ||
timestamp.DefaultHTTPClient = thc | ||
} | ||
|
||
type testTSA struct { | ||
ident *fakeca.Identity | ||
sn int64 | ||
hookInfo func(timestamp.Info) timestamp.Info | ||
hookToken func(*protocol.SignedData) *protocol.SignedData | ||
hookResponse func(timestamp.Response) timestamp.Response | ||
} | ||
|
||
func (tt *testTSA) Clear() { | ||
tt.hookInfo = nil | ||
tt.hookToken = nil | ||
tt.hookResponse = nil | ||
} | ||
|
||
func (tt *testTSA) HookInfo(hook func(timestamp.Info) timestamp.Info) { | ||
tt.Clear() | ||
tt.hookInfo = hook | ||
} | ||
|
||
func (tt *testTSA) HookToken(hook func(*protocol.SignedData) *protocol.SignedData) { | ||
tt.Clear() | ||
tt.hookToken = hook | ||
} | ||
|
||
func (tt *testTSA) HookResponse(hook func(timestamp.Response) timestamp.Response) { | ||
tt.Clear() | ||
tt.hookResponse = hook | ||
} | ||
|
||
func (tt *testTSA) nextSN() *big.Int { | ||
defer func() { tt.sn++ }() | ||
return big.NewInt(tt.sn) | ||
} | ||
|
||
func (tt *testTSA) Do(req timestamp.Request) (timestamp.Response, error) { | ||
info := timestamp.Info{ | ||
Version: 1, | ||
Policy: asn1.ObjectIdentifier{1, 2, 3}, | ||
SerialNumber: tt.nextSN(), | ||
GenTime: time.Now(), | ||
MessageImprint: req.MessageImprint, | ||
Nonce: req.Nonce, | ||
} | ||
|
||
if tt.hookInfo != nil { | ||
info = tt.hookInfo(info) | ||
} | ||
|
||
eciDER, err := asn1.Marshal(info) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
eci, err := protocol.NewEncapsulatedContentInfo(oid.ContentTypeTSTInfo, eciDER) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
tst, err := protocol.NewSignedData(eci) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
if err = tst.AddSignerInfo(tsa.ident.Chain(), tsa.ident.PrivateKey); err != nil { | ||
panic(err) | ||
} | ||
|
||
if tt.hookToken != nil { | ||
tt.hookToken(tst) | ||
} | ||
|
||
ci, err := tst.ContentInfo() | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
resp := timestamp.Response{ | ||
Status: timestamp.PKIStatusInfo{Status: 0}, | ||
TimeStampToken: ci, | ||
} | ||
|
||
if tt.hookResponse != nil { | ||
resp = tt.hookResponse(resp) | ||
} | ||
|
||
return resp, nil | ||
} | ||
|
||
type testHTTPClient struct { | ||
tt *testTSA | ||
} | ||
|
||
func (thc *testHTTPClient) Do(httpReq *http.Request) (*http.Response, error) { | ||
buf := new(bytes.Buffer) | ||
if _, err := io.Copy(buf, httpReq.Body); err != nil { | ||
return nil, err | ||
} | ||
|
||
var tsReq timestamp.Request | ||
if _, err := asn1.Unmarshal(buf.Bytes(), &tsReq); err != nil { | ||
return nil, err | ||
} | ||
|
||
tsResp, err := thc.tt.Do(tsReq) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
respDER, err := asn1.Marshal(tsResp) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
return &http.Response{ | ||
StatusCode: 200, | ||
Header: http.Header{"Content-Type": {"application/timestamp-reply"}}, | ||
Body: ioutil.NopCloser(bytes.NewReader(respDER)), | ||
}, nil | ||
} |
Oops, something went wrong.