muun-recovery/vendor/github.com/muun/libwallet/emergencykit/emergencykit.go

158 lines
3.9 KiB
Go

package emergencykit
import (
"bytes"
"crypto/sha256"
"fmt"
"strconv"
"text/template"
"time"
)
// Input struct to fill the PDF
type Input struct {
FirstEncryptedKey string
FirstFingerprint string
SecondEncryptedKey string
SecondFingerprint string
Version int
}
// Output with the html as string and the verification code
type Output struct {
HTML string
VerificationCode string
}
var spanishMonthNames = []string{
"Enero",
"Febrero",
"Marzo",
"Abril",
"Mayo",
"Junio",
"Julio",
"Agosto",
"Septiembre",
"Octubre",
"Noviembre",
"Diciembre",
}
// GenerateHTML returns the translated emergency kit html as a string along with the verification code.
func GenerateHTML(params *Input, lang string) (*Output, error) {
verificationCode := generateDeterministicCode(params)
// Render output descriptors:
var descriptors string
if params.hasFingerprints() {
descriptors = GetDescriptorsHTML(&DescriptorsData{
FirstFingerprint: params.FirstFingerprint,
SecondFingerprint: params.SecondFingerprint,
})
}
// Render page body:
content, err := render("EmergencyKitContent", lang, &contentData{
// Externally provided:
FirstEncryptedKey: params.FirstEncryptedKey,
SecondEncryptedKey: params.SecondEncryptedKey,
// Computed by us:
VerificationCode: verificationCode,
CurrentDate: formatDate(time.Now(), lang),
Descriptors: descriptors,
// Template pieces separated for reuse:
IconHelp: iconHelp,
IconPadlock: iconPadlock,
})
if err != nil {
return nil, fmt.Errorf("failed to render EmergencyKitContent template: %w", err)
}
// Render complete HTML page:
page, err := render("EmergencyKitPage", lang, &pageData{
Css: css,
Content: content,
})
if err != nil {
return nil, fmt.Errorf("failed to render EmergencyKitPage template: %w", err)
}
return &Output{
HTML: page,
VerificationCode: verificationCode,
}, nil
}
func formatDate(t time.Time, lang string) string {
if lang == "en" {
return t.Format("January 2, 2006")
} else {
// Golang has no i18n facilities, so we do our own formatting.
year, month, day := t.Date()
monthName := spanishMonthNames[month-1]
return fmt.Sprintf("%d de %s, %d", day, monthName, year)
}
}
func generateDeterministicCode(params *Input) string {
// NOTE:
// This function creates a stable verification code given the inputs to render the Emergency Kit. For now, the
// implementation relies exclusively on the SecondEncryptedKey, which is the Muun key. This is obviously not ideal,
// since we're both dropping part of the input and introducing the assumption that the Muun key will always be
// rendered second -- but it compensates for a problem with one of our clients that causes the user key serialization
// to be recreated each time the kit is rendered (making this deterministic approach useless).
// Create a deterministic serialization of the input:
inputMaterial := params.SecondEncryptedKey + strconv.Itoa(params.Version)
// Compute a cryptographically secure hash of the material (critical, these are keys):
inputHash := sha256.Sum256([]byte(inputMaterial))
// Extract a verification code from the hash (doesn't matter if we discard bytes):
var code string
for _, b := range inputHash[:6] {
code += strconv.Itoa(int(b) % 10)
}
return code
}
func render(name, language string, data interface{}) (string, error) {
tmpl, err := template.New(name).Parse(getContent(name, language))
if err != nil {
return "", err
}
var buf bytes.Buffer
err = tmpl.Execute(&buf, data)
if err != nil {
return "", err
}
return buf.String(), nil
}
func getContent(name string, language string) string {
switch name {
case "EmergencyKitPage":
return page
case "EmergencyKitContent":
if language == "es" {
return contentES
}
return contentEN
default:
panic("could not find template with name: " + name)
}
}
func (i *Input) hasFingerprints() bool {
return i.FirstFingerprint != "" && i.SecondFingerprint != ""
}