Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Golang Proxy Scrypt module to be compatible with the Perl Crypt::ScryptKDF lib #1560

Merged
merged 10 commits into from
Nov 17, 2017

Conversation

dewrich
Copy link
Contributor

@dewrich dewrich commented Nov 16, 2017

Wrote this module to be used for creating user passwords in the tm_user table that are backward compatible with the Perl http://search.cpan.org/~mik/Crypt-ScryptKDF-0.010/lib/Crypt/ScryptKDF.pm library being used by Traffic Ops.

The goal is to have this function be useful for Golang TO API testing, TO /login and /users functionality as well.

@dewrich dewrich requested review from rob05c and dangogh November 16, 2017 21:21
@asfgit
Copy link
Contributor

asfgit commented Nov 16, 2017

SUCCESS
No test results found.
Test PASSed.

@asfgit
Copy link
Contributor

asfgit commented Nov 16, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/incubator-trafficcontrol-PR/661/
Test PASSed.

@asfgit
Copy link
Contributor

asfgit commented Nov 16, 2017

SUCCESS
No test results found.
Test PASSed.

@asfgit
Copy link
Contributor

asfgit commented Nov 16, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/incubator-trafficcontrol-PR/662/
Test PASSed.

@dewrich dewrich added the WIP "Work-in-Progress" - do not merge! (use 'draft' pull requests from now on) label Nov 16, 2017
@dewrich dewrich self-assigned this Nov 16, 2017
@dewrich dewrich added this to the 2.2.0 milestone Nov 16, 2017
@asfgit
Copy link
Contributor

asfgit commented Nov 16, 2017

SUCCESS
No test results found.
Test PASSed.

@asfgit
Copy link
Contributor

asfgit commented Nov 16, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/incubator-trafficcontrol-PR/663/
Test PASSed.

@asfgit
Copy link
Contributor

asfgit commented Nov 16, 2017

SUCCESS
No test results found.
Test PASSed.

@asfgit
Copy link
Contributor

asfgit commented Nov 16, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/incubator-trafficcontrol-PR/667/
Test PASSed.

Copy link
Member

@dangogh dangogh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggested a couple of minor changes.. but I think this is a great step forward

@asfgit
Copy link
Contributor

asfgit commented Nov 16, 2017

SUCCESS
No test results found.

@asfgit
Copy link
Contributor

asfgit commented Nov 16, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/incubator-trafficcontrol-PR-trafficops-test/620/

@asfgit
Copy link
Contributor

asfgit commented Nov 16, 2017

SUCCESS
No test results found.

@asfgit
Copy link
Contributor

asfgit commented Nov 16, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/incubator-trafficcontrol-PR-trafficops-test/621/

@asfgit
Copy link
Contributor

asfgit commented Nov 16, 2017

SUCCESS
No test results found.

@asfgit
Copy link
Contributor

asfgit commented Nov 16, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/incubator-trafficcontrol-PR-trafficops-test/622/

Copy link
Member

@rob05c rob05c left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, mostly minor suggestions, it's fine if you don't agree with them.

But the VerifyPassword err is an issue, it's successfully verifying everything. Negative tests, which will catch that in the future, would also be really good to have.


const KEY_DELIM = ":"

var DefaultParams = SCRYPTComponents{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor suggestion: a comment here linking https://godoc.org/golang.org/x/crypto/scrypt or https://www.tarsnap.com/scrypt/scrypt.pdf or both would be good, since these numbers are magical, and anyone trying to understand them will be confused without those explanations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 -- would be clearer..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, I'll add

func DerivePassword(password string) (string, error) {
var salt []byte
var err error
salt, err = generateSalt(64)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A GUID, 16 bytes, should be sufficient. Though I'm not strongly opposed to more, if you feel strongly about it. Also, what about putting GUIDLen in a constant? Maybe even in lib/go-tc? Seems generally useful.

Copy link
Contributor Author

@dewrich dewrich Nov 17, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got the parameters to that from https://github.com/apache/incubator-trafficcontrol/blob/master/traffic_ops/app/lib/Utils/Helper.pm#L148. The reason I chose 64 because I thought keys need the same scrypt parameters to make the passwords format and length compatible

I could add that to the DefaultsParams and use from their to make the parameter more prominent.

}

keylenBytes := len(scryptPassword) - DefaultParams.DKLen
if keylenBytes < 1 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: could be if keylenBytes := len(scryptPassword) - DefaultParams.DKLen; keylenBytes < 1 {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-1 on that one.. I find it less readable when the assignment is this long

return nil
}

return err
Copy link
Member

@rob05c rob05c Nov 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to construct an error. It's returning the err we just verified is nil, and thus verifying invalid passwords.

Verified with a negative test:

derivedPassword, err := DerivePassword(pass)
err = VerifyPassword("notpassword", derivedPassword)
if err == nil {
  t.Errorf(" expected error, actual success")
}
$ go test
--- FAIL: TestDerivePassword (0.20s)
        authenticate_test.go:39:  expected error, actual success


var err error
var scomp SCRYPTComponents
if scryptPassword == "" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be at the top of the func, to avoid calling Split if unnecessary?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not quite sure what you mean here

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If scryptPassword is empty, we return, but we don't need sh for that. The Split function is moderately expensive. So, it's slightly more efficient to check and return before calling Split.

+func parseScrypt(scryptPassword string) (SCRYPTComponents, error) {
+	if scryptPassword == "" {
+		return SCRYPTComponents{}, errors.New("scrypt password is required")
+	}
+	sh := strings.Split(scryptPassword, ":")
⋮

But Split isn't that expensive, so it's not a big deal to leave it; just a minor improvement.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, done!

pass := "password"
derivedPassword, err := DerivePassword(pass)
err = VerifyPassword(pass, derivedPassword)
if err != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A negative test would be really good, too. Making sure a bad password doesn't successfully verify. I'd vote for some edge cases too, e.g. "", "0", ".", "\0", "\n" "somethingextremelylong".

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with that, I'll add that

return scomp, errors.New("salt cannot be decoded")
}
scomp.SaltLen = len(scomp.Salt)
if len(scomp.Salt) < 0 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

len will never return less than zero. Should this be == 0? Or should it be removed?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1


scomp.R, err = strconv.Atoi(r)
if err != nil {
return scomp, errors.New(fmt.Sprintf("i=%d, type: %T\n", scomp.R, scomp.R))
Copy link
Member

@rob05c rob05c Nov 16, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor suggestion: could be fmt.Errorf("i=%d, type: %T", scomp.R, scomp.R). Suggest removing newline, they're usually put in the print rather than the error

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good to know, wasn't aware of fmt.Errorf

var err error
salt, err = generateSalt(64)
if err != nil {
return "", err
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor suggestion: context on these errors would help debugging, e.g. return "", errors.New("generating salt: " + err.Error())

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, done


// Algorithm
scomp.Algorithm = sh[0]
if scomp.Algorithm == "" {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we also verify Algorithm == "SCRYPT"?

@rob05c
Copy link
Member

rob05c commented Nov 16, 2017

I agree with @dangogh though, this is great, the login endpoint is one of the "hard" things, great to check off the list for traffic_ops_golang.

@@ -0,0 +1,10 @@
# Treat all files in this repo as binary, with no git magic updating
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd vote removing this file, too. I'm not enough of a Git Wizard to know if it's going to mess with our repo, but .git* files in subdirectories make me anxious it's going to mess with something. I don't think it'll hurt to delete it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

will do

@asfgit
Copy link
Contributor

asfgit commented Nov 17, 2017

SUCCESS
No test results found.

@asfgit
Copy link
Contributor

asfgit commented Nov 17, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/incubator-trafficcontrol-PR-trafficops-test/626/

@asfgit
Copy link
Contributor

asfgit commented Nov 17, 2017

SUCCESS
No test results found.
Test PASSed.

@asfgit
Copy link
Contributor

asfgit commented Nov 17, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/incubator-trafficcontrol-PR/670/
Test PASSed.

@asfgit
Copy link
Contributor

asfgit commented Nov 17, 2017

SUCCESS
No test results found.
Test PASSed.

@asfgit
Copy link
Contributor

asfgit commented Nov 17, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/incubator-trafficcontrol-PR/671/
Test PASSed.

@asfgit
Copy link
Contributor

asfgit commented Nov 17, 2017

SUCCESS
No test results found.

@asfgit
Copy link
Contributor

asfgit commented Nov 17, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/incubator-trafficcontrol-PR-trafficops-test/629/


// VerifyPassword parses the original Derived Key (DK) from the SCRYPT password
// so that it can compare that with the password/scriptPassword param
func VerifyPassword(password string, scryptPassword string) (bool, error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had to read the whole function to figure out what cases return true/false, and what return error/nil, and when (false,nil) and (true,error) were returned. As-is, adding the specifics about what's returned in what case to the function-level comment would be really helpful.

But, I like the original better, just returning an error, where nil means success. Since (true,error) never makes sense, we can return nil on success. Just change the last line to return errors.New("invalid password").

But, it works, so I'll pass the review, if you disagree.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@rob05c I went back and forth on the (bool, error) return. I decided to go back to the original and added the return error that you mentioned.

@asfgit
Copy link
Contributor

asfgit commented Nov 17, 2017

SUCCESS
No test results found.

@asfgit
Copy link
Contributor

asfgit commented Nov 17, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/incubator-trafficcontrol-PR-trafficops-test/630/

@rob05c rob05c merged commit 69e509b into apache:master Nov 17, 2017
@asfgit
Copy link
Contributor

asfgit commented Nov 17, 2017

FAILURE
No test results found.
Test FAILed.

@asfgit
Copy link
Contributor

asfgit commented Nov 17, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/incubator-trafficcontrol-PR/674/
Test FAILed.

@asfgit
Copy link
Contributor

asfgit commented Nov 17, 2017

SUCCESS
No test results found.

@asfgit
Copy link
Contributor

asfgit commented Nov 17, 2017

Refer to this link for build results (access rights to CI server needed):
https://builds.apache.org/job/incubator-trafficcontrol-PR-trafficops-test/633/

@mitchell852 mitchell852 removed the WIP "Work-in-Progress" - do not merge! (use 'draft' pull requests from now on) label Nov 27, 2017
@zrhoffman zrhoffman added the authentication Relating to login, registration, passwords, tokens, etc. label Jan 12, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
authentication Relating to login, registration, passwords, tokens, etc.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants