mirror of
https://github.com/passbolt/go-passbolt-cli.git
synced 2025-05-11 02:28:22 +00:00
Implement Custom TOTP URL Generation
This commit is contained in:
parent
869f33cfa3
commit
1229ad7b59
1 changed files with 52 additions and 37 deletions
|
@ -4,13 +4,15 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/passbolt/go-passbolt-cli/util"
|
"github.com/passbolt/go-passbolt-cli/util"
|
||||||
"github.com/passbolt/go-passbolt/api"
|
"github.com/passbolt/go-passbolt/api"
|
||||||
"github.com/passbolt/go-passbolt/helper"
|
"github.com/passbolt/go-passbolt/helper"
|
||||||
"github.com/pquerna/otp"
|
|
||||||
"github.com/pquerna/otp/totp"
|
|
||||||
"github.com/pterm/pterm"
|
"github.com/pterm/pterm"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/tobischo/gokeepasslib/v3"
|
"github.com/tobischo/gokeepasslib/v3"
|
||||||
|
@ -92,7 +94,7 @@ func KeepassExport(cmd *cobra.Command, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, resource := range resources {
|
for _, resource := range resources {
|
||||||
entry, err := GetKeepassEntry(client, resource, resource.Secrets[0], resource.ResourceType)
|
entry, err := getKeepassEntry(client, resource, resource.Secrets[0], resource.ResourceType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Printf("\nSkipping Export of Resource %v %v Because of: %v\n", resource.ID, resource.Name, err)
|
fmt.Printf("\nSkipping Export of Resource %v %v Because of: %v\n", resource.ID, resource.Name, err)
|
||||||
progressbar.Increment()
|
progressbar.Increment()
|
||||||
|
@ -127,7 +129,7 @@ func KeepassExport(cmd *cobra.Command, args []string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetKeepassEntry(client *api.Client, resource api.Resource, secret api.Secret, rType api.ResourceType) (*gokeepasslib.Entry, error) {
|
func getKeepassEntry(client *api.Client, resource api.Resource, secret api.Secret, rType api.ResourceType) (*gokeepasslib.Entry, error) {
|
||||||
_, _, _, _, pass, desc, err := helper.GetResourceFromData(client, resource, resource.Secrets[0], resource.ResourceType)
|
_, _, _, _, pass, desc, err := helper.GetResourceFromData(client, resource, resource.Secrets[0], resource.ResourceType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("Get Resource %v: %w", resource.ID, err)
|
return nil, fmt.Errorf("Get Resource %v: %w", resource.ID, err)
|
||||||
|
@ -151,8 +153,6 @@ func GetKeepassEntry(client *api.Client, resource api.Resource, secret api.Secre
|
||||||
return nil, fmt.Errorf("Decrypting Secret Data: %w", err)
|
return nil, fmt.Errorf("Decrypting Secret Data: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\nRaw Secret %v: %v\n", resource.ResourceType.Slug, rawSecretData)
|
|
||||||
|
|
||||||
if resource.ResourceType.Slug == "password-description-totp" {
|
if resource.ResourceType.Slug == "password-description-totp" {
|
||||||
var secretData api.SecretDataTypePasswordDescriptionTOTP
|
var secretData api.SecretDataTypePasswordDescriptionTOTP
|
||||||
err = json.Unmarshal([]byte(rawSecretData), &secretData)
|
err = json.Unmarshal([]byte(rawSecretData), &secretData)
|
||||||
|
@ -169,46 +169,61 @@ func GetKeepassEntry(client *api.Client, resource api.Resource, secret api.Secre
|
||||||
totpData = secretData.TOTP
|
totpData = secretData.TOTP
|
||||||
}
|
}
|
||||||
|
|
||||||
var alg otp.Algorithm
|
v := url.Values{}
|
||||||
|
v.Set("secret", totpData.SecretKey)
|
||||||
switch totpData.Algorithm {
|
v.Set("period", strconv.FormatUint(uint64(totpData.Period), 10))
|
||||||
case "SHA1":
|
v.Set("algorithm", totpData.Algorithm)
|
||||||
alg = otp.AlgorithmSHA1
|
v.Set("digits", fmt.Sprint(totpData.Digits))
|
||||||
case "SHA256":
|
|
||||||
alg = otp.AlgorithmSHA256
|
|
||||||
case "SHA512":
|
|
||||||
alg = otp.AlgorithmSHA512
|
|
||||||
case "MD5":
|
|
||||||
alg = otp.AlgorithmMD5
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("Unsupported TOTP Algorithm: %v ", totpData.Algorithm)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := totp.GenerateOpts{
|
|
||||||
Issuer: resource.URI,
|
|
||||||
AccountName: resource.Username,
|
|
||||||
Secret: []byte(totpData.SecretKey),
|
|
||||||
Algorithm: alg,
|
|
||||||
Period: uint(totpData.Period),
|
|
||||||
Digits: otp.Digits(totpData.Digits),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
issuer := resource.URI
|
||||||
if resource.URI == "" {
|
if resource.URI == "" {
|
||||||
opts.Issuer = resource.Name
|
issuer = resource.Name
|
||||||
|
|
||||||
}
|
}
|
||||||
|
v.Set("issuer", issuer)
|
||||||
|
|
||||||
|
accountName := resource.Username
|
||||||
if resource.Username == "" {
|
if resource.Username == "" {
|
||||||
opts.AccountName = resource.Name
|
accountName = resource.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
totpKey, err := totp.Generate(opts)
|
u := url.URL{
|
||||||
if err != nil {
|
Scheme: "otpauth",
|
||||||
return nil, fmt.Errorf("Generating TOTP Key: %w", err)
|
Host: "totp",
|
||||||
|
Path: "/" + issuer + ":" + accountName,
|
||||||
|
RawQuery: encodeQuery(v),
|
||||||
}
|
}
|
||||||
|
|
||||||
totpKey.Secret()
|
entry.Values = append(entry.Values, gokeepasslib.ValueData{Key: "otp", Value: gokeepasslib.V{Content: u.String(), Protected: w.NewBoolWrapper(true)}})
|
||||||
|
|
||||||
entry.Values = append(entry.Values, gokeepasslib.ValueData{Key: "otp", Value: gokeepasslib.V{Content: totpKey.URL(), Protected: w.NewBoolWrapper(true)}})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return &entry, nil
|
return &entry, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EncodeQuery is a copy-paste of url.Values.Encode, except it uses %20 instead
|
||||||
|
// of + to encode spaces. This is necessary to correctly render spaces in some
|
||||||
|
// authenticator apps, like Google Authenticator.
|
||||||
|
func encodeQuery(v url.Values) string {
|
||||||
|
if v == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
var buf strings.Builder
|
||||||
|
keys := make([]string, 0, len(v))
|
||||||
|
for k := range v {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
sort.Strings(keys)
|
||||||
|
for _, k := range keys {
|
||||||
|
vs := v[k]
|
||||||
|
keyEscaped := url.PathEscape(k) // changed from url.QueryEscape
|
||||||
|
for _, v := range vs {
|
||||||
|
if buf.Len() > 0 {
|
||||||
|
buf.WriteByte('&')
|
||||||
|
}
|
||||||
|
buf.WriteString(keyEscaped)
|
||||||
|
buf.WriteByte('=')
|
||||||
|
buf.WriteString(url.PathEscape(v)) // changed from url.QueryEscape
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buf.String()
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue