Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start adding more verification with VerificationOpts struct #153

Merged
merged 77 commits into from
Dec 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
fcfceba
add verification opts struct
malancas Nov 8, 2022
f0c2d4b
add optional nonce arg
malancas Nov 8, 2022
6905a33
Merge branch 'main' into verify-opts-struct
malancas Nov 9, 2022
857d175
add oid flag
malancas Nov 16, 2022
fa279a2
start adding more verification functions
malancas Nov 16, 2022
3025cbf
add verification calls
malancas Nov 17, 2022
ccb8a18
add more verification functions and tests
malancas Nov 17, 2022
35515ca
fix linter errors
malancas Nov 17, 2022
13989e8
more linter fixes
malancas Nov 17, 2022
567a92d
update testing
malancas Nov 17, 2022
ccd6671
handle optional flags, reorganize code
malancas Nov 17, 2022
b7c9c9f
centralize verification function calls
malancas Nov 18, 2022
31e870a
move flag parsing to cli app package
malancas Nov 21, 2022
008d1da
move newVerifyOpts
malancas Nov 21, 2022
8b296db
remove unused function
malancas Nov 21, 2022
a0de29c
add more error messaging
malancas Nov 21, 2022
39f901b
return correct number of values
malancas Nov 21, 2022
7e61435
typo
malancas Nov 21, 2022
b6002a2
run formatter
malancas Nov 21, 2022
2faf315
update test data
malancas Nov 21, 2022
6096026
udpate issuer comparison
malancas Nov 21, 2022
9a041fb
PR feedback
malancas Nov 28, 2022
9c432f7
update function names
malancas Nov 28, 2022
4278d20
gather leaf cert verification under a single function
malancas Nov 28, 2022
3060888
fix linting errors
malancas Nov 28, 2022
950369c
fix error in test
malancas Nov 28, 2022
04abe51
udpate tests with new types
malancas Nov 29, 2022
11f21fb
update verification to handle presence or absence of leaf certs
malancas Nov 29, 2022
30c19c8
Update cmd/timestamp-cli/app/verify.go
malancas Nov 30, 2022
0380bf6
Update cmd/timestamp-cli/app/verify.go
malancas Nov 30, 2022
e4d0be8
Update pkg/verification/verify.go
malancas Nov 30, 2022
bef558c
Update pkg/verification/verify.go
malancas Nov 30, 2022
da026b5
PR feedback
malancas Nov 30, 2022
6cbe2ff
update flag name
malancas Nov 30, 2022
e913a34
better function name
malancas Nov 30, 2022
7638c84
update flag name
malancas Nov 30, 2022
ff25635
pr feedback
malancas Nov 30, 2022
34d3e92
use function for parsing cert
malancas Nov 30, 2022
a4eaa70
use library function for parsing certs from pem file
malancas Nov 30, 2022
45c6c40
pr feedback
malancas Nov 30, 2022
d98b439
format files
malancas Nov 30, 2022
dca61a8
Merge branch 'main' into verify-opts-struct
malancas Dec 1, 2022
7dd21bc
remove unused function
malancas Dec 1, 2022
3a09b61
update func name and var testing
malancas Dec 1, 2022
16e5d2e
Update cmd/timestamp-cli/app/verify.go
malancas Dec 3, 2022
bf04ae7
PR feedback
malancas Dec 5, 2022
e19c4c2
Update pkg/verification/verify.go
malancas Dec 5, 2022
055c5c6
Update pkg/verification/verify.go
malancas Dec 5, 2022
c36e033
Update pkg/verification/verify.go
malancas Dec 5, 2022
733cb82
Update pkg/verification/verify.go
malancas Dec 5, 2022
f95598a
Update pkg/verification/verify.go
malancas Dec 5, 2022
f57176d
Update pkg/verification/verify.go
malancas Dec 5, 2022
a1f4be6
Update pkg/verification/verify.go
malancas Dec 5, 2022
390ba41
Update pkg/verification/verify.go
malancas Dec 5, 2022
f5a8e08
Update pkg/verification/verify.go
malancas Dec 5, 2022
e507546
Update pkg/verification/verify.go
malancas Dec 5, 2022
0834b94
PR feedback
malancas Dec 5, 2022
95d3f00
Merge branch 'verify-opts-struct' of github.com:malancas/timestamp-au…
malancas Dec 5, 2022
163fd99
add more comments
malancas Dec 5, 2022
90fa5e1
formatting
malancas Dec 5, 2022
b83f333
update eku chaining function to take leaf cert as a parameter
malancas Dec 5, 2022
4bff132
reoganized some functions
malancas Dec 5, 2022
807cbc8
add leaf verification tests
malancas Dec 5, 2022
93b9e20
formatting
malancas Dec 5, 2022
7c04f4b
add nonce and policy oid flags to verification test
malancas Dec 5, 2022
0ed6163
add common name arg to verification test
malancas Dec 5, 2022
c6dc949
update verify test to include more flags. add verify test with certif…
malancas Dec 6, 2022
9f08373
Update pkg/verification/verify.go
malancas Dec 6, 2022
137d49b
Update pkg/verification/verify.go
malancas Dec 6, 2022
1bc74b8
Update pkg/verification/verify.go
malancas Dec 6, 2022
215c8eb
clean up field if statements
malancas Dec 6, 2022
47660e6
add temporary skip statement for test
malancas Dec 6, 2022
c93a3ed
formatting
malancas Dec 6, 2022
131993e
Merge branch 'verify-opts-struct' of github.com:malancas/timestamp-au…
malancas Dec 6, 2022
0bbd9d5
update code for linter
malancas Dec 7, 2022
fc33633
update verification to add the leaf cert to the pkcs7 object
malancas Dec 8, 2022
7545817
fix linter error
malancas Dec 8, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ To fetch a timestamp with the provided `timestamp-cli`:
1. Create test blob to sign: `echo "myblob" > myblob`
1. Build client: `make timestamp-cli`
1. Fetch timestamp: `./bin/timestamp-cli --timestamp_server http://localhost:3000 timestamp --hash sha256 --artifact myblob --out response.tsr`
1. Verify timestamp: `./bin/timestamp-cli verify --timestamp response.tsr --artifact "myblob" --cert-chain ts_chain.pem`
1. Verify timestamp: `./bin/timestamp-cli verify --timestamp response.tsr --artifact "myblob" --certificate-chain ts_chain.pem`
1. Inspect timestamp: `./bin/timestamp-cli inspect --timestamp response.tsr --format json`

To fetch a timestamp with `openssl` and `curl`:
Expand Down
142 changes: 129 additions & 13 deletions cmd/timestamp-cli/app/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,13 @@ package app
import (
"crypto/x509"
"fmt"
"math/big"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/sigstore/sigstore/pkg/cryptoutils"
"github.com/sigstore/timestamp-authority/cmd/timestamp-cli/app/format"
"github.com/sigstore/timestamp-authority/pkg/log"
"github.com/sigstore/timestamp-authority/pkg/verification"
Expand All @@ -41,8 +45,12 @@ func addVerifyFlags(cmd *cobra.Command) {
cmd.MarkFlagRequired("artifact") //nolint:errcheck
cmd.Flags().Var(NewFlagValue(fileFlag, ""), "timestamp", "path to timestamp response to verify")
cmd.MarkFlagRequired("timestamp") //nolint:errcheck
cmd.Flags().Var(NewFlagValue(fileFlag, ""), "cert-chain", "path to certificate chain PEM file")
cmd.MarkFlagRequired("cert-chain") //nolint:errcheck
cmd.Flags().Var(NewFlagValue(fileFlag, ""), "certificate-chain", "path to file with PEM-encoded certificate chain. Ordered from intermediate CA certificate that issued the TSA certificate, ending with the root CA certificate.")
cmd.MarkFlagRequired("certificate-chain") //nolint:errcheck
cmd.Flags().String("nonce", "", "optional nonce passed with the request")
cmd.Flags().Var(NewFlagValue(oidFlag, ""), "oid", "optional TSA policy OID passed with the request")
cmd.Flags().String("common-name", "", "expected leaf certificate subject common name")
cmd.Flags().Var(NewFlagValue(fileFlag, ""), "certificate", "path to file with PEM-encoded leaf certificate")
}

var verifyCmd = &cobra.Command{
Expand All @@ -67,27 +75,135 @@ func runVerify() (interface{}, error) {
return nil, fmt.Errorf("error reading request from file: %w", err)
}

certChainPEM := viper.GetString("cert-chain")
pemBytes, err := os.ReadFile(filepath.Clean(certChainPEM))
artifactPath := viper.GetString("artifact")
artifact, err := os.Open(filepath.Clean(artifactPath))
if err != nil {
return nil, fmt.Errorf("error reading request from file: %w", err)
return nil, err
}

opts, err := newVerifyOpts()
if err != nil {
return verifyCmdOutput{TimestampPath: tsrPath}, fmt.Errorf("failed to created VerifyOpts: %w", err)
}

err = verification.VerifyTimestampResponse(tsrBytes, artifact, opts)

return &verifyCmdOutput{TimestampPath: tsrPath}, err
}

func newVerifyOpts() (verification.VerifyOpts, error) {
opts := verification.VerifyOpts{}

oid, err := getOID()
if err != nil {
return verification.VerifyOpts{}, fmt.Errorf("failed to parse value from oid flag: %w", err)
}
opts.OID = oid

certPathFlagVal := viper.GetString("certificate")
if certPathFlagVal != "" {
cert, err := parseTSACertificate(certPathFlagVal)
if err != nil {
return verification.VerifyOpts{}, fmt.Errorf("failed to parse cert flag value from PEM file: %w", err)
}
opts.TSACertificate = cert
}

roots, intermediates, err := getRootAndIntermediateCerts()
if err != nil {
return verification.VerifyOpts{}, fmt.Errorf("failed to parse root and intermediate certs from certificate-chain flag: %w", err)
}
opts.Roots = roots
opts.Intermediates = intermediates

nonce, err := getNonce()
if err != nil {
return verification.VerifyOpts{}, fmt.Errorf("failed to parse value from nonce flag: %w", err)
}
opts.Nonce = nonce

commonNameFlagVal := viper.GetString("common-name")
opts.CommonName = commonNameFlagVal

return opts, nil
}

func getNonce() (*big.Int, error) {
nonceFlagVal := viper.GetString("nonce")
if nonceFlagVal == "" {
return nil, nil
}

certPool := x509.NewCertPool()
ok := certPool.AppendCertsFromPEM(pemBytes)
nonce := new(big.Int)
nonce, ok := nonce.SetString(nonceFlagVal, 10)
if !ok {
return nil, fmt.Errorf("error parsing response into Timestamp while appending certs from PEM")
return nil, fmt.Errorf("failed to convert string to big.Int")
}
return nonce, nil
}

artifactPath := viper.GetString("artifact")
artifact, err := os.Open(filepath.Clean(artifactPath))
func getRootAndIntermediateCerts() ([]*x509.Certificate, []*x509.Certificate, error) {
certChainPEM := viper.GetString("certificate-chain")
if certChainPEM == "" {
return nil, nil, nil
}

pemBytes, err := os.ReadFile(filepath.Clean(certChainPEM))
if err != nil {
return nil, err
return nil, nil, fmt.Errorf("error reading request from file: %w", err)
}

err = verification.VerifyTimestampResponse(tsrBytes, artifact, certPool)
certs, err := cryptoutils.UnmarshalCertificatesFromPEM(pemBytes)
if err != nil {
return nil, nil, fmt.Errorf("failed to parse intermediate and root certs from PEM file: %w", err)
}

return &verifyCmdOutput{TimestampPath: tsrPath}, err
if len(certs) == 0 {
return nil, nil, fmt.Errorf("expected at least one certificate to represent the root")
}

// intermediate certs are above the root certificate in the PEM file
intermediateCerts := certs[0 : len(certs)-1]
// the root certificate is last in the PEM file
rootCerts := []*x509.Certificate{certs[len(certs)-1]}

return rootCerts, intermediateCerts, nil
}

func getOID() ([]int, error) {
oidFlagVal := viper.GetString("oid")
if oidFlagVal == "" {
return nil, nil
}

oidStrSlice := strings.Split(oidFlagVal, ".")
oid := make([]int, len(oidStrSlice))
for i, el := range oidStrSlice {
intVar, err := strconv.Atoi(el)
if err != nil {
return nil, err
}
oid[i] = intVar
}

return oid, nil
}

func parseTSACertificate(certPath string) (*x509.Certificate, error) {
pemBytes, err := os.ReadFile(filepath.Clean(certPath))
if err != nil {
return nil, fmt.Errorf("error reading TSA's certificate file: %w", err)
}

certs, err := cryptoutils.UnmarshalCertificatesFromPEM(pemBytes)
if err != nil {
return nil, fmt.Errorf("failed to parse TSA certificate during verification: %w", err)
}
if len(certs) != 1 {
return nil, fmt.Errorf("expected one certificate, received %d instead", len(certs))
}

return certs[0], nil
}

func init() {
Expand Down
94 changes: 77 additions & 17 deletions pkg/tests/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package tests
import (
"bytes"
"crypto"
"encoding/asn1"
"encoding/json"
"errors"
"io"
Expand All @@ -41,7 +42,7 @@ const (
func TestInspect(t *testing.T) {
serverURL := createServer(t)

tsrPath := getTimestamp(t, serverURL, "blob")
tsrPath := getTimestamp(t, serverURL, "blob", big.NewInt(0), nil, true)

// It should create timestamp successfully.
out := runCli(t, "inspect", "--timestamp", tsrPath, "--format", "json")
Expand Down Expand Up @@ -79,20 +80,50 @@ func TestVerify(t *testing.T) {
artifactContent := "blob"
artifactPath := makeArtifact(t, artifactContent)

tsrPath := getTimestamp(t, restapiURL, artifactContent)
// this is the common name for the in-memory leaf certificate, copied
// from pkg/signer/memory.go
commonName := "Test TSA Timestamping"
nonce := big.NewInt(456)
policyOID := asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 2}
tsrContainsCerts := true

tsrPath := getTimestamp(t, restapiURL, artifactContent, nonce, policyOID, tsrContainsCerts)

// write the cert chain to a PEM file
pemPath := getCertChainPEM(t, restapiURL)
_, certChainPemPath := writeCertChainToPEMFiles(t, restapiURL)

// It should verify timestamp successfully.
out := runCli(t, "--timestamp_server", restapiURL, "verify", "--timestamp", tsrPath, "--artifact", artifactPath, "--cert-chain", pemPath)
out := runCli(t, "--timestamp_server", restapiURL, "verify", "--timestamp", tsrPath, "--artifact", artifactPath, "--certificate-chain", certChainPemPath, "--nonce", nonce.String(), "--oid", policyOID.String(), "--common-name", commonName)
outputContains(t, out, "Successfully verified timestamp")
}

func TestVerifyPassLeafCertificate(t *testing.T) {
restapiURL := createServer(t)

artifactContent := "blob"
artifactPath := makeArtifact(t, artifactContent)

// this is the common name for the in-memory leaf certificate, copied
// from pkg/signer/memory.go
commonName := "Test TSA Timestamping"
nonce := big.NewInt(456)
policyOID := asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 2}
tsrContainsCerts := false

tsrPath := getTimestamp(t, restapiURL, artifactContent, nonce, policyOID, tsrContainsCerts)

// write the cert chain to a PEM file
leafCertPemPath, certChainPemPath := writeCertChainToPEMFiles(t, restapiURL)

// It should verify timestamp successfully.
out := runCli(t, "--timestamp_server", restapiURL, "verify", "--timestamp", tsrPath, "--artifact", artifactPath, "--certificate-chain", certChainPemPath, "--nonce", nonce.String(), "--oid", policyOID.String(), "--common-name", commonName, "--certificate", leafCertPemPath)
outputContains(t, out, "Successfully verified timestamp")
}

func TestVerify_InvalidTSR(t *testing.T) {
restapiURL := createServer(t)

pemPath := getCertChainPEM(t, restapiURL)
_, pemPath := writeCertChainToPEMFiles(t, restapiURL)

artifactContent := "blob"
artifactPath := makeArtifact(t, artifactContent)
Expand All @@ -104,7 +135,7 @@ func TestVerify_InvalidTSR(t *testing.T) {
}

// It should return a message that the PEM is not valid
out := runCliErr(t, "--timestamp_server", restapiURL, "verify", "--timestamp", invalidTSR, "--artifact", artifactPath, "--cert-chain", pemPath)
out := runCliErr(t, "--timestamp_server", restapiURL, "verify", "--timestamp", invalidTSR, "--artifact", artifactPath, "--certificate-chain", pemPath)
outputContains(t, out, "error parsing response into Timestamp")
}

Expand All @@ -114,17 +145,17 @@ func TestVerify_InvalidPEM(t *testing.T) {
artifactContent := "blob"
artifactPath := makeArtifact(t, artifactContent)

tsrPath := getTimestamp(t, restapiURL, artifactContent)
tsrPath := getTimestamp(t, restapiURL, artifactContent, big.NewInt(0), nil, true)

// Create invalid pem
invalidPEMPath := filepath.Join(t.TempDir(), "ts_chain.pem")
invalidPEMPath := filepath.Join(t.TempDir(), "invalid_pem_path")
if err := os.WriteFile(invalidPEMPath, []byte("invalid PEM"), 0600); err != nil {
t.Fatal(err)
}

// It should return a message that the PEM is not valid
out := runCliErr(t, "--timestamp_server", restapiURL, "verify", "--timestamp", tsrPath, "--artifact", artifactPath, "--cert-chain", invalidPEMPath)
outputContains(t, out, "error parsing response into Timestamp while appending certs from PEM")
out := runCliErr(t, "--timestamp_server", restapiURL, "verify", "--timestamp", tsrPath, "--artifact", artifactPath, "--certificate-chain", invalidPEMPath)
outputContains(t, out, "failed to parse intermediate and root certs from PEM file")
}

func runCliErr(t *testing.T, arg ...string) string {
Expand Down Expand Up @@ -178,17 +209,27 @@ func outputContains(t *testing.T, output, sub string) {
}
}

func getTimestamp(t *testing.T, url string, artifactContent string) string {
func getTimestamp(t *testing.T, url string, artifactContent string, nonce *big.Int, policyOID asn1.ObjectIdentifier, tsrContainsCerts bool) string {
c, err := client.GetTimestampClient(url)
if err != nil {
t.Fatalf("unexpected error creating client: %v", err)
}

tsNonce := big.NewInt(1234)
if nonce != nil {
tsNonce = nonce
}

tsPolicyOID := asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 57264, 2}
if policyOID != nil {
tsPolicyOID = policyOID
}

tsq, err := ts.CreateRequest(strings.NewReader(artifactContent), &ts.RequestOptions{
Hash: crypto.SHA256,
Certificates: true,
Certificates: tsrContainsCerts,
Nonce: tsNonce,
TSAPolicyOID: tsPolicyOID,
})
if err != nil {
t.Fatalf("unexpected error creating request: %v", err)
Expand All @@ -211,8 +252,10 @@ func getTimestamp(t *testing.T, url string, artifactContent string) string {
return path
}

// getCertChainPEM returns the CA certificates to verify a signed timestamp
func getCertChainPEM(t *testing.T, restapiURL string) string {
// getCertChainPEM returns the path of a pem file containaing
// the leaf certificate and the path of a pem file containing the
// root and intermediate certificates. Used to verify a signed timestamp
func writeCertChainToPEMFiles(t *testing.T, restapiURL string) (string, string) {
c, err := client.GetTimestampClient(restapiURL)
if err != nil {
t.Fatalf("unexpected error creating client: %v", err)
Expand All @@ -223,8 +266,9 @@ func getCertChainPEM(t *testing.T, restapiURL string) string {
t.Fatalf("unexpected error getting timestamp chain: %v", err)
}

path := filepath.Join(t.TempDir(), "artifact")
file, err := os.Create(path)
// create PEM file containing intermediate and root certificates
certChainPath := filepath.Join(t.TempDir(), "ts_certchain.pem")
file, err := os.Create(certChainPath)
if err != nil {
t.Fatal(err)
}
Expand All @@ -243,7 +287,23 @@ func getCertChainPEM(t *testing.T, restapiURL string) string {
reader := bytes.NewReader(caCertsPEM)
file.ReadFrom(reader)

return path
// create PEM file containing the leaf certificate
leafCertPath := filepath.Join(t.TempDir(), "ts_leafcert.pem")
file, err = os.Create(leafCertPath)
if err != nil {
t.Fatal(err)
}
defer file.Close()

leafCertPEM, err := cryptoutils.MarshalCertificatesToPEM(certs[0:1])
if err != nil {
t.Fatalf("unexpected error marshalling leaf cert: %v", err)
}

reader = bytes.NewReader(leafCertPEM)
file.ReadFrom(reader)

return leafCertPath, certChainPath
}

// Create a random artifact to sign
Expand Down
Loading