Skip to content
This repository was archived by the owner on Jul 21, 2024. It is now read-only.

Initial code for decoding OTPs #1

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
WIP
AlekSi committed Jun 30, 2021
commit 690731da4ef1a9bb1593ff71653d0c34040a015a
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
/.idea/
/.vscode/

/bin/

.env
.envrc

/bin/
*.json
62 changes: 53 additions & 9 deletions yubiotp/decode.go
Original file line number Diff line number Diff line change
@@ -5,36 +5,80 @@
package yubiotp

import (
"crypto/aes"
"encoding/hex"
"fmt"
"log"
"math/big"

"github.com/AlekSi/modhex"
)

type Info struct {
PublicID string
PublicIDBin []byte
PublicIDHex string
PublicIDDec *big.Int
PublicIDBin []byte
PrivateIDBin []byte

Rnd uint16
}

func (i *Info) PublicIDModHex() string {
return modhex.EncodeToString(i.PublicIDBin)
}

func (i *Info) PublicIDHex() string {
return hex.EncodeToString(i.PublicIDBin)
}

func (i *Info) PublicIDDec() *big.Int {
return new(big.Int).SetBytes(i.PublicIDBin)
}

func (i *Info) PrivateIDModHex() string {
return modhex.EncodeToString(i.PrivateIDBin)
}

func (i *Info) PrivateIDHex() string {
return hex.EncodeToString(i.PrivateIDBin)
}

func (i *Info) PrivateIDDec() *big.Int {
return new(big.Int).SetBytes(i.PrivateIDBin)
}

func Decode(otp string, secretKey string) (*Info, error) {
if len(otp) != 44 {
return nil, fmt.Errorf("not a 44-character long OTP")
}

b, err := modhex.DecodeString(otp)
otpB, err := modhex.DecodeString(otp)
if err != nil {
return nil, err
}

info := &Info{
PublicID: otp[:12],
PublicIDBin: b[:6],
PublicIDHex: hex.EncodeToString(b[:6]),
PublicIDDec: new(big.Int).SetBytes(b[:6]),
PublicIDBin: otpB[:6],
}

if secretKey == "" {
return info, nil
}

secretKeyB, err := hex.DecodeString(secretKey)
if err != nil {
return nil, err
}

c, err := aes.NewCipher(secretKeyB)
if err != nil {
return nil, err
}

d := make([]byte, c.BlockSize())
c.Decrypt(d, otpB[6:])

log.Print(hex.Dump(d))

info.PrivateIDBin = d[:6]

return info, nil
}
46 changes: 31 additions & 15 deletions yubiotp/decode_test.go
Original file line number Diff line number Diff line change
@@ -5,34 +5,50 @@
package yubiotp

import (
"math/big"
"encoding/json"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

type testdata struct {
OTP string
SecretKey string

PublicIDModHex string
PrivateIDHex string
}

func TestDecode(t *testing.T) {
t.Parallel()

otp := "vvfbhrhghngettteklnejthbvcehettgdcntrgddknvr"
secretKey := os.Getenv("TEST_YUBIKEY_DECODE_SECRET_KEY")
if secretKey == "" {
t.Skip("TEST_YUBIKEY_DECODE_SECRET_KEY is not set, skipping.")
pattern := filepath.Join("testdata", "*.json")
files, err := filepath.Glob(pattern)
require.NoError(t, err)

if len(files) == 0 {
t.Skipf("no files matching %s, skipping", pattern)
}

actual, err := Decode(otp, secretKey)
require.NoError(t, err)
for _, file := range files {
file := file
t.Run(file, func(t *testing.T) {
t.Parallel()

b, err := os.ReadFile(file)
require.NoError(t, err)

publicIDDec, ok := new(big.Int).SetString("280656456543059", 10)
require.True(t, ok)
var expected testdata
err = json.Unmarshal(b, &expected)
require.NoError(t, err)

expected := &Info{
PublicID: "vvfbhrhghnge",
PublicIDBin: []byte{0xff, 0x41, 0x6c, 0x65, 0x6b, 0x53},
PublicIDHex: "ff416c656b53",
PublicIDDec: publicIDDec,
actual, err := Decode(expected.OTP, expected.SecretKey)
require.NoError(t, err)
assert.Equal(t, expected.PublicIDModHex, actual.PublicIDModHex())
assert.Equal(t, expected.PrivateIDHex, actual.PrivateIDHex())
})
}
assert.Equal(t, expected, actual)
}
6 changes: 6 additions & 0 deletions yubiotp/testdata/decode.json.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"OTP": "vvbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"SecretKey": "00000000000000000000000000000000",
"PublicIDModHex": "vvbbbbbbbbbb",
"PrivateIDHex": "000000000000"
}