golang.org/x/build@v0.0.0-20240506185731-218518f32b70/buildlet/keypair.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package buildlet
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/rand"
    10  	"crypto/rsa"
    11  	"crypto/sha1"
    12  	"crypto/tls"
    13  	"crypto/x509"
    14  	"crypto/x509/pkix"
    15  	"encoding/pem"
    16  	"errors"
    17  	"fmt"
    18  	"math/big"
    19  	"net"
    20  	"time"
    21  )
    22  
    23  // KeyPair is the TLS public certificate PEM file and its associated
    24  // private key PEM file that a builder will use for its HTTPS
    25  // server. The zero value means no HTTPs, which is used by the
    26  // coordinator for machines running within a firewall.
    27  type KeyPair struct {
    28  	CertPEM string
    29  	KeyPEM  string
    30  }
    31  
    32  func (kp KeyPair) IsZero() bool { return kp == KeyPair{} }
    33  
    34  // Password returns the SHA1 of the KeyPEM. This is used as the HTTP
    35  // Basic Auth password.
    36  func (kp KeyPair) Password() string {
    37  	if kp.KeyPEM != "" {
    38  		return fmt.Sprintf("%x", sha1.Sum([]byte(kp.KeyPEM)))
    39  	}
    40  	return ""
    41  }
    42  
    43  // tlsDialer returns a TLS dialer for http.Transport.DialTLS that expects
    44  // exactly our TLS cert.
    45  func (kp KeyPair) tlsDialer() func(network, addr string) (net.Conn, error) {
    46  	if kp.IsZero() {
    47  		// Unused.
    48  		return nil
    49  	}
    50  	wantCert, _ := tls.X509KeyPair([]byte(kp.CertPEM), []byte(kp.KeyPEM))
    51  	var wantPubKey *rsa.PublicKey = &wantCert.PrivateKey.(*rsa.PrivateKey).PublicKey
    52  
    53  	return func(network, addr string) (net.Conn, error) {
    54  		if network != "tcp" {
    55  			return nil, fmt.Errorf("unexpected network %q", network)
    56  		}
    57  		plainConn, err := defaultDialer()("tcp", addr)
    58  		if err != nil {
    59  			return nil, err
    60  		}
    61  		tlsConn := tls.Client(plainConn, &tls.Config{InsecureSkipVerify: true})
    62  		if err := tlsConn.Handshake(); err != nil {
    63  			return nil, err
    64  		}
    65  		certs := tlsConn.ConnectionState().PeerCertificates
    66  		if len(certs) < 1 {
    67  			return nil, errors.New("no server peer certificate")
    68  		}
    69  		cert := certs[0]
    70  		peerPubRSA, ok := cert.PublicKey.(*rsa.PublicKey)
    71  		if !ok {
    72  			return nil, fmt.Errorf("peer cert was a %T; expected RSA", cert.PublicKey)
    73  		}
    74  		if peerPubRSA.N.Cmp(wantPubKey.N) != 0 {
    75  			return nil, fmt.Errorf("unexpected TLS certificate")
    76  		}
    77  		return tlsConn, nil
    78  	}
    79  }
    80  
    81  // NoKeyPair is used by the coordinator to speak http directly to buildlets,
    82  // inside their firewall, without TLS.
    83  var NoKeyPair = KeyPair{}
    84  
    85  func NewKeyPair() (KeyPair, error) {
    86  	fail := func(err error) (KeyPair, error) { return KeyPair{}, err }
    87  	failf := func(format string, args ...interface{}) (KeyPair, error) { return fail(fmt.Errorf(format, args...)) }
    88  
    89  	priv, err := rsa.GenerateKey(rand.Reader, 2048)
    90  	if err != nil {
    91  		return failf("rsa.GenerateKey: %s", err)
    92  	}
    93  
    94  	notBefore := time.Now()
    95  	notAfter := notBefore.Add(5 * 365 * 24 * time.Hour) // 5 years
    96  
    97  	serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
    98  	serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
    99  	if err != nil {
   100  		return failf("failed to generate serial number: %s", err)
   101  	}
   102  
   103  	template := x509.Certificate{
   104  		SerialNumber: serialNumber,
   105  		Subject: pkix.Name{
   106  			Organization: []string{"Gopher Co"},
   107  		},
   108  		NotBefore: notBefore,
   109  		NotAfter:  notAfter,
   110  
   111  		KeyUsage:              x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature,
   112  		ExtKeyUsage:           []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
   113  		BasicConstraintsValid: true,
   114  		DNSNames:              []string{"localhost"},
   115  	}
   116  
   117  	derBytes, err := x509.CreateCertificate(rand.Reader, &template, &template, &priv.PublicKey, priv)
   118  	if err != nil {
   119  		return failf("Failed to create certificate: %s", err)
   120  	}
   121  
   122  	var certOut bytes.Buffer
   123  	pem.Encode(&certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes})
   124  	var keyOut bytes.Buffer
   125  	pem.Encode(&keyOut, &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(priv)})
   126  	return KeyPair{
   127  		CertPEM: certOut.String(),
   128  		KeyPEM:  keyOut.String(),
   129  	}, nil
   130  }