yats.git

commit 206041518aeb1106d8d4dab65ffc175aad1da4da

Author: Paolo Lulli <paolo@lulli.net>

Get certificates from web PKI

 client/config/cli-config.go | 4 +
 client/csr.pem | 11 +++++
 client/files.go | 24 +++++++++++
 client/io.go | 63 +++++++++++++++++++++++++++++
 client/main.go | 84 +++++++++++++--------------------------
 client/pki.go | 40 ++++++-----------
 client/pkiclient.go | 56 ++++++++++++++++++++++++++
 client/time-util.go | 38 +++++++++++++++++


diff --git a/client/config/cli-config.go b/client/config/cli-config.go
index 6cd357b202469fdf2996f56b133c14ea1d1f1c4e..ae8bce2a047d0a14e2747ac78495abd9b2ee0277 100644
--- a/client/config/cli-config.go
+++ b/client/config/cli-config.go
@@ -22,9 +22,11 @@ 	TlsCertificate  string `json:"tlsCertificate"`
 	TlsVerifyServer string `json:"tlsVerifyServer"`
 
 	// For certificate request over PKI endpoint
-	ClientCnName       string `json:"clientCnName"`
+	ClientCertDir      string `json:"clientCertDir"`
+	ClientCn           string `json:"clientCn"`
 	ClientOrganization string `json:"clientOrganization"`
 	ClientEmail        string `json:"clientEmail"`
+	PkiEndpoint        string `json:"pkiEndpoint"`
 }
 
 func GetClientConfig(fileName string) ClientConfiguration {




diff --git a/client/csr.pem b/client/csr.pem
new file mode 100644
index 0000000000000000000000000000000000000000..50841fccb86c76b448844ae8964008b373a87c7f
--- /dev/null
+++ b/client/csr.pem
@@ -0,0 +1,11 @@
+-----BEGIN CERTIFICATE REQUEST-----
+MIIBjjCB+AIBADBPMRgwFgYDVQQKEw95YXRzLXNlcnZpY2UtY2ExDzANBgNVBAMT
+BmNubmFtZTEiMCAGCSqGSIb3DQEJARYTZXhwZXJpbWVudEBlbWFpbC5jbzCBnzAN
+BgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAwCKr4yTflNFJw+9Lcb0q7JbXON6n7stj
+XptCMt8h2nY4N/nSc74Tm0EfLwIDQovohyUMWJ+wI7dFwlt5/ZMWxqh+lzXXj2Nv
+yLiJ9MhUqj2IONY4jeX2R97+uDzZiJhF2ri68NaYLG3tpQ3DCgnyFXXtj4qhSiCi
+YsE+22WdGxMCAwEAAaAAMA0GCSqGSIb3DQEBCwUAA4GBAIwWVCtQO6udDwk/zT/6
+TDwhLBBY16rEiEZ3UNer3lont9MZOkDOQcKVdOWNIHl8aIvAYyiQYhHUfVOe6KmN
+1EfTUykQMvL6FOR20Ye7Wbn3EX12b6lMGlyi/jLa7x6JmR5cM71Za/7udg522w3v
+dh0XRe2eTB5ZyoI/Sd6N9DsS
+-----END CERTIFICATE REQUEST-----




diff --git a/client/files.go b/client/files.go
new file mode 100644
index 0000000000000000000000000000000000000000..f6d9b0bd3bc397daa643e72c1be0c53a3c35dcfb
--- /dev/null
+++ b/client/files.go
@@ -0,0 +1,24 @@
+/**
+ * Yats - yats
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Paolo Lulli <kevwe.com>
+ * @copyright Paolo Lulli 2024
+ */
+package main
+
+import (
+	"fmt"
+	"os"
+)
+
+func makeDirIfItDoesNotExist(path string) {
+	if _, err := os.Stat(path); os.IsNotExist(err) {
+		err := os.MkdirAll(path, 0700)
+		if err != nil {
+			fmt.Printf("Error creating dir: %s %v", path, err)
+		}
+	}
+}




diff --git a/client/io.go b/client/io.go
new file mode 100644
index 0000000000000000000000000000000000000000..450f0865ff95312f52a30271fde31a658c717fa1
--- /dev/null
+++ b/client/io.go
@@ -0,0 +1,63 @@
+/**
+ * Yats - yats
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Paolo Lulli <kevwe.com>
+ * @copyright Paolo Lulli 2024
+ */
+package main
+
+import (
+	"encoding/base64"
+	"encoding/json"
+	"fmt"
+	"os"
+)
+
+func mapFromBase64(value string) (map[string]any, bool) {
+	decodedBytes, err := base64.StdEncoding.DecodeString(value)
+	if err != nil {
+		fmt.Println("Error decoding string:", err)
+		return nil, true
+	}
+
+	decoded := string(decodedBytes)
+	var dataMap map[string]any
+	json.Unmarshal([]byte(decoded), &dataMap)
+	return dataMap, false
+}
+
+func getDataJson(list string) ([]byte, bool) {
+	var result map[string]interface{}
+	json.Unmarshal([]byte(list), &result)
+	innerData := result["data"]
+
+	dataJson, er := json.Marshal(innerData)
+	if er != nil {
+		fmt.Printf("Parsing response: %v", er)
+		return nil, true
+	}
+	return dataJson, false
+}
+
+func readSecretPin(prompt string) string {
+	var input string
+	fmt.Printf("%s\n", prompt)
+	_, err := fmt.Scan(&input)
+	if err != nil {
+		fmt.Println("Error:", err)
+		os.Exit(1)
+	}
+	return input
+}
+
+func fileExists(fileName string) bool {
+	_, error := os.Stat(fileName)
+
+	if os.IsNotExist(error) {
+		return false
+	}
+	return true
+}




diff --git a/client/main.go b/client/main.go
index 436186c55859746dc52407008aef752362cec370..428119a29182ea1e90240540d824915c221e2fe5 100644
--- a/client/main.go
+++ b/client/main.go
@@ -14,8 +14,8 @@ 	"encoding/base64"
 	"encoding/json"
 	"fmt"
 	"os"
+	"path/filepath"
 	"sort"
-	"time"
 	"yats/config"
 
 	flag "github.com/spf13/pflag"
@@ -102,7 +102,31 @@ 		os.Exit(0)
 	}
 
 	if *generatePki {
-		yatsClient.CreateCsr()
+		makeDirIfItDoesNotExist(cfg.ClientCertDir)
+		secretPin := readSecretPin("Insert PIN from email")
+
+		csrFile := filepath.Join(cfg.ClientCertDir, cfg.ClientCn+".csr")
+		csr, err := yatsClient.CreateCsr(csrFile)
+		if err != nil {
+			fmt.Println(err)
+			os.Exit(1)
+		}
+
+		base64EncodedCsr := base64.StdEncoding.EncodeToString(csr)
+
+		certRequest := CertificateRequest{
+			Csr:       base64EncodedCsr,
+			SecretPin: secretPin,
+			ClientCn:  cfg.ClientCn,
+		}
+
+		jsonResponse := yatsClient.PkiPostCsr(yatsClient.config.PkiEndpoint, certRequest)
+		var certificateResponse CertificateResponse
+		json.Unmarshal(jsonResponse, &certificateResponse)
+		certPem, err := base64.StdEncoding.DecodeString(certificateResponse.Base64Certificate)
+		certificateFile := filepath.Join(cfg.ClientCertDir, cfg.ClientCn+".crt")
+		os.WriteFile(certificateFile, certPem, 0644)
+		fmt.Printf("Certificate written on: [%s]", certificateFile)
 		os.Exit(0)
 	}
 
@@ -227,7 +251,7 @@ 				err := json.Unmarshal(dataJson, &metricListModel)
 
 				if err == nil {
 					metric := metricListModel[0]
-					dataMap, errNoValue := loadBase64ValueAsMap(metric)
+					dataMap, errNoValue := mapFromBase64(metric.Value)
 					if errNoValue {
 						return
 					}
@@ -243,7 +267,7 @@ 						fmt.Printf("\"%s\"%s", k, recordSeparator)
 					}
 					fmt.Printf("%s", rowSeparator)
 					for _, row := range metricListModel {
-						rowMap, errNoValue := loadBase64ValueAsMap(row)
+						rowMap, errNoValue := mapFromBase64(row.Value)
 						if errNoValue {
 							return
 						}
@@ -275,55 +299,3 @@ 		fmt.Printf("%s", listResponse)
 		os.Exit(0)
 	}
 }
-
-func formattedUtcTimeInSec(int64Ts int64) string {
-	unixTimeUTC := time.Unix(int64Ts/1000, 0)
-	return unixTimeUTC.Format(time.RFC3339)
-}
-
-func loadBase64ValueAsMap(metric MetricModel) (map[string]any, bool) {
-	decodedBytes, err := base64.StdEncoding.DecodeString(metric.Value)
-	if err != nil {
-		fmt.Println("Error decoding string:", err)
-		return nil, true
-	}
-
-	decoded := string(decodedBytes)
-	var dataMap map[string]any
-	json.Unmarshal([]byte(decoded), &dataMap)
-	return dataMap, false
-}
-
-func getDataJson(list string) ([]byte, bool) {
-	var result map[string]interface{}
-	json.Unmarshal([]byte(list), &result)
-	innerData := result["data"]
-
-	dataJson, er := json.Marshal(innerData)
-	if er != nil {
-		fmt.Printf("Parsing response: %v", er)
-		return nil, true
-	}
-	return dataJson, false
-}
-
-func calculateCutDate(ts int64, sinceSeconds *bool, sinceMinutes *bool, sinceHours *bool, sinceDays *bool, sinceYears *bool) int64 {
-	if 0 != ts {
-		if *sinceSeconds {
-			return time.Now().Unix() - ts
-		}
-		if *sinceMinutes {
-			return time.Now().Unix() - ts*60
-		}
-		if *sinceHours {
-			return time.Now().Unix() - ts*60*60
-		}
-		if *sinceDays {
-			return time.Now().Unix() - ts*60*60*24
-		}
-		if *sinceYears {
-			return time.Now().Unix() - ts*60*60*24*365
-		}
-	}
-	return ts
-}




diff --git a/client/pki.go b/client/pki.go
index a0b1415e771b4c1afcba3b6227b2abe3c73a214f..184f43d0f696f28a793259af70d13c2ab8307485 100644
--- a/client/pki.go
+++ b/client/pki.go
@@ -5,23 +5,13 @@ 	"crypto/rand"
 	"crypto/rsa"
 	"crypto/x509"
 	"crypto/x509/pkix"
-	"encoding/asn1"
 	"encoding/pem"
 	"fmt"
 	"os"
 )
 
-func fileExists(fileName string) bool {
-	_, error := os.Stat(fileName)
-
-	if os.IsNotExist(error) {
-		return false
-	}
-	return true
-}
-
-func (c *YatsClient) CreateCsr() ([]byte, error) {
-	var oidEmailAddress = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}
+func (c *YatsClient) CreateCsr(csrFile string) ([]byte, error) {
+	//var oidEmailAddress = asn1.ObjectIdentifier{1, 2, 840, 113549, 1, 9, 1}
 
 	if fileExists(c.config.TlsKeyFile) {
 		fmt.Printf("Private key file already exists: %v\n", c.config.TlsKeyFile)
@@ -33,8 +23,8 @@ 		fmt.Printf("Certificate file already exists: %v\n", c.config.TlsKeyFile)
 		os.Exit(-1)
 	}
 
-	if ("" == c.config.ClientCnName) || ("" == c.config.ClientOrganization) || ("" == c.config.ClientCnName) {
-		fmt.Printf("Client name: [%v], organization: [%v], email address: [%v]\n", c.config.ClientCnName, c.config.ClientOrganization, c.config.ClientCnName)
+	if ("" == c.config.ClientCn) || ("" == c.config.ClientOrganization) || ("" == c.config.ClientEmail) {
+		fmt.Printf("Client name: [%v], organization: [%v], email address: [%v]\n", c.config.ClientCn, c.config.ClientOrganization, c.config.ClientEmail)
 		fmt.Println("No param can be empty, exiting")
 		os.Exit(-1)
 	}
@@ -50,7 +40,7 @@
 	os.WriteFile(c.config.TlsKeyFile, pemdata, 0644)
 
 	subj := pkix.Name{
-		CommonName: c.config.ClientCnName,
+		CommonName: c.config.ClientCn,
 		/*
 			Country:            []string{"AU"},
 			Province:           []string{"Some-State"},
@@ -58,16 +48,17 @@ 			Locality:           []string{"MyCity"},
 		*/
 		Organization: []string{c.config.ClientOrganization},
 		//OrganizationalUnit: []string{organizationalUnit},
-
-		ExtraNames: []pkix.AttributeTypeAndValue{
-			{
-				Type: oidEmailAddress,
-				Value: asn1.RawValue{
-					Tag:   asn1.TagIA5String,
-					Bytes: []byte(c.config.ClientCnName),
+		/*
+			ExtraNames: []pkix.AttributeTypeAndValue{
+				{
+					Type: oidEmailAddress,
+					Value: asn1.RawValue{
+						Tag:   asn1.TagIA5String,
+						Bytes: []byte(c.config.ClientEmail),
+					},
 				},
 			},
-		},
+		*/
 	}
 
 	template := x509.CertificateRequest{
@@ -76,7 +67,6 @@ 		SignatureAlgorithm: x509.SHA256WithRSA,
 	}
 
 	csrBytes, _ := x509.CreateCertificateRequest(rand.Reader, &template, keyBytes)
-	pem.Encode(os.Stdout, &pem.Block{Type: "CERTIFICATE REQUEST", Bytes: csrBytes})
 
 	pemcsrdata := pem.EncodeToMemory(
 		&pem.Block{
@@ -85,6 +75,6 @@ 			Bytes: csrBytes,
 		},
 	)
 
-	os.WriteFile("csr.pem", pemcsrdata, 0644)
+	os.WriteFile(csrFile, pemcsrdata, 0644)
 	return pemcsrdata, nil
 }




diff --git a/client/pkiclient.go b/client/pkiclient.go
new file mode 100644
index 0000000000000000000000000000000000000000..890a5044d2a50a6430778381f6296a8706a59a3b
--- /dev/null
+++ b/client/pkiclient.go
@@ -0,0 +1,56 @@
+/**
+ * Yats - yats
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Paolo Lulli <kevwe.com>
+ * @copyright Paolo Lulli 2024
+ */
+package main
+
+import (
+	"bytes"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"os"
+	"time"
+)
+
+func (c *YatsClient) PkiPostCsr(endpoint string, certRequest CertificateRequest) []byte {
+	client := http.Client{
+		Timeout: 30 * time.Second,
+	}
+
+	body, err := json.Marshal(certRequest)
+
+	//fmt.Printf("body: -> %s\n", body)
+	req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(body))
+	req.Header.Set("Content-Type", "application/json; charset=UTF-8")
+	if err != nil {
+		fmt.Println("Unable to make POST request", err)
+		os.Exit(1)
+	}
+	req.Header.Add("Accept", "*/*")
+	resp, err := client.Do(req)
+	if err != nil {
+		fmt.Println(err)
+		os.Exit(1)
+	}
+	defer resp.Body.Close()
+	data, _ := io.ReadAll(resp.Body)
+
+	return data
+}
+
+type CertificateRequest struct {
+	ClientCn  string `json:"clientCn"`
+	Csr       string `json:"csr"`
+	SecretPin string `json:"pin"`
+}
+
+type CertificateResponse struct {
+	Base64Certificate string `json:"base64cert"`
+}




diff --git a/client/time-util.go b/client/time-util.go
new file mode 100644
index 0000000000000000000000000000000000000000..3d4b2390fa1bf43c6f9b1261e734808caf4c9d7f
--- /dev/null
+++ b/client/time-util.go
@@ -0,0 +1,38 @@
+/**
+ * Yats - yats
+ *
+ * This file is licensed under the Affero General Public License version 3 or
+ * later. See the COPYING file.
+ *
+ * @author Paolo Lulli <kevwe.com>
+ * @copyright Paolo Lulli 2024
+ */
+package main
+
+import "time"
+
+func formattedUtcTimeInSec(int64Ts int64) string {
+	unixTimeUTC := time.UnixMilli(int64Ts)
+	return unixTimeUTC.Format(time.RFC3339)
+}
+
+func calculateCutDate(ts int64, sinceSeconds *bool, sinceMinutes *bool, sinceHours *bool, sinceDays *bool, sinceYears *bool) int64 {
+	if 0 != ts {
+		if *sinceSeconds {
+			return time.Now().Unix() - ts
+		}
+		if *sinceMinutes {
+			return time.Now().Unix() - ts*60
+		}
+		if *sinceHours {
+			return time.Now().Unix() - ts*60*60
+		}
+		if *sinceDays {
+			return time.Now().Unix() - ts*60*60*24
+		}
+		if *sinceYears {
+			return time.Now().Unix() - ts*60*60*24*365
+		}
+	}
+	return ts
+}