Skip to content

Commit

Permalink
BREAKING CHANGE: unify UNetStack and CA's CertificationAuthority beha…
Browse files Browse the repository at this point in the history
…vior

The UNetStack and CA structures have similar functionality exposed
through a different interface, which causes us to create an
interface and a wrapper for the code in probe-cli.

I noticed about this when I was trying to integrate new netem
changes, and it occurred to me we can just make the interfaces
uniform here and avoid wrapping in probe-cli.

Also part of ooni/probe#2531
  • Loading branch information
bassosimone committed Sep 20, 2023
1 parent df36342 commit 2cb3b12
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 52 deletions.
45 changes: 23 additions & 22 deletions ca.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,7 @@ func caMustNewAuthority(name, organization string, validity time.Duration,
//
// SPDX-License-Identifier: Apache-2.0.
type CA struct {
// CACert is the public certificate used by the CA.
CACert *x509.Certificate

// These fields are not exported
caCert *x509.Certificate
capriv any
keyID []byte
org string
Expand All @@ -101,6 +98,8 @@ func MustNewCA() *CA {
return MustNewCAWithTimeNow(time.Now)
}

var _ CertificationAuthority = &CA{}

// MustNewCA is like [NewCA] but uses a custom [time.Now] func.
//
// This code is derived from github.com/google/martian/v3.
Expand All @@ -123,7 +122,7 @@ func MustNewCAWithTimeNow(timeNow func() time.Time) *CA {
keyID := h.Sum(nil)

return &CA{
CACert: ca,
caCert: ca,
capriv: privateKey,
priv: priv,
keyID: keyID,
Expand All @@ -132,11 +131,23 @@ func MustNewCAWithTimeNow(timeNow func() time.Time) *CA {
}
}

// CertPool returns an [x509.CertPool] using the given [*CA].
func (c *CA) CertPool() *x509.CertPool {
pool := x509.NewCertPool()
pool.AddCert(c.CACert)
return pool
// CACert implements [CertificationAuthority].
func (ca *CA) CACert() *x509.Certificate {
return ca.caCert
}

// DefaultCertPool implements [CertificationAuthority].
func (c *CA) DefaultCertPool() *x509.CertPool {
p := x509.NewCertPool()
p.AddCert(c.caCert)
return p
}

// MustNewServerTLSConfig implements [CertificationAuthority].
func (ca *CA) MustNewServerTLSConfig(commonName string, extraNames ...string) *tls.Config {
return &tls.Config{
Certificates: []tls.Certificate{*ca.MustNewCert(commonName, extraNames...)},
}
}

// MustNewCert creates a new certificate for the given common name or PANICS.
Expand Down Expand Up @@ -188,26 +199,16 @@ func (c *CA) MustNewCertWithTimeNow(timeNow func() time.Time, commonName string,
}
}

raw := Must1(x509.CreateCertificate(rand.Reader, tmpl, c.CACert, c.priv.Public(), c.capriv))
raw := Must1(x509.CreateCertificate(rand.Reader, tmpl, c.caCert, c.priv.Public(), c.capriv))

// Parse certificate bytes so that we have a leaf certificate.
x509c := Must1(x509.ParseCertificate(raw))

tlsc := &tls.Certificate{
Certificate: [][]byte{raw, c.CACert.Raw},
Certificate: [][]byte{raw, c.caCert.Raw},
PrivateKey: c.priv,
Leaf: x509c,
}

return tlsc
}

// MustServerTLSConfig generates a server-side [*tls.Config] that uses the given [*CA] and
// a generated certificate for the given common name and extra names.
//
// See [CA.MustNewCert] documentation for more details about what common name and extra names should be.
func (ca *CA) MustServerTLSConfig(commonName string, extraNames ...string) *tls.Config {
return &tls.Config{
Certificates: []tls.Certificate{*ca.MustNewCert(commonName, extraNames...)},
}
}
2 changes: 1 addition & 1 deletion ca_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ func TestCAWeCanGenerateAnExpiredCertificate(t *testing.T) {
Handler: http.NewServeMux(),
TLSConfig: &tls.Config{
Certificates: []tls.Certificate{
*serverStack.CA().MustNewCertWithTimeNow(func() time.Time {
*serverStack.ca.MustNewCertWithTimeNow(func() time.Time {
return time.Date(2017, time.July, 17, 0, 0, 0, 0, time.UTC)
},
"www.example.com",
Expand Down
2 changes: 1 addition & 1 deletion example_star_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ func Example_starTopologyHTTPSAndDNS() {
}
httpsServer := &http.Server{
Handler: mux,
TLSConfig: httpsServerStack.CA().MustServerTLSConfig("tyrell.wellick.name"),
TLSConfig: httpsServerStack.MustNewServerTLSConfig("tyrell.wellick.name"),
}
go httpsServer.ServeTLS(httpsListener, "", "") // empty string: use .TLSConfig
defer httpsServer.Close()
Expand Down
2 changes: 1 addition & 1 deletion integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ func TestRoutingWorksHTTPS(t *testing.T) {
t.Fatal(err)
}
httpServer := &http.Server{
TLSConfig: serverStack.CA().MustServerTLSConfig("example.local", "10.0.0.1"),
TLSConfig: serverStack.MustNewServerTLSConfig("example.local", "10.0.0.1"),
Handler: mux,
}
go httpServer.ServeTLS(listener, "", "") // empty strings mean: use TLSConfig
Expand Down
27 changes: 19 additions & 8 deletions model.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package netem

import (
"context"
"crypto/tls"
"crypto/x509"
"net"
"syscall"
Expand All @@ -23,6 +24,21 @@ const (
FrameFlagDrop
)

// CertificationAuthority is a TLS certification authority.
type CertificationAuthority interface {
// CACert returns the CA certificate used by the server, which
// allows you to add to an existing [*x509.CertPool].
CACert() *x509.Certificate

// DefaultCertPool returns the default cert pool to use.
DefaultCertPool() *x509.CertPool

// MustNewServerTLSConfig constructs a server certificate for
// the given common name and extra names, all of which could be
// either IPv4/IPv6 addresses or domain names.
MustNewServerTLSConfig(commonName string, extraNames ...string) *tls.Config
}

// Frame contains an IPv4 or IPv6 packet.
type Frame struct {
// Deadline is the time when this frame should be delivered.
Expand Down Expand Up @@ -154,14 +170,9 @@ type UDPLikeConn interface {

// UnderlyingNetwork replaces for functions in the [net] package.
type UnderlyingNetwork interface {
// CA returns the CA we're using.
CA() *CA

// CACert returns the CA cert we're using.
CACert() *x509.Certificate

// DefaultCertPool returns the underlying cert pool to be used.
DefaultCertPool() *x509.CertPool
// CertificationAuthority allows accessing the certification authority
// associated with this host or set of hosts.
CertificationAuthority

// DialContext dials a TCP or UDP connection. Unlike [net.DialContext], this
// function does not implement dialing when address contains a domain.
Expand Down
2 changes: 1 addition & 1 deletion ndt0.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ func RunNDT0Server(
}

// generate a config for the given SNI and for the given IP addr
tlsConfig := stack.CA().MustServerTLSConfig(serverIPAddr.String(), serverNames...)
tlsConfig := stack.MustNewServerTLSConfig(serverIPAddr.String(), serverNames...)

// conditionally use TLS
ns := &Net{stack}
Expand Down
31 changes: 13 additions & 18 deletions unetstack.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ type UNetStack struct {
}

var (
_ HTTPUnderlyingNetwork = &UNetStack{}
_ NIC = &UNetStack{}
_ UnderlyingNetwork = &UNetStack{}
_ CertificationAuthority = &UNetStack{}
_ HTTPUnderlyingNetwork = &UNetStack{}
_ NIC = &UNetStack{}
_ UnderlyingNetwork = &UNetStack{}
)

// NewUNetStack constructs a new [UNetStack] instance.
Expand Down Expand Up @@ -104,20 +105,19 @@ func NewUNetStack(
return stack, nil
}

// CA implements UnderlyingNetwork.
func (gs *UNetStack) CA() *CA {
return gs.ca
// CACert implements CertificationAuthority.
func (gs *UNetStack) CACert() *x509.Certificate {
return gs.ca.CACert()
}

// CACert implements UnderlyingNetwork.
func (gs *UNetStack) CACert() *x509.Certificate {
return gs.ca.CACert
// DefaultCertPool implements CertificationAuthority.
func (gs *UNetStack) DefaultCertPool() *x509.CertPool {
return gs.ca.DefaultCertPool()
}

// MustServerTLSConfig is used by [github.com/ooni/probe-cli] code
// when generating configuration for servers using TLS.
func (gs *UNetStack) MustServerTLSConfig(commonName string, extraNames ...string) *tls.Config {
return gs.ca.MustServerTLSConfig(commonName, extraNames...)
// MustNewServerTLSConfig implements CertificationAuthority.
func (gs *UNetStack) MustNewServerTLSConfig(commonName string, extraNames ...string) *tls.Config {
return gs.ca.MustNewServerTLSConfig(commonName, extraNames...)
}

// Logger implements HTTPUnderlyingNetwork.
Expand Down Expand Up @@ -160,11 +160,6 @@ func (gs *UNetStack) Close() error {
return gs.ns.Close()
}

// DefaultCertPool implements UnderlyingNetwork.
func (gs *UNetStack) DefaultCertPool() *x509.CertPool {
return gs.ca.CertPool()
}

// DialContext implements UnderlyingNetwork.
func (gs *UNetStack) DialContext(
ctx context.Context, network string, address string) (net.Conn, error) {
Expand Down

0 comments on commit 2cb3b12

Please sign in to comment.