testing_helpers.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429
  1. // These functions are designed for use in testing other parts of the code.
  2. package testsuite
  3. import (
  4. "bufio"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "io/ioutil"
  9. "os"
  10. "os/exec"
  11. "strconv"
  12. "strings"
  13. "testing"
  14. "time"
  15. "github.com/cloudflare/cfssl/config"
  16. "github.com/cloudflare/cfssl/csr"
  17. )
  18. // CFSSLServerData is the data with which a server is initialized. These fields
  19. // can be left empty if desired. Any empty fields passed in to StartServer will
  20. // lead to the server being initialized with the default values defined by the
  21. // 'cfssl serve' command.
  22. type CFSSLServerData struct {
  23. CA []byte
  24. CABundle []byte
  25. CAKey []byte
  26. IntBundle []byte
  27. }
  28. // CFSSLServer is the type returned by StartCFSSLServer. It serves as a handle
  29. // to a running CFSSL server.
  30. type CFSSLServer struct {
  31. process *os.Process
  32. tempFiles []string
  33. }
  34. // StartCFSSLServer creates a local server listening on the given address and
  35. // port number. Both the address and port number are assumed to be valid.
  36. func StartCFSSLServer(address string, portNumber int, serverData CFSSLServerData) (*CFSSLServer, error) {
  37. // This value is explained below.
  38. startupTime := time.Second
  39. // We return this when an error occurs.
  40. nilServer := &CFSSLServer{nil, nil}
  41. args := []string{"serve", "-address", address, "-port", strconv.Itoa(portNumber)}
  42. var tempCAFile, tempCABundleFile, tempCAKeyFile, tempIntBundleFile string
  43. var err error
  44. var tempFiles []string
  45. if len(serverData.CA) > 0 {
  46. tempCAFile, err = createTempFile(serverData.CA)
  47. tempFiles = append(tempFiles, tempCAFile)
  48. args = append(args, "-ca")
  49. args = append(args, tempCAFile)
  50. }
  51. if len(serverData.CABundle) > 0 {
  52. tempCABundleFile, err = createTempFile(serverData.CABundle)
  53. tempFiles = append(tempFiles, tempCABundleFile)
  54. args = append(args, "-ca-bundle")
  55. args = append(args, tempCABundleFile)
  56. }
  57. if len(serverData.CAKey) > 0 {
  58. tempCAKeyFile, err = createTempFile(serverData.CAKey)
  59. tempFiles = append(tempFiles, tempCAKeyFile)
  60. args = append(args, "-ca-key")
  61. args = append(args, tempCAKeyFile)
  62. }
  63. if len(serverData.IntBundle) > 0 {
  64. tempIntBundleFile, err = createTempFile(serverData.IntBundle)
  65. tempFiles = append(tempFiles, tempIntBundleFile)
  66. args = append(args, "-int-bundle")
  67. args = append(args, tempIntBundleFile)
  68. }
  69. // If an error occurred in the creation of any file, return an error.
  70. if err != nil {
  71. for _, file := range tempFiles {
  72. os.Remove(file)
  73. }
  74. return nilServer, err
  75. }
  76. command := exec.Command("cfssl", args...)
  77. stdErrPipe, err := command.StderrPipe()
  78. if err != nil {
  79. for _, file := range tempFiles {
  80. os.Remove(file)
  81. }
  82. return nilServer, err
  83. }
  84. err = command.Start()
  85. if err != nil {
  86. for _, file := range tempFiles {
  87. os.Remove(file)
  88. }
  89. return nilServer, err
  90. }
  91. // We check to see if the address given is already in use. There is no way
  92. // to do this other than to just wait and see if an error message pops up.
  93. // Therefore we wait for startupTime, and if we don't see an error message
  94. // by then, we deem the server ready and return.
  95. errorOccurred := make(chan bool)
  96. go func() {
  97. scanner := bufio.NewScanner(stdErrPipe)
  98. for scanner.Scan() {
  99. line := scanner.Text()
  100. if strings.Contains(line, "address already in use") {
  101. errorOccurred <- true
  102. }
  103. }
  104. }()
  105. select {
  106. case <-errorOccurred:
  107. for _, file := range tempFiles {
  108. os.Remove(file)
  109. }
  110. return nilServer, errors.New(
  111. "Error occurred on server: address " + address + ":" +
  112. strconv.Itoa(portNumber) + " already in use.")
  113. case <-time.After(startupTime):
  114. return &CFSSLServer{command.Process, tempFiles}, nil
  115. }
  116. }
  117. // Kill a running CFSSL server.
  118. func (server *CFSSLServer) Kill() error {
  119. for _, file := range server.tempFiles {
  120. os.Remove(file)
  121. }
  122. return server.process.Kill()
  123. }
  124. // CreateCertificateChain creates a chain of certificates from a slice of
  125. // requests. The first request is the root certificate and the last is the
  126. // leaf. The chain is returned as a slice of PEM-encoded bytes.
  127. func CreateCertificateChain(requests []csr.CertificateRequest) (certChain []byte, key []byte, err error) {
  128. // Create the root certificate using the first request. This will be
  129. // self-signed.
  130. certChain = make([]byte, 0)
  131. rootCert, prevKey, err := CreateSelfSignedCert(requests[0])
  132. if err != nil {
  133. return nil, nil, err
  134. }
  135. certChain = append(certChain, rootCert...)
  136. // For each of the next requests, create a certificate signed by the
  137. // previous certificate.
  138. prevCert := rootCert
  139. for _, request := range requests[1:] {
  140. cert, key, err := SignCertificate(request, prevCert, prevKey)
  141. if err != nil {
  142. return nil, nil, err
  143. }
  144. certChain = append(certChain, byte('\n'))
  145. certChain = append(certChain, cert...)
  146. prevCert = cert
  147. prevKey = key
  148. }
  149. return certChain, key, nil
  150. }
  151. // CreateSelfSignedCert creates a self-signed certificate from a certificate
  152. // request. This function just calls the CLI "gencert" command.
  153. func CreateSelfSignedCert(request csr.CertificateRequest) (encodedCert, encodedKey []byte, err error) {
  154. // Marshall the request into JSON format and write it to a temporary file.
  155. jsonBytes, err := json.Marshal(request)
  156. if err != nil {
  157. return nil, nil, err
  158. }
  159. tempFile, err := createTempFile(jsonBytes)
  160. if err != nil {
  161. os.Remove(tempFile)
  162. return nil, nil, err
  163. }
  164. // Create the certificate with the CLI tools.
  165. command := exec.Command("cfssl", "gencert", "-initca", tempFile)
  166. CLIOutput, err := command.CombinedOutput()
  167. if err != nil {
  168. os.Remove(tempFile)
  169. return nil, nil, fmt.Errorf("%v - CLI output: %s", err, string(CLIOutput))
  170. }
  171. err = checkCLIOutput(CLIOutput)
  172. if err != nil {
  173. os.Remove(tempFile)
  174. return nil, nil, err
  175. }
  176. encodedCert, err = cleanCLIOutput(CLIOutput, "cert")
  177. if err != nil {
  178. os.Remove(tempFile)
  179. return nil, nil, err
  180. }
  181. encodedKey, err = cleanCLIOutput(CLIOutput, "key")
  182. if err != nil {
  183. os.Remove(tempFile)
  184. return nil, nil, err
  185. }
  186. os.Remove(tempFile)
  187. return encodedCert, encodedKey, nil
  188. }
  189. // SignCertificate uses a certificate (input as signerCert) to create a signed
  190. // certificate for the input request.
  191. func SignCertificate(request csr.CertificateRequest, signerCert, signerKey []byte) (encodedCert, encodedKey []byte, err error) {
  192. // Marshall the request into JSON format and write it to a temporary file.
  193. jsonBytes, err := json.Marshal(request)
  194. if err != nil {
  195. return nil, nil, err
  196. }
  197. tempJSONFile, err := createTempFile(jsonBytes)
  198. if err != nil {
  199. os.Remove(tempJSONFile)
  200. return nil, nil, err
  201. }
  202. // Create a CSR file with the CLI tools.
  203. command := exec.Command("cfssl", "genkey", tempJSONFile)
  204. CLIOutput, err := command.CombinedOutput()
  205. if err != nil {
  206. os.Remove(tempJSONFile)
  207. return nil, nil, fmt.Errorf("%v - CLI output: %s", err, string(CLIOutput))
  208. }
  209. err = checkCLIOutput(CLIOutput)
  210. if err != nil {
  211. os.Remove(tempJSONFile)
  212. return nil, nil, err
  213. }
  214. encodedCSR, err := cleanCLIOutput(CLIOutput, "csr")
  215. if err != nil {
  216. os.Remove(tempJSONFile)
  217. return nil, nil, err
  218. }
  219. encodedCSRKey, err := cleanCLIOutput(CLIOutput, "key")
  220. if err != nil {
  221. os.Remove(tempJSONFile)
  222. return nil, nil, err
  223. }
  224. // Now we write this encoded CSR and its key to file.
  225. tempCSRFile, err := createTempFile(encodedCSR)
  226. if err != nil {
  227. os.Remove(tempJSONFile)
  228. os.Remove(tempCSRFile)
  229. return nil, nil, err
  230. }
  231. // We also need to write the signer's certficate and key to temporary files.
  232. tempSignerCertFile, err := createTempFile(signerCert)
  233. if err != nil {
  234. os.Remove(tempJSONFile)
  235. os.Remove(tempCSRFile)
  236. os.Remove(tempSignerCertFile)
  237. return nil, nil, err
  238. }
  239. tempSignerKeyFile, err := createTempFile(signerKey)
  240. if err != nil {
  241. os.Remove(tempJSONFile)
  242. os.Remove(tempCSRFile)
  243. os.Remove(tempSignerCertFile)
  244. os.Remove(tempSignerKeyFile)
  245. return nil, nil, err
  246. }
  247. // Now we use the signer's certificate and key file along with the CSR file
  248. // to sign a certificate for the input request. We use the CLI tools to do
  249. // this.
  250. command = exec.Command(
  251. "cfssl",
  252. "sign",
  253. "-ca", tempSignerCertFile,
  254. "-ca-key", tempSignerKeyFile,
  255. "-hostname", request.CN,
  256. tempCSRFile,
  257. )
  258. CLIOutput, err = command.CombinedOutput()
  259. if err != nil {
  260. return nil, nil, fmt.Errorf("%v - CLI output: %s", err, string(CLIOutput))
  261. }
  262. err = checkCLIOutput(CLIOutput)
  263. if err != nil {
  264. return nil, nil, fmt.Errorf("%v - CLI output: %s", err, string(CLIOutput))
  265. }
  266. encodedCert, err = cleanCLIOutput(CLIOutput, "cert")
  267. if err != nil {
  268. return nil, nil, err
  269. }
  270. // Clean up.
  271. os.Remove(tempJSONFile)
  272. os.Remove(tempCSRFile)
  273. os.Remove(tempSignerCertFile)
  274. os.Remove(tempSignerKeyFile)
  275. return encodedCert, encodedCSRKey, nil
  276. }
  277. // Creates a temporary file with the given data. Returns the file name.
  278. func createTempFile(data []byte) (fileName string, err error) {
  279. // Avoid overwriting a file in the currect directory by choosing an unused
  280. // file name.
  281. baseName := "temp"
  282. tempFileName := baseName
  283. tryIndex := 0
  284. for {
  285. if _, err := os.Stat(tempFileName); err == nil {
  286. tempFileName = baseName + strconv.Itoa(tryIndex)
  287. tryIndex++
  288. } else {
  289. break
  290. }
  291. }
  292. readWritePermissions := os.FileMode(0664)
  293. err = ioutil.WriteFile(tempFileName, data, readWritePermissions)
  294. if err != nil {
  295. return "", err
  296. }
  297. return tempFileName, nil
  298. }
  299. // Checks the CLI Output for failure.
  300. func checkCLIOutput(CLIOutput []byte) error {
  301. outputString := string(CLIOutput)
  302. // Proper output will contain the substring "---BEGIN" somewhere
  303. failureOccurred := !strings.Contains(outputString, "---BEGIN")
  304. if failureOccurred {
  305. return errors.New("Failure occurred during CLI execution: " + outputString)
  306. }
  307. return nil
  308. }
  309. // Returns the cleaned up PEM encoding for the item specified (for example,
  310. // 'cert' or 'key').
  311. func cleanCLIOutput(CLIOutput []byte, item string) (cleanedOutput []byte, err error) {
  312. outputString := string(CLIOutput)
  313. // The keyword will be surrounded by quotes.
  314. itemString := "\"" + item + "\""
  315. // We should only search for the keyword beyond this point.
  316. eligibleSearchIndex := strings.Index(outputString, "{")
  317. outputString = outputString[eligibleSearchIndex:]
  318. // Make sure the item is present in the output.
  319. if strings.Index(outputString, itemString) == -1 {
  320. return nil, errors.New("Item " + item + " not found in CLI Output")
  321. }
  322. // We add 2 for the [:"] that follows the item
  323. startIndex := strings.Index(outputString, itemString) + len(itemString) + 2
  324. outputString = outputString[startIndex:]
  325. endIndex := strings.Index(outputString, "\\n\"")
  326. outputString = outputString[:endIndex]
  327. outputString = strings.Replace(outputString, "\\n", "\n", -1)
  328. return []byte(outputString), nil
  329. }
  330. // NewConfig returns a config object from the data passed.
  331. func NewConfig(t *testing.T, configBytes []byte) *config.Config {
  332. conf, err := config.LoadConfig([]byte(configBytes))
  333. if err != nil {
  334. t.Fatal("config loading error:", err)
  335. }
  336. if !conf.Valid() {
  337. t.Fatal("config is not valid")
  338. }
  339. return conf
  340. }
  341. // CSRTest holds information about CSR test files.
  342. type CSRTest struct {
  343. File string
  344. KeyAlgo string
  345. KeyLen int
  346. // Error checking function
  347. ErrorCallback func(*testing.T, error)
  348. }
  349. // CSRTests define a set of CSR files for testing.
  350. var CSRTests = []CSRTest{
  351. {
  352. File: "../../signer/local/testdata/rsa2048.csr",
  353. KeyAlgo: "rsa",
  354. KeyLen: 2048,
  355. ErrorCallback: nil,
  356. },
  357. {
  358. File: "../../signer/local/testdata/rsa3072.csr",
  359. KeyAlgo: "rsa",
  360. KeyLen: 3072,
  361. ErrorCallback: nil,
  362. },
  363. {
  364. File: "../../signer/local/testdata/rsa4096.csr",
  365. KeyAlgo: "rsa",
  366. KeyLen: 4096,
  367. ErrorCallback: nil,
  368. },
  369. {
  370. File: "../../signer/local/testdata/ecdsa256.csr",
  371. KeyAlgo: "ecdsa",
  372. KeyLen: 256,
  373. ErrorCallback: nil,
  374. },
  375. {
  376. File: "../../signer/local/testdata/ecdsa384.csr",
  377. KeyAlgo: "ecdsa",
  378. KeyLen: 384,
  379. ErrorCallback: nil,
  380. },
  381. {
  382. File: "../../signer/local/testdata/ecdsa521.csr",
  383. KeyAlgo: "ecdsa",
  384. KeyLen: 521,
  385. ErrorCallback: nil,
  386. },
  387. }