frostfs-mfa/mfa/pack_test.go
Alex Vanin 9462aea03d [#1] Add initial implementation of MFA library
Signed-off-by: Alex Vanin <a.vanin@yadro.com>
2025-03-13 13:40:57 +03:00

105 lines
4.7 KiB
Go

package mfa
import (
"encoding/base64"
"encoding/hex"
"testing"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/pquerna/otp/totp"
"github.com/stretchr/testify/require"
"google.golang.org/protobuf/proto"
)
func TestPackUnpackBox(t *testing.T) {
unlockerKeys := generateKeys(t, 3)
unlockerPublicKeys := make([]*keys.PublicKey, len(unlockerKeys))
for i, key := range unlockerKeys {
unlockerPublicKeys[i] = key.PublicKey()
}
otpKey, err := totp.Generate(totp.GenerateOpts{
Issuer: "iam-" + hex.EncodeToString(unlockerPublicKeys[0].Bytes()),
AccountName: "NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM (devenv)",
})
require.NoError(t, err)
box, err := PackMFABox(otpKey, unlockerPublicKeys)
require.NoError(t, err)
// make sure secrets are encrypted
secrets := new(Secrets)
err = proto.Unmarshal(box.GetEncryptedSecrets(), secrets)
require.Error(t, err)
// make sure MFA secret encryption key is encrypted
_, err = decryptData(box.EncryptedSecrets, box.Unlockers[0].EncryptedSecretsKey, box.Salt)
require.Error(t, err)
for _, unlockerKey := range unlockerKeys {
otpKeyFromBox, err := UnpackMFABox(box, unlockerKey)
require.NoError(t, err)
require.Equal(t, otpKey.URL(), otpKeyFromBox.URL())
}
randomKey := generateKeys(t, 1)
_, err = UnpackMFABox(box, randomKey[0])
require.Error(t, err)
}
func TestProprietaryCompatibility(t *testing.T) {
for _, tc := range []struct {
binary string // base64 encoded MFA Box
otpURL string // secret
unlockerPrivateKeys []string // hex-encoded private keys
}{
{
// this is MFA box created with proprietary code with HKDF salt
binary: "Cn8KIQNSH3wIalCMX35hjaEV9sYCJYJ9QC1EFy1eTH/ZaTdaIxJIl6m1UcOuwmtkkcwIqLbX+DNiIvzkGw29YiWbyycvWh048nf4phBKXkzIMy7GXzKJz1n3BWV7q9QPezpLkjeU1Nn2u1czyXrnGhCJ7VGazNvBg50zsokWEXXWCn8KIQK/IVlNTlmdqA0+XxuUR5KjRvEOYVmyO9JjUMSGaFb0ERJIlmYTw2HcLvscJCLiLLGBh+lXRaFUOqCYgKwwT5G352cYJnMejeQ6QlIlzygHTn9N+RHvh4Hmy5Pt1SUik6hVvfil/9LsX8T0GhDHh7f4v3mjmSgU4T4mjX7lCn8KIQLnAI+bca1SzSSPtsp9n6k2+uC4pVPw4rY0eYKUu6fJ4hJI5Pb7qVzFMDQcTLxLrA0vIQ4DEnwGt78QMdZaHM4FqosIMO0C6TLoTUOyXSgK/scF785JEH0freFiOh+fR5OqQNCrLHMjzoL4GhA2xmq4WmFuRaToK9pm4B0YEiEDWC0ggRO0nfUoYBDO58amY6Y13Zs4h05gjLXIp23y8r4avwEoZqY9aG/sHlso1pRtMtSUJdFGpsONIQvhG3nnJHEy1vbDiVzmThK9Onn2huX5vGJ4iKZX8sj2pd9/yo+twqKA9osNQDlVnJikd5wapnPWKKxjAN22pIQC3TJV0AXJdtklg39YO9H0cz5r2ZDFTWOlCZdOnBxoDW3CyxUX+lQMA5GJ/v+zVPQpCu8no8z1NdLNbiIHI6Gn6e0hfSENMeizH3QvKLLaAqR7VUK3dHNAhyHquIe05xt28RImSnZewCIQf3et/IfqVLm8QU9kcfGIFA==",
otpURL: "otpauth://totp/iam-test:NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM?algorithm=SHA1&digits=6&issuer=iam-test&period=30&secret=2EIZ6JTGWBZHYEMTFQGTCWLUIGHMNU2S",
unlockerPrivateKeys: []string{
"b4e6b0cfd844f454a1cd0c726455356aa2120743d112805cfb05a67202b6a7a4",
"679f5eeab1a885ae7c8c2992149145265f46458cacc8191f6b6902aa7e7efdc8",
"e3ac3501472ab144dfc99d61b69e3fccc6ffa63519a728307624e9aa1e99350d",
},
},
{
// this is MFA box created with proprietary code without HKDF salt
binary: "Cm0KIQPjDLGDyyM2KTMx7JxNGYCcY+8V74wgFehEc10gxPde4RJIGWiM6mxo9q0EF8rp/pdFULNy9O+BQc89NIess3z5nSmWFFZxx7j/yKF22hRxLQYenxciHBC0PhbNGXoHG7H4q1a9rhlftcdbCm0KIQOxT6G7TamFLQnktNXXkPrVEXgO4w6yEJ7SQScsXc30EBJI7PSxnqIZmyufYiYkhmwhtP9OSziIA2mFbSz+Wwp45XLKkLYVsT+1Bvj2fKLPZdb5LIPielfRXZpoCrHo8jSDigFae5zxBfsSCm0KIQPEdTuOmmavHwgjPCCgh2ldCTNpubMNzcUc7zakNTDB9hJIl25dRwdxqdV1secWXnhlX6QGWOy8jIswE3MFP0mHhE5yy9jctpIhQKctOCtdsDdMZ081T72omxEc8wRdOK83/cE54P8G7cjPEiECRnc7bPPWVRZtcd5xc4Vvu4LXak16RwA09x4oKsYMB/MavwELRmMpyMwoFgixJrCdFg7PFML4SEb9LE2sj361psAS4kslwIDWMWMPJr110dtzMIL2wOl9AlfpHqHd6CRDhNIv5zOUTyvLSiFZbhC9l8mjEd672uBMlvkSZALOGTnsIigp31a1nDlbyuo9YuGddYGHVyg0tpN3RtHnklL/Bp3rdVGbjPqdo6stp8993syJ1+8mxAE0SvnN/AkuwMODlAHbtieoENVxZWHTtPX0FuFbv9v69I5Sm30RkuKqruPFlw==",
otpURL: "otpauth://totp/iam-test:NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM?algorithm=SHA1&digits=6&issuer=iam-test&period=30&secret=2EIZ6JTGWBZHYEMTFQGTCWLUIGHMNU2S",
unlockerPrivateKeys: []string{
"17cbdf19c6ebb8c8e7753c489ee7851bf6fe9d157f5dc382bd55a84547cfe050",
"a7b0d0e3e88f2dafa73606d39cadfcf18d1a91d606db70e95815f15b9bd34ea6",
"b7f93214f7897a863cd9e0bdc3dfb1d7bc1fa49672d5e930d7682b22e1341076",
},
},
} {
boxData, err := base64.StdEncoding.DecodeString(tc.binary)
require.NoError(t, err)
var box = new(MFABox)
require.NoError(t, box.Unmarshal(boxData))
for _, keyStr := range tc.unlockerPrivateKeys {
key, err := keys.NewPrivateKeyFromHex(keyStr)
require.NoError(t, err)
otpKey, err := UnpackMFABox(box, key)
require.NoError(t, err)
require.Equal(t, tc.otpURL, otpKey.URL())
}
}
}
func generateKeys(t *testing.T, n int) []*keys.PrivateKey {
var err error
res := make([]*keys.PrivateKey, n)
for i := 0; i < n; i++ {
res[i], err = keys.NewPrivateKey()
require.NoError(t, err)
}
return res
}