// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package buildlet import ( "bytes" "crypto/rand" "crypto/rsa" "crypto/sha1" "crypto/tls" "crypto/x509" "crypto/x509/pkix" "encoding/pem" "errors" "fmt" "math/big" "net" "time" ) // KeyPair is the TLS public certificate PEM file and its associated // private key PEM file that a builder will use for its HTTPS // server. The zero value means no HTTPs, which is used by the // coordinator for machines running within a firewall. type KeyPair struct { CertPEM string KeyPEM string } func (kp KeyPair) IsZero() bool { return kp == KeyPair{} } // Password returns the SHA1 of the KeyPEM. This is used as the HTTP // Basic Auth password. func (kp KeyPair) Password() string { if kp.KeyPEM != "" { return fmt.Sprintf("%x", sha1.Sum([]byte(kp.KeyPEM))) } return "" } // tlsDialer returns a TLS dialer for http.Transport.DialTLS that expects // exactly our TLS cert. func (kp KeyPair) tlsDialer() func(network, addr string) (net.Conn, error) { if kp.IsZero() { // Unused. return nil } wantCert, _ := tls.X509KeyPair([]byte(kp.CertPEM), []byte(kp.KeyPEM)) var wantPubKey *rsa.PublicKey = &wantCert.PrivateKey.(*rsa.PrivateKey).PublicKey return func(network, addr string) (net.Conn, error) { if network != "tcp" { return nil, fmt.Errorf("unexpected network %q", network) } plainConn, err := defaultDialer()("tcp", addr) if err != nil { return nil, err } tlsConn := tls.Client(plainConn, &tls.Config{InsecureSkipVerify: true}) if err := tlsConn.Handshake(); err != nil { return nil, err } certs := tlsConn.ConnectionState().PeerCertificates if len(certs) < 1 { return nil, errors.New("no server peer certificate") } cert := certs[0] peerPubRSA, ok := cert.PublicKey.(*rsa.PublicKey) if !ok { return nil, fmt.Errorf("peer cert was a %T; expected RSA", cert.PublicKey) } if peerPubRSA.N.Cmp(wantPubKey.N) != 0 { return nil, fmt.Errorf("unexpected TLS certificate") } return tlsConn, nil } } // NoKeyPair is used by the coordinator to speak http directly to buildlets, // inside their firewall, without TLS. var NoKeyPair = KeyPair{} func NewKeyPair() (KeyPair, error) { fail := func(err error) (KeyPair, error) { return KeyPair{}, err } failf := func(format string, args ...interface{}) (KeyPair, error) { return fail(fmt.Errorf(format, args...)) } priv, err := rsa.GenerateKey(rand.Reader, 2048) if err != nil { return failf("rsa.GenerateKey: %s", err) } notBefore := time.Now() notAfter := notBefore.Add(5 * 365 * 24 * time.Hour) // 5 years serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) if err != nil { return failf("failed to generate serial number: %s", err) } template := x509.Certificate{ SerialNumber: serialNumber, Subject: pkix.Name{ Organization: []string{"Gopher Co"}, }, NotBefore: notBefore, NotAfter: notAfter, KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, BasicConstraintsValid: true, DNSNames: []string{"localhost"}, } derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv) if err != nil { return failf("Failed to create certificate: %s", err) } var certOut bytes.Buffer pem.Encode(&certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) var keyOut bytes.Buffer pem.Encode(&keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)}) return KeyPair{ CertPEM: certOut.String(), KeyPEM: keyOut.String(), }, nil }