Skip to content

Add VALIDATE calls#13

Open
ViViDboarder wants to merge 6 commits intoyawn:masterfrom
ViViDboarder:validate
Open

Add VALIDATE calls#13
ViViDboarder wants to merge 6 commits intoyawn:masterfrom
ViViDboarder:validate

Conversation

@ViViDboarder
Copy link
Copy Markdown

My Yubikey Neo is password protected and requires a call to validate the password before listing or performing other actions.

In order to make the magic numbers more readable I added all the ones I found on the Yubico site to constants.go.

Other smaller changes:

  • Export CALCULATE as CalculateOne and CALCULATE_ALL as CalculateAll
  • Add more error codes to codes.go

This is used as follows:

package main

import (
	"fmt"
	"git.iamthefij.com/iamthefij/slog"
	"github.com/yawn/ykoath"
	"golang.org/x/crypto/ssh/terminal"
	"syscall"
)

func main() {
	oath, err := ykoath.New()
	slog.FatalOnErr(err, "failed to initialize new oath")
	oath.Debug = slog.Debug

	defer oath.Close()

	s, err := oath.Select()
	slog.FatalOnErr(err, "failed to select oath")

	if s.Challenge != nil {
		fmt.Println("Passphrase? ")
		passphrase, err := terminal.ReadPassword(int(syscall.Stdin))
		slog.FatalOnErr(err, "failed reading passphrase")
		key := s.DeriveKey(string(passphrase))

		ok, err := oath.Validate(s, key)
		slog.FatalOnErr(err, "validation failed")
		if !ok {
			panic("could not validate")
		}
	}

	names, err := oath.List()
	slog.FatalOnErr(err, "failed to list names")

	for _, name := range names {
		slog.Log(name.Name)
	}

	otp, err := oath.Calculate(names[0].Name, ykoath.ErrorTouchCallback)
	slog.FatalOnErr(err, "Failed to retrieve otp")
	slog.Log(otp)
}

@yawn
Copy link
Copy Markdown
Owner

yawn commented Dec 10, 2020

Awesome PR @ViViDboarder, thanks a lot! Review will be done ~Monday.

I actually have no idea what happened here. This was working for me
before and then suddenly wasn't and required this change.
@ViViDboarder
Copy link
Copy Markdown
Author

For some reason I started getting 8 digit codes where I was supposed to get 6. I didn't change anything so I wonder if it has something to do with drivers or a dependency. To resolve it, I implemented a similar solution to yubikey-manager

@ViViDboarder
Copy link
Copy Markdown
Author

Any chance of a review in the next week or so? I wrote a cli tool and an Alfred Workflow that depends on this. I can build and distribute binaries using replace in my go.mod for now, but it's easier for others to test and write patches if they can build directly.

@yawn
Copy link
Copy Markdown
Owner

yawn commented Jan 7, 2021

Yeah, sorry about that. I'll do the review today / in the afternoon.

Copy link
Copy Markdown
Owner

@yawn yawn left a comment

Choose a reason for hiding this comment

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

Thanks again for the PR, very nice work! Apart from the individual comments I have two three high-level questions:

  1. Why the KDF in select.go?
  2. Can you contribute changes to README as well (you made VALIDATE support a reality after all 👍 )
  3. Can you contribute additional unit tests (please note that current suite will delete existing OATH tokens from your test key)?

Thanks again for the work & sorry for not getting back earlier!

Comment thread algorithm.go

const (
// HmacSha1 describes a HMAC with SHA-1
HmacSha1 Algorithm = 0x01
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Not sure about this - this is already a public constant. What good does the additional constant do?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

That's a good point. I had done this initially to minimize the changes, but a single constant should be sufficient.

Comment thread algorithm.go
Comment thread algorithm.go
Comment thread constants.go
@@ -0,0 +1,49 @@
package ykoath
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I would prefer the constants to be private tbh - they do help readability but trigger linting advice when undocumented and public.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point. I don't believe the API that this package exposes requires someone to access these either. I'll rename them.

Comment thread calculate.go Outdated
codes = append(codes, touchRequired)

case 0x76:
case TAG_RESPONSE:
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Can you elaborate the scenario in which we need to handle a 0x75?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm not sure if there is a legitimate case for this, however it is included as a possible response in the spec. The result (once properly wrapping errors, as suggested) will be the same as not handling it, except with an error message that will be somewhat more helpful.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Ok, understood.

Comment thread select.go
Comment thread validate.go
func (o *OATH) Validate(s *Select, key []byte) (bool, error) {
algo, err := s.Hash()
if err != nil {
return false, err
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Here another errors.Wrapf + constant would be preferred.

Comment thread validate.go
return false, err
}

mac := hmac.New(algo, key)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Hm. If you really want to introduce a Hash function, you could potentially use the Sum / Sum256 / Sum512 functions from standard and return a slices on the array returned by them.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm not sure I follow.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I meant you could use the "sum" forms such as https://golang.org/pkg/crypto/sha256/#Sum256 - but I guess your way is more generic. Let's ignore this ...

Comment thread validate.go

randChallenge := make([]byte, 8)
_, err = rand.Read(randChallenge)
if err != nil {
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Same error remark as above.

Comment thread validate.go
challenge := write(TAG_CHALLENGE, challengeSum)
_, err = o.send(0x00, INST_VALIDATE, 0x00, 0x00, response, challenge)
if err != nil {
return false, err
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Same error remark as above.

Copy link
Copy Markdown
Author

@ViViDboarder ViViDboarder left a comment

Choose a reason for hiding this comment

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

Thanks for the feedback. Looks like a lot of cleanup on the constants and errors. I'll also update documentation to reflect the usage of the new functions within select.go.

Edit: Ugh. I guess that I didn't do responses properly. They show as replies above, but are duplicated here.

Comment thread algorithm.go

const (
// HmacSha1 describes a HMAC with SHA-1
HmacSha1 Algorithm = 0x01
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

That's a good point. I had done this initially to minimize the changes, but a single constant should be sufficient.

Comment thread calculate.go Outdated
codes = append(codes, touchRequired)

case 0x76:
case TAG_RESPONSE:
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm not sure if there is a legitimate case for this, however it is included as a possible response in the spec. The result (once properly wrapping errors, as suggested) will be the same as not handling it, except with an error message that will be somewhat more helpful.

Comment thread calculate.go
digits := int(value[0])
code := binary.BigEndian.Uint32(value[1:])
// Limit code to a maximum number of digits
code = code % uint32(math.Pow(10.0, float64(digits)))
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I had some TOTP that were supposed to be 6 digits (this was the value of digits), and they were returned as 8 digits. This will trim them to length of whatever the the value of digits is. This logic is the same as in yubikey-manager

Comment thread code.go Outdated
@@ -13,12 +13,15 @@ func (c code) Error() string {

if bytes.Equal(c, []byte{0x6a, 0x80}) {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I only included the constants listed at the top of https://developers.yubico.com/OATH/YKOATH_Protocol.html

Although this 0x6a80 is not listed as a constant there, it appears to be consistent throughout the documentation. I'll take a look through and include those and make a note along with the documentation link.

Comment thread code.go Outdated
@@ -13,12 +13,15 @@ func (c code) Error() string {

if bytes.Equal(c, []byte{0x6a, 0x80}) {
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

On a similar note, rather than returning a string, I could make this return an error for each of these. What do you think?

Comment thread constants.go
@@ -0,0 +1,49 @@
package ykoath
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good point. I don't believe the API that this package exposes requires someone to access these either. I'll rename them.

Comment thread select.go
Comment thread validate.go
return false, err
}

mac := hmac.New(algo, key)
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I'm not sure I follow.

@yawn
Copy link
Copy Markdown
Owner

yawn commented Feb 1, 2021

Hey @ViViDboarder - are the remaining issues clear? Should we proceed with this PR?

@ViViDboarder
Copy link
Copy Markdown
Author

Thanks @yawn. I'm still a little confused on errors, which I think are the only outstanding items.

The way I understand error handling in Go right now (it seems to change frequently) is that a class should do something like this:

package example

import (
    "errors"
    "fmt"
)

var (
    ErrSomethingNotFound = errors.New("thing not found")
)

func CalledError() error {
    err := maybeHasError()
    if err != nil {
        return fmt.Errorf("error in this thing: %w", err)
    }

    return nil
}

func FindThing(p string) error {
    var b bool
    b = isThingFound(p)
    if !b {
        return fmt.Errorf("error finding %p: %w", p, ErrSomethingNotFound)
    }

    return nil
}

This allows some using the module and function to handle errors from findThing by using the wraps.

package main

import (
    "errors"
    "fmt"

    "example"
)

func main() {
    err = example.FindThing("foo")
    if errors.Is(err, example.ErrSomethingNotFound) {
        fmt.Println("Thing wasn't found")
    } else {
        panic(err)
    }
}

I can definitely do this, but it would be inconsistent with some of the others. I can refactor those as well, but maybe in a different patch.

@yawn
Copy link
Copy Markdown
Owner

yawn commented Feb 2, 2021

As an alternative: just add the tests and I'll do the minor / cosmetic changes in your PR? Maybe easier?

@ViViDboarder
Copy link
Copy Markdown
Author

That works for me.

Regarding the tests, I can definitely write some, but I do not have a spare Yubikey to test with. It also looks like I'll need to implement the SET CODE instruction that sets up authentication if a full test is to be written so that auth can be enabled, validated, and disabled.

@yawn
Copy link
Copy Markdown
Owner

yawn commented Feb 2, 2021

I can totally live without SET CODE if you can substitute it with a ykman call. For the test environment that's fine. Spare Yubikey, hm - that's an issue of course :-)

@yawn yawn force-pushed the master branch 3 times, most recently from 3c0dfa3 to 201009e Compare June 2, 2022 12:54
@yawn yawn self-assigned this Jan 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants