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 }