xamarin-macios/tests/monotouch-test/Security/KeyChainTest.cs

237 строки
7.5 KiB
C#

// Copyright 2011, 2013 Xamarin Inc. All rights reserved
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.IO;
using System.Reflection;
using CoreFoundation;
using Foundation;
using Security;
using ObjCRuntime;
using NUnit.Framework;
using Xamarin.Utils;
namespace MonoTouchFixtures.Security {
[TestFixture]
[Preserve (AllMembers = true)]
public class KeyChainTest {
[DllImport (Constants.CoreFoundationLibrary)]
extern static int CFGetRetainCount (IntPtr handle);
[Test]
#if __MACCATALYST__
[Ignore ("This test requires an app signed with the keychain-access-groups entitlement, and for Mac Catalyst that requires a custom provisioning profile.")]
#endif
public void Add_Certificate ()
{
#if MONOMAC && !NET
Stream certStream = typeof (KeyChainTest).Assembly.GetManifestResourceStream ("xammac_tests.Security.openssl_crt.der");
#else
Stream certStream = typeof (KeyChainTest).Assembly.GetManifestResourceStream ("monotouchtest.Security.openssl_crt.der");
#endif
NSData data = NSData.FromStream (certStream);
var query = RecordTest.CreateSecRecord (SecKind.Certificate,
label: $"Internet Widgits Pty Ltd"
);
var rec = query.Clone ();
rec.SetValueRef (new SecCertificate (data));
try {
// delete any existing certificates first.
SecKeyChain.Remove (query);
// add the new certificate
var rc = SecKeyChain.Add (rec);
Assert.That (rc, Is.EqualTo (SecStatusCode.Success), "Add_Certificate");
} finally {
// clean up after ourselves
SecKeyChain.Remove (query);
}
}
#if !MONOMAC // No QueryAsConcreteType on Mac
[Test]
#if __MACCATALYST__
[Ignore ("This test requires an app signed with the keychain-access-groups entitlement, and for Mac Catalyst that requires a custom provisioning profile.")]
#endif
public void AddQueryRemove_Identity ()
{
if (TestRuntime.CheckXcodeVersion (13, 0))
Assert.Ignore ("code == errSecInternal (-26276)");
using (var rec = RecordTest.CreateSecRecord (SecKind.Identity))
using (var id = IdentityTest.GetIdentity ()) {
rec.SetValueRef (id);
SecStatusCode code = SecKeyChain.Add (rec);
Assert.That (code, Is.EqualTo (SecStatusCode.DuplicateItem).Or.EqualTo (SecStatusCode.Success), "code");
}
if (!TestRuntime.CheckXcodeVersion (5, 0))
Assert.Inconclusive ("QueryAsConcreteType does not work before iOS7");
using (var rec = RecordTest.CreateSecRecord (SecKind.Identity)) {
SecStatusCode code;
var match = SecKeyChain.QueryAsConcreteType (rec, out code);
if ((match is null) && (code == SecStatusCode.ItemNotFound))
Assert.Inconclusive ("Test randomly fails (race condition between addtion/commit/query?");
Assert.That (code, Is.EqualTo (SecStatusCode.Success), "QueryAsRecord-2");
Assert.NotNull (match, "match-2");
code = SecKeyChain.Remove (rec);
Assert.That (code, Is.EqualTo (SecStatusCode.Success), "Remove");
match = SecKeyChain.QueryAsConcreteType (rec, out code);
Assert.That (code, Is.EqualTo (SecStatusCode.ItemNotFound), "QueryAsRecord-3");
Assert.Null (match, "match-3");
}
}
#endif
[DllImport ("/System/Library/Frameworks/Security.framework/Security")]
internal extern static SecStatusCode SecItemAdd (IntPtr cfDictRef, IntPtr result);
[Test]
// same as Add_Identity but directly p/invoking - shows that the type MUST NOT be included for Identity
#if __MACCATALYST__
[Ignore ("This test requires an app signed with the keychain-access-groups entitlement, and for Mac Catalyst that requires a custom provisioning profile.")]
#endif
public void SecItemAdd_Identity ()
{
using (NSString valueref = new NSString ("v_Ref"))
using (NSMutableDictionary data = new NSMutableDictionary ())
using (var id = IdentityTest.GetIdentity ()) {
data.LowlevelSetObject (id.Handle, valueref.Handle);
SecStatusCode code = SecItemAdd (data.Handle, IntPtr.Zero);
var expected = Is.EqualTo (SecStatusCode.DuplicateItem).Or.EqualTo (SecStatusCode.Success);
#if __MACOS__
if (!TestRuntime.CheckSystemVersion (ApplePlatform.MacOSX, 10, 9))
expected = Is.EqualTo (SecStatusCode.Param);
#endif
Assert.That (code, expected);
}
}
string uniqueString;
string UniqueString {
get {
if (uniqueString is null)
uniqueString = $"{CFBundle.GetMain ().Identifier}-{GetType ().FullName}-{Process.GetCurrentProcess ().Id}";
return uniqueString;
}
}
string RecordLabel {
get {
return $"{UniqueString}-Label";
}
}
// The uniqueness of GenericPassword entries are based on Service+Account (only).
// Here we have a per-process + per-test unique Service value,
// and a constant Account value (which makes it easier to find all entries if need be).
string RecordService {
get {
return $"{UniqueString}-Service";
}
}
string RecordAccount {
get {
return $"XAMARIN_KEYCHAIN_ACCOUNT";
}
}
Guid GetID ()
{
SecStatusCode code;
var queryRec = RecordTest.CreateSecRecord (SecKind.GenericPassword,
service: RecordService,
account: RecordAccount
);
var queryResponse = SecKeyChain.QueryAsRecord (queryRec, out code);
if (code == SecStatusCode.Success && queryResponse?.Generic is not null)
return new Guid (NSString.FromData (queryResponse.Generic, NSStringEncoding.UTF8));
return Guid.Empty;
}
[Test]
public void QueryAsData ()
{
SecStatusCode code;
var queryRec = RecordTest.CreateSecRecord (SecKind.GenericPassword,
service: RecordService,
account: RecordAccount
);
var data = SecKeyChain.QueryAsData (queryRec, true, out code);
if (code == SecStatusCode.Success && queryRec is not null) {
Assert.NotNull (data.Bytes);
}
}
[Test]
public void QueryAsDataArray ()
{
SecStatusCode code;
var queryRec = RecordTest.CreateSecRecord (SecKind.GenericPassword,
service: RecordService,
account: RecordAccount
);
var data = SecKeyChain.QueryAsData (queryRec, true, 1, out code);
if (code == SecStatusCode.Success && queryRec is not null) {
Assert.NotNull (data [0].Bytes);
}
}
SecStatusCode RemoveID ()
{
var queryRec = RecordTest.CreateSecRecord (SecKind.GenericPassword,
service: RecordService,
account: RecordAccount
);
return SecKeyChain.Remove (queryRec);
}
SecStatusCode SetID (Guid setID)
{
var queryRec = RecordTest.CreateSecRecord (SecKind.GenericPassword,
service: RecordService,
account: RecordAccount
);
var record = queryRec.Clone ();
record.Generic = NSData.FromString (Convert.ToString (setID), NSStringEncoding.UTF8);
record.Accessible = SecAccessible.Always;
record.Label = RecordLabel;
SecStatusCode code = SecKeyChain.Add (record);
if (code == SecStatusCode.DuplicateItem) {
code = RemoveID ();
if (code == SecStatusCode.Success)
code = SecKeyChain.Add (record);
}
return code;
}
[Test]
#if __MACCATALYST__
[Ignore ("This test requires an app signed with the keychain-access-groups entitlement, and for Mac Catalyst that requires a custom provisioning profile.")]
#endif
public void CheckId ()
{
TestRuntime.AssertXcodeVersion (5, 1); // macOS 10.9
// test case from http://stackoverflow.com/questions/9481860/monotouch-cant-get-value-of-existing-keychain-item
// not a bug (no class lib fix) just a misuse of the API wrt status codes
Guid g = Guid.NewGuid ();
try {
Assert.That (SetID (g), Is.EqualTo (SecStatusCode.Success), "success");
Assert.That (GetID (), Is.EqualTo (g), "same guid");
} finally {
RemoveID ();
}
}
}
}