Skip to content

Commit

Permalink
test: Correct the server and client certificates used in dialer_test.go
Browse files Browse the repository at this point in the history
  • Loading branch information
hessjcg committed Jan 31, 2025
1 parent 9261b48 commit 28e50db
Show file tree
Hide file tree
Showing 4 changed files with 373 additions and 147 deletions.
17 changes: 2 additions & 15 deletions internal/cloudsql/instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,19 +150,6 @@ func TestConnectionInfoTLSConfig(t *testing.T) {
t.Fatal(err)
}

// Now self sign the server's cert
// TODO: this also should return structured data and handle the PEM
// encoding elsewhere
certBytes, err := mock.SelfSign(i.Cert, i.Key)
if err != nil {
t.Fatal(err)
}
b, _ = pem.Decode(certBytes)
serverCACert, err := x509.ParseCertificate(b.Bytes)
if err != nil {
t.Fatal(err)
}

// Assemble a connection info with the raw and parsed client cert
// and the self-signed server certificate
ci := ConnectionInfo{
Expand All @@ -172,7 +159,7 @@ func TestConnectionInfoTLSConfig(t *testing.T) {
PrivateKey: RSAKey,
Leaf: clientCert,
},
ServerCACert: []*x509.Certificate{serverCACert},
ServerCACert: []*x509.Certificate{i.Cert},
DBVersion: "doesn't matter here",
Expiration: clientCert.NotAfter,
}
Expand All @@ -198,7 +185,7 @@ func TestConnectionInfoTLSConfig(t *testing.T) {
}

verifyPeerCert := got.VerifyPeerCertificate
err = verifyPeerCert([][]byte{serverCACert.Raw}, nil)
err = verifyPeerCert([][]byte{i.Cert.Raw}, nil)
if err != nil {
t.Fatalf("expected to verify peer cert, got error: %v", err)
}
Expand Down
302 changes: 302 additions & 0 deletions internal/mock/certs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,302 @@
// Copyright 2025 Google LLC

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at

// https://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package mock

import "C"
import (
"bytes"
"crypto/rand"
"crypto/rsa"
"crypto/sha1"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/binary"
"encoding/pem"
"math/big"
"time"
)

func name(cn string) pkix.Name {
return pkix.Name{
Country: []string{"US"},
Organization: []string{"Google\\, Inc"},
CommonName: cn,
}
}

// "C=US,O=Google\\, Inc,CN=Google Cloud SQL Root CA"
var serverCaSubject = name("Google Cloud SQL Root CA")
var intermediateCaSubject = name("Google Cloud SQL Intermediate CA")
var signingCaSubject = name("Google Cloud SQL Signing CA foo:baz")
var instanceWithCnSubject = name("myProject:myInstance")

// TLSCertificates generates an accurate reproduction of the TLS certificates
// used by Cloud SQL. This was translated to go from the Java connector.
//
// From the cloud-sql-jdbc-socket-factory project:
// core/src/test/java/com/google/cloud/sql/core/TestCertificateGenerator.java
type TLSCertificates struct {
ServerCaKeyPair *rsa.PrivateKey
SigningCaKeyPair *rsa.PrivateKey
ServerKeyPair *rsa.PrivateKey
ServerIntermediateCaKeyPair *rsa.PrivateKey
ServerSigningCaKeyPair *rsa.PrivateKey
ClientKeyPair *rsa.PrivateKey
DomainServerKeyPair *rsa.PrivateKey

ServerCaCert *x509.Certificate
SigningCaCert *x509.Certificate
ServerCert *x509.Certificate
ServerIntermediateCaCert *x509.Certificate
CasServerCertificate *x509.Certificate
CasServerCertificateChain []*x509.Certificate
DomainServerCertificate *x509.Certificate
clientCertExpires time.Time
}

func mustGenerateKey() *rsa.PrivateKey {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
panic(err)
}
return key
}

// newTLSCertificates creates a new instance of the TLSCertificates.
func newTLSCertificates(projectName, instanceName string, sans []string, clientCertExpires time.Time) *TLSCertificates {
oneYear := time.Now().AddDate(1, 0, 0)

c := &TLSCertificates{
clientCertExpires: clientCertExpires,
ServerCaKeyPair: mustGenerateKey(),
SigningCaKeyPair: mustGenerateKey(),
ServerKeyPair: mustGenerateKey(),
ServerIntermediateCaKeyPair: mustGenerateKey(),
ServerSigningCaKeyPair: mustGenerateKey(),
ClientKeyPair: mustGenerateKey(),
DomainServerKeyPair: mustGenerateKey(),
}

c.ServerCaCert = mustBuildRootCertificate(serverCaSubject, c.ServerCaKeyPair)
c.SigningCaCert = mustBuildRootCertificate(signingCaSubject, c.SigningCaKeyPair)

c.ServerCert = mustBuildSignedCertificate(
false,
name(projectName+":"+instanceName),
c.ServerKeyPair,
serverCaSubject,
c.ServerCaKeyPair,
oneYear,
nil)

c.ServerIntermediateCaCert =
mustBuildSignedCertificate(
true,
intermediateCaSubject,
c.ServerIntermediateCaKeyPair,
serverCaSubject,
c.ServerCaKeyPair,
oneYear,
nil)

c.CasServerCertificate =
mustBuildSignedCertificate(
false,
name(""),
c.ServerKeyPair,
intermediateCaSubject,
c.ServerIntermediateCaKeyPair,
oneYear,
sans)

c.CasServerCertificateChain =
[]*x509.Certificate{
c.CasServerCertificate, c.ServerIntermediateCaCert, c.ServerCaCert}

return c
}

// generateSKI Generate public key id. Certificates need to include
// the key id to make the certificate chain work.
func generateSKI(pub *rsa.PublicKey) []byte {
bs := make([]byte, 8)
binary.LittleEndian.PutUint64(bs, uint64(pub.E))

hasher := sha1.New()
hasher.Write(bs)
if pub.N != nil {
hasher.Write(pub.N.Bytes())
}
ski := hasher.Sum(nil)

return ski
}

// mustBuildRootCertificate produces a self-signed certificate.
// or panics - use only for testing.
func mustBuildRootCertificate(subject pkix.Name, k *rsa.PrivateKey) *x509.Certificate {

sn, err := rand.Int(rand.Reader, big.NewInt(1000))
if err != nil {
panic(err)
}

cert := &x509.Certificate{
SerialNumber: sn,
SubjectKeyId: generateSKI(&k.PublicKey),
Subject: subject,
NotBefore: time.Now(),
NotAfter: time.Now().AddDate(1, 0, 0),
IsCA: true,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}

certDerBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &k.PublicKey, k)
c, err := x509.ParseCertificate(certDerBytes)
if err != nil {
panic(err)
}
return c
}

// mustBuildSignedCertificate produces a certificate for Subject that is signed
// by the issuer.
func mustBuildSignedCertificate(
isCa bool,
subject pkix.Name,
subjectPublicKey *rsa.PrivateKey,
certificateIssuer pkix.Name,
issuerPrivateKey *rsa.PrivateKey,
notAfter time.Time,
subjectAlternativeNames []string) *x509.Certificate {

sn, err := rand.Int(rand.Reader, big.NewInt(1000))
if err != nil {
panic(err)
}

cert := &x509.Certificate{
SerialNumber: sn,
Subject: subject,
SubjectKeyId: generateSKI(&subjectPublicKey.PublicKey),
AuthorityKeyId: generateSKI(&issuerPrivateKey.PublicKey),
Issuer: certificateIssuer,
NotBefore: time.Now(),
NotAfter: notAfter,
IsCA: isCa,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
DNSNames: subjectAlternativeNames,
}

certDerBytes, err := x509.CreateCertificate(rand.Reader, cert, cert, &subjectPublicKey.PublicKey, issuerPrivateKey)
c, err := x509.ParseCertificate(certDerBytes)
if err != nil {
panic(err)
}
return c

}

// toPEMFormat Converts an array of certificates to PEM format.
func toPEMFormat(certs ...*x509.Certificate) ([]byte, error) {
certPEM := new(bytes.Buffer)

for _, cert := range certs {
err := pem.Encode(certPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: cert.Raw,
})
if err != nil {
return nil, err
}
}

return certPEM.Bytes(), nil
}

// signWithClientKey produces a PEM encoded certificate client certificate
// containing the clientKey public key, signed by the client CA certificate.
func (ct *TLSCertificates) signWithClientKey(clientKey *rsa.PublicKey) ([]byte, error) {
notAfter := ct.clientCertExpires
if ct.clientCertExpires.IsZero() {
notAfter = time.Now().Add(1 * time.Hour)
}

// Create a signed cert from the client's public key.
cert := &x509.Certificate{ // TODO: Validate this format vs API
SerialNumber: &big.Int{},
Subject: pkix.Name{
Country: []string{"US"},
Organization: []string{"Google, Inc"},
CommonName: "Google Cloud SQL Client",
},
NotBefore: time.Now(),
NotAfter: notAfter,
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth, x509.ExtKeyUsageServerAuth},
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
BasicConstraintsValid: true,
}
certBytes, err := x509.CreateCertificate(rand.Reader, cert, ct.SigningCaCert, clientKey, ct.SigningCaKeyPair)
if err != nil {
return nil, err
}
certPEM := new(bytes.Buffer)
err = pem.Encode(certPEM, &pem.Block{
Type: "CERTIFICATE",
Bytes: certBytes,
})
if err != nil {
return nil, err
}
return certPEM.Bytes(), nil
}

// generateServerCertWithCn generates a server certificate for legacy
// GOOGLE_MANAGED_INTERNAL_CA mode where the instance name is in the CN.
func (ct *TLSCertificates) generateServerCertWithCn(cn string) *x509.Certificate {
return mustBuildSignedCertificate(
false,
name(cn),
ct.ServerKeyPair,
serverCaSubject,
ct.ServerCaKeyPair,
time.Now().Add(1*time.Hour), nil)
}

// serverChain creates a []tls.Certificate for use with a TLS server socket.
// serverCAMode controls whether this returns a legacy or CAS server
// certificate.
func (ct *TLSCertificates) serverChain(serverCAMode string) []tls.Certificate {
// if this server is running in legacy mode
if serverCAMode == "" || serverCAMode == "GOOGLE_MANAGED_INTERNAL_CA" {
return []tls.Certificate{{
Certificate: [][]byte{ct.ServerCert.Raw, ct.ServerCaCert.Raw},
PrivateKey: ct.ServerKeyPair,
Leaf: ct.ServerCert,
}}
}

return []tls.Certificate{{
Certificate: [][]byte{ct.CasServerCertificate.Raw, ct.ServerIntermediateCaCert.Raw, ct.ServerCaCert.Raw},
PrivateKey: ct.ServerKeyPair,
Leaf: ct.CasServerCertificate,
}}

}
Loading

0 comments on commit 28e50db

Please sign in to comment.