blob: 5474ee36c72ca98fd7300cede8093a52892127cb [file] [log] [blame] [edit]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package session
import (
"context"
"crypto"
"crypto/rsa"
"crypto/sha1"
"crypto/x509"
"io/ioutil"
"os"
"path/filepath"
"time"
"chromiumos/tast/common/testexec"
"chromiumos/tast/errors"
"chromiumos/tast/local/bundles/cros/session/cmp"
"chromiumos/tast/local/cryptohome"
"chromiumos/tast/local/session"
"chromiumos/tast/local/session/ownership"
"chromiumos/tast/local/upstart"
"chromiumos/tast/testing"
)
func init() {
testing.AddTest(&testing.Test{
Func: OwnershipAPI,
Desc: "Verifies that Ownership API works for a local device owner",
Contacts: []string{
"mnissler@chromium.org", // session_manager owner
"hidehiko@chromium.org", // Tast port author
},
Data: []string{"testcert.p12"},
Attr: []string{"group:mainline", "informational"},
})
}
// deviceSetUp clears the ownership data of the DUT, then recreates the
// ownership data.
func deviceSetUp(ctx context.Context, user, pass, p12Path string, key *rsa.PublicKey) (err error) {
const uiSetupTimeout = 90 * time.Second
testing.ContextLog(ctx, "Restarting ui job")
sctx, cancel := context.WithTimeout(ctx, uiSetupTimeout)
defer cancel()
if err = upstart.StopJob(sctx, "ui"); err != nil {
return err
}
defer func() {
// In case of error, run EnsureJobRunning with the original
// context to recover the job for the following tests.
if err == nil {
return
}
// Ignore error.
upstart.EnsureJobRunning(ctx, "ui")
}()
if err = session.ClearDeviceOwnership(sctx); err != nil {
return err
}
if err = cryptohome.CreateVault(sctx, user, pass); err != nil {
return err
}
if err = createOwnerKey(sctx, user, p12Path, key); err != nil {
return err
}
err = upstart.EnsureJobRunning(sctx, "ui")
return
}
// createOwnerKey creates ownership data of the DUT. Specifically, this
// pushes PKCS #12 certification data into NSS database, and creates
// /var/lib/devicesettings/owner.key file.
func createOwnerKey(ctx context.Context, user, p12Path string, pubkey *rsa.PublicKey) error {
testing.ContextLog(ctx, "Creating owner.key file")
// Convert pubkey to PKIX DER form first, so that in case of error
// happens, it won't be pushed to NSS.
der, err := x509.MarshalPKIXPublicKey(pubkey)
if err != nil {
return errors.Wrap(err, "RSA public key cannot be marshaled into PKIX")
}
// Install p12 data into NSS database.
if err = pushToNSS(ctx, user, p12Path); err != nil {
return err
}
if err = setupOwnerKey(der); err != nil {
return err
}
return nil
}
// pushToNSS installs PKCS #12 certification data into nssdb for the given user.
func pushToNSS(ctx context.Context, user, p12Path string) error {
upath, err := cryptohome.UserPath(ctx, user)
if err != nil {
return err
}
nssdb := filepath.Join(upath, ".pki", "nssdb")
cmd := testexec.CommandContext(ctx, "pk12util", "-d", "sql:"+nssdb, "-i", p12Path, "-W", "" /* password */)
if err := cmd.Run(); err != nil {
cmd.DumpLog(ctx)
return err
}
return nil
}
// setupOwnerKey creates /var/lib/devicesettings/owner.key file with given DER.
func setupOwnerKey(der []byte) error {
// Ensure parent dir exists.
const devicesettingsDir = "/var/lib/devicesettings"
if _, err := os.Stat(devicesettingsDir); err != nil {
if !os.IsNotExist(err) {
return errors.Wrapf(err, "failed to stat %s", devicesettingsDir)
}
return errors.Wrapf(err, "directory for owner key does not exists %s", devicesettingsDir)
}
ownerKeyPath := filepath.Join(devicesettingsDir, "owner.key")
if err := ioutil.WriteFile(ownerKeyPath, der, 0604); err != nil {
return errors.Wrapf(err, "failed to write to %s", ownerKeyPath)
}
return nil
}
// sign signs the blob with the given key, and returns the signature.
func sign(key *rsa.PrivateKey, blob []byte) ([]byte, error) {
h := sha1.New()
h.Write(blob)
digest := h.Sum(nil)
return rsa.SignPKCS1v15(nil, key, crypto.SHA1, digest)
}
func OwnershipAPI(ctx context.Context, s *testing.State) {
const (
testUser = "ownership_test@chromium.org"
testPass = "testme"
)
p12Path := s.DataPath("testcert.p12")
privKey, err := session.ExtractPrivKey(p12Path)
if err != nil {
s.Fatal("Failed to parse PKCS #12 file: ", err)
}
if err := deviceSetUp(ctx, testUser, testPass, p12Path, &privKey.PublicKey); err != nil {
s.Fatal("Failed to reset device ownership: ", err)
}
sm, err := session.NewSessionManager(ctx)
if err != nil {
s.Fatal("Failed to create session_manager binding: ", err)
}
if err := session.PrepareChromeForPolicyTesting(ctx, sm); err != nil {
s.Fatal("Failed to prepare Chrome for testing: ", err)
}
if err = sm.StartSession(ctx, testUser, ""); err != nil {
s.Fatalf("Failed to start new session for %s: %v", testUser, err)
}
settings := ownership.BuildTestSettings(testUser)
if err := session.StoreSettings(ctx, sm, testUser, privKey, nil, settings); err != nil {
s.Fatal("Failed to store settings data: ", err)
}
// Fetch the data from the session_manager.
ret, err := session.RetrieveSettings(ctx, sm)
if err != nil {
s.Fatal("Failed to retrieve settings: ", err)
}
// Verify that there's no diff between sent data and fetched data.
if diff := cmp.ProtoDiff(settings, ret); diff != "" {
const diffName = "diff.txt"
if err = ioutil.WriteFile(filepath.Join(s.OutDir(), diffName), []byte(diff), 0644); err != nil {
s.Error("Failed to write diff: ", err)
}
s.Error("Sent data and fetched data has diff, which is found in ", diffName)
}
}