-
Notifications
You must be signed in to change notification settings - Fork 980
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
enforce certificate correctness in TBSCertificate.SignWith #1266
base: cert-v2
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -76,15 +76,89 @@ func (t *TBSCertificate) Sign(signer Certificate, curve Curve, key []byte) (Cert | |
} | ||
} | ||
|
||
// readyToSign checks all signing requirements that don't require us to cross-reference with a CA | ||
func (t *TBSCertificate) readyToSign() error { | ||
if t.PublicKey == nil { | ||
JackDoanRivian marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return fmt.Errorf("public key not set") | ||
} | ||
|
||
if !t.IsCA && len(t.Networks) == 0 { | ||
return fmt.Errorf("non-CA certificates must contain at least one network") | ||
} | ||
|
||
hasV4Networks := false | ||
hasV6Networks := false | ||
for _, n := range t.Networks { | ||
if !n.IsValid() || !n.Addr().IsValid() { | ||
return fmt.Errorf("invalid network: %s", n) | ||
} | ||
if t.Version == Version1 && n.Addr().Is6() { | ||
return fmt.Errorf("certificate v1 may not contain IPv6 networks: %v", t.Networks) | ||
} | ||
if n.Addr().Zone() != "" { | ||
return fmt.Errorf("networks may not contain zones: %s", n) | ||
} | ||
if n.Addr().Is4In6() { | ||
return fmt.Errorf("4in6 networks are not allowed: %s", n) | ||
} | ||
if !t.IsCA && n.Addr().IsUnspecified() { | ||
return fmt.Errorf("non-CA certificates must not use the zero address as a network: %s", n) | ||
} | ||
|
||
hasV4Networks = hasV4Networks || n.Addr().Is4() | ||
hasV6Networks = hasV6Networks || n.Addr().Is6() | ||
} | ||
|
||
slices.SortFunc(t.Networks, comparePrefix) | ||
err := findDuplicatePrefix(t.Networks) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
for _, n := range t.UnsafeNetworks { | ||
if !n.IsValid() || !n.Addr().IsValid() { | ||
return fmt.Errorf("invalid unsafe network: %s", n) | ||
} | ||
if n.Addr().Zone() != "" { | ||
return fmt.Errorf("unsafe_networks may not contain zones: %s", n) | ||
} | ||
//todo are unsafe networks that overlap networks allowed? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, you might have |
||
|
||
if n.Addr().Is6() { | ||
if t.Version == Version1 { | ||
return fmt.Errorf("certificate v1 may not contain IPv6 unsafe networks: %v", t.Networks) | ||
} | ||
if !hasV6Networks && !t.IsCA { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't disagree but I am curious if this will haunt us in the future |
||
return fmt.Errorf("IPv6 unsafe networks require an IPv6 address assignment") | ||
} | ||
} else if n.Addr().Is4() { | ||
if !hasV4Networks && !t.IsCA { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't disagree but I am curious if this will haunt us in the future |
||
return fmt.Errorf("IPv4 unsafe networks require an IPv4 address assignment") | ||
} | ||
} | ||
} | ||
|
||
slices.SortFunc(t.UnsafeNetworks, comparePrefix) | ||
err = findDuplicatePrefix(t.UnsafeNetworks) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// SignWith does the same thing as sign, but uses the function in `sp` to calculate the signature. | ||
// You should only use SignWith if you do not have direct access to your private key. | ||
func (t *TBSCertificate) SignWith(signer Certificate, curve Curve, sp SignerLambda) (Certificate, error) { | ||
if curve != t.Curve { | ||
return nil, fmt.Errorf("curve in cert and private key supplied don't match") | ||
} | ||
|
||
//TODO: make sure we have all minimum properties to sign, like a public key | ||
//TODO: we need to verify networks and unsafe networks (no duplicates, max of 1 of each version for v2 certs | ||
//readyToSign sorts Networks and UnsafeNetworks for us | ||
err := t.readyToSign() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
if signer != nil { | ||
if t.IsCA { | ||
|
@@ -107,20 +181,17 @@ func (t *TBSCertificate) SignWith(signer Certificate, curve Curve, sp SignerLamb | |
} | ||
} | ||
|
||
slices.SortFunc(t.Networks, comparePrefix) | ||
slices.SortFunc(t.UnsafeNetworks, comparePrefix) | ||
|
||
var c beingSignedCertificate | ||
switch t.Version { | ||
case Version1: | ||
c = &certificateV1{} | ||
err := c.fromTBSCertificate(t) | ||
err = c.fromTBSCertificate(t) | ||
if err != nil { | ||
return nil, err | ||
} | ||
case Version2: | ||
c = &certificateV2{} | ||
err := c.fromTBSCertificate(t) | ||
err = c.fromTBSCertificate(t) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
@@ -158,3 +229,16 @@ func comparePrefix(a, b netip.Prefix) int { | |
} | ||
return addr | ||
} | ||
|
||
// findDuplicatePrefix returns an error if there is a duplicate prefix in the pre-sorted input slice sortedPrefixes | ||
func findDuplicatePrefix(sortedPrefixes []netip.Prefix) error { | ||
if len(sortedPrefixes) < 2 { | ||
return nil | ||
} | ||
for i := 1; i < len(sortedPrefixes); i++ { | ||
if comparePrefix(sortedPrefixes[i], sortedPrefixes[i-1]) == 0 { | ||
return fmt.Errorf("duplicate network detected: %v", sortedPrefixes[i]) | ||
} | ||
} | ||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had started on this validation in
cert_v1.go
andcert_v2.go
specifically that way we don't accidentally leak rules from one version to the other that shouldn't apply. We also need to do these things at unmarshal (or at a minimum at handshake and cert loading time) to ensure we are working with a valid certificate.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah yeah nice catch, I'll move this