Skip to content

Commit

Permalink
Checking policy signature against cert constraints (#144)
Browse files Browse the repository at this point in the history
* adding logic so policy signature can be checked against constraints
* threaded options into policy validation functionary
---------

Signed-off-by: chaosinthecrd <[email protected]>
Signed-off-by: John Kjell <[email protected]>
Co-authored-by: John Kjell <[email protected]>
  • Loading branch information
ChaosInTheCRD and jkjell authored Feb 3, 2024
1 parent 3ce1385 commit 86f5096
Show file tree
Hide file tree
Showing 5 changed files with 425 additions and 30 deletions.
145 changes: 145 additions & 0 deletions internal/test/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright 2024 The Witness Contributors
//
// 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 test

import (
"crypto"
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"crypto/x509/pkix"
"encoding/pem"
"math/big"
"time"

"github.com/in-toto/go-witness/cryptoutil"
)

func CreateRsaKey() (*rsa.PrivateKey, *rsa.PublicKey, error) {
priv, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
return nil, nil, err
}

return priv, &priv.PublicKey, nil
}

func CreateTestKey() (cryptoutil.Signer, cryptoutil.Verifier, []byte, error) {
privKey, _, err := CreateRsaKey()
if err != nil {
return nil, nil, nil, err
}

signer := cryptoutil.NewRSASigner(privKey, crypto.SHA256)
verifier := cryptoutil.NewRSAVerifier(&privKey.PublicKey, crypto.SHA256)
keyBytes, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey)
if err != nil {
return nil, nil, nil, err
}

pemBytes := pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: keyBytes})

return signer, verifier, pemBytes, nil
}

func CreateCert(priv, pub interface{}, temp, parent *x509.Certificate) (*x509.Certificate, error) {
var err error
temp.SerialNumber, err = rand.Int(rand.Reader, big.NewInt(4294967295))
if err != nil {
return nil, err
}

certBytes, err := x509.CreateCertificate(rand.Reader, temp, parent, pub, priv)
if err != nil {
return nil, err
}

return x509.ParseCertificate(certBytes)
}

func CreateRoot() (*x509.Certificate, interface{}, error) {
priv, pub, err := CreateRsaKey()
if err != nil {
return nil, nil, err
}

template := &x509.Certificate{
DNSNames: []string{"in-toto.io"},
Subject: pkix.Name{
Country: []string{"US"},
Organization: []string{"in-toto"},
CommonName: "Test Root",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLenZero: false,
MaxPathLen: 2,
}

cert, err := CreateCert(priv, pub, template, template)
return cert, priv, err
}

func CreateIntermediate(parent *x509.Certificate, parentPriv interface{}) (*x509.Certificate, interface{}, error) {
priv, pub, err := CreateRsaKey()
if err != nil {
return nil, nil, err
}

template := &x509.Certificate{
Subject: pkix.Name{
Country: []string{"US"},
Organization: []string{"TestifySec"},
CommonName: "Test Intermediate",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign,
BasicConstraintsValid: true,
IsCA: true,
MaxPathLenZero: false,
MaxPathLen: 1,
}

cert, err := CreateCert(parentPriv, pub, template, parent)
return cert, priv, err
}

func CreateLeaf(parent *x509.Certificate, parentPriv interface{}) (*x509.Certificate, interface{}, error) {
priv, pub, err := CreateRsaKey()
if err != nil {
return nil, nil, err
}

template := &x509.Certificate{
DNSNames: []string{"in-toto.io"},
Subject: pkix.Name{
Country: []string{"US"},
Organization: []string{"In-toto"},
CommonName: "Test Leaf",
},
NotBefore: time.Now(),
NotAfter: time.Now().Add(24 * time.Hour),
KeyUsage: x509.KeyUsageDigitalSignature,
BasicConstraintsValid: true,
IsCA: false,
}

cert, err := CreateCert(parentPriv, pub, template, parent)
return cert, priv, err
}
30 changes: 4 additions & 26 deletions policy/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -236,35 +236,13 @@ func (step Step) checkFunctionaries(verifiedStatements []source.VerifiedCollecti
}

for _, verifier := range verifiedStatement.Verifiers {
verifierID, err := verifier.KeyID()
if err != nil {
log.Debugf("(policy) skipping verifier: could not get key id: %w", err)
continue
}

for _, functionary := range step.Functionaries {
if functionary.PublicKeyID != "" && functionary.PublicKeyID == verifierID {
collections = append(collections, verifiedStatement)
break
}

x509Verifier, ok := verifier.(*cryptoutil.X509Verifier)
if !ok {
log.Debugf("(policy) skipping verifier: verifier with ID %v is not a public key verifier or a x509 verifier", verifierID)
continue
}

if len(functionary.CertConstraint.Roots) == 0 {
log.Debugf("(policy) skipping verifier: verifier with ID %v is an x509 verifier, but step %v does not have any truested roots", verifierID, step)
continue
}

if err := functionary.CertConstraint.Check(x509Verifier, trustBundles); err != nil {
log.Debugf("(policy) skipping verifier: verifier with ID %v doesn't meet certificate constraint: %w", verifierID, err)
if err := functionary.Validate(verifier, trustBundles); err != nil {
log.Debugf("(policy) skipping verifier: %w", err)
continue
} else {
collections = append(collections, verifiedStatement)
}

collections = append(collections, verifiedStatement)
}
}
}
Expand Down
27 changes: 27 additions & 0 deletions policy/step.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"strings"

"github.com/in-toto/go-witness/attestation"
"github.com/in-toto/go-witness/cryptoutil"
"github.com/in-toto/go-witness/source"
)

Expand Down Expand Up @@ -80,6 +81,32 @@ type RejectedCollection struct {
Reason error
}

func (f Functionary) Validate(verifier cryptoutil.Verifier, trustBundles map[string]TrustBundle) error {
verifierID, err := verifier.KeyID()
if err != nil {
return fmt.Errorf("could not get key id: %w", err)
}

if f.PublicKeyID != "" && f.PublicKeyID == verifierID {
return nil
}

x509Verifier, ok := verifier.(*cryptoutil.X509Verifier)
if !ok {
return fmt.Errorf("verifier with ID %v is not a public key verifier or a x509 verifier", verifierID)
}

if len(f.CertConstraint.Roots) == 0 {
return fmt.Errorf("verifier with ID %v is an x509 verifier, but no trusted roots provided in functionary", verifierID)
}

if err := f.CertConstraint.Check(x509Verifier, trustBundles); err != nil {
return fmt.Errorf("verifier with ID %v doesn't meet certificate constraint: %w", verifierID, err)
}

return nil
}

// validateAttestations will test each collection against to ensure the expected attestations
// appear in the collection as well as that any rego policies pass for the step.
func (s Step) validateAttestations(verifiedCollections []source.VerifiedCollection) StepResult {
Expand Down
91 changes: 87 additions & 4 deletions verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ package witness
import (
"context"
"crypto/x509"
"encoding/base64"
"encoding/json"
"fmt"
"io"

"github.com/in-toto/go-witness/cryptoutil"
"github.com/in-toto/go-witness/dsse"
"github.com/in-toto/go-witness/log"
"github.com/in-toto/go-witness/policy"
"github.com/in-toto/go-witness/source"
"github.com/in-toto/go-witness/timestamp"
Expand All @@ -43,6 +45,11 @@ type verifyOptions struct {
policyTimestampAuthorities []timestamp.TimestampVerifier
policyCARoots []*x509.Certificate
policyCAIntermediates []*x509.Certificate
policyCommonName string
policyDNSNames []string
policyEmails []string
policyOrganizations []string
policyURIs []string
policyEnvelope dsse.Envelope
policyVerifiers []cryptoutil.Verifier
collectionSource source.Sourcer
Expand Down Expand Up @@ -85,22 +92,39 @@ func VerifyWithPolicyCAIntermediates(intermediates []*x509.Certificate) VerifyOp
}
}

func VerifyWithPolicyCertConstraints(commonName string, dnsNames []string, emails []string, organizations []string, uris []string) VerifyOption {
return func(vo *verifyOptions) {
vo.policyCommonName = commonName
vo.policyDNSNames = dnsNames
vo.policyEmails = emails
vo.policyOrganizations = organizations
vo.policyURIs = uris
}
}

// Verify verifies a set of attestations against a provided policy. The set of attestations that satisfy the policy will be returned
// if verifiation is successful.
func Verify(ctx context.Context, policyEnvelope dsse.Envelope, policyVerifiers []cryptoutil.Verifier, opts ...VerifyOption) (map[string][]source.VerifiedCollection, error) {
vo := verifyOptions{
policyEnvelope: policyEnvelope,
policyVerifiers: policyVerifiers,
policyEnvelope: policyEnvelope,
policyVerifiers: policyVerifiers,
policyCommonName: "*",
policyDNSNames: []string{"*"},
policyOrganizations: []string{"*"},
policyURIs: []string{"*"},
policyEmails: []string{"*"},
}

for _, opt := range opts {
opt(&vo)
}

if _, err := vo.policyEnvelope.Verify(dsse.VerifyWithVerifiers(vo.policyVerifiers...), dsse.VerifyWithTimestampVerifiers(vo.policyTimestampAuthorities...), dsse.VerifyWithRoots(vo.policyCARoots...), dsse.VerifyWithIntermediates(vo.policyCAIntermediates...)); err != nil {
return nil, fmt.Errorf("could not verify policy: %w", err)
if err := verifyPolicySignature(ctx, vo); err != nil {
return nil, fmt.Errorf("failed to verify policy signature: %w", err)
}

log.Info("Policy signature verification passed")

pol := policy.Policy{}
if err := json.Unmarshal(vo.policyEnvelope.Payload, &pol); err != nil {
return nil, fmt.Errorf("failed to unmarshal policy from envelope: %w", err)
Expand Down Expand Up @@ -154,3 +178,62 @@ func Verify(ctx context.Context, policyEnvelope dsse.Envelope, policyVerifiers [

return accepted, nil
}

func verifyPolicySignature(ctx context.Context, vo verifyOptions) error {
passedPolicyVerifiers, err := vo.policyEnvelope.Verify(dsse.VerifyWithVerifiers(vo.policyVerifiers...), dsse.VerifyWithTimestampVerifiers(vo.policyTimestampAuthorities...), dsse.VerifyWithRoots(vo.policyCARoots...), dsse.VerifyWithIntermediates(vo.policyCAIntermediates...))
if err != nil {
return fmt.Errorf("could not verify policy: %w", err)
}

var passed bool
for _, verifier := range passedPolicyVerifiers {
kid, err := verifier.Verifier.KeyID()
if err != nil {
return fmt.Errorf("could not get verifier key id: %w", err)
}

var f policy.Functionary
trustBundle := make(map[string]policy.TrustBundle)
if _, ok := verifier.Verifier.(*cryptoutil.X509Verifier); ok {
rootIDs := make([]string, 0)
for _, root := range vo.policyCARoots {
id := base64.StdEncoding.EncodeToString(root.Raw)
rootIDs = append(rootIDs, id)
trustBundle[id] = policy.TrustBundle{
Root: root,
}
}

f = policy.Functionary{
Type: "root",
CertConstraint: policy.CertConstraint{
Roots: rootIDs,
CommonName: vo.policyCommonName,
URIs: vo.policyURIs,
Emails: vo.policyEmails,
Organizations: vo.policyOrganizations,
DNSNames: vo.policyDNSNames,
},
}

} else {
f = policy.Functionary{
Type: "key",
PublicKeyID: kid,
}
}

err = f.Validate(verifier.Verifier, trustBundle)
if err != nil {
log.Debugf("Policy Verifier %s failed failed to match supplied constraints: %w, continuing...", kid, err)
continue
}
passed = true
}

if !passed {
return fmt.Errorf("no policy verifiers passed verification")
} else {
return nil
}
}
Loading

0 comments on commit 86f5096

Please sign in to comment.