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 }