Skip to content

Commit

Permalink
Merge pull request #10 from jeremmfr/fix-panic-multiple-routines
Browse files Browse the repository at this point in the history
Fix panic when multiple routines call decode or with empty input
  • Loading branch information
jeremmfr authored Mar 6, 2023
2 parents 1aa4464 + 95c8e38 commit 80b2579
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 47 deletions.
36 changes: 25 additions & 11 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ jobs:
- name: Set up Go 1.15
uses: actions/setup-go@v3
with:
go-version: 1.15
go-version: '1.15'
id: go
- name: Check out code
uses: actions/checkout@v3
- name: Test
run: go test -v ./...
run: go test -race -v ./...

test-1_16:
name: Test 1.16
Expand All @@ -22,12 +22,12 @@ jobs:
- name: Set up Go 1.16
uses: actions/setup-go@v3
with:
go-version: 1.16
go-version: '1.16'
id: go
- name: Check out code
uses: actions/checkout@v3
- name: Test
run: go test -v ./...
run: go test -race -v ./...

test-1_17:
name: Test 1.17
Expand All @@ -36,12 +36,12 @@ jobs:
- name: Set up Go 1.17
uses: actions/setup-go@v3
with:
go-version: 1.17
go-version: '1.17'
id: go
- name: Check out code
uses: actions/checkout@v3
- name: Test
run: go test -v ./...
run: go test -race -v ./...

test-1_18:
name: Test 1.18
Expand All @@ -50,12 +50,12 @@ jobs:
- name: Set up Go 1.18
uses: actions/setup-go@v3
with:
go-version: 1.18
go-version: '1.18'
id: go
- name: Check out code
uses: actions/checkout@v3
- name: Test
run: go test -v ./...
run: go test -race -v ./...

test-1_19:
name: Test 1.19
Expand All @@ -64,15 +64,29 @@ jobs:
- name: Set up Go 1.19
uses: actions/setup-go@v3
with:
go-version: 1.19
go-version: '1.19'
id: go
- name: Check out code
uses: actions/checkout@v3
- name: Test
run: go test -v ./... -coverprofile=coverage.txt -covermode=atomic
run: go test -race -v ./...

test-1_20:
name: Test 1.20
runs-on: ubuntu-latest
steps:
- name: Set up Go 1.20
uses: actions/setup-go@v3
with:
go-version: '1.20'
id: go
- name: Check out code
uses: actions/checkout@v3
- name: Test
run: go test -race -v ./... -coverprofile=coverage.out -covermode=atomic

- name: Codecov
uses: codecov/codecov-action@v3
with:
token: ${{secrets.CODECOV_TOKEN}}
file: ./coverage.txt
file: ./coverage.out
2 changes: 1 addition & 1 deletion .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,5 @@ jobs:
- name: golangci-lint
uses: golangci/golangci-lint-action@v3
with:
version: v1.50
version: v1.51
args: -c .golangci.yml -v
12 changes: 5 additions & 7 deletions .github/workflows/releases.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,10 @@ jobs:
run: echo "RELEASE_VERSION=$(echo ${GITHUB_REF} | cut -d'/' -f3)" >> $GITHUB_ENV
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}
uses: ncipollo/release-action@v1
with:
tag_name: ${{ github.ref }}
release_name: ${{ env.RELEASE_VERSION }}
body: ${{ env.RELEASE_VERSION }}
token: ${{ secrets.RELEASE_TOKEN }}
name: ${{ env.RELEASE_VERSION }}
omitBody: true
draft: true
prerelease: false
prerelease: false
1 change: 0 additions & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ linters:
- wsl
- gochecknoglobals
- gomnd
- paralleltest
- nonamedreturns
linters-settings:
govet:
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
BUG FIXES:

* fix panic when call `Decode()` in parallel
* fix panic when string input of `Decode()` is empty
* return error when there isn't enough characters in string input of `Decode()`

## v1.1.0

NOTES:
Expand Down
16 changes: 16 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
default: gotest/html

.PHONY: gotest gotest/html pkgsite pkgsite/install

gotest:
go test -race -covermode=count -v ./...

gotest/html:
go test -race -v ./... -coverprofile=coverage.out && go tool cover -html=coverage.out

pkgsite: pkgsite/install
@echo "open -> http://localhost:8080/$(shell go list -m)"
pkgsite

pkgsite/install:
go install golang.org/x/pkgsite/cmd/pkgsite@latest
69 changes: 42 additions & 27 deletions jdecode.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,39 @@ import (
// MagicPrefix: Junos encrypted secret prefix.
const MagicPrefix = "$9$"

// ErrDiffGapDec is returned when can't decode a character due to a missing or extra character(s).
//
// This is detected by a difference in length of the internal `gaps` and `decode` list.
var ErrDiffGapDec = errors.New("junosdecode: missing or extra character(s) (gaps and decode size not the same)")

// ErrEmptySecret is returned when can't decode due to empty input.
var ErrEmptySecret = errors.New("junosdecode: no secret to decode")

// ErrNotEnoughChars is returned when can't decode due to not enough characters input.
var ErrNotEnoughChars = errors.New("junosdecode: not enough characters")

// Decode Junos encrypted secret ($9$).
func Decode(encryptedSecret string) (secretDecoded string, _ error) {
initialize()
if encryptedSecret == "" {
return "", ErrEmptySecret
}
chars := strings.TrimPrefix(encryptedSecret, MagicPrefix)
if len(chars) < 3 {
return "", ErrNotEnoughChars
}
dict := newDictAlpha()
var first string
first, chars = nibble(chars, 1)
firstR := []rune(first)
_, chars = nibble(chars, extra[firstR[0]])
_, chars = nibble(chars, dict.extra[firstR[0]])
prev := first
for chars != "" {
decode := encoding[len(secretDecoded)%len(encoding)]
var nib string
nib, chars = nibble(chars, len(decode))
var gaps []int
for _, i := range nib {
g := gap(prev, string(i))
g := dict.gap(prev, string(i))
prev = string(i)
gaps = append(gaps, g)
}
Expand All @@ -38,13 +55,6 @@ func Decode(encryptedSecret string) (secretDecoded string, _ error) {
return secretDecoded, nil
}

var family = []string{
"QzF3n6/9CAtpu0O",
"B1IREhcSyrleKvMW8LXx",
"7N-dVbwsY2g4oaJZGUDj",
"iHkq.mPf5T",
}

var encoding = [][]int{
{
1, 4, 32,
Expand All @@ -69,33 +79,38 @@ var encoding = [][]int{
},
}

var (
type dictAlpha struct {
numAlpha map[int]rune
alphaNum map[rune]int
extra map[rune]int
)
}

// initialize fill numAlpha, alphaNum, extra with family.
func initialize() {
numAlpha = make(map[int]rune)
alphaNum = make(map[rune]int)
extra = make(map[rune]int)
// newDictAlpha create dictAlpha with data filled.
func newDictAlpha() dictAlpha {
family := []string{
"QzF3n6/9CAtpu0O",
"B1IREhcSyrleKvMW8LXx",
"7N-dVbwsY2g4oaJZGUDj",
"iHkq.mPf5T",
}
dict := dictAlpha{
numAlpha: make(map[int]rune),
alphaNum: make(map[rune]int),
extra: make(map[rune]int),
}
for i, r := range []rune(strings.Join(family, "")) {
numAlpha[i] = r
alphaNum[r] = i
dict.numAlpha[i] = r
dict.alphaNum[r] = i
}

for i, fam := range family {
for _, c := range fam {
extra[c] = 3 - i
dict.extra[c] = 3 - i
}
}
}

// ErrDiffGapDec is returned when can't decode a character due to a missing or extra character(s).
//
// This is detected by a difference in length of the internal `gaps` and `decode` list.
var ErrDiffGapDec = errors.New("junosdecode: missing or extra character(s) (gaps and decode size not the same)")
return dict
}

// gapDecode.
func gapDecode(gaps []int, dec []int) (string, error) {
Expand All @@ -122,11 +137,11 @@ func nibble(cref string, length int) (string, string) {
}

// gap betwean characters.
func gap(c1 string, c2 string) int {
func (d *dictAlpha) gap(c1 string, c2 string) int {
c1rune := []rune(c1)
c2rune := []rune(c2)

return pmod((alphaNum[c2rune[0]]-alphaNum[c1rune[0]]), (len(numAlpha))) - 1
return pmod((d.alphaNum[c2rune[0]]-d.alphaNum[c1rune[0]]), (len(d.numAlpha))) - 1
}

// modulus positive (same as python).
Expand Down
54 changes: 54 additions & 0 deletions jdecode_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package junosdecode_test

import (
"errors"
"fmt"
"math/rand"
"testing"

Expand All @@ -14,6 +16,8 @@ const (

// TestDecode decode example password.
func TestDecodePassword(t *testing.T) {
t.Parallel()

passwordDecoded, err := junosdecode.Decode(junWordCoded)
if err != nil {
t.Errorf("error on decode %v", err)
Expand All @@ -25,6 +29,8 @@ func TestDecodePassword(t *testing.T) {

// TestDecodeBadEncoded try decode encrypted secret but with missing or extra character.
func TestDecodeBadEncoded(t *testing.T) {
t.Parallel()

// remove last character
if passwordDecoded, err := junosdecode.Decode(junWordCoded[:len(junWordCoded)-1]); err == nil {
t.Errorf("missing character in junWordCoded not detected, passwordDecoded: %s", passwordDecoded)
Expand All @@ -39,3 +45,51 @@ func TestDecodeBadEncoded(t *testing.T) {
t.Errorf("extra characters in junWordCoded not detected, passwordDecoded: %s", passwordDecoded)
}
}

func TestDecodeEmptyEncoded(t *testing.T) {
t.Parallel()

// empty input
if passwordDecoded, err := junosdecode.Decode(""); err == nil {
t.Errorf("empty input not detected, passwordDecoded: %s", passwordDecoded)
} else if !errors.Is(err, junosdecode.ErrEmptySecret) {
t.Errorf("got unexpected error: %s", err)
}
}

func TestDecodeNotEnoughCharsEncoded(t *testing.T) {
t.Parallel()

// MagicPrefix input
if passwordDecoded, err := junosdecode.Decode(junosdecode.MagicPrefix); err == nil {
t.Errorf("MagicPrefix input not detected, passwordDecoded: %s", passwordDecoded)
} else if !errors.Is(err, junosdecode.ErrNotEnoughChars) {
t.Errorf("got unexpected error: %s", err)
}

// MagicPrefix and 2 chars input
if passwordDecoded, err := junosdecode.Decode(junosdecode.MagicPrefix + "!!"); err == nil {
t.Errorf("MagicPrefix + 2 chars input not detected, passwordDecoded: %s", passwordDecoded)
} else if !errors.Is(err, junosdecode.ErrNotEnoughChars) {
t.Errorf("got unexpected error: %s", err)
}
}

func TestDecodeParrallel(t *testing.T) {
t.Parallel()

for i, v := range []string{junWordCoded, junWordCoded, junWordCoded} {
word := v
t.Run(fmt.Sprintf("Loop number %d", i), func(t *testing.T) {
t.Parallel()

output, err := junosdecode.Decode(word)
if err != nil {
t.Errorf("got unexpected error: %s", err)
}
if output != junWordDecoded {
t.Errorf("got unexpected decoded word: %s", output)
}
})
}
}

0 comments on commit 80b2579

Please sign in to comment.