Skip to content

Commit

Permalink
lesson 7
Browse files Browse the repository at this point in the history
  • Loading branch information
bodrovis committed Jun 17, 2024
1 parent 4db621c commit 7beea04
Show file tree
Hide file tree
Showing 12 changed files with 431 additions and 0 deletions.
167 changes: 167 additions & 0 deletions lesson_7/cmd/keys/generate.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package keys

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

"lesson7/crypto_utils"
"lesson7/logger"
"lesson7/utils"

"github.com/spf13/cobra"
)

type PrivateKeyGen struct {
outputPath string
keyBitSize int
saltSize int
}

func init() {
keysCmd.AddCommand(keysGenerateCmd)

keysGenerateCmd.Flags().String("pub-out", "pub_key.pem", "Path to save the public key")
keysGenerateCmd.Flags().String("priv-out", "priv_key.pem", "Path to save the private key")
keysGenerateCmd.Flags().Int("priv-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) {
pkOut, _ := cmd.Flags().GetString("priv-out")
pkSize, _ := cmd.Flags().GetInt("priv-size")
saltSize, _ := cmd.Flags().GetInt("salt-size")

pkGenConfig := PrivateKeyGen{
outputPath: pkOut,
keyBitSize: pkSize,
saltSize: saltSize,
}

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

pubOut, _ := cmd.Flags().GetString("pub-out")
err = generatePubKey(pubOut, 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_7/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)
}
13 changes: 13 additions & 0 deletions lesson_7/cmd/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package cmd

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

func RootCmd() *cobra.Command {
return &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.`,
}
}
16 changes: 16 additions & 0 deletions lesson_7/cmd/signatures/signatures.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package signatures

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

var signaturesCmd = &cobra.Command{
Use: "signatures",
Short: "Create and verify signatures.",
Long: `Use subcommands to create signature (.sig) with private key and verify signature with public key.`,
}

// Init initializes signatures commands
func Init(rootCmd *cobra.Command) {
rootCmd.AddCommand(signaturesCmd)
}
49 changes: 49 additions & 0 deletions lesson_7/crypto_utils/crypto_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package crypto_utils

import (
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"fmt"

"golang.org/x/crypto/argon2"
)

type KeyDerivationConfig struct {
Passphrase []byte
Salt []byte
}

// MakeNonce creates a nonce suitable for use with the provided AEAD cipher.
func MakeNonce(crypter cipher.AEAD) ([]byte, error) {
nonce := make([]byte, crypter.NonceSize())
if _, err := rand.Read(nonce); err != nil {
return nil, fmt.Errorf("failed to generate nonce: %v", err)
}

return nonce, nil
}

// MakeCrypter creates a cipher.AEAD from a given key using AES in GCM mode.
func MakeCrypter(key []byte) (cipher.AEAD, error) {
block, err := aes.NewCipher(key)
if err != nil {
return nil, fmt.Errorf("failed to create cipher block: %v", err)
}

gcm, err := cipher.NewGCM(block)
if err != nil {
return nil, fmt.Errorf("failed to create GCM cipher: %v", err)
}

return gcm, nil
}

// DeriveKey generates a cryptographic key using Argon2 from a given passphrase and salt.
func DeriveKey(config KeyDerivationConfig) ([]byte, error) {
if len(config.Passphrase) == 0 || len(config.Salt) == 0 {
return nil, fmt.Errorf("passphrase and salt cannot be empty")
}

return argon2.IDKey(config.Passphrase, config.Salt, 1, 64*1024, 4, 32), nil
}
15 changes: 15 additions & 0 deletions lesson_7/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
module lesson7

go 1.22.2

require (
github.com/spf13/cobra v1.8.1
golang.org/x/crypto v0.24.0
golang.org/x/term v0.21.0
)

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/sys v0.21.0 // indirect
)
16 changes: 16 additions & 0 deletions lesson_7/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA=
golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
46 changes: 46 additions & 0 deletions lesson_7/logger/logger.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package logger

import (
"fmt"
"log"
"os"
"strings"
)

var (
// Using standard error for error logs and standard output for info and warning logs.
errorLogger = log.New(os.Stderr, "ERROR: ", log.LstdFlags|log.Lshortfile)
warnLogger = log.New(os.Stdout, "WARN: ", log.LstdFlags)
infoLogger = log.New(os.Stdout, "INFO: ", log.LstdFlags)
)

// HaltOnErr logs an error and exits if the error is non-nil.
func HaltOnErr(err error, messages ...string) {
if err == nil {
return
}

message := "An error occurred"

if len(messages) > 0 {
message = fmt.Sprintf("%s: %s", message, strings.Join(messages, " "))
}
errorLogger.Printf("%s: %v", message, err)
os.Exit(1)
}

// Info logs an informational message.
func Info(message string) {
infoLogger.Println(message)
}

// Warn logs a warning message along with an error if provided.
func Warn(err error, messages ...string) {
if err != nil {
message := "A warning occurred"
if len(messages) > 0 {
message = fmt.Sprintf("%s: %s", message, strings.Join(messages, " "))
}
warnLogger.Printf("%s: %v", message, err)
}
}
18 changes: 18 additions & 0 deletions lesson_7/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package main

import (
"lesson7/cmd"
"lesson7/cmd/keys"
"lesson7/cmd/signatures"
"lesson7/logger"
)

func main() {
rootCmd := cmd.RootCmd()
keys.Init(rootCmd)
signatures.Init(rootCmd)

if err := rootCmd.Execute(); err != nil {
logger.HaltOnErr(err, "Initial setup failed")
}
}
32 changes: 32 additions & 0 deletions lesson_7/priv_key.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-----BEGIN ENCRYPTED PRIVATE KEY-----
Key-Derivation-Function: Argon2
Nonce: luxSG2uq5IhEGn7y
Salt: nOogu0LAy2n+1L5xHBjKOw==

gJB0xo0QIsPh5nZjPkqoEPURsll/Pq+XkIa44wiraXxn1Rd7g5yk5lZiNvo2AJuZ
gPAqFIIRTeJxpoqL8KkgyYeOnendVULotnvfYWQ42dx4z2Fn9YAa5PSim3EgfzfN
Cawrzf8v5aXbmVp8aH1PEsSELl/Nat6bLI9duvVbiZNRwkMX9eiZWjSHAvtRqGFW
ub3VzfEXaGmdeZheA0LljRIY+uKQXY6/6Ad2eQ+HX0Mca44guljAy+3Y/qtL1+5k
DKiuNe1AxSEhyyAjbw8w53x71VVQdfKnGyICKQsoiIUNsFMnJ1i8BViyXiKh2xLg
9gOivgRtYSBzu2GeW5p5GvuSHJ5bGN/RAOR+d7IVUN4lRPah6VmSaayTttgcA72s
McMNNbMgq+jJSUMbUOlXh58MNXgU0TDllWg56BAG8oLcEjL0jPqS2mcOf985QJLP
R3OjdJ7uxJXC58R8/+VzpZXesJwKWPcB1EQTpdcuh61gEyEQu0V5PtkQkxHF+qu4
7hsz8EXBQ5IM3JWUKPEuwJbY9LRKr0fPNi/Ia3F7l7rnz9P6vC33rYOFnXj47KcP
Sj7quUaXiA8Z0pwxQ33ZVAiVlTvsivlX3qhhx6qo+FN77RMO7kE1WmWB61Sepo2Y
whog1x0WTWeC5/H4s415VO0NCcPNRKMG+mA0YKnxn/yz6teEFzgTV+3yorlU0xsZ
HigXmSBLXP5Y9Sk7AIEVq7F3RlguknbkGjfnci/NjYWHZThVtUUoLeMoujQhadt3
WQ52VLH+Jx5IH6M8A0NgDfwZEbjR7XtmGXXBDLSMlD8OtpMk9j7OiwspnW9NkyyY
J3qeyLHIlHaOO5mPxrvr6eiQldr9yyjdyjeT917XEMm5SFJOa0ARD0q0inbFZe13
OwsLaNCmA81nD94tQkiMIz5CDvvG3q0noU27SxBeUVXDRG00c1Cq+81t8SVJ5hLG
Qb7mFYSHbX9C6f85TLXu/WYV4UNiTP6y1+lLn8gcmpF39uvk+yIjFIlZm1/k4/0z
NXwxZWxPxfvivBy2k4liqUVXKMx8sl3en+YP96QdHXp0rUSYkd2f9p3puot3fRkk
7m0KkEK7cQ1DSPp8fI7h9bk2IFSGP0QrWzyGUnncsxhWzpbO2WMkbEA/eijX1wUL
C6r35/nGf4DlKG5ENmjDbo1Uia+8rxO8Ey6eFH22KtuV4eKXixbsZZIfqeX/hdAS
2BaiyNndFFpnc7j8+8SM+pGZuKx6ioqenQPvxZvmIzpnuwoimywZfYbvqXFFBIUv
vbLRPy6Su/EWOQVMzONfbSo19zal+1Y+LjGAjHw27Hr0V2wtZEmGHeCafzZh9bfL
JzS0j7bAFumjvA1j47MW+XYZSEQDuWWtf8eB7agJXtrBuyIB7unN5U06eB3yuqzK
34+hz9bfYnCE67S9/glkTjzG7QKSuRiYTyQWVihemAaL4ICbfJMakwClOyHE/Ta3
8Kwu+M5hMUQJPkEjjikfBbQM4AuvgSae9N3WUfay5yyK+FmftsaV/CFAbN3jWm2W
Y8cn6A3NM0K6P2mxCTgKRtw1VLkYbWd/9Jekrrrv99/A4Bv8NPg28vYGdMmEioyU
S+0Q02c+q60q
-----END ENCRYPTED PRIVATE KEY-----
Loading

0 comments on commit 7beea04

Please sign in to comment.