Skip to content

Commit 84f676b

Browse files
authored
Add WithMinLengthPasswordFile pemutil option (#711)
1 parent 98cc861 commit 84f676b

File tree

3 files changed

+98
-5
lines changed

3 files changed

+98
-5
lines changed

pemutil/pem.go

+19-2
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ type context struct {
6868
func newContext(name string) *context {
6969
return &context{
7070
filename: name,
71-
perm: 0600,
71+
perm: 0o600,
7272
}
7373
}
7474

@@ -128,7 +128,7 @@ func WithFilename(name string) Options {
128128
ctx.filename = name
129129
// Default perm mode if not set
130130
if ctx.perm == 0 {
131-
ctx.perm = 0600
131+
ctx.perm = 0o600
132132
}
133133
return nil
134134
}
@@ -164,6 +164,23 @@ func WithPasswordFile(filename string) Options {
164164
}
165165
}
166166

167+
// WithMinLengthPasswordFile is a method that adds the password in a file to the
168+
// context. If the password does not meet the minimum length requirement an
169+
// error is returned. If minimum length input is <=0 then the requirement is
170+
// ignored.
171+
func WithMinLengthPasswordFile(filename string, minLength int) Options {
172+
return func(ctx *context) error {
173+
if err := WithPasswordFile(filename)(ctx); err != nil {
174+
return err
175+
}
176+
177+
if minLength > 0 && len(ctx.password) < minLength {
178+
return fmt.Errorf("password does not meet minimum length requirement; must be at least %v characters", minLength)
179+
}
180+
return nil
181+
}
182+
}
183+
167184
// WithPasswordPrompt ask the user for a password and adds it to the context.
168185
func WithPasswordPrompt(prompt string, fn PasswordPrompter) Options {
169186
return func(ctx *context) error {

pemutil/pem_test.go

+78-3
Original file line numberDiff line numberDiff line change
@@ -648,15 +648,15 @@ func TestSerialize(t *testing.T) {
648648
case test.pass == "" && test.file == "":
649649
p, err = Serialize(in)
650650
case test.pass != "" && test.file != "":
651-
p, err = Serialize(in, WithPassword([]byte(test.pass)), ToFile(test.file, 0600))
651+
p, err = Serialize(in, WithPassword([]byte(test.pass)), ToFile(test.file, 0o600))
652652
case test.pass != "" && test.pkcs8:
653653
p, err = Serialize(in, WithPKCS8(true), WithPasswordPrompt("Please enter the password to encrypt the key", func(prompt string) ([]byte, error) {
654654
return []byte(test.pass), nil
655655
}))
656656
case test.pass != "":
657657
p, err = Serialize(in, WithPassword([]byte(test.pass)))
658658
default:
659-
p, err = Serialize(in, ToFile(test.file, 0600))
659+
p, err = Serialize(in, ToFile(test.file, 0o600))
660660
}
661661

662662
if err != nil {
@@ -722,7 +722,7 @@ func TestSerialize(t *testing.T) {
722722
var fileInfo os.FileInfo
723723
fileInfo, err = os.Stat(test.file)
724724
require.NoError(t, err)
725-
assert.Equal(t, fileInfo.Mode(), os.FileMode(0600))
725+
assert.Equal(t, fileInfo.Mode(), os.FileMode(0o600))
726726
// Verify that key written to file is correct
727727
var keyFileBytes []byte
728728
keyFileBytes, err = os.ReadFile(test.file)
@@ -1024,6 +1024,7 @@ func TestRead_options(t *testing.T) {
10241024
{"withPasswordPromptError", args{"testdata/openssl.p256.enc.pem", []Options{WithPasswordPrompt("Enter the password", func(s string) ([]byte, error) {
10251025
return nil, errors.New("an error")
10261026
})}}, nil, true},
1027+
{"withPasswordFile", args{"testdata/openssl.p256.enc.pem", []Options{WithPasswordFile("testdata/password.txt")}}, p256Key, false},
10271028
}
10281029
for _, tt := range tests {
10291030
t.Run(tt.name, func(t *testing.T) {
@@ -1039,6 +1040,80 @@ func TestRead_options(t *testing.T) {
10391040
}
10401041
}
10411042

1043+
func TestWithMinLengthPasswordFile(t *testing.T) {
1044+
tests := []struct {
1045+
name string
1046+
length int
1047+
file string
1048+
want []byte
1049+
wantErr bool
1050+
}{
1051+
{
1052+
name: "negative",
1053+
length: -5,
1054+
file: "testdata/password.txt",
1055+
wantErr: false,
1056+
want: []byte("mypassword"),
1057+
},
1058+
{
1059+
name: "zero",
1060+
length: 0,
1061+
file: "testdata/password.txt",
1062+
wantErr: false,
1063+
want: []byte("mypassword"),
1064+
},
1065+
{
1066+
name: "greater-than-min-length",
1067+
length: 9,
1068+
file: "testdata/password.txt",
1069+
wantErr: false,
1070+
want: []byte("mypassword"),
1071+
},
1072+
{
1073+
name: "equal-min-length",
1074+
length: 10,
1075+
file: "testdata/password.txt",
1076+
wantErr: false,
1077+
want: []byte("mypassword"),
1078+
},
1079+
{
1080+
name: "less-than-min-length",
1081+
length: 11,
1082+
file: "testdata/password.txt",
1083+
wantErr: true,
1084+
},
1085+
{
1086+
name: "ignore-pre-post-whitespace-characters",
1087+
length: 7,
1088+
file: "testdata/password2.txt",
1089+
wantErr: true,
1090+
},
1091+
{
1092+
name: "ignore-pre-post-whitespace-characters-ok",
1093+
length: 6,
1094+
file: "testdata/password2.txt",
1095+
wantErr: false,
1096+
want: []byte(" pass"),
1097+
},
1098+
}
1099+
for _, tt := range tests {
1100+
t.Run(tt.name, func(t *testing.T) {
1101+
ctx := newContext(tt.name)
1102+
gotErr := WithMinLengthPasswordFile(tt.file, tt.length)(ctx) != nil
1103+
if gotErr != tt.wantErr {
1104+
t.Errorf("WithMinLengthPasswordFile(%v, %v) = %v, want %v", tt.file, tt.length, gotErr, tt.wantErr)
1105+
return
1106+
}
1107+
if gotErr {
1108+
return
1109+
}
1110+
if !bytes.Equal(ctx.password, tt.want) {
1111+
t.Errorf("Expected %v, but got %v", tt.want, ctx.password)
1112+
}
1113+
})
1114+
}
1115+
}
1116+
10421117
func TestRead_promptPassword(t *testing.T) {
10431118
mustKey := func(filename string) interface{} {
10441119
b, err := os.ReadFile(filename)

pemutil/testdata/password2.txt

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
pass

0 commit comments

Comments
 (0)