mirror of
https://github.com/wneessen/apg-go.git
synced 2024-11-22 13:50:49 +01:00
Compare commits
33 commits
641af1f88c
...
5ba220f1b9
Author | SHA1 | Date | |
---|---|---|---|
5ba220f1b9 | |||
3ffb499c1b | |||
b40b4b7e63 | |||
f5f6a12e83 | |||
f65feff1f9 | |||
7f8fbb05bc | |||
b289d440da | |||
ef8e334df0 | |||
ba891efd37 | |||
bfc12841ce | |||
4ea41be22f | |||
2691b04e38 | |||
decf5526d1 | |||
061b9f4f7f | |||
bffc8ac65e | |||
6f25663957 | |||
31cf70c678 | |||
4bc210f1ab | |||
b36aeeeab6 | |||
043008a97d | |||
2af31dcb48 | |||
7ebaf2d2b7 | |||
eec1b36edc | |||
2d674214a7 | |||
a61ac9b877 | |||
6697ac53db | |||
64f7eed954 | |||
4a6b9b325f | |||
9f035c5834 | |||
183754e869 | |||
c697a8ef8e | |||
c8a4cf2837 | |||
acadccc84a |
18 changed files with 420 additions and 7 deletions
1
.github/workflows/codecov.yml
vendored
1
.github/workflows/codecov.yml
vendored
|
@ -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
91
.github/workflows/codeql.yml
vendored
Normal 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}}"
|
3
.github/workflows/docker-publish.yml
vendored
3
.github/workflows/docker-publish.yml
vendored
|
@ -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
|
||||||
|
|
2
.github/workflows/reuse.yml
vendored
2
.github/workflows/reuse.yml
vendored
|
@ -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
76
.github/workflows/scorecard.yml
vendored
Normal 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
|
1
.github/workflows/sonarqube.yml
vendored
1
.github/workflows/sonarqube.yml
vendored
|
@ -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:
|
||||||
|
|
29
README.md
29
README.md
|
@ -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
38
SECURITY.md
Normal 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-----
|
||||||
|
```
|
5
algo.go
5
algo.go
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
38
algo_test.go
38
algo_test.go
|
@ -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
2
apg.go
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
14
config.go
14
config.go
|
@ -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 {
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
25
random.go
25
random.go
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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{"ä"},
|
||||||
|
|
Loading…
Reference in a new issue