1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
|
//
// Copyright 2022 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 verification
import (
"bytes"
"crypto/x509"
"encoding/asn1"
"fmt"
"hash"
"io"
"math/big"
"github.com/digitorus/pkcs7"
"github.com/digitorus/timestamp"
"github.com/pkg/errors"
)
var (
// EKUOID is the Extended Key Usage OID, per RFC 5280
EKUOID = asn1.ObjectIdentifier{2, 5, 29, 37}
)
// VerifyOpts contains verification options for a RFC3161 timestamp
type VerifyOpts struct {
// OID verifies that the TSR's OID has an expected value. Optional, used when
// an alternative OID was passed with a request to the TSA
OID asn1.ObjectIdentifier
// TSACertificate verifies that the TSR uses the TSACertificate as expected. Optional if the TSR contains the TSA certificate
TSACertificate *x509.Certificate
// Intermediates verifies the TSR's certificate. Optional, used for chain building
Intermediates []*x509.Certificate
// Roots is the set of trusted root certificates that verifies the TSR's certificate
Roots []*x509.Certificate
// Nonce verifies that the TSR contains the expected nonce. Optional, used when
// an optional nonce was passed with a request to the TSA
Nonce *big.Int
// CommonName verifies that the TSR certificate subject's Common Name matches the expected value. Optional
CommonName string
}
// Verify the TSR's certificate identifier matches a provided TSA certificate
func verifyESSCertID(tsaCert *x509.Certificate, opts VerifyOpts) error {
if opts.TSACertificate == nil {
return nil
}
if !bytes.Equal(opts.TSACertificate.RawIssuer, tsaCert.RawIssuer) {
return fmt.Errorf("TSR cert issuer does not match provided TSA cert issuer")
}
if opts.TSACertificate.SerialNumber.Cmp(tsaCert.SerialNumber) != 0 {
return fmt.Errorf("TSR cert serial number does not match provided TSA cert serial number")
}
return nil
}
// Verify the leaf certificate's subject Common Name matches a provided Common Name
func verifySubjectCommonName(cert *x509.Certificate, opts VerifyOpts) error {
if opts.CommonName == "" {
return nil
}
if cert.Subject.CommonName != opts.CommonName {
return fmt.Errorf("the certificate's subject Common Name %s does not match the provided Common Name %s", cert.Subject.CommonName, opts.CommonName)
}
return nil
}
// If embedded in the TSR, verify the TSR's leaf certificate matches a provided TSA certificate
func verifyEmbeddedLeafCert(tsaCert *x509.Certificate, opts VerifyOpts) error {
if opts.TSACertificate != nil && !opts.TSACertificate.Equal(tsaCert) {
return fmt.Errorf("certificate embedded in the TSR does not match the provided TSA certificate")
}
return nil
}
// Verify the leaf's EKU is set to critical, per RFC 3161 2.3
func verifyLeafCertCriticalEKU(cert *x509.Certificate) error {
var criticalEKU bool
for _, ext := range cert.Extensions {
if ext.Id.Equal(EKUOID) {
criticalEKU = ext.Critical
break
}
}
if !criticalEKU {
return errors.New("certificate must set EKU to critical")
}
return nil
}
func verifyLeafCert(ts timestamp.Timestamp, opts VerifyOpts) error {
if len(ts.Certificates) == 0 && opts.TSACertificate == nil {
return fmt.Errorf("leaf certificate must be present the in TSR or as a verify option")
}
errMsg := "failed to verify TSA certificate"
var leafCert *x509.Certificate
if len(ts.Certificates) != 0 {
leafCert = ts.Certificates[0]
err := verifyEmbeddedLeafCert(leafCert, opts)
if err != nil {
return fmt.Errorf("%s: %w", errMsg, err)
}
} else {
leafCert = opts.TSACertificate
}
err := verifyLeafCertCriticalEKU(leafCert)
if err != nil {
return fmt.Errorf("%s: %w", errMsg, err)
}
err = verifyESSCertID(leafCert, opts)
if err != nil {
return fmt.Errorf("%s: %w", errMsg, err)
}
err = verifySubjectCommonName(leafCert, opts)
if err != nil {
return fmt.Errorf("%s: %w", errMsg, err)
}
// verifies that the leaf certificate and any intermediate certificates
// have EKU set to only time stamping usage
err = verifyLeafAndIntermediatesTimestampingEKU(leafCert, opts)
if err != nil {
return fmt.Errorf("failed to verify EKU on leaf certificate: %w", err)
}
return nil
}
func verifyExtendedKeyUsage(cert *x509.Certificate) error {
certEKULen := len(cert.ExtKeyUsage)
if certEKULen != 1 {
return fmt.Errorf("certificate has %d extended key usages, expected only one", certEKULen)
}
if cert.ExtKeyUsage[0] != x509.ExtKeyUsageTimeStamping {
return fmt.Errorf("leaf certificate EKU is not set to TimeStamping as required")
}
return nil
}
// Verify the leaf and intermediate certificates (called "EKU chaining") all
// have the extended key usage set to only time stamping usage
func verifyLeafAndIntermediatesTimestampingEKU(leafCert *x509.Certificate, opts VerifyOpts) error {
err := verifyExtendedKeyUsage(leafCert)
if err != nil {
return fmt.Errorf("failed to verify EKU on leaf certificate: %w", err)
}
for _, cert := range opts.Intermediates {
err := verifyExtendedKeyUsage(cert)
if err != nil {
return fmt.Errorf("failed to verify EKU on intermediate certificate: %w", err)
}
}
return nil
}
// Verify the OID of the TSR matches an expected OID
func verifyOID(oid []int, opts VerifyOpts) error {
if opts.OID == nil {
return nil
}
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 VerifyOpts) error {
if opts.Nonce == nil {
return nil
}
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, opts VerifyOpts) (*timestamp.Timestamp, 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("")
if errors.As(err, &pe) {
return nil, fmt.Errorf("timestamp response is not valid: %w", err)
}
return nil, fmt.Errorf("error parsing response into Timestamp: %w", err)
}
// verify the timestamp response signature using the provided certificate pool
if err = verifyTSRWithChain(ts, opts); err != nil {
return nil, err
}
if err = verifyNonce(ts.Nonce, opts); err != nil {
return nil, err
}
if err = verifyOID(ts.Policy, opts); err != nil {
return nil, err
}
if err = verifyLeafCert(*ts, opts); err != nil {
return nil, err
}
// verify the hash in the timestamp response matches the artifact hash
if err = verifyHashedMessages(ts.HashAlgorithm.New(), ts.HashedMessage, artifact); err != nil {
return nil, err
}
// if the parsed timestamp is verified, return the timestamp
return ts, nil
}
func verifyTSRWithChain(ts *timestamp.Timestamp, opts VerifyOpts) error {
p7Message, err := pkcs7.Parse(ts.RawToken)
if err != nil {
return fmt.Errorf("error parsing hashed message: %w", err)
}
if len(opts.Roots) == 0 {
return fmt.Errorf("no root certificates provided for verifying the certificate chain")
}
rootCertPool := x509.NewCertPool()
for _, cert := range opts.Roots {
rootCertPool.AddCert(cert)
}
intermediateCertPool := x509.NewCertPool()
for _, cert := range opts.Intermediates {
intermediateCertPool.AddCert(cert)
}
x509Opts := x509.VerifyOptions{
Roots: rootCertPool,
Intermediates: intermediateCertPool,
}
// if the PCKS7 object does not have any certificates set in the
// Certificates field, the VerifyWithChain method will because it will be
// unable to find a leaf certificate associated with a signer. Since the
// leaf certificate issuer and serial number information is already part of
// the PKCS7 object, adding the leaf certificate to the Certificates field
// will allow verification to pass
if p7Message.Certificates == nil && opts.TSACertificate != nil {
p7Message.Certificates = []*x509.Certificate{opts.TSACertificate}
}
err = p7Message.VerifyWithOpts(x509Opts)
if err != nil {
return fmt.Errorf("error while verifying with chain: %w", err)
}
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 {
return fmt.Errorf("failed to create hash %w", err)
}
localHashedMsg := h.Sum(nil)
if !bytes.Equal(localHashedMsg, hashedMessage) {
return fmt.Errorf("hashed messages don't match")
}
return nil
}
|