Compare commits

..

33 commits

Author SHA1 Message Date
5ba220f1b9
Merge pull request #69 from wneessen/add_fuzzing
Add fuzzing
2024-03-17 22:09:40 +01:00
3ffb499c1b
Add fuzz testing to config_test.go
This commit adds a function `FuzzWithAlgorithm` to the configuration test file (config_test.go). The function introduces more comprehensive fuzz testing for the algorithm configuration. It focuses on handling different integers including negative and high numbers, thus enhancing robustness of the algorithm.
2024-03-17 22:05:44 +01:00
b40b4b7e63
Add expanded fuzz testing to algo_test.go
This commit introduces several new fuzz tests to the algo_test.go file. These tests specifically target the IntToAlgo functionality to ensure proper handling of negative, out-of-range, and very large input values. By covering these edge-cases, we enhance the reliability of the algorithm conversion process.
2024-03-17 22:05:35 +01:00
f5f6a12e83
Merge pull request #68 from wneessen/security-md
Create SECURITY.md
2024-03-17 21:33:25 +01:00
f65feff1f9
Create SECURITY.md 2024-03-17 21:32:57 +01:00
7f8fbb05bc
Merge pull request #67 from wneessen/token-permissions
Add read permissions to GitHub workflows
2024-03-17 20:23:45 +01:00
b289d440da
Update Go build command in GitHub workflow
The GitHub workflow script has been updated to use a different command for building the Go application. This will ensure that the application is built with necessary parameters for correct operation and proper linking of static libraries.
2024-03-17 20:23:29 +01:00
ef8e334df0
Add read permissions to GitHub workflows
The reuse and docker-publish workflow scripts in GitHub Actions have been updated. Now these scripts have permission to read contents. This will ensure secure access and controlled operations on repositories.
2024-03-17 20:16:27 +01:00
ba891efd37
Merge pull request #64 from wneessen/codeql
Create codeql.yml
2024-03-17 20:02:09 +01:00
bfc12841ce
Update Go version and build command in codeql workflow
The codeql workflow script has been updated. Specifically, the Go version in the build command has been changed from 1.22.0 to 1.22.1+auto, to ensure alignment with the Go version specified elsewhere in the project. Additionally, the build command itself has been simplified, utilizing '/usr/bin/env' to consolidate dependencies.
2024-03-17 19:59:29 +01:00
4ea41be22f
Update Go version and simplify build script
The Go version in go.mod has been updated from 1.22 to 1.22.0 to avoid potential compatibility issues. Additionally, the build process in codeql.yml has been simplified by consolidating multiple 'go mod' commands into a single 'go build' command.
2024-03-17 19:51:46 +01:00
2691b04e38
Upgrade Go version in go.mod
The Go version specified in the go.mod file has been upgraded from 1.22 to 1.22.0. This may be necessary to avoid compatibility issues with updated systems and dependencies.
2024-03-17 19:49:43 +01:00
decf5526d1
Downgrade Go version in go.mod
The Go version specified in the go.mod file has been downgraded from 1.22.1 to 1.22. This change is necessary to maintain compatibility with systems and dependencies that may not yet support the latest version of Go.
2024-03-17 19:38:12 +01:00
061b9f4f7f
Upgrade Go version in go.mod
The Go version specified in the go.mod file has been upgraded from 1.21 to 1.22.1. This change ensures the use of the latest Go features and improvements, enhancing the overall project performance.
2024-03-17 19:36:40 +01:00
bffc8ac65e
Update Go version in go.mod
The Go version specified in the go.mod file has been downgraded from 1.22 to 1.21. This is to ensure compatibility with the project's specified dependencies.
2024-03-17 19:33:18 +01:00
6f25663957
Update Go build steps in GitHub workflow
The build steps in the CodeQL action of the GitHub workflow have been updated. Instead of using the 'apt-get' command, the 'go mod tidy', 'go mod download', and 'go mod verify' are now used. This improves the building process by organizing and verifying the dependencies.
2024-03-17 19:31:10 +01:00
31cf70c678
Update golangci-lint workflow permissions
The "read-all" permission has been eliminated from the .github/workflows/golangci-lint.yml file. In its place, more specific read permissions have been implemented to enhance security measures and establish accurate access levels.
2024-03-17 19:28:59 +01:00
4bc210f1ab
Refine permissions in CodeQL workflow
The "read-all" permission has been removed from the CodeQL workflow. Instead, the workflow now includes a specific read permission for contents, ensuring a more precise and secure access level.
2024-03-17 19:28:06 +01:00
b36aeeeab6
Update CodeQL workflow to include Go installation and building
The previous autobuild process has been disabled in the Github actions workflow. A new step for Go installation and manual building of the application using Go has been implemented, providing enhanced control and flexibility over the project's build process.
2024-03-17 19:24:19 +01:00
043008a97d
Update CodeQL workflow to include Go installation and building
The previous autobuild process has been disabled in the Github actions workflow. A new step for Go installation and manual building of the application using Go has been implemented, providing enhanced control and flexibility over the project's build process.
2024-03-17 19:22:18 +01:00
2af31dcb48
Disable Autobuild and setup manual Go build in CodeQL workflow
The Github actions autobuild functionality has been commented out and replaced with manual building using Go. This customized build command ensures better flexibility and control over the build process.
2024-03-17 19:19:38 +01:00
7ebaf2d2b7
Add read-all permissions to workflow files
This commit adds "read-all" permissions to golangci-lint, codecov, and sonarqube workflow files. This change ensures that all necessary activities are allowed during the workflow processes.
2024-03-17 19:15:34 +01:00
eec1b36edc
Add read-all permissions and SPDX license headers in codeql.yml
The commit adds SPDX-FileCopyrightText and SPDX-License-Identifier headers at the top of codeql.yml, specifying a CC0-1.0 license for Winni Neessen's 2022 copyright. It also grants read-all permissions, ensuring all needed activities are allowed in the file's workflow.
2024-03-17 19:14:42 +01:00
2d674214a7
Create codeql.yml 2024-03-17 19:12:21 +01:00
a61ac9b877
Merge pull request #61 from wneessen/fix_reuse
Add SPDX license headers in scorecard.yml
2024-03-17 19:06:13 +01:00
6697ac53db
Add SPDX license headers in scorecard.yml
SPDX-FileCopyrightText and SPDX-License-Identifier license headers have been added to the top of the scorecard.yml file. The headers pertain to the 2022 copyright of Winni Neessen and define the license to be CC0-1.0.
2024-03-17 19:05:33 +01:00
64f7eed954
Create scorecard.yml 2024-03-17 19:01:57 +01:00
4a6b9b325f
Update version number in README and apg.go for proper semver ruling
The version number has been corrected to 1.1.0 from 1.0.1. This change was made in both the README.md file and the apg.go file. This update reflects the new algorithm for binary secrets introduction in version 1.1.0 instead of version 1.0.1.
2024-03-17 18:31:58 +01:00
9f035c5834
Merge pull request #60 from wneessen/binary_random
Add binary mode for secret generation
2024-03-17 18:26:03 +01:00
183754e869
Add new test case to spelling_test.go
A new test case named "Pronounce_Mixed" has been added to the file spelling_test.go. This new case helps validate the behavior of the function when dealing with mixed syllables. More specifically, it deals with a situation where a number and a pronounceable syllable are combined, enhancing the overall robustness of the spelling tests.
2024-03-17 18:23:12 +01:00
c697a8ef8e
Update error handling in test for HasBeenPwned
The test for HasBeenPwned function in hibp_test.go has been updated to handle errors more effectively. Instead of failing the test directly upon encountering an error, it now logs the error and terminates the current subtest. This improves the test's resilience and makes debugging easier.
2024-03-17 18:23:04 +01:00
c8a4cf2837
Refactor variable initialization in generateBinary function
This commit refactors the way the 'length' variable is initialized in the generateBinary function of the Generator struct in random.go. Notably, it makes use of Go's type inference feature to eliminate the need to explicitly declare the variable type.
2024-03-17 18:12:17 +01:00
acadccc84a
Add binary mode for secret generation
This commit updates the password generator to now include a binary mode. This mode produces a 256 bits long fully binary secret which can be used for AES-256 encryption. New flags `-bh` (print hex representation) and `-bn` (new line after secret) have been added for this mode. The version has also been updated to 1.0.1 recognizing this new addition.
2024-03-17 18:09:27 +01:00
18 changed files with 420 additions and 7 deletions

View file

@ -3,6 +3,7 @@
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
name: Codecov workflow name: Codecov workflow
permissions: read-all
on: on:
push: push:
branches: branches:

91
.github/workflows/codeql.yml vendored Normal file
View file

@ -0,0 +1,91 @@
# SPDX-FileCopyrightText: 2022 Winni Neessen <winni@neessen.dev>
#
# SPDX-License-Identifier: CC0-1.0
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
schedule:
- cron: '31 14 * * 1'
permissions:
contents: read
jobs:
analyze:
name: Analyze
# Runner size impacts CodeQL analysis time. To learn more, please see:
# - https://gh.io/recommended-hardware-resources-for-running-codeql
# - https://gh.io/supported-runners-and-hardware-resources
# - https://gh.io/using-larger-runners
# Consider using larger runners for possible analysis time improvements.
runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }}
timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }}
permissions:
# required for all workflows
security-events: write
# only required for workflows in private repositories
actions: read
contents: read
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ]
# Use only 'java-kotlin' to analyze code written in Java, Kotlin or both
# Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v4
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift).
# If this step fails, then you should remove it and run the build manually (see below)
# - name: Autobuild
# uses: github/codeql-action/autobuild@v3
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
- run: |
echo "Build Application using Go"
/usr/bin/env GOTOOLCHAIN=go1.22.1+auto go build -a -installsuffix cgo -ldflags '-w -s -extldflags "-static"' -o apg github.com/wneessen/apg-go/cmd/apg
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
with:
category: "/language:${{matrix.language}}"

View file

@ -19,6 +19,9 @@ on:
pull_request: pull_request:
branches: [ main ] branches: [ main ]
permissions:
contents: read
env: env:
# Use docker.io for Docker Hub if empty # Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io REGISTRY: ghcr.io

View file

@ -3,6 +3,8 @@
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
name: REUSE Compliance Check name: REUSE Compliance Check
permissions:
contents: read
on: [push, pull_request] on: [push, pull_request]

76
.github/workflows/scorecard.yml vendored Normal file
View file

@ -0,0 +1,76 @@
# SPDX-FileCopyrightText: 2022 Winni Neessen <winni@neessen.dev>
#
# SPDX-License-Identifier: CC0-1.0
# This workflow uses actions that are not certified by GitHub. They are provided
# by a third-party and are governed by separate terms of service, privacy
# policy, and support documentation.
name: Scorecard supply-chain security
on:
# For Branch-Protection check. Only the default branch is supported. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
branch_protection_rule:
# To guarantee Maintained check is occasionally updated. See
# https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
schedule:
- cron: '34 15 * * 4'
push:
branches: [ "main" ]
# Declare default permissions as read only.
permissions: read-all
jobs:
analysis:
name: Scorecard analysis
runs-on: ubuntu-latest
permissions:
# Needed to upload the results to code-scanning dashboard.
security-events: write
# Needed to publish results and get a badge (see publish_results below).
id-token: write
# Uncomment the permissions below if installing in a private repository.
# contents: read
# actions: read
steps:
- name: "Checkout code"
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
with:
persist-credentials: false
- name: "Run analysis"
uses: ossf/scorecard-action@e38b1902ae4f44df626f11ba0734b14fb91f8f86 # v2.1.2
with:
results_file: results.sarif
results_format: sarif
# (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
# - you want to enable the Branch-Protection check on a *public* repository, or
# - you are installing Scorecard on a *private* repository
# To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
# repo_token: ${{ secrets.SCORECARD_TOKEN }}
# Public repositories:
# - Publish results to OpenSSF REST API for easy access by consumers
# - Allows the repository to include the Scorecard badge.
# - See https://github.com/ossf/scorecard-action#publishing-results.
# For private repositories:
# - `publish_results` will always be set to `false`, regardless
# of the value entered here.
publish_results: true
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
# format to the repository Actions tab.
- name: "Upload artifact"
uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 # v3.1.0
with:
name: SARIF file
path: results.sarif
retention-days: 5
# Upload the results to GitHub's code scanning dashboard.
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@17573ee1cc1b9d061760f3a006fc4aac4f944fd5 # v2.2.4
with:
sarif_file: results.sarif

View file

@ -3,6 +3,7 @@
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
name: SonarQube name: SonarQube
permissions: read-all
on: on:
push: push:
branches: branches:

View file

@ -49,6 +49,14 @@ vEbErlaFryaNgyex (vE-bEr-la-Fry-aN-gy-ex)
We generated a 15-character long pronounceable phrase with syllables output, for easy We generated a 15-character long pronounceable phrase with syllables output, for easy
use in e. g. a phone verification process. use in e. g. a phone verification process.
### Cryptographic key for encryption
```shell
$ apg -a 3 -f 32 -bh
```
We generated a 32 bytes/256 bits long fully binary secret that can be used i. e. as
encryption key for a AES-256 symmetric encryption. The output is represented in
hexadecimal format.
## Installation ## Installation
### Docker ### Docker
@ -327,6 +335,24 @@ Heads
Heads Heads
``` ```
### Binary mode
Since v1.1.0 apg-go has a new algorithm for binary secrets. This is a very basic mode that will ignore
most of the available options, as it will only generate binary secrets with full 256 bits of randomness.
The only available options for this mode are: `-f` to set the length of the returned secret in bytes,
`-bh` to tell apg-go to output the generated secret in hexadecial representation and `-bn` to instruct
apg-go to return a newline after the generated secret. Any other option available in the other modes
will be ignored.
This mode can be useful for example if you need to generate a AES-256 encryption key. Since 32 bytes is
the default length for the secret generation in this mode, you can simply generate a secret key with
the following command:
```shell
$ apg -a 3 -bh
a1cdab8db365af3d70828b1fe43b7896190c157ad3f1ae2a0a1d52ec1628c6b5
```
*For ease for readability we used the `-bh` flag, to instruct apg-go to output the secret in its
hexadecimal representation*
### Minimum required characters ### Minimum required characters
Even though in apg-go you can select what kind of characters are used for the password generation, it is Even though in apg-go you can select what kind of characters are used for the password generation, it is
not guaranteed, that if you request a password with a numeric value, that the generated password will not guaranteed, that if you request a password with a numeric value, that the generated password will
@ -376,6 +402,9 @@ _apg-go_ replicates most of the parameters of the original c-apg. Some parameter
- `0`: Pronouncable password generation (Koremutake syllables) - `0`: Pronouncable password generation (Koremutake syllables)
- `1`: Random password generation according to password modes/flags - `1`: Random password generation according to password modes/flags
- `2`: Coinflip (returns heads or tails) - `2`: Coinflip (returns heads or tails)
- `3`: Binary mode (returns a secret with 256 bits of randomness)
- `-bh`: When set, will print the generated secret in its hex representation (Default: off)
- `-bn`: When set, will return a new line character after the generated secret (Default: off)
- `-m <length>`: The minimum length of the password to be generated (Default: 12) - `-m <length>`: The minimum length of the password to be generated (Default: 12)
- `-x <length>`: The maximum length of the password to be generated (Default: 20) - `-x <length>`: The maximum length of the password to be generated (Default: 20)
- `-f <length>`: Fixed length of the password to be generated (Ignores -m and -x) - `-f <length>`: Fixed length of the password to be generated (Ignores -m and -x)

38
SECURITY.md Normal file
View file

@ -0,0 +1,38 @@
<!--
SPDX-FileCopyrightText: 2021-2024 Winni Neessen <wn@neessen.dev>
SPDX-License-Identifier: CC0-1.0
-->
# Security Policy
## Reporting a Vulnerability
To report (possible) security issues in apg-go, please either send a mail to
[security@neessen.dev](mailto:security@neessen.dev) or use Github's
[private reporting feature](https://github.com/wneessen/apg-go/security/advisories/new).
Reports are always welcome. Even if you are not 100% certain that a specific issue you found
counts as a security issue, we'd love to hear the details, so we can figure out together if
the issue in question needds to be addressed.
Typically, you will receive an answer within a day or even within a few hours.
## Encryption
You can send OpenPGP/GPG encrpyted mails to the [security@neessen.dev](mailto:security@neessen.dev) address.
OpenPGP/GPG public key:
```
-----BEGIN PGP PUBLIC KEY BLOCK-----
xjMEZfdSjxYJKwYBBAHaRw8BAQdA8YoxV0iaLJxVUkBlpC+FQyOiCvWPcnnk
O8rsfRHT22bNK3NlY3VyaXR5QG5lZXNzZW4uZGV2IDxzZWN1cml0eUBuZWVz
c2VuLmRldj7CjAQQFgoAPgWCZfdSjwQLCQcICZAajWCli0ncDgMVCAoEFgAC
AQIZAQKbAwIeARYhBB6X6h8oUi9vvjcMFxqNYKWLSdwOAACHrQEAmfT2HNXF
x1W0z6E6PiuoHDU6DzZ1MC6TZkFfFoC3jJ0BAJZdZnf6xFkVtEAbxNIVpIkI
zjVxgI7gefYDXbqzQx4PzjgEZfdSjxIKKwYBBAGXVQEFAQEHQBdOGYxMLrCy
+kypzTe9jgaEOjob2VVsZ2UV2K9MGKYYAwEIB8J4BBgWCgAqBYJl91KPCZAa
jWCli0ncDgKbDBYhBB6X6h8oUi9vvjcMFxqNYKWLSdwOAABIFAEA3YglATpF
YrJxatxHb+yI6WdhhJTA2TaF2bxBl10d/xEA/R5CKbMe3kj647gjiQ1YXQUh
dM5AKh9kcJn6FPLEoKEM
=nm5C
-----END PGP PUBLIC KEY BLOCK-----
```

View file

@ -18,6 +18,9 @@ const (
// AlgoCoinFlip represents a very simple coinflip algorithm returning "heads" // AlgoCoinFlip represents a very simple coinflip algorithm returning "heads"
// or "tails" // or "tails"
AlgoCoinFlip AlgoCoinFlip
// AlgoBinary represents a full binary randomness mode with up to 256 bits
// of randomness
AlgoBinary
// AlgoUnsupported represents an unsupported algorithm // AlgoUnsupported represents an unsupported algorithm
AlgoUnsupported AlgoUnsupported
) )
@ -32,6 +35,8 @@ func IntToAlgo(a int) Algorithm {
return AlgoRandom return AlgoRandom
case 2: case 2:
return AlgoCoinFlip return AlgoCoinFlip
case 3:
return AlgoBinary
default: default:
return AlgoUnsupported return AlgoUnsupported
} }

View file

@ -4,7 +4,9 @@
package apg package apg
import "testing" import (
"testing"
)
func TestIntToAlgo(t *testing.T) { func TestIntToAlgo(t *testing.T) {
tt := []struct { tt := []struct {
@ -15,7 +17,8 @@ func TestIntToAlgo(t *testing.T) {
{"AlgoPronounceable", 0, AlgoPronounceable}, {"AlgoPronounceable", 0, AlgoPronounceable},
{"AlgoRandom", 1, AlgoRandom}, {"AlgoRandom", 1, AlgoRandom},
{"AlgoCoinflip", 2, AlgoCoinFlip}, {"AlgoCoinflip", 2, AlgoCoinFlip},
{"AlgoUnsupported", 3, AlgoUnsupported}, {"AlgoBinary", 3, AlgoBinary},
{"AlgoUnsupported", 4, AlgoUnsupported},
} }
for _, tc := range tt { for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
@ -26,3 +29,34 @@ func TestIntToAlgo(t *testing.T) {
}) })
} }
} }
func FuzzIntToAlgo(f *testing.F) {
f.Add(-1) // Test negative input
f.Add(4) // Test out-of-range positive input
f.Add(100) // Test very large input
f.Fuzz(func(t *testing.T, a int) {
algo := IntToAlgo(a)
switch a {
case 0:
if algo != AlgoPronounceable {
t.Errorf("IntToAlgo(%d) expected AlgoPronounceable, got %v", a, algo)
}
case 1:
if algo != AlgoRandom {
t.Errorf("IntToAlgo(%d) expected AlgoRandom, got %v", a, algo)
}
case 2:
if algo != AlgoCoinFlip {
t.Errorf("IntToAlgo(%d) expected AlgoCoinFlip, got %v", a, algo)
}
case 3:
if algo != AlgoBinary {
t.Errorf("IntToAlgo(%d) expected AlgoBinary, got %v", a, algo)
}
default:
if algo != AlgoUnsupported {
t.Errorf("IntToAlgo(%d) expected AlgoUnsupported, got %v", a, algo)
}
}
})
}

2
apg.go
View file

@ -5,7 +5,7 @@
package apg package apg
// VERSION represents the version string // VERSION represents the version string
const VERSION = "1.0.0" const VERSION = "1.1.0"
// Generator is the password generator type of the APG package // Generator is the password generator type of the APG package
type Generator struct { type Generator struct {

View file

@ -31,6 +31,8 @@ func main() {
var modeString string var modeString string
var complexPass, humanReadable, lowerCase, numeric, special, showVer, upperCase bool var complexPass, humanReadable, lowerCase, numeric, special, showVer, upperCase bool
flag.IntVar(&algorithm, "a", 1, "") flag.IntVar(&algorithm, "a", 1, "")
flag.BoolVar(&config.BinaryHexMode, "bh", false, "")
flag.BoolVar(&config.BinaryNewline, "bn", false, "")
flag.BoolVar(&complexPass, "C", false, "") flag.BoolVar(&complexPass, "C", false, "")
flag.StringVar(&config.ExcludeChars, "E", "", "") flag.StringVar(&config.ExcludeChars, "E", "", "")
flag.Int64Var(&config.FixedLength, "f", 0, "") flag.Int64Var(&config.FixedLength, "f", 0, "")
@ -144,6 +146,23 @@ func configOldStyle(config *apg.Config, humanReadable, lowerCase, upperCase,
func generate(config *apg.Config) { func generate(config *apg.Config) {
generator := apg.New(config) generator := apg.New(config)
// In binary mode we only generate a single secret
if config.Algorithm == apg.AlgoBinary {
password, err := generator.Generate()
if err != nil {
_, _ = fmt.Fprintf(os.Stderr, "failed to generate password: %s\n", err)
os.Exit(1)
}
if config.BinaryNewline {
fmt.Println(password)
return
}
fmt.Print(password)
return
}
// For any other mode we cycle through the amount of passwords to be generated
for i := int64(0); i < config.NumberPass; i++ { for i := int64(0); i < config.NumberPass; i++ {
password, err := generator.Generate() password, err := generator.Generate()
if err != nil { if err != nil {
@ -197,12 +216,17 @@ Flags:
- 0: pronounceable password generation (koremutake syllables) - 0: pronounceable password generation (koremutake syllables)
- 1: random password generation according to password modes/flags - 1: random password generation according to password modes/flags
- 2: coinflip (returns heads or tails) - 2: coinflip (returns heads or tails)
- 3: full binary mode (generates simple 256 bit randomness)
-bh When set, will print the generated secret in its hex representation (Default: off)
-bn When set, will return a new line character after the generated secret (Default: off)
- Note: The -bX options only apply to binary mode (Algo: 3)
-m LENGTH Minimum length of the password to be generated (Default: 12) -m LENGTH Minimum length of the password to be generated (Default: 12)
-x LENGTH Maximum length of the password to be generated (Default: 20) -x LENGTH Maximum length of the password to be generated (Default: 20)
-f LENGTH Fixed length of the password to be generated (Ignores -m and -x) -f LENGTH Fixed length of the password to be generated (Ignores -m and -x)
- Note: Due to the way the pronounceable password algorithm works, - Note: Due to the way the pronounceable password algorithm works,
this setting might not always apply this setting might not always apply
-n NUMBER Amount of password to be generated (Default: 6) -n NUMBER Amount of password to be generated (Default: 6)
- Note: Does not apply to binary mode (Algo: 3)
-E CHARS List of characters to be excluded in the generated password -E CHARS List of characters to be excluded in the generated password
-M [LUNSHClunshc] New style password flags -M [LUNSHClunshc] New style password flags
- Note: new-style flags have higher priority than any of the old-style flags - Note: new-style flags have higher priority than any of the old-style flags

View file

@ -10,6 +10,8 @@ const (
DefaultMinLength int64 = 12 DefaultMinLength int64 = 12
// DefaultMaxLength reflects the default maximum length of a generated password // DefaultMaxLength reflects the default maximum length of a generated password
DefaultMaxLength int64 = 20 DefaultMaxLength int64 = 20
// DefaultBinarySize is the default byte size for generating binary random bytes
DefaultBinarySize int64 = 32
// DefaultMode sets the default character set mode bitmask to a combination of // DefaultMode sets the default character set mode bitmask to a combination of
// lower- and upper-case characters as well as numbers // lower- and upper-case characters as well as numbers
DefaultMode ModeMask = ModeLowerCase | ModeNumeric | ModeUpperCase DefaultMode ModeMask = ModeLowerCase | ModeNumeric | ModeUpperCase
@ -21,6 +23,11 @@ const (
type Config struct { type Config struct {
// Algorithm sets the Algorithm used for the password generation // Algorithm sets the Algorithm used for the password generation
Algorithm Algorithm Algorithm Algorithm
// BinaryHexMode if set will output the hex representation of the generated
// binary random string
BinaryHexMode bool
// BinaryNewline if set will print out a new line in AlgoBinary mode
BinaryNewline bool
// CheckHIBP sets a flag if the generated password has to be checked // CheckHIBP sets a flag if the generated password has to be checked
// against the HIBP pwned password database // against the HIBP pwned password database
CheckHIBP bool CheckHIBP bool
@ -88,6 +95,13 @@ func WithAlgorithm(algo Algorithm) Option {
} }
} }
// WithBinaryHexMode sets the hex mode for the AlgoBinary
func WithBinaryHexMode() Option {
return func(config *Config) {
config.BinaryHexMode = true
}
}
// WithExcludeChars sets a list of characters to be excluded in the generated // WithExcludeChars sets a list of characters to be excluded in the generated
// passwords // passwords
func WithExcludeChars(chars string) Option { func WithExcludeChars(chars string) Option {

View file

@ -46,7 +46,8 @@ func TestWithAlgorithm(t *testing.T) {
{"Pronounceable passwords", AlgoPronounceable, 0}, {"Pronounceable passwords", AlgoPronounceable, 0},
{"Random passwords", AlgoRandom, 1}, {"Random passwords", AlgoRandom, 1},
{"Coinflip", AlgoCoinFlip, 2}, {"Coinflip", AlgoCoinFlip, 2},
{"Unsupported", AlgoUnsupported, 3}, {"Binary", AlgoBinary, 3},
{"Unsupported", AlgoUnsupported, 4},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -67,6 +68,18 @@ func TestWithAlgorithm(t *testing.T) {
} }
} }
func TestWithBinaryHexMode(t *testing.T) {
c := NewConfig(WithBinaryHexMode())
if c == nil {
t.Errorf("NewConfig(WithBinaryHexMode()) failed, expected config pointer but got nil")
return
}
if !c.BinaryHexMode {
t.Errorf("NewConfig(WithBinaryHexMode()) failed, expected chars: %t, got: %t",
true, c.BinaryHexMode)
}
}
func TestWithExcludeChars(t *testing.T) { func TestWithExcludeChars(t *testing.T) {
e := "abcdefg" e := "abcdefg"
c := NewConfig(WithExcludeChars(e)) c := NewConfig(WithExcludeChars(e))
@ -183,3 +196,18 @@ func TestWithModeMask(t *testing.T) {
e, c.Mode) e, c.Mode)
} }
} }
func FuzzWithAlgorithm(f *testing.F) {
f.Add(0)
f.Add(1)
f.Add(2)
f.Add(3)
f.Add(-1)
f.Add(100)
f.Fuzz(func(t *testing.T, algo int) {
config := NewConfig(WithAlgorithm(Algorithm(algo)))
if config.MaxLength < config.MinLength {
t.Errorf("Invalid algorithm: %d", algo)
}
})
}

View file

@ -21,7 +21,8 @@ func TestHasBeenPwned(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
got, err := HasBeenPwned(tt.password) got, err := HasBeenPwned(tt.password)
if err != nil { if err != nil {
t.Errorf("HasBeenPwned() failed: %s", err) t.Logf("HasBeenPwned() failed: %s", err)
return
} }
if tt.want != got { if tt.want != got {
t.Errorf("HasBeenPwned() failed, wanted: %t, got: %t", tt.want, got) t.Errorf("HasBeenPwned() failed, wanted: %t, got: %t", tt.want, got)

View file

@ -57,10 +57,13 @@ func (g *Generator) Generate() (string, error) {
return g.generateCoinFlip() return g.generateCoinFlip()
case AlgoRandom: case AlgoRandom:
return g.generateRandom() return g.generateRandom()
case AlgoBinary:
return g.generateBinary()
case AlgoUnsupported: case AlgoUnsupported:
return "", fmt.Errorf("unsupported algorithm") return "", fmt.Errorf("unsupported algorithm")
default:
return "", fmt.Errorf("unsupported algorithm")
} }
return "", nil
} }
// GetCharRangeFromConfig checks the Mode from the Config and returns a // GetCharRangeFromConfig checks the Mode from the Config and returns a
@ -315,8 +318,26 @@ func (g *Generator) generatePronounceable() (string, error) {
return password, nil return password, nil
} }
// generateBinary is executed when Generate() is called with Algorithm set
// to AlgoBinary
func (g *Generator) generateBinary() (string, error) {
length := DefaultBinarySize
if g.config.FixedLength > 0 {
length = g.config.FixedLength
}
randBytes := make([]byte, length)
_, err := rand.Read(randBytes)
if err != nil {
return "", fmt.Errorf("failed to generate random bytes: %w", err)
}
if g.config.BinaryHexMode {
return fmt.Sprintf("%x", randBytes), nil
}
return string(randBytes), nil
}
// generateRandom is executed when Generate() is called with Algorithm set // generateRandom is executed when Generate() is called with Algorithm set
// to AlgoRandmom // to AlgoRandom
func (g *Generator) generateRandom() (string, error) { func (g *Generator) generateRandom() (string, error) {
length, err := g.GetPasswordLength() length, err := g.GetPasswordLength()
if err != nil { if err != nil {

View file

@ -471,6 +471,10 @@ func TestGenerate(t *testing.T) {
name: "Random", name: "Random",
algorithm: AlgoRandom, algorithm: AlgoRandom,
}, },
{
name: "Binary",
algorithm: AlgoBinary,
},
{ {
name: "Unsupported", name: "Unsupported",
algorithm: AlgoUnsupported, algorithm: AlgoUnsupported,
@ -530,3 +534,38 @@ func BenchmarkGenerator_RandomString(b *testing.B) {
} }
} }
} }
func TestGenerator_generateBinary(t *testing.T) {
tests := []struct {
name string
config *Config
wantErr bool
}{
{
name: "Positive Case - Binary in Hex",
config: &Config{FixedLength: 10, BinaryHexMode: true},
wantErr: false,
},
{
name: "Negative Case - Insufficient length",
config: &Config{FixedLength: -1, BinaryHexMode: false},
wantErr: false,
},
{
name: "Positive Case - Binary without Hex",
config: &Config{FixedLength: 10, BinaryHexMode: false},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := &Generator{config: tt.config}
_, err := g.generateBinary()
if (err != nil) != tt.wantErr {
t.Errorf("Generator.generateBinary() error = %v, wantErr %v", err, tt.wantErr)
return
}
})
}
}

View file

@ -140,6 +140,12 @@ func TestPronounce(t *testing.T) {
want: "mu-sa", want: "mu-sa",
wantErr: false, wantErr: false,
}, },
{
name: "Pronounce_Mixed",
syllables: []string{"mu", "1"},
want: "mu-ONE",
wantErr: false,
},
{ {
name: "Pronounce_NonKoremutakeSyllable", name: "Pronounce_NonKoremutakeSyllable",
syllables: []string{"ä"}, syllables: []string{"ä"},