Skip to content

Commit

Permalink
refactor command
Browse files Browse the repository at this point in the history
  • Loading branch information
nothinux committed Apr 30, 2022
1 parent 98c66d2 commit 32d556b
Show file tree
Hide file tree
Showing 4 changed files with 349 additions and 99 deletions.
140 changes: 140 additions & 0 deletions cmd/certify/command.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package main

import (
"fmt"
"io"
"log"
"os"
"strings"
"syscall"

"github.com/nothinux/certify"
"golang.org/x/term"
)

// initCA create private key and certificate for certificate authority
func initCA(args []string) error {
pkey, err := generatePrivateKey(caKeyPath)
if err != nil {
return err
}
fmt.Println("CA private key file generated", caKeyPath)

var cn string

if len(args) < 3 {
cn = "cn:"
} else {
if strings.Contains(args[2], "cn:") {
cn = args[2]
} else {
cn = "cn:"
}
}

if err := generateCA(pkey.PrivateKey, cn, caPath); err != nil {
return err
}

fmt.Println("CA certificate file generated", caPath)
return nil
}

// readCertificate read certificate from stdin or from file
func readCertificate(args []string, stdin *os.File) (string, error) {
var certByte []byte
var err error

if len(args) < 3 {
if err := isPipe(stdin); err != nil {
return "", err
}

certByte, err = io.ReadAll(stdin)
if err != nil {
return "", err
}
} else {
certByte, err = os.ReadFile(args[2])
if err != nil {
return "", err
}
}

cert, err := certify.ParseCertificate(certByte)
if err != nil {
return "", err
}

return certify.CertInfo(cert), nil
}

// readRemoteCertificate read certificate from remote host
func readRemoteCertificate(args []string) (string, error) {
if len(args) < 3 {
return "", fmt.Errorf("you must provide remote host.\n")
}

result, err := tlsDial(args[2])
if err != nil {
return "", err
}

return certify.CertInfo(result), nil
}

// matchCertificate math certificate with private key
func matchCertificate(args []string) error {
if len(args) < 4 {
return fmt.Errorf("you must provide pkey and cert.\n")
}

pubkey, pubcert, err := matcher(args[2], args[3])
if err != nil {
return err
}

fmt.Printf(
"pubkey from %s:\n%s\n\npubkey from %s:\n%s\n✅ certificate and private key match\n",
args[2],
pubkey,
args[3],
pubcert,
)

return nil
}

func exportCertificate(args []string) {
if len(args) < 5 {
fmt.Println("you must provide [key-path] [cert-path] and [ca-path]")
os.Exit(1)
}

fmt.Print("enter password: ")
bytePass, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
log.Fatal(err)
}

// verify if cert and key has same public key
_, _, err = matcher(args[2], args[3])
if err != nil {
log.Fatal("\n", err)
}

pfxData, err := getPfxData(
args[2],
args[3],
args[4],
string(bytePass),
)
if err != nil {
log.Fatal(err)
}

if err := os.WriteFile("client.p12", pfxData, 0644); err != nil {
log.Fatal(err)
}
fmt.Println("\ncertificate exported to client.p12")
}
202 changes: 202 additions & 0 deletions cmd/certify/command_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
package main

import (
"log"
"os"
"strings"
"testing"
)

var TestCertificate = `-----BEGIN CERTIFICATE-----
MIIBmDCCAT2gAwIBAgIQUjIMhHGW4CreYEIQOnPDdDAKBggqhkjOPQQDAjAkMRAw
DgYDVQQKEwdjZXJ0aWZ5MRAwDgYDVQQDEwdjZXJ0aWZ5MB4XDTIyMDMxNzA4NDQx
MloXDTIzMDMxNzE0NDQxMlowJDEQMA4GA1UEChMHY2VydGlmeTEQMA4GA1UEAxMH
Y2VydGlmeTBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABIPmsrI8hCLHryeWc0wz
zrrbAXhohqMfFnZS95qM83p/EHHUO4yoi4LSZhZnvPhPYG+St4KBZj2mqZYs6nf8
sTSjUTBPMB0GA1UdJQQWMBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8E
BTADAQH/MB0GA1UdDgQWBBTuUKyfBpn78BTa2fodsucBYuApejAKBggqhkjOPQQD
AgNJADBGAiEAlYCxixkXh6eI1nHBAhaUHajYF6ZWpK4tiDCWR5lHIA0CIQCpgqUp
+R8a3HBTIcrpgdoI2g11HmV9+qOysbuWNpTnMw==
-----END CERTIFICATE-----`

func TestInitCA(t *testing.T) {
tests := []struct {
Name string
Args []string
expectedCN string
}{
{
Name: "Test run -init without cn",
Args: []string{"certify", "-init"},
expectedCN: "certify",
},
{
Name: "Test run -init with wrong cn format",
Args: []string{"certify", "-init", "cn-nothinux"},
expectedCN: "certify",
},
{
Name: "Test run -init with other argument",
Args: []string{"certify", "-init", "cert"},
expectedCN: "certify",
},
{
Name: "Test run -init with 4 argument",
Args: []string{"certify", "-init", "cert", "cn:aaa"},
expectedCN: "certify",
},
{
Name: "Test run -init with cn",
Args: []string{"certify", "-init", "cn:nothinux"},
expectedCN: "nothinux",
},
}

for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
if err := initCA(tt.Args); err != nil {
t.Fatal(err)
}

t.Run("Test parse certificate", func(t *testing.T) {
cert, err := getCACert()
if err != nil {
t.Fatal(err)
}

if cert.Subject.CommonName != tt.expectedCN {
t.Fatalf("got %v, want %v", cert.Subject.CommonName, tt.expectedCN)
}
})

t.Cleanup(func() {
if err := os.Remove(caPath); err != nil {
t.Fatal(err)
}
if err := os.Remove(caKeyPath); err != nil {
t.Fatal(err)
}
})
})
}
}

func getTestCertificate(filename string) *os.File {
f, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}

_, err = f.Seek(0, 0)
if err != nil {
log.Fatal(err)
}

os.Stdin = f

return os.Stdin
}

func TestReadCertificate(t *testing.T) {
// TODO: add test reading certificate from stdin
tests := []struct {
Name string
Args []string
Stdin *os.File
expectedOutput string
expectedError string
}{
{
Name: "Test read certificate from file",
Args: []string{"certify", "-read", "testdata/ca-cert.pem"},
Stdin: nil,
expectedOutput: "Issuer: CN=certify, O=certify",
},
{
Name: "Test read not exists certificate",
Args: []string{"certify", "-read", "ca-cert.pem"},
Stdin: nil,
expectedError: "open ca-cert.pem: no such file or directory",
},
{
Name: "Test read content from stdin",
Args: []string{"certify", "-read"},
Stdin: getTestCertificate("testdata/empty"),
expectedError: "can't decode CA cert file",
},
}

for _, tt := range tests {
t.Run(tt.Name, func(t *testing.T) {
cert, err := readCertificate(tt.Args, tt.Stdin)

if err != nil {
if !strings.Contains(err.Error(), tt.expectedError) {
t.Fatalf("got %v, want %v", err, tt.expectedError)
}
}

if !strings.Contains(cert, tt.expectedOutput) {
t.Fatalf("error, want output %s", tt.expectedOutput)
}

if tt.Stdin != nil {
tt.Stdin.Close()
}
})
}
}

func TestReadRemoteCertificate(t *testing.T) {
tests := []struct {
Name string
Args []string
ExpectedOutput string
ExpectedError string
}{
{
Name: "Test valid Host",
Args: []string{"certify", "-connect", "google.com:443"},
ExpectedOutput: "Subject: CN=*.google.com",
},
{
Name: "Test invalid Host",
Args: []string{"certify", "-connect", "google.com"},
ExpectedError: "missing port in address",
},
{
Name: "Test invalid Host",
Args: []string{"certify", "-connect", "google"},
ExpectedError: "missing port in address",
},
{
Name: "Test invalid Host",
Args: []string{"certify", "-connect", "1.1.1.1"},
ExpectedError: "missing port in address",
},
}

for _, tt := range tests {
result, err := readRemoteCertificate(tt.Args)
if err != nil {
if !strings.Contains(err.Error(), tt.ExpectedError) {
t.Fatalf("got %v want %v", err.Error(), tt.ExpectedError)
}
}

if !strings.Contains(result, tt.ExpectedOutput) {
t.Fatalf("certificate doesn't containing %s", tt.ExpectedOutput)
}
}
}

func TestMatchCertificate(t *testing.T) {
if err := matchCertificate([]string{
"certify",
"-match",
"testdata/ca-key.pem",
"testdata/ca-cert.pem",
}); err != nil {
t.Fatal("private key and public key must match")
}
}
Loading

0 comments on commit 32d556b

Please sign in to comment.