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 7 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
78 changes: 77 additions & 1 deletion cmd/timestamp-cli/app/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ package app

import (
"crypto/x509"
"encoding/pem"
"fmt"
"math/big"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/digitorus/timestamp"

"github.com/sigstore/timestamp-authority/cmd/timestamp-cli/app/format"
"github.com/sigstore/timestamp-authority/pkg/log"
Expand All @@ -43,6 +49,10 @@ func addVerifyFlags(cmd *cobra.Command) {
cmd.MarkFlagRequired("timestamp") //nolint:errcheck
cmd.Flags().Var(NewFlagValue(fileFlag, ""), "cert-chain", "path to certificate chain PEM file")
malancas marked this conversation as resolved.
Show resolved Hide resolved
malancas marked this conversation as resolved.
Show resolved Hide resolved
cmd.MarkFlagRequired("cert-chain") //nolint:errcheck
cmd.Flags().String("nonce", "", "optional nonce passed with the request")
cmd.Flags().Var(NewFlagValue(oidFlag, ""), "oid", "optional oid passed with the request")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this refer to the hash algorithm OID ? If so, we might want to explain it in the description.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so, we could rename this flag to hash-algorithm-oid

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this refers to the TSA policy OID. I'll update the description to "optional TSA policy OID passed with the request".

cmd.Flags().String("subject", "", "expected leaf certificate subject")
cmd.Flags().Var(NewFlagValue(fileFlag, ""), "tsa-cert", "path to TSA cert")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we could omit adding --tsa- as prefix to the flag.

Q: Is this an intermediate or root cert ?

}

var verifyCmd = &cobra.Command{
Expand All @@ -66,6 +76,10 @@ func runVerify() (interface{}, error) {
if err != nil {
return nil, fmt.Errorf("error reading request from file: %w", err)
}
ts, err := timestamp.ParseResponse(tsrBytes)
if err != nil {
return nil, err
}

certChainPEM := viper.GetString("cert-chain")
pemBytes, err := os.ReadFile(filepath.Clean(certChainPEM))
Expand All @@ -79,13 +93,75 @@ func runVerify() (interface{}, error) {
return nil, fmt.Errorf("error parsing response into Timestamp while appending certs from PEM")
}

tsaCertPath := viper.GetString("tsa-cert")
pemBytes, err = os.ReadFile(filepath.Clean(tsaCertPath))
if err != nil {
return nil, fmt.Errorf("error reading request from file: %w", err)
}
block, rest := pem.Decode(pemBytes)
if block == nil || block.Type != "PUBLIC KEY" {
return &verifyCmdOutput{TimestampPath: tsrPath}, fmt.Errorf("failed to decode PEM block containing public key")
}
if rest != nil {
return &verifyCmdOutput{TimestampPath: tsrPath}, fmt.Errorf("only expected one certificate")
}

tsaCert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return &verifyCmdOutput{TimestampPath: tsrPath}, err
}

artifactPath := viper.GetString("artifact")
artifact, err := os.Open(filepath.Clean(artifactPath))
if err != nil {
return nil, err
}

err = verification.VerifyTimestampResponse(tsrBytes, artifact, certPool)
opts, err := verification.NewVerificationOpts(tsrBytes, artifact, pemBytes)
if err != nil {
return nil, err
}

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

if err := verification.VerifyOID(reqOID, opts); err != nil {
return &verifyCmdOutput{TimestampPath: tsrPath}, err
}

nonce := new(big.Int)
nonce, ok = nonce.SetString(viper.GetString("nonce"), 10)
if !ok {
return &verifyCmdOutput{TimestampPath: tsrPath}, fmt.Errorf("SetString: error")
}
if err := verification.VerifyNonce(nonce, opts); err != nil {
return &verifyCmdOutput{TimestampPath: tsrPath}, err
}

if err := verification.VerifyLeafCertSubject(viper.GetString("subject"), opts); err != nil {
return &verifyCmdOutput{TimestampPath: tsrPath}, err
}

if err := verification.VerifyEmbeddedLeafCert(tsaCert, opts); err != nil {
return &verifyCmdOutput{TimestampPath: tsrPath}, err
}

if err := verification.VerifyESSCertID(tsaCert, opts); err != nil {
return &verifyCmdOutput{TimestampPath: tsrPath}, err
}

if verified := verification.VerifyTSRSignature(ts, opts); !verified {
return nil, err
}

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

return &verifyCmdOutput{TimestampPath: tsrPath}, err
}
Expand Down
142 changes: 141 additions & 1 deletion pkg/verification/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,156 @@ package verification
import (
"bytes"
"crypto/x509"
"errors"
"encoding/asn1"
"encoding/pem"
"fmt"
"hash"
"io"
"math/big"

"github.com/digitorus/pkcs7"
"github.com/digitorus/timestamp"
"github.com/pkg/errors"
)

type VerificationOpts struct {
Oid asn1.ObjectIdentifier
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Abbreviations should be capitalized, so OID and TSACertificate

TsaCertificate *x509.Certificate
Intermediates []*x509.Certificate
Roots []*x509.Certificate
Nonce *big.Int
Subject string
HashAlgorithm hash.Hash
HashedMessage []byte
}

func NewVerificationOpts(ts *timestamp.Timestamp, artifact io.Reader, pemCerts []byte) (VerificationOpts, error) {
intermediateCerts := []*x509.Certificate{}
rootCerts := []*x509.Certificate{}
for len(pemCerts) > 0 {
block, rest := pem.Decode(pemCerts)
// if there is nothing left, we have found the last block
// which should be the root
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return VerificationOpts{}, fmt.Errorf("failed to parse certificate")
}
if rest == nil {
rootCerts = append(rootCerts, cert)
} else {
intermediateCerts = append(intermediateCerts, cert)
}
pemCerts = rest
}

opts := VerificationOpts{}
opts.Oid = ts.Policy
opts.TsaCertificate = ts.Certificates[0]
opts.Intermediates = intermediateCerts
opts.Roots = rootCerts
opts.Nonce = ts.Nonce
opts.Subject = ts.Certificates[0].Subject.String()
opts.HashAlgorithm = ts.HashAlgorithm.New()
opts.HashedMessage = ts.HashedMessage

return opts, nil
}

func createCertPool(certBytes []byte) (*x509.CertPool, error) {
certPool := x509.NewCertPool()
if ok := certPool.AppendCertsFromPEM(certBytes); !ok {
return nil, fmt.Errorf("failed to append certs to cert pool")
}
return certPool, nil
}

// Verify the TSR's certificate identifier matches a provided TSA certificate
func VerifyESSCertID(tsaCert *x509.Certificate, opt VerificationOpts) error {
var err error = nil
if opt.TsaCertificate.Issuer.String() != tsaCert.Issuer.String() {
err = errors.Wrap(err, "TSR cert issuer does not match provided TSA cert issuer")
}
if opt.TsaCertificate.SerialNumber != tsaCert.SerialNumber {
errors.Wrap(err, "TSR cert issuer does not match provided TSA cert issuer")
}
return err
}

// Verify the leaf certificate's subject and/or subject alternative name matches a provided subject
func VerifyLeafCertSubject(subject string, opts VerificationOpts) error {
leafCertSubject := opts.TsaCertificate.Subject.String()
if leafCertSubject != subject {
return fmt.Errorf("Leaf cert subject %s does not match provided subject %s", leafCertSubject, subject)
}
return nil
}

func verifyExtendedKeyUsage(cert *x509.Certificate) error {
certEKULen := len(cert.ExtKeyUsage)
if certEKULen != 1 {
return fmt.Errorf("cert has %d extended key usages, expected only one", certEKULen)
malancas marked this conversation as resolved.
Show resolved Hide resolved
}

if cert.ExtKeyUsage[0] != x509.ExtKeyUsageTimeStamping {
return fmt.Errorf("leaf cert EKU is not set to TimeStamping as required")
malancas marked this conversation as resolved.
Show resolved Hide resolved
}
return nil
}

// Verify the TSA certificate and the intermediates (called "EKU chaining") all
// have the extended key usage set to only time stamping usage
func VerifyExtendedKeyUsageForLeafAndIntermediates(opts VerificationOpts) error {
leafCert := opts.TsaCertificate
err := verifyExtendedKeyUsage(leafCert)
if err != nil {
return fmt.Errorf("failed to verify EKU on leaf cert: %w", err)
}

for _, cert := range opts.Intermediates {
err := verifyExtendedKeyUsage(cert)
if err != nil {
return fmt.Errorf("failed to verify EKU on intermediate cert: %w", err)
}
}
return nil
}

// If embedded in the TSR, verify the TSR's leaf certificate matches a provided TSA certificate
func VerifyEmbeddedLeafCert(tsaCert *x509.Certificate, opts VerificationOpts) error {
if opts.TsaCertificate != nil {
if !opts.TsaCertificate.Equal(tsaCert) {
return fmt.Errorf("certificate embedded in the TSR does not match the provided TSA certificate")
}
}
return nil
}

// Verify the OID of the TSR matches an expected OID
func VerifyOID(oid []int, opts VerificationOpts) error {
responseOid := opts.Oid
if len(oid) != len(responseOid) {
return fmt.Errorf("OID lengths do not match")
}
for i, v := range oid {
if v != responseOid[i] {
return fmt.Errorf("OID content does not match")
}
}
return nil
}

// Verify the nonce - Mostly important for when the response is first returned
func VerifyNonce(requestNonce *big.Int, opts VerificationOpts) error {
if opts.Nonce.Cmp(requestNonce) != 0 {
return fmt.Errorf("incoming nonce %d does not match TSR nonce %d", requestNonce, opts.Nonce)
}
return nil
}

// VerifyTimestampResponse the timestamp response using a timestamp certificate chain.
func VerifyTimestampResponse(tsrBytes []byte, artifact io.Reader, certPool *x509.CertPool) error {
// Verify the status of the TSR does not contain an error
// handled by the timestamp.ParseResponse function
ts, err := timestamp.ParseResponse(tsrBytes)
if err != nil {
pe := timestamp.ParseError("")
Expand Down Expand Up @@ -62,6 +201,7 @@ func verifyTSRWithChain(ts *timestamp.Timestamp, certPool *x509.CertPool) error
return nil
}

// Verify that the TSR's hashed message matches the digest of the artifact to be timestamped
func verifyHashedMessages(hashAlg hash.Hash, hashedMessage []byte, artifactReader io.Reader) error {
h := hashAlg
if _, err := io.Copy(h, artifactReader); err != nil {
Expand Down
Loading