testing_helpers.go 12 KB

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