add dcv-filter
This commit is contained in:
commit
0bc1ac1934
|
|
@ -0,0 +1,2 @@
|
|||
cmd/certrobot.conf
|
||||
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
TCI CertRobot
|
||||
Клиентская утилита, автоматизирующая запрос/получение сертификатов и прохождение DCV.
|
||||
|
||||
Требуется аккаунт на tlscc.ru. То есть, пользователь регистрируется в ЦС TLS, а реквизиты акканута (логин/пароль)
|
||||
передаёт данной утилите (в конфигурационном файле, либо пароль можно указать в командной строке).
|
||||
|
||||
Для проверки права управления доменным именем используется HTTP-подтверждение, которое реализуется двумя способами:
|
||||
1) запуск собственного HTTP-респондера, встроенного в утилиту (требуется возможность приёма соединений 80/tcp);
|
||||
2) размещение файла с кодом подтверждения в директории независимого (от утилиты) веб-сервера (требуется указать путь к директории web root).
|
||||
|
||||
Конфигурационный файл
|
||||
|
||||
По умолчанию - tcibot.conf (см. ниже).
|
||||
Файл в формате JSON, описание полей:
|
||||
|
||||
"backend_hostname" - строка; имя хоста, под которым доступен сервис ЦС ("tlscc.ru");
|
||||
|
||||
"api_path" - строка; часть URL, обозначающая корневую директорию API ("api/1.0"); слева не должно быть косой черты ("/");
|
||||
|
||||
"login" - строка; логин пользователя в ЦС TLS ТЦИ ("user@test.ru");
|
||||
|
||||
"password" - строка; опциональный пароль к логину (обратите внимание: в данной версии пароль сохраняется в открытом виде;
|
||||
пароль можно указывать в параметрах командной строки, а не только в конфигурационном файле);
|
||||
|
||||
"working_dir" - строка; рабочая директория утилиты, без завершающей косой черты, - в эту директорию записываются полученные сертификаты
|
||||
и СЕКРЕТНЫЕ ключи ("/etc/pki/private/operations");
|
||||
|
||||
"root_cert_file" - строка; ОПЦИОНАЛЬНЫЙ СЛУЖЕБНЫЙ параметр - позволяет использовать заданный сертификат в качестве
|
||||
доверенного корневого сертификата при доступе к API; указывает на путь к PEM-файлу сертификата; при отсутствии данного
|
||||
параметра (или если значением является пустая строка) используется системный набор корневых сертификатов;
|
||||
|
||||
"debug" - true/false, флаг; включает режим отладки - в данном режиме утилита выводит дополнительные сообщения
|
||||
о статусе во время работы (false).
|
||||
|
||||
Пример файла конфигурации:
|
||||
{
|
||||
"backend_hostname" : "cs-tls.ru",
|
||||
"api_path" : "api/1.0/",
|
||||
"login" : "user@test.ru",
|
||||
"password" : "your_password",
|
||||
"working_dir" : "/etc/tcibot/private",
|
||||
"root_cert_file" : "path_to_cert's_dir",
|
||||
"debug" : false
|
||||
}
|
||||
|
||||
Вызов
|
||||
|
||||
./tcibot -d domain.tld [-c config.conf] [-p pwd123] [-r /var/www/site/]
|
||||
где:
|
||||
-d - доменное имя, для которого заказывается сертификат;
|
||||
-c - конфигурационный файл (по умолчанию - tcibot.conf в текущей директории);
|
||||
-p - пароль для аккаунта на tlscc.ru (ЦС TLS ТЦИ); пароль также может быть указан в конфигурационном файле, приоритет имеет параметр командной строки;
|
||||
-r - корневая директория веб-сервера, означает, что для подтверждения права управления используется независимый веб-сервер.
|
||||
|
||||
Основной вывод производится в STDERR, имена файлов сертификатов и ключей - выводятся в STDOUT.
|
||||
|
||||
Сборка
|
||||
|
||||
cd cmd/
|
||||
|
||||
go build -o /path/to/utils/tcibot main.go
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"backend_hostname" : "cs-tls.ru",
|
||||
"login" : "login@test.ru",
|
||||
"password" : "pwdpwdpwd",
|
||||
"debug" : true
|
||||
}
|
||||
|
|
@ -0,0 +1,653 @@
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package req
|
||||
|
||||
import(
|
||||
"os"
|
||||
"fmt"
|
||||
"crypto/x509"
|
||||
"crypto/ecdsa"
|
||||
"crypto/rand"
|
||||
"crypto/elliptic"
|
||||
"encoding/base64"
|
||||
"encoding/pem"
|
||||
"encoding/asn1"
|
||||
)
|
||||
type CSRContainer struct{
|
||||
CSR string // Данные CSR в виде строки (PEM-блок и base64).
|
||||
PrivateKey string // Секретный ключ (PEM-блок и base64).
|
||||
}
|
||||
type subjectTemplate struct{
|
||||
Subject subjectSet `asn1:"set"`
|
||||
}
|
||||
type subjectSet struct{
|
||||
CN subjectCN
|
||||
}
|
||||
type subjectCN struct{
|
||||
OID asn1.ObjectIdentifier
|
||||
Value string `asn1:"utf8"`
|
||||
}
|
||||
func CraftCSRandKey(name string) (CSRContainer, bool){
|
||||
var res CSRContainer
|
||||
var sT subjectTemplate
|
||||
privKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to generate private key: %s\n\n", err.Error())
|
||||
return res, false
|
||||
}
|
||||
sT.Subject.CN = subjectCN{
|
||||
OID : []int{ 0x02, 0x05, 0x04, 0x03 },
|
||||
Value : name,
|
||||
}
|
||||
nameRaw, err := asn1.Marshal(sT)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to generate private key: %s\n\n", err.Error())
|
||||
return res, false
|
||||
}
|
||||
var csrTemplate = x509.CertificateRequest {
|
||||
RawSubject : nameRaw,
|
||||
SignatureAlgorithm : x509.ECDSAWithSHA256,
|
||||
}
|
||||
csr, err := x509.CreateCertificateRequest(rand.Reader, &csrTemplate, privKey)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to generate CSR: %s\n\n", err.Error())
|
||||
return res, false
|
||||
}
|
||||
exportKey, err := x509.MarshalECPrivateKey(privKey)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to marshal private key: %s\n\n", err.Error())
|
||||
return res, false
|
||||
}
|
||||
block := &pem.Block{
|
||||
Type: "EC PRIVATE KEY",
|
||||
Bytes: exportKey,
|
||||
}
|
||||
pemEncodedKey := pem.EncodeToMemory(block)
|
||||
if pemEncodedKey == nil {
|
||||
fmt.Fprintf(os.Stderr, "Unable to generate PEM for key: %s\n\n", err.Error())
|
||||
return res, false
|
||||
}
|
||||
res.CSR = "-----BEGIN CERTIFICATE REQUEST-----" + base64.StdEncoding.EncodeToString(csr) + "-----END CERTIFICATE REQUEST-----"
|
||||
res.PrivateKey = string(pemEncodedKey)
|
||||
return res, true
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
package responder
|
||||
|
||||
import(
|
||||
"strings"
|
||||
"strconv"
|
||||
"errors"
|
||||
"bufio"
|
||||
"time"
|
||||
"net"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
const error404StatusEnd = "HTTP/1.1 404 Not Found\r\n\r\n"
|
||||
const error400StatusEnd = "HTTP/1.1 400 Bad Request\r\n\r\n"
|
||||
|
||||
func handler(conn net.Conn, docUrl string, codeVal string) (ret bool) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
conn.Close()
|
||||
ret = false
|
||||
return
|
||||
}
|
||||
}()
|
||||
defer conn.Close()
|
||||
contentLen := strconv.Itoa(len(codeVal))
|
||||
timeout := 11 * time.Second
|
||||
conn.SetReadDeadline(time.Now().Add(timeout))
|
||||
c := bufio.NewReader(conn)
|
||||
line, _, _ := c.ReadLine()
|
||||
|
||||
srcFields := strings.Split(string(line), " ")
|
||||
if len(srcFields) < 3 {
|
||||
conn.Write([]byte(error400StatusEnd))
|
||||
return false
|
||||
}
|
||||
if srcFields[0] != "GET" {
|
||||
conn.Write([]byte(error400StatusEnd))
|
||||
return false
|
||||
}
|
||||
if srcFields[2] != "HTTP/1.1" {
|
||||
conn.Write([]byte(error400StatusEnd))
|
||||
return false
|
||||
}
|
||||
if srcFields[1] == docUrl {
|
||||
retData := []byte("HTTP/1.1 200 OK\r\n")
|
||||
retData = append(retData, []byte("Server: tci-certrobot\r\n")[0:]...)
|
||||
retData = append(retData, []byte("Content-Type: text/html\r\n")[0:]...)
|
||||
retData = append(retData, []byte("Content-Length: " + contentLen)[0:]...)
|
||||
retData = append(retData, []byte("\r\nConnection: close\r\n\r\n")[0:]...)
|
||||
conn.Write(retData)
|
||||
conn.Write([]byte(codeVal))
|
||||
return true
|
||||
}
|
||||
conn.Write([]byte(error404StatusEnd))
|
||||
return false
|
||||
}
|
||||
|
||||
func SpinServer(c chan int, portnum string, documentUrl string, code string) bool{
|
||||
l, err := net.Listen("tcp", portnum)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error listening:", err.Error())
|
||||
return false
|
||||
}
|
||||
defer l.Close()
|
||||
go func(){
|
||||
<- c
|
||||
fmt.Fprintf(os.Stderr, "Closing!\n")
|
||||
l.Close()
|
||||
}()
|
||||
for {
|
||||
conn, err := l.Accept()
|
||||
if err != nil { // TODO: нужна обработка закрытия "сокета".
|
||||
if errors.Is(err, net.ErrClosed) {
|
||||
return true
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "FATAL: Accept error!\n", err.Error())
|
||||
return false
|
||||
}
|
||||
go handler(conn, documentUrl, code)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
|
@ -0,0 +1,612 @@
|
|||
package tciapi
|
||||
|
||||
import(
|
||||
"io"
|
||||
"os"
|
||||
"fmt"
|
||||
"bytes"
|
||||
"net/url"
|
||||
"net/http"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
const apiUrlAuth = "auth"
|
||||
const apiUrlOrder = "order"
|
||||
const apiUrlCode = "proxy/csr"
|
||||
const apiUrlValidate = "order/check-domain-ownership"
|
||||
|
||||
type stUserAuth struct {
|
||||
Status string `json:"status"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
Message string `json:"message"`
|
||||
Result stUserAuthResult `json:"result"`
|
||||
}
|
||||
|
||||
type stUserAuthResult struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
type stUnauthorized struct {
|
||||
StatusCode int `json:"statusCode"`
|
||||
ErrorDesc string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type stErrorResp struct {
|
||||
StatusCode int `json:"statusCode"`
|
||||
ErrorDesc string `json:"error"`
|
||||
Message []string `json:"message"`
|
||||
}
|
||||
|
||||
type stErrorLimit struct {
|
||||
StatusCode int `json:"statusCode"`
|
||||
ErrorDesc string `json:"error"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type stCode struct {
|
||||
Status string `json:"status"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
Message string `json:"message"`
|
||||
Result stCodeResult `json:"result"`
|
||||
}
|
||||
|
||||
type stCodeResult struct {
|
||||
Code string `json:"code"`
|
||||
DocumentName string `json:"filename"`
|
||||
}
|
||||
|
||||
type stOrderResponse struct {
|
||||
Status string `json:"status"`
|
||||
StatusCode int `json:"statusCode"`
|
||||
Message string `json:"message"`
|
||||
Result stOrderResult `json:"result"`
|
||||
}
|
||||
|
||||
type stOrderResult struct {
|
||||
DomainName string `json:"domain"`
|
||||
ValidityDays int `json:"period"`
|
||||
Method string `json:"method"`
|
||||
Wildcard bool `json:"wildcard"`
|
||||
CSR string `json:"csr"`
|
||||
Cryptosystem string `json:"certificate_type"`
|
||||
Type string `json:"certificate_kind"`
|
||||
DCVMethod string `json:"confirmation_type"`
|
||||
CustomerId string `json:"customerId"`
|
||||
Issued string `json:"issued"`
|
||||
LastCodeDate string `json:"last_new_code_date"`
|
||||
Id string `json:"id"`
|
||||
OrderNumber int `json:"number"`
|
||||
Created string `json:"created"`
|
||||
Updated string `json:"updated"`
|
||||
State string `json:"state"`
|
||||
AttemptsCounter int `json:"confirm_attempts"`
|
||||
UserInfo stUserInfo `json:"user"`
|
||||
CertificateInfo stCertificateInfo `json:"certificate"`
|
||||
}
|
||||
|
||||
type stCertificateInfo struct {
|
||||
Id string `json:"id"`
|
||||
Created string `json:"created"`
|
||||
Updated string `json:"updated"`
|
||||
DomainName string `json:"domain"`
|
||||
NotBeforeDate string `json:"start"`
|
||||
NotAfterDate string `json:"finish"`
|
||||
State string `json:"state"`
|
||||
SerialNumber string `json:"serial"`
|
||||
IssuerName string `json:"ca_name"`
|
||||
Data string `json:"body"`
|
||||
}
|
||||
|
||||
type stUserInfo struct {
|
||||
Id string `json:"id"`
|
||||
Created string `json:"created"`
|
||||
Updated string `json:"updated"`
|
||||
Number int `json:"number"`
|
||||
FirstName string `json:"firstName"`
|
||||
LastName string `json:"lastName"`
|
||||
MiddleName string `json:"middleName"`
|
||||
UserName string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
Agreement bool `json:"agreement"`
|
||||
Convention bool `json:"convention"`
|
||||
}
|
||||
|
||||
type rtOrder struct {
|
||||
DomainName string `json:"domain"`
|
||||
ValidityDays int `json:"period"`
|
||||
OrderMethod string `json:"method"`
|
||||
Wildcard bool `json:"wildcard"`
|
||||
CSR string `json:"csr"`
|
||||
Cryptosystem string `json:"certificate_type"`
|
||||
Type string `json:"certificate_kind"`
|
||||
DCVMethod string `json:"confirmation_type"`
|
||||
}
|
||||
|
||||
type rtCode struct {
|
||||
OrderId string `json:"order_id"`
|
||||
UserId string `json:"user_id"`
|
||||
Cryptosystem string `json:"type"`
|
||||
DomainName string `json:"name"`
|
||||
DCVMethod string `json:"check_type"`
|
||||
CSR string `json:"csr"`
|
||||
}
|
||||
|
||||
type rtValidate struct {
|
||||
OrderId string `json:"id"`
|
||||
}
|
||||
|
||||
type TciApi struct {
|
||||
Connected bool // Состояние "подключения"; true - был успешный логин и есть активный токен (JWT).
|
||||
Complete bool
|
||||
Login string // Логин - имя пользователя на сервисе ЦС.
|
||||
AuthToken string // Токен для аутентификации (получен с сервера на этапе "подключения").
|
||||
Hostname string // Имя хоста сервиса ЦС.
|
||||
APIPath string // Путь к API на сервере.
|
||||
CurrentOrderId string // Используемый идентификатор заказа.
|
||||
CurrentUserId string // Используемый идентификатор пользователя.
|
||||
CurrentCSR string
|
||||
CertData string
|
||||
ValidationCode string // Код валидации.
|
||||
ValidationURL string // Адрес документа для размещения кода валидации.
|
||||
DebugMode bool
|
||||
DomainName string // Используемое в заказе имя домена.
|
||||
C *http.Client
|
||||
}
|
||||
|
||||
func NewTciApi(hostname, api, login, passwd string, rootCert string, debugFlag bool) (instance *TciApi, status bool) {
|
||||
var res TciApi
|
||||
defer func() {
|
||||
if boo := recover(); boo != nil {
|
||||
instance = nil
|
||||
status = false
|
||||
return
|
||||
}
|
||||
}()
|
||||
var tlsConfig tls.Config
|
||||
res.DebugMode = debugFlag
|
||||
if rootCert != "" {
|
||||
caCertPool := x509.NewCertPool()
|
||||
if !caCertPool.AppendCertsFromPEM([]byte(rootCert)) {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", "Unable to load root certificate!")
|
||||
return &res, false
|
||||
}
|
||||
tlsConfig.RootCAs = caCertPool
|
||||
}
|
||||
res.C = &http.Client{ // TODO: добавить (здесь) параметры в конфигурацию клиента.
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tlsConfig,
|
||||
},
|
||||
}
|
||||
|
||||
res.Login = login
|
||||
res.Hostname = "https://" + hostname + "/"
|
||||
res.APIPath = api
|
||||
|
||||
response, err := res.C.PostForm(res.Hostname + res.APIPath + apiUrlAuth,
|
||||
url.Values{
|
||||
"username" : { login },
|
||||
"password" : { passwd },
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
|
||||
return &res, false
|
||||
}
|
||||
defer response.Body.Close()
|
||||
body, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
|
||||
return &res, false
|
||||
}
|
||||
switch response.StatusCode {
|
||||
case 200: // 201
|
||||
{
|
||||
s := new(stUserAuth)
|
||||
e := json.Unmarshal([]byte(body), s)
|
||||
if e != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
|
||||
return &res, false
|
||||
}
|
||||
res.AuthToken = s.Result.AccessToken
|
||||
if debugFlag {
|
||||
fmt.Fprintf(os.Stderr, "Connected (%s)\n", res.Hostname)
|
||||
fmt.Fprintf(os.Stderr, "%s\n", res.AuthToken)
|
||||
}
|
||||
return &res, true
|
||||
}
|
||||
case 401:
|
||||
{
|
||||
s := new(stUnauthorized)
|
||||
e := json.Unmarshal([]byte(body), s)
|
||||
if e != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
|
||||
return &res, false
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
|
||||
return &res, false
|
||||
}
|
||||
default:
|
||||
{
|
||||
fmt.Fprintf(os.Stderr, "Unexpected response (%s)!\n", res.Hostname)
|
||||
return &res, false
|
||||
}
|
||||
}
|
||||
|
||||
return &res, true
|
||||
}
|
||||
|
||||
func (r *TciApi) RequestCertificate(name, csr string) (status bool){
|
||||
defer func() {
|
||||
if boo := recover(); boo != nil {
|
||||
status = false
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
s := new(rtOrder)
|
||||
s.DomainName = name
|
||||
|
||||
s.Cryptosystem = "ecdsa"
|
||||
s.CSR = csr
|
||||
s.ValidityDays = 90
|
||||
s.OrderMethod = "auto"
|
||||
s.Type = "domain"
|
||||
s.DCVMethod = "http"
|
||||
s.Wildcard = false
|
||||
|
||||
reqBody, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
requestData := bytes.NewBuffer(reqBody)
|
||||
req, err := http.NewRequest("POST", r.Hostname + r.APIPath + apiUrlOrder, requestData)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
|
||||
return false
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer " + r.AuthToken)
|
||||
req.Header.Set("Cookie", "access_token=" + r.AuthToken)
|
||||
|
||||
if r.DebugMode {
|
||||
fmt.Fprintf(os.Stderr, "\nSending order (%s)\n", r.Hostname)
|
||||
}
|
||||
|
||||
response, err := r.C.Do(req)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
|
||||
return false
|
||||
}
|
||||
defer response.Body.Close()
|
||||
respBody, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
|
||||
return false
|
||||
}
|
||||
if r.DebugMode {
|
||||
fmt.Fprintf(os.Stderr, "Response code: %d\n", response.StatusCode)
|
||||
}
|
||||
switch response.StatusCode {
|
||||
case 201:
|
||||
{
|
||||
orderData := new(stOrderResponse)
|
||||
e := json.Unmarshal([]byte(respBody), orderData)
|
||||
if e != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
|
||||
return false
|
||||
}
|
||||
if r.DebugMode {
|
||||
fmt.Fprintf(os.Stderr, "OrderId: %s\n\n", orderData.Result.Id)
|
||||
}
|
||||
r.DomainName = name
|
||||
r.CurrentOrderId = orderData.Result.Id
|
||||
r.CurrentUserId = orderData.Result.CustomerId
|
||||
r.CurrentCSR = csr
|
||||
return true
|
||||
}
|
||||
case 400:
|
||||
{
|
||||
s := new(stErrorResp)
|
||||
e := json.Unmarshal([]byte(respBody), s)
|
||||
if e != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
|
||||
return false
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
|
||||
return false
|
||||
}
|
||||
case 401:
|
||||
{
|
||||
s := new(stUnauthorized)
|
||||
e := json.Unmarshal([]byte(respBody), s)
|
||||
if e != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
|
||||
return false
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
|
||||
return false
|
||||
}
|
||||
case 429:
|
||||
{
|
||||
s := new(stErrorLimit)
|
||||
e := json.Unmarshal([]byte(respBody), s)
|
||||
if e != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
|
||||
return false
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
|
||||
return false
|
||||
}
|
||||
|
||||
default:
|
||||
{
|
||||
fmt.Fprintf(os.Stderr, "Unexpected response (%s)!\n", r.Hostname)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *TciApi) RequestCode() (status bool){
|
||||
defer func() {
|
||||
if boo := recover(); boo != nil {
|
||||
status = false
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
if r.DebugMode {
|
||||
fmt.Fprintf(os.Stderr, "Requesting code for DCV\n")
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", r.Hostname + r.APIPath + apiUrlCode + "/" + r.CurrentOrderId, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
|
||||
return false
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer " + r.AuthToken)
|
||||
|
||||
response, err := r.C.Do(req)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
|
||||
return false
|
||||
}
|
||||
defer response.Body.Close()
|
||||
respBody, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
|
||||
return false
|
||||
}
|
||||
|
||||
switch response.StatusCode {
|
||||
case 200: //201
|
||||
{
|
||||
codeData := new(stCode)
|
||||
e := json.Unmarshal([]byte(respBody), codeData)
|
||||
if e != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
|
||||
return false
|
||||
}
|
||||
r.ValidationCode = codeData.Result.Code
|
||||
r.ValidationURL = codeData.Result.DocumentName
|
||||
if r.DebugMode {
|
||||
fmt.Fprintf(os.Stderr, "Code: %s\nDocument: %s\n", codeData.Result.Code, codeData.Result.DocumentName)
|
||||
}
|
||||
return true
|
||||
}
|
||||
case 400:
|
||||
{
|
||||
s := new(stErrorResp)
|
||||
e := json.Unmarshal([]byte(respBody), s)
|
||||
if e != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
|
||||
return false
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
|
||||
return false
|
||||
}
|
||||
case 401:
|
||||
{
|
||||
s := new(stUnauthorized)
|
||||
e := json.Unmarshal([]byte(respBody), s)
|
||||
if e != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
|
||||
return false
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
|
||||
return false
|
||||
}
|
||||
default:
|
||||
{
|
||||
fmt.Fprintf(os.Stderr, "Unexpected response (%s)!\n", r.Hostname)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *TciApi) StartDCV() (status bool){
|
||||
defer func() {
|
||||
if boo := recover(); boo != nil {
|
||||
status = false
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
s := new(rtValidate)
|
||||
s.OrderId = r.CurrentOrderId
|
||||
|
||||
reqBody, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
|
||||
return false
|
||||
}
|
||||
requestData := bytes.NewBuffer(reqBody)
|
||||
req, err := http.NewRequest("POST", r.Hostname + r.APIPath + apiUrlValidate, requestData)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
|
||||
return false
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer " + r.AuthToken)
|
||||
|
||||
if r.DebugMode {
|
||||
fmt.Fprintf(os.Stderr, "Start DCV\n")
|
||||
}
|
||||
|
||||
response, err := r.C.Do(req)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
|
||||
return false
|
||||
}
|
||||
defer response.Body.Close()
|
||||
respBody, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
|
||||
return false
|
||||
}
|
||||
fmt.Printf("Status Code: %d\n", response.StatusCode)
|
||||
|
||||
switch response.StatusCode {
|
||||
case 200:
|
||||
{
|
||||
if r.DebugMode {
|
||||
fmt.Fprintf(os.Stderr, "DCV process started\n")
|
||||
}
|
||||
return true
|
||||
}
|
||||
case 400:
|
||||
{
|
||||
s := new(stErrorResp)
|
||||
e := json.Unmarshal([]byte(respBody), s)
|
||||
if e != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
|
||||
return false
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
|
||||
return false
|
||||
}
|
||||
case 401:
|
||||
{
|
||||
s := new(stUnauthorized)
|
||||
e := json.Unmarshal([]byte(respBody), s)
|
||||
if e != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
|
||||
return false
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
|
||||
return false
|
||||
}
|
||||
default:
|
||||
{
|
||||
fmt.Fprintf(os.Stderr, "Unexpected response (%s)!\n", r.Hostname)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (r *TciApi) RequestStatus() (res, status bool){
|
||||
defer func() {
|
||||
if boo := recover(); boo != nil {
|
||||
res = false
|
||||
status = false
|
||||
return
|
||||
}
|
||||
}()
|
||||
|
||||
req, err := http.NewRequest("GET", r.Hostname + r.APIPath + apiUrlOrder, nil)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
|
||||
return false, false
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer " + r.AuthToken)
|
||||
|
||||
if r.DebugMode {
|
||||
fmt.Fprintf(os.Stderr, "Status check\n")
|
||||
}
|
||||
|
||||
response, err := r.C.Do(req)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
|
||||
return false, false
|
||||
}
|
||||
defer response.Body.Close()
|
||||
respBody, err := io.ReadAll(response.Body)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", err.Error())
|
||||
return false, false
|
||||
}
|
||||
|
||||
switch response.StatusCode {
|
||||
case 200:
|
||||
{
|
||||
s := new([]stOrderResult)
|
||||
e := json.Unmarshal([]byte(respBody), s)
|
||||
if e != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
|
||||
return false, false
|
||||
}
|
||||
|
||||
found := false
|
||||
var order stOrderResult
|
||||
for _, o := range(*s) {
|
||||
if r.CurrentOrderId == o.Id {
|
||||
found = true
|
||||
order = o
|
||||
}
|
||||
}
|
||||
if found {
|
||||
if (order.State == "validated") && (len(order.CertificateInfo.Data) > 0) {
|
||||
r.CertData = order.CertificateInfo.Data
|
||||
if r.DebugMode {
|
||||
fmt.Fprintf(os.Stderr, "DCV confirmed!\n")
|
||||
}
|
||||
return true, true
|
||||
}else{
|
||||
return false, true
|
||||
}
|
||||
}else{
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
case 400:
|
||||
{
|
||||
s := new(stErrorResp)
|
||||
e := json.Unmarshal([]byte(respBody), s)
|
||||
if e != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
|
||||
return false, false
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
|
||||
return false, false
|
||||
}
|
||||
case 401:
|
||||
{
|
||||
s := new(stUnauthorized)
|
||||
e := json.Unmarshal([]byte(respBody), s)
|
||||
if e != nil {
|
||||
fmt.Fprintf(os.Stderr, "ERROR: %s\n\n", e.Error())
|
||||
return false, false
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "\t%s\n\t%s\n\n", s.ErrorDesc, s.Message)
|
||||
return false, false
|
||||
}
|
||||
default:
|
||||
{
|
||||
fmt.Fprintf(os.Stderr, "Unexpected response (%s)!\n", r.Hostname)
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
|
||||
return false, false
|
||||
}
|
||||
Loading…
Reference in New Issue