613 lines
15 KiB
Go
613 lines
15 KiB
Go
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
|
||
}
|