-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add helper for encoding/decoding root tokens and OTP generation in SD…
- Loading branch information
Showing
18 changed files
with
239 additions
and
90 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
```release-note:improvement | ||
sdk: Add helper for decoding root tokens | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package roottoken | ||
|
||
import ( | ||
"encoding/base64" | ||
"fmt" | ||
"strings" | ||
|
||
uuid "github.com/hashicorp/go-uuid" | ||
"github.com/hashicorp/vault/sdk/helper/xor" | ||
) | ||
|
||
// DecodeToken will decode the root token returned by the Vault API | ||
// The algorithm was initially used in the generate root command | ||
func DecodeToken(encoded, otp string, otpLength int) (string, error) { | ||
switch otpLength { | ||
case 0: | ||
// Backwards compat | ||
tokenBytes, err := xor.XORBase64(encoded, otp) | ||
if err != nil { | ||
return "", fmt.Errorf("error xoring token: %s", err) | ||
} | ||
|
||
uuidToken, err := uuid.FormatUUID(tokenBytes) | ||
if err != nil { | ||
return "", fmt.Errorf("error formatting base64 token value: %s", err) | ||
} | ||
return strings.TrimSpace(uuidToken), nil | ||
default: | ||
tokenBytes, err := base64.RawStdEncoding.DecodeString(encoded) | ||
if err != nil { | ||
return "", fmt.Errorf("error decoding base64'd token: %v", err) | ||
} | ||
|
||
tokenBytes, err = xor.XORBytes(tokenBytes, []byte(otp)) | ||
if err != nil { | ||
return "", fmt.Errorf("error xoring token: %v", err) | ||
} | ||
return string(tokenBytes), nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package roottoken | ||
|
||
import ( | ||
"encoding/base64" | ||
"fmt" | ||
|
||
"github.com/hashicorp/vault/sdk/helper/xor" | ||
) | ||
|
||
// EncodeToken gets a token and an OTP and encodes the token. | ||
// The OTP must have the same length as the token. | ||
func EncodeToken(token, otp string) (string, error) { | ||
if len(token) == 0 { | ||
return "", fmt.Errorf("no token provided") | ||
} else if len(otp) == 0 { | ||
return "", fmt.Errorf("no otp provided") | ||
} | ||
|
||
// This function performs decoding checks so rather than decode the OTP, | ||
// just encode the value we're passing in. | ||
tokenBytes, err := xor.XORBytes([]byte(otp), []byte(token)) | ||
if err != nil { | ||
return "", fmt.Errorf("xor of root token failed: %w", err) | ||
} | ||
return base64.RawStdEncoding.EncodeToString(tokenBytes), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
package roottoken | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestTokenEncodingDecodingWithOTP(t *testing.T) { | ||
otpTestCases := []struct { | ||
token string | ||
name string | ||
otpLength int | ||
expectedEncodingErr string | ||
expectedDecodingErr string | ||
}{ | ||
{ | ||
token: "someToken", | ||
name: "test token encoding with base64", | ||
otpLength: 0, | ||
expectedEncodingErr: "xor of root token failed: length of byte slices is not equivalent: 24 != 9", | ||
expectedDecodingErr: "", | ||
}, | ||
{ | ||
token: "someToken", | ||
name: "test token encoding with base62", | ||
otpLength: len("someToken"), | ||
expectedEncodingErr: "", | ||
expectedDecodingErr: "", | ||
}, | ||
{ | ||
token: "someToken", | ||
name: "test token encoding with base62 - wrong otp length", | ||
otpLength: len("someToken") + 1, | ||
expectedEncodingErr: "xor of root token failed: length of byte slices is not equivalent: 10 != 9", | ||
expectedDecodingErr: "", | ||
}, | ||
{ | ||
token: "", | ||
name: "test no token to encode", | ||
otpLength: 0, | ||
expectedEncodingErr: "no token provided", | ||
expectedDecodingErr: "", | ||
}, | ||
} | ||
for _, otpTestCase := range otpTestCases { | ||
t.Run(otpTestCase.name, func(t *testing.T) { | ||
otp, err := GenerateOTP(otpTestCase.otpLength) | ||
if err != nil { | ||
t.Fatal(err.Error()) | ||
} | ||
encodedToken, err := EncodeToken(otpTestCase.token, otp) | ||
if err != nil || otpTestCase.expectedDecodingErr != "" { | ||
assert.EqualError(t, err, otpTestCase.expectedEncodingErr) | ||
return | ||
} | ||
assert.NotEqual(t, otp, encodedToken) | ||
assert.NotEqual(t, encodedToken, otpTestCase.token) | ||
decodedToken, err := DecodeToken(encodedToken, otp, len(otp)) | ||
if err != nil || otpTestCase.expectedDecodingErr != "" { | ||
assert.EqualError(t, err, otpTestCase.expectedDecodingErr) | ||
return | ||
} | ||
assert.Equal(t, otpTestCase.token, decodedToken) | ||
}) | ||
} | ||
} | ||
|
||
func TestTokenEncodingDecodingWithNoOTPorPGPKey(t *testing.T) { | ||
_, err := EncodeToken("", "") | ||
assert.EqualError(t, err, "no token provided") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package roottoken | ||
|
||
import ( | ||
"crypto/rand" | ||
"encoding/base64" | ||
"fmt" | ||
|
||
"github.com/hashicorp/go-secure-stdlib/base62" | ||
) | ||
|
||
// DefaultBase64EncodedOTPLength is the number of characters that will be randomly generated | ||
// before the Base64 encoding process takes place. | ||
const defaultBase64EncodedOTPLength = 16 | ||
|
||
// GenerateOTP generates a random token and encodes it as a Base64 or as a Base62 encoded string. | ||
// Returns 0 if the generation completed without any error, 2 otherwise, along with the error. | ||
func GenerateOTP(otpLength int) (string, error) { | ||
switch otpLength { | ||
case 0: | ||
// This is the fallback case | ||
buf := make([]byte, defaultBase64EncodedOTPLength) | ||
readLen, err := rand.Read(buf) | ||
if err != nil { | ||
return "", fmt.Errorf("error reading random bytes: %s", err) | ||
} | ||
|
||
if readLen != defaultBase64EncodedOTPLength { | ||
return "", fmt.Errorf("read %d bytes when we should have read 16", readLen) | ||
} | ||
|
||
return base64.StdEncoding.EncodeToString(buf), nil | ||
default: | ||
otp, err := base62.Random(otpLength) | ||
if err != nil { | ||
return "", fmt.Errorf("error reading random bytes: %w", err) | ||
} | ||
|
||
return otp, nil | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package roottoken | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestBase64OTPGeneration(t *testing.T) { | ||
token, err := GenerateOTP(0) | ||
assert.Len(t, token, 24) | ||
assert.Nil(t, err) | ||
} | ||
|
||
func TestBase62OTPGeneration(t *testing.T) { | ||
token, err := GenerateOTP(20) | ||
assert.Len(t, token, 20) | ||
assert.Nil(t, err) | ||
} |
File renamed without changes.
File renamed without changes.
Oops, something went wrong.