-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
12 changed files
with
431 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.`, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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----- |
Oops, something went wrong.