654 lines
20 KiB
Go
654 lines
20 KiB
Go
package main
|
||
|
||
|
||
/*
|
||
* Общий ход процесса:
|
||
* 1) выполнение логина (имя пользователя и пароль из параметров) и получение авторизационного токена;
|
||
* 2) подготовка ключа и CSR; вызов метода ЦС, создающего новый заказ, получение идентификатора заказа, идентификатора пользователя;
|
||
* 3) вызов метода ЦС, формирующего параметры проверки (DCV) домена, получение кода валидации, адреса документа;
|
||
* 4) запуск собственного HTTP-респондера или размещение файла с кодом подтверждения в директории веб-сервера (web root);
|
||
* 5) вызов метода ЦС, запускающего проверку домена на стороне ЦС;
|
||
* 6) ожидание валидации (см. следующие шаги);
|
||
* 7) периодический вызов метода ЦС, который возвращает статус заказов, ожидание изменения статуса для заказа с текущим идентификатором;
|
||
* 8) в случае, если получен статус заказа, соответствующий выпуску сертификата, - извлечение файла сертификата, вывод файлов, успешное завершение;
|
||
* 9) в случае, если сертификат не выпущен (то есть, закончилось время ожидания, так как статус отказа не проверяется) - завершение с ошибкой.
|
||
*
|
||
* Особенности:
|
||
* для использования необходим аккаунт в ЦС TLS ТЦИ (https://tlscc.ru/);
|
||
*
|
||
* данная версия ожидает изменение значения поля статуса размещённого заказа, при этом успешным считается
|
||
* только один из статусов ("validated"); это означает, что выход из цикла ожидания возможен только
|
||
* в двух случаях: когда заказ успешно обработан, а сертификат выпущен, и когда превышено максимальное
|
||
* время ожидания (при этом на стороне ЦС возможны различные статусы заказов, в том числе, отмена заказа,
|
||
* однако эти статусы игнорируются);
|
||
*
|
||
* возможны ограничения на строне API сервера по количеству запросов, количеству выпущенных сертификатов
|
||
* и др.
|
||
*
|
||
*
|
||
*/
|
||
|
||
import (
|
||
"io"
|
||
"os"
|
||
"fmt"
|
||
"time"
|
||
"net"
|
||
"net/http"
|
||
"bufio"
|
||
"errors"
|
||
"regexp"
|
||
"strings"
|
||
"crypto/rand"
|
||
"crypto/x509"
|
||
"crypto/sha256"
|
||
"encoding/pem"
|
||
"encoding/json"
|
||
"certrobot/req"
|
||
"certrobot/tciapi"
|
||
"certrobot/responder"
|
||
)
|
||
|
||
const MAXOPTIONS int = 8
|
||
|
||
const helpString = `
|
||
tcibot -d domain.tld [-c config.conf] [-p pwd123] [-r /var/www/site/]
|
||
Automation for TCI CA certificate issuance.
|
||
Options:
|
||
-d - domain name;
|
||
-c - config file name (defaults to tcibot.conf);
|
||
-p - password (overwrites password from config);
|
||
-r - web root, path to (for static file validation method).
|
||
|
||
`
|
||
const errorMsgConfigEmpty = "Config: %s parameter is not set!\n"
|
||
const wellKnownSubPath = ".well-known"
|
||
const pkiValidationSubPath = "pki-validation"
|
||
|
||
type Config struct{
|
||
Backend string `json:"backend_hostname"`
|
||
APIPath string `json:"api_path"`
|
||
Login string `json:"login"`
|
||
Password string `json:"password"`
|
||
WorkingDir string `json:"working_dir"`
|
||
RootCertFile string `json:"root_cert_file"`
|
||
DebugMode bool `json:"debug"`
|
||
}
|
||
|
||
type Options struct{
|
||
DomainName string
|
||
ConfigFile string
|
||
WebRoot string
|
||
Password string
|
||
}
|
||
|
||
type StaticChallenge struct{
|
||
IsSet bool
|
||
WithWellKnowDir bool
|
||
WellKnownPath string
|
||
WithPkiValidationDir bool
|
||
PkiValidationPath string
|
||
DocFilePath string
|
||
}
|
||
|
||
func wrapper(in []byte) string {
|
||
var res []byte
|
||
counter := 0
|
||
for _, c := range(in) {
|
||
res = append(res, c)
|
||
counter = counter + 1
|
||
if counter >= 50 {
|
||
res = append(res, '\n')
|
||
counter = 0
|
||
}
|
||
}
|
||
if (counter > 0) && (counter < 50) {
|
||
res = append(res, '\n')
|
||
}
|
||
return "-----BEGIN CERTIFICATE-----\n" + string(res) + "-----END CERTIFICATE-----"
|
||
}
|
||
|
||
var validDomainName = regexp.MustCompile(`^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\.]{2,}\.[a-zA-Z0-9]{1,}[a-zA-Z0-9\-]{1,}$`)
|
||
var validChallengeName = regexp.MustCompile(`^[a-fA-F0-9]{16,64}\.[a-zA-Z]{3,5}$`)
|
||
func validateName(s string) bool {
|
||
return validDomainName.MatchString(s)
|
||
}
|
||
func validateChallengeName(s string) bool {
|
||
return validChallengeName.MatchString(s)
|
||
}
|
||
func placeStaticChallenge(webRoot, docName, docValue string) (s StaticChallenge, e error) {
|
||
var res StaticChallenge
|
||
res.IsSet = false
|
||
res.WithWellKnowDir = false
|
||
res.WithPkiValidationDir = false
|
||
|
||
if (!validateChallengeName(docName)) || (len(webRoot) < 1) || (len(docName) < 7) || (len(docValue) < 16) {
|
||
return res, errors.New("Bad challenge!")
|
||
}
|
||
if !strings.HasSuffix(webRoot, "/") {
|
||
webRoot = webRoot + "/"
|
||
}
|
||
|
||
webDir, err := os.Stat(webRoot)
|
||
if os.IsNotExist(err) {
|
||
return res, err
|
||
}
|
||
if !webDir.IsDir() {
|
||
return res, errors.New("Web root is not a directory!")
|
||
}
|
||
|
||
constructedPath := webRoot + wellKnownSubPath + "/"
|
||
res.WellKnownPath = constructedPath
|
||
_, err = os.Stat(constructedPath)
|
||
if os.IsNotExist(err) {
|
||
status := os.Mkdir(constructedPath, 0755)
|
||
if status != nil {
|
||
return res, status
|
||
}
|
||
res.WithWellKnowDir = true
|
||
}
|
||
cPath, err := os.Stat(constructedPath)
|
||
if os.IsNotExist(err) {
|
||
return res, errors.New("Unable to create " + constructedPath + " subdirectory!")
|
||
}
|
||
if !cPath.IsDir() {
|
||
return res, errors.New("Unexpected directory options!")
|
||
}
|
||
|
||
constructedPath = constructedPath + pkiValidationSubPath + "/"
|
||
res.PkiValidationPath = constructedPath
|
||
_, err = os.Stat(constructedPath)
|
||
if os.IsNotExist(err) {
|
||
status := os.Mkdir(constructedPath, 0755)
|
||
if status != nil {
|
||
return res, status
|
||
}
|
||
res.WithPkiValidationDir = true
|
||
}
|
||
cPath, err = os.Stat(constructedPath)
|
||
if os.IsNotExist(err) {
|
||
return res, errors.New("Unable to create " + constructedPath + " subdirectory!")
|
||
}
|
||
if !cPath.IsDir() {
|
||
return res, errors.New("Unexpected directory options!")
|
||
}
|
||
|
||
docPath := constructedPath + docName
|
||
_, err = os.Stat(docPath)
|
||
if !os.IsNotExist(err) {
|
||
return res, errors.New("Challenge file exists!")
|
||
}
|
||
codeFile, err := os.OpenFile(docPath, os.O_RDWR|os.O_CREATE, 0644)
|
||
if err != nil {
|
||
return res, err
|
||
}
|
||
_, err = os.Stat(docPath)
|
||
if os.IsNotExist(err) {
|
||
return res, err
|
||
}
|
||
codeFile.WriteString(docValue)
|
||
codeFile.Close()
|
||
res.DocFilePath = docPath
|
||
res.IsSet = true
|
||
return res, nil
|
||
}
|
||
|
||
func cleanStaticChallenge(c StaticChallenge) error {
|
||
if c.IsSet {
|
||
err := os.Remove(c.DocFilePath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
if c.WithPkiValidationDir {
|
||
err := os.Remove(c.PkiValidationPath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
if c.WithWellKnowDir {
|
||
err := os.Remove(c.WellKnownPath)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
func parseOptions(s []string) (Options, bool){
|
||
var res Options
|
||
if len(s) > MAXOPTIONS {
|
||
return res, false
|
||
}
|
||
i := 0;
|
||
for{
|
||
if i >= len(s){
|
||
return res, true
|
||
}
|
||
switch(s[i]){
|
||
case "-d":
|
||
if (i + 1) < len(s) {
|
||
res.DomainName = s[i+1] // TODO: Validate name.
|
||
if !validateName(res.DomainName) {
|
||
return res, false
|
||
}
|
||
i = i + 2
|
||
}else{
|
||
return res, false
|
||
}
|
||
case "-c":
|
||
if (i + 1) < len(s) {
|
||
res.ConfigFile = s[i+1]
|
||
i = i + 2
|
||
}else{
|
||
return res, false
|
||
}
|
||
case "-r":
|
||
if (i + 1) < len(s) {
|
||
res.WebRoot = s[i+1]
|
||
i = i + 2
|
||
}else{
|
||
return res, false
|
||
}
|
||
case "-p":
|
||
if (i + 1) < len(s) {
|
||
res.Password = s[i+1]
|
||
i = i + 2
|
||
}else{
|
||
return res, false
|
||
}
|
||
default:
|
||
return res, false
|
||
}
|
||
}
|
||
}
|
||
|
||
func getCertViaHttp(url string) (result []byte, status bool){
|
||
var ourUserAgent = "tcibot-client/0.1"
|
||
var backTransport = &http.Transport{
|
||
Dial: (&net.Dialer{
|
||
Timeout: 55 * time.Second,
|
||
}).Dial,
|
||
DisableKeepAlives : true, // HTTP Keep-alives.
|
||
DisableCompression : true,
|
||
MaxIdleConns : 1,
|
||
MaxIdleConnsPerHost : 1,
|
||
IdleConnTimeout : 7 * time.Second,
|
||
ExpectContinueTimeout : 7 * time.Second,
|
||
MaxResponseHeaderBytes : 4096,
|
||
}
|
||
var httpC = &http.Client{
|
||
Timeout: time.Second * 60,
|
||
Transport: backTransport,
|
||
CheckRedirect: func(req *http.Request, via []*http.Request) (error) {
|
||
return http.ErrUseLastResponse
|
||
},
|
||
}
|
||
var ret []byte
|
||
|
||
req, err := http.NewRequest("GET", url, nil)
|
||
if err != nil {
|
||
return nil, false
|
||
}
|
||
req.Header.Set("User-Agent", ourUserAgent)
|
||
|
||
resp, err := httpC.Do(req)
|
||
if err != nil {
|
||
return nil, false
|
||
}
|
||
|
||
if resp.StatusCode != 200 {
|
||
return nil, false
|
||
}
|
||
|
||
//body, err := ioutil.ReadAll(resp.Body)
|
||
body, err := io.ReadAll(resp.Body)
|
||
resp.Body.Close()
|
||
if err != nil {
|
||
return nil, false
|
||
}
|
||
|
||
if (len(body) < 128) || (len(body) > 16384) {
|
||
return nil, false
|
||
}
|
||
|
||
ret = append(ret, body...)
|
||
return ret, true
|
||
}
|
||
|
||
func loadIntermCert(eeCert string) (string, bool) {
|
||
block, _ := pem.Decode([]byte(eeCert))
|
||
if block == nil {
|
||
return "", false
|
||
}
|
||
cert, err := x509.ParseCertificate(block.Bytes)
|
||
if err != nil {
|
||
return "", false
|
||
}
|
||
if len(cert.IssuingCertificateURL) != 1 {
|
||
return "", false
|
||
}
|
||
intermPEM, _ := getCertViaHttp(cert.IssuingCertificateURL[0])
|
||
if intermPEM == nil {
|
||
return "", false
|
||
}
|
||
block, _ = pem.Decode(intermPEM)
|
||
if block == nil {
|
||
return "", false
|
||
}
|
||
return string(intermPEM), true
|
||
}
|
||
|
||
func main() {
|
||
var config Config
|
||
var DCVtype int
|
||
if len(os.Args) < 3 {
|
||
fmt.Fprintf(os.Stderr, "Not enough arguments!\n")
|
||
fmt.Fprintf(os.Stderr, helpString)
|
||
os.Exit(1)
|
||
}
|
||
|
||
opt, status := parseOptions(os.Args[1:])
|
||
if (!status) || (opt.DomainName == "") {
|
||
fmt.Fprintf(os.Stderr, "Bad options!\n")
|
||
fmt.Fprintf(os.Stderr, helpString)
|
||
os.Exit(2)
|
||
}
|
||
|
||
configFile := ""
|
||
if opt.ConfigFile == "" {
|
||
configFile = "tcibot.conf"
|
||
}else{
|
||
configFile = opt.ConfigFile
|
||
}
|
||
file, err := os.Open(configFile)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
defer file.Close()
|
||
|
||
config.Backend = "tlscc.ru"
|
||
config.APIPath = "api/1.0/"
|
||
config.WorkingDir = "certificates"
|
||
config.Login = "demo"
|
||
config.Password = "demo"
|
||
config.DebugMode = true
|
||
decoder := json.NewDecoder(file)
|
||
err = decoder.Decode(&config)
|
||
if err != nil {
|
||
panic(err)
|
||
}
|
||
|
||
if config.Backend == "" {
|
||
fmt.Fprintf(os.Stderr, errorMsgConfigEmpty, "backend_hostname")
|
||
os.Exit(1)
|
||
}
|
||
if config.Login == "" {
|
||
fmt.Fprintf(os.Stderr, errorMsgConfigEmpty, "login")
|
||
os.Exit(1)
|
||
}
|
||
if (config.Password == "") && (opt.Password == "") {
|
||
fmt.Fprintf(os.Stderr, errorMsgConfigEmpty, "password")
|
||
os.Exit(1)
|
||
}
|
||
|
||
if opt.Password != "" {
|
||
config.Password = opt.Password
|
||
fmt.Fprintf(os.Stderr, "Using password from command line.\n")
|
||
}
|
||
|
||
if config.WorkingDir == "" {
|
||
fmt.Fprintf(os.Stderr, errorMsgConfigEmpty, "working_dir")
|
||
os.Exit(1)
|
||
}
|
||
|
||
var rootCertExt []byte
|
||
if config.RootCertFile != "" {
|
||
var err error
|
||
rootCertExt, err = os.ReadFile(config.RootCertFile)
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "Unable to load root certificate! (%s)\n", err.Error())
|
||
fmt.Fprintf(os.Stderr, errorMsgConfigEmpty, "working_dir")
|
||
os.Exit(1)
|
||
}
|
||
}
|
||
|
||
if opt.WebRoot != "" {
|
||
DCVtype = 2 // Static file.
|
||
}else{
|
||
l, err := net.Listen("tcp", ":80")
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "Error listening: %s\n\n", err.Error())
|
||
os.Exit(1)
|
||
}
|
||
l.Close()
|
||
DCVtype = 1 // Web server.
|
||
}
|
||
|
||
if config.DebugMode {
|
||
fmt.Fprintf(os.Stderr, "DEBUG\nAPI:\n\t%s\n\t%s\n", config.Backend, config.Login)
|
||
fmt.Fprintf(os.Stderr, "\t%s\t%s\n", opt.DomainName, opt.WebRoot)
|
||
switch DCVtype {
|
||
case 2:
|
||
fmt.Fprintf(os.Stderr, "\t%s\n", "DCV: place challenge in web root")
|
||
case 1:
|
||
fmt.Fprintf(os.Stderr, "\t%s\n", "DCV: spin web server")
|
||
default:
|
||
}
|
||
}
|
||
|
||
fmt.Fprintf(os.Stderr, "TCI BOT beta\nLogging in...")
|
||
backend, status := tciapi.NewTciApi(config.Backend, config.APIPath, config.Login, config.Password, string(rootCertExt), config.DebugMode)
|
||
if status {
|
||
fmt.Fprintf(os.Stderr, "\033[1;32mOK\033[0m\nBackend: %s, %s \033[1;32mOK\033[0m\n", backend.Hostname, backend.Login)
|
||
fmt.Fprintf(os.Stderr, "Generating key and CSR...")
|
||
// TODO: Нужно проверять формат параметра - имени домена.
|
||
reqData, what := req.CraftCSRandKey(opt.DomainName)
|
||
if !what {
|
||
fmt.Fprintf(os.Stderr,"ERROR: Unable to create CSR and key!\n\n")
|
||
os.Exit(3)
|
||
}
|
||
fmt.Fprintf(os.Stderr, "\033[1;32mOK\033[0m\n")
|
||
fmt.Fprintf(os.Stderr, "Placing order...")
|
||
what = backend.RequestCertificate(opt.DomainName, reqData.CSR)
|
||
if !what {
|
||
fmt.Fprintf(os.Stderr, "ERROR: Unable to place order!\n\n")
|
||
os.Exit(3)
|
||
}
|
||
fmt.Fprintf(os.Stderr, "\033[1;32mOK\033[0m\n")
|
||
fmt.Fprintf(os.Stderr, "Requesting validation...\n")
|
||
if !backend.RequestCode() {
|
||
fmt.Fprintf(os.Stderr,"ERROR: Failed to get verification code and url!\n\n")
|
||
os.Exit(3)
|
||
}
|
||
fmt.Fprintf(os.Stderr, "\033[1;32mOK\033[0m\n")
|
||
|
||
var serverCh chan int
|
||
var challengePlaced StaticChallenge
|
||
switch DCVtype {
|
||
case 1:
|
||
serverCh = make(chan int)
|
||
fmt.Fprintf(os.Stderr, "Spinning web server...")
|
||
var portNum string
|
||
portNum = ":80"
|
||
go responder.SpinServer(serverCh, portNum, "/" + wellKnownSubPath + "/" + pkiValidationSubPath + "/" + backend.ValidationURL, backend.ValidationCode)
|
||
fmt.Fprintf(os.Stderr, "\033[1;32mOK\033[0m\n")
|
||
case 2:
|
||
fmt.Fprintf(os.Stderr, "Placing static challenge...")
|
||
var err error
|
||
challengePlaced, err = placeStaticChallenge(opt.WebRoot, backend.ValidationURL, backend.ValidationCode)
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "\nERROR: %s\n\n", err.Error())
|
||
os.Exit(11)
|
||
}
|
||
fmt.Fprintf(os.Stderr, "\033[1;32mOK\033[0m\n")
|
||
default:
|
||
fmt.Fprintf(os.Stderr, "ERROR: Internal error (unexpected DCV method)!\n\n")
|
||
os.Exit(3)
|
||
}
|
||
|
||
fmt.Fprintf(os.Stderr, "Requesting verification...\n\n")
|
||
if !backend.StartDCV() {
|
||
if DCVtype == 2 {
|
||
err := cleanStaticChallenge(challengePlaced)
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "\nWARNING: %s\n\n", err.Error())
|
||
}
|
||
}
|
||
fmt.Fprintf(os.Stderr, "ERROR: Unable to start DCV process!\n\n")
|
||
os.Exit(3)
|
||
}
|
||
fmt.Fprintf(os.Stderr, "\033[1;32mOK\033[0m\n")
|
||
|
||
start := time.Now()
|
||
fmt.Fprintf(os.Stderr, "Waiting for certificate...")
|
||
for j := 0; j < 35; j++ {
|
||
result, e := backend.RequestStatus()
|
||
if !e {
|
||
switch DCVtype {
|
||
case 1:
|
||
serverCh <- 1
|
||
case 2:
|
||
fmt.Fprintf(os.Stderr, "Cleaning static challenge...")
|
||
errStatus := cleanStaticChallenge(challengePlaced)
|
||
if errStatus != nil {
|
||
fmt.Fprintf(os.Stderr, "\nWARNING: %s \n\n", errStatus.Error())
|
||
}else{
|
||
fmt.Fprintf(os.Stderr, "\033[1;32mOK\033[0m\n")
|
||
}
|
||
default:
|
||
fmt.Fprintf(os.Stderr, "\nInternal error\n\n")
|
||
}
|
||
fmt.Fprintf(os.Stderr, "ERROR: Unexpected result - missing OrderId!\n\n")
|
||
os.Exit(3)
|
||
}
|
||
if result {
|
||
switch DCVtype {
|
||
case 1:
|
||
serverCh <- 1
|
||
case 2:
|
||
fmt.Fprintf(os.Stderr, "Cleaning static challenge...")
|
||
errStatus := cleanStaticChallenge(challengePlaced)
|
||
if errStatus != nil {
|
||
fmt.Fprintf(os.Stderr, "\nWARNING: %s \n\n", errStatus.Error())
|
||
}else{
|
||
fmt.Fprintf(os.Stderr, "\033[1;32mOK\033[0m\n")
|
||
}
|
||
default:
|
||
fmt.Fprintf(os.Stderr, "\nInternal error\n\n")
|
||
}
|
||
fmt.Fprintf(os.Stderr, "\033[1;32mOK\033[0m\n")
|
||
fmt.Fprintf(os.Stderr, "Elapsed time: %s\n\n", time.Since(start))
|
||
// TODO: добавить проверку того, что файлы с заданными именами уже существуют.
|
||
// TODO: переделать шаблон для имён файлов - на более понятный, но со счётчиком.
|
||
|
||
var certFileName, intermFileName, keyFileName string
|
||
gotNames := false
|
||
for nameCounter := 0; nameCounter < 5; nameCounter++ {
|
||
timeStr := time.Now().Format("2006-01-02")
|
||
buf := make([]byte, 16)
|
||
_, err := rand.Read(buf)
|
||
if err != nil {
|
||
panic(err.Error())
|
||
}
|
||
h := sha256.New()
|
||
h.Write(buf)
|
||
nameS := h.Sum(nil)[0:2]
|
||
|
||
certFileName = config.WorkingDir + "/" + timeStr + "-" + backend.DomainName + fmt.Sprintf("-%x%d", nameS, nameCounter) + ".cert.pem"
|
||
intermFileName = config.WorkingDir + "/" + timeStr + "-" + backend.DomainName + fmt.Sprintf("-%x%d", nameS, nameCounter) + ".bundle.pem"
|
||
keyFileName = config.WorkingDir + "/" + timeStr + "-" + backend.DomainName + fmt.Sprintf("-%x%d", nameS, nameCounter) + ".private.key.pem"
|
||
_, errCert := os.Stat(certFileName)
|
||
_, errInterm := os.Stat(intermFileName)
|
||
_, errKey := os.Stat(keyFileName)
|
||
if os.IsNotExist(errCert) && os.IsNotExist(errInterm) && os.IsNotExist(errKey) {
|
||
gotNames = true
|
||
break
|
||
}
|
||
}
|
||
certString := wrapper([]byte(backend.CertData))
|
||
keyString := reqData.PrivateKey
|
||
if gotNames {
|
||
certFile, err := os.Create(certFileName)
|
||
if err != nil {
|
||
fmt.Printf("Certificate:\n%s\n\nKey:\n%s\n\n", certString, keyString)
|
||
panic("Could not create output file (certificate)! " + err.Error())
|
||
}
|
||
keyFile, err := os.Create(keyFileName)
|
||
if err != nil {
|
||
fmt.Printf("Certificate:\n%s\n\nKey:\n%s\n\n", certString, keyString)
|
||
certFile.Close()
|
||
panic("Could not create output file (key)! " + err.Error())
|
||
}
|
||
wKey := bufio.NewWriter(keyFile)
|
||
_, err = wKey.WriteString(reqData.PrivateKey)
|
||
if err != nil {
|
||
fmt.Printf("Certificate:\n%s\n\nKey:\n%s\n\n", certString, keyString)
|
||
keyFile.Close()
|
||
certFile.Close()
|
||
panic("Error writing key! " + err.Error())
|
||
}
|
||
wCert := bufio.NewWriter(certFile)
|
||
_, err = wCert.WriteString(wrapper([]byte(backend.CertData)))
|
||
if err != nil {
|
||
fmt.Printf("Certificate:\n%s\n\nKey:\n%s\n\n", certString, keyString)
|
||
keyFile.Close()
|
||
certFile.Close()
|
||
panic("Error writing certificate! " + err.Error())
|
||
}
|
||
wKey.Flush()
|
||
wCert.Flush()
|
||
keyFile.Close()
|
||
certFile.Close()
|
||
intermCert, loadSt := loadIntermCert(certString)
|
||
if loadSt {
|
||
intermFile, err := os.Create(intermFileName)
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "Warning: could not create file for intermediate certificate!\n")
|
||
}
|
||
wInterm := bufio.NewWriter(intermFile)
|
||
_, err = wInterm.WriteString(intermCert)
|
||
if err != nil {
|
||
fmt.Fprintf(os.Stderr, "Warning: could not write intermediate certificate to file!\n")
|
||
}
|
||
wInterm.Flush()
|
||
intermFile.Close()
|
||
}else{
|
||
fmt.Fprintf(os.Stderr, "Warning: could not load intermediate certificate!\n")
|
||
}
|
||
fmt.Printf("Certificate file: %s\nKey file: %s\n", certFileName, keyFileName)
|
||
if loadSt {
|
||
fmt.Printf("CA bundle file: %s\n", intermFileName)
|
||
}
|
||
fmt.Fprintf(os.Stderr, "\n\033[1;32mDONE\033[0m\n\n")
|
||
os.Exit(0)
|
||
}else{
|
||
fmt.Printf("ERROR writing certificate and key!\nCertificate:\n%s\n\nKey:\n%s\n\n", certString, keyString)
|
||
os.Exit(3)
|
||
}
|
||
}
|
||
time.Sleep(7 * time.Second)
|
||
fmt.Fprintf(os.Stderr, ".")
|
||
}
|
||
switch DCVtype {
|
||
case 1:
|
||
serverCh <- 1
|
||
case 2:
|
||
fmt.Fprintf(os.Stderr, "Cleaning static challenge...")
|
||
errStatus := cleanStaticChallenge(challengePlaced)
|
||
if errStatus != nil {
|
||
fmt.Fprintf(os.Stderr, "\nWARNING: %s \n\n", errStatus.Error())
|
||
}else{
|
||
fmt.Fprintf(os.Stderr, "\033[1;32mOK\033[0m\n")
|
||
}
|
||
default:
|
||
fmt.Fprintf(os.Stderr, "\nInternal error\n\n")
|
||
}
|
||
fmt.Fprintf(os.Stderr,"ERROR: Unable to get certificate!\n\n")
|
||
os.Exit(3)
|
||
}else{
|
||
fmt.Fprintf(os.Stderr, "\nUnable to connect to backend!\n\n")
|
||
os.Exit(3)
|
||
}
|
||
}
|