1package certificates 2 3import ( 4 "bytes" 5 "crypto/rand" 6 "crypto/rsa" 7 "crypto/x509" 8 "crypto/x509/pkix" 9 "encoding/pem" 10 "errors" 11 "fmt" 12 "io/ioutil" 13 "math/big" 14 "net" 15 "os" 16 "time" 17 18 "github.com/hashicorp/go-multierror" 19 "github.com/hashicorp/go-uuid" 20) 21 22// Generate is a convenience method for testing. It creates a group of test certificates with the 23// client certificate reflecting the given values. Close() should be called when done to immediately 24// delete the three temporary files it has created. 25// 26// Usage: 27// 28// testCerts, err := certificates.Generate(...) 29// if err != nil { 30// ... 31// } 32// defer func(){ 33// if err := testCerts.Close(); err != nil { 34// ... 35// } 36// }() 37// 38func Generate(instanceID, orgID, spaceID, appID, ipAddress string) (*TestCertificates, error) { 39 caCert, instanceCert, instanceKey, err := generate(instanceID, orgID, spaceID, appID, ipAddress) 40 if err != nil { 41 return nil, err 42 } 43 44 // Keep a list of paths we've created so that if we fail along the way, 45 // we can attempt to clean them up. 46 var paths []string 47 pathToCACertificate, err := makePathTo(caCert) 48 if err != nil { 49 // No path was successfully created, so we don't need to cleanup here. 50 return nil, err 51 } 52 paths = append(paths, pathToCACertificate) 53 54 pathToInstanceCertificate, err := makePathTo(instanceCert) 55 if err != nil { 56 if cleanupErr := cleanup(paths); cleanupErr != nil { 57 return nil, multierror.Append(err, cleanupErr) 58 } 59 return nil, err 60 } 61 paths = append(paths, pathToInstanceCertificate) 62 63 pathToInstanceKey, err := makePathTo(instanceKey) 64 if err != nil { 65 if cleanupErr := cleanup(paths); cleanupErr != nil { 66 return nil, multierror.Append(err, cleanupErr) 67 } 68 return nil, err 69 } 70 paths = append(paths, pathToInstanceKey) 71 72 // Provide a function to be called at the end cleaning up our temporary files. 73 cleanup := func() error { 74 return cleanup(paths) 75 } 76 77 return &TestCertificates{ 78 CACertificate: caCert, 79 InstanceCertificate: instanceCert, 80 InstanceKey: instanceKey, 81 PathToCACertificate: pathToCACertificate, 82 PathToInstanceCertificate: pathToInstanceCertificate, 83 PathToInstanceKey: pathToInstanceKey, 84 cleanup: cleanup, 85 }, nil 86} 87 88type TestCertificates struct { 89 CACertificate string 90 InstanceCertificate string 91 InstanceKey string 92 93 PathToCACertificate string 94 PathToInstanceCertificate string 95 PathToInstanceKey string 96 97 // cleanup contains a function that has a path to all the temporary files we made, 98 // and deletes them. They're all in the /tmp folder so they'll disappear on the next 99 // system restart anyways, but in case of repeated tests, it's best to leave nothing 100 // behind if possible. 101 cleanup func() error 102} 103 104func (e *TestCertificates) Close() error { 105 return e.cleanup() 106} 107 108func generate(instanceID, orgID, spaceID, appID, ipAddress string) (caCert, instanceCert, instanceKey string, err error) { 109 caCert, caPriv, err := generateCA("", nil) 110 if err != nil { 111 return "", "", "", err 112 } 113 114 intermediateCert, intermediatePriv, err := generateCA(caCert, caPriv) 115 if err != nil { 116 return "", "", "", err 117 } 118 119 identityCert, identityPriv, err := generateIdentity(intermediateCert, intermediatePriv, instanceID, orgID, spaceID, appID, ipAddress) 120 if err != nil { 121 return "", "", "", err 122 } 123 124 // Convert the identity key to something appropriate for a file body. 125 out := &bytes.Buffer{} 126 pem.Encode(out, pemBlockForKey(identityPriv)) 127 instanceKey = out.String() 128 return caCert, fmt.Sprintf("%s%s", intermediateCert, identityCert), instanceKey, nil 129} 130 131func generateCA(caCert string, caPriv *rsa.PrivateKey) (string, *rsa.PrivateKey, error) { 132 template := x509.Certificate{ 133 SerialNumber: big.NewInt(1), 134 Subject: pkix.Name{ 135 Country: []string{"US"}, 136 Province: []string{"CA"}, 137 Organization: []string{"Testing, Inc."}, 138 CommonName: "test-CA", 139 }, 140 NotBefore: time.Now(), 141 NotAfter: time.Now().Add(time.Hour * 24 * 365 * 100), 142 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 143 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 144 BasicConstraintsValid: true, 145 IsCA: true, 146 } 147 148 // Default to self-signing certificates by listing using itself as a parent. 149 parent := &template 150 151 // If a cert is provided, use it as the parent. 152 if caCert != "" { 153 block, certBytes := pem.Decode([]byte(caCert)) 154 if block == nil { 155 return "", nil, errors.New("block shouldn't be nil") 156 } 157 if len(certBytes) > 0 { 158 return "", nil, errors.New("there shouldn't be more bytes") 159 } 160 ca509cert, err := x509.ParseCertificate(block.Bytes) 161 if err != nil { 162 return "", nil, err 163 } 164 parent = ca509cert 165 } 166 // If a private key isn't provided, make a new one. 167 priv := caPriv 168 if priv == nil { 169 newPriv, err := rsa.GenerateKey(rand.Reader, 2048) 170 if err != nil { 171 return "", nil, err 172 } 173 priv = newPriv 174 } 175 176 derBytes, err := x509.CreateCertificate(rand.Reader, &template, parent, publicKey(priv), priv) 177 if err != nil { 178 return "", nil, err 179 } 180 181 out := &bytes.Buffer{} 182 pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) 183 cert := out.String() 184 return cert, priv, nil 185} 186 187func generateIdentity(caCert string, caPriv *rsa.PrivateKey, instanceID, orgID, spaceID, appID, ipAddress string) (string, *rsa.PrivateKey, error) { 188 block, certBytes := pem.Decode([]byte(caCert)) 189 if block == nil { 190 return "", nil, errors.New("block shouldn't be nil") 191 } 192 if len(certBytes) > 0 { 193 return "", nil, errors.New("there shouldn't be more bytes") 194 } 195 ca509cert, err := x509.ParseCertificate(block.Bytes) 196 if err != nil { 197 return "", nil, err 198 } 199 200 template := x509.Certificate{ 201 SerialNumber: big.NewInt(1), 202 Subject: pkix.Name{ 203 Country: []string{"US"}, 204 Province: []string{"CA"}, 205 Organization: []string{"Cloud Foundry"}, 206 OrganizationalUnit: []string{ 207 fmt.Sprintf("organization:%s", orgID), 208 fmt.Sprintf("space:%s", spaceID), 209 fmt.Sprintf("app:%s", appID), 210 }, 211 CommonName: instanceID, 212 }, 213 NotBefore: time.Now(), 214 NotAfter: time.Now().Add(time.Hour * 24 * 365 * 100), 215 KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, 216 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, 217 BasicConstraintsValid: true, 218 IsCA: false, 219 IPAddresses: []net.IP{net.ParseIP(ipAddress)}, 220 } 221 222 priv, err := rsa.GenerateKey(rand.Reader, 2048) 223 if err != nil { 224 return "", nil, err 225 } 226 227 derBytes, err := x509.CreateCertificate(rand.Reader, &template, ca509cert, publicKey(priv), caPriv) 228 if err != nil { 229 return "", nil, err 230 } 231 232 out := &bytes.Buffer{} 233 pem.Encode(out, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) 234 cert := out.String() 235 return cert, priv, nil 236} 237 238func makePathTo(certOrKey string) (string, error) { 239 u, err := uuid.GenerateUUID() 240 if err != nil { 241 return "", err 242 } 243 tmpFile, err := ioutil.TempFile("", u) 244 if err != nil { 245 return "", err 246 } 247 if _, err := tmpFile.Write([]byte(certOrKey)); err != nil { 248 return "", err 249 } 250 if err := tmpFile.Close(); err != nil { 251 return "", err 252 } 253 return tmpFile.Name(), nil 254} 255 256func publicKey(priv interface{}) interface{} { 257 switch k := priv.(type) { 258 case *rsa.PrivateKey: 259 return &k.PublicKey 260 default: 261 return nil 262 } 263} 264 265func pemBlockForKey(priv interface{}) *pem.Block { 266 switch k := priv.(type) { 267 case *rsa.PrivateKey: 268 return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} 269 default: 270 return nil 271 } 272} 273 274func cleanup(paths []string) error { 275 var result error 276 for i := 0; i < len(paths); i++ { 277 if err := os.Remove(paths[i]); err != nil { 278 result = multierror.Append(result, err) 279 } 280 } 281 return result 282} 283