Commit ea481352 authored by ale's avatar ale

Initialize storage encryption on password change

parent 5c80803b
......@@ -33,6 +33,7 @@ type Backend interface {
SetResourcePassword(context.Context, string, *Resource, string) error
GetUserEncryptionKeys(context.Context, *User) ([]*UserEncryptionKey, error)
SetUserEncryptionKeys(context.Context, *User, []*UserEncryptionKey) error
SetUserEncryptionPublicKey(context.Context, *User, []byte) error
SetApplicationSpecificPassword(context.Context, *User, *AppSpecificPasswordInfo, string) error
DeleteApplicationSpecificPassword(context.Context, *User, string) error
}
......@@ -171,12 +172,18 @@ func (s *AccountService) ChangeUserPassword(ctx context.Context, req *ChangeUser
return err
}
if err := req.Validate(); err != nil {
if err = req.Validate(); err != nil {
return newRequestError(err)
}
if err := s.updateUserEncryptionKeys(ctx, user, req.CurPassword, req.Password, UserEncryptionKeyMainID); err != nil {
return newBackendError(err)
// If the user does not yet have an encryption key, generate one now.
if !user.HasEncryptionKeys {
err = s.initializeUserEncryptionKeys(ctx, user, req.CurPassword)
} else {
err = s.updateUserEncryptionKeys(ctx, user, req.CurPassword, req.Password, UserEncryptionKeyMainID)
}
if err != nil {
return err
}
// Set the encrypted password attribute on the user and email resources.
......@@ -193,8 +200,43 @@ func (s *AccountService) ChangeUserPassword(ctx context.Context, req *ChangeUser
return nil
}
// Initialize the user encryption key list, by creating a new "main" key
// encrypted with the given password (which must be the primary password for the
// user).
func (s *AccountService) initializeUserEncryptionKeys(ctx context.Context, user *User, curPassword string) error {
// Generate a new key pair.
pub, priv, err := userenckey.GenerateKey()
if err != nil {
return err
}
// Encrypt the private key with the password.
enc, err := userenckey.Encrypt(priv, []byte(curPassword))
if err != nil {
return err
}
keys := []*UserEncryptionKey{
&UserEncryptionKey{
ID: UserEncryptionKeyMainID,
Key: enc,
},
}
// Update the backend database.
if err := s.backend.SetUserEncryptionKeys(ctx, user, keys); err != nil {
return newBackendError(err)
}
if err := s.backend.SetUserEncryptionPublicKey(ctx, user, pub); err != nil {
return newBackendError(err)
}
return nil
}
// Re-encrypt the specified user encryption key with newPassword. For this
// operation to succeed, we must be able to decrypt one of the keys (not
// necessarily the same one) with curPassword.
func (s *AccountService) updateUserEncryptionKeys(ctx context.Context, user *User, curPassword, newPassword, keyID string) error {
// Re-encrypt the user encryption key with the new password.
keys, err := s.backend.GetUserEncryptionKeys(ctx, user)
if err != nil {
return newBackendError(err)
......@@ -288,9 +330,11 @@ func (s *AccountService) CreateApplicationSpecificPassword(ctx context.Context,
// Create or update the user encryption key associated with
// this password. The user encryption key IDs for ASPs all
// have an 'asp_' prefix, followed by the ASP ID.
keyID := "asp_" + asp.ID
if err := s.updateUserEncryptionKeys(ctx, user, req.CurPassword, password, keyID); err != nil {
return nil, err
if user.HasEncryptionKeys {
keyID := "asp_" + asp.ID
if err := s.updateUserEncryptionKeys(ctx, user, req.CurPassword, password, keyID); err != nil {
return nil, err
}
}
return &CreateApplicationSpecificPasswordResponse{
......
......@@ -45,6 +45,10 @@ func (b *fakeBackend) SetUserEncryptionKeys(_ context.Context, user *User, keys
return nil
}
func (b *fakeBackend) SetUserEncryptionPublicKey(_ context.Context, user *User, pub []byte) error {
return nil
}
func (b *fakeBackend) SetApplicationSpecificPassword(_ context.Context, user *User, info *AppSpecificPasswordInfo, _ string) error {
b.appSpecificPasswords[user.Name] = append(b.appSpecificPasswords[user.Name], info)
return nil
......@@ -90,6 +94,10 @@ func createFakeBackend() *fakeBackend {
},
},
},
resources: make(map[string]map[string]*Resource),
passwords: make(map[string]string),
appSpecificPasswords: make(map[string][]*AppSpecificPasswordInfo),
encryptionKeys: make(map[string][]*UserEncryptionKey),
}
return fb
}
......@@ -159,7 +167,7 @@ func TestService_ChangePassword(t *testing.T) {
t.Fatal(err)
}
if fb.passwords["testuser"] != "password" {
t.Fatalf("password was not set on the backend")
if _, ok := fb.passwords["testuser"]; !ok {
t.Fatal("password was not set on the backend")
}
}
......@@ -333,9 +333,10 @@ type ldapUserData struct {
func newUser(entry *ldap.Entry) (*accountserver.User, error) {
user := &accountserver.User{
Name: entry.GetAttributeValue("uid"),
Lang: entry.GetAttributeValue("preferredLanguage"),
Has2FA: (entry.GetAttributeValue("totpSecret") != ""),
Name: entry.GetAttributeValue("uid"),
Lang: entry.GetAttributeValue("preferredLanguage"),
Has2FA: (entry.GetAttributeValue("totpSecret") != ""),
HasEncryptionKeys: (len(entry.GetAttributeValues("storageEncryptionKey")) > 0),
//PasswordRecoveryHint: entry.GetAttributeValue("recoverQuestion"),
}
if user.Lang == "" {
......@@ -459,6 +460,16 @@ func (b *LDAPBackend) SetUserEncryptionKeys(ctx context.Context, user *accountse
return b.conn.Modify(ctx, mod)
}
func (b *LDAPBackend) SetUserEncryptionPublicKey(ctx context.Context, user *accountserver.User, pub []byte) error {
mod := ldap.NewModifyRequest(getUserDN(user))
if user.HasEncryptionKeys {
mod.Replace("storageEncryptionPublicKey", []string{string(pub)})
} else {
mod.Add("storageEncryptionPublicKey", []string{string(pub)})
}
return b.conn.Modify(ctx, mod)
}
func (b *LDAPBackend) SetApplicationSpecificPassword(ctx context.Context, user *accountserver.User, info *accountserver.AppSpecificPasswordInfo, encryptedPassword string) error {
emailRsrc := user.GetSingleResourceByType(accountserver.ResourceTypeEmail)
if emailRsrc == nil {
......
package userenckey
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/x509"
"encoding/pem"
)
func encodePublicKeyToPEM(pub *ecdsa.PublicKey) ([]byte, error) {
der, err := x509.MarshalPKIXPublicKey(pub)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der}), nil
}
// GenerateKey generates a new ECDSA key pair, and returns the
// PEM-encoded public and private key (in order).
func GenerateKey() ([]byte, []byte, error) {
pkey, err := ecdsa.GenerateKey(elliptic.P224(), rand.Reader)
if err != nil {
return nil, nil, err
}
privBytes, err := encodePrivateKeyToPEM(pkey)
if err != nil {
return nil, nil, err
}
pubBytes, err := encodePublicKeyToPEM(&pkey.PublicKey)
if err != nil {
return nil, nil, err
}
return pubBytes, privBytes, nil
}
// +build go1.10
package userenckey
import (
"crypto/ecdsa"
"encoding/pem"
)
// Encode a private key to PEM-encoded PKCS8.
func encodePrivateKeyToPEM(priv *ecdsa.PrivateKey) ([]byte, error) {
der, err := x509.MarshalPKCS8PrivateKey(priv)
if err != nil {
return nil, err
}
return pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: der}), nil
}
// +build !go1.10
package userenckey
import (
"bytes"
"crypto/ecdsa"
"crypto/x509"
"encoding/pem"
"os/exec"
)
// Encode a private key to PEM-encoded PKCS8.
//
// In Go versions prior to 1.10, we must shell out to openssl to
// convert the private key to PKCS8 format.
func encodePrivateKeyToPEM(priv *ecdsa.PrivateKey) ([]byte, error) {
der, err := x509.MarshalECPrivateKey(priv)
if err != nil {
return nil, err
}
pkcs1 := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: der})
cmd := exec.Command("/usr/bin/openssl", "pkey")
cmd.Stdin = bytes.NewReader(pkcs1)
return cmd.Output()
}
......@@ -39,10 +39,10 @@
"revisionTime": "2018-02-18T15:46:43Z"
},
{
"checksumSHA1": "hNMdjPPJ99kTEVdtM8oQvx/FoWY=",
"checksumSHA1": "rPaBG1IesiEZi/dPae7wvsWtYdc=",
"path": "git.autistici.org/id/keystore/userenckey",
"revision": "128c384b1908f52c7425367d8c9579d50c639c77",
"revisionTime": "2018-02-16T21:12:09Z"
"revision": "f086b1b88dfbd16a2a4f68239d08f1195f4d9c72",
"revisionTime": "2018-04-02T09:08:18Z"
},
{
"checksumSHA1": "yReqUM4tQkY+1YEI+L2d0SOzFWs=",
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment