Skip to content

Commit

Permalink
Verify timestamps.
Browse files Browse the repository at this point in the history
  • Loading branch information
q-uint committed Jun 3, 2024
1 parent 3d11816 commit a280463
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 13 deletions.
15 changes: 14 additions & 1 deletion agent.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ type Agent struct {
// New returns a new Agent based on the given configuration.
func New(cfg Config) (*Agent, error) {
if cfg.IngressExpiry == 0 {
cfg.IngressExpiry = 10 * time.Second
cfg.IngressExpiry = time.Minute
}
// By default, use the anonymous identity.
var id identity.Identity = new(identity.AnonymousIdentity)
Expand Down Expand Up @@ -326,6 +326,12 @@ func (a Agent) readStateCertificate(ecID principal.Principal, paths [][]hashtree
if err := cbor.Unmarshal(resp["certificate"], &certificate); err != nil {
return nil, err
}
if err := certificate.VerifyTime(a.ingressExpiry); err != nil {
return nil, err
}
if err := certification.VerifyCertificate(certificate, ecID, a.rootKey); err != nil {
return nil, err
}
return &certificate, nil
}

Expand Down Expand Up @@ -357,6 +363,12 @@ func (a Agent) readSubnetStateCertificate(subnetID principal.Principal, paths []
if err := cbor.Unmarshal(resp["certificate"], &certificate); err != nil {
return nil, err
}
if err := certificate.VerifyTime(a.ingressExpiry); err != nil {
return nil, err
}
if err := certification.VerifySubnetCertificate(certificate, subnetID, a.rootKey); err != nil {
return nil, err
}
return &certificate, nil
}

Expand All @@ -378,6 +390,7 @@ type Config struct {
// Identity is the identity used by the Agent.
Identity identity.Identity
// IngressExpiry is the duration for which an ingress message is valid.
// The default is set to 1 minute.
IngressExpiry time.Duration
// ClientConfig is the configuration for the underlying Client.
ClientConfig *ClientConfig
Expand Down
94 changes: 82 additions & 12 deletions certification/certificate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import (
"crypto/ed25519"
"encoding/asn1"
"fmt"
"slices"

"github.com/aviate-labs/agent-go/certification/bls"
"github.com/aviate-labs/agent-go/certification/hashtree"
"github.com/aviate-labs/agent-go/principal"
"github.com/aviate-labs/leb128"
"slices"
"time"

"github.com/fxamacker/cbor/v2"
)
Expand Down Expand Up @@ -95,7 +96,6 @@ func PublicED25519KeyFromDER(der []byte) (*ed25519.PublicKey, error) {
publicKey := ed25519.PublicKey(bs.Bytes)
return &publicKey, nil
}

func VerifyCertificate(
certificate Certificate,
canisterID principal.Principal,
Expand All @@ -109,8 +109,7 @@ func VerifyCertificate(
if certificate.Delegation != nil {
delegation := certificate.Delegation
k, err := verifyDelegationCertificate(
delegation.Certificate,
delegation.SubnetId,
delegation,
publicKey,
canisterID,
)
Expand Down Expand Up @@ -145,6 +144,18 @@ func VerifyCertifiedData(
return nil
}

func VerifySubnetCertificate(
certificate Certificate,
subnetID principal.Principal,
rootPublicKey []byte,
) error {
publicKey, err := PublicBLSKeyFromDER(rootPublicKey)
if err != nil {
return err
}
return verifySubnetCertificate(certificate, subnetID, publicKey)
}

func verifyCertificateSignature(certificate Certificate, publicKey *bls.PublicKey) error {
rootHash := certificate.Tree.Digest()
message := append(hashtree.DomainSeparator("ic-state-root"), rootHash[:]...)
Expand All @@ -159,21 +170,20 @@ func verifyCertificateSignature(certificate Certificate, publicKey *bls.PublicKe
}

func verifyDelegationCertificate(
certificate Certificate,
subnetID principal.Principal,
delegation *Delegation,
rootPublicKey *bls.PublicKey,
canisterID principal.Principal,
) (*bls.PublicKey, error) {
if certificate.Delegation != nil {
if delegation.Certificate.Delegation != nil {
return nil, fmt.Errorf("multiple delegations are not supported")
}
if err := verifyCertificateSignature(certificate, rootPublicKey); err != nil {
if err := verifyCertificateSignature(delegation.Certificate, rootPublicKey); err != nil {
return nil, err
}

rawRanges, err := certificate.Tree.Lookup(
rawRanges, err := delegation.Certificate.Tree.Lookup(
hashtree.Label("subnet"),
subnetID.Raw,
delegation.SubnetId.Raw,
hashtree.Label("canister_ranges"),
)
if err != nil {
Expand All @@ -187,7 +197,51 @@ func verifyDelegationCertificate(
return nil, fmt.Errorf("canister %s is not in range", canisterID)
}

rawPublicKey, err := certificate.Tree.Lookup(
rawPublicKey, err := delegation.Certificate.Tree.Lookup(
hashtree.Label("subnet"),
delegation.SubnetId.Raw,
hashtree.Label("public_key"),
)
if err != nil {
return nil, err
}
return PublicBLSKeyFromDER(rawPublicKey)
}

func verifySubnetCertificate(
certificate Certificate,
subnetID principal.Principal,
rootPublicKey *bls.PublicKey,
) error {
key := rootPublicKey
if certificate.Delegation != nil {
delegation := certificate.Delegation
k, err := verifySubnetDelegationCertificate(
delegation,
subnetID,
rootPublicKey,
)
if err != nil {
return err
}
key = k
}
return verifyCertificateSignature(certificate, key)
}

func verifySubnetDelegationCertificate(
delegation *Delegation,
subnetID principal.Principal,
rootPublicKey *bls.PublicKey,
) (*bls.PublicKey, error) {
if delegation.Certificate.Delegation != nil {
return nil, fmt.Errorf("multiple delegations are not supported")
}
if err := verifySubnetCertificate(delegation.Certificate, subnetID, rootPublicKey); err != nil {
return nil, err
}

rawPublicKey, err := delegation.Certificate.Tree.Lookup(
hashtree.Label("subnet"),
subnetID.Raw,
hashtree.Label("public_key"),
Expand Down Expand Up @@ -237,6 +291,22 @@ type Certificate struct {
Delegation *Delegation `cbor:"delegation"`
}

// VerifyTime verifies the time of a certificate.
func (c Certificate) VerifyTime(ingressExpiry time.Duration) error {
rawTime, err := c.Tree.Lookup(hashtree.Label("time"))
if err != nil {
return err
}
t, err := leb128.DecodeUnsigned(bytes.NewReader(rawTime))
if err != nil {
return err
}
if int64(ingressExpiry) < time.Now().UnixNano()-t.Int64() {
return fmt.Errorf("certificate outdated, exceeds ingress expiry")
}
return nil
}

// Delegation is a delegation of a certificate.
type Delegation struct {
// SubnetId is the subnet ID of the delegation.
Expand Down
3 changes: 3 additions & 0 deletions query.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,9 @@ func (q Query) Query(values ...any) error {
if err != nil {
return err
}
if err := c.VerifyTime(q.a.ingressExpiry); err != nil {
return err
}
if err := certification.VerifyCertificate(*c, q.effectiveCanisterID, q.a.rootKey); err != nil {
return err
}
Expand Down

0 comments on commit a280463

Please sign in to comment.