Skip to content
Snippets Groups Projects
Commit ea481352 authored by ale's avatar ale
Browse files

Initialize storage encryption on password change

parent 5c80803b
No related branches found
No related tags found
No related merge requests found
...@@ -33,6 +33,7 @@ type Backend interface { ...@@ -33,6 +33,7 @@ type Backend interface {
SetResourcePassword(context.Context, string, *Resource, string) error SetResourcePassword(context.Context, string, *Resource, string) error
GetUserEncryptionKeys(context.Context, *User) ([]*UserEncryptionKey, error) GetUserEncryptionKeys(context.Context, *User) ([]*UserEncryptionKey, error)
SetUserEncryptionKeys(context.Context, *User, []*UserEncryptionKey) error SetUserEncryptionKeys(context.Context, *User, []*UserEncryptionKey) error
SetUserEncryptionPublicKey(context.Context, *User, []byte) error
SetApplicationSpecificPassword(context.Context, *User, *AppSpecificPasswordInfo, string) error SetApplicationSpecificPassword(context.Context, *User, *AppSpecificPasswordInfo, string) error
DeleteApplicationSpecificPassword(context.Context, *User, string) error DeleteApplicationSpecificPassword(context.Context, *User, string) error
} }
...@@ -171,12 +172,18 @@ func (s *AccountService) ChangeUserPassword(ctx context.Context, req *ChangeUser ...@@ -171,12 +172,18 @@ func (s *AccountService) ChangeUserPassword(ctx context.Context, req *ChangeUser
return err return err
} }
if err := req.Validate(); err != nil { if err = req.Validate(); err != nil {
return newRequestError(err) return newRequestError(err)
} }
if err := s.updateUserEncryptionKeys(ctx, user, req.CurPassword, req.Password, UserEncryptionKeyMainID); err != nil { // If the user does not yet have an encryption key, generate one now.
return newBackendError(err) 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. // Set the encrypted password attribute on the user and email resources.
...@@ -193,8 +200,43 @@ func (s *AccountService) ChangeUserPassword(ctx context.Context, req *ChangeUser ...@@ -193,8 +200,43 @@ func (s *AccountService) ChangeUserPassword(ctx context.Context, req *ChangeUser
return nil 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 { 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) keys, err := s.backend.GetUserEncryptionKeys(ctx, user)
if err != nil { if err != nil {
return newBackendError(err) return newBackendError(err)
...@@ -288,10 +330,12 @@ func (s *AccountService) CreateApplicationSpecificPassword(ctx context.Context, ...@@ -288,10 +330,12 @@ func (s *AccountService) CreateApplicationSpecificPassword(ctx context.Context,
// Create or update the user encryption key associated with // Create or update the user encryption key associated with
// this password. The user encryption key IDs for ASPs all // this password. The user encryption key IDs for ASPs all
// have an 'asp_' prefix, followed by the ASP ID. // have an 'asp_' prefix, followed by the ASP ID.
if user.HasEncryptionKeys {
keyID := "asp_" + asp.ID keyID := "asp_" + asp.ID
if err := s.updateUserEncryptionKeys(ctx, user, req.CurPassword, password, keyID); err != nil { if err := s.updateUserEncryptionKeys(ctx, user, req.CurPassword, password, keyID); err != nil {
return nil, err return nil, err
} }
}
return &CreateApplicationSpecificPasswordResponse{ return &CreateApplicationSpecificPasswordResponse{
Password: password, Password: password,
......
...@@ -45,6 +45,10 @@ func (b *fakeBackend) SetUserEncryptionKeys(_ context.Context, user *User, keys ...@@ -45,6 +45,10 @@ func (b *fakeBackend) SetUserEncryptionKeys(_ context.Context, user *User, keys
return nil 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 { func (b *fakeBackend) SetApplicationSpecificPassword(_ context.Context, user *User, info *AppSpecificPasswordInfo, _ string) error {
b.appSpecificPasswords[user.Name] = append(b.appSpecificPasswords[user.Name], info) b.appSpecificPasswords[user.Name] = append(b.appSpecificPasswords[user.Name], info)
return nil return nil
...@@ -90,6 +94,10 @@ func createFakeBackend() *fakeBackend { ...@@ -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 return fb
} }
...@@ -159,7 +167,7 @@ func TestService_ChangePassword(t *testing.T) { ...@@ -159,7 +167,7 @@ func TestService_ChangePassword(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if fb.passwords["testuser"] != "password" { if _, ok := fb.passwords["testuser"]; !ok {
t.Fatalf("password was not set on the backend") t.Fatal("password was not set on the backend")
} }
} }
...@@ -336,6 +336,7 @@ func newUser(entry *ldap.Entry) (*accountserver.User, error) { ...@@ -336,6 +336,7 @@ func newUser(entry *ldap.Entry) (*accountserver.User, error) {
Name: entry.GetAttributeValue("uid"), Name: entry.GetAttributeValue("uid"),
Lang: entry.GetAttributeValue("preferredLanguage"), Lang: entry.GetAttributeValue("preferredLanguage"),
Has2FA: (entry.GetAttributeValue("totpSecret") != ""), Has2FA: (entry.GetAttributeValue("totpSecret") != ""),
HasEncryptionKeys: (len(entry.GetAttributeValues("storageEncryptionKey")) > 0),
//PasswordRecoveryHint: entry.GetAttributeValue("recoverQuestion"), //PasswordRecoveryHint: entry.GetAttributeValue("recoverQuestion"),
} }
if user.Lang == "" { if user.Lang == "" {
...@@ -459,6 +460,16 @@ func (b *LDAPBackend) SetUserEncryptionKeys(ctx context.Context, user *accountse ...@@ -459,6 +460,16 @@ func (b *LDAPBackend) SetUserEncryptionKeys(ctx context.Context, user *accountse
return b.conn.Modify(ctx, mod) 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 { func (b *LDAPBackend) SetApplicationSpecificPassword(ctx context.Context, user *accountserver.User, info *accountserver.AppSpecificPasswordInfo, encryptedPassword string) error {
emailRsrc := user.GetSingleResourceByType(accountserver.ResourceTypeEmail) emailRsrc := user.GetSingleResourceByType(accountserver.ResourceTypeEmail)
if emailRsrc == nil { 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 @@ ...@@ -39,10 +39,10 @@
"revisionTime": "2018-02-18T15:46:43Z" "revisionTime": "2018-02-18T15:46:43Z"
}, },
{ {
"checksumSHA1": "hNMdjPPJ99kTEVdtM8oQvx/FoWY=", "checksumSHA1": "rPaBG1IesiEZi/dPae7wvsWtYdc=",
"path": "git.autistici.org/id/keystore/userenckey", "path": "git.autistici.org/id/keystore/userenckey",
"revision": "128c384b1908f52c7425367d8c9579d50c639c77", "revision": "f086b1b88dfbd16a2a4f68239d08f1195f4d9c72",
"revisionTime": "2018-02-16T21:12:09Z" "revisionTime": "2018-04-02T09:08:18Z"
}, },
{ {
"checksumSHA1": "yReqUM4tQkY+1YEI+L2d0SOzFWs=", "checksumSHA1": "yReqUM4tQkY+1YEI+L2d0SOzFWs=",
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment