github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/cert/cert_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package cert_test
     5  
     6  import (
     7  	"bytes"
     8  	"crypto/rsa"
     9  	"crypto/tls"
    10  	"crypto/x509"
    11  	"io"
    12  	"io/ioutil"
    13  	"net"
    14  	"strings"
    15  	"testing"
    16  	"time"
    17  
    18  	jc "github.com/juju/testing/checkers"
    19  	"github.com/juju/utils"
    20  	gc "gopkg.in/check.v1"
    21  
    22  	"github.com/juju/juju/cert"
    23  )
    24  
    25  func TestAll(t *testing.T) {
    26  	gc.TestingT(t)
    27  }
    28  
    29  type certSuite struct{}
    30  
    31  var _ = gc.Suite(certSuite{})
    32  
    33  func checkNotBefore(c *gc.C, cert *x509.Certificate, now time.Time) {
    34  	// Check that the certificate is valid from one week before today.
    35  	c.Check(cert.NotBefore.Before(now), jc.IsTrue)
    36  	c.Check(cert.NotBefore.Before(now.AddDate(0, 0, -6)), jc.IsTrue)
    37  	c.Check(cert.NotBefore.After(now.AddDate(0, 0, -8)), jc.IsTrue)
    38  }
    39  
    40  func checkNotAfter(c *gc.C, cert *x509.Certificate, expiry time.Time) {
    41  	// Check the surrounding day.
    42  	c.Assert(cert.NotAfter.Before(expiry.AddDate(0, 0, 1)), jc.IsTrue)
    43  	c.Assert(cert.NotAfter.After(expiry.AddDate(0, 0, -1)), jc.IsTrue)
    44  }
    45  
    46  func (certSuite) TestParseCertificate(c *gc.C) {
    47  	xcert, err := cert.ParseCert(caCertPEM)
    48  	c.Assert(err, jc.ErrorIsNil)
    49  	c.Assert(xcert.Subject.CommonName, gc.Equals, "juju testing")
    50  
    51  	xcert, err = cert.ParseCert(caKeyPEM)
    52  	c.Check(xcert, gc.IsNil)
    53  	c.Assert(err, gc.ErrorMatches, "no certificates found")
    54  
    55  	xcert, err = cert.ParseCert("hello")
    56  	c.Check(xcert, gc.IsNil)
    57  	c.Assert(err, gc.ErrorMatches, "no certificates found")
    58  }
    59  
    60  func (certSuite) TestParseCertAndKey(c *gc.C) {
    61  	xcert, key, err := cert.ParseCertAndKey(caCertPEM, caKeyPEM)
    62  	c.Assert(err, jc.ErrorIsNil)
    63  	c.Assert(xcert.Subject.CommonName, gc.Equals, "juju testing")
    64  	c.Assert(key, gc.NotNil)
    65  
    66  	c.Assert(xcert.PublicKey.(*rsa.PublicKey), gc.DeepEquals, &key.PublicKey)
    67  }
    68  
    69  func (certSuite) TestNewCA(c *gc.C) {
    70  	now := time.Now()
    71  	expiry := roundTime(now.AddDate(0, 0, 1))
    72  	caCertPEM, caKeyPEM, err := cert.NewCA("foo", "1", expiry)
    73  	c.Assert(err, jc.ErrorIsNil)
    74  
    75  	caCert, caKey, err := cert.ParseCertAndKey(caCertPEM, caKeyPEM)
    76  	c.Assert(err, jc.ErrorIsNil)
    77  
    78  	c.Check(caKey, gc.FitsTypeOf, (*rsa.PrivateKey)(nil))
    79  	c.Check(caCert.Subject.CommonName, gc.Equals, `juju-generated CA for model "foo"`)
    80  	checkNotBefore(c, caCert, now)
    81  	checkNotAfter(c, caCert, expiry)
    82  	c.Check(caCert.BasicConstraintsValid, jc.IsTrue)
    83  	c.Check(caCert.IsCA, jc.IsTrue)
    84  	//c.Assert(caCert.MaxPathLen, Equals, 0)	TODO it ends up as -1 - check that this is ok.
    85  }
    86  
    87  func (certSuite) TestNewServer(c *gc.C) {
    88  	now := time.Now()
    89  	expiry := roundTime(now.AddDate(1, 0, 0))
    90  	caCertPEM, caKeyPEM, err := cert.NewCA("foo", "1", expiry)
    91  	c.Assert(err, jc.ErrorIsNil)
    92  
    93  	caCert, _, err := cert.ParseCertAndKey(caCertPEM, caKeyPEM)
    94  	c.Assert(err, jc.ErrorIsNil)
    95  
    96  	srvCertPEM, srvKeyPEM, err := cert.NewServer(caCertPEM, caKeyPEM, expiry, nil)
    97  	c.Assert(err, jc.ErrorIsNil)
    98  	checkCertificate(c, caCert, srvCertPEM, srvKeyPEM, now, expiry)
    99  }
   100  
   101  func (certSuite) TestNewDefaultServer(c *gc.C) {
   102  	now := time.Now()
   103  	expiry := roundTime(now.AddDate(1, 0, 0))
   104  	caCertPEM, caKeyPEM, err := cert.NewCA("foo", "1", expiry)
   105  	c.Assert(err, jc.ErrorIsNil)
   106  
   107  	caCert, _, err := cert.ParseCertAndKey(caCertPEM, caKeyPEM)
   108  	c.Assert(err, jc.ErrorIsNil)
   109  
   110  	srvCertPEM, srvKeyPEM, err := cert.NewDefaultServer(caCertPEM, caKeyPEM, nil)
   111  	c.Assert(err, jc.ErrorIsNil)
   112  	srvCertExpiry := roundTime(time.Now().AddDate(10, 0, 0))
   113  	checkCertificate(c, caCert, srvCertPEM, srvKeyPEM, now, srvCertExpiry)
   114  }
   115  
   116  func checkCertificate(c *gc.C, caCert *x509.Certificate, srvCertPEM, srvKeyPEM string, now, expiry time.Time) {
   117  	srvCert, srvKey, err := cert.ParseCertAndKey(srvCertPEM, srvKeyPEM)
   118  	c.Assert(err, jc.ErrorIsNil)
   119  	c.Assert(srvCert.Subject.CommonName, gc.Equals, "*")
   120  	checkNotBefore(c, srvCert, now)
   121  	checkNotAfter(c, srvCert, expiry)
   122  	c.Assert(srvCert.BasicConstraintsValid, jc.IsFalse)
   123  	c.Assert(srvCert.IsCA, jc.IsFalse)
   124  	c.Assert(srvCert.ExtKeyUsage, gc.DeepEquals, []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth})
   125  
   126  	checkTLSConnection(c, caCert, srvCert, srvKey)
   127  }
   128  
   129  func (certSuite) TestNewServerHostnames(c *gc.C) {
   130  	type test struct {
   131  		hostnames           []string
   132  		expectedDNSNames    []string
   133  		expectedIPAddresses []net.IP
   134  	}
   135  	tests := []test{{
   136  		[]string{},
   137  		nil,
   138  		nil,
   139  	}, {
   140  		[]string{"example.com"},
   141  		[]string{"example.com"},
   142  		nil,
   143  	}, {
   144  		[]string{"example.com", "127.0.0.1"},
   145  		[]string{"example.com"},
   146  		[]net.IP{net.IPv4(127, 0, 0, 1).To4()},
   147  	}, {
   148  		[]string{"::1"},
   149  		nil,
   150  		[]net.IP{net.IPv6loopback},
   151  	}}
   152  	for i, t := range tests {
   153  		c.Logf("test %d: %v", i, t.hostnames)
   154  		expiry := roundTime(time.Now().AddDate(1, 0, 0))
   155  		srvCertPEM, srvKeyPEM, err := cert.NewServer(caCertPEM, caKeyPEM, expiry, t.hostnames)
   156  		c.Assert(err, jc.ErrorIsNil)
   157  		srvCert, _, err := cert.ParseCertAndKey(srvCertPEM, srvKeyPEM)
   158  		c.Assert(err, jc.ErrorIsNil)
   159  		c.Assert(srvCert.DNSNames, gc.DeepEquals, t.expectedDNSNames)
   160  		c.Assert(srvCert.IPAddresses, gc.DeepEquals, t.expectedIPAddresses)
   161  	}
   162  }
   163  
   164  func (certSuite) TestWithNonUTCExpiry(c *gc.C) {
   165  	expiry, err := time.Parse("2006-01-02 15:04:05.999999999 -0700 MST", "2012-11-28 15:53:57 +0100 CET")
   166  	c.Assert(err, jc.ErrorIsNil)
   167  	certPEM, keyPEM, err := cert.NewCA("foo", "1", expiry)
   168  	xcert, err := cert.ParseCert(certPEM)
   169  	c.Assert(err, jc.ErrorIsNil)
   170  	checkNotAfter(c, xcert, expiry)
   171  
   172  	var noHostnames []string
   173  	certPEM, _, err = cert.NewServer(certPEM, keyPEM, expiry, noHostnames)
   174  	xcert, err = cert.ParseCert(certPEM)
   175  	c.Assert(err, jc.ErrorIsNil)
   176  	checkNotAfter(c, xcert, expiry)
   177  }
   178  
   179  func (certSuite) TestNewServerWithInvalidCert(c *gc.C) {
   180  	var noHostnames []string
   181  	srvCert, srvKey, err := cert.NewServer(nonCACert, nonCAKey, time.Now(), noHostnames)
   182  	c.Check(srvCert, gc.Equals, "")
   183  	c.Check(srvKey, gc.Equals, "")
   184  	c.Assert(err, gc.ErrorMatches, "CA certificate is not a valid CA")
   185  }
   186  
   187  func (certSuite) TestVerify(c *gc.C) {
   188  	now := time.Now()
   189  	caCert, caKey, err := cert.NewCA("foo", "1", now.Add(1*time.Minute))
   190  	c.Assert(err, jc.ErrorIsNil)
   191  
   192  	var noHostnames []string
   193  	srvCert, _, err := cert.NewServer(caCert, caKey, now.Add(3*time.Minute), noHostnames)
   194  	c.Assert(err, jc.ErrorIsNil)
   195  
   196  	err = cert.Verify(srvCert, caCert, now)
   197  	c.Assert(err, jc.ErrorIsNil)
   198  
   199  	err = cert.Verify(srvCert, caCert, now.Add(55*time.Second))
   200  	c.Assert(err, jc.ErrorIsNil)
   201  
   202  	err = cert.Verify(srvCert, caCert, now.AddDate(0, 0, -8))
   203  	c.Check(err, gc.ErrorMatches, "x509: certificate has expired or is not yet valid")
   204  
   205  	err = cert.Verify(srvCert, caCert, now.Add(2*time.Minute))
   206  	c.Check(err, gc.ErrorMatches, "x509: certificate has expired or is not yet valid")
   207  
   208  	caCert2, caKey2, err := cert.NewCA("bar", "1", now.Add(1*time.Minute))
   209  	c.Assert(err, jc.ErrorIsNil)
   210  
   211  	// Check original server certificate against wrong CA.
   212  	err = cert.Verify(srvCert, caCert2, now)
   213  	c.Check(err, gc.ErrorMatches, "x509: certificate signed by unknown authority")
   214  
   215  	srvCert2, _, err := cert.NewServer(caCert2, caKey2, now.Add(1*time.Minute), noHostnames)
   216  	c.Assert(err, jc.ErrorIsNil)
   217  
   218  	// Check new server certificate against original CA.
   219  	err = cert.Verify(srvCert2, caCert, now)
   220  	c.Check(err, gc.ErrorMatches, "x509: certificate signed by unknown authority")
   221  }
   222  
   223  // checkTLSConnection checks that we can correctly perform a TLS
   224  // handshake using the given credentials.
   225  func checkTLSConnection(c *gc.C, caCert, srvCert *x509.Certificate, srvKey *rsa.PrivateKey) (caName string) {
   226  	clientCertPool := x509.NewCertPool()
   227  	clientCertPool.AddCert(caCert)
   228  
   229  	var outBytes bytes.Buffer
   230  
   231  	const msg = "hello to the server"
   232  	p0, p1 := net.Pipe()
   233  	p0 = &recordingConn{
   234  		Conn:   p0,
   235  		Writer: io.MultiWriter(p0, &outBytes),
   236  	}
   237  
   238  	var clientState tls.ConnectionState
   239  	done := make(chan error)
   240  	go func() {
   241  		config := utils.SecureTLSConfig()
   242  		config.Certificates = []tls.Certificate{{
   243  			Certificate: [][]byte{srvCert.Raw},
   244  			PrivateKey:  srvKey,
   245  		}}
   246  
   247  		conn := tls.Server(p1, config)
   248  		defer conn.Close()
   249  		data, err := ioutil.ReadAll(conn)
   250  		c.Assert(err, jc.ErrorIsNil)
   251  		c.Assert(string(data), gc.Equals, msg)
   252  		close(done)
   253  	}()
   254  
   255  	tlsConfig := utils.SecureTLSConfig()
   256  	tlsConfig.ServerName = "anyServer"
   257  	tlsConfig.RootCAs = clientCertPool
   258  	clientConn := tls.Client(p0, tlsConfig)
   259  	defer clientConn.Close()
   260  
   261  	_, err := clientConn.Write([]byte(msg))
   262  	c.Assert(err, jc.ErrorIsNil)
   263  	clientState = clientConn.ConnectionState()
   264  	clientConn.Close()
   265  
   266  	// wait for server to exit
   267  	<-done
   268  
   269  	outData := outBytes.String()
   270  	c.Assert(outData, gc.Not(gc.HasLen), 0)
   271  	if strings.Index(outData, msg) != -1 {
   272  		c.Fatalf("TLS connection not encrypted")
   273  	}
   274  	c.Assert(clientState.VerifiedChains, gc.HasLen, 1)
   275  	c.Assert(clientState.VerifiedChains[0], gc.HasLen, 2)
   276  	return clientState.VerifiedChains[0][1].Subject.CommonName
   277  }
   278  
   279  type recordingConn struct {
   280  	net.Conn
   281  	io.Writer
   282  }
   283  
   284  func (c recordingConn) Write(buf []byte) (int, error) {
   285  	return c.Writer.Write(buf)
   286  }
   287  
   288  // roundTime returns t rounded to the previous whole second.
   289  func roundTime(t time.Time) time.Time {
   290  	return t.Add(time.Duration(-t.Nanosecond()))
   291  }
   292  
   293  var (
   294  	caCertPEM = `
   295  -----BEGIN CERTIFICATE-----
   296  MIIBnTCCAUmgAwIBAgIBADALBgkqhkiG9w0BAQUwJjENMAsGA1UEChMEanVqdTEV
   297  MBMGA1UEAxMManVqdSB0ZXN0aW5nMB4XDTEyMTExNDE0Mzg1NFoXDTIyMTExNDE0
   298  NDM1NFowJjENMAsGA1UEChMEanVqdTEVMBMGA1UEAxMManVqdSB0ZXN0aW5nMFow
   299  CwYJKoZIhvcNAQEBA0sAMEgCQQCCOOpn9aWKcKr2GQGtygwD7PdfNe1I9BYiPAqa
   300  2I33F5+6PqFdfujUKvoyTJI6XG4Qo/CECaaN9smhyq9DxzMhAgMBAAGjZjBkMA4G
   301  A1UdDwEB/wQEAwIABDASBgNVHRMBAf8ECDAGAQH/AgEBMB0GA1UdDgQWBBQQDswP
   302  FQGeGMeTzPbHW62EZbbTJzAfBgNVHSMEGDAWgBQQDswPFQGeGMeTzPbHW62EZbbT
   303  JzALBgkqhkiG9w0BAQUDQQAqZzN0DqUyEfR8zIanozyD2pp10m9le+ODaKZDDNfH
   304  8cB2x26F1iZ8ccq5IC2LtQf1IKJnpTcYlLuDvW6yB96g
   305  -----END CERTIFICATE-----
   306  `
   307  
   308  	caKeyPEM = `
   309  -----BEGIN RSA PRIVATE KEY-----
   310  MIIBOwIBAAJBAII46mf1pYpwqvYZAa3KDAPs91817Uj0FiI8CprYjfcXn7o+oV1+
   311  6NQq+jJMkjpcbhCj8IQJpo32yaHKr0PHMyECAwEAAQJAYctedh4raLE+Ir0a3qnK
   312  pjQSfiUggtYTvTf7+tfAnZu946PX88ysr7XHPkXEGP4tWDTbl8BfGndrTKswVOx6
   313  RQIhAOT5OzafJneDQ5cuGLN/hxIPBLWxKT1/25O6dhtBlRyPAiEAkZfFvCtBZyKB
   314  JFwDdp+7gE98mXtaFrjctLWeFx797U8CIAnnqiMTwWM8H2ljyhfBtYMXeTmu3zzU
   315  0hfS4hcNwDiLAiEAkNXXU7YEPkFJD46ps1x7/s0UOutHV8tXZD44ou+l1GkCIQDO
   316  HOzuvYngJpoClGw0ipzJPoNZ2Z/GkdOWGByPeKu/8g==
   317  -----END RSA PRIVATE KEY-----
   318  `
   319  
   320  	nonCACert = `-----BEGIN CERTIFICATE-----
   321  MIIBmjCCAUagAwIBAgIBADALBgkqhkiG9w0BAQUwJjENMAsGA1UEChMEanVqdTEV
   322  MBMGA1UEAxMManVqdSB0ZXN0aW5nMB4XDTEyMTExNDE3MTU1NloXDTIyMTExNDE3
   323  MjA1NlowJjENMAsGA1UEChMEanVqdTEVMBMGA1UEAxMManVqdSB0ZXN0aW5nMFow
   324  CwYJKoZIhvcNAQEBA0sAMEgCQQC96/CsTTY1Va8et6QYNXwrssAi36asFlV/fksG
   325  hqRucidiz/+xHvhs9EiqEu7NGxeVAkcfIhXu6/BDlobtj2v5AgMBAAGjYzBhMA4G
   326  A1UdDwEB/wQEAwIABDAPBgNVHRMBAf8EBTADAgEBMB0GA1UdDgQWBBRqbxkIW4R0
   327  vmmkUoYuWg9sDob4jzAfBgNVHSMEGDAWgBRqbxkIW4R0vmmkUoYuWg9sDob4jzAL
   328  BgkqhkiG9w0BAQUDQQC3+KN8RppKdvlbP6fDwRC22PaCxd0PVyIHsn7I4jgpBPf8
   329  Z3codMYYA5/f0AmUsD7wi7nnJVPPLZK7JWu4VI/w
   330  -----END CERTIFICATE-----
   331  `
   332  
   333  	nonCAKey = `-----BEGIN RSA PRIVATE KEY-----
   334  MIIBOgIBAAJBAL3r8KxNNjVVrx63pBg1fCuywCLfpqwWVX9+SwaGpG5yJ2LP/7Ee
   335  +Gz0SKoS7s0bF5UCRx8iFe7r8EOWhu2Pa/kCAwEAAQJAdzuAxStUNPeuEWLJKkmp
   336  wuVdqocuZCtBUeE/yMEOyibZ9NLKSuDJuDorkoeoiBz2vyUITHkLp4jgNmCI8NGg
   337  AQIhAPZG9+3OghlzcqWR4nTho8KO/CuO9bu5G4jNEdIrSJ6BAiEAxWtoLZNMwI4Q
   338  kj2moFk9GdBXZV9I0t1VTwcDvVyeAXkCIDrfvldQPdO9wJOKK3vLkS1qpyf2lhIZ
   339  b1alx3PZuxOBAiAthPltYMRWtar+fTaZTFo5RH+SQSkibaRI534mQF+ySQIhAIml
   340  yiWVLC2XrtwijDu1fwh/wtFCb/bPvqvgG5wgAO+2
   341  -----END RSA PRIVATE KEY-----
   342  `
   343  )