diff --git a/internal/qawemail/store.go b/internal/qawemail/store.go index 9073994..3ec5d70 100644 --- a/internal/qawemail/store.go +++ b/internal/qawemail/store.go @@ -163,7 +163,9 @@ func (s *Store) seal(secret string, revision int64) ([]byte, error) { if _, err = rand.Read(nonce); err != nil { return nil, ErrUnavailable } - return aead.Seal(nonce, nonce, []byte(secret), []byte(fmt.Sprintf("weekly-mail:%s:%d", s.tenant, revision))), nil + // The AAD must not follow the settings revision: rotating recipients would otherwise + // make a previously saved password undecryptable. + return aead.Seal(nonce, nonce, []byte(secret), []byte("weekly-mail:"+s.tenant)), nil } func (s *Store) unseal(sealed []byte, revision int64) (string, error) { @@ -182,11 +184,19 @@ func (s *Store) unseal(sealed []byte, revision int64) (string, error) { if err != nil || len(sealed) < aead.NonceSize() { return "", ErrUnavailable } - raw, err := aead.Open(nil, sealed[:aead.NonceSize()], sealed[aead.NonceSize():], []byte(fmt.Sprintf("weekly-mail:%s:%d", s.tenant, revision))) - if err != nil { - return "", ErrUnavailable + nonce, body := sealed[:aead.NonceSize()], sealed[aead.NonceSize():] + raw, err := aead.Open(nil, nonce, body, []byte("weekly-mail:"+s.tenant)) + if err == nil { + return string(raw), nil } - return string(raw), nil + // Legacy rows were sealed with the revision in the AAD; recover them without forcing + // the operator to paste the authorization code again. + for candidate := int64(1); candidate <= 8; candidate++ { + if plain, openErr := aead.Open(nil, nonce, body, []byte(fmt.Sprintf("weekly-mail:%s:%d", s.tenant, candidate))); openErr == nil { + return string(plain), nil + } + } + return "", ErrUnavailable } func validAddress(value string) bool {