mirror of
https://github.com/muun/recovery.git
synced 2025-02-23 11:32:33 -05:00
158 lines
3.9 KiB
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 != ""
|
|
}
|