Skip to content

Commit

Permalink
lesson 9
Browse files Browse the repository at this point in the history
  • Loading branch information
bodrovis committed Jul 1, 2024
1 parent 48878f7 commit 149e79d
Show file tree
Hide file tree
Showing 27 changed files with 1,353 additions and 0 deletions.
33 changes: 33 additions & 0 deletions lesson_9/.github/workflows/goreleaser.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: goreleaser

on:
push:
tags:
- '*'

permissions:
contents: write

jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@v4
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
with:
# either 'goreleaser' (default) or 'goreleaser-pro'
distribution: goreleaser
# 'latest', 'nightly', or a semver
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
47 changes: 47 additions & 0 deletions lesson_9/.goreleaser.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
version: 1

before:
hooks:
# You may remove this if you don't use go modules.
- go mod tidy

builds:
- env:
- CGO_ENABLED=0
goos:
- darwin
- windows
- linux
- freebsd
goarch:
- 386
- amd64
- arm
- arm64
goarm:
- 6
- 7
ldflags:
- "-X 'brave_signer/cmd.version={{.Version}}'"

archives:
- format: tar.gz
# this name template makes the OS and Arch compatible with the results of `uname`.
name_template: >-
{{ .ProjectName }}_
{{- title .Os }}_
{{- if eq .Arch "amd64" }}x86_64
{{- else if eq .Arch "386" }}i386
{{- else }}{{ .Arch }}{{ end }}
{{- if .Arm }}v{{ .Arm }}{{ end }}
# use zip for windows archives
format_overrides:
- goos: windows
format: zip

changelog:
sort: asc
filters:
exclude:
- "^docs:"
- "^test:"
166 changes: 166 additions & 0 deletions lesson_9/cmd/keys/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package keys

import (
"crypto/rand"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"fmt"
"os"
"path/filepath"

"brave_signer/internal/config"
"brave_signer/internal/logger"
"brave_signer/pkg/crypto_utils"
"brave_signer/pkg/utils"

"github.com/spf13/cobra"
"github.com/spf13/viper"
)

type PrivateKeyGen struct {
outputPath string
keyBitSize int
saltSize int
}

func init() {
keysCmd.AddCommand(keysGenerateCmd)

keysGenerateCmd.Flags().String("pub-key-path", "pub_key.pem", "Path to save the public key")
keysGenerateCmd.Flags().String("priv-key-path", "priv_key.pem", "Path to save the private key")
keysGenerateCmd.Flags().Int("priv-key-size", 2048, "Private key size in bits")
keysGenerateCmd.Flags().Int("salt-size", 16, "Salt size used in key derivation in bytes")
}

var keysGenerateCmd = &cobra.Command{
Use: "generate",
Short: "Generates key pair.",
Long: `Generate an RSA key pair and store it in PEM files. The private key will be encrypted using a passphrase that you'll need to enter. AES encryption with Argon2 key derivation function is utilized.`,
Run: func(cmd *cobra.Command, args []string) {
localViper := cmd.Context().Value(config.ViperKey).(*viper.Viper)

pkGenConfig := PrivateKeyGen{
outputPath: localViper.GetString("priv-key-path"),
keyBitSize: localViper.GetInt("priv-key-size"),
saltSize: localViper.GetInt("salt-size"),
}

privateKey, err := generatePrivKey(pkGenConfig)
logger.HaltOnErr(err)

err = generatePubKey(localViper.GetString("pub-key-path"), privateKey)
logger.HaltOnErr(err)
},
}

func generatePubKey(path string, privKey *rsa.PrivateKey) error {
absPath, err := filepath.Abs(path)
if err != nil {
return fmt.Errorf("failed to get absolute path: %v", err)
}

pubASN1, err := x509.MarshalPKIXPublicKey(&privKey.PublicKey)
if err != nil {
return fmt.Errorf("failed to marshal public key: %w", err)
}

file, err := os.Create(absPath)
if err != nil {
return fmt.Errorf("failed to create public key file: %w", err)
}
defer file.Close()

if err := pem.Encode(file, &pem.Block{Type: "RSA PUBLIC KEY", Bytes: pubASN1}); err != nil {
return fmt.Errorf("failed to encode public key to PEM: %w", err)
}

return nil
}

func generatePrivKey(pkGenConfig PrivateKeyGen) (*rsa.PrivateKey, error) {
absPath, err := filepath.Abs(pkGenConfig.outputPath)
if err != nil {
return nil, fmt.Errorf("failed to get absolute path: %v", err)
}

privateKey, err := rsa.GenerateKey(rand.Reader, pkGenConfig.keyBitSize)
if err != nil {
return nil, fmt.Errorf("failed to generate private key: %v", err)
}

passphrase, err := utils.GetPassphrase()
if err != nil {
return nil, err
}

privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey)

salt, err := makeSalt(pkGenConfig.saltSize)
if err != nil {
return nil, err
}

key, err := crypto_utils.DeriveKey(crypto_utils.KeyDerivationConfig{
Passphrase: passphrase,
Salt: salt,
})
if err != nil {
return nil, err
}

crypter, err := crypto_utils.MakeCrypter(key)
if err != nil {
return nil, err
}

nonce, err := crypto_utils.MakeNonce(crypter)
if err != nil {
return nil, err
}

encryptedData := crypter.Seal(nil, nonce, privateKeyBytes, nil)

// Create a PEM block with the encrypted data
encryptedPEMBlock := &pem.Block{
Type: "ENCRYPTED PRIVATE KEY",
Bytes: encryptedData,
Headers: map[string]string{
"Nonce": base64.StdEncoding.EncodeToString(nonce),
"Salt": base64.StdEncoding.EncodeToString(salt),
"Key-Derivation-Function": "Argon2",
},
}

err = savePrivKeyToPEM(absPath, encryptedPEMBlock)
if err != nil {
return nil, err
}

return privateKey, nil
}

func savePrivKeyToPEM(absPath string, encryptedPEMBlock *pem.Block) error {
privKeyFile, err := os.Create(absPath)
if err != nil {
return fmt.Errorf("failed to create private key file: %v", err)
}
defer privKeyFile.Close()

if err := pem.Encode(privKeyFile, encryptedPEMBlock); err != nil {
return fmt.Errorf("failed to encode private key to PEM: %w", err)
}

return nil
}

// makeSalt generates a cryptographic salt.
func makeSalt(saltSize int) ([]byte, error) {
salt := make([]byte, saltSize)
if _, err := rand.Read(salt); err != nil {
return nil, fmt.Errorf("failed to generate salt: %v", err)
}

return salt, nil
}
16 changes: 16 additions & 0 deletions lesson_9/cmd/keys/keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package keys

import (
"github.com/spf13/cobra"
)

var keysCmd = &cobra.Command{
Use: "keys",
Short: "Manage key pairs.",
Long: `Use subcommands to create public/private key pairs in PEM files.`,
}

// Init initializes keys commands
func Init(rootCmd *cobra.Command) {
rootCmd.AddCommand(keysCmd)
}
79 changes: 79 additions & 0 deletions lesson_9/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cmd

import (
"context"
"fmt"
"os"

"brave_signer/cmd/keys"
"brave_signer/cmd/signatures"
"brave_signer/internal/config"
"brave_signer/internal/logger"

"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
)

var version = "dev"

func RootCmd() *cobra.Command {
rootCmd := &cobra.Command{
Use: "brave_signer",
Short: "Bravely generate key pairs, sign files, and check signatures.",
Long: `A collection of tools to generate key pairs in PEM files, sign files, and verify signatures.`,
SilenceUsage: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return initializeConfig(cmd)
},
}

// Add subcommands
keys.Init(rootCmd)
signatures.Init(rootCmd)

rootCmd.AddCommand(&cobra.Command{
Use: "version",
Short: "Print the version number of brave_signer",
Long: `All software has versions. This is brave_signer's version.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("brave_signer version:", version)
},
})

rootCmd.AddCommand(&cobra.Command{
Use: "gendocs",
Hidden: true,
Run: func(cmd *cobra.Command, args []string) {
if err := generateDocs(rootCmd, "./docs"); err != nil {
logger.Warn(fmt.Errorf("error generating docs: %v", err))
}
},
})

return rootCmd
}

func generateDocs(rootCmd *cobra.Command, dir string) error {
// Ensure the base directory exists
if err := os.MkdirAll(dir, 0o755); err != nil {
return err
}

// Generate markdown documentation for all commands
return doc.GenMarkdownTree(rootCmd, dir)
}

func initializeConfig(cmd *cobra.Command) error {
localViper, err := config.LoadYamlConfig()
if err != nil {
return err
}

if err := config.BindFlags(cmd, localViper); err != nil {
return err
}

ctx := context.WithValue(cmd.Context(), config.ViperKey, localViper)
cmd.SetContext(ctx)
return nil
}
Loading

0 comments on commit 149e79d

Please sign in to comment.