2015-01-16 23:59:14 +03:00
|
|
|
// 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)
|
|
|
|
}
|
2015-02-03 14:09:50 +03:00
|
|
|
plainConn, err := defaultDialer()("tcp", addr)
|
2015-01-16 23:59:14 +03:00
|
|
|
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
|
|
|
|
}
|